diff --git a/proliantutils/ilo/client.py b/proliantutils/ilo/client.py index cd3e0fa2..9fa619b5 100644 --- a/proliantutils/ilo/client.py +++ b/proliantutils/ilo/client.py @@ -59,9 +59,10 @@ class IloClient(operations.IloOperations): bios_password=bios_password, cacert=cacert) self.info = {'address': host, 'username': login, 'password': password} + self.host = host self.model = self.ribcl.get_product_name() - LOG.debug("IloClient object created for host {} [model: {}]".format( - host, self.model)) + LOG.debug(self._("IloClient object created. " + "Model: %(model)s"), {'model': self.model}) def _call_method(self, method_name, *args, **kwargs): """Call the corresponding method using either RIBCL or RIS.""" @@ -70,9 +71,9 @@ class IloClient(operations.IloOperations): the_operation_object = self.ris method = getattr(the_operation_object, method_name) - LOG.debug("Calling {} method of {} for host {}".format( - method_name, type(the_operation_object).__name__, - the_operation_object.host)) + LOG.debug(self._("Using %(class)s for method %(method)s."), + {'class': type(the_operation_object).__name__, + 'method': method_name}) return method(*args, **kwargs) diff --git a/proliantutils/ilo/operations.py b/proliantutils/ilo/operations.py index b2e9d25d..f47207f0 100644 --- a/proliantutils/ilo/operations.py +++ b/proliantutils/ilo/operations.py @@ -25,6 +25,13 @@ class IloOperations(object): python as described in HP iLO 4 Scripting and Command Line Guide. """ + def _(self, msg): + """Prepends host information if available to msg and returns it.""" + try: + return "[iLO %s] %s" % (self.host, msg) + except AttributeError: + return "[iLO ] %s" % msg + def get_all_licenses(self): """Retrieve license type, key, installation date, etc.""" raise exception.IloCommandNotSupportedError(ERRMSG) diff --git a/proliantutils/ilo/ribcl.py b/proliantutils/ilo/ribcl.py index 180b275e..2d282e11 100644 --- a/proliantutils/ilo/ribcl.py +++ b/proliantutils/ilo/ribcl.py @@ -17,6 +17,7 @@ over RIBCL scripting language """ +import copy import re import xml.etree.ElementTree as etree @@ -47,6 +48,22 @@ BOOT_MODE_CMDS = [ LOG = log.get_logger(__name__) +class MaskedRequestData(object): + def __init__(self, request_data): + self.request_data = request_data + + def __str__(self): + request_data_copy = copy.deepcopy(self.request_data) + xml_data = request_data_copy.get('data') + if xml_data: + xml_data = re.sub(r'USER_LOGIN="(.*?)"', r'USER_LOGIN="*****"', + xml_data) + xml_data = re.sub(r'PASSWORD="(.*?)"', r'PASSWORD="*****"', + xml_data) + request_data_copy['data'] = xml_data + return str(request_data_copy) + + class RIBCLOperations(operations.IloOperations): """iLO class for RIBCL interface for iLO. @@ -69,9 +86,6 @@ class RIBCLOperations(operations.IloOperations): if self.cacert is None: urllib3.disable_warnings(urllib3_exceptions.InsecureRequestWarning) - LOG.debug("RIBCLOperations object created for host {}".format( - self.host)) - def _request_ilo(self, root): """Send RIBCL XML data to iLO. @@ -93,9 +107,15 @@ class RIBCLOperations(operations.IloOperations): kwargs['verify'] = False try: + LOG.debug(self._("POST %(url)s with request data: " + "%(request_data)s"), + {'url': urlstr, + 'request_data': MaskedRequestData(kwargs)}) response = requests.post(urlstr, **kwargs) response.raise_for_status() except Exception as e: + LOG.exception(self._("An error occurred while " + "contacting iLO. Error: %s"), e) raise exception.IloConnectionError(e) return response.text @@ -253,13 +273,26 @@ class RIBCLOperations(operations.IloOperations): platform = self.get_product_name() msg = ("%(cmd)s is not supported on %(platform)s" % {'cmd': cmd, 'platform': platform}) + LOG.error(self._("Got invalid response with " + "message: '%(message)s'"), + {'message': msg}) raise (exception.IloCommandNotSupportedError (msg, status)) else: + LOG.error(self._("Got invalid response with " + "message: '%(message)s'"), + {'message': msg}) raise exception.IloClientInternalError(msg, status) if (status in exception.IloLoginFailError.statuses or msg in exception.IloLoginFailError.messages): + LOG.error(self._("Got invalid response with " + "message: '%(message)s'"), + {'message': msg}) raise exception.IloLoginFailError(msg, status) + + LOG.error(self._("Got invalid response with " + "message: '%(message)s'"), + {'message': msg}) raise exception.IloError(msg, status) def _execute_command(self, create_command, tag_info, mode, dic={}): @@ -272,6 +305,7 @@ class RIBCLOperations(operations.IloOperations): create_command, tag_info, mode, dic) d = self._request_ilo(xml) data = self._parse_output(d) + LOG.debug(self._("Received response data: %s"), data) return data def get_all_licenses(self): diff --git a/proliantutils/ilo/ris.py b/proliantutils/ilo/ris.py index d4498422..637f04a2 100755 --- a/proliantutils/ilo/ris.py +++ b/proliantutils/ilo/ris.py @@ -64,9 +64,6 @@ class RISOperations(operations.IloOperations): if self.cacert is None: urllib3.disable_warnings(urllib3_exceptions.InsecureRequestWarning) - LOG.debug("RISOperations object created for host {}".format( - self.host)) - def _rest_op(self, operation, suburi, request_headers, request_body): """Generic REST Operation handler.""" @@ -74,6 +71,12 @@ class RISOperations(operations.IloOperations): # Used for logging on redirection error. start_url = url.geturl() + LOG.debug(self._("%(operation)s %(url)s " + "with headers '%(header)s' and body '%(body)s'"), + {'operation': operation, 'url': start_url, + 'header': request_headers, + 'body': request_body}) + if request_headers is None: request_headers = {} @@ -98,6 +101,8 @@ class RISOperations(operations.IloOperations): try: response = request_method(url.geturl(), **kwargs) except Exception as e: + LOG.exception(self._("An error occurred while " + "contacting iLO. Error: %s"), e) raise exception.IloConnectionError(e) # NOTE:Do not assume every HTTP operation will return a JSON body. @@ -112,12 +117,15 @@ class RISOperations(operations.IloOperations): if response.status_code == 301 and 'location' in response.headers: url = urlparse.urlparse(response.headers['location']) redir_count -= 1 + LOG.debug(self._("Request redirected to %s."), url.geturl()) else: break else: # Redirected for 5th time. Throw error - msg = ("URL Redirected 5 times continuously. " - "URL incorrect: %s" % start_url) + msg = (self._("URL Redirected 5 times continuously. " + "URL incorrect: %(start_url)s") % + {'start_url': start_url}) + LOG.error(msg) raise exception.IloConnectionError(msg) response_body = {} @@ -134,11 +142,20 @@ class RISOperations(operations.IloOperations): try: gzipper = gzip.GzipFile( fileobj=six.BytesIO(response.text)) + LOG.debug(self._("Received compressed response " + "for url %(url)s."), + {'url': url.geturl()}) uncompressed_string = gzipper.read().decode('UTF-8') response_body = json.loads(uncompressed_string) except Exception as e: + LOG.error(self._("Got invalid response " + "'%(response)s' for url %(url)s."), + {'url': url.geturl(), + 'response': response.text}) raise exception.IloError(e) + LOG.debug(self._("Received response %(response)s for url %(url)s."), + {'url': url.geturl(), 'response': response_body}) return response.status_code, response.headers, response_body def _rest_get(self, suburi, request_headers=None): diff --git a/proliantutils/tests/ilo/test_operations.py b/proliantutils/tests/ilo/test_operations.py new file mode 100644 index 00000000..4eb6cc5d --- /dev/null +++ b/proliantutils/tests/ilo/test_operations.py @@ -0,0 +1,35 @@ +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# 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. +"""Test Class for Common Operations.""" + +import unittest + +from proliantutils.ilo import operations + + +class IloOperationsTestCase(unittest.TestCase): + + def setUp(self): + super(IloOperationsTestCase, self).setUp() + self.operations_object = operations.IloOperations() + + def test__okay(self): + self.operations_object.host = '1.2.3.4' + self.assertEqual('[iLO 1.2.3.4] foo', + self.operations_object._('foo')) + + def test__no_host(self): + self.assertEqual('[iLO ] foo', + self.operations_object._('foo')) diff --git a/proliantutils/tests/ilo/test_ribcl.py b/proliantutils/tests/ilo/test_ribcl.py index a76cddfd..2153deeb 100644 --- a/proliantutils/tests/ilo/test_ribcl.py +++ b/proliantutils/tests/ilo/test_ribcl.py @@ -29,6 +29,36 @@ from proliantutils.ilo import ribcl from proliantutils.tests.ilo import ribcl_sample_outputs as constants +class MaskedRequestDataTestCase(unittest.TestCase): + + def setUp(self): + super(MaskedRequestDataTestCase, self).setUp() + self.maskedRequestData = ribcl.MaskedRequestData({}) + + def test___str__with_user_credential_present(self): + xml_data = ( + '' + '' + '') + masked_xml_data = ( + '\'' + '' + '\'') + self.maskedRequestData.request_data = {'headers': 'some-headers', + 'data': xml_data, + 'verify': False} + self.assertIn(masked_xml_data, str(self.maskedRequestData)) + + def test___str__with_user_credential_not_present(self): + xml_data = ( + '' + '') + self.maskedRequestData.request_data = {'headers': 'some-headers', + 'data': xml_data, + 'verify': True} + self.assertIn(xml_data, str(self.maskedRequestData)) + + class IloRibclTestCaseInitTestCase(unittest.TestCase): @mock.patch.object(urllib3, 'disable_warnings')