Files
freeipa/ipapython/install/core.py
T
Martin Basti 45e3aee352 Pylint: enable check for unused-variables
Unused variables may:
* make code less readable
* create dead code
* potentialy hide issues/errors

Enabled check should prevent to leave unused variable in code

Check is locally disabled for modules that fix is not clear or easy or have too many occurences of
unused variables

Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
Reviewed-By: Stanislav Laznicka <slaznick@redhat.com>
2016-09-27 13:35:58 +02:00

609 lines
16 KiB
Python

#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
"""
The framework core.
"""
import abc
import collections
import functools
import itertools
import sys
import six
from ipapython.ipa_log_manager import root_logger
from . import util
from .util import from_
# pylint: disable=unused-variable
__all__ = ['InvalidStateError', 'KnobValueError', 'Property', 'Knob',
'Configurable', 'Group', 'Component', 'Composite']
# Configurable states
_VALIDATE_PENDING = 'VALIDATE_PENDING'
_VALIDATE_RUNNING = 'VALIDATE_RUNNING'
_EXECUTE_PENDING = 'EXECUTE_PENDING'
_EXECUTE_RUNNING = 'EXECUTE_RUNNING'
_STOPPED = 'STOPPED'
_FAILED = 'FAILED'
_CLOSED = 'CLOSED'
_missing = object()
_counter = itertools.count()
@functools.cmp_to_key
def _class_key(a, b):
if a is b:
return 0
elif issubclass(a, b):
return -1
elif issubclass(b, a):
return 1
else:
return 0
class InvalidStateError(Exception):
pass
class KnobValueError(ValueError):
def __init__(self, name, message):
super(KnobValueError, self).__init__(message)
self.name = name
class PropertyBase(six.with_metaclass(util.InnerClassMeta, object)):
# shut up pylint
__outer_class__ = None
__outer_name__ = None
_order = None
@property
def default(self):
raise AttributeError('default')
def __init__(self, outer):
pass
def __get__(self, obj, obj_type):
while obj is not None:
try:
return obj.__dict__[self.__outer_name__]
except KeyError:
pass
obj = obj._get_fallback()
try:
return self.default
except AttributeError:
raise AttributeError(self.__outer_name__)
def __set__(self, obj, value):
try:
obj.__dict__[self.__outer_name__] = value
except KeyError:
raise AttributeError(self.__outer_name__)
def __delete__(self, obj):
try:
del obj.__dict__[self.__outer_name__]
except KeyError:
raise AttributeError(self.__outer_name__)
def Property(default=_missing):
class_dict = {}
if default is not _missing:
class_dict['default'] = default
return util.InnerClassMeta('Property', (PropertyBase,), class_dict)
class KnobBase(PropertyBase):
type = None
sensitive = False
deprecated = False
description = None
cli_name = None
cli_short_name = None
cli_aliases = None
cli_metavar = None
def __init__(self, outer):
self.outer = outer
def validate(self, value):
pass
@classmethod
def default_getter(cls, func):
@property
def default(self):
return func(self.outer)
cls.default = default
return cls
@classmethod
def validator(cls, func):
def validate(self, value):
func(self.outer, value)
super(cls, self).validate(value)
cls.validate = validate
return cls
def Knob(type_or_base, default=_missing, sensitive=_missing,
deprecated=_missing, description=_missing, cli_name=_missing,
cli_short_name=_missing, cli_aliases=_missing, cli_metavar=_missing):
class_dict = {}
class_dict['_order'] = next(_counter)
if (not isinstance(type_or_base, type) or
not issubclass(type_or_base, KnobBase)):
class_dict['type'] = type_or_base
type_or_base = KnobBase
if default is not _missing:
class_dict['default'] = default
if sensitive is not _missing:
class_dict['sensitive'] = sensitive
if deprecated is not _missing:
class_dict['deprecated'] = deprecated
if description is not _missing:
class_dict['description'] = description
if cli_name is not _missing:
class_dict['cli_name'] = cli_name
if cli_short_name is not _missing:
class_dict['cli_short_name'] = cli_short_name
if cli_aliases is not _missing:
class_dict['cli_aliases'] = cli_aliases
if cli_metavar is not _missing:
class_dict['cli_metavar'] = cli_metavar
return util.InnerClassMeta('Knob', (type_or_base,), class_dict)
class Configurable(six.with_metaclass(abc.ABCMeta, object)):
"""
Base class of all configurables.
FIXME: details of validate/execute, properties and knobs
"""
@classmethod
def properties(cls):
"""
Iterate over properties defined for the configurable.
"""
assert not hasattr(super(Configurable, cls), 'properties')
seen = set()
for owner_cls in cls.__mro__:
result = []
for name, prop_cls in owner_cls.__dict__.items():
if name in seen:
continue
seen.add(name)
if not isinstance(prop_cls, type):
continue
if not issubclass(prop_cls, PropertyBase):
continue
result.append((prop_cls._order, owner_cls, name))
result = sorted(result, key=lambda r: r[0])
for order, owner_cls, name in result:
yield owner_cls, name
@classmethod
def knobs(cls):
for owner_cls, name in cls.properties():
prop_cls = getattr(owner_cls, name)
if issubclass(prop_cls, KnobBase):
yield owner_cls, name
@classmethod
def group(cls):
assert not hasattr(super(Configurable, cls), 'group')
return None
def __init__(self, **kwargs):
"""
Initialize the configurable.
"""
self.log = root_logger
cls = self.__class__
for owner_cls, name in cls.properties():
if name.startswith('_'):
continue
prop_cls = getattr(owner_cls, name)
if not isinstance(prop_cls, type):
continue
if not issubclass(prop_cls, PropertyBase):
continue
try:
value = kwargs.pop(name)
except KeyError:
pass
else:
setattr(self, name, value)
for owner_cls, name in cls.knobs():
if name.startswith('_'):
continue
if not isinstance(self, owner_cls):
continue
value = getattr(self, name, None)
if value is None:
continue
prop_cls = getattr(owner_cls, name)
prop = prop_cls(self)
try:
prop.validate(value)
except KnobValueError:
raise
except ValueError as e:
raise KnobValueError(name, str(e))
if kwargs:
extra = sorted(kwargs)
raise TypeError(
"{0}() got {1} unexpected keyword arguments: {2}".format(
type(self).__name__,
len(extra),
', '.join(repr(name) for name in extra)))
self._reset()
def _reset(self):
assert not hasattr(super(Configurable, self), '_reset')
self.__state = _VALIDATE_PENDING
self.__gen = util.run_generator_with_yield_from(self._configure())
def _get_components(self):
assert not hasattr(super(Configurable, self), '_get_components')
raise TypeError("{0} is not composite".format(self))
def _get_fallback(self):
return None
@abc.abstractmethod
def _configure(self):
"""
Coroutine which defines the logic of the configurable.
"""
assert not hasattr(super(Configurable, self), '_configure')
self.__transition(_VALIDATE_RUNNING, _EXECUTE_PENDING)
while self.__state != _EXECUTE_RUNNING:
yield
def run(self):
"""
Run the configurable.
"""
self.validate()
if self.__state == _EXECUTE_PENDING:
self.execute()
def validate(self):
"""
Run the validation part of the configurable.
"""
for nothing in self._validator():
pass
def _validator(self):
"""
Coroutine which runs the validation part of the configurable.
"""
return self.__runner(_VALIDATE_PENDING,
_VALIDATE_RUNNING,
self._handle_validate_exception)
def execute(self):
"""
Run the execution part of the configurable.
"""
for nothing in self._executor():
pass
def _executor(self):
"""
Coroutine which runs the execution part of the configurable.
"""
return self.__runner(_EXECUTE_PENDING,
_EXECUTE_RUNNING,
self._handle_execute_exception)
def done(self):
"""
Return True if the configurable has finished.
"""
return self.__state in (_STOPPED, _FAILED, _CLOSED)
def run_until_executing(self, gen):
while self.__state != _EXECUTE_RUNNING:
try:
yield next(gen)
except StopIteration:
break
def __runner(self, pending_state, running_state, exc_handler):
self.__transition(pending_state, running_state)
step = lambda: next(self.__gen)
while True:
try:
step()
except StopIteration:
self.__transition(running_state, _STOPPED)
break
except GeneratorExit:
self.__transition(running_state, _CLOSED)
break
except BaseException:
exc_info = sys.exc_info()
try:
exc_handler(exc_info)
except BaseException:
self.__transition(running_state, _FAILED)
raise
if self.__state != running_state:
break
try:
yield
except BaseException:
exc_info = sys.exc_info()
step = lambda: self.__gen.throw(*exc_info)
else:
step = lambda: next(self.__gen)
def _handle_exception(self, exc_info):
assert not hasattr(super(Configurable, self), '_handle_exception')
six.reraise(*exc_info)
def _handle_validate_exception(self, exc_info):
assert not hasattr(super(Configurable, self),
'_handle_validate_exception')
self._handle_exception(exc_info)
def _handle_execute_exception(self, exc_info):
assert not hasattr(super(Configurable, self),
'_handle_execute_exception')
self._handle_exception(exc_info)
def __transition(self, from_state, to_state):
if self.__state != from_state:
raise InvalidStateError(self.__state)
self.__state = to_state
class Group(Configurable):
@classmethod
def group(cls):
return cls
class ComponentMeta(util.InnerClassMeta, abc.ABCMeta):
pass
class ComponentBase(six.with_metaclass(ComponentMeta, Configurable)):
# shut up pylint
__outer_class__ = None
__outer_name__ = None
_order = None
@classmethod
def group(cls):
result = super(ComponentBase, cls).group()
if result is not None:
return result
else:
return cls.__outer_class__.group()
def __init__(self, parent, **kwargs):
self.__parent = parent
super(ComponentBase, self).__init__(**kwargs)
@property
def parent(self):
return self.__parent
def __get__(self, obj, obj_type):
obj.__dict__[self.__outer_name__] = self
return self
def _get_fallback(self):
return self.__parent
def _handle_exception(self, exc_info):
try:
super(ComponentBase, self)._handle_exception(exc_info)
except BaseException:
exc_info = sys.exc_info()
self.__parent._handle_exception(exc_info)
def Component(cls):
class_dict = {}
class_dict['_order'] = next(_counter)
return ComponentMeta('Component', (ComponentBase, cls), class_dict)
class Composite(Configurable):
"""
Configurable composed of any number of components.
Provides knobs of all child components.
"""
@classmethod
def properties(cls):
name_dict = {}
owner_dict = collections.OrderedDict()
for owner_cls, name in super(Composite, cls).properties():
name_dict[name] = owner_cls
owner_dict.setdefault(owner_cls, []).append(name)
for owner_cls, name in cls.components():
comp_cls = getattr(cls, name)
for owner_cls, name in comp_cls.knobs():
if hasattr(cls, name):
continue
try:
last_owner_cls = name_dict[name]
except KeyError:
name_dict[name] = owner_cls
owner_dict.setdefault(owner_cls, []).append(name)
else:
knob_cls = getattr(owner_cls, name)
last_knob_cls = getattr(last_owner_cls, name)
if issubclass(knob_cls, last_knob_cls):
name_dict[name] = owner_cls
owner_dict[last_owner_cls].remove(name)
owner_dict.setdefault(owner_cls, [])
if name not in owner_dict[owner_cls]:
owner_dict[owner_cls].append(name)
elif not issubclass(last_knob_cls, knob_cls):
raise TypeError("{0}.knobs(): conflicting definitions "
"of '{1}' in {2} and {3}".format(
cls.__name__,
name,
last_owner_cls.__name__,
owner_cls.__name__))
for owner_cls in sorted(owner_dict, key=_class_key):
for name in owner_dict[owner_cls]:
yield owner_cls, name
@classmethod
def components(cls):
assert not hasattr(super(Composite, cls), 'components')
seen = set()
for owner_cls in cls.__mro__:
result = []
for name, comp_cls in owner_cls.__dict__.items():
if name in seen:
continue
seen.add(name)
if not isinstance(comp_cls, type):
continue
if not issubclass(comp_cls, ComponentBase):
continue
result.append((comp_cls._order, owner_cls, name))
result = sorted(result, key=lambda r: r[0])
for order, owner_cls, name in result:
yield owner_cls, name
def __getattr__(self, name):
for owner_cls, knob_name in self.knobs():
if knob_name == name:
break
else:
raise AttributeError(name)
for component in self.__components:
if isinstance(component, owner_cls):
break
else:
raise AttributeError(name)
return getattr(component, name)
def _reset(self):
self.__components = list(self._get_components())
super(Composite, self)._reset()
def _get_components(self):
for owner_cls, name in self.components():
yield getattr(self, name)
def _configure(self):
validate = [(c, c._validator()) for c in self.__components]
while True:
new_validate = []
for child, validator in validate:
try:
next(validator)
except StopIteration:
pass
else:
new_validate.append((child, validator))
if not new_validate:
break
validate = new_validate
yield
if not self.__components:
return
yield from_(super(Composite, self)._configure())
execute = [(c, c._executor()) for c in self.__components
if not c.done()]
while True:
new_execute = []
for child, executor in execute:
try:
next(executor)
except StopIteration:
pass
else:
new_execute.append((child, executor))
if not new_execute:
break
execute = new_execute
yield