def _create(self, name, image, size, network, key_pair, fip):
        """Create.

        This method will create a resource (node) in openstack. It will
        create the node, wait for the node to finish building and optionally
        attach a floating ip address. At any point if an exception is raised
        trying to wait for the node to finish building or a floating ip
        cannot be attached. It will clean up the resource (node).

        :param name: Node name.
        :type name: str
        :param image: Image name.
        :type image: str
        :param size: Size name.
        :type size: str
        :param network: Network name.
        :type network: str
        :param key_pair: Key pair to inject into node.
        :type key_pair: str
        :param fip: Floating ip pool.
        :type fip: str
        :return: Node fip. id.
        :rtype: str(s)
        """
        self.logger.info('Provisioning node %s.' % name)

        # create node
        try:
            node = self.create_node(name, image, size, network, key_pair)
        except Exception:
            self.logger.error("Failed to create node %s " % name)
            raise

        # wait for node to complete building
        try:
            self.wait_for_building_finish(node)
        except Exception as ex:
            self.logger.error(ex.message)
            self.logger.error('Node %s did not finish building.' % node.name)
            self.delete_node(node)
            raise OpenstackProviderError('Node did not finish building.')

        # attach floating ip to node
        try:
            ip = self.attach_floating_ip(node, fip)
        except Exception as ex:
            self.logger.error(ex.message)
            self.logger.error('Failed to attach fip to node %s.' % node.name)
            self.delete_node(node)
            raise OpenstackProviderError('Failed to attach fip.')

        self.logger.info('Successfully provisioned node %s.' % name)

        # if no floating ip is assigned get updated node details and look for private_ip
        # TODO: This might need more logic if we support a use case for more than one network specified
        if ip is None:
            node = self.driver.ex_get_node_details(node.id)
            ip = node.private_ips[-1]
        return ip, node.id
    def detach_floating_ip(self, node):
        """Detach floating ip from a given node.

        This method will get the floating ip from the node provided, get the
        floating ip object, detach the floating ip from the node and delete
        the floating ip (free up resources).

        :param node: Node object.
        :type node: object
        """
        try:
            # cache floating ip
            _fip = self.floating_ip_lookup(node)

            # get floating ip object
            _fip_obj = self.driver.ex_get_floating_ip(_fip)

            # detach ip from node
            self.driver.ex_detach_floating_ip_from_node(node, _fip_obj)

            # delete ip
            self.driver.ex_delete_floating_ip(_fip_obj)
        except OpenstackProviderError:
            self.logger.warning('Node %s does not have fip.' % node.name)
        except Exception as ex:
            self.logger.error(ex)
            raise OpenstackProviderError('Unable to detach FIP from %s' % node)
        finally:
            self.unset_driver()
    def _delete(self, name):
        """Delete.

        This method will delete a resource (node) in openstack. It will check
        if the node exists, optionally detach a floating ip address and then
        delete the resource (node).

        :param name: Node name.
        :type name: str
        """
        self.logger.info('Tearing down node %s.' % name)

        try:
            # cache node
            _node = self.node_lookup(name)

            # detach floating ip
            self.detach_floating_ip(_node)

            # delete node
            self.delete_node(_node)
        except Exception as ex:
            self.logger.error(ex.message)
            raise OpenstackProviderError('Unable to delete node %s' % ex)
        finally:
            self.unset_driver()

        self.logger.info('Successfully teared down node %s.' % name)
    def create_node(self, name, image, size, network, key_pair):
        """Create node.

        This method will create a new node in openstack. The node will be
        created based on the resource specifications provided. At any point if
        the creation gets an exception, it will wait and retry creating the
        node. If maximum attempts are reached, an exception will be raised.

        :param name: Node name.
        :type name: str
        :param image: Image name.
        :type image: str
        :param size: Size name.
        :type size: str
        :param network: Network name.
        :type network: str
        :param key_pair: Key pair to inject into node.
        :type key_pair: str
        :return: Node object.
        :rtype: object
        """
        attempt = 1

        self.logger.debug('Creating node %s.' % name)
        self.logger.debug('Node details:\n * keypair=%s\n * image=%s\n '
                          '* flavor=%s\n * networks=%s' %
                          (key_pair, image, size, network))

        # cache image object
        _image = self.image_lookup(image)

        # cache size object
        _size = self.size_lookup(size)

        # network object
        _network = self.network_lookup(network)

        # create node
        while attempt <= MAX_ATTEMPTS:
            try:
                node = self.driver.create_node(name=name,
                                               image=_image,
                                               size=_size,
                                               networks=_network,
                                               ex_keyname=key_pair)
                self.logger.info('Successfully booted node %s.' % name)
                return node
            except Exception as ex:
                self.logger.error(ex.message)
                wait_time = random.randint(10, MAX_WAIT_TIME)
                self.logger.info('Attempt %s of %s: retrying in %s seconds' %
                                 (attempt, MAX_ATTEMPTS, wait_time))
                time.sleep(wait_time)
                attempt += 1
            finally:
                self.unset_driver()

        # reach this point, maximum attempts to create node reached
        raise OpenstackProviderError(
            'Maximum attempts reached to boot node %s.' % name)
    def delete_node(self, node):
        """Delete node.

        This method will delete an existing node in openstack. At any point if
        the deletion gets an exception, it will wait and retry creating the
        node. If maximum attempts are reached, an exception will be raised.

        :param node: Node object.
        :type node: object
        """
        attempt = 1

        # delete node
        while attempt <= MAX_ATTEMPTS:
            try:
                self.driver.destroy_node(node)
                self.logger.info('Successfully deleted node %s.' % node.name)
                return
            except Exception as ex:
                self.logger.error(ex.message)
                wait_time = random.randint(10, MAX_WAIT_TIME)
                self.logger.info('Attempt %s of %s: retrying in %s seconds' %
                                 (attempt, MAX_ATTEMPTS, wait_time))
                time.sleep(wait_time)
                attempt += 1
            finally:
                self.unset_driver()

        # reach this point, maximum attempts to delete node reached
        raise OpenstackProviderError(
            'Maximum attempts reached to delete node %s.' % node.name)
    def network_lookup(self, name):
        """Get the libcloud network object based on the network provided.

        This method will fetch all networks using libcloud and return the
        network object matching the network given. If no network is found, an
        exception will be raised.

        :param name: Network name.
        :type name: str or list
        :return: Network object.
        :rtype: object
        """
        # collect networks
        _networks = self.networks

        # filter networks
        nets = list()
        if isinstance(name, string_types):
            nets = list(filter(lambda elm: elm.name == name, _networks))
        elif isinstance(name, list):
            for net in name:
                data = list(filter(lambda elm: elm.name == net, _networks))
                if len(data) != 0:
                    nets.append(data)

        # process results
        if len(nets) == 0:
            raise OpenstackProviderError('Network(s) %s not found!' % name)
        else:
            return nets[0]
    def size_lookup(self, name):
        """Get the libcloud size object based on the size provided.

        This method will fetch all sizes (flavors) using libcloud and return
        the size object matching the size (flavor) given. If no size is found,
        an exception will be raised.

        :param name: Image name.
        :type name: str
        :return: Size object.
        :rtype: object
        """
        # collect sizes
        _sizes = self.sizes

        # filter sizes
        by_name = list(filter(lambda elm: elm.name == name, _sizes))
        by_id = list()
        for size in _sizes:
            try:
                if name == int(size.id):
                    by_id.append(size)
                    break
            except ValueError:
                # base 10
                continue

        # process results
        if len(by_name) != 0:
            return by_name[0]
        elif len(by_id) != 0:
            return by_id[0]
        else:
            raise OpenstackProviderError('Flavor %s not found!' % name)
    def image_lookup(self, name):
        """Get the libcloud image object based on the image provided.

        This method will fetch all images using libcloud and return the
        image object matching the image given. If no image is found, an
        exception will be raised.

        :param name: Image name.
        :type name: str
        :return: Image object.
        :rtype: object
        """
        # collect images
        _images = self.images

        # filter images
        by_name = list(filter(lambda elm: elm.name == name, _images))
        by_id = list(filter(lambda elm: elm.id == name, _images))

        # process results
        if len(by_name) != 0:
            return by_name[0]
        elif len(by_id) != 0:
            return by_id[0]
        else:
            raise OpenstackProviderError('Image %s not found!' % name)
    def authenticate(self):
        """Openstack authentication.

        This method will create a driver object with libcloud provider class.
        Once driver object is created, it will verify the authentication with
        openstack endpoint is valid. If invalid, an exception will be raised.

        Apache libcloud recommends to test authentication to test calling a
        method to perform some request against openstack. Checking the
        networks is quick to determine if authentication is good. FAQ:
            - http://libcloud.readthedocs.io/en/latest/faq.html
        """
        # ignore SSL
        libcloud.security.VERIFY_SSL_CERT = False

        # suppress insecure request warning messages
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

        credentials = self.provider_credentials

        # determine region
        try:
            if not credentials['region']:
                # set default region if no region is defined
                credentials['region'] = 'regionOne'
        except KeyError:
            credentials['region'] = 'regionOne'

        # determine domain
        try:
            if not credentials['domain_name']:
                # set the default domain if no domain is defined
                credentials['domain_name'] = 'default'
        except KeyError:
            credentials['domain_name'] = 'default'

        # create libcloud driver object
        self._driver = get_driver(Provider.OPENSTACK)(
            credentials['username'],
            credentials['password'],
            ex_tenant_name=credentials['tenant_name'],
            ex_force_auth_url=credentials['auth_url'].split('/v')[0],
            ex_force_auth_version='3.x_password',
            ex_domain_name=credentials['domain_name'],
            ex_force_service_region=credentials['region'])
        # test authentication
        try:
            self._driver.ex_list_networks()
        except InvalidCredsError as ex:
            raise OpenstackProviderError('Authentication failed: %s' %
                                         ex.message)
    def floating_ip_lookup(self, node):
        """Get the floating ip object based on the node provided.

        This method will return the foating ip address for the node given. If
        no floating ip is found, an exception will be raised.

        :param node: Node object.
        :type node: object
        :return: Floating ip object.
        :rtype: object
        """
        for key in node.extra['addresses']:
            for network in node.extra['addresses'][key]:
                # skip if network is not type floating
                if network['OS-EXT-IPS:type'] != 'floating':
                    continue
                return network['addr']

        raise OpenstackProviderError('Unable to get FIP for node!')
    def key_pair_lookup(self, name):
        """Get the libcloud key pair object based on the key pair provided.

        This method will fetch all key pairs using libcloud and return the
        key pair object matching the key pair name given. If no key pair is
        found, an exception will be raised.

        :param name: Key pair name.
        :type name: str
        :return: Key pair object.
        :rtype: object
        """
        # filter key pairs
        data = list(filter(lambda elm: elm.name == name, self.key_pairs))

        # process results
        if len(data) == 0:
            raise OpenstackProviderError('Keypair %s not found!' % name)
        else:
            return data[0]
    def node_lookup(self, name):
        """Get the libcloud node object based on the node name provided.

        This method will fetch all nodes using libcloud and return the node
        object matching the node name given. If no node is found, an exception
        will be raised.

        :param name: Node name.
        :type name: str
        :return: Node object.
        :rtype: object
        """
        # filter nodes
        data = list(filter(lambda elm: elm.name == name, self.nodes))

        # process results
        if len(data) == 0:
            raise OpenstackProviderError('Node %s not found!' % name)
        else:
            return data[0]
    def floating_ip_pool_lookup(self, name):
        """Get the libcloud fip object based on the fip provided.

        This method will fetch all floating ip pools using libcloud and
        return the floating ip pool object matching the floating ip pool name
        given. If no floating ip pool is found, an exception will be raised.

        :param name: Floating ip pool name.
        :type name: str
        :return: Floating ip pool object.
        :rtype: object
        """
        # filter floating ip pools
        data = list(
            filter(lambda elm: elm.name == name, self.floating_ip_pools))

        # process results
        if len(data) == 0:
            raise OpenstackProviderError('FIP %s not found!' % name)
        else:
            return data[0]
    def attach_floating_ip(self, node, fip):
        """Attach a floating ip address to a node.

        This method will get the floating ip pool object, create a floating
        ip within that pool and attach to the node provided.

        :param node: Node object.
        :type node: object
        :param fip: Floating ip pool.
        :type fip: str
        """
        # do not attach floating ip if variable has None value
        if not fip:
            self.logger.warning('Node %s does not require fip.' % node.name)
            return

        self.logger.info('Attaching fip to node %s.' % node.name)

        try:
            # cache floating ip pool object
            _pool = self.floating_ip_pool_lookup(fip)

            # create floating ip
            _ip = _pool.create_floating_ip()

            # attach ip to node
            self.driver.ex_attach_floating_ip_to_node(node, _ip)
        except Exception as ex:
            self.logger.error(ex)
            raise OpenstackProviderError('Unable to attach FIP to %s' % node)
        finally:
            self.unset_driver()

        self.logger.info('FIP %s successfully attached to node %s.' %
                         (_ip.ip_address, node.name))
        return _ip.ip_address
    def wait_for_building_finish(self, node):
        """Wait until a node is finished building.

        :param node: node object
        """
        self.logger.info('Wait for node %s to finish building.' % node.name)

        status = 0
        attempt = 1
        while attempt <= 30:
            node = self.driver.ex_get_node_details(node.id)
            state = getattr(node, 'state')
            msg = '%s. VM %s, STATE=%s' % (attempt, node.name, state)

            if state.lower() == 'error':
                self.logger.info(msg)
                self.logger.error('VM %s got an into an error state!' %
                                  node.name)
                break
            elif state.lower() == 'running':
                self.logger.info(msg)
                self.logger.info('VM %s successfully finished building!' %
                                 node.name)
                status = 1
                break
            else:
                self.logger.info('%s, rechecking in 20 seconds.', msg)
                time.sleep(20)

        self.unset_driver()
        if status:
            self.logger.info('Node %s successfully finished building.' %
                             node.name)
        else:
            raise OpenstackProviderError('Node was unable to build, %s' %
                                         node.name)
Example #16
0
def test_openstack_provider_error():
    with pytest.raises(OpenstackProviderError):
        raise OpenstackProviderError('error message')