From 2146903c2e5fd5ade59e6badce61c8bdb17fd7ae Mon Sep 17 00:00:00 2001 From: Mike Shriver Date: Wed, 5 Jun 2019 10:10:46 -0400 Subject: [PATCH] Add SSHDummy for dev appliance --- cfme/utils/appliance/__init__.py | 31 +++++++++++++++++++++++-------- cfme/utils/ssh.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/cfme/utils/appliance/__init__.py b/cfme/utils/appliance/__init__.py index ab1df843a2..5837aa5383 100644 --- a/cfme/utils/appliance/__init__.py +++ b/cfme/utils/appliance/__init__.py @@ -54,6 +54,7 @@ from cfme.utils.path import data_path from cfme.utils.path import patches_path from cfme.utils.path import scripts_path +from cfme.utils.ssh import SSHDummy from cfme.utils.ssh import SSHTail from cfme.utils.version import get_stream from cfme.utils.version import Version @@ -337,6 +338,7 @@ class IPAppliance(object): 'ssh_port': 'ssh_port', 'project': 'project', 'version': 'version', + 'root_dir': 'root_dir', # manageIQ source dir for dev appliance } CONFIG_NONGLOBAL = {'hostname'} PROTOCOL_PORT_MAPPING = {'http': 80, 'https': 443} @@ -374,7 +376,7 @@ def from_json(cls, json_string): def __init__( self, hostname, ui_protocol='https', ui_port=None, browser_steal=False, project=None, container=None, openshift_creds=None, db_host=None, db_port=None, ssh_port=None, - is_dev=False, version=None, + is_dev=False, version=None, root_dir=None, ): if not isinstance(hostname, six.string_types): raise TypeError('Appliance\'s hostname must be a string!') @@ -1018,6 +1020,9 @@ def ssh_client(self): The credentials default to those found under ``ssh`` key in ``credentials.yaml``. """ + if self.is_dev: + logger.warning('Using a Dummy SSH object for dev appliance') + return SSHDummy() logger.debug('Waiting for SSH to %s to become connective.', self.hostname) self.wait_for_ssh() @@ -1793,7 +1798,10 @@ def wait_for_ssh(self, timeout=600): Args: timeout: Number of seconds to wait until timeout (default ``600``) """ - wait_for(func=lambda: self.is_ssh_running, + if self.is_dev: + return + wait_for(func=getattr, + func_args=[self, 'is_ssh_running'], message='appliance.is_ssh_running', delay=5, num_sec=timeout) @@ -1833,7 +1841,8 @@ def wait_for_embedded_ansible(self, timeout=1200): timeout *= 2 wait_for( - func=lambda: self.is_embedded_ansible_running, + func=getattr, + func_args=[self, 'is_embedded_ansible_running'], message='appliance.is_embedded_ansible_running', delay=60, num_sec=timeout @@ -1851,7 +1860,8 @@ def get_host_address(self): def wait_for_host_address(self): try: - wait_for(func=lambda: self.get_host_address, + wait_for(func=getattr, + func_args=[self, 'get_host_address'], fail_condition=None, delay=5, num_sec=120, @@ -1863,6 +1873,8 @@ def wait_for_host_address(self): @property def is_ssh_running(self): + if self.is_dev: + return True # shortcut without blocking logic if self.openshift_creds and 'hostname' in self.openshift_creds: hostname = self.openshift_creds['hostname'] else: @@ -1888,14 +1900,17 @@ def is_idle(self): True if appliance is idling for longer or equal to idle_time seconds. False if appliance is not idling for longer or equal to idle_time seconds. """ + # TODO Handle is_dev idle_time = 3600 - ssh_output = self.ssh_client.run_command('if [ $((`date "+%s"` - `date -d "$(egrep -v ' + ssh_output = self.ssh_client.run_command( + 'if [ $((`date "+%s"` - `date -d "$(egrep -v ' r'"(Processing by Api::ApiController\#index as JSON|Started GET "/api" for ' - '127.0.0.1|Completed 200 OK in)" /var/www/miq/vmdb/log/production.log | tail -1 |cut ' - '-d"[" -f3 | cut -d"]" -f1 | cut -d" " -f1)\" \"+%s\"`)) -lt {} ];' + '127.0.0.1|Completed 200 OK in)" {} | tail -1 |cut ' + r'-d"[" -f3 | cut -d"]" -f1 | cut -d" " -f1)\" \"+%s\"`)) -lt {} ];' 'then echo "False";' 'else echo "True";' - 'fi;'.format(idle_time)) + 'fi;'.format('/var/www/miq/vmdb/log/production.log', idle_time) + ) return True if 'True' in ssh_output else False @cached_property diff --git a/cfme/utils/ssh.py b/cfme/utils/ssh.py index 8e5e55233c..4d02db09ae 100644 --- a/cfme/utils/ssh.py +++ b/cfme/utils/ssh.py @@ -95,6 +95,12 @@ def failed(self): return self.rc != 0 +class SSHResultDummy(SSHResult): + """ Dummy result class to handle partial calls with extra kwargs for SSHClient methods""" + def __init__(self, command, rc, output, *args, **kwargs): + SSHResult.__init__(self, command, rc, output) + + _ssh_key_file = project_path.join('.generated_ssh_key') _ssh_pubkey_file = project_path.join('.generated_ssh_key.pub') @@ -782,6 +788,29 @@ def lines_as_list(self): return list(self) +class SSHDummy(object): + """Dummy object that support contextmanager and just returns a logger.info bound method + This is for dev appliances that don't support ssh + Framework/tests that use appliance.ssh_client will end up just logging what they would have run + """ + def __enter__(self, *args, **kwargs): + return self + + def __exit__(self, *args, **kwargs): + pass + + @staticmethod + def is_appliance_downstream(self): + return False + + def __getattr__(self, item): + if item in ['run_command', 'run_rails_command', 'run_rails_console', 'run_rake_command']: + logger.error('Cannot run ssh against a dev appliance, SSH function call skipped: %s', + item) + from functools import partial + return partial(SSHResultDummy, rc=1, output=None) + + def keygen(): """Generate temporary ssh keypair for appliance SSH auth