Merge "Adding scale up feature to prox ACL SRIOV OvS-DPDK."
[yardstick.git] / yardstick / common / openstack_utils.py
index c2ef267..5410613 100644 (file)
@@ -7,20 +7,20 @@
 # http://www.apache.org/licenses/LICENSE-2.0
 ##############################################################################
 
-import os
-import time
-import sys
+import copy
 import logging
+import os
 
+from cinderclient import client as cinderclient
+from novaclient import client as novaclient
+from glanceclient import client as glanceclient
 from keystoneauth1 import loading
 from keystoneauth1 import session
+from neutronclient.neutron import client as neutronclient
 import shade
 from shade import exc
 
-from cinderclient import client as cinderclient
-from novaclient import client as novaclient
-from glanceclient import client as glanceclient
-from neutronclient.neutron import client as neutronclient
+from yardstick.common import constants
 
 
 log = logging.getLogger(__name__)
@@ -156,99 +156,48 @@ def get_glance_client():    # pragma: no cover
     return glanceclient.Client(get_glance_client_version(), session=sess)
 
 
-def get_shade_client():
-    return shade.openstack_cloud()
-
-
-# *********************************************
-#   NOVA
-# *********************************************
-def get_aggregates(nova_client):    # pragma: no cover
-    try:
-        return nova_client.aggregates.list()
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [get_aggregates(nova_client)]")
-
-
-def get_availability_zones(nova_client):    # pragma: no cover
-    try:
-        return nova_client.availability_zones.list()
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [get_availability_zones(nova_client)]")
-
-
-def get_availability_zone_names(nova_client):   # pragma: no cover
-    try:
-        return [az.zoneName for az in get_availability_zones(nova_client)]
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [get_availability_zone_names(nova_client)]")
-
-
-def create_aggregate(nova_client, aggregate_name, av_zone):  # pragma: no cover
-    try:
-        nova_client.aggregates.create(aggregate_name, av_zone)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [create_aggregate(nova_client, %s, %s)]",
-                      aggregate_name, av_zone)
-        return False
-    else:
-        return True
+def get_shade_client(**os_cloud_config):
+    """Get Shade OpenStack cloud client
 
+    By default, the input parameters given to "shade.openstack_cloud" method
+    are stored in "constants.OS_CLOUD_DEFAULT_CONFIG". The input parameters
+    passed in this function, "os_cloud_config", will overwrite the default
+    ones.
 
-def get_aggregate_id(nova_client, aggregate_name):      # pragma: no cover
-    try:
-        aggregates = get_aggregates(nova_client)
-        _id = next((ag.id for ag in aggregates if ag.name == aggregate_name))
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [get_aggregate_id(nova_client, %s)]",
-                      aggregate_name)
-    else:
-        return _id
-
-
-def add_host_to_aggregate(nova_client, aggregate_name,
-                          compute_host):    # pragma: no cover
-    try:
-        aggregate_id = get_aggregate_id(nova_client, aggregate_name)
-        nova_client.aggregates.add_host(aggregate_id, compute_host)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [add_host_to_aggregate(nova_client, %s, %s)]",
-                      aggregate_name, compute_host)
-        return False
-    else:
-        return True
+    :param os_cloud_config: (kwargs) input arguments for
+                            "shade.openstack_cloud" method.
+    :return: ``shade.OpenStackCloud`` object.
+    """
+    params = copy.deepcopy(constants.OS_CLOUD_DEFAULT_CONFIG)
+    params.update(os_cloud_config)
+    return shade.openstack_cloud(**params)
 
+def get_shade_operator_client(**os_cloud_config):
+    """Get Shade Operator cloud client
 
-def create_aggregate_with_host(nova_client, aggregate_name, av_zone,
-                               compute_host):    # pragma: no cover
-    try:
-        create_aggregate(nova_client, aggregate_name, av_zone)
-        add_host_to_aggregate(nova_client, aggregate_name, compute_host)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [create_aggregate_with_host("
-                      "nova_client, %s, %s, %s)]",
-                      aggregate_name, av_zone, compute_host)
-        return False
-    else:
-        return True
+    :return: ``shade.OperatorCloud`` object.
+    """
+    params = copy.deepcopy(constants.OS_CLOUD_DEFAULT_CONFIG)
+    params.update(os_cloud_config)
+    return shade.operator_cloud(**params)
 
 
-def create_keypair(name, key_path=None):    # pragma: no cover
-    try:
-        with open(key_path) as fpubkey:
-            keypair = get_nova_client().keypairs.create(
-                name=name, public_key=fpubkey.read())
-            return keypair
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [create_keypair(nova_client)]")
+# *********************************************
+#   NOVA
+# *********************************************
+def create_keypair(shade_client, name, public_key=None):
+    """Create a new keypair.
 
+    :param name: Name of the keypair being created.
+    :param public_key: Public key for the new keypair.
 
-def create_instance(json_body):    # pragma: no cover
+    :return: Created keypair.
+    """
     try:
-        return get_nova_client().servers.create(**json_body)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error create instance failed")
-        return None
+        return shade_client.create_keypair(name, public_key=public_key)
+    except exc.OpenStackCloudException as o_exc:
+        log.error("Error [create_keypair(shade_client)]. "
+                  "Exception message, '%s'", o_exc.orig_message)
 
 
 def create_instance_and_wait_for_active(shade_client, name, image,
@@ -338,70 +287,83 @@ def create_instance_and_wait_for_active(shade_client, name, image,
                   "Exception message, '%s'", o_exc.orig_message)
 
 
-def attach_server_volume(server_id, volume_id,
-                         device=None):    # pragma: no cover
-    try:
-        get_nova_client().volumes.create_server_volume(server_id,
-                                                       volume_id, device)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [attach_server_volume(nova_client, '%s', '%s')]",
-                      server_id, volume_id)
-        return False
-    else:
-        return True
+def attach_volume_to_server(shade_client, server_name_or_id, volume_name_or_id,
+                            device=None, wait=True, timeout=None):
+    """Attach a volume to a server.
 
+    This will attach a volume, described by the passed in volume
+    dict, to the server described by the passed in server dict on the named
+    device on the server.
 
-def delete_instance(nova_client, instance_id):      # pragma: no cover
-    try:
-        nova_client.servers.force_delete(instance_id)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [delete_instance(nova_client, '%s')]",
-                      instance_id)
-        return False
-    else:
-        return True
+    If the volume is already attached to the server, or generally not
+    available, then an exception is raised. To re-attach to a server,
+    but under a different device, the user must detach it first.
 
+    :param server_name_or_id:(string) The server name or id to attach to.
+    :param volume_name_or_id:(string) The volume name or id to attach.
+    :param device:(string) The device name where the volume will attach.
+    :param wait:(bool) If true, waits for volume to be attached.
+    :param timeout: Seconds to wait for volume attachment. None is forever.
 
-def remove_host_from_aggregate(nova_client, aggregate_name,
-                               compute_host):  # pragma: no cover
+    :returns: True if attached successful, False otherwise.
+    """
     try:
-        aggregate_id = get_aggregate_id(nova_client, aggregate_name)
-        nova_client.aggregates.remove_host(aggregate_id, compute_host)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error remove_host_from_aggregate(nova_client, %s, %s)",
-                      aggregate_name, compute_host)
-        return False
-    else:
+        server = shade_client.get_server(name_or_id=server_name_or_id)
+        volume = shade_client.get_volume(volume_name_or_id)
+        shade_client.attach_volume(
+            server, volume, device=device, wait=wait, timeout=timeout)
         return True
+    except exc.OpenStackCloudException as o_exc:
+        log.error("Error [attach_volume_to_server(shade_client)]. "
+                  "Exception message: %s", o_exc.orig_message)
+        return False
 
 
-def remove_hosts_from_aggregate(nova_client,
-                                aggregate_name):   # pragma: no cover
-    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_instance(shade_client, name_or_id, wait=False, timeout=180,
+                    delete_ips=False, delete_ip_retry=1):
+    """Delete a server instance.
 
-def delete_aggregate(nova_client, aggregate_name):  # pragma: no cover
+    :param name_or_id: name or ID of the server to delete
+    :param wait:(bool) If true, waits for server to be deleted.
+    :param timeout:(int) Seconds to wait for server deletion.
+    :param delete_ips:(bool) If true, deletes any floating IPs associated with
+                      the instance.
+    :param delete_ip_retry:(int) Number of times to retry deleting
+                           any floating ips, should the first try be
+                           unsuccessful.
+    :returns: True if delete succeeded, False otherwise.
+    """
     try:
-        remove_hosts_from_aggregate(nova_client, aggregate_name)
-        nova_client.aggregates.delete(aggregate_name)
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [delete_aggregate(nova_client, %s)]",
-                      aggregate_name)
+        return shade_client.delete_server(
+            name_or_id, wait=wait, timeout=timeout, delete_ips=delete_ips,
+            delete_ip_retry=delete_ip_retry)
+    except exc.OpenStackCloudException as o_exc:
+        log.error("Error [delete_instance(shade_client, '%s')]. "
+                  "Exception message: %s", name_or_id,
+                  o_exc.orig_message)
         return False
-    else:
-        return True
 
 
-def get_server_by_name(name):   # pragma: no cover
+def get_server(shade_client, name_or_id=None, filters=None, detailed=False,
+               bare=False):
+    """Get a server by name or ID.
+
+    :param name_or_id: Name or ID of the server.
+    :param filters:(dict) A dictionary of meta data to use for further
+                   filtering.
+    :param detailed:(bool) Whether or not to add detailed additional
+                    information.
+    :param bare:(bool) Whether to skip adding any additional information to the
+                server record.
+
+    :returns: A server ``munch.Munch`` or None if no matching server is found.
+    """
     try:
-        return get_nova_client().servers.list(search_opts={'name': name})[0]
-    except IndexError:
-        log.exception('Failed to get nova client')
-        raise
+        return shade_client.get_server(name_or_id=name_or_id, filters=filters,
+                                       detailed=detailed, bare=bare)
+    except exc.OpenStackCloudException as o_exc:
+        log.error("Error [get_server(shade_client, '%s')]. "
+                  "Exception message: %s", name_or_id, o_exc.orig_message)
 
 
 def create_flavor(name, ram, vcpus, disk, **kwargs):   # pragma: no cover
@@ -414,14 +376,6 @@ def create_flavor(name, ram, vcpus, disk, **kwargs):   # pragma: no cover
         return None
 
 
-def get_image_by_name(name):    # pragma: no cover
-    images = get_nova_client().images.list()
-    try:
-        return next((a for a in images if a.name == name))
-    except StopIteration:
-        log.exception('No image matched')
-
-
 def get_flavor_id(nova_client, flavor_name):    # pragma: no cover
     flavors = nova_client.flavors.list(detailed=True)
     flavor_id = ''
@@ -432,27 +386,22 @@ def get_flavor_id(nova_client, flavor_name):    # pragma: no cover
     return flavor_id
 
 
-def get_flavor_by_name(name):   # pragma: no cover
-    flavors = get_nova_client().flavors.list()
-    try:
-        return next((a for a in flavors if a.name == name))
-    except StopIteration:
-        log.exception('No flavor matched')
-
-
-def check_status(status, name, iterations, interval):   # pragma: no cover
-    for _ in range(iterations):
-        try:
-            server = get_server_by_name(name)
-        except IndexError:
-            log.error('Cannot found %s server', name)
-            raise
+def get_flavor(shade_client, name_or_id, filters=None, get_extra=True):
+    """Get a flavor by name or ID.
 
-        if server.status == status:
-            return True
+    :param name_or_id: Name or ID of the flavor.
+    :param filters: A dictionary of meta data to use for further filtering.
+    :param get_extra: Whether or not the list_flavors call should get the extra
+    flavor specs.
 
-        time.sleep(interval)
-    return False
+    :returns: A flavor ``munch.Munch`` or None if no matching flavor is found.
+    """
+    try:
+        return shade_client.get_flavor(name_or_id, filters=filters,
+                                       get_extra=get_extra)
+    except exc.OpenStackCloudException as o_exc:
+        log.error("Error [get_flavor(shade_client, '%s')]. "
+                  "Exception message: %s", name_or_id, o_exc.orig_message)
 
 
 def delete_flavor(flavor_id):    # pragma: no cover
@@ -465,12 +414,18 @@ def delete_flavor(flavor_id):    # pragma: no cover
         return True
 
 
-def delete_keypair(nova_client, key):     # pragma: no cover
+def delete_keypair(shade_client, name):
+    """Delete a keypair.
+
+    :param name: Name of the keypair to delete.
+
+    :returns: True if delete succeeded, False otherwise.
+    """
     try:
-        nova_client.keypairs.delete(key=key)
-        return True
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [delete_keypair(nova_client)]")
+        return shade_client.delete_keypair(name)
+    except exc.OpenStackCloudException as o_exc:
+        log.error("Error [delete_neutron_router(shade_client, '%s')]. "
+                  "Exception message: %s", name, o_exc.orig_message)
         return False
 
 
@@ -778,48 +733,75 @@ def create_security_group_full(shade_client, sg_name,
 # *********************************************
 #   GLANCE
 # *********************************************
-def get_image_id(glance_client, image_name):    # pragma: no cover
-    images = glance_client.images.list()
-    return next((i.id for i in images if i.name == image_name), None)
-
-
-def create_image(glance_client, image_name, file_path, disk_format,
-                 container_format, min_disk, min_ram, protected, tag,
-                 public, **kwargs):    # pragma: no cover
-    if not os.path.isfile(file_path):
-        log.error("Error: file %s does not exist.", file_path)
-        return None
+def create_image(shade_client, name, filename=None, container='images',
+                 md5=None, sha256=None, disk_format=None,
+                 container_format=None, disable_vendor_agent=True,
+                 wait=False, timeout=3600, allow_duplicates=False, meta=None,
+                 volume=None, **kwargs):
+    """Upload an image.
+
+    :param name:(str) Name of the image to create. If it is a pathname of an
+                image, the name will be constructed from the extensionless
+                basename of the path.
+    :param filename:(str) The path to the file to upload, if needed.
+    :param container:(str) Name of the container in swift where images should
+                     be uploaded for import if the cloud requires such a thing.
+    :param md5:(str) md5 sum of the image file. If not given, an md5 will
+            be calculated.
+    :param sha256:(str) sha256 sum of the image file. If not given, an md5
+                  will be calculated.
+    :param disk_format:(str) The disk format the image is in.
+    :param container_format:(str) The container format the image is in.
+    :param disable_vendor_agent:(bool) Whether or not to append metadata
+                                flags to the image to inform the cloud in
+                                question to not expect a vendor agent to be running.
+    :param wait:(bool) If true, waits for image to be created.
+    :param timeout:(str) Seconds to wait for image creation.
+    :param allow_duplicates:(bool) If true, skips checks that enforce unique
+                            image name.
+    :param meta:(dict) A dict of key/value pairs to use for metadata that
+                bypasses automatic type conversion.
+    :param volume:(str) Name or ID or volume object of a volume to create an
+                  image from.
+    Additional kwargs will be passed to the image creation as additional
+    metadata for the image and will have all values converted to string
+    except for min_disk, min_ram, size and virtual_size which will be
+    converted to int.
+    If you are sure you have all of your data types correct or have an
+    advanced need to be explicit, use meta. If you are just a normal
+    consumer, using kwargs is likely the right choice.
+    If a value is in meta and kwargs, meta wins.
+    :returns: Image id
+    """
     try:
-        image_id = get_image_id(glance_client, image_name)
+        image_id = shade_client.get_image_id(name)
         if image_id is not None:
-            log.info("Image %s already exists.", image_name)
-        else:
-            log.info("Creating image '%s' from '%s'...", image_name, file_path)
-
-            image = glance_client.images.create(
-                name=image_name, visibility=public, disk_format=disk_format,
-                container_format=container_format, min_disk=min_disk,
-                min_ram=min_ram, tags=tag, protected=protected, **kwargs)
-            image_id = image.id
-            with open(file_path) as image_data:
-                glance_client.images.upload(image_id, image_data)
+            log.info("Image %s already exists.", name)
+            return image_id
+        log.info("Creating image '%s'", name)
+        image = shade_client.create_image(
+            name, filename=filename, container=container, md5=md5, sha256=sha256,
+            disk_format=disk_format, container_format=container_format,
+            disable_vendor_agent=disable_vendor_agent, wait=wait, timeout=timeout,
+            allow_duplicates=allow_duplicates, meta=meta, volume=volume, **kwargs)
+        image_id = image["id"]
         return image_id
-    except Exception:  # pylint: disable=broad-except
-        log.error(
-            "Error [create_glance_image(glance_client, '%s', '%s', '%s')]",
-            image_name, file_path, public)
-        return None
+    except exc.OpenStackCloudException as op_exc:
+        log.error("Failed to create_image(shade_client). "
+                  "Exception message: %s", op_exc.orig_message)
 
 
-def delete_image(glance_client, image_id):    # pragma: no cover
+def delete_image(shade_client, name_or_id, wait=False, timeout=3600,
+                 delete_objects=True):
     try:
-        glance_client.images.delete(image_id)
+        return shade_client.delete_image(name_or_id, wait=wait,
+                                         timeout=timeout,
+                                         delete_objects=delete_objects)
 
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [delete_flavor(glance_client, %s)]", image_id)
+    except exc.OpenStackCloudException as op_exc:
+        log.error("Failed to delete_image(shade_client). "
+                  "Exception message: %s", op_exc.orig_message)
         return False
-    else:
-        return True
 
 
 def list_images(shade_client=None):
@@ -837,54 +819,79 @@ def list_images(shade_client=None):
 # *********************************************
 #   CINDER
 # *********************************************
-def get_volume_id(volume_name):    # pragma: no cover
-    volumes = get_cinder_client().volumes.list()
-    return next((v.id for v in volumes if v.name == volume_name), None)
-
-
-def create_volume(cinder_client, volume_name, volume_size,
-                  volume_image=False):    # pragma: no cover
-    try:
-        if volume_image:
-            volume = cinder_client.volumes.create(name=volume_name,
-                                                  size=volume_size,
-                                                  imageRef=volume_image)
-        else:
-            volume = cinder_client.volumes.create(name=volume_name,
-                                                  size=volume_size)
-        return volume
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [create_volume(cinder_client, %s)]",
-                      (volume_name, volume_size))
-        return None
+def get_volume_id(shade_client, volume_name):
+    return shade_client.get_volume_id(volume_name)
 
 
-def delete_volume(cinder_client, volume_id,
-                  forced=False):      # pragma: no cover
-    try:
-        if forced:
-            try:
-                cinder_client.volumes.detach(volume_id)
-            except Exception:  # pylint: disable=broad-except
-                log.error(sys.exc_info()[0])
-            cinder_client.volumes.force_delete(volume_id)
-        else:
-            while True:
-                volume = get_cinder_client().volumes.get(volume_id)
-                if volume.status.lower() == 'available':
-                    break
-            cinder_client.volumes.delete(volume_id)
-        return True
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [delete_volume(cinder_client, '%s')]", volume_id)
+def get_volume(shade_client, name_or_id, filters=None):
+    """Get a volume by name or ID.
+
+    :param name_or_id: Name or ID of the volume.
+    :param filters: A dictionary of meta data to use for further filtering.
+
+    :returns: A volume ``munch.Munch`` or None if no matching volume is found.
+    """
+    return shade_client.get_volume(name_or_id, filters=filters)
+
+
+def create_volume(shade_client, size, wait=True, timeout=None,
+                  image=None, **kwargs):
+    """Create a volume.
+
+    :param size: Size, in GB of the volume to create.
+    :param name: (optional) Name for the volume.
+    :param description: (optional) Name for the volume.
+    :param wait: If true, waits for volume to be created.
+    :param timeout: Seconds to wait for volume creation. None is forever.
+    :param image: (optional) Image name, ID or object from which to create
+                  the volume.
+
+    :returns: The created volume object.
+
+    """
+    try:
+        return shade_client.create_volume(size, wait=wait, timeout=timeout,
+                                          image=image, **kwargs)
+    except (exc.OpenStackCloudException, exc.OpenStackCloudTimeout) as op_exc:
+        log.error("Failed to create_volume(shade_client). "
+                  "Exception message: %s", op_exc.orig_message)
+
+
+def delete_volume(shade_client, name_or_id=None, wait=True, timeout=None):
+    """Delete a volume.
+
+    :param name_or_id:(string) Name or unique ID of the volume.
+    :param wait:(bool) If true, waits for volume to be deleted.
+    :param timeout:(string) Seconds to wait for volume deletion. None is forever.
+
+    :return:  True on success, False otherwise.
+    """
+    try:
+        return shade_client.delete_volume(name_or_id=name_or_id,
+                                          wait=wait, timeout=timeout)
+    except (exc.OpenStackCloudException, exc.OpenStackCloudTimeout) as o_exc:
+        log.error("Error [delete_volume(shade_client,'%s')]. "
+                  "Exception message: %s", name_or_id, o_exc.orig_message)
         return False
 
 
-def detach_volume(server_id, volume_id):      # pragma: no cover
+def detach_volume(shade_client, server_name_or_id, volume_name_or_id,
+                  wait=True, timeout=None):
+    """Detach a volume from a server.
+
+    :param server_name_or_id: The server name or id to detach from.
+    :param volume_name_or_id: The volume name or id to detach.
+    :param wait: If true, waits for volume to be detached.
+    :param timeout: Seconds to wait for volume detachment. None is forever.
+
+    :return: True on success.
+    """
     try:
-        get_nova_client().volumes.delete_server_volume(server_id, volume_id)
+        volume = shade_client.get_volume(volume_name_or_id)
+        server = get_server(shade_client, name_or_id=server_name_or_id)
+        shade_client.detach_volume(server, volume, wait=wait, timeout=timeout)
         return True
-    except Exception:  # pylint: disable=broad-except
-        log.exception("Error [detach_server_volume(nova_client, '%s', '%s')]",
-                      server_id, volume_id)
+    except (exc.OpenStackCloudException, exc.OpenStackCloudTimeout) as o_exc:
+        log.error("Error [detach_volume(shade_client)]. "
+                  "Exception message: %s", o_exc.orig_message)
         return False