Deprecating launch app and ansible support.
[snaps.git] / snaps / openstack / create_instance.py
index b68372e..b158a25 100644 (file)
 import logging
 import time
 
-from novaclient.exceptions import NotFound, BadRequest
+from novaclient.exceptions import NotFound
 
 from snaps.config.vm_inst import VmInstanceConfig, FloatingIpConfig
 from snaps.openstack.openstack_creator import OpenStackComputeObject
-from snaps.openstack.utils import glance_utils, cinder_utils, settings_utils
+from snaps.openstack.utils import (
+    glance_utils, cinder_utils, settings_utils, keystone_utils)
 from snaps.openstack.utils import neutron_utils
 from snaps.openstack.utils import nova_utils
 from snaps.openstack.utils.nova_utils import RebootType
@@ -32,7 +33,6 @@ logger = logging.getLogger('create_instance')
 POLL_INTERVAL = 3
 STATUS_ACTIVE = 'ACTIVE'
 STATUS_DELETED = 'DELETED'
-CLOUD_INIT_TIMEOUT = 120
 
 
 class OpenStackVmInstance(OpenStackComputeObject):
@@ -73,7 +73,14 @@ class OpenStackVmInstance(OpenStackComputeObject):
         """
         super(self.__class__, self).initialize()
 
-        self.__neutron = neutron_utils.neutron_client(self._os_creds)
+        self.__neutron = neutron_utils.neutron_client(
+            self._os_creds, self._os_session)
+        self.__keystone = keystone_utils.keystone_client(
+            self._os_creds, self._os_session)
+        self.__cinder = cinder_utils.cinder_client(
+            self._os_creds, self._os_session)
+        self.__glance = glance_utils.glance_client(
+            self._os_creds, self._os_session)
 
         self.__ports = self.__query_ports(self.instance_settings.port_settings)
         self.__lookup_existing_vm_by_name()
@@ -89,7 +96,7 @@ class OpenStackVmInstance(OpenStackComputeObject):
         """
         self.initialize()
 
-        if len(self.__ports) == 0:
+        if len(self.__ports) != len(self.instance_settings.port_settings):
             self.__ports = self.__create_ports(
                 self.instance_settings.port_settings)
         if not self.__vm:
@@ -104,7 +111,7 @@ class OpenStackVmInstance(OpenStackComputeObject):
         within the project
         """
         server = nova_utils.get_server(
-            self._nova, self.__neutron,
+            self._nova, self.__neutron, self.__keystone,
             vm_inst_settings=self.instance_settings)
         if server:
             if server.name == self.instance_settings.name:
@@ -113,8 +120,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
                     'Found existing machine with name - %s',
                     self.instance_settings.name)
 
-                fips = neutron_utils.get_floating_ips(self.__neutron,
-                                                      self.__ports)
+                fips = neutron_utils.get_port_floating_ips(
+                    self.__neutron, self.__ports)
                 for port_id, fip in fips:
                     settings = self.instance_settings.floating_ip_settings
                     for fip_setting in settings:
@@ -133,10 +140,10 @@ class OpenStackVmInstance(OpenStackComputeObject):
                       active, error, or timeout waiting. Floating IPs will be
                       assigned after active when block=True
         """
-        glance = glance_utils.glance_client(self._os_creds)
         self.__vm = nova_utils.create_server(
-            self._nova, self.__neutron, glance, self.instance_settings,
-            self.image_settings, self.keypair_settings)
+            self._nova, self.__keystone, self.__neutron, self.__glance,
+            self.instance_settings, self.image_settings,
+            self._os_creds.project_name, self.keypair_settings)
         logger.info('Created instance with name - %s',
                     self.instance_settings.name)
 
@@ -160,20 +167,20 @@ class OpenStackVmInstance(OpenStackComputeObject):
 
         if self.instance_settings.volume_names:
             for volume_name in self.instance_settings.volume_names:
-                cinder = cinder_utils.cinder_client(self._os_creds)
                 volume = cinder_utils.get_volume(
-                    cinder, volume_name=volume_name)
+                    self.__cinder, self.__keystone, volume_name=volume_name,
+                    project_name=self._os_creds.project_name)
 
                 if volume and self.vm_active(block=True):
-                    timeout = 30
                     vm = nova_utils.attach_volume(
-                        self._nova, self.__neutron, self.__vm, volume, timeout)
+                        self._nova, self.__neutron, self.__keystone, self.__vm,
+                        volume, self._os_creds.project_name)
 
                     if vm:
                         self.__vm = vm
                     else:
-                        logger.warn('Volume [%s] not attached within timeout '
-                                    'of [%s]', volume.name, timeout)
+                        logger.warn(
+                            'Volume [%s] attachment timeout ', volume.name)
                 else:
                     logger.warn('Unable to attach volume named [%s]',
                                 volume_name)
@@ -214,18 +221,15 @@ class OpenStackVmInstance(OpenStackComputeObject):
         # gateway
         ext_gateway = self.__ext_gateway_by_router(
             floating_ip_setting.router_name)
-        if ext_gateway:
-            subnet = neutron_utils.get_subnet(
-                self.__neutron,
-                subnet_name=floating_ip_setting.subnet_name)
+        if ext_gateway and self.vm_active(block=True):
             floating_ip = neutron_utils.create_floating_ip(
-                self.__neutron, ext_gateway)
+                self.__neutron, self.__keystone, ext_gateway, port.id)
             self.__floating_ip_dict[floating_ip_setting.name] = floating_ip
 
             logger.info(
                 'Created floating IP %s via router - %s', floating_ip.ip,
                 floating_ip_setting.router_name)
-            self.__add_floating_ip(floating_ip, port, subnet)
+
             return floating_ip
         else:
             raise VmInstanceCreationError(
@@ -240,7 +244,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
         :return: the external network name or None
         """
         router = neutron_utils.get_router(
-            self.__neutron, router_name=router_name)
+            self.__neutron, self.__keystone, router_name=router_name,
+            project_name=self._os_creds.project_name)
         if router and router.external_network_id:
             network = neutron_utils.get_network_by_id(
                 self.__neutron, router.external_network_id)
@@ -270,12 +275,12 @@ class OpenStackVmInstance(OpenStackComputeObject):
         if self.__vm:
             # Detach Volume
             for volume_rec in self.__vm.volume_ids:
-                cinder = cinder_utils.cinder_client(self._os_creds)
                 volume = cinder_utils.get_volume_by_id(
-                    cinder, volume_rec['id'])
+                    self.__cinder, volume_rec['id'])
                 if volume:
                     vm = nova_utils.detach_volume(
-                        self._nova, self.__neutron, self.__vm, volume, 30)
+                        self._nova, self.__neutron, self.__keystone, self.__vm,
+                        volume, self._os_creds.project_name)
                     if vm:
                         self.__vm = vm
                     else:
@@ -308,6 +313,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
                     'VM not deleted within the timeout period of %s '
                     'seconds', self.instance_settings.vm_delete_timeout)
 
+        super(self.__class__, self).clean()
+
     def __query_ports(self, port_settings):
         """
         Returns the previously configured ports or an empty list if none
@@ -320,7 +327,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
 
         for port_setting in port_settings:
             port = neutron_utils.get_port(
-                self.__neutron, port_settings=port_setting)
+                self.__neutron, self.__keystone, port_settings=port_setting,
+                project_name=self._os_creds.project_name)
             if port:
                 ports.append((port_setting.name, port))
 
@@ -338,63 +346,16 @@ class OpenStackVmInstance(OpenStackComputeObject):
 
         for port_setting in port_settings:
             port = neutron_utils.get_port(
-                self.__neutron, port_settings=port_setting)
+                self.__neutron, self.__keystone, port_settings=port_setting,
+                project_name=self._os_creds.project_name)
             if not port:
                 port = neutron_utils.create_port(
                     self.__neutron, self._os_creds, port_setting)
-                if port:
-                    ports.append((port_setting.name, port))
+            if port:
+                ports.append((port_setting.name, port))
 
         return ports
 
-    def __add_floating_ip(self, floating_ip, port, subnet, timeout=30,
-                          poll_interval=POLL_INTERVAL):
-        """
-        Returns True when active else False
-        TODO - Make timeout and poll_interval configurable...
-        """
-        ip = None
-
-        if subnet:
-            # Take IP of subnet if there is one configured on which to place
-            # the floating IP
-            for fixed_ip in port.ips:
-                if fixed_ip['subnet_id'] == subnet.id:
-                    ip = fixed_ip['ip_address']
-                    break
-        else:
-            # Simply take the first
-            ip = port.ips[0]['ip_address']
-
-        if ip:
-            count = timeout / poll_interval
-            while count > 0:
-                logger.debug('Attempting to add floating IP to instance')
-                try:
-                    nova_utils.add_floating_ip_to_server(
-                        self._nova, self.__vm, floating_ip, ip)
-                    logger.info(
-                        'Added floating IP %s to port IP %s on instance %s',
-                        floating_ip.ip, ip, self.instance_settings.name)
-                    return
-                except BadRequest as bre:
-                    logger.error('Cannot add floating IP [%s]', bre)
-                    raise
-                except Exception as e:
-                    logger.debug(
-                        'Retry adding floating IP to instance. Last attempt '
-                        'failed with - %s', e)
-                    time.sleep(poll_interval)
-                    count -= 1
-                    pass
-        else:
-            raise VmInstanceCreationError(
-                'Unable find IP address on which to place the floating IP')
-
-        logger.error('Timeout attempting to add the floating IP to instance.')
-        raise VmInstanceCreationError(
-            'Timeout while attempting add floating IP to instance')
-
     def get_os_creds(self):
         """
         Returns the OpenStack credentials used to create these objects
@@ -408,7 +369,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
         :return: Server object
         """
         return nova_utils.get_server_object_by_id(
-            self._nova, self.__neutron, self.__vm.id)
+            self._nova, self.__neutron, self.__keystone, self.__vm.id,
+            self._os_creds.project_name)
 
     def get_console_output(self):
         """
@@ -429,8 +391,10 @@ class OpenStackVmInstance(OpenStackComputeObject):
         port = self.get_port_by_name(port_name)
         if port:
             if subnet_name:
+                network = neutron_utils.get_network_by_id(
+                    self.__neutron, port.network_id)
                 subnet = neutron_utils.get_subnet(
-                    self.__neutron, subnet_name=subnet_name)
+                    self.__neutron, network, subnet_name=subnet_name)
                 if not subnet:
                     logger.warning('Cannot retrieve port IP as subnet could '
                                    'not be located with name - %s',
@@ -475,6 +439,10 @@ class OpenStackVmInstance(OpenStackComputeObject):
         Returns a dictionary of a VMs info as returned by OpenStack
         :return: a dict()
         """
+        from warnings import warn
+        warn('Do not use the returned dict() structure',
+             DeprecationWarning)
+
         return nova_utils.get_server_info(self._nova, self.__vm)
 
     def __get_first_provisioning_floating_ip(self):
@@ -506,12 +474,16 @@ class OpenStackVmInstance(OpenStackComputeObject):
                           playbook
         :param fip_name: the name of the floating IP to use for applying the
                          playbook (default - will take the first)
-        :return: the return value from ansible
         """
-        return ansible_utils.apply_playbook(
+        from warnings import warn
+        warn('This method will be removed in a subsequent release',
+             DeprecationWarning)
+
+        ansible_utils.apply_playbook(
             pb_file_loc, [self.get_floating_ip(fip_name=fip_name).ip],
-            self.get_image_user(), self.keypair_settings.private_filepath,
-            variables, self._os_creds.proxy_settings)
+            self.get_image_user(),
+            ssh_priv_key_file_path=self.keypair_settings.private_filepath,
+            variables=variables, proxy_setting=self._os_creds.proxy_settings)
 
     def get_image_user(self):
         """
@@ -555,7 +527,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
                 STATUS_ACTIVE, block, self.instance_settings.vm_boot_timeout,
                 poll_interval):
             self.__vm = nova_utils.get_server_object_by_id(
-                self._nova, self.__neutron, self.__vm.id)
+                self._nova, self.__neutron, self.__keystone, self.__vm.id,
+                self._os_creds.project_name)
             return True
         return False
 
@@ -620,18 +593,27 @@ class OpenStackVmInstance(OpenStackComputeObject):
             status)
         return status == expected_status_code
 
-    def vm_ssh_active(self, block=False, poll_interval=POLL_INTERVAL):
+    def vm_ssh_active(self, user_override=None, password=None, block=False,
+                      timeout=None, poll_interval=POLL_INTERVAL):
         """
         Returns true when the VM can be accessed via SSH
+        :param user_override: overrides the user with which to create the
+                              connection
+        :param password: overrides the use of a password instead of a private
+                         key with which to create the connection
         :param block: When true, thread will block until active or timeout
                       value in seconds has been exceeded (False)
+        :param timeout: the number of seconds to retry obtaining the connection
+                        and overrides the ssh_connect_timeout member of the
+                        self.instance_settings object
         :param poll_interval: The polling interval
         :return: T/F
         """
         # sleep and wait for VM status change
         logger.info('Checking if VM is active')
 
-        timeout = self.instance_settings.ssh_connect_timeout
+        if not timeout:
+            timeout = self.instance_settings.ssh_connect_timeout
 
         if self.vm_active(block=True):
             if block:
@@ -640,7 +622,8 @@ class OpenStackVmInstance(OpenStackComputeObject):
                 start = time.time() - timeout
 
             while timeout > time.time() - start:
-                status = self.__ssh_active()
+                status = self.__ssh_active(
+                    user_override=user_override, password=password)
                 if status:
                     logger.info('SSH is active for VM instance')
                     return True
@@ -654,13 +637,14 @@ class OpenStackVmInstance(OpenStackComputeObject):
         logger.error('Timeout attempting to connect with VM via SSH')
         return False
 
-    def __ssh_active(self):
+    def __ssh_active(self, user_override=None, password=None):
         """
         Returns True when can create a SSH session else False
         :return: T/F
         """
         if len(self.__floating_ip_dict) > 0:
-            ssh = self.ssh_client()
+            ssh = self.ssh_client(
+                user_override=user_override, password=password)
             if ssh:
                 ssh.close()
                 return True
@@ -680,7 +664,7 @@ class OpenStackVmInstance(OpenStackComputeObject):
         # sleep and wait for VM status change
         logger.info('Checking if cloud-init has completed')
 
-        timeout = CLOUD_INIT_TIMEOUT
+        timeout = self.instance_settings.cloud_init_timeout
 
         if self.vm_active(block=True) and self.vm_ssh_active(block=True):
             if block:
@@ -728,19 +712,32 @@ class OpenStackVmInstance(OpenStackComputeObject):
         else:
             return self.__get_first_provisioning_floating_ip()
 
-    def ssh_client(self, fip_name=None):
+    def ssh_client(self, fip_name=None, user_override=None, password=None):
         """
         Returns an SSH client using the name or the first known floating IP if
         exists, else None
         :param fip_name: the name of the floating IP to return
+        :param user_override: the username to use instead of the default
+        :param password: the password to use instead of the private key
         :return: the SSH client or None
         """
         fip = self.get_floating_ip(fip_name)
+
+        ansible_user = self.get_image_user()
+        if user_override:
+            ansible_user = user_override
+
+        if password:
+            private_key = None
+        else:
+            private_key = self.keypair_settings.private_filepath
+
         if fip:
             return ansible_utils.ssh_client(
                 self.__get_first_provisioning_floating_ip().ip,
-                self.get_image_user(),
-                self.keypair_settings.private_filepath,
+                ansible_user,
+                private_key_filepath=private_key,
+                password=password,
                 proxy_settings=self._os_creds.proxy_settings)
         else:
             FloatingIPAllocationError(
@@ -798,24 +795,32 @@ class OpenStackVmInstance(OpenStackComputeObject):
             self._nova, self.__vm, reboot_type=reboot_type)
 
 
-def generate_creator(os_creds, vm_inst, image_config, keypair_config=None):
+def generate_creator(os_creds, vm_inst, image_config, project_name,
+                     keypair_config=None):
     """
     Initializes an OpenStackVmInstance object
     :param os_creds: the OpenStack credentials
     :param vm_inst: the SNAPS-OO VmInst domain object
     :param image_config: the associated ImageConfig object
+    :param project_name: the associated project ID
     :param keypair_config: the associated KeypairConfig object (optional)
     :return: an initialized OpenStackVmInstance object
     """
-    nova = nova_utils.nova_client(os_creds)
-    neutron = neutron_utils.neutron_client(os_creds)
-    derived_inst_config = settings_utils.create_vm_inst_config(
-        nova, neutron, vm_inst)
-
-    derived_inst_creator = OpenStackVmInstance(
-        os_creds, derived_inst_config, image_config, keypair_config)
-    derived_inst_creator.initialize()
-    return derived_inst_creator
+    session = keystone_utils.keystone_session(os_creds)
+    nova = nova_utils.nova_client(os_creds, session)
+    keystone = keystone_utils.keystone_client(os_creds, session)
+    neutron = neutron_utils.neutron_client(os_creds, session)
+
+    try:
+        derived_inst_config = settings_utils.create_vm_inst_config(
+            nova, keystone, neutron, vm_inst, project_name)
+
+        derived_inst_creator = OpenStackVmInstance(
+            os_creds, derived_inst_config, image_config, keypair_config)
+        derived_inst_creator.initialize()
+        return derived_inst_creator
+    finally:
+        keystone_utils.close_session(session)
 
 
 class VmInstanceSettings(VmInstanceConfig):