From 7c928e878be2e78621d637c549daed8936d38dc7 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Sat, 20 Aug 2016 06:57:13 -0400 Subject: [PATCH] moves Cli and Command class from network to netcmd module This restructure moves the Cli object to netcmd and includes a roll up of inor bugfix updates to CommandRunner * CommandRunner now only allows one instance of a command in the stack and raise an exception if a duplidate command is detected * CommandRunner now caches returns based on command and output * CommandRunner is not responsible for creating Command instances --- lib/ansible/module_utils/netcmd.py | 202 ++++++++++++++++++++--------- 1 file changed, 143 insertions(+), 59 deletions(-) diff --git a/lib/ansible/module_utils/netcmd.py b/lib/ansible/module_utils/netcmd.py index fc77af8d12d..b5a5637f31f 100644 --- a/lib/ansible/module_utils/netcmd.py +++ b/lib/ansible/module_utils/netcmd.py @@ -33,6 +33,149 @@ import shlex from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE +def to_list(val): + if isinstance(val, (list, tuple)): + return list(val) + elif val is not None: + return [val] + else: + return list() + +class FailedConditionsError(Exception): + def __init__(self, msg, failed_conditions): + super(FailedConditionsError, self).__init__(msg) + self.failed_conditions = failed_conditions + +class AddCommandError(Exception): + def __init__(self, msg, command): + super(AddCommandError, self).__init__(msg) + self.command = command + +class Cli(object): + + def __init__(self, connection): + self.connection = connection + self.default_output = connection.default_output or 'text' + self._commands = list() + + @property + def commands(self): + return [str(c) for c in self._commands] + + def __call__(self, commands, output=None): + objects = list() + for cmd in commands: + objects.append(self.to_command(cmd, output)) + return self.connection.run_commands(objects) + + def to_command(self, command, output=None, prompt=None, response=None): + output = output or self.default_output + if isinstance(command, Command): + return command + elif isinstance(command, dict): + output = cmd.get('output') or output + cmd = cmd['command'] + if isinstance(prompt, basestring): + prompt = re.compile(re.escape(prompt)) + return Command(command, output, prompt=prompt, response=response) + + def add_commands(self, commands, output=None, **kwargs): + for cmd in commands: + self._commands.append(self.to_command(cmd, output, **kwargs)) + + def run_commands(self): + responses = self.connection.run_commands(self._commands) + for resp, cmd in itertools.izip(responses, self._commands): + cmd.response = resp + + # wipe out the commands list to avoid issues if additional + # commands are executed later + self._commands = list() + + return responses + +class Command(object): + + def __init__(self, command, output=None, prompt=None, is_reboot=False, + response=None, delay=0): + + self.command = command + self.output = output + + self.prompt = prompt + self.response = response + + self.is_reboot = is_reboot + self.delay = delay + + def __str__(self): + return self.command + +class CommandRunner(object): + + def __init__(self, module): + self.module = module + + self.items = list() + self.conditionals = set() + + self.commands = list() + + self.retries = 10 + self.interval = 1 + + self.match = 'all' + + self._cache = dict() + self._default_output = module.connection.default_output + + + def add_command(self, command, output=None, prompt=None, response=None): + if command in [str(c) for c in self.commands]: + raise AddCommandError('duplicated command detected', command=command) + cmd = self.module.cli.to_command(command, output=output, prompt=prompt, + response=response) + self.commands.append(cmd) + + def get_command(self, command, output=None): + output = output or self._default_output + try: + cmdobj = self._cache[(command, output)] + return cmdobj.response + except KeyError: + for cmd in self.commands: + if str(cmd) == command and cmd.output == output: + self._cache[(command, output)] = cmd + return cmd.response + raise ValueError("command '%s' not found" % command) + + def get_responses(self): + return [cmd.response for cmd in self.commands] + + def add_conditional(self, condition): + self.conditionals.add(Conditional(condition)) + + def run(self): + while self.retries > 0: + self.module.cli.add_commands(self.commands) + responses = self.module.cli.run_commands() + + for item in list(self.conditionals): + if item(responses): + if self.match == 'any': + return item + self.conditionals.remove(item) + + if not self.conditionals: + break + + time.sleep(self.interval) + self.retries -= 1 + else: + failed_conditions = [item.raw for item in self.conditionals] + errmsg = 'One or more conditional statements have not be satisfied' + raise FailedConditionsError(errmsg, failed_conditions) + class Conditional(object): """Used in command modules to evaluate waitfor conditions """ @@ -154,62 +297,3 @@ class Conditional(object): match = re.search(value, self.value, re.M) return match is not None - -class FailedConditionsError(Exception): - - def __init__(self, msg, failed_conditions): - super(FailedConditionsError, self).__init__(msg) - self.failed_conditions = failed_conditions - -class CommandRunner(object): - - def __init__(self, module): - self.module = module - - self.items = list() - self.conditionals = set() - - self.retries = 10 - self.interval = 1 - - self._cache = dict() - - def add_command(self, command, output=None): - self.module.cli.add_commands(command, output=output) - - def get_command(self, command): - try: - cmdobj = self._cache[command] - return cmdobj.response - except KeyError: - for cmd in self.module.cli.commands: - if str(cmd) == command: - self._cache[command] = cmd - return cmd.response - raise ValueError("command '%s' not found" % command) - - - def add_conditional(self, condition): - self.conditionals.add(Conditional(condition)) - - def run_commands(self): - responses = self.module.cli.run_commands() - self.items = responses - - def run(self): - while self.retries > 0: - self.run_commands() - - for item in list(self.conditionals): - if item(self.items): - self.conditionals.remove(item) - - if not self.conditionals: - break - - time.sleep(self.interval) - self.retries -= 1 - else: - failed_conditions = [item.raw for item in self.conditionals] - raise FailedConditionsError('timeout waiting for value', failed_conditions) -