Align api /nodes with api-ref spec
1. Return briefly info of node after composed node 2. Fixed hardcode fields in node details, like ip addr, mac, etc. Added more fields according to valence api spec. Change-Id: I9291b1245895fd7693f5e960559e9f29a314b712 Closes-Bug: #1636826
This commit is contained in:
parent
7a52ec90d1
commit
59d6a712a0
api-ref/source/mockup
valence
@ -6,6 +6,7 @@
|
||||
"target_boot_source": "Pxe",
|
||||
"health_status" : "ok",
|
||||
"name" : "Server-1",
|
||||
"description": "Description for this node",
|
||||
"pooled_group_id" : "11z23344-0099-7766-5544-33225511",
|
||||
"metadata" : {
|
||||
"system_nic" : [
|
||||
|
@ -17,7 +17,9 @@ import logging
|
||||
from flask import request
|
||||
from flask_restful import abort
|
||||
from flask_restful import Resource
|
||||
from six.moves import http_client
|
||||
|
||||
from valence.common import utils
|
||||
from valence.redfish import redfish
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -26,22 +28,26 @@ LOG = logging.getLogger(__name__)
|
||||
class NodesList(Resource):
|
||||
|
||||
def get(self):
|
||||
return redfish.nodes_list(request.args)
|
||||
return utils.make_response(http_client.OK,
|
||||
redfish.list_nodes())
|
||||
|
||||
def post(self):
|
||||
return redfish.compose_node(request.get_json())
|
||||
return utils.make_response(
|
||||
http_client.OK, redfish.compose_node(request.get_json()))
|
||||
|
||||
|
||||
class Nodes(Resource):
|
||||
|
||||
def get(self, nodeid):
|
||||
return redfish.get_nodebyid(nodeid)
|
||||
return utils.make_response(http_client.OK,
|
||||
redfish.get_node_by_id(nodeid))
|
||||
|
||||
def delete(self, nodeid):
|
||||
return redfish.delete_composednode(nodeid)
|
||||
return utils.make_response(http_client.OK,
|
||||
redfish.delete_composednode(nodeid))
|
||||
|
||||
|
||||
class NodesStorage(Resource):
|
||||
|
||||
def get(self, nodeid):
|
||||
return abort(501)
|
||||
return abort(http_client.NOT_IMPLEMENTED)
|
||||
|
@ -17,10 +17,12 @@ import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import flask
|
||||
import requests
|
||||
from requests import auth
|
||||
from six.moves import http_client
|
||||
|
||||
from valence.api import link
|
||||
from valence.common import constants
|
||||
from valence.common import exception
|
||||
from valence.common import utils
|
||||
@ -283,11 +285,143 @@ def get_systembyid(systemid):
|
||||
return systems_list({"Id": systemid})
|
||||
|
||||
|
||||
def get_nodebyid(nodeid):
|
||||
node = nodes_list({"Id": nodeid})
|
||||
if not node:
|
||||
raise exception.NotFound(detail='Node: %s not found' % nodeid)
|
||||
return node[0]
|
||||
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["InstructionSet"],
|
||||
"model": respdata["Model"],
|
||||
"speed_mhz": respdata["MaxSpeedMHz"],
|
||||
"total_core": respdata["TotalCores"]
|
||||
}
|
||||
|
||||
return cpu_details
|
||||
|
||||
|
||||
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["DataWidthBits"],
|
||||
"speed_mhz": respdata["OperatingSpeedMHz"],
|
||||
"total_memory_mb": respdata["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["SpeedMbps"],
|
||||
"mac": respdata["MACAddress"],
|
||||
"status": respdata["Status"]["State"],
|
||||
"ipv4": [{
|
||||
"address": ipv4["Address"],
|
||||
"subnet_mask": ipv4["SubnetMask"],
|
||||
"gateway": ipv4["Gateway"]
|
||||
} for ipv4 in respdata["IPv4Addresses"]]
|
||||
}
|
||||
|
||||
if respdata["VLANs"]:
|
||||
# Get vlan info
|
||||
vlan_url_list = urls2list(respdata["VLANs"]["@odata.id"])
|
||||
network_details["vlans"] = []
|
||||
for url in vlan_url_list:
|
||||
vlan_info = send_request(url).json()
|
||||
network_details["vlans"].append({
|
||||
"vlanid": vlan_info["VLANId"],
|
||||
"status": vlan_info["Status"]["State"]
|
||||
})
|
||||
|
||||
return network_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["Name"],
|
||||
"node_power_state": respdata["PowerState"],
|
||||
"links": [
|
||||
link.Link.make_link('self', flask.request.url_root,
|
||||
'nodes/' + respdata["UUID"], '').as_dict(),
|
||||
link.Link.make_link('bookmark', flask.request.url_root,
|
||||
'nodes/' + respdata["UUID"], '',
|
||||
bookmark=True).as_dict()
|
||||
]
|
||||
}
|
||||
|
||||
if show_detail:
|
||||
node_detail.update({
|
||||
"index": node_index,
|
||||
"description": respdata["Description"],
|
||||
"node_state": respdata["ComposedNodeState"],
|
||||
"boot_source": respdata["Boot"]["BootSourceOverrideTarget"],
|
||||
"target_boot_source": respdata["Boot"]["BootSourceOverrideTarget"],
|
||||
"health_status": respdata["Status"]["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["@odata.id"])
|
||||
for i in respdata["Links"]["Processors"]],
|
||||
"memory": [show_ram_details(i["@odata.id"])
|
||||
for i in respdata["Links"]["Memory"]],
|
||||
"network": [show_network_details(i["@odata.id"])
|
||||
for i in respdata["Links"]["EthernetInterfaces"]]
|
||||
}
|
||||
})
|
||||
|
||||
return node_detail
|
||||
|
||||
|
||||
def build_hierarchy_tree():
|
||||
@ -309,39 +443,62 @@ def build_hierarchy_tree():
|
||||
|
||||
|
||||
def compose_node(request_body):
|
||||
"""Compose new node through podm api.
|
||||
|
||||
:param request_body: The request content to compose new node, which should
|
||||
follow podm format. Valence api directly pass it to
|
||||
podm right now.
|
||||
:returns: The numeric index of new composed node.
|
||||
"""
|
||||
|
||||
# Get url of allocating resource to node
|
||||
nodes_url = get_base_resource_url('Nodes')
|
||||
headers = {'Content-type': 'application/json'}
|
||||
nodes_resp = send_request(nodes_url, 'GET', headers=headers)
|
||||
if nodes_resp.status_code != http_client.OK:
|
||||
resp = send_request(nodes_url, 'GET')
|
||||
if resp.status_code != http_client.OK:
|
||||
LOG.error('Unable to query ' + nodes_url)
|
||||
raise exception.RedfishException(nodes_resp.json(),
|
||||
status_code=nodes_resp.status_code)
|
||||
nodes_json = json.loads(nodes_resp.content)
|
||||
allocate_url = nodes_json['Actions']['#ComposedNodeCollection.Allocate'][
|
||||
'target']
|
||||
resp = send_request(allocate_url, 'POST', headers=headers,
|
||||
json=request_body)
|
||||
if resp.status_code == http_client.CREATED:
|
||||
allocated_node = resp.headers['Location']
|
||||
node_resp = send_request(allocated_node, "GET", headers=headers)
|
||||
LOG.debug('Successfully allocated node:' + allocated_node)
|
||||
node_json = json.loads(node_resp.content)
|
||||
assemble_url = node_json['Actions']['#ComposedNode.Assemble']['target']
|
||||
LOG.debug('Assembling Node: ' + assemble_url)
|
||||
assemble_resp = send_request(assemble_url, "POST", headers=headers)
|
||||
LOG.debug(assemble_resp.status_code)
|
||||
if assemble_resp.status_code == http_client.NO_CONTENT:
|
||||
LOG.debug('Successfully assembled node: ' + allocated_node)
|
||||
return {"node": allocated_node}
|
||||
else:
|
||||
parts = allocated_node.split('/')
|
||||
node_id = parts[-1]
|
||||
delete_composednode(node_id)
|
||||
raise exception.RedfishException(assemble_resp.json(),
|
||||
status_code=resp.status_code)
|
||||
else:
|
||||
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(cfg.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_composednode(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, show_detail=False)
|
||||
|
||||
|
||||
def delete_composednode(nodeid):
|
||||
@ -351,80 +508,24 @@ def delete_composednode(nodeid):
|
||||
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
|
||||
resp_dict = exception.confirmation(confirm_detail="DELETED")
|
||||
return utils.make_response(http_client.OK, resp_dict)
|
||||
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 nodes_list(filters={}):
|
||||
def list_nodes():
|
||||
# list of nodes with hardware details needed for flavor creation
|
||||
LOG.debug(filters)
|
||||
lst_nodes = []
|
||||
|
||||
# TODO(lin.yang): support filter when list nodes
|
||||
nodes = []
|
||||
nodes_url = get_base_resource_url("Nodes")
|
||||
nodeurllist = urls2list(nodes_url)
|
||||
# podmtree = build_hierarchy_tree()
|
||||
# podmtree.writeHTML("0","/tmp/a.html")
|
||||
node_url_list = urls2list(nodes_url)
|
||||
|
||||
for lnk in nodeurllist:
|
||||
filterPassed = True
|
||||
resp = send_request(lnk)
|
||||
if resp.status_code != http_client.OK:
|
||||
LOG.info("Error in fetching Node details " + lnk)
|
||||
else:
|
||||
node = resp.json()
|
||||
for url in node_url_list:
|
||||
node_index = url.split('/')[-1]
|
||||
nodes.append(get_node_by_id(node_index, show_detail=False))
|
||||
|
||||
if any(filters):
|
||||
filterPassed = utils.match_conditions(node, filters)
|
||||
LOG.info("FILTER PASSED" + str(filterPassed))
|
||||
if not filterPassed:
|
||||
continue
|
||||
|
||||
nodeid = lnk.split("/")[-1]
|
||||
nodeuuid = node['UUID']
|
||||
nodelocation = node['AssetTag']
|
||||
# podmtree.getPath(lnk) commented as location should be
|
||||
# computed using other logic.consult Chester
|
||||
nodesystemurl = node["Links"]["ComputerSystem"]["@odata.id"]
|
||||
cpu = {}
|
||||
ram = 0
|
||||
nw = 0
|
||||
storage = system_storage_details(nodesystemurl)
|
||||
cpu = system_cpu_details(nodesystemurl)
|
||||
|
||||
if "Memory" in node:
|
||||
ram = node["Memory"]["TotalSystemMemoryGiB"]
|
||||
|
||||
if ("EthernetInterfaces" in node["Links"] and
|
||||
node["Links"]["EthernetInterfaces"]):
|
||||
nw = len(node["Links"]["EthernetInterfaces"])
|
||||
|
||||
bmcip = "127.0.0.1" # system['Oem']['Dell_G5MC']['BmcIp']
|
||||
bmcmac = "00:00:00:00:00" # system['Oem']['Dell_G5MC']['BmcMac']
|
||||
node = {"id": nodeid, "cpu": cpu,
|
||||
"ram": ram, "storage": storage,
|
||||
"nw": nw, "location": nodelocation,
|
||||
"uuid": nodeuuid, "bmcip": bmcip, "bmcmac": bmcmac}
|
||||
|
||||
# filter based on RAM, CPU, NETWORK..etc
|
||||
if 'ram' in filters:
|
||||
filterPassed = (True
|
||||
if int(ram) >= int(filters['ram'])
|
||||
else False)
|
||||
|
||||
# filter based on RAM, CPU, NETWORK..etc
|
||||
if 'nw' in filters:
|
||||
filterPassed = (True
|
||||
if int(nw) >= int(filters['nw'])
|
||||
else False)
|
||||
|
||||
# filter based on RAM, CPU, NETWORK..etc
|
||||
if 'storage' in filters:
|
||||
filterPassed = (True
|
||||
if int(storage) >= int(filters['storage'])
|
||||
else False)
|
||||
|
||||
if filterPassed:
|
||||
lst_nodes.append(node)
|
||||
return lst_nodes
|
||||
return nodes
|
||||
|
@ -51,6 +51,107 @@ def fake_service_root():
|
||||
}
|
||||
|
||||
|
||||
def fake_nodes_root():
|
||||
return {
|
||||
"@odata.context": "/redfish/v1/$metadata#Nodes",
|
||||
"@odata.id": "/redfish/v1/Nodes",
|
||||
"@odata.type": "#ComposedNodeCollection.ComposedNodeCollection",
|
||||
"Name": "Composed Nodes Collection",
|
||||
"Members@odata.count": 1,
|
||||
"Members": [{
|
||||
"@odata.id": "/redfish/v1/Nodes/14"
|
||||
}],
|
||||
"Actions": {
|
||||
"#ComposedNodeCollection.Allocate": {
|
||||
"target": "/redfish/v1/Nodes/Actions/Allocate"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fake_node_detail():
|
||||
return {
|
||||
"@odata.context": "/redfish/v1/$metadata#Nodes/Members/$entity",
|
||||
"@odata.id": "/redfish/v1/Nodes/6",
|
||||
"@odata.type": "#ComposedNode.1.0.0.ComposedNode",
|
||||
"Id": "6",
|
||||
"Name": "test",
|
||||
"Description": "",
|
||||
"SystemType": "Logical",
|
||||
"AssetTag": "",
|
||||
"Manufacturer": "",
|
||||
"Model": "",
|
||||
"SKU": "",
|
||||
"SerialNumber": "",
|
||||
"PartNumber": "",
|
||||
"UUID": "deba2630-d2af-11e6-a65f-4d709ab9a725",
|
||||
"HostName": "web-srv344",
|
||||
"PowerState": "On",
|
||||
"BiosVersion": "P79 v1.00 (09/20/2013)",
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK",
|
||||
"HealthRollup": "OK"
|
||||
},
|
||||
"Processors": {
|
||||
"Count": 1,
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK",
|
||||
"HealthRollup": "OK"
|
||||
}
|
||||
},
|
||||
"Memory": {
|
||||
"TotalSystemMemoryGiB": 8,
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK",
|
||||
"HealthRollup": "OK"
|
||||
}
|
||||
},
|
||||
"ComposedNodeState": "PoweredOff",
|
||||
"Boot": {
|
||||
"BootSourceOverrideEnabled": "Continuous",
|
||||
"BootSourceOverrideTarget": "Hdd",
|
||||
"BootSourceOverrideTarget@Redfish.AllowableValues": [
|
||||
"None", "Pxe", "Floppy", "Cd", "Usb", "Hdd", "BiosSetup",
|
||||
"Utilities", "Diags", "UefiTarget"]
|
||||
},
|
||||
"Oem": {},
|
||||
"Links": {
|
||||
"ComputerSystem": {
|
||||
"@odata.id": "/redfish/v1/Systems/1"
|
||||
},
|
||||
"Processors": [{
|
||||
"@odata.id": "/redfish/v1/Systems/1/Processors/1"
|
||||
}],
|
||||
"Memory": [{
|
||||
"@odata.id": "/redfish/v1/Systems/1/Memory/1"
|
||||
}],
|
||||
"EthernetInterfaces": [{
|
||||
"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/2"
|
||||
}],
|
||||
"LocalDrives": [],
|
||||
"RemoteDrives": [],
|
||||
"ManagedBy": [{
|
||||
"@odata.id": "/redfish/v1/Managers/1"
|
||||
}],
|
||||
"Oem": {}
|
||||
},
|
||||
"Actions": {
|
||||
"#ComposedNode.Reset": {
|
||||
"target": "/redfish/v1/Nodes/6/Actions/ComposedNode.Reset",
|
||||
"ResetType@DMTF.AllowableValues": [
|
||||
"On", "ForceOff", "GracefulShutdown", "ForceRestart",
|
||||
"Nmi", "GracefulRestart", "ForceOn", "PushPowerButton"]
|
||||
},
|
||||
"#ComposedNode.Assemble": {
|
||||
"target": "/redfish/v1/Nodes/6/Actions/ComposedNode.Assemble"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fake_chassis_list():
|
||||
return [
|
||||
{
|
||||
@ -135,6 +236,50 @@ def fake_simple_storage():
|
||||
}
|
||||
|
||||
|
||||
def fake_processor():
|
||||
return {
|
||||
"InstructionSet": "x86-64",
|
||||
"Model": "Intel(R) Core(TM) i7-4790",
|
||||
"MaxSpeedMHz": 3700,
|
||||
"TotalCores": 8,
|
||||
}
|
||||
|
||||
|
||||
def fake_memory():
|
||||
return {
|
||||
"DataWidthBits": 0,
|
||||
"OperatingSpeedMHz": 2400,
|
||||
"CapacityMiB": 8192
|
||||
}
|
||||
|
||||
|
||||
def fake_network_interface():
|
||||
return {
|
||||
"MACAddress": "e9:47:d3:60:64:66",
|
||||
"SpeedMbps": 100,
|
||||
"Status": {
|
||||
"State": "Enabled"
|
||||
},
|
||||
"IPv4Addresses": [{
|
||||
"Address": "192.168.0.10",
|
||||
"SubnetMask": "255.255.252.0",
|
||||
"Gateway": "192.168.0.1",
|
||||
}],
|
||||
"VLANs": {
|
||||
"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/2/VLANs"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fake_vlan():
|
||||
return {
|
||||
"VLANId": 99,
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fake_system_ethernet_interfaces():
|
||||
return {
|
||||
"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces",
|
||||
@ -163,3 +308,36 @@ def fake_delete_composednode_fail():
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fake_allocate_node_conflict():
|
||||
return {
|
||||
"error": {
|
||||
"code": "Base.1.0.ResourcesStateMismatch",
|
||||
"message": "Conflict during allocation",
|
||||
"@Message.ExtendedInfo": [{
|
||||
"Message": "There are no computer systems available for this "
|
||||
"allocation request."
|
||||
}, {
|
||||
"Message": "Available assets count after applying filters: ["
|
||||
"available: 0 -> status: 0 -> resource: 0 -> "
|
||||
"chassis: 0 -> processors: 0 -> memory: 0 -> "
|
||||
"local drives: 0 -> ethernet interfaces: 0]"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def fake_assemble_node_failed():
|
||||
return {
|
||||
"error": {
|
||||
"code": "Base.1.0.InvalidPayload",
|
||||
"message": "Request payload is invalid or missing",
|
||||
"@Message.ExtendedInfo": [{
|
||||
"Message": "Assembly action could not be completed!"
|
||||
}, {
|
||||
"Message": "Assembly failed: Only composed node in ALLOCATED "
|
||||
"state can be assembled"
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
from unittest import TestCase
|
||||
|
||||
import mock
|
||||
@ -189,24 +190,80 @@ class TestRedfish(TestCase):
|
||||
result = redfish.system_storage_details("/redfish/v1/Systems/test")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch('valence.common.utils.make_response')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_show_cpu_details(self, mock_request):
|
||||
mock_request.return_value = fakes.mock_request_get(
|
||||
fakes.fake_processor(), http_client.OK)
|
||||
expected = {
|
||||
"instruction_set": "x86-64",
|
||||
"model": "Intel(R) Core(TM) i7-4790",
|
||||
"speed_mhz": 3700,
|
||||
"total_core": 8,
|
||||
}
|
||||
|
||||
result = redfish.show_cpu_details("/redfish/v1/Systems/1/Processors/1")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_show_memory_details(self, mock_request):
|
||||
mock_request.return_value = fakes.mock_request_get(
|
||||
fakes.fake_memory(), http_client.OK)
|
||||
expected = {
|
||||
"data_width_bit": 0,
|
||||
"speed_mhz": 2400,
|
||||
"total_memory_mb": 8192
|
||||
}
|
||||
|
||||
result = redfish.show_ram_details("/redfish/v1/Systems/1/Memory/1")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch('valence.redfish.redfish.urls2list')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_show_network_interface_details(self, mock_request, mock_url2list):
|
||||
mock_request.side_effect = [
|
||||
fakes.mock_request_get(fakes.fake_network_interface(),
|
||||
http_client.OK),
|
||||
fakes.mock_request_get(fakes.fake_vlan(),
|
||||
http_client.OK)
|
||||
]
|
||||
mock_url2list.return_value = [
|
||||
"redfish/v1/Systems/1/EthernetInterfaces/2/VLANs/1"]
|
||||
expected = {
|
||||
"mac": "e9:47:d3:60:64:66",
|
||||
"speed_mbps": 100,
|
||||
"status": "Enabled",
|
||||
"ipv4": [{
|
||||
"address": "192.168.0.10",
|
||||
"subnet_mask": "255.255.252.0",
|
||||
"gateway": "192.168.0.1",
|
||||
}],
|
||||
'vlans': [{
|
||||
'status': 'Enabled',
|
||||
'vlanid': 99
|
||||
}]
|
||||
}
|
||||
|
||||
result = redfish.show_network_details(
|
||||
"/redfish/v1/Systems/1/EthernetInterfaces/1")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_delete_composednode_ok(self, mock_request, mock_get_url,
|
||||
mock_make_response):
|
||||
def test_delete_composednode_ok(self, mock_request, mock_get_url):
|
||||
mock_get_url.return_value = '/redfish/v1/Nodes'
|
||||
delete_result = fakes.fake_delete_composednode_ok()
|
||||
fake_delete_response = fakes.mock_request_get(delete_result,
|
||||
http_client.NO_CONTENT)
|
||||
mock_request.return_value = fake_delete_response
|
||||
redfish.delete_composednode(101)
|
||||
result = redfish.delete_composednode(101)
|
||||
mock_request.assert_called_with('/redfish/v1/Nodes/101', 'DELETE')
|
||||
expected_content = {
|
||||
"code": "",
|
||||
"detail": "DELETED",
|
||||
expected = {
|
||||
"code": "DELETED",
|
||||
"detail": "This composed node has been deleted successfully.",
|
||||
"request_id": exception.FAKE_REQUEST_ID,
|
||||
}
|
||||
mock_make_response.assert_called_with(http_client.OK, expected_content)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
@mock.patch('valence.common.utils.make_response')
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@ -260,3 +317,108 @@ class TestRedfish(TestCase):
|
||||
mock_get.asset_called_once_with('url',
|
||||
auth=auth.HTTPBasicAuth('username',
|
||||
'password'))
|
||||
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_allocate_node_conflict(self, mock_request, mock_get_url):
|
||||
"""Test allocate resource conflict when compose node"""
|
||||
mock_get_url.return_value = '/redfish/v1/Nodes'
|
||||
|
||||
# Fake response for getting nodes root
|
||||
fake_node_root_resp = fakes.mock_request_get(fakes.fake_nodes_root(),
|
||||
http_client.OK)
|
||||
# Fake response for allocating node
|
||||
fake_node_allocation_conflict = \
|
||||
fakes.mock_request_get(fakes.fake_allocate_node_conflict(),
|
||||
http_client.CONFLICT)
|
||||
mock_request.side_effect = [fake_node_root_resp,
|
||||
fake_node_allocation_conflict]
|
||||
|
||||
with self.assertRaises(exception.RedfishException) as context:
|
||||
redfish.compose_node({"name": "test_node"})
|
||||
|
||||
self.assertTrue("There are no computer systems available for this "
|
||||
"allocation request." in str(context.exception.detail))
|
||||
|
||||
@mock.patch('valence.redfish.redfish.delete_composednode')
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_assemble_node_failed(self, mock_request, mock_get_url,
|
||||
mock_delete_node):
|
||||
"""Test allocate resource conflict when compose node"""
|
||||
mock_get_url.return_value = '/redfish/v1/Nodes'
|
||||
|
||||
# Fake response for getting nodes root
|
||||
fake_node_root_resp = fakes.mock_request_get(fakes.fake_nodes_root(),
|
||||
http_client.OK)
|
||||
# Fake response for allocating node
|
||||
fake_node_allocation_conflict = mock.MagicMock()
|
||||
fake_node_allocation_conflict.status_code = http_client.CREATED
|
||||
fake_node_allocation_conflict.headers['Location'] = \
|
||||
os.path.normpath("/".join([cfg.podm_url, 'redfish/v1/Nodes/1']))
|
||||
|
||||
# Fake response for getting url of node assembling
|
||||
fake_node_detail = fakes.mock_request_get(fakes.fake_node_detail(),
|
||||
http_client.OK)
|
||||
|
||||
# Fake response for assembling node
|
||||
fake_node_assemble_failed = fakes.mock_request_get(
|
||||
fakes.fake_assemble_node_failed(), http_client.BAD_REQUEST)
|
||||
mock_request.side_effect = [fake_node_root_resp,
|
||||
fake_node_allocation_conflict,
|
||||
fake_node_detail,
|
||||
fake_node_assemble_failed]
|
||||
|
||||
with self.assertRaises(exception.RedfishException):
|
||||
redfish.compose_node({"name": "test_node"})
|
||||
|
||||
mock_delete_node.assert_called_once()
|
||||
|
||||
@mock.patch('valence.redfish.redfish.get_node_by_id')
|
||||
@mock.patch('valence.redfish.redfish.delete_composednode')
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
@mock.patch('valence.redfish.redfish.send_request')
|
||||
def test_assemble_node_success(self, mock_request, mock_get_url,
|
||||
mock_delete_node, mock_get_node_by_id):
|
||||
"""Test compose node successfully"""
|
||||
mock_get_url.return_value = '/redfish/v1/Nodes'
|
||||
|
||||
# Fake response for getting nodes root
|
||||
fake_node_root_resp = fakes.mock_request_get(fakes.fake_nodes_root(),
|
||||
http_client.OK)
|
||||
# Fake response for allocating node
|
||||
fake_node_allocation_conflict = mock.MagicMock()
|
||||
fake_node_allocation_conflict.status_code = http_client.CREATED
|
||||
fake_node_allocation_conflict.headers['Location'] = \
|
||||
os.path.normpath("/".join([cfg.podm_url, 'redfish/v1/Nodes/1']))
|
||||
|
||||
# Fake response for getting url of node assembling
|
||||
fake_node_detail = fakes.mock_request_get(fakes.fake_node_detail(),
|
||||
http_client.OK)
|
||||
|
||||
# Fake response for assembling node
|
||||
fake_node_assemble_failed = fakes.mock_request_get(
|
||||
{}, http_client.NO_CONTENT)
|
||||
mock_request.side_effect = [fake_node_root_resp,
|
||||
fake_node_allocation_conflict,
|
||||
fake_node_detail,
|
||||
fake_node_assemble_failed]
|
||||
|
||||
redfish.compose_node({"name": "test_node"})
|
||||
|
||||
mock_delete_node.assert_not_called()
|
||||
mock_get_node_by_id.assert_called_once()
|
||||
|
||||
@mock.patch('valence.redfish.redfish.get_node_by_id')
|
||||
@mock.patch('valence.redfish.redfish.urls2list')
|
||||
@mock.patch('valence.redfish.redfish.get_base_resource_url')
|
||||
def test_list_node(self, mock_get_url, mock_url2list, mock_get_node_by_id):
|
||||
"""Test list node"""
|
||||
mock_get_url.return_value = '/redfish/v1/Nodes'
|
||||
mock_url2list.return_value = ['redfish/v1/Nodes/1']
|
||||
mock_get_node_by_id.side_effect = ["node1_detail"]
|
||||
|
||||
result = redfish.list_nodes()
|
||||
|
||||
mock_get_node_by_id.assert_called_with("1", show_detail=False)
|
||||
self.assertEqual(["node1_detail"], result)
|
||||
|
Loading…
x
Reference in New Issue
Block a user