diff --git a/releasenotes/notes/add_update_service-b54c9bb0177e3468.yaml b/releasenotes/notes/add_update_service-b54c9bb0177e3468.yaml new file mode 100644 index 00000000..e9dd6620 --- /dev/null +++ b/releasenotes/notes/add_update_service-b54c9bb0177e3468.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds support for the UpdateService resource to the library. + `UpdateService` is responsible for managing firmware updates. diff --git a/sushy/main.py b/sushy/main.py index eb7a5b83..12e26726 100644 --- a/sushy/main.py +++ b/sushy/main.py @@ -24,6 +24,7 @@ from sushy.resources.registry import message_registry_file from sushy.resources.sessionservice import session from sushy.resources.sessionservice import sessionservice from sushy.resources.system import system +from sushy.resources.updateservice import updateservice LOG = logging.getLogger(__name__) @@ -79,6 +80,9 @@ class Sushy(base.ResourceBase): _registries_path = base.Field(['Registries', '@odata.id']) """Registries path""" + _update_service_path = base.Field(['UpdateService', '@odata.id']) + """UpdateService path""" + def __init__(self, base_url, username=None, password=None, root_prefix='/redfish/v1/', verify=True, auth=None, connector=None): @@ -226,6 +230,19 @@ class Sushy(base.ResourceBase): return session.Session(self._conn, identity, redfish_version=self.redfish_version) + def get_update_service(self): + """Get the UpdateService object + + :returns: The UpdateService object + """ + if not self._update_service_path: + raise exceptions.MissingAttributeError( + attribute='UpdateService/@odata.id', resource=self._path) + + return updateservice.UpdateService( + self._conn, self._update_service_path, + redfish_version=self.redfish_version) + def _get_registry_collection(self): """Get MessageRegistryFileCollection object diff --git a/sushy/resources/updateservice/__init__.py b/sushy/resources/updateservice/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy/resources/updateservice/constants.py b/sushy/resources/updateservice/constants.py new file mode 100644 index 00000000..bffa493f --- /dev/null +++ b/sushy/resources/updateservice/constants.py @@ -0,0 +1,26 @@ +# 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 come from the Redfish UpdateService json-schema. +# https://redfish.dmtf.org/schemas/UpdateService.v1_2_2.json + +# Transfer Protocol Type constants + +TRANSFER_PROTOCOL_TYPE_CIFS = 'CIFS' +TRANSFER_PROTOCOL_TYPE_FTP = 'FTP' +TRANSFER_PROTOCOL_TYPE_SFTP = 'SFTP' +TRANSFER_PROTOCOL_TYPE_HTTP = 'HTTP' +TRANSFER_PROTOCOL_TYPE_HTTPS = 'HTTPS' +TRANSFER_PROTOCOL_TYPE_SCP = 'SCP' +TRANSFER_PROTOCOL_TYPE_TFTP = 'TFTP' +TRANSFER_PROTOCOL_TYPE_OEM = 'OEM' +TRANSFER_PROTOCOL_TYPE_NFS = 'NFS' diff --git a/sushy/resources/updateservice/mappings.py b/sushy/resources/updateservice/mappings.py new file mode 100644 index 00000000..4f5d60ed --- /dev/null +++ b/sushy/resources/updateservice/mappings.py @@ -0,0 +1,35 @@ +# 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.updateservice import constants as ups_cons +from sushy import utils + + +TRANSFER_PROTOCOL_TYPE_VALUE_MAP = { + 'Common Internet File System Protocol': + ups_cons.TRANSFER_PROTOCOL_TYPE_CIFS, + 'File Transfer Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_FTP, + 'Secure File Transfer Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_SFTP, + 'Hypertext Transfer Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_HTTP, + 'HTTP Secure Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_HTTPS, + 'Secure File Copy Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_SCP, + 'Trivial File Transfer Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_TFTP, + 'A protocol defined by the manufacturer': + ups_cons.TRANSFER_PROTOCOL_TYPE_OEM, + 'Network File System Protocol': ups_cons.TRANSFER_PROTOCOL_TYPE_NFS +} + +TRANSFER_PROTOCOL_TYPE_VALUE_MAP_REV = ( + utils.revert_dictionary(TRANSFER_PROTOCOL_TYPE_VALUE_MAP)) + +TRANSFER_PROTOCOL_TYPE_VALUE_MAP[ + 'Network File System Protocol'] = ups_cons.TRANSFER_PROTOCOL_TYPE_NFS diff --git a/sushy/resources/updateservice/softwareinventory.py b/sushy/resources/updateservice/softwareinventory.py new file mode 100644 index 00000000..3615bb80 --- /dev/null +++ b/sushy/resources/updateservice/softwareinventory.py @@ -0,0 +1,96 @@ +# 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. + +# This is referred from Redfish standard schema. +# https://redfish.dmtf.org/schemas/SoftwareInventory.v1_2_0.json + +import logging + +from sushy.resources import base +from sushy.resources import common + +LOG = logging.getLogger(__name__) + + +class SoftwareInventory(base.ResourceBase): + + identity = base.Field('Id', required=True) + """The software inventory identity""" + + lowest_supported_version = base.Field('LowestSupportedVersion') + """The lowest supported version of the software""" + + manufacturer = base.Field('Manufacturer') + """The manufacturer of the software""" + + name = base.Field('Name', required=True) + """The software inventory name""" + + release_date = base.Field('ReleaseDate') + """Release date of the software""" + + related_item = base.Field('RelatedItem') + """The ID(s) of the resources associated with the software inventory + item""" + + status = common.StatusField('Status') + """The status of the software inventory""" + + software_id = base.Field('SoftwareId') + """The identity of the software""" + + uefi_device_paths = base.Field('UefiDevicePaths') + """Represents the UEFI Device Path(s)""" + + updateable = base.Field('Updateable') + """Indicates whether this software can be updated by the update + service""" + + version = base.Field('Version') + """The version of the software""" + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a SoftwareInventory + + :param connector: A Connector instance + :param identity: The identity of the SoftwareInventory resources + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of given version. + """ + super(SoftwareInventory, self).__init__( + connector, + identity, + redfish_version) + + +class SoftwareInventoryCollection(base.ResourceCollectionBase): + + name = base.Field('Name') + """The software inventory collection name""" + + description = base.Field('Description') + """The software inventory collection description""" + + @property + def _resource_type(self): + return SoftwareInventory + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a SoftwareInventoryCollection + + :param connector: A Connector instance + :param identity: The identity of SoftwareInventory resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of given version. + """ + super(SoftwareInventoryCollection, self).__init__( + connector, identity, redfish_version) diff --git a/sushy/resources/updateservice/updateservice.py b/sushy/resources/updateservice/updateservice.py new file mode 100644 index 00000000..cd2e90c9 --- /dev/null +++ b/sushy/resources/updateservice/updateservice.py @@ -0,0 +1,155 @@ +# 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. + +# This is referred from Redfish standard schema. +# https://redfish.dmtf.org/schemas/UpdateService.v1_2_2.json + +import logging + +from sushy import exceptions +from sushy.resources import base +from sushy.resources import common +from sushy.resources.updateservice import mappings as up_maps +from sushy.resources.updateservice import softwareinventory +from sushy import utils + +LOG = logging.getLogger(__name__) + + +class SimpleUpdateActionField(common.ActionField): + + image_uri = base.Field('ImageURI') + """The URI of the software image to be installed""" + + targets = base.Field('Targets') + """The array of URIs indicating where the update image is to be""" + \ + """applied""" + + transfer_protocol = base.MappedField( + 'TransferProtocol', + up_maps.TRANSFER_PROTOCOL_TYPE_VALUE_MAP) + """The network protocol used by the Update Service""" + + +class ActionsField(base.CompositeField): + + simple_update = SimpleUpdateActionField( + '#UpdateService.SimpleUpdate') + + +class UpdateService(base.ResourceBase): + + identity = base.Field('Id', required=True) + """The update service identity""" + + http_push_uri = base.Field('HttpPushUri') + """The URI used to perform an HTTP or HTTPS push update to the Update + Service""" + + http_push_uri_targets = base.Field('HttpPushUriTargets') + """The array of URIs indicating the target for applying the""" + \ + """update image""" + + http_push_uri_targets_busy = base.Field('HttpPushUriTargetsBusy') + """This represents if the HttpPushUriTargets property is reserved""" + \ + """by anyclient""" + + name = base.Field('Name', required=True) + """The update service name""" + + service_enabled = base.Field('ServiceEnabled') + """The status of whether this service is enabled""" + + status = common.StatusField('Status') + """The status of the update service""" + + _actions = ActionsField('Actions', required=True) + + def __init__(self, connector, identity, redfish_version=None): + """A class representing a UpdateService + + :param connector: A Connector instance + :param identity: The identity of the UpdateService resource + :param redfish_version: The version of RedFish. Used to construct + the object according to schema of given version + """ + super(UpdateService, self).__init__( + connector, + identity, + redfish_version) + + def _get_simple_update_element(self): + simple_update_action = self._actions.simple_update + if not simple_update_action: + raise exceptions.MissingAttributeError( + action='#UpdateService.SimpleUpdate', + resource=self._path) + return simple_update_action + + def get_allowed_transfer_protocol_values(self): + """Get the allowed values for transfer protocol. + + :returns: A set of allowed values. + :raises: MissingAttributeError, if Actions/#UpdateService.SimpleUpdate + attribute not present. + """ + simple_update_action = self._get_simple_update_element() + + if not simple_update_action.transfer_protocol: + LOG.warning( + 'Could not figure out the allowed values for the simple ' + 'update action for UpdateService %s', self.identity) + return set(up_maps.TRANSFER_PROTOCOL_TYPE_VALUE_MAP_REV) + + return set(up_maps.TRANSFER_PROTOCOL_TYPE_VALUE_MAP[v] for v in + simple_update_action.transfer_protocol if v in + up_maps.TRANSFER_PROTOCOL_TYPE_VALUE_MAP) + + def simple_update(self, image_uri, targets, transfer_protocol): + """Simple Update is used to update software components""" + transfer_protocol = transfer_protocol + + valid_transfer_protocols = self.get_allowed_transfer_protocol_values() + if transfer_protocol not in valid_transfer_protocols: + raise exceptions.InvalidParameterValueError( + parameter='transfer_protocol', value=transfer_protocol, + valid_values=valid_transfer_protocols) + + self._conn.post(data={ + 'ImageURI': image_uri, + 'Targets': targets, + 'TransferProtocol': transfer_protocol}) + + def _get_software_inventory_collection_path(self): + """Helper function to find the SoftwareInventoryCollections path""" + soft_inv_col = self.json.get('SoftwareInventory') + if not soft_inv_col: + raise exceptions.MissingAttributeError( + attribute='SoftwareInventory', resource=self._path) + return soft_inv_col.get('@odata.id') + + @property + @utils.cache_it + def software_inventory(self): + """Property to reference SoftwareInventoryCollection instance""" + return softwareinventory.SoftwareInventoryCollection( + self._conn, self._get_software_inventory_collection_path, + redfish_version=self.redfish_version) + + @property + @utils.cache_it + def firmware_inventory(self): + """Property to reference SoftwareInventoryCollection instance""" + return softwareinventory.SoftwareInventoryCollection( + self._conn, self._get_software_inventory_collection_path, + redfish_version=self.redfish_version) diff --git a/sushy/tests/unit/json_samples/root.json b/sushy/tests/unit/json_samples/root.json index 66f6d558..40d950fc 100644 --- a/sushy/tests/unit/json_samples/root.json +++ b/sushy/tests/unit/json_samples/root.json @@ -27,6 +27,9 @@ "SessionService": { "@odata.id": "/redfish/v1/SessionService" }, + "UpdateService": { + "@odata.id": "/redfish/v1/UpdateService" + }, "AccountService": { "@odata.id": "/redfish/v1/AccountService" }, diff --git a/sushy/tests/unit/json_samples/softwareinventory.json b/sushy/tests/unit/json_samples/softwareinventory.json new file mode 100644 index 00000000..2e3264fa --- /dev/null +++ b/sushy/tests/unit/json_samples/softwareinventory.json @@ -0,0 +1,29 @@ +{ + "@odata.type": "#SoftwareInventory.v1_2_0.SoftwareInventory", + "Id": "BMC", + "Name": "Contoso BMC Firmware", + "Status": { + "State": "Enabled", + "Health": "OK" + }, + "Updateable": true, + "Manufacturer": "Contoso", + "ReleaseDate": "2017-08-22T12:00:00", + "Version": "1.45.455b66-rev4", + "SoftwareId": "1624A9DF-5E13-47FC-874A-DF3AFF143089", + "LowestSupportedVersion": "1.30.367a12-rev1", + "UefiDevicePaths": [ + "BMC(0x1,0x0ABCDEF)" + ], + "RelatedItem": [ + { + "@odata.id": "/redfish/v1/Managers/1" + } + ], + "Actions": { + "Oem": {} + }, + "Oem": {}, + "@odata.context": "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory", + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory/BMC" +} diff --git a/sushy/tests/unit/json_samples/softwareinventory_collection.json b/sushy/tests/unit/json_samples/softwareinventory_collection.json new file mode 100644 index 00000000..71c58e4f --- /dev/null +++ b/sushy/tests/unit/json_samples/softwareinventory_collection.json @@ -0,0 +1,14 @@ +{ + "@odata.type": "#SoftwareInventoryCollection.v1_4_0.SoftwareInventoryCollection", + "@odata.id": "/redfish/v1/UpdateService/SoftwareInventory", + "Name": "Software Inventory Collection", + "Members@odata.count": 2, + "Members": [ + { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory" + }, + { + "@odata.id": "/redfish/v1/UpdateService/SoftwareInventory" + } + ] +} diff --git a/sushy/tests/unit/json_samples/updateservice.json b/sushy/tests/unit/json_samples/updateservice.json new file mode 100644 index 00000000..1a67b5e9 --- /dev/null +++ b/sushy/tests/unit/json_samples/updateservice.json @@ -0,0 +1,30 @@ +{ + "@odata.type": "#UpdateService.v1_2_1.UpdateService", + "Id": "UpdateService", + "Name": "Update service", + "Status": { + "State": "Enabled", + "Health": "OK", + "HealthRollup": "OK" + }, + "ServiceEnabled": true, + "HttpPushUri": "/FWUpdate", + "HttpPushUriTargets": ["/FWUpdate"], + "HttpPushUriTargetsBusy": false, + "FirmwareInventory": { + "@odata.id": "/redfish/v1/UpdateService/FirmwareInventory" + }, + "SoftwareInventory": { + "@odata.id": "/redfish/v1/UpdateService/SoftwareInventory" + }, + "Actions": { + "#UpdateService.SimpleUpdate": { + "target": "/redfish/v1/UpdateService/Actions/SimpleUpdate", + "@Redfish.ActionInfo": "/redfish/v1/UpdateService/SimpleUpdateActionInfo" + }, + "Oem": {} + }, + "Oem": {}, + "@odata.context": "/redfish/v1/$metadata#UpdateService.UpdateService", + "@odata.id": "/redfish/v1/UpdateService" +} diff --git a/sushy/tests/unit/resources/updateservice/__init__.py b/sushy/tests/unit/resources/updateservice/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sushy/tests/unit/resources/updateservice/test_softwareinventory.py b/sushy/tests/unit/resources/updateservice/test_softwareinventory.py new file mode 100644 index 00000000..98536ca0 --- /dev/null +++ b/sushy/tests/unit/resources/updateservice/test_softwareinventory.py @@ -0,0 +1,90 @@ +# 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 import constants as res_cons +from sushy.resources.updateservice import softwareinventory +from sushy.tests.unit import base + + +class SoftwareInventoryTestCase(base.TestCase): + + def setUp(self): + super(SoftwareInventoryTestCase, self).setUp() + conn = mock.Mock() + with open( + 'sushy/tests/unit/json_samples/softwareinventory.json') as f: + conn.get.return_value.json.return_value = json.load(f) + + self.soft_inv = softwareinventory.SoftwareInventory( + conn, + '/redfish/v1/UpdateService/SoftwareInventory/1', + redfish_version='1.3.0') + + def test__parse_attributes(self): + self.soft_inv._parse_attributes() + self.assertEqual('BMC', self.soft_inv.identity) + self.assertEqual( + '1.30.367a12-rev1', + self.soft_inv.lowest_supported_version) + self.assertEqual('Contoso', self.soft_inv.manufacturer) + self.assertEqual('Contoso BMC Firmware', self.soft_inv.name) + self.assertEqual('2017-08-22T12:00:00', self.soft_inv.release_date) + self.assertEqual( + res_cons.STATE_ENABLED, + self.soft_inv.status.state) + self.assertEqual(res_cons.HEALTH_OK, self.soft_inv.status.health) + self.assertEqual( + '1624A9DF-5E13-47FC-874A-DF3AFF143089', + self.soft_inv.software_id) + self.assertTrue(self.soft_inv.updateable) + self.assertEqual('1.45.455b66-rev4', self.soft_inv.version) + + def test__parse_attributes_missing_identity(self): + self.soft_inv.json.pop('Id') + self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute Id', + self.soft_inv._parse_attributes) + + +class SoftwareInventoryCollectionTestCase(base.TestCase): + + def setUp(self): + super(SoftwareInventoryCollectionTestCase, self).setUp() + conn = mock.Mock() + with open('sushy/tests/unit/json_samples/' + 'softwareinventory_collection.json') as f: + conn.get.return_value.json.return_value = json.load(f) + + self.soft_inv_col = softwareinventory.SoftwareInventoryCollection( + conn, '/redfish/v1/UpdateService/SoftwareInventory', + redfish_version='1.3.0') + + def test__parse_attributes(self): + self.soft_inv_col._parse_attributes() + self.assertEqual('1.3.0', self.soft_inv_col.redfish_version) + self.assertEqual( + 'Software Inventory Collection', + self.soft_inv_col.name) + + @mock.patch.object( + softwareinventory, 'SoftwareInventory', autospec=True) + def test_get_member(self, mock_softwareinventory): + path = '/redfish/v1/UpdateService/SoftwareInventory/1' + self.soft_inv_col.get_member(path) + mock_softwareinventory.assert_called_once_with( + self.soft_inv_col._conn, path, + redfish_version=self.soft_inv_col.redfish_version) diff --git a/sushy/tests/unit/resources/updateservice/test_updateservice.py b/sushy/tests/unit/resources/updateservice/test_updateservice.py new file mode 100644 index 00000000..741d81af --- /dev/null +++ b/sushy/tests/unit/resources/updateservice/test_updateservice.py @@ -0,0 +1,106 @@ +# 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 import constants as res_cons +from sushy.resources.updateservice import constants as ups_cons +from sushy.resources.updateservice import softwareinventory +from sushy.resources.updateservice import updateservice +from sushy.tests.unit import base + + +class UpdateServiceTestCase(base.TestCase): + + def setUp(self): + super(UpdateServiceTestCase, self).setUp() + self.conn = mock.Mock() + with open('sushy/tests/unit/json_samples/updateservice.json') as f: + self.conn.get.return_value.json.return_value = json.load(f) + + self.upd_serv = updateservice.UpdateService( + self.conn, '/redfish/v1/UpdateService/UpdateService', + redfish_version='1.3.0') + + def test__parse_attributes(self): + self.upd_serv._parse_attributes() + self.assertEqual('UpdateService', self.upd_serv.identity) + self.assertEqual('/FWUpdate', self.upd_serv.http_push_uri) + self.assertIn('/FWUpdate', self.upd_serv.http_push_uri_targets) + self.assertFalse(self.upd_serv.http_push_uri_targets_busy) + self.assertEqual('Update service', self.upd_serv.name) + self.assertTrue(self.upd_serv.service_enabled) + self.assertEqual(res_cons.STATE_ENABLED, self.upd_serv.status.state) + self.assertEqual(res_cons.HEALTH_OK, self.upd_serv.status.health) + self.assertEqual( + res_cons.HEALTH_OK, + self.upd_serv.status.health_rollup) + + def test__parse_attributes_missing_actions(self): + self.upd_serv.json.pop('Actions') + self.assertRaisesRegex( + exceptions.MissingAttributeError, 'attribute Actions', + self.upd_serv._parse_attributes) + + def test_simple_update(self): + self.upd_serv.simple_update( + image_uri='local.server/update.exe', + targets='/redfish/v1/UpdateService/Actions/SimpleUpdate', + transfer_protocol=ups_cons.TRANSFER_PROTOCOL_TYPE_HTTPS) + self.upd_serv._conn.post.assert_called_once_with( + data={ + 'ImageURI': 'local.server/update.exe', + 'Targets': '/redfish/v1/UpdateService/Actions/SimpleUpdate', + 'TransferProtocol': 'HTTPS'}) + + def test_software_inventory(self): + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('sushy/tests/unit/json_samples/' + 'softwareinventory_collection.json') as f: + self.conn.get.return_value.json.return_value = json.load(f) + # | WHEN | + actual_software_inventory = self.upd_serv.software_inventory + # | THEN | + self.assertIsInstance(actual_software_inventory, + softwareinventory.SoftwareInventoryCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + self.assertIs(actual_software_inventory, + self.upd_serv.software_inventory) + self.conn.get.return_value.json.assert_not_called() + + def test_firmware_inventory(self): + # | GIVEN | + self.conn.get.return_value.json.reset_mock() + with open('sushy/tests/unit/json_samples/' + 'softwareinventory_collection.json') as f: + self.conn.get.return_value.json.return_value = json.load(f) + # | WHEN | + actual_firmware_inventory = self.upd_serv.firmware_inventory + # | THEN | + self.assertIsInstance(actual_firmware_inventory, + softwareinventory.SoftwareInventoryCollection) + self.conn.get.return_value.json.assert_called_once_with() + + # reset mock + self.conn.get.return_value.json.reset_mock() + # | WHEN & THEN | + self.assertIs(actual_firmware_inventory, + self.upd_serv.firmware_inventory) + self.conn.get.return_value.json.assert_not_called() diff --git a/sushy/tests/unit/test_main.py b/sushy/tests/unit/test_main.py index 486616fd..4bc615b0 100644 --- a/sushy/tests/unit/test_main.py +++ b/sushy/tests/unit/test_main.py @@ -27,6 +27,7 @@ from sushy.resources.registry import message_registry_file from sushy.resources.sessionservice import session from sushy.resources.sessionservice import sessionservice from sushy.resources.system import system +from sushy.resources.updateservice import updateservice from sushy.tests.unit import base @@ -142,6 +143,13 @@ class MainTestCase(base.TestCase): self.root._conn, 'asdf', redfish_version=self.root.redfish_version) + @mock.patch.object(updateservice, 'UpdateService', autospec=True) + def test_get_update_service(self, mock_upd_serv): + self.root.get_update_service() + mock_upd_serv.assert_called_once_with( + self.root._conn, '/redfish/v1/UpdateService', + redfish_version=self.root.redfish_version) + @mock.patch.object(message_registry_file, 'MessageRegistryFileCollection', autospec=True) @@ -179,5 +187,10 @@ class BareMinimumMainTestCase(base.TestCase): exceptions.MissingAttributeError, 'SessionService/@odata.id', self.root.get_session_service) + def test_get_update_service_when_updateservice_attr_absent(self): + self.assertRaisesRegex( + exceptions.MissingAttributeError, + 'UpdateService/@odata.id', self.root.get_update_service) + def test__get_registry_collection_when_registries_attr_absent(self): self.assertIsNone(self.root._get_registry_collection())