Quick fix around static IPs and ports in launch.py application.
[snaps.git] / examples / launch.py
index 5f2e999..88ff420 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/python
 #
-# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
 #                    and others.  All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # This script is responsible for deploying virtual environments
 import argparse
 import logging
-import os
 import re
 
+import time
+from jinja2 import Environment, FileSystemLoader
+import os
+import yaml
+
 from snaps import file_utils
 from snaps.openstack.create_flavor import FlavorSettings, OpenStackFlavor
-from snaps.openstack.create_image import ImageSettings
+from snaps.openstack.create_image import ImageSettings, OpenStackImage
 from snaps.openstack.create_instance import VmInstanceSettings
-from snaps.openstack.create_network import PortSettings, NetworkSettings
-from snaps.openstack.create_router import RouterSettings
-from snaps.openstack.create_keypairs import KeypairSettings
+from snaps.openstack.create_keypairs import KeypairSettings, OpenStackKeypair
+from snaps.openstack.create_network import (
+    PortSettings, NetworkSettings, OpenStackNetwork)
+from snaps.openstack.create_project import OpenStackProject, ProjectSettings
+from snaps.openstack.create_qos import QoSSettings, OpenStackQoS
+from snaps.openstack.create_router import RouterSettings, OpenStackRouter
+from snaps.openstack.create_security_group import (
+    OpenStackSecurityGroup, SecurityGroupSettings)
+from snaps.openstack.create_user import OpenStackUser, UserSettings
+from snaps.openstack.create_volume import OpenStackVolume, VolumeSettings
+from snaps.openstack.create_volume_type import (
+    OpenStackVolumeType, VolumeTypeSettings)
 from snaps.openstack.os_credentials import OSCreds, ProxySettings
 from snaps.openstack.utils import deploy_utils
 from snaps.provisioning import ansible_utils
 
 __author__ = 'spisarski'
 
-logger = logging.getLogger('deploy_venv')
+logger = logging.getLogger('snaps_launcher')
 
 ARG_NOT_SET = "argument not set"
+DEFAULT_CREDS_KEY = 'admin'
+
+
+def __get_creds_dict(os_conn_config):
+    """
+    Returns a dict of OSCreds where the key is the creds name.
+    For backwards compatibility, credentials not contained in a list (only
+    one) will be returned with the key of None
+    :param os_conn_config: the credential configuration
+    :return: a dict of OSCreds objects
+    """
+    if 'connection' in os_conn_config:
+        return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)}
+    elif 'connections' in os_conn_config:
+        out = dict()
+        for os_conn_dict in os_conn_config['connections']:
+            config = os_conn_dict.get('connection')
+            if not config:
+                raise Exception('Invalid connection format')
+
+            name = config.get('name')
+            if not name:
+                raise Exception('Connection config requires a name field')
+
+            out[name] = __get_os_credentials(os_conn_dict)
+        return out
+
+
+def __get_creds(os_creds_dict, os_user_dict, inst_config):
+    """
+    Returns the appropriate credentials
+    :param os_creds_dict: a dictionary of OSCreds objects where the name is the
+                          key
+    :param os_user_dict: a dictionary of OpenStackUser objects where the name
+                         is the key
+    :param inst_config:
+    :return: an OSCreds instance or None
+    """
+    os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY)
+    if 'os_user' in inst_config:
+        os_user_conf = inst_config['os_user']
+        if 'name' in os_user_conf:
+            user_creator = os_user_dict.get(os_user_conf['name'])
+            if user_creator:
+                return user_creator.get_os_creds(
+                    project_name=os_user_conf.get('project_name'))
+    elif 'os_creds_name' in inst_config:
+        if 'os_creds_name' in inst_config:
+            os_creds = os_creds_dict[inst_config['os_creds_name']]
+    return os_creds
 
 
 def __get_os_credentials(os_conn_config):
     """
-    Returns an object containing all of the information required to access OpenStack APIs
+    Returns an object containing all of the information required to access
+    OpenStack APIs
     :param os_conn_config: The configuration holding the credentials
     :return: an OSCreds instance
     """
+    config = os_conn_config.get('connection')
+    if not config:
+        raise Exception('Invalid connection configuration')
+
     proxy_settings = None
-    http_proxy = os_conn_config.get('http_proxy')
+    http_proxy = config.get('http_proxy')
     if http_proxy:
         tokens = re.split(':', http_proxy)
-        ssh_proxy_cmd = os_conn_config.get('ssh_proxy_cmd')
-        proxy_settings = ProxySettings(tokens[0], tokens[1], ssh_proxy_cmd)
+        ssh_proxy_cmd = config.get('ssh_proxy_cmd')
+        proxy_settings = ProxySettings(host=tokens[0], port=tokens[1],
+                                       ssh_proxy_cmd=ssh_proxy_cmd)
+    else:
+        if 'proxy_settings' in config:
+            host = config['proxy_settings'].get('host')
+            port = config['proxy_settings'].get('port')
+            if host and host != 'None' and port and port != 'None':
+                proxy_settings = ProxySettings(**config['proxy_settings'])
+
+    if proxy_settings:
+        config['proxy_settings'] = proxy_settings
+    else:
+        if config.get('proxy_settings'):
+            del config['proxy_settings']
 
-    return OSCreds(username=os_conn_config.get('username'),
-                   password=os_conn_config.get('password'),
-                   auth_url=os_conn_config.get('auth_url'),
-                   project_name=os_conn_config.get('project_name'),
-                   proxy_settings=proxy_settings)
+    return OSCreds(**config)
 
 
 def __parse_ports_config(config):
     """
     Parses the "ports" configuration
     :param config: The dictionary to parse
-    :param os_creds: The OpenStack credentials object
     :return: a list of PortConfig objects
     """
     out = list()
     for port_config in config:
-        out.append(PortSettings(config=port_config.get('port')))
+        out.append(PortSettings(**port_config.get('port')))
     return out
 
 
-def __create_flavors(os_conn_config, flavors_config, cleanup=False):
-    """
-    Returns a dictionary of flavors where the key is the image name and the value is the image object
-    :param os_conn_config: The OpenStack connection credentials
-    :param flavors_config: The list of image configurations
-    :param cleanup: Denotes whether or not this is being called for cleanup or not
-    :return: dictionary
-    """
-    flavors = {}
-
-    if flavors_config:
-        try:
-            for flavor_config_dict in flavors_config:
-                flavor_config = flavor_config_dict.get('flavor')
-                if flavor_config and flavor_config.get('name'):
-                    flavor_creator = OpenStackFlavor(__get_os_credentials(os_conn_config),
-                                                     FlavorSettings(flavor_config))
-                    flavor_creator.create(cleanup=cleanup)
-                    flavors[flavor_config['name']] = flavor_creator
-        except Exception as e:
-            for key, flavor_creator in flavors.iteritems():
-                flavor_creator.clean()
-            raise e
-        logger.info('Created configured flavors')
-
-    return flavors
-
-
-def __create_images(os_conn_config, images_config, cleanup=False):
-    """
-    Returns a dictionary of images where the key is the image name and the value is the image object
-    :param os_conn_config: The OpenStack connection credentials
-    :param images_config: The list of image configurations
-    :param cleanup: Denotes whether or not this is being called for cleanup or not
-    :return: dictionary
-    """
-    images = {}
-
-    if images_config:
-        try:
-            for image_config_dict in images_config:
-                image_config = image_config_dict.get('image')
-                if image_config and image_config.get('name'):
-                    images[image_config['name']] = deploy_utils.create_image(__get_os_credentials(os_conn_config),
-                                                                             ImageSettings(image_config), cleanup)
-        except Exception as e:
-            for key, image_creator in images.iteritems():
-                image_creator.clean()
-            raise e
-        logger.info('Created configured images')
-
-    return images
-
-
-def __create_networks(os_conn_config, network_confs, cleanup=False):
-    """
-    Returns a dictionary of networks where the key is the network name and the value is the network object
-    :param os_conn_config: The OpenStack connection credentials
-    :param network_confs: The list of network configurations
-    :param cleanup: Denotes whether or not this is being called for cleanup or not
-    :return: dictionary
-    """
-    network_dict = {}
-
-    if network_confs:
-        try:
-            for network_conf in network_confs:
-                net_name = network_conf['network']['name']
-                os_creds = __get_os_credentials(os_conn_config)
-                network_dict[net_name] = deploy_utils.create_network(
-                    os_creds, NetworkSettings(config=network_conf['network']), cleanup)
-        except Exception as e:
-            for key, net_creator in network_dict.iteritems():
-                net_creator.clean()
-            raise e
-
-        logger.info('Created configured networks')
-
-    return network_dict
-
-
-def __create_routers(os_conn_config, router_confs, cleanup=False):
+def __create_instances(os_creds_dict, creator_class, config_class, config,
+                       config_key, cleanup=False, os_users_dict=None):
     """
-    Returns a dictionary of networks where the key is the network name and the value is the network object
-    :param os_conn_config: The OpenStack connection credentials
-    :param router_confs: The list of router configurations
-    :param cleanup: Denotes whether or not this is being called for cleanup or not
+    Returns a dictionary of SNAPS creator objects where the key is the name
+    :param os_creds_dict: Dictionary of OSCreds objects where the key is the
+                          name
+    :param config: The list of configurations for the same type
+    :param config_key: The list of configurations for the same type
+    :param cleanup: Denotes whether or not this is being called for cleanup
     :return: dictionary
     """
-    router_dict = {}
-    os_creds = __get_os_credentials(os_conn_config)
+    out = {}
 
-    if router_confs:
-        try:
-            for router_conf in router_confs:
-                router_name = router_conf['router']['name']
-                router_dict[router_name] = deploy_utils.create_router(
-                    os_creds, RouterSettings(config=router_conf['router']), cleanup)
-        except Exception as e:
-            for key, router_creator in router_dict.iteritems():
-                router_creator.clean()
-            raise e
-
-        logger.info('Created configured networks')
-
-    return router_dict
-
-
-def __create_keypairs(os_conn_config, keypair_confs, cleanup=False):
-    """
-    Returns a dictionary of keypairs where the key is the keypair name and the value is the keypair object
-    :param os_conn_config: The OpenStack connection credentials
-    :param keypair_confs: The list of keypair configurations
-    :param cleanup: Denotes whether or not this is being called for cleanup or not
-    :return: dictionary
-    """
-    keypairs_dict = {}
-    if keypair_confs:
+    if config:
         try:
-            for keypair_dict in keypair_confs:
-                keypair_config = keypair_dict['keypair']
-                kp_settings = KeypairSettings(keypair_config)
-                keypairs_dict[keypair_config['name']] = deploy_utils.create_keypair(
-                    __get_os_credentials(os_conn_config), kp_settings, cleanup)
+            for config_dict in config:
+                inst_config = config_dict.get(config_key)
+                if inst_config:
+                    creator = creator_class(
+                        __get_creds(os_creds_dict, os_users_dict, inst_config),
+                        config_class(**inst_config))
+
+                    if cleanup:
+                        creator.initialize()
+                    else:
+                        creator.create()
+                    out[inst_config['name']] = creator
+            logger.info('Created configured %s', config_key)
         except Exception as e:
-            for key, keypair_creator in keypairs_dict.iteritems():
-                keypair_creator.clean()
-            raise e
+            logger.error('Unexpected error instantiating creator [%s] '
+                         'with exception %s', creator_class, e)
 
-        logger.info('Created configured keypairs')
-
-    return keypairs_dict
+    return out
 
 
-def __create_instances(os_conn_config, instances_config, image_dict, keypairs_dict, cleanup=False):
+def __create_vm_instances(os_creds_dict, os_users_dict, instances_config,
+                          image_dict, keypairs_dict, cleanup=False):
     """
-    Returns a dictionary of instances where the key is the instance name and the value is the VM object
-    :param os_conn_config: The OpenStack connection credentials
+    Returns a dictionary of OpenStackVmInstance objects where the key is the
+    instance name
+    :param os_creds_dict: Dictionary of OSCreds objects where the key is the
+                          name
+    :param os_users_dict: Dictionary of OpenStackUser objects where the key is
+                          the username
     :param instances_config: The list of VM instance configurations
-    :param image_dict: A dictionary of images that will probably be used to instantiate the VM instance
-    :param keypairs_dict: A dictionary of keypairs that will probably be used to instantiate the VM instance
-    :param cleanup: Denotes whether or not this is being called for cleanup or not
+    :param image_dict: A dictionary of images that will probably be used to
+                       instantiate the VM instance
+    :param keypairs_dict: A dictionary of keypairs that will probably be used
+                          to instantiate the VM instance
+    :param cleanup: Denotes whether or not this is being called for cleanup
     :return: dictionary
     """
-    os_creds = __get_os_credentials(os_conn_config)
-
     vm_dict = {}
 
     if instances_config:
@@ -228,57 +210,79 @@ def __create_instances(os_conn_config, instances_config, image_dict, keypairs_di
                     if image_dict:
                         image_creator = image_dict.get(conf.get('imageName'))
                         if image_creator:
-                            instance_settings = VmInstanceSettings(config=instance_config['instance'])
-                            kp_name = conf.get('keypair_name')
-                            vm_dict[conf['name']] = deploy_utils.create_vm_instance(
-                                os_creds, instance_settings, image_creator.image_settings,
-                                keypair_creator=keypairs_dict[kp_name], cleanup=cleanup)
+                            instance_settings = VmInstanceSettings(
+                                **instance_config['instance'])
+                            kp_creator = keypairs_dict.get(
+                                conf.get('keypair_name'))
+                            vm_dict[conf[
+                                'name']] = deploy_utils.create_vm_instance(
+                                __get_creds(
+                                    os_creds_dict, os_users_dict, conf),
+                                instance_settings,
+                                image_creator.image_settings,
+                                keypair_creator=kp_creator,
+                                init_only=cleanup)
                         else:
-                            raise Exception('Image creator instance not found. Cannot instantiate')
+                            raise Exception('Image creator instance not found.'
+                                            ' Cannot instantiate')
                     else:
-                        raise Exception('Image dictionary is None. Cannot instantiate')
+                        raise Exception('Image dictionary is None. Cannot '
+                                        'instantiate')
                 else:
-                    raise Exception('Instance configuration is None. Cannot instantiate')
+                    raise Exception('Instance configuration is None. Cannot '
+                                    'instantiate')
+            logger.info('Created configured instances')
         except Exception as e:
-            logger.error('Unexpected error creating instances. Attempting to cleanup environment - ' + e.message)
-            for key, inst_creator in vm_dict.iteritems():
-                inst_creator.clean()
-            raise e
-
-        logger.info('Created configured instances')
-
+            logger.error('Unexpected error creating VM instances - %s', e)
     return vm_dict
 
 
-def __apply_ansible_playbooks(ansible_configs, os_conn_config, vm_dict, image_dict, flavor_dict, env_file):
+def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict,
+                              image_dict, flavor_dict, env_file):
     """
     Applies ansible playbooks to running VMs with floating IPs
     :param ansible_configs: a list of Ansible configurations
-    :param os_conn_config: the OpenStack connection configuration used to create an OSCreds instance
-    :param vm_dict: the dictionary of newly instantiated VMs where the name is the key
-    :param image_dict: the dictionary of newly instantiated images where the name is the key
-    :param flavor_dict: the dictionary of newly instantiated flavors where the name is the key
-    :param env_file: the path of the environment for setting the CWD so playbook location is relative to the deployment
-                     file
+    :param os_creds_dict: Dictionary of OSCreds objects where the key is the
+                          name
+    :param vm_dict: the dictionary of newly instantiated VMs where the name is
+                    the key
+    :param image_dict: the dictionary of newly instantiated images where the
+                       name is the key
+    :param flavor_dict: the dictionary of newly instantiated flavors where the
+                        name is the key
+    :param env_file: the path of the environment for setting the CWD so
+                     playbook location is relative to the deployment file
     :return: t/f - true if successful
     """
     logger.info("Applying Ansible Playbooks")
     if ansible_configs:
         # Ensure all hosts are accepting SSH session requests
-        for vm_inst in vm_dict.values():
+        for vm_inst in list(vm_dict.values()):
             if not vm_inst.vm_ssh_active(block=True):
-                logger.warn("Timeout waiting for instance to respond to SSH requests")
+                logger.warning(
+                    "Timeout waiting for instance to respond to SSH requests")
                 return False
 
-        # Set CWD so the deployment file's playbook location can leverage relative paths
+        # Set CWD so the deployment file's playbook location can leverage
+        # relative paths
         orig_cwd = os.getcwd()
         env_dir = os.path.dirname(env_file)
         os.chdir(env_dir)
 
         # Apply playbooks
         for ansible_config in ansible_configs:
-            os_creds = __get_os_credentials(os_conn_config)
-            __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict, flavor_dict)
+            if 'pre_sleep_time' in ansible_config:
+                try:
+                    sleep_time = int(ansible_config['pre_sleep_time'])
+                    logger.info('Waiting %s seconds to apply playbooks',
+                                sleep_time)
+                    time.sleep(sleep_time)
+                except:
+                    pass
+
+            os_creds = os_creds_dict.get(None, 'admin')
+            __apply_ansible_playbook(ansible_config, os_creds, vm_dict,
+                                     image_dict, flavor_dict)
 
         # Return to original directory
         os.chdir(orig_cwd)
@@ -286,25 +290,36 @@ def __apply_ansible_playbooks(ansible_configs, os_conn_config, vm_dict, image_di
     return True
 
 
-def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict, flavor_dict):
+def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict,
+                             flavor_dict):
     """
     Applies an Ansible configuration setting
     :param ansible_config: the configuration settings
     :param os_creds: the OpenStack credentials object
-    :param vm_dict: the dictionary of newly instantiated VMs where the name is the key
-    :param image_dict: the dictionary of newly instantiated images where the name is the key
-    :param flavor_dict: the dictionary of newly instantiated flavors where the name is the key
+    :param vm_dict: the dictionary of newly instantiated VMs where the name is
+                    the key
+    :param image_dict: the dictionary of newly instantiated images where the
+                       name is the key
+    :param flavor_dict: the dictionary of newly instantiated flavors where the
+                        name is the key
     """
     if ansible_config:
-        remote_user, floating_ips, private_key_filepath, proxy_settings = __get_connection_info(ansible_config, vm_dict)
+        (remote_user, floating_ips, private_key_filepath,
+         proxy_settings) = __get_connection_info(
+            ansible_config, vm_dict)
         if floating_ips:
             retval = ansible_utils.apply_playbook(
-                ansible_config['playbook_location'], floating_ips, remote_user, private_key_filepath,
-                variables=__get_variables(ansible_config.get('variables'), os_creds, vm_dict, image_dict, flavor_dict),
+                ansible_config['playbook_location'], floating_ips, remote_user,
+                private_key_filepath,
+                variables=__get_variables(ansible_config.get('variables'),
+                                          os_creds, vm_dict, image_dict,
+                                          flavor_dict),
                 proxy_setting=proxy_settings)
             if retval != 0:
                 # Not a fatal type of event
-                logger.warn('Unable to apply playbook found at location - ' + ansible_config('playbook_location'))
+                logger.warning(
+                    'Unable to apply playbook found at location - %s',
+                    ansible_config.get('playbook_location'))
 
 
 def __get_connection_info(ansible_config, vm_dict):
@@ -313,16 +328,19 @@ def __get_connection_info(ansible_config, vm_dict):
     (remote_user, [floating_ips], private_key_filepath, proxy_settings)
     :param ansible_config: the configuration settings
     :param vm_dict: the dictionary of VMs where the VM name is the key
-    :return: tuple where the first element is the user and the second is a list of floating IPs and the third is the
-    private key file location and the fourth is an instance of the snaps.ProxySettings class
-    (note: in order to work, each of the hosts need to have the same sudo_user and private key file location values)
+    :return: tuple where the first element is the user and the second is a list
+             of floating IPs and the third is the
+    private key file location and the fourth is an instance of the
+    snaps.ProxySettings class
+    (note: in order to work, each of the hosts need to have the same sudo_user
+    and private key file location values)
     """
     if ansible_config.get('hosts'):
         hosts = ansible_config['hosts']
         if len(hosts) > 0:
             floating_ips = list()
             remote_user = None
-            private_key_filepath = None
+            pk_file = None
             proxy_settings = None
             for host in hosts:
                 vm = vm_dict.get(host)
@@ -334,48 +352,63 @@ def __get_connection_info(ansible_config, vm_dict):
                         if fip:
                             floating_ips.append(fip.ip)
                         else:
-                            raise Exception('Could not find floating IP for VM - ' + vm.name)
+                            raise Exception(
+                                'Could not find floating IP for VM - ' +
+                                vm.name)
 
-                        private_key_filepath = vm.keypair_settings.private_filepath
+                        pk_file = vm.keypair_settings.private_filepath
                         proxy_settings = vm.get_os_creds().proxy_settings
                 else:
                     logger.error('Could not locate VM with name - ' + host)
 
-            return remote_user, floating_ips, private_key_filepath, proxy_settings
+            return remote_user, floating_ips, pk_file, proxy_settings
     return None
 
 
 def __get_variables(var_config, os_creds, vm_dict, image_dict, flavor_dict):
     """
-    Returns a dictionary of substitution variables to be used for Ansible templates
+    Returns a dictionary of substitution variables to be used for Ansible
+    templates
     :param var_config: the variable configuration settings
     :param os_creds: the OpenStack credentials object
-    :param vm_dict: the dictionary of newly instantiated VMs where the name is the key
-    :param image_dict: the dictionary of newly instantiated images where the name is the key
-    :param flavor_dict: the dictionary of newly instantiated flavors where the name is the key
+    :param vm_dict: the dictionary of newly instantiated VMs where the name is
+                    the key
+    :param image_dict: the dictionary of newly instantiated images where the
+                       name is the key
+    :param flavor_dict: the dictionary of newly instantiated flavors where the
+                        name is the key
     :return: dictionary or None
     """
     if var_config and vm_dict and len(vm_dict) > 0:
         variables = dict()
-        for key, value in var_config.iteritems():
-            value = __get_variable_value(value, os_creds, vm_dict, image_dict, flavor_dict)
+        for key, value in var_config.items():
+            value = __get_variable_value(value, os_creds, vm_dict, image_dict,
+                                         flavor_dict)
             if key and value:
                 variables[key] = value
-                logger.info("Set Jinga2 variable with key [" + key + "] the value [" + value + ']')
+                logger.info(
+                    "Set Jinga2 variable with key [%s] the value [%s]",
+                    key, value)
             else:
-                logger.warn('Key [' + str(key) + '] or Value [' + str(value) + '] must not be None')
+                logger.warning('Key [%s] or Value [%s] must not be None',
+                               str(key), str(value))
         return variables
     return None
 
 
-def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict, flavor_dict):
+def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict,
+                         flavor_dict):
     """
-    Returns the associated variable value for use by Ansible for substitution purposes
+    Returns the associated variable value for use by Ansible for substitution
+    purposes
     :param var_config_values: the configuration dictionary
     :param os_creds: the OpenStack credentials object
-    :param vm_dict: the dictionary of newly instantiated VMs where the name is the key
-    :param image_dict: the dictionary of newly instantiated images where the name is the key
-    :param flavor_dict: the dictionary of newly instantiated flavors where the name is the key
+    :param vm_dict: the dictionary of newly instantiated VMs where the name is
+                    the key
+    :param image_dict: the dictionary of newly instantiated images where the
+                       name is the key
+    :param flavor_dict: the dictionary of newly instantiated flavors where the
+                        name is the key
     :return:
     """
     if var_config_values['type'] == 'string':
@@ -386,6 +419,8 @@ def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict, flavo
         return __get_os_creds_variable_value(var_config_values, os_creds)
     if var_config_values['type'] == 'port':
         return __get_vm_port_variable_value(var_config_values, vm_dict)
+    if var_config_values['type'] == 'floating_ip':
+        return __get_vm_fip_variable_value(var_config_values, vm_dict)
     if var_config_values['type'] == 'image':
         return __get_image_variable_value(var_config_values, image_dict)
     if var_config_values['type'] == 'flavor':
@@ -406,7 +441,8 @@ def __get_vm_attr_variable_value(var_config_values, vm_dict):
     """
     Returns the associated value contained on a VM instance
     :param var_config_values: the configuration dictionary
-    :param vm_dict: the dictionary containing all VMs where the key is the VM's name
+    :param vm_dict: the dictionary containing all VMs where the key is the VM's
+                    name
     :return: the value
     """
     vm = vm_dict.get(var_config_values['vm_name'])
@@ -447,7 +483,8 @@ def __get_vm_port_variable_value(var_config_values, vm_dict):
     """
     Returns the associated OS credentials value
     :param var_config_values: the configuration dictionary
-    :param vm_dict: the dictionary containing all VMs where the key is the VM's name
+    :param vm_dict: the dictionary containing all VMs where the key is the VM's
+                    name
     :return: the value
     """
     port_name = var_config_values.get('port_name')
@@ -464,11 +501,31 @@ def __get_vm_port_variable_value(var_config_values, vm_dict):
                     return vm.get_port_ip(port_name)
 
 
+def __get_vm_fip_variable_value(var_config_values, vm_dict):
+    """
+    Returns the floating IP value if found
+    :param var_config_values: the configuration dictionary
+    :param vm_dict: the dictionary containing all VMs where the key is the VM's
+                    name
+    :return: the floating IP string value or None
+    """
+    fip_name = var_config_values.get('fip_name')
+    vm_name = var_config_values.get('vm_name')
+
+    if vm_name:
+        vm = vm_dict.get(vm_name)
+        if vm:
+            fip = vm.get_floating_ip(fip_name)
+            if fip:
+                return fip.ip
+
+
 def __get_image_variable_value(var_config_values, image_dict):
     """
     Returns the associated image value
     :param var_config_values: the configuration dictionary
-    :param image_dict: the dictionary containing all images where the key is the name
+    :param image_dict: the dictionary containing all images where the key is
+                       the name
     :return: the value
     """
     logger.info("Retrieving image values")
@@ -477,9 +534,11 @@ def __get_image_variable_value(var_config_values, image_dict):
         if var_config_values.get('image_name'):
             image_creator = image_dict.get(var_config_values['image_name'])
             if image_creator:
-                if var_config_values.get('value') and var_config_values['value'] == 'id':
+                if var_config_values.get('value') and \
+                                var_config_values['value'] == 'id':
                     return image_creator.get_image().id
-                if var_config_values.get('value') and var_config_values['value'] == 'user':
+                if var_config_values.get('value') and \
+                        var_config_values['value'] == 'user':
                     return image_creator.image_settings.image_user
 
     logger.info("Returning none")
@@ -490,7 +549,8 @@ def __get_flavor_variable_value(var_config_values, flavor_dict):
     """
     Returns the associated flavor value
     :param var_config_values: the configuration dictionary
-    :param flavor_dict: the dictionary containing all flavor creators where the key is the name
+    :param flavor_dict: the dictionary containing all flavor creators where the
+                        key is the name
     :return: the value or None
     """
     logger.info("Retrieving flavor values")
@@ -499,17 +559,16 @@ def __get_flavor_variable_value(var_config_values, flavor_dict):
         if var_config_values.get('flavor_name'):
             flavor_creator = flavor_dict.get(var_config_values['flavor_name'])
             if flavor_creator:
-                if var_config_values.get('value') and var_config_values['value'] == 'id':
+                if var_config_values.get('value') and \
+                                var_config_values['value'] == 'id':
                     return flavor_creator.get_flavor().id
 
-    logger.info("Returning none")
-    return None
-
 
 def main(arguments):
     """
-    Will need to set environment variable ANSIBLE_HOST_KEY_CHECKING=False or ...
-    Create a file located in /etc/ansible/ansible/cfg or ~/.ansible.cfg containing the following content:
+    Will need to set environment variable ANSIBLE_HOST_KEY_CHECKING=False or
+    Create a file located in /etc/ansible/ansible/cfg or ~/.ansible.cfg
+    containing the following content:
 
     [defaults]
     host_key_checking = False
@@ -524,112 +583,193 @@ def main(arguments):
     logging.basicConfig(level=log_level)
 
     logger.info('Starting to Deploy')
-    config = file_utils.read_yaml(arguments.environment)
-    logger.info('Read configuration file - ' + arguments.environment)
+
+    # Apply env_file/substitution file to template
+    env = Environment(loader=FileSystemLoader(
+        searchpath=os.path.dirname(arguments.tmplt_file)))
+    template = env.get_template(os.path.basename(arguments.tmplt_file))
+
+    env_dict = dict()
+    if arguments.env_file:
+        env_dict = file_utils.read_yaml(arguments.env_file)
+    output = template.render(**env_dict)
+
+    config = yaml.load(output)
 
     if config:
         os_config = config.get('openstack')
 
-        image_dict = {}
-        network_dict = {}
-        router_dict = {}
-        keypairs_dict = {}
-        vm_dict = {}
+        creators = list()
+        vm_dict = dict()
+        images_dict = dict()
+        flavors_dict = dict()
+        os_creds_dict = dict()
+        clean = arguments.clean is not ARG_NOT_SET
 
         if os_config:
+            os_creds_dict = __get_creds_dict(os_config)
+
             try:
-                os_conn_config = os_config.get('connection')
+                # Create projects
+                projects_dict = __create_instances(
+                    os_creds_dict, OpenStackProject, ProjectSettings,
+                    os_config.get('projects'), 'project', clean)
+                creators.append(projects_dict)
+
+                # Create users
+                users_dict = __create_instances(
+                    os_creds_dict, OpenStackUser, UserSettings,
+                    os_config.get('users'), 'user', clean)
+                creators.append(users_dict)
+
+                # Associate new users to projects
+                if not clean:
+                    for project_creator in projects_dict.values():
+                        users = project_creator.project_settings.users
+                        for user_name in users:
+                            user_creator = users_dict.get(user_name)
+                            if user_creator:
+                                project_creator.assoc_user(
+                                    user_creator.get_user())
 
                 # Create flavors
-                flavor_dict = __create_flavors(os_conn_config, os_config.get('flavors'),
-                                              arguments.clean is not ARG_NOT_SET)
+                flavors_dict = __create_instances(
+                    os_creds_dict, OpenStackFlavor, FlavorSettings,
+                    os_config.get('flavors'), 'flavor', clean, users_dict)
+                creators.append(flavors_dict)
+
+                # Create QoS specs
+                qos_dict = __create_instances(
+                    os_creds_dict, OpenStackQoS, QoSSettings,
+                    os_config.get('qos_specs'), 'qos_spec', clean, users_dict)
+                creators.append(qos_dict)
+
+                # Create volume types
+                vol_type_dict = __create_instances(
+                    os_creds_dict, OpenStackVolumeType, VolumeTypeSettings,
+                    os_config.get('volume_types'), 'volume_type', clean,
+                    users_dict)
+                creators.append(vol_type_dict)
+
+                # Create volume types
+                vol_dict = __create_instances(
+                    os_creds_dict, OpenStackVolume, VolumeSettings,
+                    os_config.get('volumes'), 'volume', clean, users_dict)
+                creators.append(vol_dict)
 
                 # Create images
-                image_dict = __create_images(os_conn_config, os_config.get('images'),
-                                             arguments.clean is not ARG_NOT_SET)
+                images_dict = __create_instances(
+                    os_creds_dict, OpenStackImage, ImageSettings,
+                    os_config.get('images'), 'image', clean, users_dict)
+                creators.append(images_dict)
 
-                # Create network
-                network_dict = __create_networks(os_conn_config, os_config.get('networks'),
-                                                 arguments.clean is not ARG_NOT_SET)
+                # Create networks
+                creators.append(__create_instances(
+                    os_creds_dict, OpenStackNetwork, NetworkSettings,
+                    os_config.get('networks'), 'network', clean, users_dict))
 
-                # Create network
-                router_dict = __create_routers(os_conn_config, os_config.get('routers'),
-                                               arguments.clean is not ARG_NOT_SET)
+                # Create routers
+                creators.append(__create_instances(
+                    os_creds_dict, OpenStackRouter, RouterSettings,
+                    os_config.get('routers'), 'router', clean, users_dict))
 
                 # Create keypairs
-                keypairs_dict = __create_keypairs(os_conn_config, os_config.get('keypairs'),
-                                                  arguments.clean is not ARG_NOT_SET)
+                keypairs_dict = __create_instances(
+                    os_creds_dict, OpenStackKeypair, KeypairSettings,
+                    os_config.get('keypairs'), 'keypair', clean, users_dict)
+                creators.append(keypairs_dict)
+
+                # Create security groups
+                creators.append(__create_instances(
+                    os_creds_dict, OpenStackSecurityGroup,
+                    SecurityGroupSettings,
+                    os_config.get('security_groups'), 'security_group', clean,
+                    users_dict))
 
                 # Create instance
-                vm_dict = __create_instances(os_conn_config, os_config.get('instances'), image_dict, keypairs_dict,
-                                             arguments.clean is not ARG_NOT_SET)
-                logger.info('Completed creating/retrieving all configured instances')
+                vm_dict = __create_vm_instances(
+                    os_creds_dict, users_dict, os_config.get('instances'),
+                    images_dict, keypairs_dict,
+                    arguments.clean is not ARG_NOT_SET)
+                creators.append(vm_dict)
+                logger.info(
+                    'Completed creating/retrieving all configured instances')
             except Exception as e:
-                logger.error('Unexpected error deploying environment. Rolling back due to - ' + e.message)
-                __cleanup(vm_dict, keypairs_dict, router_dict, network_dict, image_dict, flavor_dict, True)
-                raise e
-
+                logger.error(
+                    'Unexpected error deploying environment. Rolling back due'
+                    ' to - ' + str(e))
+                raise
 
         # Must enter either block
         if arguments.clean is not ARG_NOT_SET:
             # Cleanup Environment
-            __cleanup(vm_dict, keypairs_dict, router_dict, network_dict, image_dict, flavor_dict,
-                      arguments.clean_image is not ARG_NOT_SET)
+            __cleanup(creators, arguments.clean_image is not ARG_NOT_SET)
         elif arguments.deploy is not ARG_NOT_SET:
             logger.info('Configuring NICs where required')
-            for vm in vm_dict.itervalues():
+            for vm in vm_dict.values():
                 vm.config_nics()
             logger.info('Completed NIC configuration')
 
             # Provision VMs
             ansible_config = config.get('ansible')
             if ansible_config and vm_dict:
-                if not __apply_ansible_playbooks(ansible_config, os_conn_config, vm_dict, image_dict, flavor_dict,
-                                                 arguments.environment):
+                if not __apply_ansible_playbooks(ansible_config,
+                                                 os_creds_dict, vm_dict,
+                                                 images_dict, flavors_dict,
+                                                 arguments.tmplt_file):
                     logger.error("Problem applying ansible playbooks")
     else:
-        logger.error('Unable to read configuration file - ' + arguments.environment)
+        logger.error(
+            'Unable to read configuration file - ' + arguments.tmplt_file)
         exit(1)
 
     exit(0)
 
 
-def __cleanup(vm_dict, keypairs_dict, router_dict, network_dict, image_dict, flavor_dict, clean_image=False):
-    for key, vm_inst in vm_dict.iteritems():
-        vm_inst.clean()
-    for key, kp_inst in keypairs_dict.iteritems():
-        kp_inst.clean()
-    for key, router_inst in router_dict.iteritems():
-        router_inst.clean()
-    for key, net_inst in network_dict.iteritems():
-        net_inst.clean()
-    if clean_image:
-        for key, image_inst in image_dict.iteritems():
-            image_inst.clean()
-    for key, flavor_inst in flavor_dict.iteritems():
-        flavor_inst.clean()
+def __cleanup(creators, clean_image=False):
+    for creator_dict in reversed(creators):
+        for key, creator in creator_dict.items():
+            if ((isinstance(creator, OpenStackImage) and clean_image)
+                    or not isinstance(creator, OpenStackImage)):
+                try:
+                    creator.clean()
+                except Exception as e:
+                    logger.warning('Error cleaning component - %s', e)
 
 
 if __name__ == '__main__':
-    # To ensure any files referenced via a relative path will begin from the diectory in which this file resides
+    # To ensure any files referenced via a relative path will begin from the
+    # directory in which this file resides
     os.chdir(os.path.dirname(os.path.realpath(__file__)))
 
     parser = argparse.ArgumentParser()
-    parser.add_argument('-d', '--deploy', dest='deploy', nargs='?', default=ARG_NOT_SET,
-                        help='When used, environment will be deployed and provisioned')
-    parser.add_argument('-c', '--clean', dest='clean', nargs='?', default=ARG_NOT_SET,
-                        help='When used, the environment will be removed')
-    parser.add_argument('-i', '--clean-image', dest='clean_image', nargs='?', default=ARG_NOT_SET,
-                        help='When cleaning, if this is set, the image will be cleaned too')
-    parser.add_argument('-e', '--env', dest='environment', required=True,
-                        help='The environment configuration YAML file - REQUIRED')
-    parser.add_argument('-l', '--log-level', dest='log_level', default='INFO', help='Logging Level (INFO|DEBUG)')
+    parser.add_argument(
+        '-d', '--deploy', dest='deploy', nargs='?', default=ARG_NOT_SET,
+        help='When used, environment will be deployed and provisioned')
+    parser.add_argument(
+        '-c', '--clean', dest='clean', nargs='?', default=ARG_NOT_SET,
+        help='When used, the environment will be removed')
+    parser.add_argument(
+        '-i', '--clean-image', dest='clean_image', nargs='?',
+        default=ARG_NOT_SET,
+        help='When cleaning, if this is set, the image will be cleaned too')
+    parser.add_argument(
+        '-t', '--tmplt', dest='tmplt_file', required=True,
+        help='The SNAPS deployment template YAML file - REQUIRED')
+    parser.add_argument(
+        '-e', '--env-file', dest='env_file',
+        help='Yaml file containing substitution values to the env file')
+    parser.add_argument(
+        '-l', '--log-level', dest='log_level', default='INFO',
+        help='Logging Level (INFO|DEBUG)')
     args = parser.parse_args()
 
     if args.deploy is ARG_NOT_SET and args.clean is ARG_NOT_SET:
-        print 'Must enter either -d for deploy or -c for cleaning up and environment'
+        print(
+            'Must enter either -d for deploy or -c for cleaning up and '
+            'environment')
         exit(1)
     if args.deploy is not ARG_NOT_SET and args.clean is not ARG_NOT_SET:
-        print 'Cannot enter both options -d/--deploy and -c/--clean'
+        print('Cannot enter both options -d/--deploy and -c/--clean')
         exit(1)
     main(args)