From 7708ef0f9c866d9ca052e9074a949d8a6a03c1d5 Mon Sep 17 00:00:00 2001 From: ankit Date: Wed, 14 Jun 2017 12:51:06 +0000 Subject: [PATCH] Redfish: Adds 'reset_ilo_credential' for redfish systems This commit provides functionality to update credentials on redish systems. Change-Id: I110f0780d1b894ce9cd7f06d32a39c3a7738e4b6 --- proliantutils/ilo/client.py | 1 + proliantutils/redfish/main.py | 10 +++ proliantutils/redfish/redfish.py | 23 ++++++ .../resources/account_service/__init__.py | 0 .../resources/account_service/account.py | 50 +++++++++++++ .../account_service/account_service.py | 44 ++++++++++++ .../tests/redfish/json_samples/account.json | 31 ++++++++ .../json_samples/account_collection.json | 15 ++++ .../redfish/json_samples/account_service.json | 24 +++++++ .../resources/account_service/__init__.py | 0 .../resources/account_service/test_account.py | 72 +++++++++++++++++++ .../account_service/test_account_service.py | 67 +++++++++++++++++ proliantutils/tests/redfish/test_main.py | 10 +++ proliantutils/tests/redfish/test_redfish.py | 72 +++++++++++++++++++ 14 files changed, 419 insertions(+) create mode 100644 proliantutils/redfish/resources/account_service/__init__.py create mode 100644 proliantutils/redfish/resources/account_service/account.py create mode 100644 proliantutils/redfish/resources/account_service/account_service.py create mode 100644 proliantutils/tests/redfish/json_samples/account.json create mode 100644 proliantutils/tests/redfish/json_samples/account_collection.json create mode 100644 proliantutils/tests/redfish/json_samples/account_service.json create mode 100644 proliantutils/tests/redfish/resources/account_service/__init__.py create mode 100644 proliantutils/tests/redfish/resources/account_service/test_account.py create mode 100644 proliantutils/tests/redfish/resources/account_service/test_account_service.py diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index 7db735e..0414141 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -76,6 +76,7 @@ SUPPORTED_REDFISH_METHODS = [ 'set_one_time_boot', 'update_persistent_boot', 'set_pending_boot_mode', + 'reset_ilo_credential', ] LOG = log.get_logger(__name__) diff --git a/proliantutils/redfish/main.py b/proliantutils/redfish/main.py index ee952ea..a9f0f9f 100644 --- a/proliantutils/redfish/main.py +++ b/proliantutils/redfish/main.py @@ -16,6 +16,7 @@ __author__ = 'HPE' import sushy +from proliantutils.redfish.resources.account_service import account_service from proliantutils.redfish.resources.manager import manager from proliantutils.redfish.resources.system import system from proliantutils.redfish.resources import update_service @@ -63,3 +64,12 @@ class HPESushy(sushy.Sushy): return (update_service. HPEUpdateService(self._conn, update_service_url, redfish_version=self.redfish_version)) + + def get_account_service(self): + """Return a HPEAccountService object""" + account_service_url = utils.get_subresource_path_by(self, + 'AccountService') + + return (account_service. + HPEAccountService(self._conn, account_service_url, + redfish_version=self.redfish_version)) diff --git a/proliantutils/redfish/redfish.py b/proliantutils/redfish/redfish.py index 2720f58..0bffd67 100644 --- a/proliantutils/redfish/redfish.py +++ b/proliantutils/redfish/redfish.py @@ -117,6 +117,7 @@ class RedfishOperations(operations.IloOperations): # for error reporting purpose self.host = redfish_controller_ip self._root_prefix = root_prefix + self._username = username try: self._sushy = main.HPESushy( @@ -570,3 +571,25 @@ class RedfishOperations(operations.IloOperations): {'device': device, 'error': str(e)}) LOG.debug(msg) raise exception.IloError(msg) + + def reset_ilo_credential(self, password): + """Resets the iLO password. + + :param password: The password to be set. + :raises: IloError, if account not found or on an error from iLO. + """ + try: + acc_service = self._sushy.get_account_service() + member = acc_service.accounts.get_member_details(self._username) + if member is None: + msg = (self._("No account found with username: %s") + % self._username) + LOG.debug(msg) + raise exception.IloError(msg) + member.update_credentials(password) + except sushy.exceptions.SushyError as e: + msg = (self._('The Redfish controller failed to update ' + 'credentials for %(username)s. Error %(error)s') % + {'username': self._username, 'error': str(e)}) + LOG.debug(msg) + raise exception.IloError(msg) diff --git a/proliantutils/redfish/resources/account_service/__init__.py b/proliantutils/redfish/resources/account_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/proliantutils/redfish/resources/account_service/account.py b/proliantutils/redfish/resources/account_service/account.py new file mode 100644 index 0000000..2b60814 --- /dev/null +++ b/proliantutils/redfish/resources/account_service/account.py @@ -0,0 +1,50 @@ +# Copyright 2017 Hewlett Packard Enterprise Development LP +# +# 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. + +__author__ = 'HPE' + +from sushy.resources import base + + +class HPEAccount(base.ResourceBase): + + username = base.Field('UserName') + + def update_credentials(self, password): + """Update credentials of a redfish system + + :param password: password to be updated + """ + data = { + 'Password': password, + } + self._conn.patch(self.path, data=data) + + +class HPEAccountCollection(base.ResourceCollectionBase): + + @property + def _resource_type(self): + return HPEAccount + + def get_member_details(self, username): + """Returns the HPEAccount object + + :param username: username of account + :returns: HPEAccount object if criterion matches, None otherwise + """ + members = self.get_members() + for member in members: + if member.username == username: + return member diff --git a/proliantutils/redfish/resources/account_service/account_service.py b/proliantutils/redfish/resources/account_service/account_service.py new file mode 100644 index 0000000..c8ca865 --- /dev/null +++ b/proliantutils/redfish/resources/account_service/account_service.py @@ -0,0 +1,44 @@ +# Copyright 2017 Hewlett Packard Enterprise Development LP +# +# 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 + +from proliantutils.redfish.resources.account_service import account +from proliantutils.redfish import utils + + +class HPEAccountService(base.ResourceBase): + """Class that extends the functionality of AccountService resource class + + This class extends the functionality of Account resource class + from sushy + """ + + _accounts = None + + @property + def accounts(self): + """Property to provide instance of HPEAccountCollection + + """ + if self._accounts is None: + self._accounts = account.HPEAccountCollection( + self._conn, utils.get_subresource_path_by(self, 'Accounts'), + redfish_version=self.redfish_version) + + return self._accounts + + def refresh(self): + super(HPEAccountService, self).refresh() + self._accounts = None diff --git a/proliantutils/tests/redfish/json_samples/account.json b/proliantutils/tests/redfish/json_samples/account.json new file mode 100644 index 0000000..1c8361d --- /dev/null +++ b/proliantutils/tests/redfish/json_samples/account.json @@ -0,0 +1,31 @@ +{ + "@odata.context": "/redfish/v1/$metadata#AccountService/Accounts/Members/$entity", + "@odata.etag": "W/\"BA0D308D\"", + "@odata.id": "/redfish/v1/AccountService/Accounts/1/", + "@odata.type": "#ManagerAccount.v1_0_0.ManagerAccount", + "Description": "iLO User Account", + "Id": "1", + "Name": "User Account", + "Oem": + { + "Hpe": + { + "@odata.type": "#HpeiLOAccount.v2_0_0.HpeiLOAccount", + "LoginName": "foo", + "Privileges": + { + "HostBIOSConfigPriv": true, + "HostNICConfigPriv": true, + "HostStorageConfigPriv": true, + "LoginPriv": true, + "RemoteConsolePriv": true, + "SystemRecoveryConfigPriv": true, + "UserConfigPriv": true, + "VirtualMediaPriv": true, + "VirtualPowerAndResetPriv": true, + "iLOConfigPriv": true + } + } + }, + "UserName": "foo" +} diff --git a/proliantutils/tests/redfish/json_samples/account_collection.json b/proliantutils/tests/redfish/json_samples/account_collection.json new file mode 100644 index 0000000..60d765e --- /dev/null +++ b/proliantutils/tests/redfish/json_samples/account_collection.json @@ -0,0 +1,15 @@ +{ + "@odata.context": "/redfish/v1/$metadata#Accounts", + "@odata.etag": "W/\"72D11D4D\"", + "@odata.id": "/redfish/v1/AccountService/Accounts/", + "@odata.type": "#ManagerAccountCollection.ManagerAccountCollection", + "Description": "iLO User Accounts", + "Members": + [ + { + "@odata.id": "/redfish/v1/AccountService/Accounts/1/" + } + ], + "Members@odata.count": 1, + "Name": "Accounts" +} diff --git a/proliantutils/tests/redfish/json_samples/account_service.json b/proliantutils/tests/redfish/json_samples/account_service.json new file mode 100644 index 0000000..99547a2 --- /dev/null +++ b/proliantutils/tests/redfish/json_samples/account_service.json @@ -0,0 +1,24 @@ +{ + "@odata.context": "/redfish/v1/$metadata#AccountService", + "@odata.etag": "W/\"677A002B\"", + "@odata.id": "/redfish/v1/AccountService/", + "@odata.type": "#AccountService.v1_0_2.AccountService", + "Accounts": + { + "@odata.id": "/redfish/v1/AccountService/Accounts/" + }, + "Description": "iLO User Accounts", + "Id": "AccountService", + "Name": "Account Service", + "Oem": + { + "Hpe": + { + "@odata.type": "#HpeiLOAccountService.v2_0_0.HpeiLOAccountService", + "AuthFailureDelayTimeSeconds": 10, + "AuthFailureLoggingThreshold": 3, + "AuthFailuresBeforeDelay": 1, + "MinPasswordLength": 8 + } + } +} diff --git a/proliantutils/tests/redfish/resources/account_service/__init__.py b/proliantutils/tests/redfish/resources/account_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/proliantutils/tests/redfish/resources/account_service/test_account.py b/proliantutils/tests/redfish/resources/account_service/test_account.py new file mode 100644 index 0000000..27b1bb9 --- /dev/null +++ b/proliantutils/tests/redfish/resources/account_service/test_account.py @@ -0,0 +1,72 @@ +# Copyright 2017 Hewlett Packard Enterprise Development LP +# +# 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. + +__author__ = 'HPE' + +import json + +import mock +import testtools + +from proliantutils.redfish.resources.account_service import account + + +class HPEAccountCollectionTestCase(testtools.TestCase): + + def setUp(self): + super(HPEAccountCollectionTestCase, self).setUp() + self.conn = mock.MagicMock() + with open('proliantutils/tests/redfish/' + 'json_samples/account_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = ( + json.loads(f.read())) + + self.account_coll_obj = account.HPEAccountCollection( + self.conn, '/redfish/v1/AccountService/Accounts', + redfish_version='1.0.2') + + def test_get_member_details(self): + with open('proliantutils/tests/redfish/' + 'json_samples/account.json', 'r') as f: + self.conn.get.return_value.json.return_value = ( + json.loads(f.read())) + + obj = self.account_coll_obj.get_member_details('foo') + self.assertIsInstance(obj, account.HPEAccount) + self.assertIsNone(self.account_coll_obj.get_member_details('bar')) + + +class HPEAccountTestCase(testtools.TestCase): + + def setUp(self): + super(HPEAccountTestCase, self).setUp() + self.conn = mock.MagicMock() + with open('proliantutils/tests/redfish/' + 'json_samples/account.json', 'r') as f: + account_json = json.loads(f.read()) + + self.conn.get.return_value.json.return_value = account_json + + self.acc_inst = account.HPEAccount( + self.conn, '/redfish/v1/AccountService/Accounts/1', + redfish_version='1.0.2') + + def test_attributes(self): + self.assertEqual('foo', self.acc_inst.username) + + def test_update_credentials(self): + target_uri = '/redfish/v1/AccountService/Accounts/1' + self.acc_inst.update_credentials('fake-password') + self.acc_inst._conn.patch.assert_called_once_with( + target_uri, data={'Password': 'fake-password'}) diff --git a/proliantutils/tests/redfish/resources/account_service/test_account_service.py b/proliantutils/tests/redfish/resources/account_service/test_account_service.py new file mode 100644 index 0000000..bd68e97 --- /dev/null +++ b/proliantutils/tests/redfish/resources/account_service/test_account_service.py @@ -0,0 +1,67 @@ +# Copyright 2017 Hewlett Packard Enterprise Development LP +# +# 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. + +__author__ = 'HPE' + +import json + +import mock +import testtools + +from proliantutils.redfish.resources.account_service import account +from proliantutils.redfish.resources.account_service import account_service + + +class HPEAccountServiceTestCase(testtools.TestCase): + + def setUp(self): + super(HPEAccountServiceTestCase, self).setUp() + self.conn = mock.MagicMock() + with open('proliantutils/tests/redfish/' + 'json_samples/account_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.acc_inst = account_service.HPEAccountService( + self.conn, '/redfish/v1/AccountService', + redfish_version='1.0.2') + + def test_accounts(self): + self.assertIsNone(self.acc_inst._accounts) + + self.conn.get.return_value.json.reset_mock() + with open('proliantutils/tests/redfish/' + 'json_samples/account_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + accounts = self.acc_inst.accounts + self.assertIsInstance(accounts, account.HPEAccountCollection) + + def test_accounts_on_refresh(self): + with open('proliantutils/tests/redfish/' + 'json_samples/account_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + accounts = self.acc_inst.accounts + self.assertIsInstance(accounts, account.HPEAccountCollection) + + with open('proliantutils/tests/redfish/' + 'json_samples/account_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.acc_inst.refresh() + self.assertIsNone(self.acc_inst._accounts) + + with open('proliantutils/tests/redfish/' + 'json_samples/account_collection.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + self.assertIsInstance(accounts, account.HPEAccountCollection) diff --git a/proliantutils/tests/redfish/test_main.py b/proliantutils/tests/redfish/test_main.py index 41e5950..d50221b 100644 --- a/proliantutils/tests/redfish/test_main.py +++ b/proliantutils/tests/redfish/test_main.py @@ -21,6 +21,7 @@ import testtools from proliantutils import exception from proliantutils.redfish import main +from proliantutils.redfish.resources.account_service import account_service from proliantutils.redfish.resources.manager import manager from proliantutils.redfish.resources.system import system from proliantutils.redfish.resources import update_service @@ -88,3 +89,12 @@ class HPESushyTestCase(testtools.TestCase): mock_update_service.assert_called_once_with( self.hpe_sushy._conn, "/redfish/v1/UpdateService/", self.hpe_sushy.redfish_version) + + @mock.patch.object(account_service, 'HPEAccountService', autospec=True) + def test_get_account_service(self, mock_account_service): + acc_inst = self.hpe_sushy.get_account_service() + self.assertIsInstance(acc_inst, + account_service.HPEAccountService.__class__) + mock_account_service.assert_called_once_with( + self.hpe_sushy._conn, "/redfish/v1/AccountService/", + self.hpe_sushy.redfish_version) diff --git a/proliantutils/tests/redfish/test_redfish.py b/proliantutils/tests/redfish/test_redfish.py index 28fe1c5..7f23492 100644 --- a/proliantutils/tests/redfish/test_redfish.py +++ b/proliantutils/tests/redfish/test_redfish.py @@ -22,6 +22,8 @@ import testtools from proliantutils import exception from proliantutils.redfish import main from proliantutils.redfish import redfish +from proliantutils.redfish.resources.account_service import account +from proliantutils.redfish.resources.account_service import account_service from proliantutils.redfish.resources.manager import manager from proliantutils.redfish.resources.manager import virtual_media from proliantutils.redfish.resources.system import constants as sys_cons @@ -573,3 +575,73 @@ class RedfishOperationsTestCase(testtools.TestCase): 'The Redfish controller failed to set one time boot.', self.rf_client.set_one_time_boot, 'CDROM') + + def _setup_reset_ilo_credential(self): + self.conn = mock.Mock() + with open('proliantutils/tests/redfish/' + 'json_samples/account_service.json', 'r') as f: + self.conn.get.return_value.json.return_value = json.loads(f.read()) + + account_mock = account_service.HPEAccountService( + self.conn, '/redfish/v1/AccountService', + redfish_version='1.0.2') + + with open('proliantutils/tests/redfish/' + 'json_samples/account_collection.json', 'r') as f: + account_collection_json = json.loads(f.read()) + + with open('proliantutils/tests/redfish/' + 'json_samples/account.json', 'r') as f: + account_json = json.loads(f.read()) + + return account_mock, account_collection_json, account_json + + @mock.patch.object(main.HPESushy, 'get_account_service') + def test_reset_ilo_credential(self, account_mock): + account_mock.return_value, account_collection_json, account_json = ( + self._setup_reset_ilo_credential()) + self.conn.get.return_value.json.side_effect = [ + account_collection_json, account_json] + + self.rf_client.reset_ilo_credential('fake-password') + (self.sushy.get_account_service.return_value. + accounts.get_member_details.return_value. + update_credentials.assert_called_once_with('fake-password')) + + @mock.patch.object(main.HPESushy, 'get_account_service') + def test_reset_ilo_credential_fail(self, account_mock): + account_mock.return_value, account_collection_json, account_json = ( + self._setup_reset_ilo_credential()) + self.conn.get.return_value.json.side_effect = [ + account_collection_json, account_json] + + (self.sushy.get_account_service.return_value.accounts. + get_member_details.return_value. + update_credentials.side_effect) = sushy.exceptions.SushyError + self.assertRaisesRegex( + exception.IloError, + 'The Redfish controller failed to update credentials', + self.rf_client.reset_ilo_credential, 'fake-password') + + @mock.patch.object(account.HPEAccount, 'update_credentials') + def test_reset_ilo_credential_get_account_service_fail(self, update_mock): + account_service_not_found_error = sushy.exceptions.SushyError + account_service_not_found_error.message = ( + 'HPEAccountService not found!!') + self.sushy.get_account_service.side_effect = ( + account_service_not_found_error) + self.assertRaisesRegex( + exception.IloError, + 'The Redfish controller failed to update credentials for foo. ' + 'Error HPEAccountService not found!!', + self.rf_client.reset_ilo_credential, 'fake-password') + self.assertFalse(update_mock.called) + + @mock.patch.object(main.HPESushy, 'get_account_service') + def test_reset_ilo_credential_no_member(self, account_mock): + (self.sushy.get_account_service.return_value.accounts. + get_member_details.return_value) = None + self.assertRaisesRegex( + exception.IloError, + 'No account found with username: foo', + self.rf_client.reset_ilo_credential, 'fake-password')