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