| # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. |
| # Copyright (c) 2017 IBM Corp. |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import fixtures |
| from oslo_log import log as logging |
| from oslo_utils import excutils |
| |
| from tempest.lib.common.utils import data_utils |
| from tempest.lib import exceptions as lib_exc |
| |
| LOG = logging.getLogger(__name__) |
| |
| |
| def _network_service(clients, use_neutron): |
| # Internal helper to select the right network clients |
| if use_neutron: |
| return clients.network |
| else: |
| return clients.compute |
| |
| |
| def create_ssh_security_group(clients, add_rule=False, ethertype='IPv4', |
| use_neutron=True): |
| """Create a security group for ping/ssh testing |
| |
| Create a security group to be attached to a VM using the nova or neutron |
| clients. If rules are added, the group can be attached to a VM to enable |
| connectivity validation over ICMP and further testing over SSH. |
| |
| :param clients: Instance of `tempest.lib.services.clients.ServiceClients` |
| or of a subclass of it. Resources are provisioned using clients from |
| `clients`. |
| :param add_rule: Whether security group rules are provisioned or not. |
| Defaults to `False`. |
| :param ethertype: 'IPv4' or 'IPv6'. Honoured only in case neutron is used. |
| :param use_neutron: When True resources are provisioned via neutron, when |
| False resources are provisioned via nova. |
| :returns: A dictionary with the security group as returned by the API. |
| |
| Examples:: |
| |
| from tempest.common import validation_resources as vr |
| from tempest.lib import auth |
| from tempest.lib.services import clients |
| |
| creds = auth.get_credentials('http://mycloud/identity/v3', |
| username='me', project_name='me', |
| password='secret', domain_name='Default') |
| osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3') |
| # Security group for IPv4 tests |
| sg4 = vr.create_ssh_security_group(osclients, add_rule=True) |
| # Security group for IPv6 tests |
| sg6 = vr.create_ssh_security_group(osclients, ethertype='IPv6', |
| add_rule=True) |
| """ |
| network_service = _network_service(clients, use_neutron) |
| security_groups_client = network_service.SecurityGroupsClient() |
| security_group_rules_client = network_service.SecurityGroupRulesClient() |
| # Security Group clients for nova and neutron behave the same |
| sg_name = data_utils.rand_name('securitygroup-') |
| sg_description = data_utils.rand_name('description-') |
| security_group = security_groups_client.create_security_group( |
| name=sg_name, description=sg_description)['security_group'] |
| # Security Group Rules clients require different parameters depending on |
| # the network service in use |
| if add_rule: |
| try: |
| if use_neutron: |
| security_group_rules_client.create_security_group_rule( |
| security_group_id=security_group['id'], |
| protocol='tcp', |
| ethertype=ethertype, |
| port_range_min=22, |
| port_range_max=22, |
| direction='ingress') |
| security_group_rules_client.create_security_group_rule( |
| security_group_id=security_group['id'], |
| protocol='icmp', |
| ethertype=ethertype, |
| direction='ingress') |
| else: |
| security_group_rules_client.create_security_group_rule( |
| parent_group_id=security_group['id'], ip_protocol='tcp', |
| from_port=22, to_port=22) |
| security_group_rules_client.create_security_group_rule( |
| parent_group_id=security_group['id'], ip_protocol='icmp', |
| from_port=-1, to_port=-1) |
| except Exception as sgc_exc: |
| # If adding security group rules fails, we cleanup the SG before |
| # re-raising the failure up |
| with excutils.save_and_reraise_exception(): |
| try: |
| msg = ('Error while provisioning security group rules in ' |
| 'security group %s. Trying to cleanup.') |
| # The exceptions logging is already handled, so using |
| # debug here just to provide more context |
| LOG.debug(msg, sgc_exc) |
| clear_validation_resources( |
| clients, keypair=None, floating_ip=None, |
| security_group=security_group, |
| use_neutron=use_neutron) |
| except Exception as cleanup_exc: |
| msg = ('Error during cleanup of a security group. ' |
| 'The cleanup was triggered by an exception during ' |
| 'the provisioning of security group rules.\n' |
| 'Provisioning exception: %s\n' |
| 'First cleanup exception: %s') |
| LOG.exception(msg, sgc_exc, cleanup_exc) |
| LOG.debug("SSH Validation resource security group with tcp and icmp " |
| "rules %s created", sg_name) |
| return security_group |
| |
| |
| def create_validation_resources(clients, keypair=False, floating_ip=False, |
| security_group=False, |
| security_group_rules=False, |
| ethertype='IPv4', use_neutron=True, |
| floating_network_id=None, |
| floating_network_name=None): |
| """Provision resources for VM ping/ssh testing |
| |
| Create resources required to be able to ping / ssh a virtual machine: |
| keypair, security group, security group rules and a floating IP. |
| Which of those resources are required may depend on the cloud setup and on |
| the specific test and it can be controlled via the corresponding |
| arguments. |
| |
| Provisioned resources are returned in a dictionary. |
| |
| :param clients: Instance of `tempest.lib.services.clients.ServiceClients` |
| or of a subclass of it. Resources are provisioned using clients from |
| `clients`. |
| :param keypair: Whether to provision a keypair. Defaults to False. |
| :param floating_ip: Whether to provision a floating IP. Defaults to False. |
| :param security_group: Whether to provision a security group. Defaults to |
| False. |
| :param security_group_rules: Whether to provision security group rules. |
| Defaults to False. |
| :param ethertype: 'IPv4' or 'IPv6'. Honoured only in case neutron is used. |
| :param use_neutron: When True resources are provisioned via neutron, when |
| False resources are provisioned via nova. |
| :param floating_network_id: The id of the network used to provision a |
| floating IP. Only used if a floating IP is requested and with neutron. |
| :param floating_network_name: The name of the floating IP pool used to |
| provision the floating IP. Only used if a floating IP is requested and |
| with nova-net. |
| :returns: A dictionary with the resources in the format they are returned |
| by the API. Valid keys are 'keypair', 'floating_ip' and |
| 'security_group'. |
| |
| Examples:: |
| |
| from tempest.common import validation_resources as vr |
| from tempest.lib import auth |
| from tempest.lib.services import clients |
| |
| creds = auth.get_credentials('http://mycloud/identity/v3', |
| username='me', project_name='me', |
| password='secret', domain_name='Default') |
| osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3') |
| # Request keypair and floating IP |
| resources = dict(keypair=True, security_group=False, |
| security_group_rules=False, floating_ip=True) |
| resources = vr.create_validation_resources( |
| osclients, use_neutron=True, |
| floating_network_id='4240E68E-23DA-4C82-AC34-9FEFAA24521C', |
| **resources) |
| |
| # The floating IP to be attached to the VM |
| floating_ip = resources['floating_ip']['ip'] |
| """ |
| # Create and Return the validation resources required to validate a VM |
| validation_data = {} |
| try: |
| if keypair: |
| keypair_name = data_utils.rand_name('keypair') |
| validation_data.update( |
| clients.compute.KeyPairsClient().create_keypair( |
| name=keypair_name)) |
| LOG.debug("Validation resource key %s created", keypair_name) |
| if security_group: |
| validation_data['security_group'] = create_ssh_security_group( |
| clients, add_rule=security_group_rules, |
| use_neutron=use_neutron, ethertype=ethertype) |
| if floating_ip: |
| floating_ip_client = _network_service( |
| clients, use_neutron).FloatingIPsClient() |
| if use_neutron: |
| floatingip = floating_ip_client.create_floatingip( |
| floating_network_id=floating_network_id) |
| # validation_resources['floating_ip'] has historically looked |
| # like a compute API POST /os-floating-ips response, so we need |
| # to mangle it a bit for a Neutron response with different |
| # fields. |
| validation_data['floating_ip'] = floatingip['floatingip'] |
| validation_data['floating_ip']['ip'] = ( |
| floatingip['floatingip']['floating_ip_address']) |
| else: |
| # NOTE(mriedem): The os-floating-ips compute API was deprecated |
| # in the 2.36 microversion. Any tests for CRUD operations on |
| # floating IPs using the compute API should be capped at 2.35. |
| validation_data.update(floating_ip_client.create_floating_ip( |
| pool=floating_network_name)) |
| LOG.debug("Validation resource floating IP %s created", |
| validation_data['floating_ip']) |
| except Exception as prov_exc: |
| # If something goes wrong, cleanup as much as possible before we |
| # re-raise the exception |
| with excutils.save_and_reraise_exception(): |
| if validation_data: |
| # Cleanup may fail as well |
| try: |
| msg = ('Error while provisioning validation resources %s. ' |
| 'Trying to cleanup what we provisioned so far: %s') |
| # The exceptions logging is already handled, so using |
| # debug here just to provide more context |
| LOG.debug(msg, prov_exc, str(validation_data)) |
| clear_validation_resources( |
| clients, |
| keypair=validation_data.get('keypair', None), |
| floating_ip=validation_data.get('floating_ip', None), |
| security_group=validation_data.get('security_group', |
| None), |
| use_neutron=use_neutron) |
| except Exception as cleanup_exc: |
| msg = ('Error during cleanup of validation resources. ' |
| 'The cleanup was triggered by an exception during ' |
| 'the provisioning step.\n' |
| 'Provisioning exception: %s\n' |
| 'First cleanup exception: %s') |
| LOG.exception(msg, prov_exc, cleanup_exc) |
| return validation_data |
| |
| |
| def clear_validation_resources(clients, keypair=None, floating_ip=None, |
| security_group=None, use_neutron=True): |
| """Cleanup resources for VM ping/ssh testing |
| |
| Cleanup a set of resources provisioned via `create_validation_resources`. |
| In case of errors during cleanup, the exception is logged and the cleanup |
| process is continued. The first exception that was raised is re-raised |
| after the cleanup is complete. |
| |
| :param clients: Instance of `tempest.lib.services.clients.ServiceClients` |
| or of a subclass of it. Resources are provisioned using clients from |
| `clients`. |
| :param keypair: A dictionary with the keypair to be deleted. Defaults to |
| None. |
| :param floating_ip: A dictionary with the floating_ip to be deleted. |
| Defaults to None. |
| :param security_group: A dictionary with the security_group to be deleted. |
| Defaults to None. |
| :param use_neutron: When True resources are provisioned via neutron, when |
| False resources are provisioned via nova. |
| |
| Examples:: |
| |
| from tempest.common import validation_resources as vr |
| from tempest.lib import auth |
| from tempest.lib.services import clients |
| |
| creds = auth.get_credentials('http://mycloud/identity/v3', |
| username='me', project_name='me', |
| password='secret', domain_name='Default') |
| osclients = clients.ServiceClients(creds, 'http://mycloud/identity/v3') |
| # Request keypair and floating IP |
| resources = dict(keypair=True, security_group=False, |
| security_group_rules=False, floating_ip=True) |
| resources = vr.create_validation_resources( |
| osclients, validation_resources=resources, use_neutron=True, |
| floating_network_id='4240E68E-23DA-4C82-AC34-9FEFAA24521C') |
| |
| # Now cleanup the resources |
| try: |
| vr.clear_validation_resources(osclients, use_neutron=True, |
| **resources) |
| except Exception as e: |
| LOG.exception('Something went wrong during cleanup, ignoring') |
| """ |
| has_exception = None |
| if keypair: |
| keypair_client = clients.compute.KeyPairsClient() |
| keypair_name = keypair['name'] |
| try: |
| keypair_client.delete_keypair(keypair_name) |
| except lib_exc.NotFound: |
| LOG.warning( |
| "Keypair %s is not found when attempting to delete", |
| keypair_name |
| ) |
| except Exception as exc: |
| LOG.exception('Exception raised while deleting key %s', |
| keypair_name) |
| if not has_exception: |
| has_exception = exc |
| network_service = _network_service(clients, use_neutron) |
| if security_group: |
| security_group_client = network_service.SecurityGroupsClient() |
| sec_id = security_group['id'] |
| try: |
| security_group_client.delete_security_group(sec_id) |
| security_group_client.wait_for_resource_deletion(sec_id) |
| except lib_exc.NotFound: |
| LOG.warning("Security group %s is not found when attempting " |
| "to delete", sec_id) |
| except lib_exc.Conflict as exc: |
| LOG.exception('Conflict while deleting security ' |
| 'group %s VM might not be deleted', sec_id) |
| if not has_exception: |
| has_exception = exc |
| except Exception as exc: |
| LOG.exception('Exception raised while deleting security ' |
| 'group %s', sec_id) |
| if not has_exception: |
| has_exception = exc |
| if floating_ip: |
| floating_ip_client = network_service.FloatingIPsClient() |
| fip_id = floating_ip['id'] |
| try: |
| if use_neutron: |
| floating_ip_client.delete_floatingip(fip_id) |
| else: |
| floating_ip_client.delete_floating_ip(fip_id) |
| except lib_exc.NotFound: |
| LOG.warning('Floating ip %s not found while attempting to ' |
| 'delete', fip_id) |
| except Exception as exc: |
| LOG.exception('Exception raised while deleting ip %s', fip_id) |
| if not has_exception: |
| has_exception = exc |
| if has_exception: |
| raise has_exception |
| |
| |
| class ValidationResourcesFixture(fixtures.Fixture): |
| """Fixture to provision and cleanup validation resources""" |
| |
| DICT_KEYS = ['keypair', 'security_group', 'floating_ip'] |
| |
| def __init__(self, clients, keypair=False, floating_ip=False, |
| security_group=False, security_group_rules=False, |
| ethertype='IPv4', use_neutron=True, floating_network_id=None, |
| floating_network_name=None): |
| """Create a ValidationResourcesFixture |
| |
| Create a ValidationResourcesFixture fixtures, which provisions the |
| resources required to be able to ping / ssh a virtual machine upon |
| setUp and clears them out upon cleanup. Resources are keypair, |
| security group, security group rules and a floating IP - depending |
| on the params. |
| |
| The fixture exposes a dictionary that includes provisioned resources. |
| |
| :param clients: `tempest.lib.services.clients.ServiceClients` or of a |
| subclass of it. Resources are provisioned using clients from |
| `clients`. |
| :param keypair: Whether to provision a keypair. Defaults to False. |
| :param floating_ip: Whether to provision a floating IP. |
| Defaults to False. |
| :param security_group: Whether to provision a security group. |
| Defaults to False. |
| :param security_group_rules: Whether to provision security group rules. |
| Defaults to False. |
| :param ethertype: 'IPv4' or 'IPv6'. Honoured only if neutron is used. |
| :param use_neutron: When True resources are provisioned via neutron, |
| when False resources are provisioned via nova. |
| :param floating_network_id: The id of the network used to provision a |
| floating IP. Only used if a floating IP is requested in case |
| neutron is used. |
| :param floating_network_name: The name of the floating IP pool used to |
| provision the floating IP. Only used if a floating IP is requested |
| and with nova-net. |
| :returns: A dictionary with the same keys as the input |
| `validation_resources` and the resources for values in the format |
| they are returned by the API. |
| |
| Examples:: |
| |
| from tempest.common import validation_resources as vr |
| from tempest.lib import auth |
| from tempest.lib.services import clients |
| import testtools |
| |
| |
| class TestWithVR(testtools.TestCase): |
| |
| def setUp(self): |
| creds = auth.get_credentials( |
| 'http://mycloud/identity/v3', |
| username='me', project_name='me', |
| password='secret', domain_name='Default') |
| |
| osclients = clients.ServiceClients( |
| creds, 'http://mycloud/identity/v3') |
| # Request keypair and floating IP |
| resources = dict(keypair=True, security_group=False, |
| security_group_rules=False, |
| floating_ip=True) |
| network_id = '4240E68E-23DA-4C82-AC34-9FEFAA24521C' |
| self.vr = self.useFixture(vr.ValidationResourcesFixture( |
| osclients, use_neutron=True, |
| floating_network_id=network_id, |
| **resources) |
| |
| def test_use_ip(self): |
| # The floating IP to be attached to the VM |
| floating_ip = self.vr['floating_ip']['ip'] |
| """ |
| self._clients = clients |
| self._keypair = keypair |
| self._floating_ip = floating_ip |
| self._security_group = security_group |
| self._security_group_rules = security_group_rules |
| self._ethertype = ethertype |
| self._use_neutron = use_neutron |
| self._floating_network_id = floating_network_id |
| self._floating_network_name = floating_network_name |
| self._validation_resources = None |
| |
| def _setUp(self): |
| self._validation_resources = create_validation_resources( |
| self._clients, keypair=self._keypair, |
| floating_ip=self._floating_ip, |
| security_group=self._security_group, |
| security_group_rules=self._security_group_rules, |
| ethertype=self._ethertype, use_neutron=self._use_neutron, |
| floating_network_id=self._floating_network_id, |
| floating_network_name=self._floating_network_name) |
| # If provisioning raises an exception we won't have anything to |
| # cleanup here, so we don't need a try-finally around provisioning |
| vr = self._validation_resources |
| self.addCleanup(clear_validation_resources, self._clients, |
| keypair=vr['keypair'], |
| floating_ip=vr['floating_ip'], |
| security_group=vr['security_group'], |
| use_neutron=self._use_neutron) |
| |
| @property |
| def resources(self): |
| return self._validation_resources |