Merge "Added images support and existing config support"
diff --git a/tools/tempest_auto_config.py b/tools/tempest_auto_config.py
index aef6a1f..fe9f5af 100644
--- a/tools/tempest_auto_config.py
+++ b/tools/tempest_auto_config.py
@@ -14,38 +14,50 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
+#
+# This script aims to configure an initial Openstack environment with all the
+# necessary configurations for tempest's run using nothing but Openstack's
+# native API.
+# That includes, creating users, tenants, registering images (cirros),
+# configuring neutron and so on.
+#
+# ASSUMPTION: this script is run by an admin user as it is meant to configure
+# the Openstack environment prior to actual use.
 
 # Config
 import ConfigParser
 import os
+import tarfile
+import urllib2
 
 # Default client libs
+import glanceclient as glance_client
 import keystoneclient.v2_0.client as keystone_client
 
 # Import Openstack exceptions
+import glanceclient.exc as glance_exception
 import keystoneclient.exceptions as keystone_exception
 
 
-DEFAULT_CONFIG_DIR = "%s/etc" % os.path.abspath(os.path.pardir)
-DEFAULT_CONFIG_FILE = "tempest.conf"
-DEFAULT_CONFIG_SAMPLE = "tempest.conf.sample"
+TEMPEST_TEMP_DIR = os.getenv("TEMPEST_TEMP_DIR", "/tmp").rstrip('/')
+TEMPEST_ROOT_DIR = os.getenv("TEMPEST_ROOT_DIR", os.getenv("HOME")).rstrip('/')
 
 # Environment variables override defaults
-TEMPEST_CONFIG_DIR = os.environ.get('TEMPEST_CONFIG_DIR') or DEFAULT_CONFIG_DIR
-TEMPEST_CONFIG = os.environ.get('TEMPEST_CONFIG') or "%s/%s" % \
-    (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_FILE)
-TEMPEST_CONFIG_SAMPLE = os.environ.get('TEMPEST_CONFIG_SAMPLE') or "%s/%s" % \
-    (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_SAMPLE)
-
-# Admin credentials
-OS_USERNAME = os.environ.get('OS_USERNAME')
-OS_PASSWORD = os.environ.get('OS_PASSWORD')
-OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME')
-OS_AUTH_URL = os.environ.get('OS_AUTH_URL')
-
+TEMPEST_CONFIG_DIR = os.getenv("TEMPEST_CONFIG_DIR",
+                               "%s%s" % (TEMPEST_ROOT_DIR, "/etc")).rstrip('/')
+TEMPEST_CONFIG_FILE = os.getenv("TEMPEST_CONFIG_FILE",
+                                "%s%s" % (TEMPEST_CONFIG_DIR, "/tempest.conf"))
+TEMPEST_CONFIG_SAMPLE = os.getenv("TEMPEST_CONFIG_SAMPLE",
+                                  "%s%s" % (TEMPEST_CONFIG_DIR,
+                                            "/tempest.conf.sample"))
 # Image references
-IMAGE_ID = os.environ.get('IMAGE_ID')
-IMAGE_ID_ALT = os.environ.get('IMAGE_ID_ALT')
+IMAGE_DOWNLOAD_CHUNK_SIZE = 8 * 1024
+IMAGE_UEC_SOURCE_URL = os.getenv("IMAGE_UEC_SOURCE_URL",
+                                 "http://download.cirros-cloud.net/0.3.1/"
+                                 "cirros-0.3.1-x86_64-uec.tar.gz")
+TEMPEST_IMAGE_ID = os.getenv('IMAGE_ID')
+TEMPEST_IMAGE_ID_ALT = os.getenv('IMAGE_ID_ALT')
+IMAGE_STATUS_ACTIVE = 'active'
 
 
 class ClientManager(object):
@@ -76,26 +88,52 @@
 
         return self.identity_client
 
+    def get_image_client(self, version="1", *args, **kwargs):
+        """
+        This method returns Openstack glance python client
+        :param version: a string representing the version of the glance client
+        to use.
+        :param string endpoint: A user-supplied endpoint URL for the glance
+                            service.
+        :param string token: Token for authentication.
+        :param integer timeout: Allows customization of the timeout for client
+                                http requests. (optional)
+        :return: a Client object representing the glance client
+        """
+        if not self.image_client:
+            self.image_client = glance_client.Client(version, *args, **kwargs)
 
-def getTempestConfigSample():
+        return self.image_client
+
+
+def get_tempest_config(path_to_config):
     """
     Gets the tempest configuration file as a ConfigParser object
-    :return: the tempest configuration file
+    :param path_to_config: path to the config file
+    :return: a ConfigParser object representing the tempest configuration file
     """
     # get the sample config file from the sample
-    config_sample = ConfigParser.ConfigParser()
-    config_sample.readfp(open(TEMPEST_CONFIG_SAMPLE))
+    config = ConfigParser.ConfigParser()
+    config.readfp(open(path_to_config))
 
-    return config_sample
+    return config
 
 
 def update_config_admin_credentials(config, config_section):
     """
     Updates the tempest config with the admin credentials
-    :param config: an object representing the tempest config file
+    :param config: a ConfigParser object representing the tempest config file
     :param config_section: the section name where the admin credentials are
     """
-    # Check if credentials are present
+    # Check if credentials are present, default uses the config credentials
+    OS_USERNAME = os.getenv('OS_USERNAME',
+                            config.get(config_section, "admin_username"))
+    OS_PASSWORD = os.getenv('OS_PASSWORD',
+                            config.get(config_section, "admin_password"))
+    OS_TENANT_NAME = os.getenv('OS_TENANT_NAME',
+                               config.get(config_section, "admin_tenant_name"))
+    OS_AUTH_URL = os.getenv('OS_AUTH_URL', config.get(config_section, "uri"))
+
     if not (OS_AUTH_URL and
             OS_USERNAME and
             OS_PASSWORD and
@@ -113,31 +151,31 @@
                                       config_identity_params)
 
 
-def update_config_section_with_params(config, section, params):
+def update_config_section_with_params(config, config_section, params):
     """
     Updates a given config object with given params
-    :param config: the object representing the config file of tempest
-    :param section: the section we would like to update
+    :param config: a ConfigParser object representing the tempest config file
+    :param config_section: the section we would like to update
     :param params: the parameters we wish to update for that section
     """
     for option, value in params.items():
-        config.set(section, option, value)
+        config.set(config_section, option, value)
 
 
-def get_identity_client_kwargs(config, section_name):
+def get_identity_client_kwargs(config, config_section):
     """
     Get the required arguments for the identity python client
-    :param config: the tempest configuration file
-    :param section_name: the section name in the configuration where the
+    :param config: a ConfigParser object representing the tempest config file
+    :param config_section: the section name in the configuration where the
     arguments can be found
     :return: a dictionary representing the needed arguments for the identity
     client
     """
-    username = config.get(section_name, 'admin_username')
-    password = config.get(section_name, 'admin_password')
-    tenant_name = config.get(section_name, 'admin_tenant_name')
-    auth_url = config.get(section_name, 'uri')
-    dscv = config.get(section_name, 'disable_ssl_certificate_validation')
+    username = config.get(config_section, 'admin_username')
+    password = config.get(config_section, 'admin_password')
+    tenant_name = config.get(config_section, 'admin_tenant_name')
+    auth_url = config.get(config_section, 'uri')
+    dscv = config.get(config_section, 'disable_ssl_certificate_validation')
     kwargs = {'username': username,
               'password': password,
               'tenant_name': tenant_name,
@@ -185,21 +223,21 @@
 
 def create_users_and_tenants(identity_client,
                              config,
-                             identity_section):
+                             config_section):
     """
     Creates the two non admin users and tenants for tempest
     :param identity_client: openstack identity python client
-    :param config: tempest configuration file
-    :param identity_section: the section name of identity in the config
+    :param config: a ConfigParser object representing the tempest config file
+    :param config_section: the section name of identity in the config
     """
     # Get the necessary params from the config file
-    tenant_name = config.get(identity_section, 'tenant_name')
-    username = config.get(identity_section, 'username')
-    password = config.get(identity_section, 'password')
+    tenant_name = config.get(config_section, 'tenant_name')
+    username = config.get(config_section, 'username')
+    password = config.get(config_section, 'password')
 
-    alt_tenant_name = config.get(identity_section, 'alt_tenant_name')
-    alt_username = config.get(identity_section, 'alt_username')
-    alt_password = config.get(identity_section, 'alt_password')
+    alt_tenant_name = config.get(config_section, 'alt_tenant_name')
+    alt_username = config.get(config_section, 'alt_username')
+    alt_password = config.get(config_section, 'alt_password')
 
     # Create the necessary users for the test runs
     create_user_with_tenant(identity_client, username, password, tenant_name)
@@ -207,28 +245,153 @@
                             alt_tenant_name)
 
 
+def get_image_client_kwargs(identity_client, config, config_section):
+    """
+    Get the required arguments for the image python client
+    :param identity_client: openstack identity python client
+    :param config: a ConfigParser object representing the tempest config file
+    :param config_section: the section name of identity in the config
+    :return: a dictionary representing the needed arguments for the image
+    client
+    """
+
+    token = identity_client.auth_token
+    endpoint = identity_client.\
+        service_catalog.url_for(service_type='image', endpoint_type='publicURL'
+                                )
+    dscv = config.get(config_section, 'disable_ssl_certificate_validation')
+    kwargs = {'endpoint': endpoint,
+              'token': token,
+              'insecure': dscv}
+
+    return kwargs
+
+
+def images_exist(image_client):
+    """
+    Checks whether the images ID's located in the environment variable are
+    indeed registered
+    :param image_client: the openstack python client representing the image
+    client
+    """
+    exist = True
+    if not TEMPEST_IMAGE_ID or not TEMPEST_IMAGE_ID_ALT:
+        exist = False
+    else:
+        try:
+            image_client.images.get(TEMPEST_IMAGE_ID)
+            image_client.images.get(TEMPEST_IMAGE_ID_ALT)
+        except glance_exception.HTTPNotFound:
+            exist = False
+
+    return exist
+
+
+def download_and_register_uec_images(image_client, download_url,
+                                     download_folder):
+    """
+    Downloads and registered the UEC AKI/AMI/ARI images
+    :param image_client:
+    :param download_url: the url of the uec tar file
+    :param download_folder: the destination folder we wish to save the file to
+    """
+    basename = os.path.basename(download_url)
+    path = os.path.join(download_folder, basename)
+
+    request = urllib2.urlopen(download_url)
+
+    # First, download the file
+    with open(path, "wb") as fp:
+        while True:
+            chunk = request.read(IMAGE_DOWNLOAD_CHUNK_SIZE)
+            if not chunk:
+                break
+
+            fp.write(chunk)
+
+    # Then extract and register images
+    tar = tarfile.open(path, "r")
+    for name in tar.getnames():
+        file_obj = tar.extractfile(name)
+        format = "aki"
+
+        if file_obj.name.endswith(".img"):
+            format = "ami"
+
+        if file_obj.name.endswith("initrd"):
+            format = "ari"
+
+        # Register images in image client
+        image_client.images.create(name=file_obj.name, disk_format=format,
+                                   container_format=format, data=file_obj,
+                                   is_public="true")
+
+    tar.close()
+
+
+def create_images(image_client, config, config_section,
+                  download_url=IMAGE_UEC_SOURCE_URL,
+                  download_folder=TEMPEST_TEMP_DIR):
+    """
+    Creates images for tempest's use and registers the environment variables
+    IMAGE_ID and IMAGE_ID_ALT with registered images
+    :param image_client: Openstack python image client
+    :param config: a ConfigParser object representing the tempest config file
+    :param config_section: the section name where the IMAGE ids are set
+    :param download_url: the URL from which we should download the UEC tar
+    :param download_folder: the place where we want to save the download file
+    """
+    if not images_exist(image_client):
+        # Falls down to the default uec images
+        download_and_register_uec_images(image_client, download_url,
+                                         download_folder)
+        image_ids = []
+        for image in image_client.images.list():
+            image_ids.append(image.id)
+
+        os.environ["IMAGE_ID"] = image_ids[0]
+        os.environ["IMAGE_ID_ALT"] = image_ids[1]
+
+    params = {'image_ref': os.getenv("IMAGE_ID"),
+              'image_ref_alt': os.getenv("IMAGE_ID_ALT")}
+
+    update_config_section_with_params(config, config_section, params)
+
+
 def main():
     """
     Main module to control the script
     """
-    # TODO(tkammer): add support for existing config file
-    config_sample = getTempestConfigSample()
-    update_config_admin_credentials(config_sample, 'identity')
+    # Check if config file exists or fall to the default sample otherwise
+    path_to_config = TEMPEST_CONFIG_SAMPLE
+
+    if os.path.isfile(TEMPEST_CONFIG_FILE):
+        path_to_config = TEMPEST_CONFIG_FILE
+
+    config = get_tempest_config(path_to_config)
+    update_config_admin_credentials(config, 'identity')
 
     client_manager = ClientManager()
 
     # Set the identity related info for tempest
-    identity_client_kwargs = get_identity_client_kwargs(config_sample,
+    identity_client_kwargs = get_identity_client_kwargs(config,
                                                         'identity')
     identity_client = client_manager.get_identity_client(
         **identity_client_kwargs)
 
     # Create the necessary users and tenants for tempest run
-    create_users_and_tenants(identity_client,
-                             config_sample,
-                             'identity')
+    create_users_and_tenants(identity_client, config, 'identity')
 
-    # TODO(tkammer): add image implementation
+    # Set the image related info for tempest
+    image_client_kwargs = get_image_client_kwargs(identity_client,
+                                                  config,
+                                                  'identity')
+    image_client = client_manager.get_image_client(**image_client_kwargs)
+
+    # Create the necessary users and tenants for tempest run
+    create_images(image_client, config, 'compute')
+
+    # TODO(tkammer): add network implementation
 
 if __name__ == "__main__":
     main()