Hello community, here is the log from the commit of package python3-ipa for openSUSE:Factory checked in at 2018-12-13 19:48:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-ipa (Old) and /work/SRC/openSUSE:Factory/.python3-ipa.new.28833 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-ipa" Thu Dec 13 19:48:53 2018 rev:11 rq:657526 version:2.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python3-ipa/python3-ipa.changes 2018-12-11 15:49:27.214099090 +0100 +++ /work/SRC/openSUSE:Factory/.python3-ipa.new.28833/python3-ipa.changes 2018-12-13 19:48:53.900665722 +0100 @@ -1,0 +2,13 @@ +Wed Dec 12 02:06:50 UTC 2018 - Sean Marlow <[email protected]> + +- Remove ec2_types.patch. + + Switched to boto3 from libcloud. Patch no longer needed. +- Update to v2.5.0 (2018-12-11) + + Switch EC2 provider back to boto3. + + Add note on installing SLES test suite. + + Add option allowing collect info about VM in cloud. + + With running instance don't cleanup. + + Systemd cleanup in distro. + + Process pytest errors in results. + +------------------------------------------------------------------- Old: ---- ec2_types.patch python3-ipa-2.4.0.tar.gz New: ---- python3-ipa-2.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-ipa.spec ++++++ --- /var/tmp/diff_new_pack.x06kja/_old 2018-12-13 19:48:54.280665231 +0100 +++ /var/tmp/diff_new_pack.x06kja/_new 2018-12-13 19:48:54.288665221 +0100 @@ -18,14 +18,13 @@ %bcond_without test Name: python3-ipa -Version: 2.4.0 +Version: 2.5.0 Release: 0 Summary: Command line and API for testing custom images License: GPL-3.0-or-later Group: Development/Languages/Python URL: https://github.com/SUSE/ipa Source: https://files.pythonhosted.org/packages/source/p/python3-ipa/%{name}-%{version}.tar.gz -Patch1: ec2_types.patch BuildRequires: python3-devel BuildRequires: python3-setuptools Requires: python3-PyYAML @@ -34,6 +33,7 @@ Requires: python3-azure-mgmt-compute Requires: python3-azure-mgmt-network Requires: python3-azure-mgmt-resource +Requires: python3-boto3 Requires: python3-certifi Requires: python3-click Requires: python3-cryptography @@ -49,6 +49,7 @@ BuildRequires: python3-azure-mgmt-compute BuildRequires: python3-azure-mgmt-network BuildRequires: python3-azure-mgmt-resource +BuildRequires: python3-boto3 BuildRequires: python3-certifi BuildRequires: python3-click BuildRequires: python3-coverage @@ -75,7 +76,6 @@ %prep %setup -q -%patch1 -p1 %build python3 setup.py build ++++++ python3-ipa-2.4.0.tar.gz -> python3-ipa-2.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/CHANGES.md new/python3-ipa-2.5.0/CHANGES.md --- old/python3-ipa-2.4.0/CHANGES.md 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/CHANGES.md 2018-12-12 02:51:13.000000000 +0100 @@ -1,3 +1,19 @@ +v2.5.0 (2018-12-11) +=================== + +- Add note on installing SLES test suite. + [\#148](https://github.com/SUSE-Enceladus/ipa/pull/148) +- Add option allowing collect info about VM in cloud. + [\#149](https://github.com/SUSE-Enceladus/ipa/pull/149) +- With running instance don't cleanup. + [\#152](https://github.com/SUSE-Enceladus/ipa/pull/152) +- Systemd cleanup in distro. + [\#153](https://github.com/SUSE-Enceladus/ipa/pull/153) +- Process pytest errors in results. + [\#154](https://github.com/SUSE-Enceladus/ipa/pull/154) +- Switch EC2 provider back to boto3. + [\#155](https://github.com/SUSE-Enceladus/ipa/pull/155) + v2.4.0 (2018-12-06) =================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/PKG-INFO new/python3-ipa-2.5.0/PKG-INFO --- old/python3-ipa-2.4.0/PKG-INFO 2018-12-07 01:08:23.000000000 +0100 +++ new/python3-ipa-2.5.0/PKG-INFO 2018-12-12 02:52:08.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: python3-ipa -Version: 2.4.0 +Version: 2.5.0 Summary: Package for automated testing of cloud images. Home-page: https://github.com/SUSE-Enceladus/ipa Author: SUSE @@ -151,6 +151,6 @@ Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.4 Description-Content-Type: text/markdown +Provides-Extra: dev Provides-Extra: test Provides-Extra: tox -Provides-Extra: dev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/__init__.py new/python3-ipa-2.5.0/ipa/__init__.py --- old/python3-ipa-2.4.0/ipa/__init__.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/__init__.py 2018-12-12 02:51:14.000000000 +0100 @@ -22,4 +22,4 @@ __author__ = """SUSE""" __email__ = '[email protected]' -__version__ = '2.4.0' +__version__ = '2.5.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_azure.py new/python3-ipa-2.5.0/ipa/ipa_azure.py --- old/python3-ipa-2.4.0/ipa/ipa_azure.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_azure.py 2018-12-12 02:51:14.000000000 +0100 @@ -67,7 +67,8 @@ test_files=None, timeout=None, vnet_name=None, - vnet_resource_group=None): + vnet_resource_group=None, + collect_vm_info=None): """Initialize Azure Provider class.""" super(AzureProvider, self).__init__('azure', cleanup, @@ -87,7 +88,8 @@ running_instance_id, test_dirs, test_files, - timeout) + timeout, + collect_vm_info) if subnet_id and not (vnet_name and vnet_resource_group): raise AzureProviderException( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_controller.py new/python3-ipa-2.5.0/ipa/ipa_controller.py --- old/python3-ipa-2.4.0/ipa/ipa_controller.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_controller.py 2018-12-12 02:51:14.000000000 +0100 @@ -65,7 +65,8 @@ tests=None, timeout=None, vnet_name=None, - vnet_resource_group=None): + vnet_resource_group=None, + collect_vm_info=None): """Creates a cloud provider instance and initiates testing.""" provider_name = provider_name.lower() if provider_name == 'azure': @@ -112,7 +113,8 @@ test_files=tests, timeout=timeout, vnet_name=vnet_name, - vnet_resource_group=vnet_resource_group + vnet_resource_group=vnet_resource_group, + collect_vm_info=collect_vm_info ) return provider.test_image() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_distro.py new/python3-ipa-2.5.0/ipa/ipa_distro.py --- old/python3-ipa-2.4.0/ipa/ipa_distro.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_distro.py 2018-12-12 02:51:14.000000000 +0100 @@ -35,7 +35,19 @@ def _set_init_system(self, client): """Determine the init system of distribution.""" - raise NotImplementedError(NOT_IMPLEMENTED) + if not self.init_system: + try: + out = ipa_utils.execute_ssh_command( + client, + 'ps -p 1 -o comm=' + ) + except Exception as e: + raise IpaDistroException( + 'An error occurred while retrieving' + ' the distro init system: %s' % e + ) + if out: + self.init_system = out.strip() def get_install_cmd(self): """Return install package command for distribution.""" @@ -61,6 +73,35 @@ """Return command to update instance.""" raise NotImplementedError(NOT_IMPLEMENTED) + def get_vm_info(self, client): + """Return vm info.""" + out = '' + self._set_init_system(client) + + if self.init_system == 'systemd': + try: + out += 'systemd-analyze:\n\n' + out += ipa_utils.execute_ssh_command( + client, + 'systemd-analyze' + ) + + out += 'systemd-analyze blame:\n\n' + out += ipa_utils.execute_ssh_command( + client, + 'systemd-analyze blame' + ) + + out += 'journalctl -b:\n\n' + out += ipa_utils.execute_ssh_command( + client, + 'sudo journalctl -b' + ) + except Exception as error: + out = 'Failed to collect VM info: {0}.'.format(error) + + return out + def install_package(self, client, package): """Install package on instance.""" install_cmd = "{sudo} '{install} {package}'".format( @@ -87,8 +128,7 @@ def reboot(self, client): """Execute reboot command on instance.""" - if not self.init_system: - self._set_init_system(client) + self._set_init_system(client) reboot_cmd = "{sudo} '{stop_ssh};{reboot}'".format( sudo=self.get_sudo_exec_wrapper(), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_ec2.py new/python3-ipa-2.5.0/ipa/ipa_ec2.py --- old/python3-ipa-2.4.0/ipa/ipa_ec2.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_ec2.py 2018-12-12 02:51:14.000000000 +0100 @@ -2,7 +2,7 @@ """Provider module for testing AWS EC2 images.""" -# Copyright (c) 2017 SUSE LLC +# Copyright (c) 2018 SUSE LLC # # This file is part of ipa. Ipa provides an api and command line # utilities for testing images in the Public Cloud. @@ -20,6 +20,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import boto3 + from ipa import ipa_utils from ipa.ipa_constants import ( BASH_SSH_SCRIPT, @@ -28,14 +30,10 @@ EC2_DEFAULT_USER ) from ipa.ipa_exceptions import IpaException, EC2ProviderException -from ipa.ipa_libcloud import LibcloudProvider - -from libcloud.common.exceptions import BaseHTTPError -from libcloud.compute.types import Provider -from libcloud.compute.providers import get_driver +from ipa.ipa_provider import IpaProvider -class EC2Provider(LibcloudProvider): +class EC2Provider(IpaProvider): """Provider class for testing AWS EC2 images.""" def __init__(self, @@ -69,8 +67,8 @@ test_files=None, timeout=None, vnet_name=None, # Not used in EC2 - vnet_resource_group=None # Not used in EC2 - ): + vnet_resource_group=None, # Not used in EC2 + collect_vm_info=None): """Initialize EC2 provider class.""" super(EC2Provider, self).__init__('ec2', cleanup, @@ -90,7 +88,8 @@ running_instance_id, test_dirs, test_files, - timeout) + timeout, + collect_vm_info) self.account_name = account_name if not self.account_name: @@ -127,7 +126,9 @@ ) self.security_group_id = ( security_group_id or - self._get_value('security_group_id') + self._get_value( + security_group_id, config_key='security_group_id' + ) ) self.ssh_key_name = ( ssh_key_name or @@ -152,26 +153,23 @@ 'SSH private key file is required to connect to instance.' ) - self.compute_driver = self._get_driver() - - def _get_driver(self): - """Get authenticated EC2 driver.""" - if not self.access_key_id: - raise EC2ProviderException( - 'Access key id is required to authenticate EC2 driver.' + def _connect(self): + """Connect to ec2 resource.""" + resource = None + try: + resource = boto3.resource( + 'ec2', + aws_access_key_id=self.access_key_id, + aws_secret_access_key=self.secret_access_key, + region_name=self.region ) - - if not self.secret_access_key: + # boto3 resource is lazy so attempt method to test connection + resource.meta.client.describe_account_attributes() + except Exception: raise EC2ProviderException( - 'Secret access key is required to authenticate EC2 driver.' + 'Could not connect to region: %s' % self.region ) - - ComputeEngine = get_driver(Provider.EC2) - return ComputeEngine( - self.access_key_id, - self.secret_access_key, - region=self.region - ) + return resource def _get_from_ec2_config(self, entry): """Get config entry from ec2utils config file.""" @@ -185,28 +183,13 @@ else: return None - def _get_image(self): - """Retrieve NodeImage given the image id.""" - try: - image = self.compute_driver.list_images( - ex_image_ids=[self.image_id] - )[0] - except (IndexError, BaseHTTPError): - raise EC2ProviderException( - 'Image with ID: {image_id} not found.'.format( - image_id=self.image_id - ) - ) - - return image - def _get_instance(self): """Retrieve instance matching instance_id.""" + resource = self._connect() + try: - instance = self.compute_driver.list_nodes( - ex_node_ids=[self.running_instance_id] - )[0] - except (IndexError, BaseHTTPError): + instance = resource.Instance(self.running_instance_id) + except Exception: raise EC2ProviderException( 'Instance with ID: {instance_id} not found.'.format( instance_id=self.running_instance_id @@ -214,36 +197,26 @@ ) return instance - def _get_instance_size(self): - """Retrieve NodeSize given the instance type.""" - instance_type = self.instance_type or EC2_DEFAULT_TYPE - - try: - sizes = self.compute_driver.list_sizes() - size = [size for size in sizes if size.id == instance_type][0] - except IndexError: - raise EC2ProviderException( - 'Instance type: {instance_type} not found.'.format( - instance_type=instance_type - ) - ) - - return size + def _get_instance_state(self): + """ + Attempt to retrieve the state of the instance. + Raises: + EC2ProviderException: If the instance cannot be found. + """ + instance = self._get_instance() + state = None - def _get_subnet(self, subnet_id): - subnet = None try: - subnet = self.compute_driver.ex_list_subnets( - subnet_ids=[subnet_id] - )[0] + state = instance.state['Name'] except Exception: raise EC2ProviderException( - 'EC2 subnet: {subnet_id} not found.'.format( - subnet_id=subnet_id + 'Instance with id: {instance_id}, ' + 'cannot be found.'.format( + instance_id=self.running_instance_id ) ) - return subnet + return state def _get_user_data(self): """ @@ -258,34 +231,96 @@ script = BASH_SSH_SCRIPT.format(user=self.ssh_user, key=key) return script + def _is_instance_running(self): + """ + Return True if instance is in running state. + """ + return self._get_instance_state() == 'running' + def _launch_instance(self): """Launch an instance of the given image.""" + resource = self._connect() + + instance_name = ipa_utils.generate_instance_name('ec2-ipa-test') kwargs = { - 'name': ipa_utils.generate_instance_name('ec2-ipa-test'), - 'size': self._get_instance_size(), - 'image': self._get_image() + 'InstanceType': self.instance_type or EC2_DEFAULT_TYPE, + 'ImageId': self.image_id, + 'MaxCount': 1, + 'MinCount': 1, + 'TagSpecifications': [ + { + 'ResourceType': 'instance', + 'Tags': [ + { + 'Key': 'Name', + 'Value': instance_name + } + ] + } + ] } if self.ssh_key_name: - kwargs['ex_keyname'] = self.ssh_key_name + kwargs['KeyName'] = self.ssh_key_name else: - kwargs['ex_userdata'] = self._get_user_data() + kwargs['UserData'] = self._get_user_data() if self.subnet_id: - kwargs['ex_subnet'] = self._get_subnet(self.subnet_id) + kwargs['SubnetId'] = self.subnet_id if self.security_group_id: - kwargs['ex_security_group_ids'] = [self.security_group_id] + kwargs['SecurityGroupIds'] = [self.security_group_id] - instance = self.compute_driver.create_node(**kwargs) + try: + instances = resource.create_instances(**kwargs) + except Exception as error: + raise EC2ProviderException( + 'Unable to create instance: {0}.'.format(error) + ) - self.compute_driver.wait_until_running( - [instance], - timeout=self.timeout - ) - self.running_instance_id = instance.id + instances[0].wait_until_running() + self.running_instance_id = instances[0].instance_id def _set_image_id(self): """If existing image used get image id.""" instance = self._get_instance() - self.image_id = instance.extra['image_id'] + self.image_id = instance.image_id + + def _set_instance_ip(self): + """ + Retrieve instance ip and cache it. + + Current preference is for public ipv4, ipv6 and private. + """ + instance = self._get_instance() + + # ipv6 + try: + ipv6 = instance.network_interfaces[0].ipv6_addresses[0] + except IndexError: + ipv6 = None + + self.instance_ip = instance.public_ip_address or \ + ipv6 or instance.private_ip_address + + if not self.instance_ip: + raise EC2ProviderException( + 'IP address for instance cannot be found.' + ) + + def _start_instance(self): + """Start the instance.""" + instance = self._get_instance() + instance.start() + instance.wait_until_running() + + def _stop_instance(self): + """Stop the instance.""" + instance = self._get_instance() + instance.stop() + instance.wait_until_stopped() + + def _terminate_instance(self): + """Terminate the instance.""" + instance = self._get_instance() + instance.terminate() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_gce.py new/python3-ipa-2.5.0/ipa/ipa_gce.py --- old/python3-ipa-2.4.0/ipa/ipa_gce.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_gce.py 2018-12-12 02:51:14.000000000 +0100 @@ -70,8 +70,8 @@ test_files=None, timeout=None, vnet_name=None, # Not used in GCE - vnet_resource_group=None # Not used in GCE - ): + vnet_resource_group=None, # Not used in GCE + collect_vm_info=None): super(GCEProvider, self).__init__('gce', cleanup, config, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_provider.py new/python3-ipa-2.5.0/ipa/ipa_provider.py --- old/python3-ipa-2.4.0/ipa/ipa_provider.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_provider.py 2018-12-12 02:51:14.000000000 +0100 @@ -78,7 +78,8 @@ running_instance_id=None, test_dirs=None, test_files=None, - timeout=None): + timeout=None, + collect_vm_info=None): """Initialize base provider class.""" super(IpaProvider, self).__init__() log_level = log_level or logging.INFO @@ -105,6 +106,11 @@ self.host_key_fingerprint = None self.instance_ip = None self.provider = provider + self.collect_vm_info = self._get_value( + collect_vm_info, + config_key='collect_vm_info', + default=False + ) self.cleanup = self._get_value(cleanup) self.distro_name = self._get_value(distro_name) @@ -293,7 +299,7 @@ self.test_files ) - def _process_sync_test_results(self, duration, test_name, success=0): + def _process_test_results(self, duration, test_name, success=0): """Create result dict for sync test and merge with overall results.""" status = 'passed' if success == 0 else 'failed' result = { @@ -336,7 +342,14 @@ cmds = shlex.split(args) plugin = Report() result = pytest.main(cmds, plugins=[plugin]) - self._merge_results(plugin.report) + + # If pytest has an error there will be no report but + # we still want to process the error as a failure. + # https://docs.pytest.org/en/latest/usage.html#possible-exit-codes + if result in (2, 3, 4): + self._process_test_results(0, 'pytest_error', 1) + else: + self._merge_results(plugin.report) return result @@ -421,6 +434,19 @@ """Terminate the instance.""" raise NotImplementedError(NOT_IMPLEMENTED) + def _collect_vm_info(self): + """ + Gather basic info about VM + """ + self.logger.info('Collecting basic info about VM') + client = self._get_ssh_client() + + out = self.distro.get_vm_info(client) + + with open(self.log_file, 'a') as log_file: + log_file.write('\n') + log_file.write(out) + def _update_history(self): """Save the current test information to history json.""" ipa_utils.update_history_log( @@ -607,6 +633,11 @@ # Use existing instance self._start_instance_if_stopped() self._set_image_id() + + # With a running instance default to no cleanup + # if a value has not been provided. + if self.cleanup is None: + self.cleanup = False else: # Launch new instance self.logger.info('Launching new instance') @@ -671,7 +702,7 @@ break finally: duration = time.time() - start - self._process_sync_test_results( + self._process_test_results( duration, 'test_hard_reboot', result ) status = status or result @@ -703,7 +734,7 @@ break finally: duration = time.time() - start - self._process_sync_test_results( + self._process_test_results( duration, 'test_soft_reboot', result ) status = status or result @@ -724,7 +755,7 @@ log_file.write(out) finally: duration = time.time() - start - self._process_sync_test_results( + self._process_test_results( duration, 'test_update', result ) status = status or result @@ -745,6 +776,10 @@ if status and self.early_exit: break + # flag set to collect VM info + if self.collect_vm_info: + self._collect_vm_info() + # If tests pass and cleanup flag is none, or # cleanup flag is true, terminate instance. if status == 0 and self.cleanup is None or self.cleanup: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_sles.py new/python3-ipa-2.5.0/ipa/ipa_sles.py --- old/python3-ipa-2.4.0/ipa/ipa_sles.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_sles.py 2018-12-12 02:51:14.000000000 +0100 @@ -20,7 +20,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -from ipa import ipa_utils from ipa.ipa_distro import Distro from ipa.ipa_exceptions import IpaSLESException @@ -28,21 +27,6 @@ class SLES(Distro): """SLES distro class.""" - def _set_init_system(self, client): - """Determine the init system of distribution.""" - try: - out = ipa_utils.execute_ssh_command( - client, - 'ps -p 1 -o comm=' - ) - except Exception as e: - raise IpaSLESException( - 'An error occurred while retrieving' - ' the distro init system: %s' % e - ) - if out: - self.init_system = out.strip() - def get_install_cmd(self): """Return install package command for SLES.""" return 'zypper -n --no-gpg-checks in -y' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/ipa_ssh.py new/python3-ipa-2.5.0/ipa/ipa_ssh.py --- old/python3-ipa-2.4.0/ipa/ipa_ssh.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/ipa_ssh.py 2018-12-12 02:51:14.000000000 +0100 @@ -59,8 +59,8 @@ test_files=None, timeout=None, vnet_name=None, # Not used in SSH - vnet_resource_group=None # Not used in SSH - ): + vnet_resource_group=None, # Not used in SSH + collect_vm_info=None): """Initialize Azure Provider class.""" super(SSHProvider, self).__init__('ssh', cleanup, @@ -80,7 +80,8 @@ running_instance_id, test_dirs, test_files, - timeout) + timeout, + collect_vm_info) # Cannot cleanup SSH instance self.cleanup = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/ipa/scripts/cli.py new/python3-ipa-2.5.0/ipa/scripts/cli.py --- old/python3-ipa-2.4.0/ipa/scripts/cli.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/ipa/scripts/cli.py 2018-12-12 02:51:14.000000000 +0100 @@ -238,6 +238,11 @@ '--vnet-resource-group', help='Azure resource group name where virtual network is found.' ) [email protected]( + '--collect-vm-info', + is_flag=True, + help='Controls whether general info about VM in cloud should be collected' +) @click.argument( 'provider', type=click.Choice(SUPPORTED_PROVIDERS) @@ -275,6 +280,7 @@ timeout, vnet_name, vnet_resource_group, + collect_vm_info, provider, tests): """Test image in the given framework using the supplied test files.""" @@ -312,7 +318,8 @@ tests, timeout, vnet_name, - vnet_resource_group + vnet_resource_group, + collect_vm_info ) echo_results(results, no_color) sys.exit(status) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/package/python3-ipa.spec new/python3-ipa-2.5.0/package/python3-ipa.spec --- old/python3-ipa-2.4.0/package/python3-ipa.spec 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/package/python3-ipa.spec 2018-12-12 02:51:14.000000000 +0100 @@ -18,7 +18,7 @@ %bcond_without test Name: python3-ipa -Version: 2.4.0 +Version: 2.5.0 Release: 0 Summary: Command line and API for testing custom images License: GPL-3.0-or-later @@ -28,6 +28,7 @@ BuildRequires: python3-devel BuildRequires: python3-setuptools Requires: python3-PyYAML +Requires: python3-boto3 Requires: python3-apache-libcloud Requires: python3-azure-common Requires: python3-azure-mgmt-compute @@ -43,6 +44,7 @@ BuildArch: noarch %if %{with test} BuildRequires: python3-PyYAML +BuildRequires: python3-boto3 BuildRequires: python3-apache-libcloud BuildRequires: python3-azure-common BuildRequires: python3-azure-mgmt-compute diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/python3_ipa.egg-info/PKG-INFO new/python3-ipa-2.5.0/python3_ipa.egg-info/PKG-INFO --- old/python3-ipa-2.4.0/python3_ipa.egg-info/PKG-INFO 2018-12-07 01:08:23.000000000 +0100 +++ new/python3-ipa-2.5.0/python3_ipa.egg-info/PKG-INFO 2018-12-12 02:52:08.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: python3-ipa -Version: 2.4.0 +Version: 2.5.0 Summary: Package for automated testing of cloud images. Home-page: https://github.com/SUSE-Enceladus/ipa Author: SUSE @@ -151,6 +151,6 @@ Classifier: Programming Language :: Python :: 3.7 Requires-Python: >=3.4 Description-Content-Type: text/markdown +Provides-Extra: dev Provides-Extra: test Provides-Extra: tox -Provides-Extra: dev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/python3_ipa.egg-info/requires.txt new/python3-ipa-2.5.0/python3_ipa.egg-info/requires.txt --- old/python3-ipa-2.4.0/python3_ipa.egg-info/requires.txt 2018-12-07 01:08:23.000000000 +0100 +++ new/python3-ipa-2.5.0/python3_ipa.egg-info/requires.txt 2018-12-12 02:52:08.000000000 +0100 @@ -1,3 +1,4 @@ +boto3 apache-libcloud azure-common azure-mgmt-compute diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/requirements.txt new/python3-ipa-2.5.0/requirements.txt --- old/python3-ipa-2.4.0/requirements.txt 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/requirements.txt 2018-12-12 02:51:14.000000000 +0100 @@ -1,3 +1,4 @@ +boto3 apache-libcloud azure-common azure-mgmt-compute diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/setup.cfg new/python3-ipa-2.5.0/setup.cfg --- old/python3-ipa-2.4.0/setup.cfg 2018-12-07 01:08:23.000000000 +0100 +++ new/python3-ipa-2.5.0/setup.cfg 2018-12-12 02:52:08.000000000 +0100 @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.4.0 +current_version = 2.5.0 commit = True tag = False diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/setup.py new/python3-ipa-2.5.0/setup.py --- old/python3-ipa-2.4.0/setup.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/setup.py 2018-12-12 02:51:14.000000000 +0100 @@ -43,7 +43,7 @@ setup( name='python3-ipa', - version='2.4.0', + version='2.5.0', description="Package for automated testing of cloud images.", long_description=readme, long_description_content_type="text/markdown", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/tests/test_ipa_distro.py new/python3-ipa-2.5.0/tests/test_ipa_distro.py --- old/python3-ipa-2.4.0/tests/test_ipa_distro.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/tests/test_ipa_distro.py 2018-12-12 02:51:14.000000000 +0100 @@ -22,8 +22,9 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from ipa.ipa_distro import Distro +from ipa.ipa_exceptions import IpaDistroException -from unittest.mock import MagicMock +from unittest.mock import call, MagicMock, patch import pytest @@ -49,16 +50,20 @@ ) -def test_distro_set_init_system(): - """Test set init system raises not implemented exception.""" - distro = Distro() +def test_distro_set_init_system_exception(): + """Test distro set init system method exception.""" client = MagicMock() + distro = Distro() - pytest.raises( - NotImplementedError, - getattr(distro, '_set_init_system'), - client - ) + with patch('ipa.ipa_utils.execute_ssh_command', MagicMock( + side_effect=Exception('ERROR!'))) as mocked: + pytest.raises( + IpaDistroException, + distro._set_init_system, + client + ) + + mocked.assert_called_once_with(client, 'ps -p 1 -o comm=') def test_distro_get_commands(): @@ -66,3 +71,20 @@ distro = Distro() assert distro.get_reboot_cmd() == 'shutdown -r now' assert distro.get_sudo_exec_wrapper() == 'sudo sh -c' + + +def test_distro_get_vm_info(): + """Test distro get vm info method.""" + client = MagicMock() + distro = Distro() + distro.init_system = 'systemd' + + with patch('ipa.ipa_utils.execute_ssh_command', + MagicMock(return_value='')) as mocked: + distro.get_vm_info(client) + + mocked.assert_has_calls([ + call(client, 'systemd-analyze'), + call(client, 'systemd-analyze blame'), + call(client, 'sudo journalctl -b') + ]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/tests/test_ipa_ec2.py new/python3-ipa-2.5.0/tests/test_ipa_ec2.py --- old/python3-ipa-2.4.0/tests/test_ipa_ec2.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/tests/test_ipa_ec2.py 2018-12-12 02:51:14.000000000 +0100 @@ -3,7 +3,7 @@ """Ipa ec2 provider unit tests.""" -# Copyright (c) 2017 SUSE LLC +# Copyright (c) 2018 SUSE LLC # # This file is part of ipa. Ipa provides an api and command line # utilities for testing images in the Public Cloud. @@ -21,6 +21,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import boto3 import pytest from ipa.ipa_ec2 import EC2Provider @@ -41,7 +42,8 @@ 'image_id': 'fakeimage', 'no_default_test_dirs': True, 'provider_config': 'tests/ec2/.ec2utils.conf', - 'test_files': ['test_image'] + 'test_files': ['test_image'], + 'ssh_key_name': 'test-key' } def test_ec2_exception_required_args(self): @@ -66,71 +68,89 @@ assert str(error.value) == msg self.kwargs['provider_config'] = 'tests/ec2/.ec2utils.conf' - @patch('libcloud.compute.drivers.ec2.EC2NodeDriver') - def test_gce_get_driver(self, mock_node_driver): - """Test ec2 get driver method.""" - driver = MagicMock() - mock_node_driver.return_value = driver + @patch.object(boto3, 'resource') + def test_ec2_bad_connection(self, mock_boto3): + """Test an exception is raised if boto3 unable to connect.""" + mock_boto3.side_effect = Exception('ERROR!') provider = EC2Provider(**self.kwargs) - assert driver == provider.compute_driver + msg = 'Could not connect to region: %s' % provider.region - @patch.object(EC2Provider, '_get_driver') - def test_ec2_get_instance(self, mock_get_driver): + # Test ssh private key file required + with pytest.raises(EC2ProviderException) as error: + provider._connect() + + assert str(error.value) == msg + assert mock_boto3.call_count > 0 + + @patch.object(EC2Provider, '_connect') + def test_ec2_get_instance(self, mock_connect): """Test get instance method.""" instance = MagicMock() - driver = MagicMock() - mock_get_driver.return_value = driver - driver.list_nodes.return_value = [instance] + resource = MagicMock() + resource.Instance.return_value = instance + mock_connect.return_value = resource provider = EC2Provider(**self.kwargs) val = provider._get_instance() assert val == instance - assert driver.list_nodes.call_count == 1 + assert mock_connect.call_count == 1 - @patch.object(EC2Provider, '_get_driver') - def test_ec2_get_instance_not_found(self, mock_get_driver): - """Test get instance method.""" - driver = MagicMock() - mock_get_driver.return_value = driver - driver.list_nodes.return_value = [] + @patch.object(EC2Provider, '_connect') + def test_ec2_get_instance_error(self, mock_connect): + """Test get instance method error.""" + resource = MagicMock() + resource.Instance.side_effect = Exception('Error!') + mock_connect.return_value = resource - self.kwargs['running_instance_id'] = 'i-123456789' provider = EC2Provider(**self.kwargs) + provider.running_instance_id = 'i-123456789' with pytest.raises(EC2ProviderException) as error: provider._get_instance() - assert str(error.value) == 'Instance with ID: i-123456789 not found.' - assert driver.list_nodes.call_count == 1 + msg = 'Instance with ID: i-123456789 not found.' + assert str(error.value) == msg - @patch.object(EC2Provider, '_get_driver') - def test_ec2_get_subnet(self, mock_get_driver): - """Test EC2 get subnetwork method.""" - subnetwork = MagicMock() - driver = MagicMock() - driver.ex_list_subnets.return_value = [subnetwork] - mock_get_driver.return_value = driver + @patch.object(EC2Provider, '_get_instance') + def test_ec2_get_instance_state(self, mock_get_instance): + """Test an exception is raised if boto3 unable to connect.""" + instance = MagicMock() + instance.state = {'Name': 'ReadyRole'} + mock_get_instance.return_value = instance provider = EC2Provider(**self.kwargs) - result = provider._get_subnet('test-subnet') + val = provider._get_instance_state() + assert val == 'ReadyRole' + assert mock_get_instance.call_count == 1 + + instance.state = {} + mock_get_instance.reset_mock() + msg = 'Instance with id: %s, cannot be found.' \ + % provider.running_instance_id + + # Test exception raised if instance state not found + with pytest.raises(EC2ProviderException) as error: + provider._get_instance_state() - assert result == subnetwork + assert str(error.value) == msg + assert mock_get_instance.call_count == 1 - @patch.object(EC2Provider, '_get_driver') - def test_ec2_get_subnet_exception(self, mock_get_driver): - """Test EC2 get subnetwork method.""" - driver = MagicMock() - driver.ex_list_subnets.side_effect = Exception('Cannot find subnet!') - mock_get_driver.return_value = driver + @patch.object(EC2Provider, '_get_instance_state') + def test_ec2_is_instance_running(self, mock_get_instance_state): + """Test ec2 provider is instance runnning method.""" + mock_get_instance_state.return_value = 'running' provider = EC2Provider(**self.kwargs) + assert provider._is_instance_running() + assert mock_get_instance_state.call_count == 1 - msg = 'EC2 subnet: test-subnet not found.' - with pytest.raises(EC2ProviderException) as error: - provider._get_subnet('test-subnet') + mock_get_instance_state.return_value = 'stopped' + mock_get_instance_state.reset_mock() - assert msg == str(error.value) + provider = EC2Provider(**self.kwargs) + assert not provider._is_instance_running() + assert mock_get_instance_state.call_count == 1 @patch('ipa.ipa_ec2.ipa_utils.generate_public_ssh_key') def test_ec2_get_user_data(self, mock_generate_ssh_key): @@ -143,87 +163,111 @@ assert result == '#!/bin/bash\n' \ 'echo testkey12345 >> /home/ec2-user/.ssh/authorized_keys\n' - @patch.object(EC2Provider, '_get_user_data') - @patch.object(EC2Provider, '_get_subnet') - @patch.object(EC2Provider, '_get_driver') - def test_ec2_launch_instance( - self, mock_get_driver, mock_get_subnet, mock_get_user_data - ): + @patch.object(EC2Provider, '_connect') + def test_ec2_launch_instance(self, mock_connect): """Test ec2 provider launch instance method.""" - driver = MagicMock() + instance = MagicMock() + instance.instance_id = 'i-123456789' + instances = [instance] - size = MagicMock() - size.id = 't2.micro' - driver.list_sizes.return_value = [size] + resource = MagicMock() + resource.create_instances.return_value = instances - image = MagicMock() - image.id = 'fakeimage' - driver.list_images.return_value = [image] + mock_connect.return_value = resource - instance = MagicMock() - instance.id = 'i-123456789' - driver.create_node.return_value = instance + provider = EC2Provider(**self.kwargs) + provider.subnet_id = 'subnet-123456789' + provider.security_group_id = 'sg-123456789' + provider._launch_instance() - mock_get_driver.return_value = driver + assert instance.instance_id == provider.running_instance_id + assert resource.create_instances.call_count == 1 - subnet = MagicMock() - mock_get_subnet.return_value = subnet + @patch.object(EC2Provider, '_get_instance') + def test_ec2_set_image_id(self, mock_get_instance): + """Test ec2 provider set image id method.""" + instance = MagicMock() + instance.image_id = 'ami-123456' + mock_get_instance.return_value = instance provider = EC2Provider(**self.kwargs) - provider.subnet_id = 'test-subnet' - provider._launch_instance() + provider._set_image_id() - assert instance.id == provider.running_instance_id - assert driver.list_sizes.call_count == 1 - assert driver.list_images.call_count == 1 - assert driver.create_node.call_count == 1 + assert provider.image_id == instance.image_id + assert mock_get_instance.call_count == 1 - @patch.object(EC2Provider, '_get_driver') - def test_ec2_launch_instance_no_size(self, mock_get_driver): - """Test exception raised if instance type not found.""" - driver = MagicMock() - driver.list_sizes.return_value = [] + @patch.object(EC2Provider, '_get_instance') + def test_ec2_set_instance_ip(self, mock_get_instance): + """Test ec2 provider set image id method.""" + instance = MagicMock() + instance.public_ip_address = None + instance.private_ip_address = None + instance.network_interfaces = [] + mock_get_instance.return_value = instance - mock_get_driver.return_value = driver provider = EC2Provider(**self.kwargs) + msg = 'IP address for instance cannot be found.' with pytest.raises(EC2ProviderException) as error: - provider._launch_instance() + provider._set_instance_ip() + + assert str(error.value) == msg + assert mock_get_instance.call_count == 1 + mock_get_instance.reset_mock() - assert str(error.value) == 'Instance type: t2.micro not found.' - assert driver.list_sizes.call_count == 1 + instance.private_ip_address = '127.0.0.1' - @patch.object(EC2Provider, '_get_driver') - def test_ec2_launch_instance_no_image(self, mock_get_driver): - """Test exception raised if image not found.""" - driver = MagicMock() + provider._set_instance_ip() + assert provider.instance_ip == '127.0.0.1' + assert mock_get_instance.call_count == 1 + mock_get_instance.reset_mock() - size = MagicMock() - size.id = 't2.micro' - driver.list_sizes.return_value = [size] - driver.list_images.return_value = [] + network_interface = MagicMock() + network_interface.ipv6_addresses = ['127.0.0.2'] + instance.network_interfaces = [network_interface] - mock_get_driver.return_value = driver - provider = EC2Provider(**self.kwargs) + provider._set_instance_ip() + assert provider.instance_ip == '127.0.0.2' + assert mock_get_instance.call_count == 1 + mock_get_instance.reset_mock() - with pytest.raises(EC2ProviderException) as error: - provider._launch_instance() + instance.public_ip_address = '127.0.0.3' - assert str(error.value) == 'Image with ID: fakeimage not found.' - assert driver.list_sizes.call_count == 1 - assert driver.list_images.call_count == 1 + provider._set_instance_ip() + assert provider.instance_ip == '127.0.0.3' + assert mock_get_instance.call_count == 1 @patch.object(EC2Provider, '_get_instance') - @patch.object(EC2Provider, '_get_driver') - def test_ec2_set_image_id(self, mock_get_driver, mock_get_instance): - """Test ec2 provider set image id method.""" + def test_ec2_start_instance(self, mock_get_instance): + """Test ec2 start instance method.""" instance = MagicMock() - instance.extra = {'image_id': 'ami-123456'} + instance.start.return_value = None + instance.wait_until_running.return_value = None mock_get_instance.return_value = instance - mock_get_driver.return_value = None provider = EC2Provider(**self.kwargs) - provider._set_image_id() + provider._start_instance() + assert mock_get_instance.call_count == 1 + + @patch.object(EC2Provider, '_get_instance') + def test_ec2_stop_instance(self, mock_get_instance): + """Test ec2 stop instance method.""" + instance = MagicMock() + instance.stop.return_value = None + instance.wait_until_stopped.return_value = None + mock_get_instance.return_value = instance + + provider = EC2Provider(**self.kwargs) + provider._stop_instance() + assert mock_get_instance.call_count == 1 - assert provider.image_id == instance.extra['image_id'] + @patch.object(EC2Provider, '_get_instance') + def test_ec2_terminate_instance(self, mock_get_instance): + """Test ec2 terminate instance method.""" + instance = MagicMock() + instance.terminate.return_value = None + mock_get_instance.return_value = instance + + provider = EC2Provider(**self.kwargs) + provider._terminate_instance() assert mock_get_instance.call_count == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/tests/test_ipa_provider.py new/python3-ipa-2.5.0/tests/test_ipa_provider.py --- old/python3-ipa-2.4.0/tests/test_ipa_provider.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/tests/test_ipa_provider.py 2018-12-12 02:51:14.000000000 +0100 @@ -188,9 +188,9 @@ for key, val in results['tests'][0].items(): assert provider.results['tests'][0][key] == val - def test_process_sync_test_results(self): + def test_process_test_results(self): provider = IpaProvider(*args, **self.kwargs) - provider._process_sync_test_results(5.0, 'test_test') + provider._process_test_results(5.0, 'test_test') assert provider.results['summary']['duration'] == 5.0 assert provider.results['summary']['num_tests'] == 1 @@ -537,6 +537,7 @@ mock_set_instance_ip.return_value = None self.kwargs['running_instance_id'] = 'fakeinstance' self.kwargs['test_files'] = ['test_update'] + self.kwargs['cleanup'] = True provider = IpaProvider(*args, **self.kwargs) provider.ssh_private_key_file = 'tests/data/ida_test' @@ -545,6 +546,7 @@ status, results = provider.test_image() assert status == 0 assert mock_distro_update.call_count == 1 + self.kwargs['cleanup'] = None @patch.object(IpaProvider, '_set_instance_ip') @patch.object(IpaProvider, '_set_image_id') @@ -589,3 +591,33 @@ provider = IpaProvider(*args, **self.kwargs) provider._wait_on_instance('Stopped') assert mock_get_instance_state.call_count == 1 + + @patch.object(IpaProvider, '_get_ssh_client') + def test_collect_vm_info(self, mock_get_ssh_client): + """Test collect_vm_info method. """ + distro = MagicMock() + client = MagicMock() + distro.get_vm_info.return_value = \ + 'Failed to collect VM info: Does not exist.' + mock_get_ssh_client.return_value = client + + provider = IpaProvider(*args, **self.kwargs) + provider.distro = distro + provider.log_file = 'fake_file.name' + provider.logger = MagicMock() + + with patch('builtins.open', create=True) as mock_open: + mock_open.return_value = MagicMock(spec=io.IOBase) + file_handle = mock_open.return_value.__enter__.return_value + + provider._collect_vm_info() + + file_handle.write.assert_has_calls([ + call('\n'), + call('Failed to collect VM info: Does not exist.') + ]) + + provider.logger.info.assert_called_once_with( + 'Collecting basic info about VM' + ) + assert mock_get_ssh_client.call_count == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python3-ipa-2.4.0/tests/test_ipa_sles_distro.py new/python3-ipa-2.5.0/tests/test_ipa_sles_distro.py --- old/python3-ipa-2.4.0/tests/test_ipa_sles_distro.py 2018-12-07 01:07:28.000000000 +0100 +++ new/python3-ipa-2.5.0/tests/test_ipa_sles_distro.py 2018-12-12 02:51:14.000000000 +0100 @@ -29,22 +29,6 @@ from unittest.mock import MagicMock, patch -def test_sles_set_init_system_exception(): - """Test SLES set init system method exception.""" - client = MagicMock() - sles = SLES() - - with patch('ipa.ipa_utils.execute_ssh_command', MagicMock( - side_effect=Exception('ERROR!'))) as mocked: - pytest.raises( - IpaSLESException, - sles._set_init_system, - client - ) - - mocked.assert_called_once_with(client, 'ps -p 1 -o comm=') - - def test_sles_get_stop_ssh_cmd(): """Test SLES get stop ssh cmd method.""" sles = SLES()
