import socket
import struct
import sys
class RfSSDPServer():
def addSearchTarget(self, target):
self.searchtargets.append(target)
def __init__(self, root, location, ip=None, port=1900, timeout=5):
ip = ip if ip is not None else "0.0.0.0"
self.searchtargets = ['ssdp:all', 'upnp:rootdevice', 'urn:dmtf-org:service:redfish-rest:1']
self.ip, self.port = ip, port
self.timeout = timeout
self.location = location
self.UUID = root.get('UUID', 'nouuid')
self.cachecontrol = 1800
myVersion = root.get('RedfishVersion', '1.0.0')
self.major, self.minor, self.errata = tuple(myVersion.split('.'))
self.addSearchTarget('urn:dmtf-org:service:redfish-rest:1:{}'.format(self.minor))
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.settimeout(timeout)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton('239.255.255.250') + struct.pack(b"@I", socket.INADDR_ANY))
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
sock.bind(('', port))
self.sock = sock
logger.info('SSDP Server Created')
def start(self):
logger.info('SSDP Server Running...')
countTimeout = pcount = 0
while True:
try:
if countTimeout % 5 == 0:
logger.info('Ssdp Poll... {} pings'.format(pcount))
pcount = 0
countTimeout = 1
data, addr = self.sock.recvfrom(1024)
pcount += 1
self.check(data, addr)
except socket.timeout:
countTimeout += 1
continue
except Exception as e:
logger.info('error occurred ' + str(e))
pass
pass
def check(self, data, addr):
logger.info('SSDP Packet received from {}'.format(addr))
decoded = data.decode().replace('\r', '').split('\n')
msgtype, decoded = decoded[0], decoded[1:]
decodeddict = {x.split(':', 1)[0].upper(): x.split(':', 1)[1].strip(' ') for x in decoded if x != ''}
if 'M-SEARCH' in msgtype:
st = decodeddict.get('ST')
if st in self.searchtargets:
response = ['HTTP/1.1 200 OK',
'CACHE-CONTROL: max-age={}'.format(self.cachecontrol),
'ST:urn:dmtf-org:service:redfish-rest:1:{}'.format(self.minor),
'USN:uuid:{}::urn:dmtf-org:service:redfish-rest:1:{}'.format(self.UUID, self.minor),
'AL:{}'.format(self.location),
'EXT:']
response.extend(('', ''))
response = '\r\n'.join(response)
self.sock.sendto(response.encode(), addr)
logger.info('SSDP Packet sent to {}'.format(addr))
import re
import sys
import argparse
import time
import collections.abc
import json
import threading
import datetime
import signal
import grequests
import os
import ssl
import logging
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, urlunparse, parse_qs
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO)
logger.addHandler(ch)
tool_version = "1.2.8"
dont_send = ["connection", "keep-alive", "content-length", "transfer-encoding"]
def dict_merge(dct, merge_dct):
for k in merge_dct:
if k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], collections.abc.Mapping):
dict_merge(dct[k], merge_dct[k])
else:
dct[k] = merge_dct[k]
def clean_path(path, isShort):
path = path.strip("/")
path = path.split("?", 1)[0]
path = path.split("#", 1)[0]
if isShort:
path = path.replace("redfish/v1", "").strip("/")
return path
class RfMockupServer(BaseHTTPRequestHandler):
patchedLinks = dict()
def construct_path(self, path, filename):
apath = self.server.mockDir
rpath = clean_path(path, self.server.shortForm)
return "/".join([apath, rpath, filename]) if filename not in ["", None] else "/".join([apath, rpath])
def get_cached_link(self, path):
if path not in self.patchedLinks:
if os.path.isfile(path):
with open(path) as f:
jsonData = json.load(f)
f.close()
else:
jsonData = None
else:
jsonData = self.patchedLinks[path]
return jsonData is not None and jsonData != "404", jsonData
def try_to_sleep(self, method, path):
if self.server.timefromJson:
responseTime = self.getResponseTime(method, path)
try:
time.sleep(float(responseTime))
except ValueError:
logger.info("Time is not a float value. Sleeping with default response time")
time.sleep(float(self.server.responseTime))
else:
time.sleep(float(self.server.responseTime))
def send_header_file(self, fpath):
with open(fpath) as headers_data:
d = json.load(headers_data)
if isinstance(d.get("GET"), dict):
for k, v in d["GET"].items():
if k.lower() not in dont_send:
self.send_header(k, v)
def send_response_file(self, fpath):
with open(fpath) as response_data:
d = json.load(response_data)
if isinstance(d.get("status"), int):
self.send_response(d.get("status"))
else:
self.send_response(200)
if isinstance(d.get("headers"), dict):
for k, v in d["headers"].items():
if k.lower() not in dont_send:
self.send_header(k, v)
body = d.get("body")
self.send_header("Content-Length", len(body))
self.end_headers()
self.wfile.write(bytes(body, "utf8"))
def add_new_member(self, payload, data_received):
members = payload.get("Members")
n = 1
if len(members):
member_id = members[0].get("@odata.id").replace(self.path, "").strip("/")
pattern = re.sub(r"\d+$", "{id}", member_id)
else:
pattern = "Member{id}"
pattern = pattern if re.search(r"\{id\}$", pattern) else pattern + "{id}"
newpath_id = data_received.get("Id", pattern.format(id=n))
if data_received.get("Id") in [m.get("@odata.id").replace(self.path, "").strip("/") for m in members]:
newpath_id = pattern.format(id=n)
newpath = "/".join([self.path, newpath_id])
while newpath in [m.get("@odata.id") for m in members]:
n = n + 1
newpath_id = pattern.format(id=n)
newpath = "/".join([self.path, newpath_id])
members.append({"@odata.id": newpath})
data_received["@odata.id"] = newpath
data_received["Id"] = newpath_id
payload["Members"] = members
payload["Members@odata.count"] = len(members)
return newpath
def handle_eventing(self, data_received):
sub_path = self.construct_path("/redfish/v1/EventService/Subscriptions", "index.json")
success, sub_payload = self.get_cached_link(sub_path)
logger.info(sub_path)
if not success:
return 404
else:
if (
("EventType" not in data_received)
or ("EventId" not in data_received)
or ("EventTimestamp" not in data_received)
or ("Severity" not in data_received)
or ("Message" not in data_received)
or ("MessageId" not in data_received)
or ("MessageArgs" not in data_received)
or ("OriginOfCondition" not in data_received)
):
return 400
else:
origin_of_cond = data_received["OriginOfCondition"]
data_received["OriginOfCondition"] = {}
data_received["OriginOfCondition"]["@odata.id"] = origin_of_cond
event_payload = {}
event_payload["@odata.type"] = "#Event.v1_2_1.Event"
event_payload["Name"] = "Test Event"
event_payload["Id"] = str(self.event_id)
event_payload["Events"] = []
event_payload["Events"].append(data_received)
events = []
for member in sub_payload.get("Members", []):
entry = member["@odata.id"]
entrypath = self.construct_path(entry, "index.json")
success, subscription = self.get_cached_link(entrypath)
if not success:
logger.info("No such resource")
else:
if ("Destination" in subscription) and ("EventTypes" in subscription):
logger.info(("Target", subscription["Destination"]))
logger.info((data_received["EventType"], subscription["EventTypes"]))
if data_received["EventType"] in subscription["EventTypes"]:
http_headers = {}
http_headers["Content-Type"] = "application/json"
event_payload["Context"] = subscription.get("Context", "Default Context")
events.append(
grequests.post(
subscription["Destination"], timeout=20, data=json.dumps(event_payload), headers=http_headers
)
)
else:
logger.info("event not in eventtypes")
try:
threading.Thread(target=grequests.map, args=(events,)).start()
except Exception as e:
logger.info("post error {}".format(str(e)))
return 204
self.event_id = self.event_id + 1
def handle_telemetry(self, data_received):
sub_path = self.construct_path("/redfish/v1/EventService/Subscriptions", "index.json")
success, sub_payload = self.get_cached_link(sub_path)
logger.info(sub_path)
if not success:
return 404
else:
if (
(("MetricReportName" in data_received) and ("MetricReportValues" in data_received))
or (("MetricReportName" in data_received) and ("GeneratedMetricReportValues" in data_received))
or (("MetricName" in data_received) and ("MetricValues" in data_received))
):
expected_keys = ["MetricId", "MetricValue", "Timestamp", "MetricProperty", "MetricDefinition"]
my_name = data_received.get("MetricName", data_received.get("MetricReportName"))
my_data = data_received.get(
"MetricValues", data_received.get("MetricReportValues", data_received.get("GeneratedMetricReportValues"))
)
event_payload = {}
value_list = []
event_payload["@odata.context"] = "/redfish/v1/$metadata#MetricReport.MetricReport"
event_payload["@odata.type"] = "#MetricReport.v1_0_0.MetricReport"
event_payload["@odata.id"] = "/redfish/v1/TelemetryService/MetricReports/" + my_name
event_payload["Id"] = my_name
event_payload["Name"] = my_name
event_payload["MetricReportDefinition"] = {"@odata.id": "/redfish/v1/TelemetryService/MetricReportDefinitions/" + my_name}
now = datetime.datetime.now()
event_payload["Timestamp"] = now.strftime("%Y-%m-%dT%H:%M:%S") + ("-%02d" % (now.microsecond / 10000))
for tup in my_data:
if all(x in tup for x in expected_keys):
value_list.append(tup)
event_payload["MetricValues"] = value_list
logger.info(event_payload)
event_fpath = self.construct_path(event_payload["@odata.id"], "index.json")
self.patchedLinks[event_fpath] = event_payload
report_path = "/redfish/v1/TelemetryService/MetricReports"
report_path = self.construct_path(report_path, "index.json")
success, collection_payload = self.get_cached_link(report_path)
if not success:
collection_payload = {"Members": []}
collection_payload["@odata.context"] = "/redfish/v1/$metadata#MetricReportCollection.MetricReportCollection"
collection_payload["@odata.type"] = "#MetricReportCollection.v1_0_0.MetricReportCollection"
collection_payload["@odata.id"] = "/redfish/v1/TelemetryService/MetricReports"
collection_payload["Name"] = "MetricReports"
if event_payload["@odata.id"] not in [member.get("@odata.id") for member in collection_payload["Members"]]:
collection_payload["Members"].append({"@odata.id": event_payload["@odata.id"]})
collection_payload["Members@odata.count"] = len(collection_payload["Members"])
self.patchedLinks[report_path] = collection_payload
events = []
for member in sub_payload.get("Members", []):
entry = member["@odata.id"]
entrypath = self.construct_path(entry, "index.json")
success, subscription = self.get_cached_link(entrypath)
if not success:
logger.info("No such resource")
else:
if ("Destination" in subscription) and ("EventTypes" in subscription):
logger.info(("Target", subscription["Destination"]))
http_headers = {}
http_headers["Content-Type"] = "application/json"
events.append(
grequests.post(
subscription["Destination"], timeout=20, data=json.dumps(event_payload), headers=http_headers
)
)
else:
logger.info("event not in eventtypes")
try:
threading.Thread(target=grequests.map, args=(events,)).start()
except Exception as e:
logger.info("post error {}".format(str(e)))
self.event_id = self.event_id + 1
return 204
else:
return 400
server_version = "RedfishMockupHTTPD_v" + tool_version
event_id = 1
def __check_if_dict_is_odataid_only(self, odata_id_dict):
if "@odata.id" in odata_id_dict and len(odata_id_dict) == 1:
return odata_id_dict["@odata.id"]
return None
def handle_expand_query(self, data, expand_type, levels):
stack = [(data, levels)]
while stack:
current_data, expand_level = stack.pop()
if expand_level < 1:
continue
for key, value in current_data.items():
if not isinstance(value, list) and not isinstance(value, dict):
continue
if expand_type == "." and key == "Links":
continue
if isinstance(value, dict):
expanded = False
odata_id = self.__check_if_dict_is_odataid_only(value)
if odata_id:
path = self.construct_path(odata_id, "index.json")
res, response_data = self.get_cached_link(path)
if res:
response_data.pop("@Redfish.Copyright", None)
current_data[key] = response_data
expanded = True
remove_level = 1 if expanded else 0
stack.append((current_data[key], expand_level - remove_level))
else:
for index, array_item in enumerate(value):
if isinstance(array_item, dict):
expanded = False
odata_id = self.__check_if_dict_is_odataid_only(array_item)
if odata_id:
path = self.construct_path(odata_id, "index.json")
res, response_data = self.get_cached_link(path)
if res:
response_data.pop("@Redfish.Copyright", None)
value[index] = response_data
expanded = True
remove_level = 1 if expanded else 0
stack.append((value[index], expand_level - remove_level))
current_data[key] = value
def do_HEAD(self):
logger.info("Headers: ")
logger.info(self.server.headers)
fpath = self.construct_path(self.path, "index.json")
fpath_xml = self.construct_path(self.path, "index.xml")
fpath_headers = self.construct_path(self.path, "headers.json")
fpath_direct = self.construct_path(self.path, "")
if self.server.headers and (os.path.isfile(fpath_headers)):
self.send_response(200)
self.send_header_file(fpath_headers)
elif (self.server.headers is False) or (os.path.isfile(fpath_headers) is False):
if self.get_cached_link(fpath)[0]:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("OData-Version", "4.0")
elif os.path.isfile(fpath_xml) or os.path.isfile(fpath_direct):
if os.path.isfile(fpath_xml):
file_extension = "xml"
elif os.path.isfile(fpath_direct):
filename, file_extension = os.path.splitext(fpath_direct)
file_extension = file_extension.strip(".")
self.send_response(200)
self.send_header("Content-Type", "application/" + file_extension + ";charset=utf-8")
self.send_header("OData-Version", "4.0")
else:
self.send_response(404)
else:
self.send_response(404)
self.end_headers()
def do_GET(self):
logger.info(("GET", self.path))
logger.info(" GET: Headers: {}".format(self.headers))
fpath = self.construct_path(self.path, "index.json")
fpath_xml = self.construct_path(self.path, "index.xml")
fpath_headers = self.construct_path(self.path, "headers.json")
fpath_custom = self.construct_path(self.path, "custom.json")
fpath_direct = self.construct_path(self.path, "")
success, payload = self.get_cached_link(fpath)
scheme, netloc, path, params, query, fragment = urlparse(self.path)
query_pieces = parse_qs(query, keep_blank_values=True)
self.try_to_sleep("GET", self.path)
if os.path.isfile(fpath_custom):
self.send_response_file(fpath_custom)
elif self.path == "/" and self.server.shortForm:
self.send_response(404)
self.end_headers()
elif self.path in ["/redfish", "/redfish/"] and self.server.shortForm:
self.send_response(200)
if self.server.headers and (os.path.isfile(fpath_headers)):
self.send_header_file(fpath_headers)
else:
self.send_header("Content-Type", "application/json")
self.send_header("OData-Version", "4.0")
encoded_data = json.dumps({"v1": "/redfish/v1"}, indent=4).encode()
if not (self.server.headers and (os.path.isfile(fpath_headers))):
self.send_header("Content-Length", len(encoded_data))
self.end_headers()
self.wfile.write(encoded_data)
elif success:
self.send_response(200)
if self.server.headers and (os.path.isfile(fpath_headers)):
self.send_header_file(fpath_headers)
else:
self.send_header("Content-Type", "application/json")
self.send_header("OData-Version", "4.0")
output_data = payload
output_data.pop("@Redfish.Copyright", None)
if "EventService/Subscriptions" in self.path:
if output_data.get("HttpHeaders") is not None:
output_data["HttpHeaders"] = []
if output_data.get("Members") is not None:
my_members = output_data["Members"]
top_count = int(query_pieces.get("$top", [str(len(my_members))])[0])
top_skip = int(query_pieces.get("$skip", ["0"])[0])
my_members = my_members[top_skip:]
if top_count < len(my_members):
my_members = my_members[:top_count]
query_out = {"$skip": top_skip + top_count, "$top": top_count}
query_string = "&".join(["{}={}".format(k, v) for k, v in query_out.items()])
output_data["Members@odata.nextLink"] = urlunparse(("", "", path, "", query_string, ""))
else:
pass
output_data["Members"] = my_members
pass
expand_str = query_pieces.get("$expand", [""])[0]
expand = re.match("([\\.~\\*])(\\(\\$levels=(\\d+)\\))?", expand_str)
if expand:
regex_groups = expand.groups()
expand_type = regex_groups[0]
levels = int(regex_groups[2]) if regex_groups[1] else 1
self.handle_expand_query(output_data, expand_type, levels)
encoded_data = json.dumps(output_data, sort_keys=True, indent=4, separators=(",", ": ")).encode()
if not (self.server.headers and (os.path.isfile(fpath_headers))):
self.send_header("Content-Length", len(encoded_data))
self.end_headers()
self.wfile.write(encoded_data)
elif os.path.isfile(fpath_xml):
f = open(fpath_xml, "r")
self.send_response(200)
self.send_header("Content-Type", "application/xml;charset=utf-8")
self.send_header("OData-Version", "4.0")
self.end_headers()
self.wfile.write(f.read().encode())
f.close()
elif os.path.isfile(fpath_direct):
self.send_response(200)
with open(fpath_direct, "rb") as f:
content = f.read()
try:
decoded_content = content.decode()
except ValueError:
self.send_header("Content-Type", "application/octet-stream")
self.send_header("OData-Version", "4.0")
self.end_headers()
self.wfile.write(content)
else:
file_extension = os.path.splitext(fpath_direct)[1]
if file_extension == "":
mime_type = "text/plain"
else:
mime_type = "application/" + file_extension[1:]
self.send_header("Content-Type", mime_type + ";charset=utf-8")
self.send_header("OData-Version", "4.0")
self.end_headers()
self.wfile.write(decoded_content.encode("utf-8"))
else:
self.send_response(404)
self.end_headers()
def do_PATCH(self):
logger.info(" PATCH: Headers: {}".format(self.headers))
self.try_to_sleep("PATCH", self.path)
if "content-length" in self.headers:
lenn = int(self.headers["content-length"])
try:
data_received = json.loads(self.rfile.read(lenn).decode("utf-8"))
except ValueError:
print("Decoding JSON has failed, sending 400")
data_received = None
if data_received:
logger.info(" PATCH: Data: {}".format(data_received))
fpath = self.construct_path(self.path, "index.json")
success, payload = self.get_cached_link(fpath)
if success:
if payload.get("Members") is not None:
self.send_response(405)
else:
logger.info(self.headers.get("content-type"))
logger.info(data_received)
logger.info(payload)
dict_merge(payload, data_received)
logger.info(payload)
self.patchedLinks[fpath] = payload
self.send_response(204)
else:
self.send_response(404)
else:
self.send_response(400)
self.end_headers()
def do_PUT(self):
logger.info(" PUT: Headers: {}".format(self.headers))
self.try_to_sleep("PUT", self.path)
if "content-length" in self.headers:
lenn = int(self.headers["content-length"])
try:
data_received = json.loads(self.rfile.read(lenn).decode("utf-8"))
except ValueError:
print("Decoding JSON has failed, sending 400")
data_received = None
logger.info(" PUT: Data: {}".format(data_received))
self.send_response(405)
self.end_headers()
def do_POST(self):
logger.info(" POST: Headers: {}".format(self.headers))
if "content-length" in self.headers:
lenn = int(self.headers["content-length"])
if lenn == 0:
data_received = {}
else:
try:
data_received = json.loads(self.rfile.read(lenn).decode("utf-8"))
except ValueError:
print("Decoding JSON has failed, sending 400")
data_received = None
else:
self.send_response(411)
self.end_headers()
return
self.try_to_sleep("POST", self.path)
if data_received is not None:
logger.info(" POST: Data: {}".format(data_received))
fpath = self.construct_path(self.path, "index.json")
success, payload = self.get_cached_link(fpath)
if success:
if payload.get("Members") is None:
self.send_response(405)
else:
logger.info(data_received)
logger.info(type(data_received))
newpath = self.add_new_member(payload, data_received)
newfpath = self.construct_path(newpath, "index.json")
logger.info(newfpath)
self.patchedLinks[newfpath] = data_received
self.patchedLinks[fpath] = payload
self.send_response(204)
self.send_header("Location", newpath)
self.send_header("Content-Length", "0")
if "SessionService/Sessions" in self.path:
self.send_header("X-Auth-Token", "1234567890ABCDEF")
self.end_headers()
else:
if "EventService/Actions/EventService.SubmitTestEvent" in self.path:
r_code = self.handle_eventing(data_received)
self.send_response(r_code)
elif "TelemetryService/Actions/TelemetryService.SubmitTestMetricReport" in self.path:
r_code = self.handle_telemetry(data_received)
self.send_response(r_code)
elif "/Actions/" in self.path:
fpath = self.construct_path(self.path.split("/Actions/", 1)[0], "index.json")
success, payload = self.get_cached_link(fpath)
if success:
action_found = False
try:
for action in payload["Actions"]:
if action == "Oem":
for oem_action in payload["Actions"][action]:
if payload["Actions"][action][oem_action]["target"] == self.path:
action_found = True
else:
if payload["Actions"][action]["target"] == self.path:
action_found = True
except Exception:
pass
if action_found:
self.send_response(204)
else:
self.send_response(404)
else:
self.send_response(404)
else:
self.send_response(404)
else:
self.send_response(400)
self.end_headers()
def do_DELETE(self):
logger.info("DELETE: Headers: {}".format(self.headers))
self.try_to_sleep("DELETE", self.path)
fpath = self.construct_path(self.path, "index.json")
ppath = "/".join(self.path.split("/")[:-1])
parent_path = self.construct_path(ppath, "index.json")
success, payload = self.get_cached_link(fpath)
if success:
success, parentData = self.get_cached_link(parent_path)
if success and parentData.get("Members") is not None:
self.patchedLinks[fpath] = "404"
parentData["Members"] = [x for x in parentData["Members"] if not x["@odata.id"] == self.path]
parentData["Members@odata.count"] = len(parentData["Members"])
self.patchedLinks[parent_path] = parentData
self.send_response(204)
else:
self.send_response(405)
else:
self.send_response(404)
self.end_headers()
def getResponseTime(self, method, path):
fpath = self.construct_path(path, "time.json")
success, item = self.get_cached_link(path)
if not any(x in method for x in ("GET", "HEAD", "POST", "PATCH", "DELETE")):
logger.info("Not a valid method")
return 0
if os.path.isfile(fpath):
with open(fpath) as time_data:
d = json.load(time_data)
time_str = method + "_Time"
if time_str in d:
try:
float(d[time_str])
except Exception:
logger.info("Time in the json file, not a float/int value. Reading the default time.")
return self.server.responseTime
return float(d[time_str])
else:
logger.info(("response time:", self.server.responseTime))
return self.server.responseTime
def main():
logger.info("Redfish Mockup Server, version {}".format(tool_version))
parser = argparse.ArgumentParser(description="Serve a static Redfish mockup.")
parser.add_argument("-H", "--host", "--Host", default="127.0.0.1", help="hostname or IP address (default 127.0.0.1)")
parser.add_argument("-p", "--port", "--Port", default=8000, type=int, help="host port (default 8000)")
parser.add_argument("-D", "--dir", "--Dir", help="path to mockup dir (may be relative to CWD)")
parser.add_argument("-E", "--test-etag", "--TestEtag", action="store_true", help="(unimplemented) etag testing")
parser.add_argument("-X", "--headers", action="store_true", help="load headers from headers.json files in mockup")
parser.add_argument("-t", "--time", default=0, help="delay in seconds added to responses (float or int)")
parser.add_argument("-T", action="store_true", help="delay response based on times in time.json files in mockup")
parser.add_argument("-s", "--ssl", action="store_true", help="place server in SSL (HTTPS) mode; requires a cert and key")
parser.add_argument("--cert", help="the certificate for SSL")
parser.add_argument("--key", help="the key for SSL")
parser.add_argument("-S", "--short-form", "--shortForm", action="store_true", help="apply short form to mockup (omit filepath /redfish/v1)")
parser.add_argument("-P", "--ssdp", action="store_true", help="make mockup SSDP discoverable")
args = parser.parse_args()
hostname = args.host
port = args.port
mockDirPath = args.dir
testEtagFlag = args.test_etag
headers = args.headers
responseTime = args.time
timefromJson = args.T
sslMode = args.ssl
sslCert = args.cert
sslKey = args.key
shortForm = args.short_form
ssdpStart = args.ssdp
if mockDirPath is None:
mockDirPath = "public-rackmount1"
shortForm = True
logger.info("Hostname: {}".format(hostname))
logger.info("Port: {}".format(port))
logger.info("Mockup directory path specified: {}".format(mockDirPath))
logger.info("Response time: {} seconds".format(responseTime))
mockDir = os.path.realpath(mockDirPath) logger.info("Serving Mockup in absolute path: {}".format(mockDir))
if not shortForm:
slashRedfishDir = os.path.join(mockDir, "redfish")
if os.path.isdir(slashRedfishDir) is not True:
logger.info("ERROR: Invalid Mockup Directory--no /redfish directory at top. Aborting")
sys.stderr.flush()
sys.exit(1)
if shortForm:
if os.path.isdir(mockDir) is not True or os.path.isfile(os.path.join(mockDir, "index.json")) is not True:
logger.info("ERROR: Invalid Mockup Directory--dir or index.json does not exist")
sys.stderr.flush()
sys.exit(1)
myServer = HTTPServer((hostname, port), RfMockupServer)
def sigterm_handler(signal_number, frame):
logger.info("SIGTERM: Shutting down http server")
myServer.server_close()
sys.exit(0)
signal.signal(signal.SIGTERM, sigterm_handler)
if sslMode:
logger.info("Using SSL with certfile: {}".format(sslCert))
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(certfile=sslCert, keyfile=sslKey)
myServer.socket = context.wrap_socket(myServer.socket, server_side=True)
myServer.mockDir = mockDir
myServer.testEtagFlag = testEtagFlag
myServer.headers = headers
myServer.timefromJson = timefromJson
myServer.shortForm = shortForm
try:
myServer.responseTime = float(responseTime)
except ValueError:
logger.info("Enter an integer or float value")
sys.exit(2)
mySSDP = None
if ssdpStart:
from gevent import monkey
monkey.patch_all()
path, filename, jsonData = "/redfish/v1", "index.json", None
apath = myServer.mockDir
rpath = clean_path(path, myServer.shortForm)
fpath = os.path.join(apath, rpath, filename) if filename not in ["", None] else os.path.join(apath, rpath)
if os.path.isfile(fpath):
with open(fpath) as f:
jsonData = json.load(f)
f.close()
else:
jsonData = None
protocol = "{}://".format("https" if sslMode else "http")
mySSDP = RfSSDPServer(jsonData, "{}{}:{}{}".format(protocol, hostname, port, "/redfish/v1"), hostname)
logger.info("Serving Redfish mockup on port: {}".format(port))
try:
if mySSDP is not None:
t2 = threading.Thread(target=mySSDP.start)
t2.daemon = True
t2.start()
logger.info("running Server...")
myServer.serve_forever()
except KeyboardInterrupt:
pass
myServer.server_close()
logger.info("Shutting down http server")
if __name__ == "__main__":
main()