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