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
This commit is contained in:
Peter Sprygada 2016-08-20 06:57:13 -04:00
parent fa395bee7b
commit 7c928e878b

View file

@ -33,6 +33,149 @@ import shlex
from ansible.module_utils.basic import BOOLEANS_TRUE, BOOLEANS_FALSE 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): class Conditional(object):
"""Used in command modules to evaluate waitfor conditions """Used in command modules to evaluate waitfor conditions
""" """
@ -154,62 +297,3 @@ class Conditional(object):
match = re.search(value, self.value, re.M) match = re.search(value, self.value, re.M)
return match is not None 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)