# 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. import functools import operator import re NEGOTIATE_VERSIONS = [ '1', # Added for auto choice for appropriate version ] SUPPORTED_MICROVERSIONS = [ '1.0', '1.1', '1.2', '1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11', '1.12', '1.13', # unused '1.14', '1.15', # unused '1.16', '1.17', '1.18', '1.19', '1.20', # unused '1.21', '1.22', '1.23', # unused '1.24', '1.25', '1.26', # unused '1.27', # unused '1.28', # Added for provider allocation (un)set (Ussuri) '1.29', '1.37', # unused '1.38', # Added for consumer types (Xena) '1.39', # Added any-traits support (Yoga) ] SUPPORTED_VERSIONS = SUPPORTED_MICROVERSIONS + NEGOTIATE_VERSIONS # The max microversion lower than which are all supported by this client. # This is used to automatically pick up the microversion to use. Change this # when you add a microversion to the `_SUPPORTED_VERSIONS` without a gap. # TestVersion.test_max_version_consistency checks its consistency. MAX_VERSION_NO_GAP = '1.29' @functools.total_ordering class _Version: _version_re = re.compile(r'^(\d) \. (\d+)$', re.VERBOSE | re.ASCII) def __init__(self, version): match = self._version_re.match(version) if not match: raise ValueError('invalid version number %s' % version) major, minor = match.group(1, 2) self.version = (int(major), int(minor)) def __str__(self): return '.'.join(str(v) for v in self.version) def __eq__(self, other): return self.version == other.version def __lt__(self, other): return self.version < other.version def _op(func, b, msg): return lambda a: func(_Version(a), _Version(b)) or msg def lt(b): msg = 'requires version less than %s' % b return _op(operator.lt, b, msg) def le(b): msg = 'requires at most version %s' % b return _op(operator.le, b, msg) def eq(b): msg = 'requires version %s' % b return _op(operator.eq, b, msg) def ne(b): msg = 'can not use version %s' % b return _op(operator.ne, b, msg) def ge(b): msg = 'requires at least version %s' % b return _op(operator.ge, b, msg) def gt(b): msg = 'requires version greater than %s' % b return _op(operator.gt, b, msg) def _compare(ver, *predicates, **kwargs): func = kwargs.get('op', all) if func(p(ver) is True for p in predicates): return True # construct an error message if the requirement not satisfied err_msg = 'Operation or argument is not supported with version %s; ' % ver err_detail = [p(ver) for p in predicates if p(ver) is not True] logic = ', and ' if func is all else ', or ' return err_msg + logic.join(err_detail) def compare(ver, *predicates, **kwargs): """Validate version satisfies provided predicates. kwargs['exc'] - boolean whether exception should be raised kwargs['op'] - (all, any) how predicates should be checked Examples: compare('1.1', version.gt('1.2'), exc=False) - False compare('1.1', version.eq('1.0'), version.eq('1.1'), op=any) - True """ exc = kwargs.get('exc', True) result = _compare(ver, *predicates, **kwargs) if result is not True: if exc: raise ValueError(result) return False return True def check(*predicates, **check_kwargs): """Decorator for command object method. See `compare` """ def wrapped(func): def inner(self, *args, **kwargs): compare(get_version(self), *predicates, **check_kwargs) return func(self, *args, **kwargs) return inner return wrapped def get_version(obj): """Extract version from a command object.""" try: if obj.app.client_manager.session is None: return MAX_VERSION_NO_GAP version = obj.app.client_manager.placement.api_version except AttributeError: # resource does not have api_version attr when docs are generated # so let's use the minimal one version = SUPPORTED_VERSIONS[0] return version class CheckerMixin(object): def check_version(self, *predicates, **kwargs): return compare(get_version(self), *predicates, **kwargs) def compare_version(self, *predicates, **kwargs): return compare(get_version(self), *predicates, exc=False, **kwargs)