def compose_node(name, description, requirements): """Compose new node through podm api. :param name: name of node :param description: description of node if any :param requirements: additional requirements of node if any :returns: The numeric index of new composed node. """ request_body = _create_compose_request(name, description, requirements) # Get url of allocating resource to node nodes_url = get_base_resource_url('Nodes') resp = send_request(nodes_url, 'GET') if resp.status_code != http_client.OK: LOG.error('Unable to query ' + nodes_url) raise exception.RedfishException(resp.json(), status_code=resp.status_code) respdata = resp.json() allocate_url = respdata['Actions']['#ComposedNodeCollection.Allocate'][ 'target'] # Allocate resource to this node LOG.debug('Allocating Node: {0}'.format(request_body)) allocate_resp = send_request(allocate_url, 'POST', headers={'Content-type': 'application/json'}, json=request_body) if allocate_resp.status_code != http_client.CREATED: # Raise exception if allocation failed raise exception.RedfishException(allocate_resp.json(), status_code=allocate_resp.status_code) # Allocated node successfully # node_url -- relative redfish url e.g redfish/v1/Nodes/1 node_url = allocate_resp.headers['Location'].lstrip(CONF.podm.url) # node_index -- numeric index of new node e.g 1 node_index = node_url.split('/')[-1] LOG.debug('Successfully allocated node:' + node_url) # Get url of assembling node resp = send_request(node_url, "GET") respdata = resp.json() assemble_url = respdata['Actions']['#ComposedNode.Assemble']['target'] # Assemble node LOG.debug('Assembling Node: {0}'.format(assemble_url)) assemble_resp = send_request(assemble_url, "POST") if assemble_resp.status_code != http_client.NO_CONTENT: # Delete node if assemble failed delete_composed_node(node_index) raise exception.RedfishException(assemble_resp.json(), status_code=resp.status_code) else: # Assemble successfully LOG.debug('Successfully assembled node: ' + node_url) # Return new composed node index return get_node_by_id(node_index)
def set_boot_source(nodeid, request): nodes_url = get_base_resource_url("Nodes") node_url = os.path.normpath("/".join([nodes_url, nodeid])) resp = send_request(node_url) if resp.status_code != http_client.OK: # Raise exception if don't find node raise exception.RedfishException(resp.json(), status_code=resp.status_code) node = resp.json() boot_enabled = request.get("Boot", {}).get("Enabled") boot_target = request.get("Boot", {}).get("Target") allowable_boot_target = \ node["Boot"]["*****@*****.**"] if not boot_enabled or not boot_target: raise exception.BadRequest( detail="The content of set boot source request is malformed. " "Please refer to Valence api specification to correct it.") if boot_enabled not in ["Disabled", "Once", "Continuous"]: raise exception.BadRequest( detail="The parameter Enabled '{0}' is not in allowable list " "['Disabled', 'Once', 'Continuous'].".format(boot_enabled)) if allowable_boot_target and \ boot_target not in allowable_boot_target: raise exception.BadRequest( detail="The parameter Target '{0}' is not in allowable list " "{1}.".format(boot_target, allowable_boot_target)) action_resp = send_request(node_url, 'PATCH', headers={'Content-type': 'application/json'}, json={ "Boot": { "BootSourceOverrideEnabled": boot_enabled, "BootSourceOverrideTarget": boot_target } }) if action_resp.status_code != http_client.NO_CONTENT: raise exception.RedfishException(action_resp.json(), status_code=action_resp.status_code) else: # Set boot source successfully LOG.debug("Set boot source of composed node {0} to '{1}' with enabled " "state '{2}' successfully.".format(nodes_url, boot_target, boot_enabled)) return exception.confirmation( confirm_code="Set Boot Source of Composed Node", confirm_detail="The boot source of composed node has been set to " "'{0}' with enabled state '{1}' successfully.".format( boot_target, boot_enabled))
def reset_node(nodeid, request): nodes_url = get_base_resource_url("Nodes") node_url = os.path.normpath("/".join([nodes_url, nodeid])) resp = send_request(node_url) if resp.status_code != http_client.OK: # Raise exception if don't find node raise exception.RedfishException(resp.json(), status_code=resp.status_code) node = resp.json() action_type = request.get("Reset", {}).get("Type") allowable_actions = node["Actions"]["#ComposedNode.Reset"][ "*****@*****.**"] if not action_type: raise exception.BadRequest( detail="The content of node action request is malformed. Please " "refer to Valence api specification to correct it.") if allowable_actions and action_type not in allowable_actions: raise exception.BadRequest( detail="Action type '{0}' is not in allowable action list " "{1}.".format(action_type, allowable_actions)) target_url = node["Actions"]["#ComposedNode.Reset"]["target"] action_resp = send_request(target_url, 'POST', headers={'Content-type': 'application/json'}, json={"ResetType": action_type}) if action_resp.status_code != http_client.NO_CONTENT: raise exception.RedfishException(action_resp.json(), status_code=action_resp.status_code) else: # Reset node successfully LOG.debug("Post action '{0}' to node {1} successfully.".format( action_type, target_url)) return exception.confirmation( confirm_code="Reset Composed Node", confirm_detail="This composed node has been set to '{0}' " "successfully.".format(action_type))
def delete_composed_node(nodeid): nodes_url = get_base_resource_url("Nodes") delete_url = nodes_url + '/' + str(nodeid) resp = send_request(delete_url, "DELETE") if resp.status_code == http_client.NO_CONTENT: # we should return 200 status code instead of 204, because 204 means # 'No Content', the message in resp_dict will be ignored in that way return exception.confirmation( confirm_code="DELETED", confirm_detail="This composed node has been deleted successfully.") else: raise exception.RedfishException(resp.json(), status_code=resp.status_code)
def show_ram_details(ram_url): """Get memory details . :param ram_url: relative redfish url to memory, e.g /redfish/v1/Systems/1/Memory/1. :returns: dict of memory detail. """ resp = send_request(ram_url) if resp.status_code != http_client.OK: # Raise exception if don't find memory raise exception.RedfishException(resp.json(), status_code=resp.status_code) respdata = resp.json() ram_details = { "data_width_bit": respdata.get("DataWidthBits"), "speed_mhz": respdata.get("OperatingSpeedMhz"), "total_memory_mb": respdata.get("CapacityMiB") } return ram_details
def show_network_details(network_url): """Get network interface details . :param ram_url: relative redfish url to network interface, e.g /redfish/v1/Systems/1/EthernetInterfaces/1. :returns: dict of network interface detail. """ resp = send_request(network_url) if resp.status_code != http_client.OK: # Raise exception if don't find network interface raise exception.RedfishException(resp.json(), status_code=resp.status_code) respdata = resp.json() network_details = { "speed_mbps": respdata.get("SpeedMbps"), "mac": respdata.get("MACAddress"), "status": respdata.get("Status", {}).get("State"), "ipv4": [{ "address": ipv4.get("Address"), "subnet_mask": ipv4.get("SubnetMask"), "gateway": ipv4.get("Gateway") } for ipv4 in respdata.get("IPv4Addresses", [])] } if respdata.get("VLANs"): # Get vlan info vlan_url_list = urls2list(respdata.get("VLANs", {}).get("@odata.id")) network_details["vlans"] = [] for url in vlan_url_list: vlan_info = send_request(url).json() network_details["vlans"].append({ "vlanid": vlan_info.get("VLANId"), "status": vlan_info.get("Status", {}).get("State") }) return network_details
def show_cpu_details(cpu_url): """Get processor details . :param cpu_url: relative redfish url to processor, e.g /redfish/v1/Systems/1/Processors/1. :returns: dict of processor detail. """ resp = send_request(cpu_url) if resp.status_code != http_client.OK: # Raise exception if don't find processor raise exception.RedfishException(resp.json(), status_code=resp.status_code) respdata = resp.json() cpu_details = { "instruction_set": respdata.get("InstructionSet"), "model": respdata.get("Model"), "speed_mhz": respdata.get("MaxSpeedMHz"), "total_core": respdata.get("TotalCores") } return cpu_details
def get_node_by_id(node_index, show_detail=True): """Get composed node details of specific index. :param node_index: numeric index of new composed node. :param show_detail: show more node detail when set to True. :returns: node detail info. """ nodes_base_url = get_base_resource_url('Nodes') node_url = os.path.normpath('/'.join([nodes_base_url, node_index])) resp = send_request(node_url) LOG.debug(resp.status_code) if resp.status_code != http_client.OK: # Raise exception if don't find node raise exception.RedfishException(resp.json(), status_code=resp.status_code) respdata = resp.json() node_detail = { "name": respdata.get("Name"), "node_power_state": respdata.get("PowerState"), "links": [ link.Link.make_link('self', flask.request.url_root, 'nodes/' + respdata.get("UUID"), '').as_dict(), link.Link.make_link('bookmark', flask.request.url_root, 'nodes/' + respdata.get("UUID"), '', bookmark=True).as_dict() ] } if show_detail: node_detail.update({ "index": node_index, "description": respdata.get("Description"), "node_state": respdata.get("ComposedNodeState"), "boot_source": respdata.get("Boot", {}).get("BootSourceOverrideTarget"), "target_boot_source": respdata.get("Boot", {}).get("BootSourceOverrideTarget"), "health_status": respdata.get("Status", {}).get("Health"), # TODO(lin.yang): "pooled_group_id" is used to check whether # resource can be assigned to composed node, which should be # supported after PODM API v2.1 released. "pooled_group_id": None, "metadata": { "processor": [ show_cpu_details(i.get("@odata.id")) for i in respdata.get("Links", {}).get("Processors", []) ], "memory": [ show_ram_details(i.get("@odata.id")) for i in respdata.get("Links", {}).get("Memory", []) ], "network": [ show_network_details(i.get("@odata.id")) for i in respdata.get("Links", {}).get( "EthernetInterfaces", []) ] }, "computer_system": respdata.get("Links").get("ComputerSystem") }) return node_detail