def __init__(self, stored=True): self._manager = multiprocessing.Manager() self._dialog_renderer = CommandlineRenderer() self._data = self._manager.list() self._answers = AnswerStore(manager=self._manager) self._new_data = self._manager.list() self._errors = self._manager.list() self._stored = stored
def __init__(self, stored=True, config_model=None): self._manager = multiprocessing.Manager() self._dialog_renderer = CommandlineRenderer() self._data = self._manager.list() self._answers = AnswerStore(manager=self._manager) self._new_data = self._manager.list() self._errors = self._manager.list() self._stored = stored self._config_models = (config_model, ) if config_model else ()
def test_answerstore_load_and_translate_for_workflow(answerfile): m = MockManager() a = AnswerStore(manager=m) store = a._storage a.load_and_translate_for_workflow(answerfile, MockWorkflow) assert store['boolscope']['key1'] is True assert store['boolscope']['key2'] is False assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2'
def answer(args): """A command to record user choices to the questions in the answerfile. Saves user answer between leapp preupgrade runs. """ cfg = get_config() if args.section: args.section = list( itertools.chain(*[i.split(',') for i in args.section])) else: raise UsageError( 'At least one dialog section must be specified, ex. --section dialog.option=mychoice' ) try: sections = [ tuple((dialog_option.split('.', 2) + [value])) for dialog_option, value in [s.split('=', 2) for s in args.section] ] except ValueError: raise UsageError( "A bad formatted section has been passed. Expected format is dialog.option=mychoice" ) answerfile_path = cfg.get('report', 'answerfile') answerstore = AnswerStore() answerstore.load(answerfile_path) for dialog, option, value in sections: answerstore.answer(dialog, option, value) not_updated = answerstore.update(answerfile_path, allow_missing=args.add) if not_updated: sys.stderr.write( "WARNING: Only sections found in original userfile can be updated, ignoring {}\n" .format(",".join(not_updated)))
def test_answerstore_load(answerfile): m = MockManager() a = AnswerStore(manager=m) store = a._storage a.load(answerfile) assert 'scope1' in store.keys() assert 'scope2' in store.keys() assert 'boolscope' in store.keys() assert 'key1' in store['scope1'].keys() assert 'key1' in store['scope1'].keys() assert 'key1' in store['scope2'] assert 'key2' in store['scope2'] assert 'key1' in store['boolscope'] assert 'key2' in store['boolscope'] assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2' assert store['boolscope']['key1'] == 'True' assert store['boolscope']['key2'] == 'False'
def __init__(self, stored=True, config_model=None, answer_store=None): self._manager = multiprocessing.Manager() self._dialog_renderer = CommandlineRenderer() self._data = self._manager.list() self._answers = answer_store or AnswerStore(manager=self._manager) self._new_data = self._manager.list() self._commands = self._manager.list() self._errors = self._manager.list() self._stored = stored self._config_models = (config_model, ) if config_model else () self._dialogs = self._manager.list() self._stop_after_phase = self._manager.Value(bool, False)
def test_answerfile_get(monkeypatch, answerfile): entries_created = [] def mocked_create_audit_entry(event, data, message=None): entries_created.append((event, data, message)) m = MockManager() a = AnswerStore(manager=m) store = a._storage a.load(answerfile) monkeypatch.setattr(leapp.messaging.answerstore, 'create_audit_entry', mocked_create_audit_entry) # Testing boolscope assert store['boolscope']['key1'] == 'True' assert store['boolscope']['key2'] == 'False' result = a.get(scope='boolscope', fallback='boolscope') assert result != 'boolscope' assert result['key1'] == 'True' assert result['key2'] == 'False' assert entries_created assert entries_created[0][1]['scope'] == 'boolscope' assert entries_created[0][1]['fallback'] == 'boolscope' assert entries_created[0][1]['answer']['key1'] == 'True' assert entries_created[0][1]['answer']['key2'] == 'False' entries_created.pop() # Testing scope1 assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' result = a.get(scope='scope1', fallback='scope1') assert result assert result['key1'] == 'scope1.key1' assert result['key2'] == 'scope1.key2' assert entries_created assert entries_created[0][1]['scope'] == 'scope1' assert entries_created[0][1]['fallback'] == 'scope1' assert entries_created[0][1]['answer']['key1'] == 'scope1.key1' assert entries_created[0][1]['answer']['key2'] == 'scope1.key2' entries_created.pop() # Testing scope2 assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2' result = a.get(scope='scope2', fallback='scope2') assert result assert result['key1'] == 'scope2.key1' assert result['key2'] == 'scope2.key2' assert entries_created assert entries_created[0][1]['scope'] == 'scope2' assert entries_created[0][1]['fallback'] == 'scope2' assert entries_created[0][1]['answer']['key1'] == 'scope2.key1' assert entries_created[0][1]['answer']['key2'] == 'scope2.key2' entries_created.pop()
def __init__(self, logger=None, auto_reboot=False): """ :param logger: Optional logger to be used instead of leapp.workflow :type logger: Instance of :py:class:`logging.Logger` """ self.log = (logger or logging.getLogger('leapp')).getChild('workflow') self._errors = [] self._all_consumed = set() self._all_produced = set() self._initial = set() self._phase_actors = [] self._experimental_whitelist = set() self._auto_reboot = auto_reboot self._unhandled_exception = False self._answer_store = AnswerStore() self._dialogs = [] self._stop_after_phase_requested = False if self.configuration: config_actors = [ actor for actor in self.tag.actors if self.configuration in actor.produces ] if config_actors: if len(config_actors) == 1: self._phase_actors.append( (_ConfigPhase, PhaseActors((), 'Before'), PhaseActors(tuple(config_actors), 'Main'), PhaseActors((), 'After'))) else: config_actor_names = [a.name for a in config_actors] raise MultipleConfigActorsError(config_actor_names) self.description = self.description or type(self).__doc__ for phase in self.phases: phase.filter.tags += (self.tag, ) self._phase_actors.append(( phase, # filters all actors with the give tags # phasetag .Before self._apply_phase(phase.filter.get_before(), 'Before'), # phasetag self._apply_phase(phase.filter.get(), 'Main'), # phasetag .After self._apply_phase(phase.filter.get_after(), 'After')))
def test_answerfile_translate(answerfile): m = MockManager() a = AnswerStore(manager=m) store = a._storage a.load(answerfile) assert store['boolscope']['key1'] == 'True' assert store['boolscope']['key2'] == 'False' assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2' a.translate(MockDialogBoolScope) assert store['boolscope']['key1'] is True assert store['boolscope']['key2'] is False assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2'
def test_answerstore_answer(): m = MockManager() a = AnswerStore(manager=m) store = a._storage a.answer(scope='scope1', key='key1', value='scope1.key1') a.answer(scope='scope1', key='key2', value='scope1.key2') a.answer(scope='scope2', key='key1', value='scope2.key1') a.answer(scope='scope2', key='key2', value='scope2.key2') assert 'scope1' in store.keys() assert 'scope2' in store.keys() assert 'key1' in store['scope1'].keys() assert 'key1' in store['scope1'].keys() assert 'key1' in store['scope2'] assert 'key2' in store['scope2'] assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2'
class BaseMessaging(object): """ BaseMessaging is the Base class for all messaging implementations. It provides the basic interface that is supported within the framework. These are called the `produce` and `consume` methods. """ def __init__(self, stored=True): self._manager = multiprocessing.Manager() self._dialog_renderer = CommandlineRenderer() self._data = self._manager.list() self._answers = AnswerStore(manager=self._manager) self._new_data = self._manager.list() self._errors = self._manager.list() self._stored = stored def load_answers(self, answer_file, workflow): """ Loads answers from a given answer file :param answer_file: Path to file to load as answer file :type answer_file: str :param workflow: :py:class:`leapp.workflows.Workflow` instance to load the answers for. :type workflow: :py:class:`leapp.workflows.Workflow` :return: None """ self._answers.load_and_translate_for_workflow(answer_file, workflow) @property def stored(self): """ :return: If the messages are stored immediately, this function returns True, otherwise False. """ return self._stored def errors(self): """ Gets all produced errors. :return: List of newly produced errors """ return list(self._errors) def messages(self): """ Gets all newly produced messages. :return: List of newly processed messages """ return list(self._new_data) def _perform_load(self, consumes): """ Loads all messages that are requested from the `consumes` attribute of :py:class:`leapp.actors.Actor` :param consumes: Tuple or list of :py:class:`leapp.models.Model` types to preload :return: None """ raise NotImplementedError() def _process_message(self, message): """ This method performs the actual message sending, which can be sent over the network or stored in a database. :param message: The message data to process :type message: dict :return: Pass through a message that might get updated through the sending process. """ raise NotImplementedError() def load(self, consumes): """ Loads all messages that are requested from the `consumes` attribute of :py:class:`leapp.actors.Actor` :param consumes: Tuple or list of :py:class:`leapp.models.Model` types to preload :return: None :raises leapp.exceptions.CannotConsumeErrorMessages: When trying to consume ErrorModel """ if ErrorModel in consumes: raise CannotConsumeErrorMessages() self._perform_load(consumes) def report_error(self, message, severity, actor, details): """ Reports an execution error :param message: Message to print the error :type message: str :param severity: Severity of the error :type severity: ErrorSeverity :param actor: Actor name that produced the message :type actor: leapp.actors.Actor :param details: A dictionary where additional context information can be passed along with the error :type details: dict :return: None """ if details: details = json.dumps(details) model = ErrorModel(message=message, actor=actor.name, severity=severity, details=details, time=datetime.datetime.utcnow()) self._do_produce(model, actor, self._errors) def produce(self, model, actor): """ Called to send a message available for other actors. :param model: Model to send as message payload :type model: :py:class:`leapp.models.Model` :param actor: Actor that sends the message :type actor: :py:class:`leapp.actors.Actor` :return: the updated message dict :rtype: dict """ return self._do_produce(model, actor, self._new_data) def feed(self, model, actor): """ Called to pre-fill sent messages and make them available for other actors. :param model: Model to send as message payload :type model: :py:class:`leapp.models.Model` :param actor: Actor that sends the message :type actor: :py:class:`leapp.actors.Actor` :return: the updated message dict :rtype: dict """ return self._do_produce(model, actor, self._data, stored=False) def _do_produce(self, model, actor, target, stored=True): if not os.environ.get('LEAPP_HOSTNAME', None): os.environ['LEAPP_HOSTNAME'] = socket.getfqdn() data = json.dumps(model.dump(), sort_keys=True) message = { 'type': type(model).__name__, 'actor': type(actor).name, 'topic': model.topic.name, 'stamp': datetime.datetime.utcnow().isoformat() + 'Z', 'phase': os.environ.get('LEAPP_CURRENT_PHASE', 'NON-WORKFLOW-EXECUTION'), 'context': os.environ.get('LEAPP_EXECUTION_ID', 'TESTING-CONTEXT'), 'hostname': os.environ['LEAPP_HOSTNAME'], 'message': { 'data': data, 'hash': hashlib.sha256(data.encode('utf-8')).hexdigest() } } if stored and self.stored: self._process_message(message.copy()) target.append(message) return message def request_answers(self, dialog): return dialog.request_answers(self._answers, self._dialog_renderer) def consume(self, actor, *types): """ Returns all consumable messages and filters them by `types` :param types: Variable number of :py:class:`leapp.models.Model` derived types to filter messages to be consumed :param actor: Actor that consumes the data :return: Iterable with messages matching the criteria """ types = tuple((getattr(t, '_resolved', t) for t in types)) messages = list(self._data) + list(self._new_data) lookup = dict([(model.__name__, model) for model in type(actor).consumes]) if types: filtered = set(requested.__name__ for requested in types) messages = [ message for message in messages if message['type'] in filtered ] return (lookup[message['type']].create( json.loads(message['message']['data'])) for message in messages)
def test_answerfile_generate(tmpdir, answerfile): f = tmpdir.join('test_answerfile_generate') m = MockManager() a = AnswerStore(manager=m) store = a._storage a.load_and_translate_for_workflow(answerfile, MockWorkflow) assert store['scope1']['key1'] == 'scope1.key1' assert store['scope1']['key2'] == 'scope1.key2' assert store['scope2']['key1'] == 'scope2.key1' assert store['scope2']['key2'] == 'scope2.key2' assert store['boolscope']['key1'] is True assert store['boolscope']['key2'] is False a.answer('scope1', 'key1', 'generate.scope1.key1') a.answer('scope1', 'key2', 'generate.scope1.key2') a.answer('scope2', 'key1', 'generate.scope2.key1') a.answer('scope2', 'key2', 'generate.scope2.key2') a.answer('boolscope', 'key1', False) a.answer('boolscope', 'key2', True) a.generate(MockWorkflow.dialogs, str(f)) a = AnswerStore(manager=m) store2 = a._storage a.load_and_translate_for_workflow(str(f), MockWorkflow) assert store2['scope1']['key1'] == 'generate.scope1.key1' assert store2['scope1']['key2'] == 'generate.scope1.key2' assert store2['scope2']['key1'] == 'generate.scope2.key1' assert store2['scope2']['key2'] == 'generate.scope2.key2' assert store2['boolscope']['key1'] is False assert store2['boolscope']['key2'] is True
def test_answerstore_update(answerfile, answerfile_data, tmpdir): m = MockManager() a = AnswerStore(manager=m) store = a._storage a.load(answerfile) assert store['boolscope']['key1'] == 'True' assert store['boolscope']['key2'] == 'False' a.answer('boolscope', 'key1', False) a.answer('boolscope', 'key2', True) assert store['boolscope']['key1'] is False assert store['boolscope']['key2'] is True f = tmpdir.join('test_answerstore_update') f.write(answerfile_data) a.update(str(f)) a2 = AnswerStore(manager=m) store2 = a2._storage a2.load(str(f)) assert store2['boolscope']['key1'] == 'False' assert store2['boolscope']['key2'] == 'True' assert store2['scope1']['key1'] == 'scope1.key1' assert store2['scope1']['key2'] == 'scope1.key2' assert store2['scope2']['key1'] == 'scope2.key1' assert store2['scope2']['key2'] == 'scope2.key2'
def test_answerstore_init(monkeypatch): manager = MockManager() assert type(AnswerStore()._storage) is multiprocessing.managers.DictProxy monkeypatch.setattr(multiprocessing, 'Manager', MockManager()) assert type(AnswerStore(manager=manager)._storage) is dict assert type(AnswerStore()._storage) is dict