1import enum
2import math
3import re
4import requests
5
6
7class RedfishHttpStatus(enum.IntEnum):
8    ok = 200
9    created = 201
10    no_content = 204
11    bad_request = 400
12    not_found = 404
13    internal_server_error = 500
14
15
16class RedfishRequest:
17    telemetry_service_path = '/redfish/v1/TelemetryService'
18    metric_definition_path = f'{telemetry_service_path}/MetricDefinitions'
19    metric_report_definition_path = \
20        f'{telemetry_service_path}/MetricReportDefinitions'
21    metric_report_path = f'{telemetry_service_path}/MetricReports'
22
23    def __init__(self, host_addr, username, password):
24        self.host_addr = host_addr
25        self.username = username
26        self.password = password
27
28    def get(self, path, code=RedfishHttpStatus.ok):
29        u = self.host_addr + path
30        r = requests.get(u, auth=(self.username, self.password), verify=False)
31        assert r.status_code == code, \
32            f'{r.status_code} == {code} on path {u}\n{r.text}'
33        print(r.text)
34        return r.json()
35
36    def post(self, path, body, code=RedfishHttpStatus.created):
37        u = self.host_addr + path
38        r = requests.post(u, auth=(self.username, self.password), verify=False,
39                          json=body)
40        assert r.status_code == code, \
41            f'{r.status_code} == {code} on path {u}\n{r.text}'
42        print(r.text)
43        return r.json()
44
45    def delete(self, path, code=RedfishHttpStatus.no_content):
46        u = self.host_addr + path
47        r = requests.delete(u, auth=(self.username, self.password),
48                            verify=False)
49        assert r.status_code == code, \
50            f'{r.status_code} == {code} on path {u}\n{r.text}'
51
52
53class TelemetryService:
54    def __init__(self, redfish, metric_limit):
55        r = redfish.get(redfish.telemetry_service_path)
56        self.min_interval = Duration.to_seconds(r['MinCollectionInterval'])
57        self.max_reports = r['MaxReports']
58        self.metrics = []
59        r = redfish.get(redfish.metric_definition_path)
60        for m in r['Members']:
61            path = m['@odata.id']
62            metricDef = redfish.get(path)
63            self.metrics += [x for x in metricDef['MetricProperties']]
64        self.metrics = self.metrics[:metric_limit]
65
66
67class ReportDef:
68    def __init__(self, redfish):
69        self.redfish = redfish
70
71    def get_collection(self):
72        r = self.redfish.get(self.redfish.metric_report_definition_path)
73        return [x['@odata.id'] for x in r['Members']]
74
75    def add_report(self, id, metrics=None, type='OnRequest', actions=None,
76                   interval=None, code=RedfishHttpStatus.created):
77        body = {
78            'Id': id,
79            'Metrics': [],
80            'MetricReportDefinitionType': type,
81            'ReportActions': ['RedfishEvent', 'LogToMetricReportsCollection']
82        }
83        if metrics is not None:
84            body['Metrics'] = metrics
85        if actions is not None:
86            body['ReportActions'] = actions
87        if interval is not None:
88            body['Schedule'] = {'RecurrenceInterval': interval}
89        return self.redfish.post(self.redfish.metric_report_definition_path,
90                                 body, code)
91
92    def delete_report(self, path):
93        self.redfish.delete(f'{path}')
94
95
96class Duration:
97    def __init__(self):
98        pass
99
100    def to_iso8061(time):
101        assert time >= 0, 'Invalid argument, time is negative'
102        days = int(time / (24 * 60 * 60))
103        time = math.fmod(time, (24 * 60 * 60))
104        hours = int(time / (60 * 60))
105        time = math.fmod(time, (60 * 60))
106        minutes = int(time / 60)
107        time = round(math.fmod(time, 60), 3)
108        return f'P{str(days)}DT{str(hours)}H{str(minutes)}M{str(time)}S'
109
110    def to_seconds(duration):
111        r = re.fullmatch(r'-?P(\d+D)?(T(\d+H)?(\d+M)?(\d+(.\d+)?S)?)?',
112                         duration)
113        assert r, 'Invalid argument, not match with regex'
114        days = r.group(1)
115        hours = r.group(3)
116        minutes = r.group(4)
117        seconds = r.group(5)
118        result = 0
119        if days is not None:
120            result += int(days[:-1]) * 60 * 60 * 24
121        if hours is not None:
122            result += int(hours[:-1]) * 60 * 60
123        if minutes is not None:
124            result += int(minutes[:-1]) * 60
125        if seconds is not None:
126            result += float(seconds[:-1])
127        return result
128