Add deployment_handler printout to prepare env
[functest.git] / functest / utils / openstack_utils.py
index 1aac869..3093cb5 100755 (executable)
 
 import os
 import os.path
-import subprocess
+import re
 import sys
 import time
 
+from keystoneauth1 import loading
+from keystoneauth1 import session
 from cinderclient import client as cinderclient
-import functest.utils.functest_logger as ft_logger
-import functest.utils.functest_utils as ft_utils
 from glanceclient import client as glanceclient
-from keystoneclient.v2_0 import client as keystoneclient
-from neutronclient.v2_0 import client as neutronclient
 from novaclient import client as novaclient
+from keystoneclient import client as keystoneclient
+from neutronclient.neutron import client as neutronclient
+
+import functest.utils.functest_logger as ft_logger
+import functest.utils.functest_utils as ft_utils
 
 logger = ft_logger.Logger("openstack_utils").getLogger()
 
+DEFAULT_API_VERSION = '2'
+
 
 # *********************************************
 #   CREDENTIALS
@@ -37,161 +42,203 @@ class MissingEnvVar(Exception):
         return str.format("Please set the mandatory env var: {}", self.var)
 
 
+def is_keystone_v3():
+    keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
+    if (keystone_api_version is None or
+            keystone_api_version == '2'):
+        return False
+    else:
+        return True
+
+
+def get_rc_env_vars():
+    env_vars = ['OS_AUTH_URL', 'OS_USERNAME', 'OS_PASSWORD']
+    if is_keystone_v3():
+        env_vars.extend(['OS_PROJECT_NAME',
+                         'OS_USER_DOMAIN_NAME',
+                         'OS_PROJECT_DOMAIN_NAME'])
+    else:
+        env_vars.extend(['OS_TENANT_NAME'])
+    return env_vars
+
+
 def check_credentials():
     """
     Check if the OpenStack credentials (openrc) are sourced
     """
-    env_vars = ['OS_AUTH_URL', 'OS_USERNAME', 'OS_PASSWORD', 'OS_TENANT_NAME']
+    env_vars = get_rc_env_vars()
     return all(map(lambda v: v in os.environ and os.environ[v], env_vars))
 
 
-def get_credentials(service):
-    """Returns a creds dictionary filled with the following keys:
-    * username
-    * password/api_key (depending on the service)
-    * tenant_name/project_id (depending on the service)
-    * auth_url
-    :param service: a string indicating the name of the service
-                    requesting the credentials.
+def get_env_cred_dict():
+    env_cred_dict = {
+        'OS_USERNAME': 'username',
+        'OS_PASSWORD': 'password',
+        'OS_AUTH_URL': 'auth_url',
+        'OS_TENANT_NAME': 'tenant_name',
+        'OS_USER_DOMAIN_NAME': 'user_domain_name',
+        'OS_PROJECT_DOMAIN_NAME': 'project_domain_name',
+        'OS_PROJECT_NAME': 'project_name',
+        'OS_ENDPOINT_TYPE': 'endpoint_type',
+        'OS_REGION_NAME': 'region_name'
+    }
+    return env_cred_dict
+
+
+def get_credentials(other_creds={}):
+    """Returns a creds dictionary filled with parsed from env
     """
     creds = {}
+    env_vars = get_rc_env_vars()
+    env_cred_dict = get_env_cred_dict()
 
-    keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
-    if (keystone_api_version is None or
-            keystone_api_version == '2'):
-        keystone_v3 = False
-        tenant_env = 'OS_TENANT_NAME'
-        tenant = 'tenant_name'
-    else:
-        keystone_v3 = True
-        tenant_env = 'OS_PROJECT_NAME'
-        tenant = 'project_name'
-
-    # Check that the env vars exists:
-    envvars = ('OS_USERNAME', 'OS_PASSWORD', 'OS_AUTH_URL', tenant_env)
-    for envvar in envvars:
+    for envvar in env_vars:
         if os.getenv(envvar) is None:
             raise MissingEnvVar(envvar)
+        else:
+            creds_key = env_cred_dict.get(envvar)
+            creds.update({creds_key: os.getenv(envvar)})
+
+    if 'tenant' in other_creds.keys():
+        if is_keystone_v3():
+            tenant = 'project_name'
+        else:
+            tenant = 'tenant_name'
+        other_creds[tenant] = other_creds.pop('tenant')
+
+    creds.update(other_creds)
 
-    # Unfortunately, each of the OpenStack client will request slightly
-    # different entries in their credentials dict.
-    if service.lower() in ("nova", "cinder"):
-        password = "api_key"
-        tenant = "project_id"
-    else:
-        password = "password"
-
-    # The most common way to pass these info to the script is to do it through
-    # environment variables.
-    creds.update({
-        "username": os.environ.get("OS_USERNAME"),
-        password: os.environ.get("OS_PASSWORD"),
-        "auth_url": os.environ.get("OS_AUTH_URL"),
-        tenant: os.environ.get(tenant_env)
-    })
-    if keystone_v3:
-        if os.getenv('OS_USER_DOMAIN_NAME') is not None:
-            creds.update({
-                "user_domain_name": os.getenv('OS_USER_DOMAIN_NAME')
-            })
-        if os.getenv('OS_PROJECT_DOMAIN_NAME') is not None:
-            creds.update({
-                "project_domain_name": os.getenv('OS_PROJECT_DOMAIN_NAME')
-            })
-
-    if os.getenv('OS_ENDPOINT_TYPE') is not None:
-        creds.update({
-            "endpoint_type": os.environ.get("OS_ENDPOINT_TYPE")
-        })
-    if os.getenv('OS_REGION_NAME') is not None:
-        creds.update({
-            "region_name": os.environ.get("OS_REGION_NAME")
-        })
-    cacert = os.environ.get("OS_CACERT")
-    if cacert is not None:
-        # each openstack client uses differnt kwargs for this
-        creds.update({"cacert": cacert,
-                      "ca_cert": cacert,
-                      "https_ca_cert": cacert,
-                      "https_cacert": cacert,
-                      "ca_file": cacert})
-        creds.update({"insecure": "True", "https_insecure": "True"})
-        if not os.path.isfile(cacert):
-            logger.info("WARNING: The 'OS_CACERT' environment variable is "
-                        "set to %s but the file does not exist." % cacert)
     return creds
 
 
 def source_credentials(rc_file):
-    pipe = subprocess.Popen(". %s; env" % rc_file, stdout=subprocess.PIPE,
-                            shell=True)
-    output = pipe.communicate()[0]
-    env = dict((line.split("=", 1) for line in output.splitlines()))
-    os.environ.update(env)
-    return env
+    with open(rc_file, "r") as f:
+        for line in f:
+            var = line.rstrip('"\n').replace('export ', '').split("=")
+            # The two next lines should be modified as soon as rc_file
+            # conforms with common rules. Be aware that it could induce
+            # issues if value starts with '
+            key = re.sub(r'^["\' ]*|[ \'"]*$', '', var[0])
+            value = re.sub(r'^["\' ]*|[ \'"]*$', '', "".join(var[1:]))
+            os.environ[key] = value
 
 
 def get_credentials_for_rally():
-    creds = get_credentials("keystone")
-    keystone_api_version = os.getenv('OS_IDENTITY_API_VERSION')
-    if (keystone_api_version is None or
-            keystone_api_version == '2'):
-        admin_keys = ['username', 'tenant_name', 'password']
-    else:
-        admin_keys = ['username', 'password', 'user_domain_name',
-                      'project_name', 'project_domain_name']
+    creds = get_credentials()
+    env_cred_dict = get_env_cred_dict()
+    rally_conf = {"type": "ExistingCloud", "admin": {}}
+    for key in creds:
+        if key == 'auth_url':
+            rally_conf[key] = creds[key]
+        else:
+            rally_conf['admin'][key] = creds[key]
 
     endpoint_types = [('internalURL', 'internal'),
                       ('publicURL', 'public'), ('adminURL', 'admin')]
-    if 'endpoint_type' in creds.keys():
+
+    endpoint_type = os.getenv('OS_ENDPOINT_TYPE')
+    if endpoint_type is not None:
+        cred_key = env_cred_dict.get('OS_ENDPOINT_TYPE')
         for k, v in endpoint_types:
-            if creds['endpoint_type'] == k:
-                creds['endpoint_type'] = v
-    rally_conf = {"type": "ExistingCloud", "admin": {}}
-    for key in creds:
-        if key in admin_keys:
-            rally_conf['admin'][key] = creds[key]
-        else:
-            rally_conf[key] = creds[key]
+            if endpoint_type == k:
+                rally_conf[cred_key] = v
+
+    region_name = os.getenv('OS_REGION_NAME')
+    if region_name is not None:
+        cred_key = env_cred_dict.get('OS_REGION_NAME')
+        rally_conf[cred_key] = region_name
     return rally_conf
 
 
+def get_session_auth(other_creds={}):
+    loader = loading.get_plugin_loader('password')
+    creds = get_credentials(other_creds)
+    auth = loader.load_from_options(**creds)
+    return auth
+
+
+def get_endpoint(service_type, endpoint_type='publicURL'):
+    auth = get_session_auth()
+    return get_session().get_endpoint(auth=auth,
+                                      service_type=service_type,
+                                      endpoint_type=endpoint_type)
+
+
+def get_session(other_creds={}):
+    auth = get_session_auth(other_creds)
+    return session.Session(auth=auth)
+
+
 # *********************************************
 #   CLIENTS
 # *********************************************
-def get_keystone_client():
-    creds_keystone = get_credentials("keystone")
-    return keystoneclient.Client(**creds_keystone)
+def get_keystone_client_version():
+    api_version = os.getenv('OS_IDENTITY_API_VERSION')
+    if api_version is not None:
+        logger.info("OS_IDENTITY_API_VERSION is set in env as '%s'",
+                    api_version)
+        return api_version
+    return DEFAULT_API_VERSION
+
+
+def get_keystone_client(other_creds={}):
+    sess = get_session(other_creds)
+    return keystoneclient.Client(get_keystone_client_version(), session=sess)
+
+
+def get_nova_client_version():
+    api_version = os.getenv('OS_COMPUTE_API_VERSION')
+    if api_version is not None:
+        logger.info("OS_COMPUTE_API_VERSION is set in env as '%s'",
+                    api_version)
+        return api_version
+    return DEFAULT_API_VERSION
 
 
-def get_nova_client():
-    creds_nova = get_credentials("nova")
-    return novaclient.Client('2', **creds_nova)
+def get_nova_client(other_creds={}):
+    sess = get_session(other_creds)
+    return novaclient.Client(get_nova_client_version(), session=sess)
 
 
-def get_cinder_client():
-    creds_cinder = get_credentials("cinder")
-    creds_cinder.update({
-        "service_type": "volume"
-    })
-    return cinderclient.Client('2', **creds_cinder)
+def get_cinder_client_version():
+    api_version = os.getenv('OS_VOLUME_API_VERSION')
+    if api_version is not None:
+        logger.info("OS_VOLUME_API_VERSION is set in env as '%s'",
+                    api_version)
+        return api_version
+    return DEFAULT_API_VERSION
 
 
-def get_neutron_client():
-    creds_neutron = get_credentials("neutron")
-    return neutronclient.Client(**creds_neutron)
+def get_cinder_client(other_creds={}):
+    sess = get_session(other_creds)
+    return cinderclient.Client(get_cinder_client_version(), session=sess)
 
 
-def get_glance_client():
-    keystone_client = get_keystone_client()
-    glance_endpoint_type = 'publicURL'
-    os_endpoint_type = os.getenv('OS_ENDPOINT_TYPE')
-    if os_endpoint_type is not None:
-        glance_endpoint_type = os_endpoint_type
-    glance_endpoint = keystone_client.service_catalog.url_for(
-        service_type='image', endpoint_type=glance_endpoint_type)
-    return glanceclient.Client(1, glance_endpoint,
-                               token=keystone_client.auth_token)
+def get_neutron_client_version():
+    api_version = os.getenv('OS_NETWORK_API_VERSION')
+    if api_version is not None:
+        logger.info("OS_NETWORK_API_VERSION is set in env as '%s'",
+                    api_version)
+        return api_version
+    return DEFAULT_API_VERSION
+
+
+def get_neutron_client(other_creds={}):
+    sess = get_session(other_creds)
+    return neutronclient.Client(get_neutron_client_version(), session=sess)
+
+
+def get_glance_client_version():
+    api_version = os.getenv('OS_IMAGE_API_VERSION')
+    if api_version is not None:
+        logger.info("OS_IMAGE_API_VERSION is set in env as '%s'", api_version)
+        return api_version
+    return DEFAULT_API_VERSION
+
+
+def get_glance_client(other_creds={}):
+    sess = get_session(other_creds)
+    return glanceclient.Client(get_glance_client_version(), session=sess)
 
 
 # *********************************************
@@ -245,6 +292,45 @@ def get_flavor_id_by_ram_range(nova_client, min_ram, max_ram):
     return id
 
 
+def get_aggregates(nova_client):
+    try:
+        aggregates = nova_client.aggregates.list()
+        return aggregates
+    except Exception, e:
+        logger.error("Error [get_aggregates(nova_client)]: %s" % e)
+        return None
+
+
+def get_aggregate_id(nova_client, aggregate_name):
+    try:
+        aggregates = get_aggregates(nova_client)
+        _id = [ag.id for ag in aggregates if ag.name == aggregate_name][0]
+        return _id
+    except Exception, e:
+        logger.error("Error [get_aggregate_id(nova_client, %s)]:"
+                     " %s" % (aggregate_name, e))
+        return None
+
+
+def get_availability_zones(nova_client):
+    try:
+        availability_zones = nova_client.availability_zones.list()
+        return availability_zones
+    except Exception, e:
+        logger.error("Error [get_availability_zones(nova_client)]: %s" % e)
+        return None
+
+
+def get_availability_zone_names(nova_client):
+    try:
+        az_names = [az.zoneName for az in get_availability_zones(nova_client)]
+        return az_names
+    except Exception, e:
+        logger.error("Error [get_availability_zone_names(nova_client)]:"
+                     " %s" % e)
+        return None
+
+
 def create_flavor(nova_client, flavor_name, ram, disk, vcpus, public=True):
     try:
         flavor = nova_client.flavors.create(
@@ -308,6 +394,40 @@ def get_hypervisors(nova_client):
         return None
 
 
+def create_aggregate(nova_client, aggregate_name, av_zone):
+    try:
+        nova_client.aggregates.create(aggregate_name, av_zone)
+        return True
+    except Exception, e:
+        logger.error("Error [create_aggregate(nova_client, %s, %s)]: %s"
+                     % (aggregate_name, av_zone, e))
+        return None
+
+
+def add_host_to_aggregate(nova_client, aggregate_name, compute_host):
+    try:
+        aggregate_id = get_aggregate_id(nova_client, aggregate_name)
+        nova_client.aggregates.add_host(aggregate_id, compute_host)
+        return True
+    except Exception, e:
+        logger.error("Error [add_host_to_aggregate(nova_client, %s, %s)]: %s"
+                     % (aggregate_name, compute_host, e))
+        return None
+
+
+def create_aggregate_with_host(
+        nova_client, aggregate_name, av_zone, compute_host):
+    try:
+        create_aggregate(nova_client, aggregate_name, av_zone)
+        add_host_to_aggregate(nova_client, aggregate_name, compute_host)
+        return True
+    except Exception, e:
+        logger.error("Error [create_aggregate_with_host("
+                     "nova_client, %s, %s, %s)]: %s"
+                     % (aggregate_name, av_zone, compute_host, e))
+        return None
+
+
 def create_instance(flavor_name,
                     image_id,
                     network_id,
@@ -400,13 +520,13 @@ def create_floating_ip(neutron_client):
     return {'fip_addr': fip_addr, 'fip_id': fip_id}
 
 
-def add_floating_ip(nova_client, server_id, floatingip_id):
+def add_floating_ip(nova_client, server_id, floatingip_addr):
     try:
-        nova_client.servers.add_floating_ip(server_id, floatingip_id)
+        nova_client.servers.add_floating_ip(server_id, floatingip_addr)
         return True
     except Exception, e:
         logger.error("Error [add_floating_ip(nova_client, '%s', '%s')]: %s"
-                     % (server_id, floatingip_id, e))
+                     % (server_id, floatingip_addr, e))
         return False
 
 
@@ -430,6 +550,36 @@ def delete_floating_ip(nova_client, floatingip_id):
         return False
 
 
+def remove_host_from_aggregate(nova_client, aggregate_name, compute_host):
+    try:
+        aggregate_id = get_aggregate_id(nova_client, aggregate_name)
+        nova_client.aggregates.remove_host(aggregate_id, compute_host)
+        return True
+    except Exception, e:
+        logger.error("Error [remove_host_from_aggregate(nova_client, %s, %s)]:"
+                     " %s" % (aggregate_name, compute_host, e))
+        return False
+
+
+def remove_hosts_from_aggregate(nova_client, aggregate_name):
+    aggregate_id = get_aggregate_id(nova_client, aggregate_name)
+    hosts = nova_client.aggregates.get(aggregate_id).hosts
+    assert(
+        all(remove_host_from_aggregate(nova_client, aggregate_name, host)
+            for host in hosts))
+
+
+def delete_aggregate(nova_client, aggregate_name):
+    try:
+        remove_hosts_from_aggregate(nova_client, aggregate_name)
+        nova_client.aggregates.delete(aggregate_name)
+        return True
+    except Exception, e:
+        logger.error("Error [delete_aggregate(nova_client, %s)]: %s"
+                     % (aggregate_name, e))
+        return False
+
+
 # *********************************************
 #   NEUTRON
 # *********************************************
@@ -967,38 +1117,29 @@ def get_image_id(glance_client, image_name):
 
 
 def create_glance_image(glance_client, image_name, file_path, disk="qcow2",
-                        container="bare", public=True):
+                        container="bare", public="public"):
     if not os.path.isfile(file_path):
         logger.error("Error: file %s does not exist." % file_path)
         return None
     try:
         image_id = get_image_id(glance_client, image_name)
         if image_id != '':
-            if logger:
-                logger.info("Image %s already exists." % image_name)
+            logger.info("Image %s already exists." % image_name)
         else:
-            if logger:
-                logger.info("Creating image '%s' from '%s'..." % (image_name,
-                                                                  file_path))
-            try:
-                properties = ft_utils.get_functest_config(
-                    'general.image_properties')
-            except ValueError:
-                # image properties are not configured
-                # therefore don't add any properties
-                properties = {}
-            with open(file_path) as fimage:
-                image = glance_client.images.create(name=image_name,
-                                                    is_public=public,
-                                                    disk_format=disk,
-                                                    container_format=container,
-                                                    properties=properties,
-                                                    data=fimage)
+            logger.info("Creating image '%s' from '%s'..." % (image_name,
+                                                              file_path))
+
+            image = glance_client.images.create(name=image_name,
+                                                visibility=public,
+                                                disk_format=disk,
+                                                container_format=container)
             image_id = image.id
+            with open(file_path) as image_data:
+                glance_client.images.upload(image_id, image_data)
         return image_id
     except Exception, e:
         logger.error("Error [create_glance_image(glance_client, '%s', '%s', "
-                     "'%s')]: %s" % (image_name, file_path, str(public), e))
+                     "'%s')]: %s" % (image_name, file_path, public, e))
         return None
 
 
@@ -1115,7 +1256,10 @@ def delete_volume_type(cinder_client, volume_type):
 # *********************************************
 def get_tenants(keystone_client):
     try:
-        tenants = keystone_client.tenants.list()
+        if is_keystone_v3():
+            tenants = keystone_client.projects.list()
+        else:
+            tenants = keystone_client.tenants.list()
         return tenants
     except Exception, e:
         logger.error("Error [get_tenants(keystone_client)]: %s" % e)
@@ -1132,7 +1276,7 @@ def get_users(keystone_client):
 
 
 def get_tenant_id(keystone_client, tenant_name):
-    tenants = keystone_client.tenants.list()
+    tenants = get_tenants(keystone_client)
     id = ''
     for t in tenants:
         if t.name == tenant_name:
@@ -1142,7 +1286,7 @@ def get_tenant_id(keystone_client, tenant_name):
 
 
 def get_user_id(keystone_client, user_name):
-    users = keystone_client.users.list()
+    users = get_users(keystone_client)
     id = ''
     for u in users:
         if u.name == user_name:
@@ -1163,9 +1307,16 @@ def get_role_id(keystone_client, role_name):
 
 def create_tenant(keystone_client, tenant_name, tenant_description):
     try:
-        tenant = keystone_client.tenants.create(tenant_name,
-                                                tenant_description,
-                                                enabled=True)
+        if is_keystone_v3():
+            tenant = keystone_client.projects.create(
+                name=tenant_name,
+                description=tenant_description,
+                domain="default",
+                enabled=True)
+        else:
+            tenant = keystone_client.tenants.create(tenant_name,
+                                                    tenant_description,
+                                                    enabled=True)
         return tenant.id
     except Exception, e:
         logger.error("Error [create_tenant(keystone_client, '%s', '%s')]: %s"
@@ -1176,9 +1327,18 @@ def create_tenant(keystone_client, tenant_name, tenant_description):
 def create_user(keystone_client, user_name, user_password,
                 user_email, tenant_id):
     try:
-        user = keystone_client.users.create(user_name, user_password,
-                                            user_email, tenant_id,
-                                            enabled=True)
+        if is_keystone_v3():
+            user = keystone_client.users.create(name=user_name,
+                                                password=user_password,
+                                                email=user_email,
+                                                project_id=tenant_id,
+                                                enabled=True)
+        else:
+            user = keystone_client.users.create(user_name,
+                                                user_password,
+                                                user_email,
+                                                tenant_id,
+                                                enabled=True)
         return user.id
     except Exception, e:
         logger.error("Error [create_user(keystone_client, '%s', '%s', '%s'"
@@ -1189,7 +1349,12 @@ def create_user(keystone_client, user_name, user_password,
 
 def add_role_user(keystone_client, user_id, role_id, tenant_id):
     try:
-        keystone_client.roles.add_user_role(user_id, role_id, tenant_id)
+        if is_keystone_v3():
+            keystone_client.roles.grant(role=role_id,
+                                        user=user_id,
+                                        project=tenant_id)
+        else:
+            keystone_client.roles.add_user_role(user_id, role_id, tenant_id)
         return True
     except Exception, e:
         logger.error("Error [add_role_user(keystone_client, '%s', '%s'"
@@ -1199,7 +1364,10 @@ def add_role_user(keystone_client, user_id, role_id, tenant_id):
 
 def delete_tenant(keystone_client, tenant_id):
     try:
-        keystone_client.tenants.delete(tenant_id)
+        if is_keystone_v3():
+            keystone_client.projects.delete(tenant_id)
+        else:
+            keystone_client.tenants.delete(tenant_id)
         return True
     except Exception, e:
         logger.error("Error [delete_tenant(keystone_client, '%s')]: %s"