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:
Lin Yang 2017-01-10 19:17:08 -08:00
parent 7a52ec90d1
commit 59d6a712a0
5 changed files with 563 additions and 115 deletions

View File

@ -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" : [

View File

@ -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)

View File

@ -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

View File

@ -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"
}]
}
}

View File

@ -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)