Add all missing attributes of Node in RSD 2.1

Change-Id: If1cb3e716d6a8997629435e5400cd4d1aa43ce23
This commit is contained in:
Lin Yang 2019-03-26 13:48:08 -07:00
parent 9adb95c051
commit b585c03852
6 changed files with 765 additions and 928 deletions

View File

@ -1,100 +0,0 @@
# Copyright 2017 Intel, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# Values comes from the Redfish System json-schema 1.0.0:
# http://redfish.dmtf.org/schemas/v1/ComputerSystem.v1_0_0.json#/definitions/ComputerSystem # noqa
# Reset action constants
RESET_ON = 'on'
RESET_FORCE_OFF = 'force off'
RESET_GRACEFUL_SHUTDOWN = 'graceful shutdown'
RESET_GRACEFUL_RESTART = 'graceful restart'
RESET_FORCE_RESTART = 'force restart'
RESET_NMI = 'nmi'
RESET_FORCE_ON = 'force on'
RESET_PUSH_POWER_BUTTON = 'push power button'
# Node PowerState constants
NODE_POWER_STATE_ON = 'on'
"""The system is powered on"""
NODE_POWER_STATE_OFF = 'off'
"""The system is powered off, although some components may continue to
have AUX power such as management controller"""
NODE_POWER_STATE_POWERING_ON = 'powering on'
"""A temporary state between Off and On. This temporary state can
be very short"""
NODE_POWER_STATE_POWERING_OFF = 'powering off'
"""A temporary state between On and Off. The power off action can take
time while the OS is in the shutdown process"""
# Composed Node State constants
COMPOSED_NODE_STATE_ALLOCATING = 'allocating'
"""Allocating resources for node is in progress. Next state can be
Allocated or Failed"""
COMPOSED_NODE_STATE_ALLOCATED = 'allocated'
"""Node resources have been allocated, but assembly not started yet.
After ComposedNode.Assemble action state will progress to Assembling"""
COMPOSED_NODE_STATE_ASSEMBLING = 'assembling'
"""Assembly process initiated, but not finished yet. When assembly
is done it will change into Assembled"""
COMPOSED_NODE_STATE_ASSEMBLED = 'assembled'
"""Node successfully assembled"""
COMPOSED_NODE_STATE_FAILED = 'failed'
"""Allocation or assembly process failed, or in runtime one of composing
components was removed or transitioned in error state"""
# Boot source target constants
BOOT_SOURCE_TARGET_NONE = 'none'
"""Boot from the normal boot device"""
BOOT_SOURCE_TARGET_PXE = 'pxe'
"""Boot from the Pre-Boot EXecution (PXE) environment"""
BOOT_SOURCE_TARGET_HDD = 'hdd'
"""Boot from a hard drive"""
# Boot source mode constants
BOOT_SOURCE_MODE_LEGACY = 'legacy'
BOOT_SOURCE_MODE_UEFI = 'uefi'
# Boot source enabled constants
BOOT_SOURCE_ENABLED_ONCE = 'once'
BOOT_SOURCE_ENABLED_CONTINUOUS = 'continuous'
BOOT_SOURCE_ENABLED_DISABLED = 'disabled'
# Processor related constants
# Values comes from the Redfish Processor json-schema 1.0.0:
# http://redfish.dmtf.org/schemas/v1/Processor.v1_0_0.json
# Processor Architecture constants
PROCESSOR_ARCH_x86 = 'x86 or x86-64'
PROCESSOR_ARCH_IA_64 = 'Intel Itanium'
PROCESSOR_ARCH_ARM = 'ARM'
PROCESSOR_ARCH_MIPS = 'MIPS'
PROCESSOR_ARCH_OEM = 'OEM-defined'

View File

@ -1,84 +0,0 @@
# Copyright 2017 Intel, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sushy import utils
from rsd_lib.resources.v2_1.node import constants as node_cons
RESET_NODE_VALUE_MAP = {
'On': node_cons.RESET_ON,
'ForceOff': node_cons.RESET_FORCE_OFF,
'GracefulShutdown': node_cons.RESET_GRACEFUL_SHUTDOWN,
'GracefulRestart': node_cons.RESET_GRACEFUL_RESTART,
'ForceRestart': node_cons.RESET_FORCE_RESTART,
'Nmi': node_cons.RESET_NMI,
'ForceOn': node_cons.RESET_FORCE_ON,
'PushPowerButton': node_cons.RESET_PUSH_POWER_BUTTON,
}
RESET_NODE_VALUE_MAP_REV = utils.revert_dictionary(RESET_NODE_VALUE_MAP)
NODE_POWER_STATE_MAP = {
'On': node_cons.NODE_POWER_STATE_ON,
'Off': node_cons.NODE_POWER_STATE_OFF,
'PoweringOn': node_cons.NODE_POWER_STATE_POWERING_ON,
'PoweringOff': node_cons.NODE_POWER_STATE_POWERING_OFF,
}
NODE_POWER_STATE_MAP_REV = utils.revert_dictionary(NODE_POWER_STATE_MAP)
COMPOSED_NODE_STATE_MAP = {
'Allocating': node_cons.COMPOSED_NODE_STATE_ALLOCATING,
'Allocated': node_cons.COMPOSED_NODE_STATE_ALLOCATED,
'Assembling': node_cons.COMPOSED_NODE_STATE_ASSEMBLING,
'Assembled': node_cons.COMPOSED_NODE_STATE_ASSEMBLED,
'Failed': node_cons.COMPOSED_NODE_STATE_FAILED,
}
COMPOSED_NODE_STATE_MAP_REV = utils.revert_dictionary(COMPOSED_NODE_STATE_MAP)
BOOT_SOURCE_TARGET_MAP = {
'None': node_cons.BOOT_SOURCE_TARGET_NONE,
'Pxe': node_cons.BOOT_SOURCE_TARGET_PXE,
'Hdd': node_cons.BOOT_SOURCE_TARGET_HDD,
}
BOOT_SOURCE_TARGET_MAP_REV = utils.revert_dictionary(BOOT_SOURCE_TARGET_MAP)
BOOT_SOURCE_MODE_MAP = {
'Legacy': node_cons.BOOT_SOURCE_MODE_LEGACY,
'UEFI': node_cons.BOOT_SOURCE_MODE_UEFI,
}
BOOT_SOURCE_MODE_MAP_REV = utils.revert_dictionary(BOOT_SOURCE_MODE_MAP)
BOOT_SOURCE_ENABLED_MAP = {
'Once': node_cons.BOOT_SOURCE_ENABLED_ONCE,
'Continuous': node_cons.BOOT_SOURCE_ENABLED_CONTINUOUS,
'Disabled': node_cons.BOOT_SOURCE_ENABLED_DISABLED,
}
BOOT_SOURCE_ENABLED_MAP_REV = utils.revert_dictionary(BOOT_SOURCE_ENABLED_MAP)
PROCESSOR_ARCH_VALUE_MAP = {
'x86': node_cons.PROCESSOR_ARCH_x86,
'IA-64': node_cons.PROCESSOR_ARCH_IA_64,
'ARM': node_cons.PROCESSOR_ARCH_ARM,
'MIPS': node_cons.PROCESSOR_ARCH_MIPS,
'OEM': node_cons.PROCESSOR_ARCH_OEM,
}
PROCESSOR_ARCH_VALUE_MAP_REV = (
utils.revert_dictionary(PROCESSOR_ARCH_VALUE_MAP))

View File

@ -19,13 +19,12 @@ import logging
from sushy import exceptions
from sushy.resources import base
from sushy.resources import common
from sushy.resources.system import system
from sushy import utils
from rsd_lib import common as rsd_lib_common
from rsd_lib.resources.v2_1.node import constants as node_cons
from rsd_lib.resources.v2_1.node import mappings as node_maps
from rsd_lib import base as rsd_lib_base
from rsd_lib import constants
from rsd_lib.resources.v2_1.node import schemas as node_schemas
from rsd_lib.resources.v2_1.system import system
from rsd_lib import utils as rsd_lib_utils
@ -33,166 +32,129 @@ LOG = logging.getLogger(__name__)
class AssembleActionField(base.CompositeField):
target_uri = base.Field('target', required=True)
target_uri = base.Field("target", required=True)
class AttachEndpointActionField(base.CompositeField):
allowed_values = base.Field('Resource@Redfish.AllowableValues',
default=(),
adapter=utils.get_members_identities)
class AttachEndpointActionField(common.ActionField):
target_uri = base.Field('target', required=True)
allowed_values = base.Field(
"Resource@Redfish.AllowableValues",
default=(),
adapter=utils.get_members_identities,
)
class DetachEndpointActionField(base.CompositeField):
allowed_values = base.Field('Resource@Redfish.AllowableValues',
default=(),
adapter=utils.get_members_identities)
class DetachEndpointActionField(common.ActionField):
target_uri = base.Field('target', required=True)
class ComposeNodeActionField(base.CompositeField):
target_uri = base.Field('target', required=True)
allowed_values = base.Field(
"Resource@Redfish.AllowableValues",
default=(),
adapter=utils.get_members_identities,
)
class NodeActionsField(base.CompositeField):
reset = common.ResetActionField('#ComposedNode.Reset')
assemble = AssembleActionField('#ComposedNode.Assemble')
attach_endpoint = AttachEndpointActionField('#ComposedNode.AttachEndpoint')
detach_endpoint = DetachEndpointActionField('#ComposedNode.DetachEndpoint')
reset = common.ResetActionField("#ComposedNode.Reset")
assemble = common.ActionField("#ComposedNode.Assemble")
attach_endpoint = AttachEndpointActionField("#ComposedNode.AttachEndpoint")
detach_endpoint = DetachEndpointActionField("#ComposedNode.DetachEndpoint")
class NodeCollectionActionsField(base.CompositeField):
compose = ComposeNodeActionField('#ComposedNodeCollection.Allocate')
class BootField(base.CompositeField):
allowed_values = base.Field(
'BootSourceOverrideTarget@Redfish.AllowableValues')
enabled = base.MappedField('BootSourceOverrideEnabled',
node_maps.BOOT_SOURCE_ENABLED_MAP)
mode = base.MappedField('BootSourceOverrideMode',
node_maps.BOOT_SOURCE_MODE_MAP)
target = base.MappedField('BootSourceOverrideTarget',
node_maps.BOOT_SOURCE_TARGET_MAP)
class MemorySummaryField(base.CompositeField):
status = rsd_lib_common.StatusField('Status')
"""The memory status"""
size_gib = base.Field('TotalSystemMemoryGiB',
adapter=rsd_lib_utils.num_or_none)
"""The size of memory of the node in GiB.
This signifies the total installed, operating system-accessible memory
(RAM), measured in GiB.
"""
class ProcessorSummaryField(base.CompositeField):
status = rsd_lib_common.StatusField('Status')
"""The processor status"""
count = base.Field('Count', adapter=rsd_lib_utils.num_or_none)
"""The number of CPUs in the node."""
model = base.Field('Model')
"""Basic information about processor model."""
compose = common.ActionField("#ComposedNodeCollection.Allocate")
class LinksField(base.CompositeField):
system = base.Field('ComputerSystem',
adapter=rsd_lib_utils.get_resource_identity)
computer_system = base.Field(
"ComputerSystem", adapter=rsd_lib_utils.get_resource_identity
)
"""Link to base computer system of this node"""
processors = base.Field('Processors', default=(),
adapter=utils.get_members_identities)
processors = base.Field("Processors", adapter=utils.get_members_identities)
"""Link to processors of this node"""
memory = base.Field('Memory', default=(),
adapter=utils.get_members_identities)
memory = base.Field("Memory", adapter=utils.get_members_identities)
"""Link to memory of this node"""
ethernet_interfaces = base.Field('EthernetInterfaces', default=(),
adapter=utils.get_members_identities)
ethernet_interfaces = base.Field(
"EthernetInterfaces", adapter=utils.get_members_identities
)
"""Link to ethernet interfaces of this node"""
local_drives = base.Field('LocalDrives', default=(),
adapter=utils.get_members_identities)
local_drives = base.Field(
"LocalDrives", adapter=utils.get_members_identities
)
"""Link to local driver of this node"""
remote_drives = base.Field('RemoteDrives', default=(),
adapter=utils.get_members_identities)
remote_drives = base.Field(
"RemoteDrives", adapter=utils.get_members_identities
)
"""Link to remote drives of this node"""
managed_by = base.Field("ManagedBy", adapter=utils.get_members_identities)
class Node(base.ResourceBase):
boot = BootField('Boot', required=True)
"""A dictionary containg the current boot device, frequency and mode"""
class Node(rsd_lib_base.ResourceBase):
"""ComposedNode resource class
composed_node_state = base.MappedField('ComposedNodeState',
node_maps.COMPOSED_NODE_STATE_MAP)
"""Current state of assembly process for this node"""
This schema defines a node and its respective properties.
"""
description = base.Field('Description')
"""The node description"""
links = LinksField("Links")
"""Contains links to other resources that are related to this resource."""
identity = base.Field('Id', required=True)
"""The node identity string"""
status = rsd_lib_base.StatusField("Status")
"""This indicates the known state of the resource, such as if it is
enabled.
"""
name = base.Field('Name')
"""The node name"""
composed_node_state = base.Field("ComposedNodeState")
power_state = base.MappedField('PowerState',
node_maps.NODE_POWER_STATE_MAP)
"""The node power state"""
asset_tag = base.Field("AssetTag")
"""The user definable tag that can be used to track this computer system
for inventory or other client purposes
"""
status = rsd_lib_common.StatusField('Status')
"""The node status"""
uuid = base.Field("UUID")
"""The universal unique identifier (UUID) for this system"""
uuid = base.Field('UUID')
"""The node UUID"""
power_state = base.Field("PowerState")
"""This is the current power state of the system"""
memory_summary = MemorySummaryField('Memory')
"""The summary info of memory of the node in general detail"""
boot = system.BootField("Boot")
"""Information about the boot settings for this system"""
processor_summary = ProcessorSummaryField('Processors')
"""The summary info for the node processors in general detail"""
processors = system.ProcessorSummaryField("Processors")
"""This object describes the central processors of the system in general
detail.
"""
links = LinksField('Links')
"""These links to related components of this composed node"""
memory = system.MemorySummaryField("Memory")
"""This object describes the central memory of the system in general
detail.
"""
_actions = NodeActionsField('Actions', required=True)
def __init__(self, connector, identity, redfish_version=None):
"""A class representing a ComposedNode
:param connector: A Connector instance
:param identity: The identity of the Node resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of the given version.
"""
super(Node, self).__init__(connector, identity, redfish_version)
_actions = NodeActionsField("Actions", required=True)
def _get_reset_action_element(self):
reset_action = self._actions.reset
if not reset_action:
raise exceptions.MissingActionError(action='#ComposedNode.Reset',
resource=self._path)
raise exceptions.MissingActionError(
action="#ComposedNode.Reset", resource=self._path
)
return reset_action
def _get_assemble_action_element(self):
assemble_action = self._actions.assemble
if not assemble_action:
raise exceptions.MissingActionError(
action='#ComposedNode.Assemble',
resource=self._path)
action="#ComposedNode.Assemble", resource=self._path
)
return assemble_action
def get_allowed_reset_node_values(self):
@ -203,13 +165,16 @@ class Node(base.ResourceBase):
reset_action = self._get_reset_action_element()
if not reset_action.allowed_values:
LOG.warning('Could not figure out the allowed values for the '
'reset node action for Node %s', self.identity)
return set(node_maps.RESET_NODE_VALUE_MAP_REV)
LOG.warning(
"Could not figure out the allowed values for the "
"reset node action for Node %s",
self.identity,
)
return set(constants.RESET_TYPE_VALUE)
return set([node_maps.RESET_NODE_VALUE_MAP[v] for v in
set(node_maps.RESET_NODE_VALUE_MAP).
intersection(reset_action.allowed_values)])
return set(constants.RESET_TYPE_VALUE).intersection(
reset_action.allowed_values
)
def reset_node(self, value):
"""Reset the node.
@ -221,12 +186,12 @@ class Node(base.ResourceBase):
valid_resets = self.get_allowed_reset_node_values()
if value not in valid_resets:
raise exceptions.InvalidParameterValueError(
parameter='value', value=value, valid_values=valid_resets)
parameter="value", value=value, valid_values=valid_resets
)
value = node_maps.RESET_NODE_VALUE_MAP_REV[value]
target_uri = self._get_reset_action_element().target_uri
self._conn.post(target_uri, data={'ResetType': value})
self._conn.post(target_uri, data={"ResetType": value})
def assemble_node(self):
"""Assemble the composed node."""
@ -239,86 +204,85 @@ class Node(base.ResourceBase):
:returns: A set with the allowed values.
"""
if not self.boot.allowed_values:
LOG.warning('Could not figure out the allowed values for '
'configuring the boot source for Node %s',
self.identity)
return set(node_maps.BOOT_SOURCE_TARGET_MAP_REV)
if not self.boot.boot_source_override_target_allowed_values:
LOG.warning(
"Could not figure out the allowed values for "
"configuring the boot source for Node %s",
self.identity,
)
return set(constants.BOOT_SOURCE_TARGET_VALUE)
return set([node_maps.BOOT_SOURCE_TARGET_MAP[v] for v in
set(node_maps.BOOT_SOURCE_TARGET_MAP).
intersection(self.boot.allowed_values)])
return set(constants.BOOT_SOURCE_TARGET_VALUE).intersection(
self.boot.boot_source_override_target_allowed_values
)
def set_node_boot_source(self, target,
enabled=node_cons.BOOT_SOURCE_ENABLED_ONCE,
mode=None):
def get_allowed_node_boot_mode_values(self):
"""Get the allowed values for the boot source mode.
:returns: A set with the allowed values.
"""
if not self.boot.boot_source_override_mode_allowed_values:
LOG.warning(
"Could not figure out the allowed values for "
"configuring the boot mode for Node %s",
self.identity,
)
return set(constants.BOOT_SOURCE_MODE_VALUE)
return set(constants.BOOT_SOURCE_MODE_VALUE).intersection(
self.boot.boot_source_override_mode_allowed_values
)
def set_node_boot_source(self, target, enabled="Once", mode=None):
"""Set the boot source.
Set the boot source to use on next reboot of the Node.
:param target: The target boot source.
:param enabled: The frequency, whether to set it for the next
reboot only (BOOT_SOURCE_ENABLED_ONCE) or persistent to all
future reboots (BOOT_SOURCE_ENABLED_CONTINUOUS) or disabled
(BOOT_SOURCE_ENABLED_DISABLED).
:param mode: The boot mode, UEFI (BOOT_SOURCE_MODE_UEFI) or
Legacy (BOOT_SOURCE_MODE_LEGACY).
reboot only ("Once") or persistent to all future reboots
("Continuous") or disabled ("Disabled").
:param mode: The boot mode, UEFI ("UEFI") or BIOS ("Legacy").
:raises: InvalidParameterValueError, if any information passed is
invalid.
"""
valid_targets = self.get_allowed_node_boot_source_values()
if target not in valid_targets:
raise exceptions.InvalidParameterValueError(
parameter='target', value=target, valid_values=valid_targets)
parameter="target", value=target, valid_values=valid_targets
)
if enabled not in node_maps.BOOT_SOURCE_ENABLED_MAP_REV:
if enabled not in constants.BOOT_SOURCE_ENABLED_VALUE:
raise exceptions.InvalidParameterValueError(
parameter='enabled', value=enabled,
valid_values=list(node_maps.BOOT_SOURCE_ENABLED_MAP_REV))
parameter="enabled",
value=enabled,
valid_values=constants.BOOT_SOURCE_ENABLED_VALUE,
)
data = {
'Boot': {
'BootSourceOverrideTarget':
node_maps.BOOT_SOURCE_TARGET_MAP_REV[target],
'BootSourceOverrideEnabled':
node_maps.BOOT_SOURCE_ENABLED_MAP_REV[enabled]
"Boot": {
"BootSourceOverrideTarget": target,
"BootSourceOverrideEnabled": enabled,
}
}
if mode is not None:
if mode not in node_maps.BOOT_SOURCE_MODE_MAP_REV:
valid_modes = self.get_allowed_node_boot_mode_values()
if mode not in valid_modes:
raise exceptions.InvalidParameterValueError(
parameter='mode', value=mode,
valid_values=list(node_maps.BOOT_SOURCE_MODE_MAP_REV))
parameter="mode", value=mode, valid_values=valid_modes
)
data['Boot']['BootSourceOverrideMode'] = (
node_maps.BOOT_SOURCE_MODE_MAP_REV[mode])
data["Boot"]["BootSourceOverrideMode"] = mode
self._conn.patch(self.path, data=data)
def _get_system_path(self):
"""Helper function to find the System path"""
return utils.get_sub_resource_path_by(
self, ['Links', 'ComputerSystem'])
@property
@utils.cache_it
def system(self):
"""Property to provide reference to `System` instance
It is calculated once the first time it is queried. On refresh,
this property is reset.
"""
return system.System(
self._conn, self._get_system_path(),
redfish_version=self.redfish_version)
def _get_attach_endpoint_action_element(self):
attach_endpoint_action = self._actions.attach_endpoint
if not attach_endpoint_action:
raise exceptions.MissingActionError(
action='#ComposedNode.AttachEndpoint',
resource=self._path)
action="#ComposedNode.AttachEndpoint", resource=self._path
)
return attach_endpoint_action
def get_allowed_attach_endpoints(self):
@ -343,14 +307,16 @@ class Node(base.ResourceBase):
if endpoint and endpoint not in valid_endpoints:
raise exceptions.InvalidParameterValueError(
parameter='endpoint', value=endpoint,
valid_values=valid_endpoints)
parameter="endpoint",
value=endpoint,
valid_values=valid_endpoints,
)
data = {}
if endpoint is not None:
data['Resource'] = {'@odata.id': endpoint}
data["Resource"] = {"@odata.id": endpoint}
if capacity is not None:
data['CapacityGiB'] = capacity
data["CapacityGiB"] = capacity
self._conn.post(target_uri, data=data)
@ -358,8 +324,8 @@ class Node(base.ResourceBase):
detach_endpoint_action = self._actions.detach_endpoint
if not detach_endpoint_action:
raise exceptions.MissingActionError(
action='#ComposedNode.DetachEndpoint',
resource=self._path)
action="#ComposedNode.DetachEndpoint", resource=self._path
)
return detach_endpoint_action
def get_allowed_detach_endpoints(self):
@ -383,10 +349,12 @@ class Node(base.ResourceBase):
if endpoint not in valid_endpoints:
raise exceptions.InvalidParameterValueError(
parameter='endpoint', value=endpoint,
valid_values=valid_endpoints)
parameter="endpoint",
value=endpoint,
valid_values=valid_endpoints,
)
data = {'Resource': endpoint}
data = {"Resource": endpoint}
self._conn.post(target_uri, data=data)
@ -401,89 +369,93 @@ class Node(base.ResourceBase):
self._conn.delete(self.path)
class NodeCollection(base.ResourceCollectionBase):
class NodeCollection(rsd_lib_base.ResourceCollectionBase):
_actions = NodeCollectionActionsField('Actions', required=True)
_actions = NodeCollectionActionsField("Actions", required=True)
@property
def _resource_type(self):
return Node
def __init__(self, connector, path, redfish_version=None):
"""A class representing a ComposedNodeCollection
:param connector: A Connector instance
:param path: The canonical path to the Node collection resource
:param redfish_version: The version of RedFish. Used to construct
the object according to schema of the given version.
"""
super(NodeCollection, self).__init__(connector, path,
redfish_version)
def _get_compose_action_element(self):
compose_action = self._actions.compose
if not compose_action:
raise exceptions.MissingActionError(
action='#ComposedNodeCollection.Allocate',
resource=self._path)
action="#ComposedNodeCollection.Allocate", resource=self._path
)
return compose_action
def _create_compose_request(self, name=None, description=None,
processor_req=None, memory_req=None,
remote_drive_req=None, local_drive_req=None,
ethernet_interface_req=None,
total_system_core_req=None,
total_system_memory_req=None):
def _create_compose_request(
self,
name=None,
description=None,
processor_req=None,
memory_req=None,
remote_drive_req=None,
local_drive_req=None,
ethernet_interface_req=None,
total_system_core_req=None,
total_system_memory_req=None,
):
request = {}
if name is not None:
request['Name'] = name
request["Name"] = name
if description is not None:
request['Description'] = description
request["Description"] = description
if processor_req is not None:
validate(processor_req,
node_schemas.processor_req_schema)
request['Processors'] = processor_req
validate(processor_req, node_schemas.processor_req_schema)
request["Processors"] = processor_req
if memory_req is not None:
validate(memory_req,
node_schemas.memory_req_schema)
request['Memory'] = memory_req
validate(memory_req, node_schemas.memory_req_schema)
request["Memory"] = memory_req
if remote_drive_req is not None:
validate(remote_drive_req,
node_schemas.remote_drive_req_schema)
request['RemoteDrives'] = remote_drive_req
validate(remote_drive_req, node_schemas.remote_drive_req_schema)
request["RemoteDrives"] = remote_drive_req
if local_drive_req is not None:
validate(local_drive_req,
node_schemas.local_drive_req_schema)
request['LocalDrives'] = local_drive_req
validate(local_drive_req, node_schemas.local_drive_req_schema)
request["LocalDrives"] = local_drive_req
if ethernet_interface_req is not None:
validate(ethernet_interface_req,
node_schemas.ethernet_interface_req_schema)
request['EthernetInterfaces'] = ethernet_interface_req
validate(
ethernet_interface_req,
node_schemas.ethernet_interface_req_schema,
)
request["EthernetInterfaces"] = ethernet_interface_req
if total_system_core_req is not None:
validate(total_system_core_req,
node_schemas.total_system_core_req_schema)
request['TotalSystemCoreCount'] = total_system_core_req
validate(
total_system_core_req,
node_schemas.total_system_core_req_schema,
)
request["TotalSystemCoreCount"] = total_system_core_req
if total_system_memory_req is not None:
validate(total_system_memory_req,
node_schemas.total_system_memory_req_schema)
request['TotalSystemMemoryMiB'] = total_system_memory_req
validate(
total_system_memory_req,
node_schemas.total_system_memory_req_schema,
)
request["TotalSystemMemoryMiB"] = total_system_memory_req
return request
def compose_node(self, name=None, description=None,
processor_req=None, memory_req=None,
remote_drive_req=None, local_drive_req=None,
ethernet_interface_req=None, total_system_core_req=None,
total_system_memory_req=None):
def compose_node(
self,
name=None,
description=None,
processor_req=None,
memory_req=None,
remote_drive_req=None,
local_drive_req=None,
ethernet_interface_req=None,
total_system_core_req=None,
total_system_memory_req=None,
):
"""Compose a node from RackScale hardware
:param name: Name of node
@ -512,15 +484,17 @@ class NodeCollection(base.ResourceCollectionBase):
"""
target_uri = self._get_compose_action_element().target_uri
properties = self._create_compose_request(
name=name, description=description,
name=name,
description=description,
processor_req=processor_req,
memory_req=memory_req,
remote_drive_req=remote_drive_req,
local_drive_req=local_drive_req,
ethernet_interface_req=ethernet_interface_req,
total_system_core_req=total_system_core_req,
total_system_memory_req=total_system_memory_req)
total_system_memory_req=total_system_memory_req,
)
resp = self._conn.post(target_uri, data=properties)
LOG.info("Node created at %s", resp.headers['Location'])
node_url = resp.headers['Location']
LOG.info("Node created at %s", resp.headers["Location"])
node_url = resp.headers["Location"]
return node_url[node_url.find(self._path):]

View File

@ -13,183 +13,201 @@
# under the License.
processor_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'Model': {'type': 'string'},
'TotalCores': {'type': 'number'},
'AchievableSpeedMHz': {'type': 'number'},
'InstructionSet': {
'type': 'string',
'enum': ['x86', 'x86-64', 'IA-64', 'ARM-A32',
'ARM-A64', 'MIPS32', 'MIPS64', 'OEM']
},
'Oem': {
'type': 'object',
'properties': {
'Brand': {
'type': 'string',
'enum': ['E3', 'E5', 'E7', 'X3', 'X5', 'X7', 'I3',
'I5', 'I7', 'Unknown']
"type": "array",
"items": [
{
"type": "object",
"properties": {
"Model": {"type": "string"},
"TotalCores": {"type": "number"},
"AchievableSpeedMHz": {"type": "number"},
"InstructionSet": {
"type": "string",
"enum": [
"x86",
"x86-64",
"IA-64",
"ARM-A32",
"ARM-A64",
"MIPS32",
"MIPS64",
"OEM",
],
},
"Oem": {
"type": "object",
"properties": {
"Brand": {
"type": "string",
"enum": [
"E3",
"E5",
"E7",
"X3",
"X5",
"X7",
"I3",
"I5",
"I7",
"Unknown",
],
},
"Capabilities": {
"type": "array",
"items": [{"type": "string"}],
},
},
'Capabilities': {
'type': 'array',
'items': [{'type': 'string'}]
}
}
},
"Resource": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
"Chassis": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
},
'additionalProperties': False,
}]
"additionalProperties": False,
}
],
}
memory_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'CapacityMiB': {'type': 'number'},
'MemoryDeviceType': {
'type': 'string',
'enum': ['DDR', 'DDR2', 'DDR3', 'DDR4', 'DDR4_SDRAM',
'DDR4E_SDRAM', 'LPDDR4_SDRAM', 'DDR3_SDRAM',
'LPDDR3_SDRAM', 'DDR2_SDRAM', 'DDR2_SDRAM_FB_DIMM',
'DDR2_SDRAM_FB_DIMM_PROBE', 'DDR_SGRAM',
'DDR_SDRAM', 'ROM', 'SDRAM', 'EDO',
'FastPageMode', 'PipelinedNibble']
"type": "array",
"items": [
{
"type": "object",
"properties": {
"CapacityMiB": {"type": "number"},
"MemoryDeviceType": {
"type": "string",
"enum": [
"DDR",
"DDR2",
"DDR3",
"DDR4",
"DDR4_SDRAM",
"DDR4E_SDRAM",
"LPDDR4_SDRAM",
"DDR3_SDRAM",
"LPDDR3_SDRAM",
"DDR2_SDRAM",
"DDR2_SDRAM_FB_DIMM",
"DDR2_SDRAM_FB_DIMM_PROBE",
"DDR_SGRAM",
"DDR_SDRAM",
"ROM",
"SDRAM",
"EDO",
"FastPageMode",
"PipelinedNibble",
],
},
"SpeedMHz": {"type": "number"},
"Manufacturer": {"type": "string"},
"DataWidthBits": {"type": "number"},
"Resource": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
"Chassis": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
},
'SpeedMHz': {'type': 'number'},
'Manufacturer': {'type': 'string'},
'DataWidthBits': {'type': 'number'},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
},
'additionalProperties': False,
}]
"additionalProperties": False,
}
],
}
remote_drive_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'CapacityGiB': {'type': 'number'},
'iSCSIAddress': {'type': 'string'},
'Master': {
'type': 'object',
'properties': {
'Type': {
'type': 'string',
'enum': ['Snapshot', 'Clone']
"type": "array",
"items": [
{
"type": "object",
"properties": {
"CapacityGiB": {"type": "number"},
"iSCSIAddress": {"type": "string"},
"Master": {
"type": "object",
"properties": {
"Type": {
"type": "string",
"enum": ["Snapshot", "Clone"],
},
"Address": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
},
'Address': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
}
}
},
'additionalProperties': False,
}]
},
},
"additionalProperties": False,
}
],
}
local_drive_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'CapacityGiB': {'type': 'number'},
'Type': {
'type': 'string',
'enum': ['HDD', 'SSD']
"type": "array",
"items": [
{
"type": "object",
"properties": {
"CapacityGiB": {"type": "number"},
"Type": {"type": "string", "enum": ["HDD", "SSD"]},
"MinRPM": {"type": "number"},
"SerialNumber": {"type": "string"},
"Interface": {
"type": "string",
"enum": ["SAS", "SATA", "NVMe"],
},
"Resource": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
"Chassis": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
"FabricSwitch": {"type": "boolean"},
},
'MinRPM': {'type': 'number'},
'SerialNumber': {'type': 'string'},
'Interface': {
'type': 'string',
'enum': ['SAS', 'SATA', 'NVMe']
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'FabricSwitch': {'type': 'boolean'}
},
'additionalProperties': False,
}]
"additionalProperties": False,
}
],
}
ethernet_interface_req_schema = {
'type': 'array',
'items': [{
'type': 'object',
'properties': {
'SpeedMbps': {'type': 'number'},
'PrimaryVLAN': {'type': 'number'},
'VLANs': {
'type': 'array',
'additionalItems': {
'type': 'object',
'properties': {
'VLANId': {'type': 'number'},
'Tagged': {'type': 'boolean'}
}
}
"type": "array",
"items": [
{
"type": "object",
"properties": {
"SpeedMbps": {"type": "number"},
"PrimaryVLAN": {"type": "number"},
"VLANs": {
"type": "array",
"additionalItems": {
"type": "object",
"properties": {
"VLANId": {"type": "number"},
"Tagged": {"type": "boolean"},
},
},
},
"Resource": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
"Chassis": {
"type": "object",
"properties": {"@odata.id": {"type": "string"}},
},
},
'Resource': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
},
'Chassis': {
'type': 'object',
'properties': {
'@odata.id': {'type': 'string'}
}
}
},
'additionalProperties': False,
}]
"additionalProperties": False,
}
],
}
total_system_core_req_schema = {
'type': 'number'
}
total_system_core_req_schema = {"type": "number"}
total_system_memory_req_schema = {
'type': 'number'
}
total_system_memory_req_schema = {"type": "number"}

View File

@ -340,11 +340,9 @@ class System(rsd_lib_base.ResourceBase):
:param target: The target boot source.
:param enabled: The frequency, whether to set it for the next
reboot only (BOOT_SOURCE_ENABLED_ONCE) or persistent to all
future reboots (BOOT_SOURCE_ENABLED_CONTINUOUS) or disabled
(BOOT_SOURCE_ENABLED_DISABLED).
:param mode: The boot mode, UEFI (BOOT_SOURCE_MODE_UEFI) or
BIOS (BOOT_SOURCE_MODE_BIOS).
reboot only ("Once") or persistent to all future reboots
("Continuous") or disabled ("Disabled").
:param mode: The boot mode, UEFI ("UEFI") or BIOS ("Legacy").
:raises: InvalidParameterValueError, if any information passed is
invalid.
"""

View File

@ -19,192 +19,299 @@ import mock
import testtools
from sushy import exceptions
from sushy.resources.system import system
from rsd_lib.resources.v2_1.node import constants as node_cons
from rsd_lib.resources.v2_1.node import mappings as node_maps
from rsd_lib import constants
from rsd_lib.resources.v2_1.node import node
from rsd_lib.tests.unit.fakes import request_fakes
class NodeTestCase(testtools.TestCase):
def setUp(self):
super(NodeTestCase, self).setUp()
self.conn = mock.Mock()
with open('rsd_lib/tests/unit/json_samples/v2_1/node.json', 'r') as f:
with open("rsd_lib/tests/unit/json_samples/v2_1/node.json", "r") as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst = node.Node(
self.conn, '/redfish/v1/Nodes/Node1',
redfish_version='1.0.2')
self.conn, "/redfish/v1/Nodes/Node1", redfish_version="1.0.2"
)
def test__parse_attributes(self):
self.node_inst._parse_attributes()
self.assertEqual('1.0.2', self.node_inst.redfish_version)
self.assertEqual('Node #1', self.node_inst.description)
self.assertEqual(node_cons.COMPOSED_NODE_STATE_ALLOCATED,
self.node_inst.composed_node_state)
self.assertEqual('Node1', self.node_inst.identity)
self.assertEqual('Composed Node', self.node_inst.name)
self.assertEqual('fa39d108-7d70-400a-9db2-6940375c31c2',
self.node_inst.uuid)
self.assertEqual(node_cons.NODE_POWER_STATE_ON,
self.node_inst.power_state)
self.assertEqual('Enabled', self.node_inst.status.state)
self.assertEqual('OK', self.node_inst.status.health)
self.assertEqual('OK', self.node_inst.status.health_rollup)
self.assertEqual(32, self.node_inst.memory_summary.size_gib)
self.assertEqual('Enabled', self.node_inst.memory_summary.status.state)
self.assertEqual('OK', self.node_inst.memory_summary.status.health)
self.assertEqual("1.0.2", self.node_inst.redfish_version)
self.assertEqual("Node #1", self.node_inst.description)
self.assertEqual("Allocated", self.node_inst.composed_node_state)
self.assertEqual("Node1", self.node_inst.identity)
self.assertEqual("Composed Node", self.node_inst.name)
self.assertEqual(
'OK', self.node_inst.memory_summary.status.health_rollup)
self.assertEqual(2, self.node_inst.processor_summary.count)
self.assertEqual('Multi-Core Intel(R) Xeon(R) processor 7xxx Series',
self.node_inst.processor_summary.model)
"fa39d108-7d70-400a-9db2-6940375c31c2", self.node_inst.uuid
)
self.assertEqual("On", self.node_inst.power_state)
self.assertEqual("Enabled", self.node_inst.status.state)
self.assertEqual("OK", self.node_inst.status.health)
self.assertEqual("OK", self.node_inst.status.health_rollup)
self.assertEqual(32, self.node_inst.memory.total_system_memory_gib)
self.assertEqual("Enabled", self.node_inst.memory.status.state)
self.assertEqual("OK", self.node_inst.memory.status.health)
self.assertEqual("OK", self.node_inst.memory.status.health_rollup)
self.assertEqual(2, self.node_inst.processors.count)
self.assertEqual(
'Enabled', self.node_inst.processor_summary.status.state)
self.assertEqual('OK', self.node_inst.processor_summary.status.health)
"Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
self.node_inst.processors.model,
)
self.assertEqual("Enabled", self.node_inst.processors.status.state)
self.assertEqual("OK", self.node_inst.processors.status.health)
self.assertEqual("OK", self.node_inst.processors.status.health_rollup)
self.assertEqual(
'OK', self.node_inst.processor_summary.status.health_rollup)
"/redfish/v1/Systems/System1", self.node_inst.links.computer_system
)
self.assertEqual(
'/redfish/v1/Systems/System1', self.node_inst.links.system)
("/redfish/v1/Systems/System1/Processors/CPU1",),
self.node_inst.links.processors,
)
self.assertEqual(
('/redfish/v1/Systems/System1/Processors/CPU1',),
self.node_inst.links.processors)
("/redfish/v1/Systems/System1/Memory/Dimm1",),
self.node_inst.links.memory,
)
self.assertEqual(
('/redfish/v1/Systems/System1/Memory/Dimm1',),
self.node_inst.links.memory)
("/redfish/v1/Systems/System1/EthernetInterfaces/LAN1",),
self.node_inst.links.ethernet_interfaces,
)
self.assertEqual(
('/redfish/v1/Systems/System1/EthernetInterfaces/LAN1',),
self.node_inst.links.ethernet_interfaces)
("/redfish/v1/Chassis/Blade1/Drives/1",),
self.node_inst.links.local_drives,
)
self.assertEqual(
('/redfish/v1/Chassis/Blade1/Drives/1',),
self.node_inst.links.local_drives)
self.assertEqual(
('/redfish/v1/Services/RSS1/Targets/target1',),
self.node_inst.links.remote_drives)
("/redfish/v1/Services/RSS1/Targets/target1",),
self.node_inst.links.remote_drives,
)
def test__parse_attributes_missing_actions(self):
self.node_inst.json.pop('Actions')
self.assertRaisesRegex(
exceptions.MissingAttributeError, 'attribute Actions',
self.node_inst._parse_attributes)
def test__parse_attributes_missing_boot(self):
self.node_inst.json.pop('Boot')
self.assertRaisesRegex(
exceptions.MissingAttributeError, 'attribute Boot',
self.node_inst._parse_attributes)
def test__parse_attributes_missing_reset_target(self):
self.node_inst.json['Actions']['#ComposedNode.Reset'].pop(
'target')
self.node_inst.json.pop("Actions")
self.assertRaisesRegex(
exceptions.MissingAttributeError,
'attribute Actions/#ComposedNode.Reset/target',
self.node_inst._parse_attributes)
"attribute Actions",
self.node_inst._parse_attributes,
)
def test__parse_attributes_missing_reset_target(self):
self.node_inst.json["Actions"]["#ComposedNode.Reset"].pop("target")
self.assertRaisesRegex(
exceptions.MissingAttributeError,
"attribute Actions/#ComposedNode.Reset/target",
self.node_inst._parse_attributes,
)
def test_get__reset_action_element(self):
value = self.node_inst._get_reset_action_element()
self.assertEqual("/redfish/v1/Nodes/Node1/Actions/"
"ComposedNode.Reset",
value.target_uri)
self.assertEqual(["On",
"ForceOff",
"GracefulRestart",
"ForceRestart",
"Nmi",
"ForceOn",
"PushPowerButton",
"GracefulShutdown"
],
value.allowed_values)
self.assertEqual(
"/redfish/v1/Nodes/Node1/Actions/" "ComposedNode.Reset",
value.target_uri,
)
self.assertEqual(
[
"On",
"ForceOff",
"GracefulRestart",
"ForceRestart",
"Nmi",
"ForceOn",
"PushPowerButton",
"GracefulShutdown",
],
value.allowed_values,
)
def test__get_reset_action_element_missing_reset_action(self):
self.node_inst._actions.reset = None
self.assertRaisesRegex(
exceptions.MissingActionError, 'action #ComposedNode.Reset',
self.node_inst._get_reset_action_element)
exceptions.MissingActionError,
"action #ComposedNode.Reset",
self.node_inst._get_reset_action_element,
)
def test__get_assemble_action_element(self):
value = self.node_inst._get_assemble_action_element()
self.assertEqual("/redfish/v1/Nodes/Node1/Actions/"
"ComposedNode.Assemble",
value.target_uri)
self.assertEqual(
"/redfish/v1/Nodes/Node1/Actions/" "ComposedNode.Assemble",
value.target_uri,
)
def test__get_attach_endpoint_action_element(self):
value = self.node_inst._get_attach_endpoint_action_element()
self.assertEqual('/redfish/v1/Nodes/Node1/Actions/'
'ComposedNode.AttachEndpoint',
value.target_uri)
self.assertEqual(
"/redfish/v1/Nodes/Node1/Actions/" "ComposedNode.AttachEndpoint",
value.target_uri,
)
self.assertEqual(('/redfish/v1/Chassis/PCIeSwitchChassis/'
'Drives/Disk.Bay.1',
'/redfish/v1/Chassis/PCIeSwitchChassis/'
'Drives/Disk.Bay.2'),
value.allowed_values)
self.assertEqual(
(
"/redfish/v1/Chassis/PCIeSwitchChassis/" "Drives/Disk.Bay.1",
"/redfish/v1/Chassis/PCIeSwitchChassis/" "Drives/Disk.Bay.2",
),
value.allowed_values,
)
def test__get_detach_endpoint_action_element(self):
value = self.node_inst._get_detach_endpoint_action_element()
self.assertEqual('/redfish/v1/Nodes/Node1/Actions/'
'ComposedNode.DetachEndpoint',
value.target_uri)
self.assertEqual(
"/redfish/v1/Nodes/Node1/Actions/" "ComposedNode.DetachEndpoint",
value.target_uri,
)
self.assertEqual(tuple(['/redfish/v1/Chassis/'
'PCIeSwitchChassis/Drives/Disk.Bay.3']),
value.allowed_values)
self.assertEqual(
tuple(
["/redfish/v1/Chassis/" "PCIeSwitchChassis/Drives/Disk.Bay.3"]
),
value.allowed_values,
)
def test_get_allowed_reset_node_values(self):
values = self.node_inst.get_allowed_reset_node_values()
expected = set([node_cons.RESET_GRACEFUL_SHUTDOWN,
node_cons.RESET_GRACEFUL_RESTART,
node_cons.RESET_FORCE_RESTART,
node_cons.RESET_FORCE_OFF,
node_cons.RESET_FORCE_ON,
node_cons.RESET_ON,
node_cons.RESET_NMI,
node_cons.RESET_PUSH_POWER_BUTTON])
expected = set(
[
"On",
"ForceOff",
"GracefulShutdown",
"GracefulRestart",
"ForceRestart",
"Nmi",
"ForceOn",
"PushPowerButton",
]
)
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
@mock.patch.object(node.LOG, 'warning', autospec=True)
def test_get_allowed_reset_system_values_no_values_specified(
self, mock_log):
@mock.patch.object(node.LOG, "warning", autospec=True)
def test_get_allowed_reset_node_values_no_values_specified(self, mock_log):
self.node_inst._actions.reset.allowed_values = {}
values = self.node_inst.get_allowed_reset_node_values()
# Assert it returns all values if it can't get the specific ones
expected = set([node_cons.RESET_GRACEFUL_SHUTDOWN,
node_cons.RESET_GRACEFUL_RESTART,
node_cons.RESET_FORCE_RESTART,
node_cons.RESET_FORCE_OFF,
node_cons.RESET_FORCE_ON,
node_cons.RESET_ON,
node_cons.RESET_NMI,
node_cons.RESET_PUSH_POWER_BUTTON])
expected = set(constants.RESET_TYPE_VALUE)
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
self.assertEqual(1, mock_log.call_count)
def test_reset_node(self):
self.node_inst.reset_node(node_cons.RESET_FORCE_OFF)
self.node_inst.reset_node("ForceOff")
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset',
data={'ResetType': 'ForceOff'})
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset",
data={"ResetType": "ForceOff"},
)
def test_reset_node_invalid_value(self):
with self.assertRaisesRegex(
exceptions.InvalidParameterValueError,
'The parameter "value" value "invalid-value" is invalid',
):
self.node_inst.reset_node("invalid-value")
def test_get_allowed_node_boot_source_values(self):
values = self.node_inst.get_allowed_node_boot_source_values()
expected = set(["None", "Pxe", "Hdd", "RemoteDrive"])
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
@mock.patch.object(node.LOG, "warning", autospec=True)
def test_get_allowed_node_boot_source_values_no_values_specified(
self, mock_log
):
self.node_inst.boot.boot_source_override_target_allowed_values = None
values = self.node_inst.get_allowed_node_boot_source_values()
# Assert it returns all values if it can't get the specific ones
expected = set(constants.BOOT_SOURCE_TARGET_VALUE)
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
self.assertEqual(1, mock_log.call_count)
def test_get_allowed_node_boot_mode_values(self):
values = self.node_inst.get_allowed_node_boot_mode_values()
expected = set(["Legacy", "UEFI"])
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
@mock.patch.object(node.LOG, "warning", autospec=True)
def test_get_allowed_node_boot_mode_values_no_values_specified(
self, mock_log
):
self.node_inst.boot.boot_source_override_mode_allowed_values = None
values = self.node_inst.get_allowed_node_boot_mode_values()
# Assert it returns all values if it can't get the specific ones
expected = set(constants.BOOT_SOURCE_MODE_VALUE)
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
self.assertEqual(1, mock_log.call_count)
def test_set_node_boot_source(self):
self.node_inst.set_node_boot_source(
target="Pxe", enabled="Continuous", mode="UEFI"
)
self.node_inst._conn.patch.assert_called_once_with(
"/redfish/v1/Nodes/Node1",
data={
"Boot": {
"BootSourceOverrideEnabled": "Continuous",
"BootSourceOverrideTarget": "Pxe",
"BootSourceOverrideMode": "UEFI",
}
},
)
def test_set_node_boot_source_no_mode_specified(self):
self.node_inst.set_node_boot_source(target="Hdd")
self.node_inst._conn.patch.assert_called_once_with(
"/redfish/v1/Nodes/Node1",
data={
"Boot": {
"BootSourceOverrideEnabled": "Once",
"BootSourceOverrideTarget": "Hdd",
}
},
)
def test_set_node_boot_source_invalid_target(self):
with self.assertRaisesRegex(
exceptions.InvalidParameterValueError,
'The parameter "target" value "invalid-target" is invalid',
):
self.node_inst.set_node_boot_source("invalid-target")
def test_set_node_boot_source_invalid_enabled(self):
with self.assertRaisesRegex(
exceptions.InvalidParameterValueError,
'The parameter "enabled" value "invalid-enabled" is invalid',
):
self.node_inst.set_node_boot_source(
"Hdd", enabled="invalid-enabled"
)
def test_set_node_boot_source_invalid_mode(self):
with self.assertRaisesRegex(
exceptions.InvalidParameterValueError,
'The parameter "mode" value "invalid-mode" is invalid',
):
self.node_inst.set_node_boot_source("Hdd", mode="invalid-mode")
def test_assemble_node(self):
self.node_inst.assemble_node()
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.Assemble')
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.Assemble"
)
def test_get_allowed_attach_endpoints(self):
expected = self.node_inst.get_allowed_attach_endpoints()
result = ("/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.1",
"/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.2")
result = (
"/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.1",
"/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.2",
)
self.assertEqual(expected, result)
(self.node_inst._json['Actions']['#ComposedNode.AttachEndpoint']
['Resource@Redfish.AllowableValues']) = []
(
self.node_inst._json["Actions"]["#ComposedNode.AttachEndpoint"][
"Resource@Redfish.AllowableValues"
]
) = []
self.node_inst._parse_attributes()
expected = self.node_inst.get_allowed_attach_endpoints()
result = ()
@ -212,32 +319,44 @@ class NodeTestCase(testtools.TestCase):
def test_attach_endpoint(self):
self.node_inst.attach_endpoint(
endpoint='/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.1',
capacity=100)
endpoint="/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.1",
capacity=100,
)
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachEndpoint',
data={'Resource': {'@odata.id': '/redfish/v1/Chassis/'
'PCIeSwitchChassis/Drives/Disk.Bay.1'},
'CapacityGiB': 100})
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachEndpoint",
data={
"Resource": {
"@odata.id": "/redfish/v1/Chassis/"
"PCIeSwitchChassis/Drives/Disk.Bay.1"
},
"CapacityGiB": 100,
},
)
def test_attach_endpoint_invalid_parameter(self):
self.assertRaises(exceptions.InvalidParameterValueError,
self.node_inst.attach_endpoint,
endpoint='invalid')
self.assertRaises(
exceptions.InvalidParameterValueError,
self.node_inst.attach_endpoint,
endpoint="invalid",
)
def test_attach_endpoint_only_with_capacity_parameter(self):
self.node_inst.attach_endpoint(capacity=100)
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachEndpoint',
data={'CapacityGiB': 100})
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachEndpoint",
data={"CapacityGiB": 100},
)
def test_get_allowed_detach_endpoints(self):
expected = self.node_inst.get_allowed_detach_endpoints()
result = ("/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.3",)
self.assertEqual(expected, result)
(self.node_inst._json['Actions']['#ComposedNode.DetachEndpoint']
['Resource@Redfish.AllowableValues']) = []
(
self.node_inst._json["Actions"]["#ComposedNode.DetachEndpoint"][
"Resource@Redfish.AllowableValues"
]
) = []
self.node_inst._parse_attributes()
expected = self.node_inst.get_allowed_detach_endpoints()
result = ()
@ -245,319 +364,231 @@ class NodeTestCase(testtools.TestCase):
def test_detach_endpoint(self):
self.node_inst.detach_endpoint(
endpoint='/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.3')
endpoint="/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.3"
)
self.node_inst._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachEndpoint',
data={'Resource': '/redfish/v1/Chassis/PCIeSwitchChassis/'
'Drives/Disk.Bay.3'})
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachEndpoint",
data={
"Resource": "/redfish/v1/Chassis/PCIeSwitchChassis/"
"Drives/Disk.Bay.3"
},
)
def test_detach_endpoint_invalid_parameter(self):
self.assertRaises(exceptions.InvalidParameterValueError,
self.node_inst.detach_endpoint,
endpoint='invalid')
def test_reset_node_invalid_value(self):
self.assertRaises(exceptions.InvalidParameterValueError,
self.node_inst.reset_node, 'invalid-value')
def test_get_allowed_node_boot_source_values(self):
values = self.node_inst.get_allowed_node_boot_source_values()
expected = set([node_cons.BOOT_SOURCE_TARGET_NONE,
node_cons.BOOT_SOURCE_TARGET_PXE,
node_cons.BOOT_SOURCE_TARGET_HDD])
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
@mock.patch.object(node.LOG, 'warning', autospec=True)
def test_get_allowed_node_boot_source_values_no_values_specified(
self, mock_log):
self.node_inst.boot.allowed_values = None
values = self.node_inst.get_allowed_node_boot_source_values()
# Assert it returns all values if it can't get the specific ones
expected = set([node_cons.BOOT_SOURCE_TARGET_NONE,
node_cons.BOOT_SOURCE_TARGET_PXE,
node_cons.BOOT_SOURCE_TARGET_HDD])
self.assertEqual(expected, values)
self.assertIsInstance(values, set)
self.assertEqual(1, mock_log.call_count)
def test_set_node_boot_source(self):
self.node_inst.set_node_boot_source(
node_cons.BOOT_SOURCE_TARGET_PXE,
enabled=node_cons.BOOT_SOURCE_ENABLED_CONTINUOUS,
mode=node_cons.BOOT_SOURCE_MODE_UEFI)
self.node_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Nodes/Node1',
data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
'BootSourceOverrideTarget': 'Pxe',
'BootSourceOverrideMode': 'UEFI'}})
def test_set_node_boot_source_no_mode_specified(self):
self.node_inst.set_node_boot_source(
node_cons.BOOT_SOURCE_TARGET_HDD,
enabled=node_cons.BOOT_SOURCE_ENABLED_ONCE)
self.node_inst._conn.patch.assert_called_once_with(
'/redfish/v1/Nodes/Node1',
data={'Boot': {'BootSourceOverrideEnabled': 'Once',
'BootSourceOverrideTarget': 'Hdd'}})
def test_set_node_boot_source_invalid_target(self):
self.assertRaises(exceptions.InvalidParameterValueError,
self.node_inst.set_node_boot_source,
'invalid-target')
def test_set_node_boot_source_invalid_enabled(self):
with self.assertRaisesRegex(
self.assertRaises(
exceptions.InvalidParameterValueError,
'"enabled" value.*{0}'.format(
list(node_maps.BOOT_SOURCE_ENABLED_MAP_REV))):
self.node_inst.set_node_boot_source(
node_cons.BOOT_SOURCE_TARGET_HDD,
enabled='invalid-enabled')
def test__get_system_path_missing_systems_attr(self):
self.node_inst._json.get('Links').pop('ComputerSystem')
self.assertRaisesRegex(
exceptions.MissingAttributeError, 'attribute Links/ComputerSystem',
self.node_inst._get_system_path)
self.node_inst.detach_endpoint,
endpoint="invalid",
)
def test_memory_summary_missing_attr(self):
# | GIVEN |
self.node_inst._json['Memory']['Status'].pop('Health')
self.node_inst._json["Memory"]["Status"].pop("Health")
# | WHEN |
self.node_inst._parse_attributes()
# | THEN |
self.assertEqual(32, self.node_inst.memory_summary.size_gib)
self.assertEqual(None, self.node_inst.memory_summary.status.health)
self.assertEqual('Enabled', self.node_inst.memory_summary.status.state)
self.assertEqual(
'OK', self.node_inst.memory_summary.status.health_rollup)
self.assertEqual(32, self.node_inst.memory.total_system_memory_gib)
self.assertEqual(None, self.node_inst.memory.status.health)
self.assertEqual("Enabled", self.node_inst.memory.status.state)
self.assertEqual("OK", self.node_inst.memory.status.health_rollup)
# | GIVEN |
self.node_inst._json['Memory']['Status'].pop('State')
self.node_inst._json["Memory"]["Status"].pop("State")
# | WHEN |
self.node_inst._parse_attributes()
# | THEN |
self.assertEqual(32, self.node_inst.memory_summary.size_gib)
self.assertEqual(None, self.node_inst.memory_summary.status.health)
self.assertEqual(None, self.node_inst.memory_summary.status.state)
self.assertEqual(
'OK', self.node_inst.memory_summary.status.health_rollup)
self.assertEqual(32, self.node_inst.memory.total_system_memory_gib)
self.assertEqual(None, self.node_inst.memory.status.health)
self.assertEqual(None, self.node_inst.memory.status.state)
self.assertEqual("OK", self.node_inst.memory.status.health_rollup)
# | GIVEN |
self.node_inst._json['Memory']['Status'].pop('HealthRollup')
self.node_inst._json["Memory"]["Status"].pop("HealthRollup")
# | WHEN |
self.node_inst._parse_attributes()
# | THEN |
self.assertEqual(32, self.node_inst.memory_summary.size_gib)
self.assertEqual(None, self.node_inst.memory_summary.status.health)
self.assertEqual(None, self.node_inst.memory_summary.status.state)
self.assertEqual(
None, self.node_inst.memory_summary.status.health_rollup)
self.assertEqual(32, self.node_inst.memory.total_system_memory_gib)
self.assertEqual(None, self.node_inst.memory.status.health)
self.assertEqual(None, self.node_inst.memory.status.state)
self.assertEqual(None, self.node_inst.memory.status.health_rollup)
# | GIVEN |
self.node_inst._json['Memory'].pop('Status')
self.node_inst._json["Memory"].pop("Status")
# | WHEN |
self.node_inst._parse_attributes()
# | THEN |
self.assertEqual(32, self.node_inst.memory_summary.size_gib)
self.assertEqual(None, self.node_inst.memory_summary.status)
self.assertEqual(32, self.node_inst.memory.total_system_memory_gib)
self.assertEqual(None, self.node_inst.memory.status)
# | GIVEN |
self.node_inst._json['Memory'].pop('TotalSystemMemoryGiB')
self.node_inst._json["Memory"].pop("TotalSystemMemoryGiB")
# | WHEN |
self.node_inst._parse_attributes()
# | THEN |
self.assertEqual(None, self.node_inst.memory_summary.size_gib)
self.assertEqual(None, self.node_inst.memory_summary.status)
self.assertEqual(None, self.node_inst.memory.total_system_memory_gib)
self.assertEqual(None, self.node_inst.memory.status)
# | GIVEN |
self.node_inst._json.pop('Memory')
self.node_inst._json.pop("Memory")
# | WHEN |
self.node_inst._parse_attributes()
# | THEN |
self.assertEqual(None, self.node_inst.memory_summary)
def test_system(self):
# | GIVEN |
self.conn.get.return_value.json.reset_mock()
with open('rsd_lib/tests/unit/json_samples/v2_1/system.json',
'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN |
actual_system = self.node_inst.system
# | THEN |
self.assertIsInstance(actual_system,
system.System)
self.conn.get.return_value.json.assert_called_once_with()
# reset mock
self.conn.get.return_value.json.reset_mock()
# | WHEN & THEN |
# tests for same object on invoking subsequently
self.assertIs(actual_system,
self.node_inst.system)
self.conn.get.return_value.json.assert_not_called()
def test_system_on_refresh(self):
# | GIVEN |
with open('rsd_lib/tests/unit/json_samples/v2_1/system.json',
'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN & THEN |
self.assertIsInstance(self.node_inst.system,
system.System)
# On refreshing the system instance...
with open('rsd_lib/tests/unit/json_samples/v2_1/node.json', 'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
self.node_inst.invalidate()
self.node_inst.refresh(force=False)
# | GIVEN |
with open('rsd_lib/tests/unit/json_samples/v2_1/system.json',
'r') as f:
self.conn.get.return_value.json.return_value = json.loads(f.read())
# | WHEN & THEN |
self.assertIsInstance(self.node_inst.system,
system.System)
self.assertEqual(None, self.node_inst.memory)
def test_delete_node(self):
self.node_inst.delete_node()
self.node_inst._conn.delete.assert_called_once_with(
self.node_inst.path)
self.node_inst.path
)
class NodeCollectionTestCase(testtools.TestCase):
def setUp(self):
super(NodeCollectionTestCase, self).setUp()
self.conn = mock.Mock()
with open('rsd_lib/tests/unit/json_samples/v2_1/node_collection.json',
'r') as f:
with open(
"rsd_lib/tests/unit/json_samples/v2_1/node_collection.json", "r"
) as f:
self.conn.get.return_value = request_fakes.fake_request_get(
json.loads(f.read()))
json.loads(f.read())
)
self.conn.post.return_value = request_fakes.fake_request_post(
None, headers={"Location": "https://localhost:8443/"
"redfish/v1/Nodes/1"})
None,
headers={
"Location": "https://localhost:8443/" "redfish/v1/Nodes/1"
},
)
self.node_col = node.NodeCollection(
self.conn, '/redfish/v1/Nodes', redfish_version='1.0.2')
self.conn, "/redfish/v1/Nodes", redfish_version="1.0.2"
)
def test__parse_attributes(self):
self.node_col._parse_attributes()
self.assertEqual('1.0.2', self.node_col.redfish_version)
self.assertEqual('Composed Nodes Collection', self.node_col.name)
self.assertEqual(('/redfish/v1/Nodes/Node1',),
self.node_col.members_identities)
self.assertEqual("1.0.2", self.node_col.redfish_version)
self.assertEqual("Composed Nodes Collection", self.node_col.name)
self.assertEqual(
("/redfish/v1/Nodes/Node1",), self.node_col.members_identities
)
@mock.patch.object(node, 'Node', autospec=True)
@mock.patch.object(node, "Node", autospec=True)
def test_get_member(self, mock_node):
self.node_col.get_member('/redfish/v1/Nodes/Node1')
self.node_col.get_member("/redfish/v1/Nodes/Node1")
mock_node.assert_called_once_with(
self.node_col._conn, '/redfish/v1/Nodes/Node1',
redfish_version=self.node_col.redfish_version)
self.node_col._conn,
"/redfish/v1/Nodes/Node1",
redfish_version=self.node_col.redfish_version,
)
@mock.patch.object(node, 'Node', autospec=True)
@mock.patch.object(node, "Node", autospec=True)
def test_get_members(self, mock_node):
members = self.node_col.get_members()
mock_node.assert_called_once_with(
self.node_col._conn, '/redfish/v1/Nodes/Node1',
redfish_version=self.node_col.redfish_version)
self.node_col._conn,
"/redfish/v1/Nodes/Node1",
redfish_version=self.node_col.redfish_version,
)
self.assertIsInstance(members, list)
self.assertEqual(1, len(members))
def test__get_compose_action_element(self):
value = self.node_col._get_compose_action_element()
self.assertEqual('/redfish/v1/Nodes/Actions/Allocate',
value.target_uri)
self.assertEqual(
"/redfish/v1/Nodes/Actions/Allocate", value.target_uri
)
def test_compose_node_no_reqs(self):
result = self.node_col.compose_node()
self.node_col._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Actions/Allocate', data={})
self.assertEqual(result, '/redfish/v1/Nodes/1')
"/redfish/v1/Nodes/Actions/Allocate", data={}
)
self.assertEqual(result, "/redfish/v1/Nodes/1")
def test_compose_node_reqs(self):
reqs = {
'Name': 'test',
'Description': 'this is a test node',
'Processors': [{
'TotalCores': 4,
'Oem': {
'Brand': 'E7',
'Capabilities': ['sse']
"Name": "test",
"Description": "this is a test node",
"Processors": [
{
"TotalCores": 4,
"Oem": {"Brand": "E7", "Capabilities": ["sse"]},
}
}],
'Memory': [{
'CapacityMiB': 8000
}],
'TotalSystemCoreCount': 8,
'TotalSystemMemoryMiB': 16000
],
"Memory": [{"CapacityMiB": 8000}],
"TotalSystemCoreCount": 8,
"TotalSystemMemoryMiB": 16000,
}
result = self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'E7',
'Capabilities': ['sse']
name="test",
description="this is a test node",
processor_req=[
{
"TotalCores": 4,
"Oem": {"Brand": "E7", "Capabilities": ["sse"]},
}
}],
memory_req=[{'CapacityMiB': 8000}],
],
memory_req=[{"CapacityMiB": 8000}],
total_system_core_req=8,
total_system_memory_req=16000)
total_system_memory_req=16000,
)
self.node_col._conn.post.assert_called_once_with(
'/redfish/v1/Nodes/Actions/Allocate', data=reqs)
self.assertEqual(result, '/redfish/v1/Nodes/1')
"/redfish/v1/Nodes/Actions/Allocate", data=reqs
)
self.assertEqual(result, "/redfish/v1/Nodes/1")
def test_compose_node_invalid_reqs(self):
self.assertRaises(jsonschema.exceptions.ValidationError,
self.node_col.compose_node,
processor_req='invalid')
self.assertRaises(
jsonschema.exceptions.ValidationError,
self.node_col.compose_node,
processor_req="invalid",
)
# Wrong processor Oem Brand
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'Platinum' is not one of \['E3', 'E5'")):
("'Platinum' is not one of \['E3', 'E5'"),
):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'Platinum',
'Capabilities': ['sse']
name="test",
description="this is a test node",
processor_req=[
{
"TotalCores": 4,
"Oem": {"Brand": "Platinum", "Capabilities": ["sse"]},
}
}])
],
)
# Wrong processor Oem Capabilities
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("'sse' is not of type 'array'")):
("'sse' is not of type 'array'"),
):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'E3',
'Capabilities': 'sse'
name="test",
description="this is a test node",
processor_req=[
{
"TotalCores": 4,
"Oem": {"Brand": "E3", "Capabilities": "sse"},
}
}])
],
)
# Wrong processor Oem Capabilities
with self.assertRaisesRegex(
jsonschema.exceptions.ValidationError,
("0 is not of type 'string'")):
("0 is not of type 'string'"),
):
self.node_col.compose_node(
name='test', description='this is a test node',
processor_req=[{
'TotalCores': 4,
'Oem': {
'Brand': 'E3',
'Capabilities': [0]
name="test",
description="this is a test node",
processor_req=[
{
"TotalCores": 4,
"Oem": {"Brand": "E3", "Capabilities": [0]},
}
}])
],
)