56c946e70f
- Prevent crash if pygit2 package requests recompilation. Add: * 0013-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch - Align OS grains from older SLES with the current one (bsc#975757) Add: * 0014-align-OS-grains-from-older-SLES-with-current-one-326.patch - remove patches which produce duplicate functions: Remove: * 0004-implement-version_cmp-for-zypper.patch * 0005-pylint-changes.patch * 0006-Check-if-rpm-python-can-be-imported.patch - remove patches which add and revert the same file Remove: * 0007-Initial-Zypper-Unit-Tests-and-bugfixes.patch * 0009-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch - rename patches: 0008-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch to 0004-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch 0010-Use-SHA256-hash-type-by-default.patch to 0005-Use-SHA256-hash-type-by-default.patch 0011-Update-to-2015.8.8.2.patch to 0006-Update-to-2015.8.8.2.patch 0012-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch to 0007-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch 0013-Cleaner-deprecation-process-with-decorators.patch to 0008-Cleaner-deprecation-process-with-decorators.patch - fix sorting by latest package Add: * 0009-fix-sorting-by-latest-version-when-called-with-an-at.patch - Prevent metadata download when getting installed products Add: * 0010-Prevent-metadata-download-when-getting-installed-pro.patch - Check if EOL is available in a particular product (bsc#975093) Add: * 0011-Check-if-EOL-is-available-in-a-particular-product-bs.patch - Bugfix: salt-key crashes if tries to generate keys to the directory w/o write access (bsc#969320) Add: * 0012-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch - Deprecation process using decorators and re-implementation of status.update function. Add: * 0013-Cleaner-deprecation-process-with-decorators.patch - Reverted the fake 2015.8.8.2 patch, with the right one, - this patch only contains: - https://github.com/saltstack/salt/pull/32135 - https://github.com/saltstack/salt/pull/32023 - https://github.com/saltstack/salt/pull/32117 - Ensure that in case of multi-packages installed on the system, the latest is reported by pkg.info_installed (bsc#972490) Add: * 0012-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch - Update to the fake 2015.8.8.2 release upstream released a bunch of fixes on top of 2015.8.8, without creating a new tag and proper release. This commit includes all the changes between tag v2015.8.8 and commit ID 596444e2b447b7378dbcdfeb9fc9610b90057745 which introduces the fake 2015.8.8.2 release. see https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html#salt-2015-8-8-2 - Update to 2015.8.8 see https://docs.saltstack.com/en/latest/topics/releases/2015.8.8.html Patches renamed: * 0004-implement-version_cmp-for-zypper.patch * 0005-pylint-changes.patch * 0006-Check-if-rpm-python-can-be-imported.patch * 0007-Initial-Zypper-Unit-Tests-and-bugfixes.patch * 0008-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch * 0009-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch * 0010-Use-SHA256-hash-type-by-default.patch Patches removed: * 0004-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch * 0005-Fix-package-status-filtering-on-latest-version-and-i.patch * 0006-add_key-reject_key-do-not-crash-w-Permission-denied-.patch * 0007-Force-kill-websocket-s-child-processes-faster-than-d.patch * 0008-Fix-types-in-the-output-data-and-return-just-a-list-.patch * 0009-The-functions-in-the-state-module-that-return-a-retc.patch * 0010-add-handling-for-OEM-products.patch * 0011-improve-doc-for-list_pkgs.patch * 0012-implement-version_cmp-for-zypper.patch * 0013-pylint-changes.patch * 0014-Check-if-rpm-python-can-be-imported.patch * 0015-call-zypper-with-option-non-interactive-everywhere.patch * 0016-write-a-zypper-command-builder-function.patch * 0017-Fix-crash-with-scheduler-and-runners-31106.patch * 0018-unify-behavior-of-refresh.patch * 0019-add-refresh-option-to-more-functions.patch * 0020-simplify-checking-the-refresh-paramater.patch * 0021-do-not-change-kwargs-in-refresh-while-checking-a-val.patch * 0022-fix-argument-handling-for-pkg.download.patch * 0023-Initial-Zypper-Unit-Tests-and-bugfixes.patch * 0024-proper-checking-if-zypper-exit-codes-and-handling-of.patch * 0025-adapt-tests-to-new-zypper_check_result-output.patch * 0026-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch * 0027-make-suse-check-consistent-with-rh_service.patch * 0028-fix-numerical-check-of-osrelease.patch * 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch * 0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch * 0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch * 0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch * 0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch * 0034-Fix-git_pillar-race-condition.patch * 0035-Fix-the-always-false-behavior-on-checking-state.patch * 0036-Use-SHA256-hash-type-by-default.patch OBS-URL: https://build.opensuse.org/request/show/391560 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=66
923 lines
32 KiB
Diff
923 lines
32 KiB
Diff
From 2dcc979ab2897619baebfef5779120a98284d408 Mon Sep 17 00:00:00 2001
|
|
From: Bo Maryniuk <bo@maryniuk.net>
|
|
Date: Wed, 6 Apr 2016 20:55:45 +0200
|
|
Subject: [PATCH 08/12] Cleaner deprecation process with decorators
|
|
|
|
* Add deprecation decorator scaffold
|
|
|
|
* Capture type error and unhandled exceptions while function calls
|
|
|
|
* Aware of the current and future version of deprecation
|
|
|
|
* Implement initially is_deprecated decorator
|
|
|
|
* Add an alias for the capitalization
|
|
|
|
* Fix capitalization easier way
|
|
|
|
* Remove an extra line
|
|
|
|
* Add successor name to the deprecation decorator.
|
|
|
|
* Granulate logging and error messages.
|
|
|
|
* Implement function swapper
|
|
|
|
* Raise later the caught exception
|
|
|
|
* Clarify exception message
|
|
|
|
* Save function original name
|
|
|
|
* Remove an extra line
|
|
|
|
* Hide an alternative hidden function name in the error message, preserving the error itself
|
|
|
|
* Rename variable as private
|
|
|
|
* Add a method to detect if a function is using its previous version
|
|
|
|
* Message to the log and/or raise an exception accordingly to the status of used function
|
|
|
|
* Log an error along with the exception
|
|
|
|
* Add internal method documentation
|
|
|
|
* Add documentation and usage process for decorator "is_deprecated"
|
|
|
|
* Add documentation and process usage for the decorator "with_deprecated"
|
|
|
|
* Hide private method name
|
|
|
|
* Fix PEP8, re-word the error message
|
|
|
|
* Deprecate basic uptime function
|
|
|
|
* Add initial decorator unit test
|
|
|
|
* Rename old/new functions, mock versions
|
|
|
|
* Move frequent data to the test setup
|
|
|
|
* Add logging on EOL exception
|
|
|
|
* Rename and document high to low version test on is_deprecated
|
|
|
|
* Implement a test on low to high version of is_deprecated decorator
|
|
|
|
* Add a correction to the test description
|
|
|
|
* Remove a dead code
|
|
|
|
* Implement a test for high to low version on is_deprecated, using with_successor param
|
|
|
|
* Correct typso adn mistaeks
|
|
|
|
* Implement high to low version with successor param on is_deprecated
|
|
|
|
* Setup a virtual name for the module
|
|
|
|
* Implement test for with_deprecated should raise an exception if same deprecated function not found
|
|
|
|
* Implement test for with_deprecated an old function is picked up if configured
|
|
|
|
* Correct test description purpose
|
|
|
|
* Implement test with_deprecated when no deprecation is requested
|
|
|
|
* Add logging test to the configured deprecation request
|
|
|
|
* Add logging testing when deprecated version wasn't requested
|
|
|
|
* Implement test EOL for with_deprecated decorator
|
|
|
|
* Correct test explanation
|
|
|
|
* Rename the test
|
|
|
|
* Implement with_deprecated no EOL, deprecated other function name
|
|
|
|
* Implement with_deprecated, deprecated other function name, EOL reached
|
|
|
|
* Add test description for the with_deprecated + with_name + EOL
|
|
|
|
* Fix confusing test names
|
|
|
|
* Add logging test to the is_deprecated decorator when function as not found.
|
|
|
|
* Add more test point to each test, remove empty lines
|
|
|
|
* Bugfix: at certain conditions a wrong alias name is reported to the log
|
|
|
|
* Fix a typo in a comment
|
|
|
|
* Add test for the logging
|
|
|
|
* Disable a pylint: None will _never_ be raised
|
|
|
|
* Fix test for the deprecated "status.uptime" version
|
|
|
|
* Bugfix: Do not yank raised exceptions
|
|
|
|
* Remove unnecessary decorator
|
|
|
|
* Add test for the new uptime
|
|
|
|
* Add test for the new uptime fails when /proc/uptime does not exists
|
|
|
|
* Rename old test case
|
|
|
|
* Skip test for the UTC time, unless freeze time is used.
|
|
|
|
* Fix pylint
|
|
|
|
* Fix documentation
|
|
|
|
* Bugfix: proxy-pass the docstring of the decorated function
|
|
|
|
* Lint fix
|
|
---
|
|
salt/modules/status.py | 40 ++++-
|
|
salt/utils/decorators/__init__.py | 345 +++++++++++++++++++++++++++++++++++-
|
|
tests/unit/modules/status_test.py | 48 ++++-
|
|
tests/unit/utils/decorators_test.py | 232 ++++++++++++++++++++++++
|
|
4 files changed, 649 insertions(+), 16 deletions(-)
|
|
create mode 100644 tests/unit/utils/decorators_test.py
|
|
|
|
diff --git a/salt/modules/status.py b/salt/modules/status.py
|
|
index 1e80b36..04c6204 100644
|
|
--- a/salt/modules/status.py
|
|
+++ b/salt/modules/status.py
|
|
@@ -11,6 +11,8 @@ import os
|
|
import re
|
|
import fnmatch
|
|
import collections
|
|
+import time
|
|
+import datetime
|
|
|
|
# Import 3rd-party libs
|
|
import salt.ext.six as six
|
|
@@ -23,6 +25,8 @@ import salt.utils.event
|
|
from salt.utils.network import host_to_ip as _host_to_ip
|
|
from salt.utils.network import remote_port_tcp as _remote_port_tcp
|
|
from salt.ext.six.moves import zip
|
|
+from salt.utils.decorators import with_deprecated
|
|
+from salt.exceptions import CommandExecutionError
|
|
|
|
__virtualname__ = 'status'
|
|
__opts__ = {}
|
|
@@ -30,7 +34,8 @@ __opts__ = {}
|
|
|
|
def __virtual__():
|
|
if salt.utils.is_windows():
|
|
- return (False, 'Cannot load status module on windows')
|
|
+ return False, 'Windows platform is not supported by this module'
|
|
+
|
|
return __virtualname__
|
|
|
|
|
|
@@ -120,7 +125,38 @@ def custom():
|
|
return ret
|
|
|
|
|
|
-def uptime(human_readable=True):
|
|
+@with_deprecated(globals(), "Boron")
|
|
+def uptime():
|
|
+ '''
|
|
+ Return the uptime for this system.
|
|
+
|
|
+ CLI Example:
|
|
+
|
|
+ .. code-block:: bash
|
|
+
|
|
+ salt '*' status.uptime
|
|
+ '''
|
|
+ ut_path = "/proc/uptime"
|
|
+ if not os.path.exists(ut_path):
|
|
+ raise CommandExecutionError("File {ut_path} was not found.".format(ut_path=ut_path))
|
|
+
|
|
+ ut_ret = {
|
|
+ 'seconds': int(float(open(ut_path).read().strip().split()[0]))
|
|
+ }
|
|
+
|
|
+ utc_time = datetime.datetime.utcfromtimestamp(time.time() - ut_ret['seconds'])
|
|
+ ut_ret['since_iso'] = utc_time.isoformat()
|
|
+ ut_ret['since_t'] = time.mktime(utc_time.timetuple())
|
|
+ ut_ret['days'] = ut_ret['seconds'] / 60 / 60 / 24
|
|
+ hours = (ut_ret['seconds'] - (ut_ret['days'] * 24 * 60 * 60)) / 60 / 60
|
|
+ minutes = ((ut_ret['seconds'] - (ut_ret['days'] * 24 * 60 * 60)) / 60) - hours * 60
|
|
+ ut_ret['time'] = '{0}:{1}'.format(hours, minutes)
|
|
+ ut_ret['users'] = len(__salt__['cmd.run']("who -s").split(os.linesep))
|
|
+
|
|
+ return ut_ret
|
|
+
|
|
+
|
|
+def _uptime(human_readable=True):
|
|
'''
|
|
Return the uptime for this minion
|
|
|
|
diff --git a/salt/utils/decorators/__init__.py b/salt/utils/decorators/__init__.py
|
|
index 45d3bd6..3b43504 100644
|
|
--- a/salt/utils/decorators/__init__.py
|
|
+++ b/salt/utils/decorators/__init__.py
|
|
@@ -13,7 +13,8 @@ from collections import defaultdict
|
|
|
|
# Import salt libs
|
|
import salt.utils
|
|
-from salt.exceptions import CommandNotFoundError
|
|
+from salt.exceptions import CommandNotFoundError, CommandExecutionError
|
|
+from salt.version import SaltStackVersion, __saltstack_version__
|
|
from salt.log import LOG_LEVELS
|
|
|
|
# Import 3rd-party libs
|
|
@@ -144,10 +145,7 @@ class Depends(object):
|
|
continue
|
|
|
|
|
|
-class depends(Depends): # pylint: disable=C0103
|
|
- '''
|
|
- Wrapper of Depends for capitalization
|
|
- '''
|
|
+depends = Depends
|
|
|
|
|
|
def timing(function):
|
|
@@ -248,3 +246,340 @@ def memoize(func):
|
|
cache[args] = func(*args)
|
|
return cache[args]
|
|
return _memoize
|
|
+
|
|
+
|
|
+class _DeprecationDecorator(object):
|
|
+ '''
|
|
+ Base mix-in class for the deprecation decorator.
|
|
+ Takes care of a common functionality, used in its derivatives.
|
|
+ '''
|
|
+
|
|
+ def __init__(self, globals, version):
|
|
+ '''
|
|
+ Constructor.
|
|
+
|
|
+ :param globals: Module globals. Important for finding out replacement functions
|
|
+ :param version: Expiration version
|
|
+ :return:
|
|
+ '''
|
|
+
|
|
+ self._globals = globals
|
|
+ self._exp_version_name = version
|
|
+ self._exp_version = SaltStackVersion.from_name(self._exp_version_name)
|
|
+ self._curr_version = __saltstack_version__.info
|
|
+ self._options = self._globals['__opts__']
|
|
+ self._raise_later = None
|
|
+ self._function = None
|
|
+ self._orig_f_name = None
|
|
+
|
|
+ def _get_args(self, kwargs):
|
|
+ '''
|
|
+ Extract function-specific keywords from all of the kwargs.
|
|
+
|
|
+ :param kwargs:
|
|
+ :return:
|
|
+ '''
|
|
+ _args = list()
|
|
+ _kwargs = dict()
|
|
+
|
|
+ for arg_item in kwargs.get('__pub_arg', list()):
|
|
+ if type(arg_item) == dict:
|
|
+ _kwargs.update(arg_item.copy())
|
|
+ else:
|
|
+ _args.append(arg_item)
|
|
+ return _args, _kwargs
|
|
+
|
|
+ def _call_function(self, kwargs):
|
|
+ '''
|
|
+ Call target function that has been decorated.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ if self._raise_later:
|
|
+ raise self._raise_later # pylint: disable=E0702
|
|
+
|
|
+ if self._function:
|
|
+ args, kwargs = self._get_args(kwargs)
|
|
+ try:
|
|
+ return self._function(*args, **kwargs)
|
|
+ except TypeError as error:
|
|
+ error = str(error).replace(self._function.func_name, self._orig_f_name) # Hide hidden functions
|
|
+ log.error('Function "{f_name}" was not properly called: {error}'.format(f_name=self._orig_f_name,
|
|
+ error=error))
|
|
+ return self._function.__doc__
|
|
+ except Exception as error:
|
|
+ log.error('Unhandled exception occurred in '
|
|
+ 'function "{f_name}: {error}'.format(f_name=self._function.func_name,
|
|
+ error=error))
|
|
+ raise error
|
|
+ else:
|
|
+ raise CommandExecutionError("Function is deprecated, but the successor function was not found.")
|
|
+
|
|
+ def __call__(self, function):
|
|
+ '''
|
|
+ Callable method of the decorator object when
|
|
+ the decorated function is gets called.
|
|
+
|
|
+ :param function:
|
|
+ :return:
|
|
+ '''
|
|
+ self._function = function
|
|
+ self._orig_f_name = self._function.func_name
|
|
+
|
|
+
|
|
+class _IsDeprecated(_DeprecationDecorator):
|
|
+ '''
|
|
+ This decorator should be used only with the deprecated functions
|
|
+ to mark them as deprecated and alter its behavior a corresponding way.
|
|
+ The usage is only suitable if deprecation process is renaming
|
|
+ the function from one to another. In case function name or even function
|
|
+ signature stays the same, please use 'with_deprecated' decorator instead.
|
|
+
|
|
+ It has the following functionality:
|
|
+
|
|
+ 1. Put a warning level message to the log, informing that
|
|
+ the deprecated function has been in use.
|
|
+
|
|
+ 2. Raise an exception, if deprecated function is being called,
|
|
+ but the lifetime of it already expired.
|
|
+
|
|
+ 3. Point to the successor of the deprecated function in the
|
|
+ log messages as well during the blocking it, once expired.
|
|
+
|
|
+ Usage of this decorator as follows. In this example no successor
|
|
+ is mentioned, hence the function "foo()" will be logged with the
|
|
+ warning each time is called and blocked completely, once EOF of
|
|
+ it is reached:
|
|
+
|
|
+ from salt.util.decorators import is_deprecated
|
|
+
|
|
+ @is_deprecated(globals(), "Beryllium")
|
|
+ def foo():
|
|
+ pass
|
|
+
|
|
+ In the following example a successor function is mentioned, hence
|
|
+ every time the function "bar()" is called, message will suggest
|
|
+ to use function "baz()" instead. Once EOF is reached of the function
|
|
+ "bar()", an exception will ask to use function "baz()", in order
|
|
+ to continue:
|
|
+
|
|
+ from salt.util.decorators import is_deprecated
|
|
+
|
|
+ @is_deprecated(globals(), "Beryllium", with_successor="baz")
|
|
+ def bar():
|
|
+ pass
|
|
+
|
|
+ def baz():
|
|
+ pass
|
|
+ '''
|
|
+
|
|
+ def __init__(self, globals, version, with_successor=None):
|
|
+ '''
|
|
+ Constructor of the decorator 'is_deprecated'.
|
|
+
|
|
+ :param globals: Module globals
|
|
+ :param version: Version to be deprecated
|
|
+ :param with_successor: Successor function (optional)
|
|
+ :return:
|
|
+ '''
|
|
+ _DeprecationDecorator.__init__(self, globals, version)
|
|
+ self._successor = with_successor
|
|
+
|
|
+ def __call__(self, function):
|
|
+ '''
|
|
+ Callable method of the decorator object when
|
|
+ the decorated function is gets called.
|
|
+
|
|
+ :param function:
|
|
+ :return:
|
|
+ '''
|
|
+ _DeprecationDecorator.__call__(self, function)
|
|
+
|
|
+ def _decorate(*args, **kwargs):
|
|
+ '''
|
|
+ Decorator function.
|
|
+
|
|
+ :param args:
|
|
+ :param kwargs:
|
|
+ :return:
|
|
+ '''
|
|
+ if self._curr_version < self._exp_version:
|
|
+ msg = ['The function "{f_name}" is deprecated and will '
|
|
+ 'expire in version "{version_name}".'.format(f_name=self._function.func_name,
|
|
+ version_name=self._exp_version_name)]
|
|
+ if self._successor:
|
|
+ msg.append('Use successor "{successor}" instead.'.format(successor=self._successor))
|
|
+ log.warning(' '.join(msg))
|
|
+ else:
|
|
+ msg = ['The lifetime of the function "{f_name}" expired.'.format(f_name=self._function.func_name)]
|
|
+ if self._successor:
|
|
+ msg.append('Please use its successor "{successor}" instead.'.format(successor=self._successor))
|
|
+ log.warning(' '.join(msg))
|
|
+ raise CommandExecutionError(' '.join(msg))
|
|
+ return self._call_function(kwargs)
|
|
+ return _decorate
|
|
+
|
|
+
|
|
+is_deprecated = _IsDeprecated
|
|
+
|
|
+
|
|
+class _WithDeprecated(_DeprecationDecorator):
|
|
+ '''
|
|
+ This decorator should be used with the successor functions
|
|
+ to mark them as a new and alter its behavior in a corresponding way.
|
|
+ It is used alone if a function content or function signature
|
|
+ needs to be replaced, leaving the name of the function same.
|
|
+ In case function needs to be renamed or just dropped, it has
|
|
+ to be used in pair with 'is_deprecated' decorator.
|
|
+
|
|
+ It has the following functionality:
|
|
+
|
|
+ 1. Put a warning level message to the log, in case a component
|
|
+ is using its deprecated version.
|
|
+
|
|
+ 2. Switch between old and new function in case an older version
|
|
+ is configured for the desired use.
|
|
+
|
|
+ 3. Raise an exception, if deprecated version reached EOL and
|
|
+ point out for the new version.
|
|
+
|
|
+ Usage of this decorator as follows. If 'with_name' is not specified,
|
|
+ then the name of the deprecated function is assumed with the "_" prefix.
|
|
+ In this case, in order to deprecate a function, it is required:
|
|
+
|
|
+ - Add a prefix "_" to an existing function. E.g.: "foo()" to "_foo()".
|
|
+
|
|
+ - Implement a new function with exactly the same name, just without
|
|
+ the prefix "_".
|
|
+
|
|
+ Example:
|
|
+
|
|
+ from salt.util.decorators import with_deprecated
|
|
+
|
|
+ @with_deprecated(globals(), "Beryllium")
|
|
+ def foo():
|
|
+ "This is a new function"
|
|
+
|
|
+ def _foo():
|
|
+ "This is a deprecated function"
|
|
+
|
|
+
|
|
+ In case there is a need to deprecate a function and rename it,
|
|
+ the decorator shuld be used with the 'with_name' parameter. This
|
|
+ parameter is pointing to the existing deprecated function. In this
|
|
+ case deprecation process as follows:
|
|
+
|
|
+ - Leave a deprecated function without changes, as is.
|
|
+
|
|
+ - Implement a new function and decorate it with this decorator.
|
|
+
|
|
+ - Set a parameter 'with_name' to the deprecated function.
|
|
+
|
|
+ - If a new function has a different name than a deprecated,
|
|
+ decorate a deprecated function with the 'is_deprecated' decorator
|
|
+ in order to let the function have a deprecated behavior.
|
|
+
|
|
+ Example:
|
|
+
|
|
+ from salt.util.decorators import with_deprecated
|
|
+
|
|
+ @with_deprecated(globals(), "Beryllium", with_name="an_old_function")
|
|
+ def a_new_function():
|
|
+ "This is a new function"
|
|
+
|
|
+ @is_deprecated(globals(), "Beryllium", with_successor="a_new_function")
|
|
+ def an_old_function():
|
|
+ "This is a deprecated function"
|
|
+
|
|
+ '''
|
|
+ MODULE_NAME = '__virtualname__'
|
|
+ CFG_KEY = 'use_deprecated'
|
|
+
|
|
+ def __init__(self, globals, version, with_name=None):
|
|
+ '''
|
|
+ Constructor of the decorator 'with_deprecated'
|
|
+
|
|
+ :param globals:
|
|
+ :param version:
|
|
+ :param with_name:
|
|
+ :return:
|
|
+ '''
|
|
+ _DeprecationDecorator.__init__(self, globals, version)
|
|
+ self._with_name = with_name
|
|
+
|
|
+ def _set_function(self, function):
|
|
+ '''
|
|
+ Based on the configuration, set to execute an old or a new function.
|
|
+ :return:
|
|
+ '''
|
|
+ full_name = "{m_name}.{f_name}".format(m_name=self._globals.get(self.MODULE_NAME, ''),
|
|
+ f_name=function.func_name)
|
|
+ if full_name.startswith("."):
|
|
+ self._raise_later = CommandExecutionError('Module not found for function "{f_name}"'.format(
|
|
+ f_name=function.func_name))
|
|
+
|
|
+ if full_name in self._options.get(self.CFG_KEY, list()):
|
|
+ self._function = self._globals.get(self._with_name or "_{0}".format(function.func_name))
|
|
+
|
|
+ def _is_used_deprecated(self):
|
|
+ '''
|
|
+ Returns True, if a component configuration explicitly is
|
|
+ asking to use an old version of the deprecated function.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ return "{m_name}.{f_name}".format(m_name=self._globals.get(self.MODULE_NAME, ''),
|
|
+ f_name=self._orig_f_name) in self._options.get(self.CFG_KEY, list())
|
|
+
|
|
+ def __call__(self, function):
|
|
+ '''
|
|
+ Callable method of the decorator object when
|
|
+ the decorated function is gets called.
|
|
+
|
|
+ :param function:
|
|
+ :return:
|
|
+ '''
|
|
+ _DeprecationDecorator.__call__(self, function)
|
|
+
|
|
+ def _decorate(*args, **kwargs):
|
|
+ '''
|
|
+ Decorator function.
|
|
+
|
|
+ :param args:
|
|
+ :param kwargs:
|
|
+ :return:
|
|
+ '''
|
|
+ self._set_function(function)
|
|
+ if self._is_used_deprecated():
|
|
+ if self._curr_version < self._exp_version:
|
|
+ msg = list()
|
|
+ if self._with_name:
|
|
+ msg.append('The function "{f_name}" is deprecated and will '
|
|
+ 'expire in version "{version_name}".'.format(
|
|
+ f_name=self._with_name.startswith("_") and self._orig_f_name or self._with_name,
|
|
+ version_name=self._exp_version_name))
|
|
+ else:
|
|
+ msg.append('The function is using its deprecated version and will '
|
|
+ 'expire in version "{version_name}".'.format(version_name=self._exp_version_name))
|
|
+ msg.append('Use its successor "{successor}" instead.'.format(successor=self._orig_f_name))
|
|
+ log.warning(' '.join(msg))
|
|
+ else:
|
|
+ msg_patt = 'The lifetime of the function "{f_name}" expired.'
|
|
+ if '_' + self._orig_f_name == self._function.func_name:
|
|
+ msg = [msg_patt.format(f_name=self._orig_f_name),
|
|
+ 'Please turn off its deprecated version in the configuration']
|
|
+ else:
|
|
+ msg = ['Although function "{f_name}" is called, an alias "{f_alias}" '
|
|
+ 'is configured as its deprecated version.'.format(
|
|
+ f_name=self._orig_f_name, f_alias=self._with_name or self._orig_f_name),
|
|
+ msg_patt.format(f_name=self._with_name or self._orig_f_name),
|
|
+ 'Please use its successor "{successor}" instead.'.format(successor=self._orig_f_name)]
|
|
+ log.error(' '.join(msg))
|
|
+ raise CommandExecutionError(' '.join(msg))
|
|
+ return self._call_function(kwargs)
|
|
+
|
|
+ _decorate.__doc__ = self._function.__doc__
|
|
+ return _decorate
|
|
+
|
|
+
|
|
+with_deprecated = _WithDeprecated
|
|
diff --git a/tests/unit/modules/status_test.py b/tests/unit/modules/status_test.py
|
|
index 191da09..b5cee4f 100644
|
|
--- a/tests/unit/modules/status_test.py
|
|
+++ b/tests/unit/modules/status_test.py
|
|
@@ -5,15 +5,14 @@ from __future__ import absolute_import
|
|
|
|
# Import Salt Libs
|
|
from salt.modules import status
|
|
+from salt.exceptions import CommandExecutionError
|
|
|
|
# Import Salt Testing Libs
|
|
-from salttesting import skipIf, TestCase
|
|
+from salttesting import TestCase
|
|
from salttesting.helpers import ensure_in_syspath
|
|
from salttesting.mock import (
|
|
MagicMock,
|
|
patch,
|
|
- NO_MOCK,
|
|
- NO_MOCK_REASON
|
|
)
|
|
|
|
ensure_in_syspath('../../')
|
|
@@ -22,36 +21,67 @@ ensure_in_syspath('../../')
|
|
status.__salt__ = {}
|
|
|
|
|
|
-@skipIf(NO_MOCK, NO_MOCK_REASON)
|
|
class StatusTestCase(TestCase):
|
|
'''
|
|
test modules.status functions
|
|
'''
|
|
+
|
|
def test_uptime(self):
|
|
'''
|
|
- test modules.status.uptime function
|
|
+ Test modules.status.uptime function, new version
|
|
+ :return:
|
|
+ '''
|
|
+ class ProcUptime(object):
|
|
+ def __init__(self, *args, **kwargs):
|
|
+ self.data = "773865.18 1003405.46"
|
|
+
|
|
+ def read(self):
|
|
+ return self.data
|
|
+
|
|
+ with patch.dict(status.__salt__, {'cmd.run': MagicMock(return_value="1\n2\n3")}):
|
|
+ with patch('os.path.exists', MagicMock(return_value=True)):
|
|
+ with patch('time.time', MagicMock(return_value=1458821523.72)):
|
|
+ status.open = ProcUptime
|
|
+ u_time = status.uptime()
|
|
+ self.assertEqual(u_time['users'], 3)
|
|
+ self.assertEqual(u_time['seconds'], 773865)
|
|
+ self.assertEqual(u_time['days'], 8)
|
|
+ self.assertEqual(u_time['time'], '22:57')
|
|
+
|
|
+ def test_uptime_failure(self):
|
|
+ '''
|
|
+ Test modules.status.uptime function should raise an exception if /proc/uptime does not exists.
|
|
+ :return:
|
|
+ '''
|
|
+ with patch('os.path.exists', MagicMock(return_value=False)):
|
|
+ with self.assertRaises(CommandExecutionError):
|
|
+ status.uptime()
|
|
+
|
|
+ def test_deprecated_uptime(self):
|
|
+ '''
|
|
+ test modules.status.uptime function, deprecated version
|
|
'''
|
|
mock_uptime = 'very often'
|
|
mock_run = MagicMock(return_value=mock_uptime)
|
|
with patch.dict(status.__salt__, {'cmd.run': mock_run}):
|
|
- self.assertEqual(status.uptime(), mock_uptime)
|
|
+ self.assertEqual(status._uptime(), mock_uptime)
|
|
|
|
mock_uptime = 'very idle'
|
|
mock_run = MagicMock(return_value=mock_uptime)
|
|
with patch.dict(status.__salt__, {'cmd.run': mock_run}):
|
|
with patch('os.path.exists', MagicMock(return_value=True)):
|
|
- self.assertEqual(status.uptime(human_readable=False), mock_uptime.split()[0])
|
|
+ self.assertEqual(status._uptime(human_readable=False), mock_uptime.split()[0])
|
|
|
|
mock_uptime = ''
|
|
mock_return = 'unexpected format in /proc/uptime'
|
|
mock_run = MagicMock(return_value=mock_uptime)
|
|
with patch.dict(status.__salt__, {'cmd.run': mock_run}):
|
|
with patch('os.path.exists', MagicMock(return_value=True)):
|
|
- self.assertEqual(status.uptime(human_readable=False), mock_return)
|
|
+ self.assertEqual(status._uptime(human_readable=False), mock_return)
|
|
|
|
mock_return = 'cannot find /proc/uptime'
|
|
with patch('os.path.exists', MagicMock(return_value=False)):
|
|
- self.assertEqual(status.uptime(human_readable=False), mock_return)
|
|
+ self.assertEqual(status._uptime(human_readable=False), mock_return)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
diff --git a/tests/unit/utils/decorators_test.py b/tests/unit/utils/decorators_test.py
|
|
new file mode 100644
|
|
index 0000000..4078340
|
|
--- /dev/null
|
|
+++ b/tests/unit/utils/decorators_test.py
|
|
@@ -0,0 +1,232 @@
|
|
+# -*- coding: utf-8 -*-
|
|
+'''
|
|
+ :codeauthor: :email:`Bo Maryniuk (bo@suse.de)`
|
|
+ unit.utils.decorators_test
|
|
+'''
|
|
+
|
|
+# Import Python libs
|
|
+from __future__ import absolute_import
|
|
+
|
|
+# Import Salt Testing libs
|
|
+from salttesting import TestCase
|
|
+from salttesting.helpers import ensure_in_syspath
|
|
+from salt.utils import decorators
|
|
+from salt.version import SaltStackVersion
|
|
+from salt.exceptions import CommandExecutionError
|
|
+
|
|
+ensure_in_syspath('../../')
|
|
+
|
|
+
|
|
+class DummyLogger(object):
|
|
+ '''
|
|
+ Dummy logger accepts everything and simply logs
|
|
+ '''
|
|
+ def __init__(self, messages):
|
|
+ self._messages = messages
|
|
+
|
|
+ def __getattr__(self, item):
|
|
+ return self._log
|
|
+
|
|
+ def _log(self, msg):
|
|
+ self._messages.append(msg)
|
|
+
|
|
+
|
|
+class DecoratorsTest(TestCase):
|
|
+ '''
|
|
+ Testing decorators.
|
|
+ '''
|
|
+ def old_function(self):
|
|
+ return "old"
|
|
+
|
|
+ def new_function(self):
|
|
+ return "new"
|
|
+
|
|
+ def _mk_version(self, name):
|
|
+ '''
|
|
+ Make a version
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ return name, SaltStackVersion.from_name(name)
|
|
+
|
|
+ def setUp(self):
|
|
+ '''
|
|
+ Setup a test
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs = {
|
|
+ '__virtualname__': 'test',
|
|
+ '__opts__': {},
|
|
+ 'old_function': self.old_function,
|
|
+ 'new_function': self.new_function,
|
|
+ }
|
|
+ self.messages = list()
|
|
+ decorators.log = DummyLogger(self.messages)
|
|
+
|
|
+ def test_is_deprecated_version_eol(self):
|
|
+ '''
|
|
+ Use of is_deprecated will result to the exception,
|
|
+ if the expiration version is lower than the current version.
|
|
+ A successor function is not pointed out.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ depr = decorators.is_deprecated(self.globs, "Helium")
|
|
+ depr._curr_version = self._mk_version("Beryllium")[1]
|
|
+ with self.assertRaises(CommandExecutionError):
|
|
+ depr(self.old_function)()
|
|
+ self.assertEqual(self.messages,
|
|
+ ['The lifetime of the function "old_function" expired.'])
|
|
+
|
|
+ def test_is_deprecated_with_successor_eol(self):
|
|
+ '''
|
|
+ Use of is_deprecated will result to the exception,
|
|
+ if the expiration version is lower than the current version.
|
|
+ A successor function is pointed out.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ depr = decorators.is_deprecated(self.globs, "Helium", with_successor="new_function")
|
|
+ depr._curr_version = self._mk_version("Beryllium")[1]
|
|
+ with self.assertRaises(CommandExecutionError):
|
|
+ depr(self.old_function)()
|
|
+ self.assertEqual(self.messages,
|
|
+ ['The lifetime of the function "old_function" expired. '
|
|
+ 'Please use its successor "new_function" instead.'])
|
|
+
|
|
+ def test_is_deprecated(self):
|
|
+ '''
|
|
+ Use of is_deprecated will result to the log message,
|
|
+ if the expiration version is higher than the current version.
|
|
+ A successor function is not pointed out.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ depr = decorators.is_deprecated(self.globs, "Beryllium")
|
|
+ depr._curr_version = self._mk_version("Helium")[1]
|
|
+ self.assertEqual(depr(self.old_function)(), self.old_function())
|
|
+ self.assertEqual(self.messages,
|
|
+ ['The function "old_function" is deprecated '
|
|
+ 'and will expire in version "Beryllium".'])
|
|
+
|
|
+ def test_is_deprecated_with_successor(self):
|
|
+ '''
|
|
+ Use of is_deprecated will result to the log message,
|
|
+ if the expiration version is higher than the current version.
|
|
+ A successor function is pointed out.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ depr = decorators.is_deprecated(self.globs, "Beryllium", with_successor="old_function")
|
|
+ depr._curr_version = self._mk_version("Helium")[1]
|
|
+ self.assertEqual(depr(self.old_function)(), self.old_function())
|
|
+ self.assertEqual(self.messages,
|
|
+ ['The function "old_function" is deprecated '
|
|
+ 'and will expire in version "Beryllium". '
|
|
+ 'Use successor "old_function" instead.'])
|
|
+
|
|
+ def test_with_deprecated_notfound(self):
|
|
+ '''
|
|
+ Test with_deprecated should raise an exception, if a same name
|
|
+ function with the "_" prefix not implemented.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs['__opts__']['use_deprecated'] = ['test.new_function']
|
|
+ depr = decorators.with_deprecated(self.globs, "Beryllium")
|
|
+ depr._curr_version = self._mk_version("Helium")[1]
|
|
+ with self.assertRaises(CommandExecutionError):
|
|
+ depr(self.new_function)()
|
|
+ self.assertEqual(self.messages,
|
|
+ ['The function is using its deprecated version and will expire in version "Beryllium". '
|
|
+ 'Use its successor "new_function" instead.'])
|
|
+
|
|
+ def test_with_deprecated_found(self):
|
|
+ '''
|
|
+ Test with_deprecated should not raise an exception, if a same name
|
|
+ function with the "_" prefix is implemented, but should use
|
|
+ an old version instead, if "use_deprecated" is requested.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs['__opts__']['use_deprecated'] = ['test.new_function']
|
|
+ self.globs['_new_function'] = self.old_function
|
|
+ depr = decorators.with_deprecated(self.globs, "Beryllium")
|
|
+ depr._curr_version = self._mk_version("Helium")[1]
|
|
+ self.assertEqual(depr(self.new_function)(), self.old_function())
|
|
+ log_msg = ['The function is using its deprecated version and will expire in version "Beryllium". '
|
|
+ 'Use its successor "new_function" instead.']
|
|
+ self.assertEqual(self.messages, log_msg)
|
|
+
|
|
+ def test_with_deprecated_found_eol(self):
|
|
+ '''
|
|
+ Test with_deprecated should raise an exception, if a same name
|
|
+ function with the "_" prefix is implemented, "use_deprecated" is requested
|
|
+ and EOL is reached.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs['__opts__']['use_deprecated'] = ['test.new_function']
|
|
+ self.globs['_new_function'] = self.old_function
|
|
+ depr = decorators.with_deprecated(self.globs, "Helium")
|
|
+ depr._curr_version = self._mk_version("Beryllium")[1]
|
|
+ with self.assertRaises(CommandExecutionError):
|
|
+ depr(self.new_function)()
|
|
+ self.assertEqual(self.messages,
|
|
+ ['Although function "new_function" is called, an alias "new_function" '
|
|
+ 'is configured as its deprecated version. The lifetime of the function '
|
|
+ '"new_function" expired. Please use its successor "new_function" instead.'])
|
|
+
|
|
+ def test_with_deprecated_no_conf(self):
|
|
+ '''
|
|
+ Test with_deprecated should not raise an exception, if a same name
|
|
+ function with the "_" prefix is implemented, but should use
|
|
+ a new version instead, if "use_deprecated" is not requested.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs['_new_function'] = self.old_function
|
|
+ depr = decorators.with_deprecated(self.globs, "Beryllium")
|
|
+ depr._curr_version = self._mk_version("Helium")[1]
|
|
+ self.assertEqual(depr(self.new_function)(), self.new_function())
|
|
+ self.assertFalse(self.messages)
|
|
+
|
|
+ def test_with_deprecated_with_name(self):
|
|
+ '''
|
|
+ Test with_deprecated should not raise an exception, if a different name
|
|
+ function is implemented and specified with the "with_name" parameter,
|
|
+ but should use an old version instead and log a warning log message.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs['__opts__']['use_deprecated'] = ['test.new_function']
|
|
+ depr = decorators.with_deprecated(self.globs, "Beryllium", with_name="old_function")
|
|
+ depr._curr_version = self._mk_version("Helium")[1]
|
|
+ self.assertEqual(depr(self.new_function)(), self.old_function())
|
|
+ self.assertEqual(self.messages,
|
|
+ ['The function "old_function" is deprecated and will expire in version "Beryllium". '
|
|
+ 'Use its successor "new_function" instead.'])
|
|
+
|
|
+ def test_with_deprecated_with_name_eol(self):
|
|
+ '''
|
|
+ Test with_deprecated should raise an exception, if a different name
|
|
+ function is implemented and specified with the "with_name" parameter
|
|
+ and EOL is reached.
|
|
+
|
|
+ :return:
|
|
+ '''
|
|
+ self.globs['__opts__']['use_deprecated'] = ['test.new_function']
|
|
+ depr = decorators.with_deprecated(self.globs, "Helium", with_name="old_function")
|
|
+ depr._curr_version = self._mk_version("Beryllium")[1]
|
|
+ with self.assertRaises(CommandExecutionError):
|
|
+ depr(self.new_function)()
|
|
+ self.assertEqual(self.messages,
|
|
+ ['Although function "new_function" is called, '
|
|
+ 'an alias "old_function" is configured as its deprecated version. '
|
|
+ 'The lifetime of the function "old_function" expired. '
|
|
+ 'Please use its successor "new_function" instead.'])
|
|
+
|
|
+
|
|
+if __name__ == '__main__':
|
|
+ from integration import run_tests
|
|
+ run_tests(DecoratorsTest, needs_daemon=False)
|
|
--
|
|
2.1.4
|
|
|