diff --git a/doc/helpers.rst b/doc/helpers.rst index c7ea422d3..78b7e4d96 100644 --- a/doc/helpers.rst +++ b/doc/helpers.rst @@ -28,6 +28,11 @@ Decorators .. automodule:: fuelweb_test.helpers.decorators :members: +Metaclasses +----------- +.. automodule:: fuelweb_test.helpers.metaclasses + :members: + Eb tables --------- .. automodule:: fuelweb_test.helpers.eb_tables diff --git a/fuelweb_test/__init__.py b/fuelweb_test/__init__.py index d9a9271f5..03aee5d0e 100644 --- a/fuelweb_test/__init__.py +++ b/fuelweb_test/__init__.py @@ -13,6 +13,7 @@ # under the License. import functools import logging +import traceback import os from fuelweb_test.settings import LOGS_DIR @@ -54,9 +55,17 @@ def debug(logger): func.__name__, args, kwargs ) ) - result = func(*args, **kwargs) - logger.debug( - "Done: {} with result: {}".format(func.__name__, result)) + try: + result = func(*args, **kwargs) + logger.debug( + "Done: {} with result: {}".format(func.__name__, result)) + except BaseException as e: + tb = traceback.format_exc() + logger.error( + '{func} raised: {exc!r}\n' + 'Traceback: {tb!s}'.format( + func=func.__name__, exc=e, tb=tb)) + raise return result return wrapped return wrapper diff --git a/fuelweb_test/helpers/metaclasses.py b/fuelweb_test/helpers/metaclasses.py new file mode 100644 index 000000000..6e1e79bcf --- /dev/null +++ b/fuelweb_test/helpers/metaclasses.py @@ -0,0 +1,27 @@ +# Copyright 2016 Mirantis, Inc. +# +# 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. + + +class SingletonMeta(type): + """Metaclass for Singleton + + Main goals: not need to implement __new__ in singleton classes + """ + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super( + SingletonMeta, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/fuelweb_test/helpers/ssh_manager.py b/fuelweb_test/helpers/ssh_manager.py index 9fff6887e..55a35457a 100644 --- a/fuelweb_test/helpers/ssh_manager.py +++ b/fuelweb_test/helpers/ssh_manager.py @@ -1,4 +1,4 @@ -# Copyright 2015 Mirantis, Inc. +# Copyright 2016 Mirantis, Inc. # # 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 @@ -20,32 +20,27 @@ import json from paramiko import RSAKey from devops.models.node import SSHClient from fuelweb_test import logger - - -class SingletonMeta(type): - def __init__(cls, name, bases, dict): - super(SingletonMeta, cls).__init__(name, bases, dict) - cls.instance = None - - def __call__(self, *args, **kw): - if self.instance is None: - self.instance = super(SingletonMeta, self).__call__(*args, **kw) - return self.instance - - def __getattr__(cls, name): - return getattr(cls(), name) +from fuelweb_test.helpers import metaclasses class SSHManager(object): - __metaclass__ = SingletonMeta + __metaclass__ = metaclasses.SingletonMeta + # Slots is used to prevent uncontrolled attributes set or remove. + __slots__ = [ + '__connections', 'admin_ip', 'admin_port', 'login', '__password' + ] def __init__(self): logger.debug('SSH_MANAGER: Run constructor SSHManager') - self.connections = {} + self.__connections = {} # Disallow direct type change and deletion self.admin_ip = None self.admin_port = None self.login = None - self.password = None + self.__password = None + + @property + def connections(self): + return self.__connections def initialize(self, admin_ip, login, password): """ It will be moved to __init__ @@ -58,7 +53,7 @@ class SSHManager(object): self.admin_ip = admin_ip self.admin_port = 22 self.login = login - self.password = password + self.__password = password def _connect(self, remote): """ Check if connection is stable and return this one @@ -97,7 +92,7 @@ class SSHManager(object): host=ip, port=port, username=self.login, - password=self.password, + password=self.__password, private_keys=keys ) logger.debug('SSH_MANAGER:Return existed connection for ' @@ -192,6 +187,10 @@ class SSHManager(object): return result + def execute_async_on_remote(self, ip, cmd, port=22): + remote = self._get_remote(ip=ip, port=port) + return remote.execute_async(cmd) + def _json_deserialize(self, json_string): """ Deserialize json_string and return object diff --git a/fuelweb_test/models/environment.py b/fuelweb_test/models/environment.py index 165ad8f64..0e4947d50 100644 --- a/fuelweb_test/models/environment.py +++ b/fuelweb_test/models/environment.py @@ -28,6 +28,7 @@ from proboscis.asserts import assert_true from fuelweb_test.helpers.decorators import revert_info from fuelweb_test.helpers.decorators import update_rpm_packages from fuelweb_test.helpers.decorators import upload_manifests +from fuelweb_test.helpers.metaclasses import SingletonMeta from fuelweb_test.helpers.eb_tables import Ebtables from fuelweb_test.helpers.fuel_actions import AdminActions from fuelweb_test.helpers.fuel_actions import BaseActions @@ -51,13 +52,7 @@ from fuelweb_test import logger class EnvironmentModel(object): """EnvironmentModel.""" # TODO documentation - _instance = None - - def __new__(cls, *args, **kwargs): - if not cls._instance: - cls._instance = super(EnvironmentModel, cls).__new__( - cls, *args, **kwargs) - return cls._instance + __metaclass__ = SingletonMeta def __init__(self, config=None): if not hasattr(self, "_virt_env"):