diff --git a/doc/source/index.rst b/doc/source/index.rst index b928df57..111f9317 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -9,7 +9,7 @@ Welcome to Sushy's documentation! Contents: .. toctree:: - :maxdepth: 1 + :maxdepth: 2 About Sushy installation diff --git a/doc/source/usage.rst b/doc/source/usage.rst index 2640543f..a0973a09 100644 --- a/doc/source/usage.rst +++ b/doc/source/usage.rst @@ -6,6 +6,10 @@ Usage To use sushy in a project: +---------------------------------------- +Creating and using a sushy system object +---------------------------------------- + .. code-block:: python import logging @@ -17,7 +21,8 @@ To use sushy in a project: LOG.setLevel(logging.DEBUG) LOG.addHandler(logging.StreamHandler()) - s = sushy.Sushy('http://localhost:8000', username='foo', password='bar') + s = sushy.Sushy('http://localhost:8000/redfish/v1', + username='foo', password='bar') # Get the Redfish version print(s.redfish_version) @@ -79,6 +84,74 @@ To use sushy in a project: print(sys_inst.processors.summary) +----------------------------------------- +Creating and using a sushy manager object +----------------------------------------- + +.. code-block:: python + + import logging + + import sushy + + # Enable logging at DEBUG level + LOG = logging.getLogger('sushy') + LOG.setLevel(logging.DEBUG) + LOG.addHandler(logging.StreamHandler()) + + s = sushy.Sushy('http://localhost:8000/redfish/v1', + username='foo', password='bar') + + # Instantiate a manager object + mgr_inst = s.get_manager('BMC') + + # Get the manager name & description + print(mgr_inst.name) + print(mgr_inst.description) + + + # Using manager collections + + + # Instantiate a ManagerCollection object + mgr_col = s.get_manager_collection() + + # Print the ID of the managers available in the collection + print(mgr_col.members_identities) + + # Get a list of manager objects available in the collection + mgr_insts = mgr_col.get_members() + + # Instantiate a manager object, same as getting it directly + # from the s.get_manager() + mgr_inst = mgr_col.get_member(mgr_col.members_identities[0]) + + # Refresh the manager collection object + mgr_col.refresh() + + + # Using manager actions + + + # Get supported graphical console types + print(mgr_inst.get_supported_graphical_console_types()) + + # Get supported serial console types + print(mgr_inst.get_supported_serial_console_types()) + + # Get supported command shell types + print(mgr_inst.get_supported_command_shell_types()) + + # Get a list of allowed manager reset values + print(mgr_inst.get_allowed_reset_manager_values()) + + # Reset the manager + mgr_inst.reset_manager(sushy.RESET_MANAGER_FORCE_RESTART) + + # Refresh the manager object + mgr_inst.refresh() + + If you do not have any real baremetal machine that supports the Redfish protocol you can look at the :ref:`contributing` page to learn how to run a Redfish emulator. diff --git a/sushy/__init__.py b/sushy/__init__.py index d635aae1..afc6c8ad 100644 --- a/sushy/__init__.py +++ b/sushy/__init__.py @@ -18,6 +18,7 @@ import pbr.version from sushy.main import Sushy from sushy.resources.system.constants import * # noqa +from sushy.resources.manager.constants import * # noqa __all__ = ('Sushy',) __version__ = pbr.version.VersionInfo( diff --git a/sushy/main.py b/sushy/main.py index e3a56716..a5e25208 100644 --- a/sushy/main.py +++ b/sushy/main.py @@ -14,21 +14,27 @@ # under the License. from sushy import connector -from sushy import exceptions from sushy.resources import base +from sushy.resources.manager import manager from sushy.resources.system import system class Sushy(base.ResourceBase): - identity = None - """The Redfish system identity""" + identity = base.Field('Id', required=True) + """The Redfish root service identity""" - name = None - """The Redfish system name""" + name = base.Field('Name') + """The Redfish root service name""" - uuid = None - """The Redfish system UUID""" + uuid = base.Field('UUID') + """The Redfish root service UUID""" + + _systems_path = base.Field(['Systems', '@odata.id'], required=True) + """SystemCollection path""" + + _managers_path = base.Field(['Managers', '@odata.id'], required=True) + """ManagerCollection path""" def __init__(self, base_url, username=None, password=None, root_prefix='/redfish/v1/', verify=True): @@ -55,18 +61,8 @@ class Sushy(base.ResourceBase): path=self._root_prefix) def _parse_attributes(self): - self.identity = self.json.get('Id') - self.name = self.json.get('Name') + super(Sushy, self)._parse_attributes() self.redfish_version = self.json.get('RedfishVersion') - self.uuid = self.json.get('UUID') - - def _get_system_collection_path(self): - """Helper function to find the SystemCollection path""" - systems_col = self.json.get('Systems') - if not systems_col: - raise exceptions.MissingAttributeError(attribute='Systems', - resource=self._path) - return systems_col.get('@odata.id') def get_system_collection(self): """Get the SystemCollection object @@ -75,9 +71,8 @@ class Sushy(base.ResourceBase): not found :returns: a SystemCollection object """ - return system.SystemCollection( - self._conn, self._get_system_collection_path(), - redfish_version=self.redfish_version) + return system.SystemCollection(self._conn, self._systems_path, + redfish_version=self.redfish_version) def get_system(self, identity): """Given the identity return a System object @@ -87,3 +82,22 @@ class Sushy(base.ResourceBase): """ return system.System(self._conn, identity, redfish_version=self.redfish_version) + + def get_manager_collection(self): + """Get the ManagerCollection object + + :raises: MissingAttributeError, if the collection attribute is + not found + :returns: a ManagerCollection object + """ + return manager.ManagerCollection(self._conn, self._managers_path, + redfish_version=self.redfish_version) + + def get_manager(self, identity): + """Given the identity return a Manager object + + :param identity: The identity of the Manager resource + :returns: The Manager object + """ + return manager.Manager(self._conn, identity, + redfish_version=self.redfish_version) diff --git a/sushy/resources/common.py b/sushy/resources/common.py new file mode 100644 index 00000000..24322cf6 --- /dev/null +++ b/sushy/resources/common.py @@ -0,0 +1,20 @@ +# 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.resources import base + + +class ResetActionField(base.CompositeField): + allowed_values = base.Field('ResetType@Redfish.AllowableValues', + adapter=list) + + target_uri = base.Field('target', required=True) diff --git a/sushy/resources/manager/__init__.py b/sushy/resources/manager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy/resources/manager/constants.py b/sushy/resources/manager/constants.py new file mode 100644 index 00000000..a1b3a9f6 --- /dev/null +++ b/sushy/resources/manager/constants.py @@ -0,0 +1,78 @@ +# 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/Manager.v1_0_0.json#/definitions/Manager # noqa + +# Manager Reset action constants + +RESET_MANAGER_GRACEFUL_RESTART = 'graceful restart' +RESET_MANAGER_FORCE_RESTART = 'force restart' + +# Manager Type constants + +MANAGER_TYPE_MANAGEMENT_CONTROLLER = 'management controller' +"""A controller used primarily to monitor or manage the operation of + a device or system""" + +MANAGER_TYPE_ENCLOSURE_MANAGER = 'enclosure manager' +"""A controller which provides management functions for a chassis + or group of devices or systems""" + +MANAGER_TYPE_BMC = 'bmc' +"""A controller which provides management functions for a single + computer system""" + +MANAGER_TYPE_RACK_MANAGER = 'rack manager' +"""A controller which provides management functions for a whole or part + of a rack""" + +MANAGER_TYPE_AUXILIARY_CONTROLLER = 'auxiliary controller' +"""A controller which provides management functions for a particular + subsystem or group of devices""" + +# Graphical Console constants + +GRAPHICAL_CONSOLE_KVMIP = 'graphical console kvmip' +"""Graphical Console connection using a KVM-IP (redirection of Keyboard, + Video, Mouse over IP) protocol""" + +GRAPHICAL_CONSOLE_OEM = 'graphical console oem' +"""Graphical Console connection using an OEM-specific protocol""" + +# Serial Console constants + +SERIAL_CONSOLE_SSH = 'serial console ssh' +"""Serial Console connection using the SSH protocol""" + +SERIAL_CONSOLE_TELNET = 'serial console telnet' +"""Serial Console connection using the Telnet protocol""" + +SERIAL_CONSOLE_IPMI = 'serial console ipmi' +"""Serial Console connection using the IPMI Serial-over-LAN (SOL) protocol""" + +SERIAL_CONSOLE_OEM = 'serial console oem' +"""Serial Console connection using an OEM-specific protocol""" + +# Command Shell constants + +COMMAND_SHELL_SSH = 'command shell ssh' +"""Command Shell connection using the SSH protocol""" + +COMMAND_SHELL_TELNET = 'command shell telnet' +"""Command Shell connection using the Telnet protocol""" + +COMMAND_SHELL_IPMI = 'command shell ipmi' +"""Command Shell connection using the IPMI Serial-over-LAN (SOL) protocol""" + +COMMAND_SHELL_OEM = 'command shell oem' +"""Command Shell connection using an OEM-specific protocol""" diff --git a/sushy/resources/manager/manager.py b/sushy/resources/manager/manager.py new file mode 100644 index 00000000..d027947e --- /dev/null +++ b/sushy/resources/manager/manager.py @@ -0,0 +1,197 @@ +# 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.manager import mappings as mgr_maps + +LOG = logging.getLogger(__name__) + + +class ActionsField(base.CompositeField): + reset = common.ResetActionField('#Manager.Reset') + + +class RemoteAccessField(base.CompositeField): + service_enabled = base.Field('ServiceEnabled') + + max_concurrent_sessions = base.Field('MaxConcurrentSessions') + + connect_types_supported = base.Field('ConnectTypesSupported', + adapter=list) + + +class Manager(base.ResourceBase): + + firmware_version = base.Field('FirmwareVersion') + """The manager firmware version""" + + graphical_console = RemoteAccessField('GraphicalConsole') + """A dictionary containing the remote access support service via + graphical console (e.g. KVMIP) and max concurrent sessions + """ + + serial_console = RemoteAccessField('SerialConsole') + """A dictionary containing the remote access support service via + serial console (e.g. Telnet, SSH, IPMI) and max concurrent sessions + """ + + command_shell = RemoteAccessField('CommandShell') + """A dictionary containing the remote access support service via + command shell (e.g. Telnet, SSH) and max concurrent sessions + """ + + description = base.Field('Description') + """The manager description""" + + identity = base.Field('Id', required=True) + """The manager identity string""" + + name = base.Field('Name') + """The manager name""" + + model = base.Field('Model') + """The manager model""" + + manager_type = base.MappedField('ManagerType', + mgr_maps.MANAGER_TYPE_VALUE_MAP) + """The manager type""" + + uuid = base.Field('UUID') + """The manager UUID""" + + _actions = ActionsField('Actions', required=True) + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a Manager + + :param connector: A Connector instance + :param identity: The identity of the Manager resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(Manager, self).__init__(connector, identity, redfish_version) + + def get_supported_graphical_console_types(self): + """Get the supported values for Graphical Console connection types. + + :returns: A set of supported values. + """ + if (not self.graphical_console + or not self.graphical_console.connect_types_supported): + LOG.warning('Could not figure out the supported values for ' + 'remote access via graphical console for Manager %s', + self.identity) + return set(mgr_maps.GRAPHICAL_CONSOLE_VALUE_MAP_REV) + + return set([mgr_maps.GRAPHICAL_CONSOLE_VALUE_MAP[v] for v in + set(mgr_maps.GRAPHICAL_CONSOLE_VALUE_MAP). + intersection(self.graphical_console. + connect_types_supported)]) + + def get_supported_serial_console_types(self): + """Get the supported values for Serial Console connection types. + + :returns: A set of supported values. + """ + if (not self.serial_console + or not self.serial_console.connect_types_supported): + LOG.warning('Could not figure out the supported values for ' + 'remote access via serial console for Manager %s', + self.identity) + return set(mgr_maps.SERIAL_CONSOLE_VALUE_MAP_REV) + + return set([mgr_maps.SERIAL_CONSOLE_VALUE_MAP[v] for v in + set(mgr_maps.SERIAL_CONSOLE_VALUE_MAP). + intersection(self.serial_console.connect_types_supported)]) + + def get_supported_command_shell_types(self): + """Get the supported values for Command Shell connection types. + + :returns: A set of supported values. + """ + if (not self.command_shell + or not self.command_shell.connect_types_supported): + LOG.warning('Could not figure out the supported values for ' + 'remote access via command shell for Manager %s', + self.identity) + return set(mgr_maps.COMMAND_SHELL_VALUE_MAP_REV) + + return set([mgr_maps.COMMAND_SHELL_VALUE_MAP[v] for v in + set(mgr_maps.COMMAND_SHELL_VALUE_MAP). + intersection(self.command_shell.connect_types_supported)]) + + def _get_reset_action_element(self): + reset_action = self._actions.reset + + if not reset_action: + raise exceptions.MissingActionError(action='#Manager.Reset', + resource=self._path) + return reset_action + + def get_allowed_reset_manager_values(self): + """Get the allowed values for resetting the manager. + + :returns: A set of allowed values. + :raises: MissingAttributeError, if Actions/#Manager.Reset attribute + not present. + """ + 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 manager action for Manager %s', self.identity) + return set(mgr_maps.RESET_MANAGER_VALUE_MAP_REV) + + return set([mgr_maps.RESET_MANAGER_VALUE_MAP[v] for v in + set(mgr_maps.RESET_MANAGER_VALUE_MAP). + intersection(reset_action.allowed_values)]) + + def reset_manager(self, value): + """Reset the manager. + + :param value: The target value. + :raises: InvalidParameterValueError, if the target value is not + allowed. + """ + valid_resets = self.get_allowed_reset_manager_values() + if value not in valid_resets: + raise exceptions.InvalidParameterValueError( + parameter='value', value=value, valid_values=valid_resets) + + value = mgr_maps.RESET_MANAGER_VALUE_MAP_REV[value] + target_uri = self._get_reset_action_element().target_uri + + LOG.debug('Resetting the Manager %s ...', self.identity) + self._conn.post(target_uri, data={'ResetType': value}) + LOG.info('The Manager %s is being reset', self.identity) + + +class ManagerCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return Manager + + def __init__(self, connector, path, redfish_version=None): + """A class representing a ManagerCollection + + :param connector: A Connector instance + :param path: The canonical path to the Manager collection resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of the given version. + """ + super(ManagerCollection, self).__init__(connector, path, + redfish_version) diff --git a/sushy/resources/manager/mappings.py b/sushy/resources/manager/mappings.py new file mode 100644 index 00000000..451dc6e1 --- /dev/null +++ b/sushy/resources/manager/mappings.py @@ -0,0 +1,61 @@ +# 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.resources.manager import constants as mgr_cons +from sushy import utils + + +RESET_MANAGER_VALUE_MAP = { + 'GracefulRestart': mgr_cons.RESET_MANAGER_GRACEFUL_RESTART, + 'ForceRestart': mgr_cons.RESET_MANAGER_FORCE_RESTART, +} + +RESET_MANAGER_VALUE_MAP_REV = utils.revert_dictionary(RESET_MANAGER_VALUE_MAP) + +MANAGER_TYPE_VALUE_MAP = { + 'ManagementController': mgr_cons.MANAGER_TYPE_MANAGEMENT_CONTROLLER, + 'EnclosureManager': mgr_cons.MANAGER_TYPE_ENCLOSURE_MANAGER, + 'BMC': mgr_cons.MANAGER_TYPE_BMC, + 'RackManager': mgr_cons.MANAGER_TYPE_RACK_MANAGER, + 'AuxiliaryController': mgr_cons.MANAGER_TYPE_AUXILIARY_CONTROLLER +} + +MANAGER_TYPE_VALUE_MAP_REV = ( + utils.revert_dictionary(MANAGER_TYPE_VALUE_MAP)) + +GRAPHICAL_CONSOLE_VALUE_MAP = { + 'KVMIP': mgr_cons.GRAPHICAL_CONSOLE_KVMIP, + 'Oem': mgr_cons.GRAPHICAL_CONSOLE_OEM, +} + +GRAPHICAL_CONSOLE_VALUE_MAP_REV = ( + utils.revert_dictionary(GRAPHICAL_CONSOLE_VALUE_MAP)) + +SERIAL_CONSOLE_VALUE_MAP = { + 'SSH': mgr_cons.SERIAL_CONSOLE_SSH, + 'Telnet': mgr_cons.SERIAL_CONSOLE_TELNET, + 'IPMI': mgr_cons.SERIAL_CONSOLE_IPMI, + 'Oem': mgr_cons.SERIAL_CONSOLE_OEM, +} + +SERIAL_CONSOLE_VALUE_MAP_REV = ( + utils.revert_dictionary(SERIAL_CONSOLE_VALUE_MAP)) + +COMMAND_SHELL_VALUE_MAP = { + 'SSH': mgr_cons.COMMAND_SHELL_SSH, + 'Telnet': mgr_cons.COMMAND_SHELL_TELNET, + 'IPMI': mgr_cons.COMMAND_SHELL_IPMI, + 'Oem': mgr_cons.COMMAND_SHELL_OEM, +} + +COMMAND_SHELL_VALUE_MAP_REV = ( + utils.revert_dictionary(COMMAND_SHELL_VALUE_MAP)) diff --git a/sushy/resources/system/system.py b/sushy/resources/system/system.py index 038586ba..f9eee1a3 100644 --- a/sushy/resources/system/system.py +++ b/sushy/resources/system/system.py @@ -17,6 +17,7 @@ import logging from sushy import exceptions from sushy.resources import base +from sushy.resources import common from sushy.resources.system import constants as sys_cons from sushy.resources.system import mappings as sys_maps from sushy.resources.system import processor @@ -25,15 +26,8 @@ from sushy.resources.system import processor LOG = logging.getLogger(__name__) -class ResetActionField(base.CompositeField): - allowed_values = base.Field('ResetType@Redfish.AllowableValues', - adapter=list) - - target_uri = base.Field('target', required=True) - - class ActionsField(base.CompositeField): - reset = ResetActionField('#ComputerSystem.Reset') + reset = common.ResetActionField('#ComputerSystem.Reset') class BootField(base.CompositeField): diff --git a/sushy/tests/unit/json_samples/manager.json b/sushy/tests/unit/json_samples/manager.json new file mode 100644 index 00000000..e79e3865 --- /dev/null +++ b/sushy/tests/unit/json_samples/manager.json @@ -0,0 +1,86 @@ +{ + "@odata.type": "#Manager.v1_1_0.Manager", + "Id": "BMC", + "Name": "Manager", + "ManagerType": "BMC", + "Description": "Contoso BMC", + "ServiceEntryPointUUID": "92384634-2938-2342-8820-489239905423", + "UUID": "58893887-8974-2487-2389-841168418919", + "Model": "Joo Janta 200", + "DateTime": "2015-03-13T04:14:33+06:00", + "DateTimeLocalOffset": "+06:00", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "GraphicalConsole": { + "ServiceEnabled": true, + "MaxConcurrentSessions": 2, + "ConnectTypesSupported": [ + "KVMIP" + ] + }, + "SerialConsole": { + "ServiceEnabled": true, + "MaxConcurrentSessions": 1, + "ConnectTypesSupported": [ + "Telnet", + "SSH", + "IPMI" + ] + }, + "CommandShell": { + "ServiceEnabled": true, + "MaxConcurrentSessions": 4, + "ConnectTypesSupported": [ + "Telnet", + "SSH" + ] + }, + "FirmwareVersion": "1.00", + "NetworkProtocol": { + "@odata.id": "/redfish/v1/Managers/BMC/NetworkProtocol" + }, + "EthernetInterfaces": { + "@odata.id": "/redfish/v1/Managers/BMC/NICs" + }, + "SerialInterfaces": { + "@odata.id": "/redfish/v1/Managers/BMC/SerialInterfaces" + }, + "LogServices": { + "@odata.id": "/redfish/v1/Managers/BMC/LogServices" + }, + "VirtualMedia": { + "@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia" + }, + "Links": { + "ManagerForServers": [ + { + "@odata.id": "/redfish/v1/Systems/437XR1138R2" + } + ], + "ManagerForChassis": [ + { + "@odata.id": "/redfish/v1/Chassis/1U" + } + ], + "ManagerInChassis": { + "@odata.id": "/redfish/v1/Chassis/1U" + }, + "Oem": {} + }, + "Actions": { + "#Manager.Reset": { + "target": "/redfish/v1/Managers/BMC/Actions/Manager.Reset", + "ResetType@Redfish.AllowableValues": [ + "ForceRestart", + "GracefulRestart" + ] + }, + "Oem": {} + }, + "Oem": {}, + "@odata.context": "/redfish/v1/$metadata#Manager.Manager", + "@odata.id": "/redfish/v1/Managers/BMC", + "@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." +} diff --git a/sushy/tests/unit/json_samples/manager_collection.json b/sushy/tests/unit/json_samples/manager_collection.json new file mode 100644 index 00000000..f0818846 --- /dev/null +++ b/sushy/tests/unit/json_samples/manager_collection.json @@ -0,0 +1,14 @@ +{ + "@odata.type": "#ManagerCollection.ManagerCollection", + "Name": "Manager Collection", + "Members@odata.count": 1, + "Members": [ + { + "@odata.id": "/redfish/v1/Managers/BMC" + } + ], + "Oem": {}, + "@odata.context": "/redfish/v1/$metadata#Managers", + "@odata.id": "/redfish/v1/Managers", + "@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." +} diff --git a/sushy/tests/unit/resources/manager/__init__.py b/sushy/tests/unit/resources/manager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy/tests/unit/resources/manager/test_manager.py b/sushy/tests/unit/resources/manager/test_manager.py new file mode 100644 index 00000000..1e78be72 --- /dev/null +++ b/sushy/tests/unit/resources/manager/test_manager.py @@ -0,0 +1,235 @@ +# 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 + +import sushy +from sushy import exceptions +from sushy.resources.manager import manager +from sushy.tests.unit import base + + +class ManagerTestCase(base.TestCase): + + def setUp(self): + super(ManagerTestCase, self).setUp() + self.conn = mock.Mock() + with open('sushy/tests/unit/json_samples/manager.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.manager = manager.Manager(self.conn, '/redfish/v1/Managers/BMC', + redfish_version='1.0.2') + + def test__parse_attributes(self): + # | WHEN | + self.manager._parse_attributes() + # | THEN | + self.assertEqual('1.0.2', self.manager.redfish_version) + self.assertEqual('1.00', self.manager.firmware_version) + self.assertEqual(True, self.manager.graphical_console.service_enabled) + self.assertEqual( + 2, self.manager.graphical_console.max_concurrent_sessions) + self.assertEqual(True, self.manager.serial_console.service_enabled) + self.assertEqual( + 1, self.manager.serial_console.max_concurrent_sessions) + self.assertEqual(True, self.manager.command_shell.service_enabled) + self.assertEqual( + 4, self.manager.command_shell.max_concurrent_sessions) + self.assertEqual('Contoso BMC', self.manager.description) + self.assertEqual('BMC', self.manager.identity) + self.assertEqual('Manager', self.manager.name) + self.assertEqual('Joo Janta 200', self.manager.model) + self.assertEqual(sushy.MANAGER_TYPE_BMC, self.manager.manager_type) + self.assertEqual('58893887-8974-2487-2389-841168418919', + self.manager.uuid) + + def test_get_supported_graphical_console_types(self): + # | GIVEN | + expected = set([sushy.GRAPHICAL_CONSOLE_KVMIP]) + # | WHEN | + values = self.manager.get_supported_graphical_console_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_graphical_console_types_for_no_connect_types(self): + # | GIVEN | + graphical_console = self.manager.graphical_console + expected = set([sushy.GRAPHICAL_CONSOLE_KVMIP, + sushy.GRAPHICAL_CONSOLE_OEM]) + + for val in [None, []]: + graphical_console.connect_types_supported = val + # | WHEN | + values = self.manager.get_supported_graphical_console_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_graphical_console_types_missing_graphcon_attr(self): + # | GIVEN | + self.manager.graphical_console = None + expected = set([sushy.GRAPHICAL_CONSOLE_KVMIP, + sushy.GRAPHICAL_CONSOLE_OEM]) + # | WHEN | + values = self.manager.get_supported_graphical_console_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_serial_console_types(self): + # | GIVEN | + expected = set([sushy.SERIAL_CONSOLE_SSH, + sushy.SERIAL_CONSOLE_TELNET, + sushy.SERIAL_CONSOLE_IPMI]) + # | WHEN | + values = self.manager.get_supported_serial_console_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_serial_console_types_for_no_connect_types(self): + # | GIVEN | + serial_console = self.manager.serial_console + expected = set([sushy.SERIAL_CONSOLE_SSH, + sushy.SERIAL_CONSOLE_TELNET, + sushy.SERIAL_CONSOLE_IPMI, + sushy.SERIAL_CONSOLE_OEM]) + + for val in [None, []]: + serial_console.connect_types_supported = val + # | WHEN | + values = self.manager.get_supported_serial_console_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_serial_console_types_missing_serialcon_attr(self): + # | GIVEN | + self.manager.serial_console = None + expected = set([sushy.SERIAL_CONSOLE_SSH, + sushy.SERIAL_CONSOLE_TELNET, + sushy.SERIAL_CONSOLE_IPMI, + sushy.SERIAL_CONSOLE_OEM]) + # | WHEN | + values = self.manager.get_supported_serial_console_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_command_shell_types(self): + # | GIVEN | + expected = set([sushy.COMMAND_SHELL_SSH, + sushy.COMMAND_SHELL_TELNET]) + # | WHEN | + values = self.manager.get_supported_command_shell_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_command_shell_types_for_no_connect_types(self): + # | GIVEN | + command_shell = self.manager.command_shell + expected = set([sushy.COMMAND_SHELL_SSH, + sushy.COMMAND_SHELL_TELNET, + sushy.COMMAND_SHELL_IPMI, + sushy.COMMAND_SHELL_OEM]) + + for val in [None, []]: + command_shell.connect_types_supported = val + # | WHEN | + values = self.manager.get_supported_command_shell_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_supported_command_shell_types_missing_cmdshell_attr(self): + # | GIVEN | + self.manager.command_shell = None + expected = set([sushy.COMMAND_SHELL_SSH, + sushy.COMMAND_SHELL_TELNET, + sushy.COMMAND_SHELL_IPMI, + sushy.COMMAND_SHELL_OEM]) + # | WHEN | + values = self.manager.get_supported_command_shell_types() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_allowed_reset_manager_values(self): + # | GIVEN | + expected = set([sushy.RESET_MANAGER_GRACEFUL_RESTART, + sushy.RESET_MANAGER_FORCE_RESTART]) + # | WHEN | + values = self.manager.get_allowed_reset_manager_values() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_allowed_reset_manager_values_for_no_values_set(self): + # | GIVEN | + self.manager._actions.reset.allowed_values = [] + expected = set([sushy.RESET_MANAGER_GRACEFUL_RESTART, + sushy.RESET_MANAGER_FORCE_RESTART]) + # | WHEN | + values = self.manager.get_allowed_reset_manager_values() + # | THEN | + self.assertEqual(expected, values) + self.assertIsInstance(values, set) + + def test_get_allowed_reset_manager_values_missing_action_reset_attr(self): + # | GIVEN | + self.manager._actions.reset = None + # | WHEN & THEN | + self.assertRaisesRegex( + exceptions.MissingActionError, 'action #Manager.Reset', + self.manager.get_allowed_reset_manager_values) + + def test_reset_manager(self): + self.manager.reset_manager(sushy.RESET_MANAGER_GRACEFUL_RESTART) + self.manager._conn.post.assert_called_once_with( + '/redfish/v1/Managers/BMC/Actions/Manager.Reset', + data={'ResetType': 'GracefulRestart'}) + + def test_reset_manager_with_invalid_value(self): + self.assertRaises(exceptions.InvalidParameterValueError, + self.manager.reset_manager, 'invalid-value') + + +class ManagerCollectionTestCase(base.TestCase): + + def setUp(self): + super(ManagerCollectionTestCase, self).setUp() + self.conn = mock.Mock() + with open('sushy/tests/unit/json_samples/' + 'manager_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + self.managers = manager.ManagerCollection( + self.conn, '/redfish/v1/Managers', redfish_version='1.0.2') + + @mock.patch.object(manager, 'Manager', autospec=True) + def test_get_member(self, Manager_mock): + self.managers.get_member('/redfish/v1/Managers/BMC') + Manager_mock.assert_called_once_with( + self.managers._conn, '/redfish/v1/Managers/BMC', + redfish_version=self.managers.redfish_version) + + @mock.patch.object(manager, 'Manager', autospec=True) + def test_get_members(self, Manager_mock): + members = self.managers.get_members() + Manager_mock.assert_called_once_with( + self.managers._conn, '/redfish/v1/Managers/BMC', + redfish_version=self.managers.redfish_version) + self.assertIsInstance(members, list) + self.assertEqual(1, len(members)) diff --git a/sushy/tests/unit/test_main.py b/sushy/tests/unit/test_main.py index 87504572..6f300bec 100644 --- a/sushy/tests/unit/test_main.py +++ b/sushy/tests/unit/test_main.py @@ -18,8 +18,8 @@ import json import mock from sushy import connector -from sushy import exceptions from sushy import main +from sushy.resources.manager import manager from sushy.resources.system import system from sushy.tests.unit import base @@ -30,12 +30,12 @@ class MainTestCase(base.TestCase): def setUp(self, mock_connector): super(MainTestCase, self).setUp() self.conn = mock.Mock() - mock_connector.return_code = self.conn + mock_connector.return_value = self.conn + with open('sushy/tests/unit/json_samples/root.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) self.root = main.Sushy( 'http://foo.bar:1234', username='foo', password='bar', verify=True) - with open('sushy/tests/unit/json_samples/root.json', 'r') as f: - self.root._json = json.loads(f.read()) mock_connector.assert_called_once_with( 'http://foo.bar:1234', 'foo', 'bar', True) @@ -46,17 +46,8 @@ class MainTestCase(base.TestCase): self.assertEqual('1.0.2', self.root.redfish_version) self.assertEqual('92384634-2938-2342-8820-489239905423', self.root.uuid) - - def test__get_system_collection_path(self): - self.assertEqual( - '/redfish/v1/Systems', self.root._get_system_collection_path()) - - def test__get_system_collection_path_missing_systems_attr(self): - self.root._json.pop('Systems') - self.assertRaisesRegex( - exceptions.MissingAttributeError, - 'The attribute Systems is missing', - self.root._get_system_collection_path) + self.assertEqual('/redfish/v1/Systems', self.root._systems_path) + self.assertEqual('/redfish/v1/Managers', self.root._managers_path) @mock.patch.object(system, 'SystemCollection', autospec=True) def test_get_system_collection(self, mock_system_collection): @@ -71,3 +62,17 @@ class MainTestCase(base.TestCase): mock_system.assert_called_once_with( self.root._conn, 'fake-system-id', redfish_version=self.root.redfish_version) + + @mock.patch.object(manager, 'ManagerCollection', autospec=True) + def test_get_manager_collection(self, ManagerCollection_mock): + self.root.get_manager_collection() + ManagerCollection_mock.assert_called_once_with( + self.root._conn, '/redfish/v1/Managers', + redfish_version=self.root.redfish_version) + + @mock.patch.object(manager, 'Manager', autospec=True) + def test_get_manager(self, Manager_mock): + self.root.get_manager('fake-manager-id') + Manager_mock.assert_called_once_with( + self.root._conn, 'fake-manager-id', + redfish_version=self.root.redfish_version)