diff --git a/magnum/conductor/k8s_api.py b/magnum/conductor/k8s_api.py index c8163fb6f0..d8173178e1 100644 --- a/magnum/conductor/k8s_api.py +++ b/magnum/conductor/k8s_api.py @@ -19,6 +19,7 @@ from k8sclient.client.apis import apiv_api from oslo_log import log as logging from magnum.conductor.handlers.common.cert_manager import create_client_files +from magnum.i18n import _LE LOG = logging.getLogger(__name__) @@ -36,7 +37,7 @@ class K8sAPI(apiv_api.ApivApi): tmp.write(content) tmp.flush() except Exception as err: - LOG.error("Error while creating temp file: %s", err) + LOG.error(_LE("Error while creating temp file: %s"), err) raise return tmp diff --git a/magnum/hacking/checks.py b/magnum/hacking/checks.py index e5a5536ad1..d0677f26d5 100644 --- a/magnum/hacking/checks.py +++ b/magnum/hacking/checks.py @@ -15,6 +15,8 @@ import re +import pep8 + """ Guidelines for writing new hacking checks @@ -56,6 +58,14 @@ assert_true_isinstance_re = re.compile( dict_constructor_with_list_copy_re = re.compile(r".*\bdict\((\[)?(\(|\[)") assert_xrange_re = re.compile( r"\s*xrange\s*\(") +log_translation = re.compile( + r"(.)*LOG\.(audit|error|critical)\(\s*('|\")") +log_translation_info = re.compile( + r"(.)*LOG\.(info)\(\s*(_\(|'|\")") +log_translation_exception = re.compile( + r"(.)*LOG\.(exception)\(\s*(_\(|'|\")") +log_translation_LW = re.compile( + r"(.)*LOG\.(warning|warn)\(\s*(_\(|'|\")") custom_underscore_check = re.compile(r"(.)*_\s*=\s*(.)*") underscore_import_check = re.compile(r"(.)*import _(.)*") translated_log = re.compile( @@ -125,6 +135,23 @@ def assert_true_isinstance(logical_line): yield (0, "M316: assertTrue(isinstance(a, b)) sentences not allowed") +def validate_log_translations(logical_line, physical_line, filename=None): + if pep8.noqa(physical_line): + return + msg = "M328: LOG.info messages require translations `_LI()`!" + if log_translation_info.match(logical_line): + yield (0, msg) + msg = "M329: LOG.exception messages require translations `_LE()`!" + if log_translation_exception.match(logical_line): + yield (0, msg) + msg = "M330: LOG.warning, LOG.warn messages require translations `_LW()`!" + if log_translation_LW.match(logical_line): + yield (0, msg) + msg = "M321: Log messages require translations!" + if log_translation.match(logical_line): + yield (0, msg) + + def assert_equal_in(logical_line): """Check for assertEqual(True|False, A in B), assertEqual(A in B, True|False) @@ -215,4 +242,5 @@ def factory(register): register(dict_constructor_with_list_copy) register(no_xrange) register(no_log_warn) + register(validate_log_translations) register(check_explicit_underscore_import) diff --git a/magnum/service/periodic.py b/magnum/service/periodic.py index b7a80c7866..85e5544543 100644 --- a/magnum/service/periodic.py +++ b/magnum/service/periodic.py @@ -132,9 +132,10 @@ class MagnumPeriodicTasks(periodic_task.PeriodicTasks): # Any other exception means we do not perform any # action on this bay in the current sync run, so remove # it from all records. - LOG.warning("Exception while attempting to retrieve " - "Heat stack %s for bay %s. Traceback " - "follows.") + LOG.warning(_LW("Exception while attempting to retrieve " + "Heat stack %(stack_id)s for bay %(bay_id)s. " + "Traceback follows."), + {'stack_id': bay.stack_id, 'bay_id': bay.id}) LOG.warning(e) _sid_to_bay_mapping.pop(bay.stack_id) _bay_stack_ids.remove(bay.stack_id) diff --git a/magnum/tests/functional/api/v1/clients/bay_client.py b/magnum/tests/functional/api/v1/clients/bay_client.py index a9833f6465..5ae9ecdf7c 100644 --- a/magnum/tests/functional/api/v1/clients/bay_client.py +++ b/magnum/tests/functional/api/v1/clients/bay_client.py @@ -13,6 +13,9 @@ from oslo_log import log as logging from tempest.lib import exceptions +from magnum.i18n import _LE +from magnum.i18n import _LI +from magnum.i18n import _LW from magnum.tests.functional.api.v1.models import bay_model from magnum.tests.functional.common import client from magnum.tests.functional.common import utils @@ -120,9 +123,9 @@ class BayClient(client.MagnumClient): lambda: self.does_bay_exist(bay_id), 10, 1800) except Exception: # In error state. Clean up the bay id if desired - self.LOG.error('Bay %s entered an exception state.' % bay_id) + self.LOG.error(_LE('Bay %s entered an exception state.') % bay_id) if delete_on_error: - self.LOG.error('We will attempt to delete bays now.') + self.LOG.error(_LE('We will attempt to delete bays now.')) self.delete_bay(bay_id) self.wait_for_bay_to_delete(bay_id) raise @@ -136,35 +139,35 @@ class BayClient(client.MagnumClient): resp, model = self.get_bay(bay_id) if model.status in ['CREATED', 'CREATE_COMPLETE', 'ERROR', 'CREATE_FAILED']: - self.LOG.info('Bay %s succeeded.' % bay_id) + self.LOG.info(_LI('Bay %s succeeded.') % bay_id) return True else: return False except exceptions.NotFound: - self.LOG.warning('Bay %s is not found.' % bay_id) + self.LOG.warning(_LW('Bay %s is not found.') % bay_id) return False def does_bay_exist(self, bay_id): try: resp, model = self.get_bay(bay_id) if model.status in ['CREATED', 'CREATE_COMPLETE']: - self.LOG.info('Bay %s is created.' % bay_id) + self.LOG.info(_LI('Bay %s is created.') % bay_id) return True elif model.status in ['ERROR', 'CREATE_FAILED']: - self.LOG.error('Bay %s is in fail state.' % bay_id) + self.LOG.error(_LE('Bay %s is in fail state.') % bay_id) raise exceptions.ServerFault( "Got into an error condition: %s for %s" % (model.status, bay_id)) else: return False except exceptions.NotFound: - self.LOG.warning('Bay %s is not found.' % bay_id) + self.LOG.warning(_LW('Bay %s is not found.') % bay_id) return False def does_bay_not_exist(self, bay_id): try: self.get_bay(bay_id) except exceptions.NotFound: - self.LOG.warning('Bay %s is not found.' % bay_id) + self.LOG.warning(_LW('Bay %s is not found.') % bay_id) return True return False diff --git a/magnum/tests/functional/common/base.py b/magnum/tests/functional/common/base.py index 9f3c8d3f6c..cd4a3c6a48 100644 --- a/magnum/tests/functional/common/base.py +++ b/magnum/tests/functional/common/base.py @@ -17,6 +17,8 @@ import subprocess from tempest.lib import base import magnum +from magnum.i18n import _LE +from magnum.i18n import _LI COPY_LOG_HELPER = "magnum/tests/contrib/copy_instance_logs.sh" @@ -44,10 +46,10 @@ class BaseMagnumTest(base.BaseTestCase): """ def int_copy_logs(exec_info): try: - cls.LOG.info("Copying logs...") + cls.LOG.info(_LI("Copying logs...")) fn = exec_info[2].tb_frame.f_locals['fn'] func_name = fn.im_self._get_test_method().__name__ - msg = "Failed to copy logs for bay" + msg = (_LE("Failed to copy logs for bay")) nodes_addresses = get_nodes_fn() master_nodes = nodes_addresses[0] @@ -61,8 +63,8 @@ class BaseMagnumTest(base.BaseTestCase): if not nodes_address: return - cls.LOG.info("copy logs from : %s" % - ','.join(nodes_address)) + msg = _LI("copy logs from : %s") % ','.join(nodes_address) + cls.LOG.info(msg) log_name = prefix + "-" + func_name for node_address in nodes_address: try: @@ -77,10 +79,13 @@ class BaseMagnumTest(base.BaseTestCase): ]) except Exception: cls.LOG.error(msg) - cls.LOG.exception( - "failed to copy from %s to %s%s-%s" % - (node_address, "/opt/stack/logs/bay-nodes/", - log_name, node_address)) + msg = (_LE("failed to copy from %{node_address}s " + "to %{base_path}s%{log_name}s-" + "%{node_address}s") % + {'node_address': node_address, + 'base_path': "/opt/stack/logs/bay-nodes/", + 'log_name': log_name}) + cls.LOG.exception(msg) do_copy_logs('master', master_nodes) do_copy_logs('node', agent_nodes) diff --git a/magnum/tests/functional/python_client_base.py b/magnum/tests/functional/python_client_base.py index 6fc747b82e..e7599cb20e 100644 --- a/magnum/tests/functional/python_client_base.py +++ b/magnum/tests/functional/python_client_base.py @@ -31,6 +31,7 @@ from k8sclient.client import api_client from k8sclient.client.apis import apiv_api from keystoneclient.v2_0 import client as ksclient from magnum.common.utils import rmtree_without_raise +from magnum.i18n import _LI from magnum.tests.functional.common import base from magnum.tests.functional.common import utils from magnumclient.common.apiclient import exceptions @@ -275,11 +276,11 @@ extendedKeyUsage = clientAuth def _get_nodes(self): nodes = self._get_nodes_from_bay() if not [x for x in nodes if x]: - self.LOG.info("the list of nodes from bay is empty") + self.LOG.info(_LI("the list of nodes from bay is empty")) nodes = self._get_nodes_from_stack() if not [x for x in nodes if x]: - self.LOG.info("the list of nodes from stack is empty") - self.LOG.info("Nodes are: %s" % nodes) + self.LOG.info(_LI("the list of nodes from stack is empty")) + self.LOG.info(_LI("Nodes are: %s") % nodes) return nodes def _get_nodes_from_bay(self): @@ -378,10 +379,10 @@ class BaseK8sTest(BayTest): def _is_api_ready(self): try: self.k8s_api.list_namespaced_node() - self.LOG.info("API is ready.") + self.LOG.info(_LI("API is ready.")) return True except Exception: - self.LOG.info("API is not ready yet.") + self.LOG.info(_LI("API is not ready yet.")) return False def test_pod_apis(self): diff --git a/magnum/tests/functional/swarm/test_swarm_python_client.py b/magnum/tests/functional/swarm/test_swarm_python_client.py index bb06a32416..dd8ce970cd 100644 --- a/magnum/tests/functional/swarm/test_swarm_python_client.py +++ b/magnum/tests/functional/swarm/test_swarm_python_client.py @@ -17,6 +17,7 @@ from oslo_config import cfg from requests import exceptions as req_exceptions from magnum.common import docker_utils +from magnum.i18n import _LI from magnum.tests.functional.python_client_base import BayTest @@ -97,15 +98,15 @@ class TestSwarmAPIs(BayTest): # investigate the cause of this issue. See bug #1583337. for i in range(150): try: - self.LOG.info("Calling function " + func.__name__) + self.LOG.info(_LI("Calling function ") + func.__name__) return func(*args, **kwargs) except req_exceptions.ConnectionError: - self.LOG.info("Connection aborted on calling Swarm API. " - "Will retry in 2 seconds.") + self.LOG.info(_LI("Connection aborted on calling Swarm API. " + "Will retry in 2 seconds.")) except errors.APIError as e: if e.response.status_code != 500: raise - self.LOG.info("Internal Server Error: " + str(e)) + self.LOG.info(_LI("Internal Server Error: ") + str(e)) time.sleep(2) raise Exception("Cannot connect to Swarm API.") diff --git a/magnum/tests/unit/test_hacking.py b/magnum/tests/unit/test_hacking.py index e014b2ff94..c71a717031 100644 --- a/magnum/tests/unit/test_hacking.py +++ b/magnum/tests/unit/test_hacking.py @@ -215,6 +215,28 @@ class HackingTestCase(base.TestCase): """ self._assert_has_no_errors(code, check) + def test_log_translations(self): + logs = ['audit', 'error', 'info', 'warning', 'critical', 'warn', + 'exception'] + levels = ['_LI', '_LW', '_LE', '_LC'] + debug = "LOG.debug('OK')" + self.assertEqual( + 0, len(list(checks.validate_log_translations(debug, debug)))) + for log in logs: + bad = 'LOG.%s("Bad")' % log + self.assertEqual( + 1, len(list(checks.validate_log_translations(bad, bad)))) + ok = "LOG.%s('OK') # noqa" % log + self.assertEqual( + 0, len(list(checks.validate_log_translations(ok, ok)))) + ok = "LOG.%s(variable)" % log + self.assertEqual( + 0, len(list(checks.validate_log_translations(ok, ok)))) + for level in levels: + ok = "LOG.%s(%s('OK'))" % (log, level) + self.assertEqual( + 0, len(list(checks.validate_log_translations(ok, ok)))) + def test_use_timeunitls_utcow(self): errors = [(1, 0, "M310")] check = checks.use_timeutils_utcnow