Implement node discovery
Adds the ability to do hardware discovery directly through redfish via an extension of the sushy library. Change-Id: Ifb40872c7a8161fa0ed3b8f9da28e0d02f073be5 Implements-Blueprint: node-discovery
This commit is contained in:
parent
ebdaaec93b
commit
e8bfa3b16b
@ -3,3 +3,4 @@
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
pbr>=2.0 # Apache-2.0
|
||||
sushy>=0.1.0 # Apache-2.0
|
||||
|
44
rsd_lib/main.py
Normal file
44
rsd_lib/main.py
Normal file
@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
|
||||
import sushy
|
||||
from sushy.resources import base
|
||||
|
||||
from rsd_lib.resources.node import node
|
||||
|
||||
|
||||
class RSDLib(sushy.Sushy):
|
||||
|
||||
_nodes_path = base.Field(['Nodes', '@odata.id'], required=True)
|
||||
"""NodeCollection path"""
|
||||
|
||||
def get_node_collection(self):
|
||||
"""Get the NodeCollection object
|
||||
|
||||
:raises: MissingAttributeError, if the collection attribute is
|
||||
not found
|
||||
:returns: a NodeCollection object
|
||||
"""
|
||||
return node.NodeCollection(self._conn, self._nodes_path,
|
||||
redfish_version=self.redfish_version)
|
||||
|
||||
def get_node(self, identity):
|
||||
"""Given the identity return a Node object
|
||||
|
||||
:param identity: The identity of the Node resource
|
||||
:returns: The Node object
|
||||
"""
|
||||
return node.Node(self._conn, identity,
|
||||
redfish_version=self.redfish_version)
|
0
rsd_lib/resources/__init__.py
Normal file
0
rsd_lib/resources/__init__.py
Normal file
0
rsd_lib/resources/node/__init__.py
Normal file
0
rsd_lib/resources/node/__init__.py
Normal file
100
rsd_lib/resources/node/constants.py
Normal file
100
rsd_lib/resources/node/constants.py
Normal file
@ -0,0 +1,100 @@
|
||||
# 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'
|
84
rsd_lib/resources/node/mappings.py
Normal file
84
rsd_lib/resources/node/mappings.py
Normal file
@ -0,0 +1,84 @@
|
||||
# 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.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))
|
271
rsd_lib/resources/node/node.py
Normal file
271
rsd_lib/resources/node/node.py
Normal file
@ -0,0 +1,271 @@
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from sushy import exceptions
|
||||
from sushy.resources import base
|
||||
from sushy.resources import common
|
||||
from sushy.resources.system import processor
|
||||
|
||||
from rsd_lib.resources.node import constants as node_cons
|
||||
from rsd_lib.resources.node import mappings as node_maps
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AssembleActionField(base.CompositeField):
|
||||
target_uri = base.Field('target', required=True)
|
||||
|
||||
|
||||
class AttachEndpointActionField(base.CompositeField):
|
||||
allowed_values = base.Field('Resource@Redfish.AllowableValues',
|
||||
adapter=list)
|
||||
|
||||
target_uri = base.Field('target', required=True)
|
||||
|
||||
|
||||
class DetachEndpointActionField(base.CompositeField):
|
||||
allowed_values = base.Field('Resource@Redfish.AllowableValues',
|
||||
adapter=list)
|
||||
|
||||
target_uri = base.Field('target', required=True)
|
||||
|
||||
|
||||
class ActionsField(base.CompositeField):
|
||||
reset = common.ResetActionField('#ComposedNode.Reset')
|
||||
assemble = AssembleActionField('#ComposedNode.Assemble')
|
||||
attach_endpoint = AttachEndpointActionField('#ComposedNode.AttachEndpoint')
|
||||
detach_endpoint = DetachEndpointActionField('#ComposedNode.DetachEndpoint')
|
||||
|
||||
|
||||
class BootField(base.CompositeField):
|
||||
allowed_values = base.Field(
|
||||
'BootSourceOverrideTarget@Redfish.AllowableValues',
|
||||
adapter=list)
|
||||
|
||||
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):
|
||||
health = base.Field(['Status', 'Health'])
|
||||
"""The overall health state of memory.
|
||||
|
||||
This signifies health state of memory along with its dependent resources.
|
||||
"""
|
||||
|
||||
size_gib = base.Field('TotalSystemMemoryGiB', adapter=int)
|
||||
"""The size of memory of the node in GiB.
|
||||
|
||||
This signifies the total installed, operating system-accessible memory
|
||||
(RAM), measured in GiB.
|
||||
"""
|
||||
|
||||
|
||||
class Node(base.ResourceBase):
|
||||
|
||||
boot = BootField('Boot', required=True)
|
||||
"""A dictionary containg the current boot device, frequency and mode"""
|
||||
|
||||
composed_node_state = base.MappedField('ComposedNodeState',
|
||||
node_maps.COMPOSED_NODE_STATE_MAP)
|
||||
"""Current state of assembly process for this node"""
|
||||
|
||||
description = base.Field('Description')
|
||||
"""The node description"""
|
||||
|
||||
identity = base.Field('Id', required=True)
|
||||
"""The node identity string"""
|
||||
|
||||
name = base.Field('Name')
|
||||
"""The node name"""
|
||||
|
||||
power_state = base.MappedField('PowerState',
|
||||
node_maps.NODE_POWER_STATE_MAP)
|
||||
"""The node power state"""
|
||||
|
||||
uuid = base.Field('UUID')
|
||||
"""The node UUID"""
|
||||
|
||||
memory_summary = MemorySummaryField('Memory')
|
||||
"""The summary info of memory of the node in general detail"""
|
||||
|
||||
_processors = None # ref to ProcessorCollection instance
|
||||
|
||||
_actions = ActionsField('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)
|
||||
|
||||
def _get_reset_action_element(self):
|
||||
reset_action = self._actions.reset
|
||||
if not reset_action:
|
||||
raise exceptions.MissingActionError(action='#ComposedNode.Reset',
|
||||
resource=self._path)
|
||||
return reset_action
|
||||
|
||||
def get_allowed_reset_node_values(self):
|
||||
"""Get the allowed values for resetting the node.
|
||||
|
||||
:returns: A set with the allowed values.
|
||||
"""
|
||||
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)
|
||||
|
||||
return set([node_maps.RESET_NODE_VALUE_MAP[v] for v in
|
||||
set(node_maps.RESET_NODE_VALUE_MAP).
|
||||
intersection(reset_action.allowed_values)])
|
||||
|
||||
def reset_node(self, value):
|
||||
"""Reset the node.
|
||||
|
||||
:param value: The target value.
|
||||
:raises: InvalidParameterValueError, if the target value is not
|
||||
allowed.
|
||||
"""
|
||||
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)
|
||||
|
||||
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})
|
||||
|
||||
def get_allowed_node_boot_source_values(self):
|
||||
"""Get the allowed values for changing the boot source.
|
||||
|
||||
: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)
|
||||
|
||||
return set([node_maps.BOOT_SOURCE_TARGET_MAP[v] for v in
|
||||
set(node_maps.BOOT_SOURCE_TARGET_MAP).
|
||||
intersection(self.boot.allowed_values)])
|
||||
|
||||
def set_node_boot_source(self, target,
|
||||
enabled=node_cons.BOOT_SOURCE_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).
|
||||
: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)
|
||||
|
||||
if enabled not in node_maps.BOOT_SOURCE_ENABLED_MAP_REV:
|
||||
raise exceptions.InvalidParameterValueError(
|
||||
parameter='enabled', value=enabled,
|
||||
valid_values=list(node_maps.BOOT_SOURCE_TARGET_MAP_REV))
|
||||
|
||||
data = {
|
||||
'Boot': {
|
||||
'BootSourceOverrideTarget':
|
||||
node_maps.BOOT_SOURCE_TARGET_MAP_REV[target],
|
||||
'BootSourceOverrideEnabled':
|
||||
node_maps.BOOT_SOURCE_ENABLED_MAP_REV[enabled]
|
||||
}
|
||||
}
|
||||
|
||||
if mode is not None:
|
||||
if mode not in node_maps.BOOT_SOURCE_MODE_MAP_REV:
|
||||
raise exceptions.InvalidParameterValueError(
|
||||
parameter='mode', value=mode,
|
||||
valid_values=list(node_maps.BOOT_SOURCE_MODE_MAP_REV))
|
||||
|
||||
data['Boot']['BootSourceOverrideMode'] = (
|
||||
node_maps.BOOT_SOURCE_MODE_MAP_REV[mode])
|
||||
|
||||
self._conn.patch(self.path, data=data)
|
||||
|
||||
def _get_processor_collection_path(self):
|
||||
"""Helper function to find the ProcessorCollection path"""
|
||||
processor_col = self.json.get('Processors')
|
||||
if not processor_col:
|
||||
raise exceptions.MissingAttributeError(attribute='Processors',
|
||||
resource=self._path)
|
||||
return processor_col.get('@odata.id')
|
||||
|
||||
@property
|
||||
def processors(self):
|
||||
"""Property to provide reference to `ProcessorCollection` instance
|
||||
|
||||
It is calculated once when the first time it is queried. On refresh,
|
||||
this property gets reset.
|
||||
"""
|
||||
if self._processors is None:
|
||||
self._processors = processor.ProcessorCollection(
|
||||
self._conn, self._get_processor_collection_path(),
|
||||
redfish_version=self.redfish_version)
|
||||
|
||||
return self._processors
|
||||
|
||||
def refresh(self):
|
||||
super(Node, self).refresh()
|
||||
self._processors = None
|
||||
|
||||
|
||||
class NodeCollection(base.ResourceCollectionBase):
|
||||
|
||||
@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)
|
@ -1,28 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
|
||||
"""
|
||||
test_rsd_lib
|
||||
----------------------------------
|
||||
|
||||
Tests for `rsd_lib` module.
|
||||
"""
|
||||
|
||||
from rsd_lib.tests import base
|
||||
|
||||
|
||||
class TestRsd_lib(base.TestCase):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
0
rsd_lib/tests/unit/__init__.py
Normal file
0
rsd_lib/tests/unit/__init__.py
Normal file
115
rsd_lib/tests/unit/json_samples/node.json
Normal file
115
rsd_lib/tests/unit/json_samples/node.json
Normal file
@ -0,0 +1,115 @@
|
||||
{
|
||||
"@odata.context": "/redfish/v1/$metadata#Nodes/Members/$entity",
|
||||
"@odata.id": "/redfish/v1/Nodes/Node1",
|
||||
"@odata.type": "#ComposedNode.1.1.0.ComposedNode",
|
||||
"Id": "Node1",
|
||||
"Name": "Composed Node",
|
||||
"Description": "Node #1",
|
||||
"UUID": "fa39d108-7d70-400a-9db2-6940375c31c2",
|
||||
"PowerState": "On",
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK",
|
||||
"HealthRollup": "OK"
|
||||
},
|
||||
"Processors": {
|
||||
"Count": 2,
|
||||
"Model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK"
|
||||
}
|
||||
},
|
||||
"Memory": {
|
||||
"TotalSystemMemoryGiB": 32,
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK"
|
||||
}
|
||||
},
|
||||
"ComposedNodeState": "Allocated",
|
||||
"Boot": {
|
||||
"BootSourceOverrideEnabled": "Disabled",
|
||||
"BootSourceOverrideTarget": "None",
|
||||
"BootSourceOverrideTarget@Redfish.AllowableValues": [
|
||||
"None",
|
||||
"Pxe",
|
||||
"Hdd",
|
||||
"RemoteDrive"
|
||||
],
|
||||
"BootSourceOverrideMode": "Legacy",
|
||||
"BootSourceOverrideMode@Redfish.AllowableValues": ["Legacy",
|
||||
"UEFI"]
|
||||
},
|
||||
"Oem": {},
|
||||
"Links": {
|
||||
"ComputerSystem": {
|
||||
"@odata.id": "/redfish/v1/Systems/System1"
|
||||
},
|
||||
"Processors": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/System1/Processors/CPU1"
|
||||
}
|
||||
],
|
||||
"Memory": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/System1/Memory/Dimm1"
|
||||
}
|
||||
],
|
||||
"EthernetInterfaces": [
|
||||
{
|
||||
"@odata.id":
|
||||
"/redfish/v1/Systems/System1/EthernetInterfaces/LAN1"
|
||||
}
|
||||
],
|
||||
"LocalDrives": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Chassis/Blade1/Drives/1"
|
||||
}
|
||||
],
|
||||
"RemoteDrives": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Services/RSS1/Targets/target1"
|
||||
}
|
||||
],
|
||||
"ManagedBy": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Managers/PODM"
|
||||
}
|
||||
],
|
||||
"Oem": {}
|
||||
},
|
||||
"Actions": {
|
||||
"#ComposedNode.Reset": {
|
||||
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset",
|
||||
"ResetType@Redfish.AllowableValues": [
|
||||
"On",
|
||||
"ForceOff",
|
||||
"GracefulRestart",
|
||||
"ForceRestart",
|
||||
"Nmi",
|
||||
"ForceOn",
|
||||
"PushPowerButton",
|
||||
"GracefulShutdown"
|
||||
]
|
||||
},
|
||||
"#ComposedNode.Assemble": {
|
||||
"target": "/redfish/v1/Nodes/Node1/Actions/ComposedNode.Assemble"
|
||||
},
|
||||
"#ComposedNode.AttachEndpoint": {
|
||||
"target":
|
||||
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.AttachEndpoint",
|
||||
"Resource@Redfish.AllowableValues": [
|
||||
{"@odata.id":"/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.1"},
|
||||
{"@odata.id":"/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.2"}
|
||||
]
|
||||
},
|
||||
"#ComposedNode.DetachEndpoint": {
|
||||
"target":
|
||||
"/redfish/v1/Nodes/Node1/Actions/ComposedNode.DetachEndpoint",
|
||||
"Resource@Redfish.AllowableValues": [
|
||||
{"@odata.id":"/redfish/v1/Chassis/PCIeSwitchChassis/Drives/Disk.Bay.3"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
17
rsd_lib/tests/unit/json_samples/node_collection.json
Normal file
17
rsd_lib/tests/unit/json_samples/node_collection.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"@odata.type": "#ComputerSystemCollection.ComposedNodeCollection",
|
||||
"Name": "Composed Nodes Collection",
|
||||
"Members@odata.count": 1,
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Nodes/Node1"
|
||||
}
|
||||
],
|
||||
"@odata.context": "/redfish/v1/$metadata#Nodes",
|
||||
"@odata.id": "/redfish/v1/Nodes",
|
||||
"Actions": {
|
||||
"#ComposedNodeCollection.Allocate": {
|
||||
"target": "/redfish/v1/Nodes/Actions/Allocate"
|
||||
}
|
||||
}
|
||||
}
|
28
rsd_lib/tests/unit/json_samples/processor.json
Normal file
28
rsd_lib/tests/unit/json_samples/processor.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"@odata.type": "#Processor.v1_0_2.Processor",
|
||||
"Id": "CPU1",
|
||||
"Socket": "CPU 1",
|
||||
"ProcessorType": "CPU",
|
||||
"ProcessorArchitecture": "x86",
|
||||
"InstructionSet": "x86-64",
|
||||
"Manufacturer": "Intel(R) Corporation",
|
||||
"Model": "Multi-Core Intel(R) Xeon(R) processor 7xxx Series",
|
||||
"ProcessorID": {
|
||||
"VendorID": "GenuineIntel",
|
||||
"IdentificationRegisters": "0x34AC34DC8901274A",
|
||||
"EffectiveFamily": "0x42",
|
||||
"EffectiveModel": "0x61",
|
||||
"Step": "0x1",
|
||||
"MicrocodeInfo": "0x429943"
|
||||
},
|
||||
"MaxSpeedMHz": 3700,
|
||||
"TotalCores": 8,
|
||||
"TotalThreads": 16,
|
||||
"Status": {
|
||||
"State": "Enabled",
|
||||
"Health": "OK"
|
||||
},
|
||||
"@odata.context": "/redfish/v1/$metadata#Systems/Members/437XR1138R2/Processors/Members/$entity",
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/Processors/CPU1",
|
||||
"@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
12
rsd_lib/tests/unit/json_samples/processor2.json
Normal file
12
rsd_lib/tests/unit/json_samples/processor2.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"@odata.type": "#Processor.v1_0_2.Processor",
|
||||
"Id": "CPU2",
|
||||
"Socket": "CPU 2",
|
||||
"ProcessorType": "CPU",
|
||||
"Status": {
|
||||
"State": "Absent"
|
||||
},
|
||||
"@odata.context": "/redfish/v1/$metadata#Systems/Members/437XR1138R2/Processors/Members/$entity",
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/Processors/CPU2",
|
||||
"@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
16
rsd_lib/tests/unit/json_samples/processor_collection.json
Normal file
16
rsd_lib/tests/unit/json_samples/processor_collection.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"@odata.type": "#ProcssorCollection.ProcessorCollection",
|
||||
"Name": "Processors Collection",
|
||||
"Members@odata.count": 2,
|
||||
"Members": [
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/Processors/CPU1"
|
||||
},
|
||||
{
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/Processors/CPU2"
|
||||
}
|
||||
],
|
||||
"@odata.context": "/redfish/v1/$metadata#Systems/Links/Members/437XR1138R2/Processors/#entity",
|
||||
"@odata.id": "/redfish/v1/Systems/437XR1138R2/Processors",
|
||||
"@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
40
rsd_lib/tests/unit/json_samples/root.json
Normal file
40
rsd_lib/tests/unit/json_samples/root.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"@odata.type": "#ServiceRoot.v1_0_2.ServiceRoot",
|
||||
"Id": "RootService",
|
||||
"Name": "Root Service",
|
||||
"RedfishVersion": "1.0.2",
|
||||
"UUID": "92384634-2938-2342-8820-489239905423",
|
||||
"Systems": {
|
||||
"@odata.id": "/redfish/v1/Systems"
|
||||
},
|
||||
"Chassis": {
|
||||
"@odata.id": "/redfish/v1/Chassis"
|
||||
},
|
||||
"Managers": {
|
||||
"@odata.id": "/redfish/v1/Managers"
|
||||
},
|
||||
"Tasks": {
|
||||
"@odata.id": "/redfish/v1/TaskService"
|
||||
},
|
||||
"SessionService": {
|
||||
"@odata.id": "/redfish/v1/SessionService"
|
||||
},
|
||||
"AccountService": {
|
||||
"@odata.id": "/redfish/v1/AccountService"
|
||||
},
|
||||
"EventService": {
|
||||
"@odata.id": "/redfish/v1/EventService"
|
||||
},
|
||||
"Nodes": {
|
||||
"@odata.id": "/redfish/v1/Nodes"
|
||||
},
|
||||
"Links": {
|
||||
"Sessions": {
|
||||
"@odata.id": "/redfish/v1/SessionService/Sessions"
|
||||
}
|
||||
},
|
||||
"Oem": {},
|
||||
"@odata.context": "/redfish/v1/$metadata#ServiceRoot",
|
||||
"@odata.id": "/redfish/v1/",
|
||||
"@Redfish.Copyright": "Copyright 2014-2016 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
|
||||
}
|
0
rsd_lib/tests/unit/resources/__init__.py
Normal file
0
rsd_lib/tests/unit/resources/__init__.py
Normal file
0
rsd_lib/tests/unit/resources/node/__init__.py
Normal file
0
rsd_lib/tests/unit/resources/node/__init__.py
Normal file
347
rsd_lib/tests/unit/resources/node/test_node.py
Normal file
347
rsd_lib/tests/unit/resources/node/test_node.py
Normal file
@ -0,0 +1,347 @@
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
import mock
|
||||
from sushy import exceptions
|
||||
from sushy.resources.system import processor
|
||||
import testtools
|
||||
|
||||
from rsd_lib.resources.node import constants as node_cons
|
||||
from rsd_lib.resources.node import node
|
||||
|
||||
|
||||
class NodeTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NodeTestCase, self).setUp()
|
||||
self.conn = mock.Mock()
|
||||
with open('rsd_lib/tests/unit/json_samples/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')
|
||||
|
||||
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(32, self.node_inst.memory_summary.size_gib)
|
||||
self.assertEqual("OK", self.node_inst.memory_summary.health)
|
||||
self.assertIsNone(self.node_inst._processors)
|
||||
|
||||
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.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)
|
||||
|
||||
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)
|
||||
|
||||
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])
|
||||
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):
|
||||
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])
|
||||
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._conn.post.assert_called_once_with(
|
||||
'/redfish/v1/Nodes/Node1/Actions/ComposedNode.Reset',
|
||||
data={'ResetType': 'ForceOff'})
|
||||
|
||||
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):
|
||||
self.assertRaises(exceptions.InvalidParameterValueError,
|
||||
self.node_inst.set_node_boot_source,
|
||||
node_cons.BOOT_SOURCE_TARGET_HDD,
|
||||
enabled='invalid-enabled')
|
||||
|
||||
def test__get_processor_collection_path_missing_processors_attr(self):
|
||||
self.node_inst._json.pop('Processors')
|
||||
self.assertRaisesRegex(
|
||||
exceptions.MissingAttributeError, 'attribute Processors',
|
||||
self.node_inst._get_processor_collection_path)
|
||||
|
||||
def test_memory_summary_missing_attr(self):
|
||||
# | GIVEN |
|
||||
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.health)
|
||||
|
||||
# | GIVEN |
|
||||
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.health)
|
||||
|
||||
# | GIVEN |
|
||||
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.health)
|
||||
|
||||
# | GIVEN |
|
||||
self.node_inst._json.pop('Memory')
|
||||
# | WHEN |
|
||||
self.node_inst._parse_attributes()
|
||||
# | THEN |
|
||||
self.assertEqual(None, self.node_inst.memory_summary)
|
||||
|
||||
def test_processors(self):
|
||||
# check for the underneath variable value
|
||||
self.assertIsNone(self.node_inst._processors)
|
||||
# | GIVEN |
|
||||
self.conn.get.return_value.json.reset_mock()
|
||||
with open('rsd_lib/tests/unit/json_samples/processor_collection.json',
|
||||
'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
# | WHEN |
|
||||
actual_processors = self.node_inst.processors
|
||||
# | THEN |
|
||||
self.assertIsInstance(actual_processors,
|
||||
processor.ProcessorCollection)
|
||||
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_processors,
|
||||
self.node_inst.processors)
|
||||
self.conn.get.return_value.json.assert_not_called()
|
||||
|
||||
def test_processors_on_refresh(self):
|
||||
# | GIVEN |
|
||||
with open('rsd_lib/tests/unit/json_samples/processor_collection.json',
|
||||
'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
# | WHEN & THEN |
|
||||
self.assertIsInstance(self.node_inst.processors,
|
||||
processor.ProcessorCollection)
|
||||
|
||||
# On refreshing the system instance...
|
||||
with open('rsd_lib/tests/unit/json_samples/node.json', 'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
self.node_inst.refresh()
|
||||
|
||||
# | WHEN & THEN |
|
||||
self.assertIsNone(self.node_inst._processors)
|
||||
|
||||
# | GIVEN |
|
||||
with open('rsd_lib/tests/unit/json_samples/processor_collection.json',
|
||||
'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
# | WHEN & THEN |
|
||||
self.assertIsInstance(self.node_inst.processors,
|
||||
processor.ProcessorCollection)
|
||||
|
||||
def _setUp_processor_summary(self):
|
||||
self.conn.get.return_value.json.reset_mock()
|
||||
with open('rsd_lib/tests/unit/json_samples/processor_collection.json',
|
||||
'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
|
||||
# fetch processors for the first time
|
||||
self.node_inst.processors
|
||||
|
||||
successive_return_values = []
|
||||
with open('rsd_lib/tests/unit/json_samples/processor.json', 'r') as f:
|
||||
successive_return_values.append(json.loads(f.read()))
|
||||
with open('rsd_lib/tests/unit/json_samples/processor2.json', 'r') as f:
|
||||
successive_return_values.append(json.loads(f.read()))
|
||||
|
||||
self.conn.get.return_value.json.side_effect = successive_return_values
|
||||
|
||||
def test_processor_summary(self):
|
||||
# | GIVEN |
|
||||
self._setUp_processor_summary()
|
||||
# | WHEN |
|
||||
actual_processor_summary = self.node_inst.processors.summary
|
||||
# | THEN |
|
||||
self.assertEqual((16, node_cons.PROCESSOR_ARCH_x86),
|
||||
actual_processor_summary)
|
||||
self.assertEqual(16, actual_processor_summary.count)
|
||||
self.assertEqual(node_cons.PROCESSOR_ARCH_x86,
|
||||
actual_processor_summary.architecture)
|
||||
|
||||
# reset mock
|
||||
self.conn.get.return_value.json.reset_mock()
|
||||
|
||||
# | WHEN & THEN |
|
||||
# tests for same object on invoking subsequently
|
||||
self.assertIs(actual_processor_summary,
|
||||
self.node_inst.processors.summary)
|
||||
self.conn.get.return_value.json.assert_not_called()
|
||||
|
||||
|
||||
class NodeCollectionTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NodeCollectionTestCase, self).setUp()
|
||||
self.conn = mock.Mock()
|
||||
with open('rsd_lib/tests/unit/json_samples/node_collection.json',
|
||||
'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
self.node_col = node.NodeCollection(
|
||||
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)
|
||||
|
||||
@mock.patch.object(node, 'Node', autospec=True)
|
||||
def test_get_member(self, mock_node):
|
||||
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)
|
||||
|
||||
@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.assertIsInstance(members, list)
|
||||
self.assertEqual(1, len(members))
|
50
rsd_lib/tests/unit/test_main.py
Normal file
50
rsd_lib/tests/unit/test_main.py
Normal file
@ -0,0 +1,50 @@
|
||||
# Copyright 2017 Red Hat, 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.
|
||||
|
||||
import json
|
||||
|
||||
import mock
|
||||
from sushy import connector
|
||||
import testtools
|
||||
|
||||
from rsd_lib import main
|
||||
from rsd_lib.resources.node import node
|
||||
|
||||
|
||||
class RSDLibTestCase(testtools.TestCase):
|
||||
|
||||
@mock.patch.object(connector, 'Connector', autospec=True)
|
||||
def setUp(self, mock_connector):
|
||||
super(RSDLibTestCase, self).setUp()
|
||||
self.conn = mock.Mock()
|
||||
mock_connector.return_value = self.conn
|
||||
with open('rsd_lib/tests/unit/json_samples/root.json', 'r') as f:
|
||||
self.conn.get.return_value.json.return_value = json.loads(f.read())
|
||||
self.rsd = main.RSDLib('http://foo.bar:8442', username='foo',
|
||||
password='bar', verify=True)
|
||||
|
||||
@mock.patch.object(node, 'NodeCollection', autospec=True)
|
||||
def test_get_node_collection(self, mock_node_collection):
|
||||
self.rsd.get_node_collection()
|
||||
mock_node_collection.assert_called_once_with(
|
||||
self.rsd._conn, '/redfish/v1/Nodes',
|
||||
redfish_version=self.rsd.redfish_version)
|
||||
|
||||
@mock.patch.object(node, 'Node', autospec=True)
|
||||
def test_get_node(self, mock_node):
|
||||
self.rsd.get_node('fake-node-id')
|
||||
mock_node.assert_called_once_with(
|
||||
self.rsd._conn, 'fake-node-id',
|
||||
redfish_version=self.rsd.redfish_version)
|
Loading…
x
Reference in New Issue
Block a user