Merge "Align api /nodes with api-ref spec"
This commit is contained in:
commit
0bda7f9558
@ -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