b0e87ba7523d2ebb568e78ed7f1d0dbab6377c7c
[snaps.git] / examples / launch.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
4 #                    and others.  All rights reserved.
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at:
9 #
10 #     http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # This script is responsible for deploying virtual environments
19 import argparse
20 import logging
21 import re
22
23 import time
24 from jinja2 import Environment, FileSystemLoader
25 import os
26 import yaml
27
28 from snaps import file_utils
29 from snaps.config.flavor import FlavorConfig
30 from snaps.config.image import ImageConfig
31 from snaps.config.keypair import KeypairConfig
32 from snaps.config.project import ProjectConfig
33 from snaps.config.qos import QoSConfig
34 from snaps.config.router import RouterConfig
35 from snaps.config.user import UserConfig
36 from snaps.openstack.create_flavor import OpenStackFlavor
37 from snaps.openstack.create_image import OpenStackImage
38 from snaps.openstack.create_instance import VmInstanceSettings
39 from snaps.openstack.create_keypairs import OpenStackKeypair
40 from snaps.openstack.create_network import (
41     PortSettings, NetworkSettings, OpenStackNetwork)
42 from snaps.openstack.create_project import OpenStackProject
43 from snaps.openstack.create_qos import OpenStackQoS
44 from snaps.openstack.create_router import OpenStackRouter
45 from snaps.openstack.create_security_group import (
46     OpenStackSecurityGroup, SecurityGroupSettings)
47 from snaps.openstack.create_user import OpenStackUser
48 from snaps.openstack.create_volume import OpenStackVolume, VolumeSettings
49 from snaps.openstack.create_volume_type import (
50     OpenStackVolumeType, VolumeTypeSettings)
51 from snaps.openstack.os_credentials import OSCreds, ProxySettings
52 from snaps.openstack.utils import deploy_utils
53 from snaps.provisioning import ansible_utils
54
55 __author__ = 'spisarski'
56
57 logger = logging.getLogger('snaps_launcher')
58
59 ARG_NOT_SET = "argument not set"
60 DEFAULT_CREDS_KEY = 'admin'
61
62
63 def __get_creds_dict(os_conn_config):
64     """
65     Returns a dict of OSCreds where the key is the creds name.
66     For backwards compatibility, credentials not contained in a list (only
67     one) will be returned with the key of None
68     :param os_conn_config: the credential configuration
69     :return: a dict of OSCreds objects
70     """
71     if 'connection' in os_conn_config:
72         return {DEFAULT_CREDS_KEY: __get_os_credentials(os_conn_config)}
73     elif 'connections' in os_conn_config:
74         out = dict()
75         for os_conn_dict in os_conn_config['connections']:
76             config = os_conn_dict.get('connection')
77             if not config:
78                 raise Exception('Invalid connection format')
79
80             name = config.get('name')
81             if not name:
82                 raise Exception('Connection config requires a name field')
83
84             out[name] = __get_os_credentials(os_conn_dict)
85         return out
86
87
88 def __get_creds(os_creds_dict, os_user_dict, inst_config):
89     """
90     Returns the appropriate credentials
91     :param os_creds_dict: a dictionary of OSCreds objects where the name is the
92                           key
93     :param os_user_dict: a dictionary of OpenStackUser objects where the name
94                          is the key
95     :param inst_config:
96     :return: an OSCreds instance or None
97     """
98     os_creds = os_creds_dict.get(DEFAULT_CREDS_KEY)
99     if 'os_user' in inst_config:
100         os_user_conf = inst_config['os_user']
101         if 'name' in os_user_conf:
102             user_creator = os_user_dict.get(os_user_conf['name'])
103             if user_creator:
104                 return user_creator.get_os_creds(
105                     project_name=os_user_conf.get('project_name'))
106     elif 'os_creds_name' in inst_config:
107         if 'os_creds_name' in inst_config:
108             os_creds = os_creds_dict[inst_config['os_creds_name']]
109     return os_creds
110
111
112 def __get_os_credentials(os_conn_config):
113     """
114     Returns an object containing all of the information required to access
115     OpenStack APIs
116     :param os_conn_config: The configuration holding the credentials
117     :return: an OSCreds instance
118     """
119     config = os_conn_config.get('connection')
120     if not config:
121         raise Exception('Invalid connection configuration')
122
123     proxy_settings = None
124     http_proxy = config.get('http_proxy')
125     if http_proxy:
126         tokens = re.split(':', http_proxy)
127         ssh_proxy_cmd = config.get('ssh_proxy_cmd')
128         proxy_settings = ProxySettings(host=tokens[0], port=tokens[1],
129                                        ssh_proxy_cmd=ssh_proxy_cmd)
130     else:
131         if 'proxy_settings' in config:
132             host = config['proxy_settings'].get('host')
133             port = config['proxy_settings'].get('port')
134             if host and host != 'None' and port and port != 'None':
135                 proxy_settings = ProxySettings(**config['proxy_settings'])
136
137     if proxy_settings:
138         config['proxy_settings'] = proxy_settings
139     else:
140         if config.get('proxy_settings'):
141             del config['proxy_settings']
142
143     return OSCreds(**config)
144
145
146 def __parse_ports_config(config):
147     """
148     Parses the "ports" configuration
149     :param config: The dictionary to parse
150     :return: a list of PortConfig objects
151     """
152     out = list()
153     for port_config in config:
154         out.append(PortSettings(**port_config.get('port')))
155     return out
156
157
158 def __create_instances(os_creds_dict, creator_class, config_class, config,
159                        config_key, cleanup=False, os_users_dict=None):
160     """
161     Returns a dictionary of SNAPS creator objects where the key is the name
162     :param os_creds_dict: Dictionary of OSCreds objects where the key is the
163                           name
164     :param config: The list of configurations for the same type
165     :param config_key: The list of configurations for the same type
166     :param cleanup: Denotes whether or not this is being called for cleanup
167     :return: dictionary
168     """
169     out = {}
170
171     if config:
172         try:
173             for config_dict in config:
174                 inst_config = config_dict.get(config_key)
175                 if inst_config:
176                     creator = creator_class(
177                         __get_creds(os_creds_dict, os_users_dict, inst_config),
178                         config_class(**inst_config))
179
180                     if cleanup:
181                         creator.initialize()
182                     else:
183                         creator.create()
184                     out[inst_config['name']] = creator
185             logger.info('Created configured %s', config_key)
186         except Exception as e:
187             logger.error('Unexpected error instantiating creator [%s] '
188                          'with exception %s', creator_class, e)
189
190     return out
191
192
193 def __create_vm_instances(os_creds_dict, os_users_dict, instances_config,
194                           image_dict, keypairs_dict, cleanup=False):
195     """
196     Returns a dictionary of OpenStackVmInstance objects where the key is the
197     instance name
198     :param os_creds_dict: Dictionary of OSCreds objects where the key is the
199                           name
200     :param os_users_dict: Dictionary of OpenStackUser objects where the key is
201                           the username
202     :param instances_config: The list of VM instance configurations
203     :param image_dict: A dictionary of images that will probably be used to
204                        instantiate the VM instance
205     :param keypairs_dict: A dictionary of keypairs that will probably be used
206                           to instantiate the VM instance
207     :param cleanup: Denotes whether or not this is being called for cleanup
208     :return: dictionary
209     """
210     vm_dict = {}
211
212     if instances_config:
213         try:
214             for instance_config in instances_config:
215                 conf = instance_config.get('instance')
216                 if conf:
217                     if image_dict:
218                         image_creator = image_dict.get(conf.get('imageName'))
219                         if image_creator:
220                             instance_settings = VmInstanceSettings(
221                                 **instance_config['instance'])
222                             kp_creator = keypairs_dict.get(
223                                 conf.get('keypair_name'))
224                             vm_dict[conf[
225                                 'name']] = deploy_utils.create_vm_instance(
226                                 __get_creds(
227                                     os_creds_dict, os_users_dict, conf),
228                                 instance_settings,
229                                 image_creator.image_settings,
230                                 keypair_creator=kp_creator,
231                                 init_only=cleanup)
232                         else:
233                             raise Exception('Image creator instance not found.'
234                                             ' Cannot instantiate')
235                     else:
236                         raise Exception('Image dictionary is None. Cannot '
237                                         'instantiate')
238                 else:
239                     raise Exception('Instance configuration is None. Cannot '
240                                     'instantiate')
241             logger.info('Created configured instances')
242         except Exception as e:
243             logger.error('Unexpected error creating VM instances - %s', e)
244     return vm_dict
245
246
247 def __apply_ansible_playbooks(ansible_configs, os_creds_dict, vm_dict,
248                               image_dict, flavor_dict, env_file):
249     """
250     Applies ansible playbooks to running VMs with floating IPs
251     :param ansible_configs: a list of Ansible configurations
252     :param os_creds_dict: Dictionary of OSCreds objects where the key is the
253                           name
254     :param vm_dict: the dictionary of newly instantiated VMs where the name is
255                     the key
256     :param image_dict: the dictionary of newly instantiated images where the
257                        name is the key
258     :param flavor_dict: the dictionary of newly instantiated flavors where the
259                         name is the key
260     :param env_file: the path of the environment for setting the CWD so
261                      playbook location is relative to the deployment file
262     :return: t/f - true if successful
263     """
264     logger.info("Applying Ansible Playbooks")
265     if ansible_configs:
266         # Ensure all hosts are accepting SSH session requests
267         for vm_inst in list(vm_dict.values()):
268             if not vm_inst.vm_ssh_active(block=True):
269                 logger.warning(
270                     "Timeout waiting for instance to respond to SSH requests")
271                 return False
272
273         # Set CWD so the deployment file's playbook location can leverage
274         # relative paths
275         orig_cwd = os.getcwd()
276         env_dir = os.path.dirname(env_file)
277         os.chdir(env_dir)
278
279         # Apply playbooks
280         for ansible_config in ansible_configs:
281             if 'pre_sleep_time' in ansible_config:
282                 try:
283                     sleep_time = int(ansible_config['pre_sleep_time'])
284                     logger.info('Waiting %s seconds to apply playbooks',
285                                 sleep_time)
286                     time.sleep(sleep_time)
287                 except:
288                     pass
289
290             os_creds = os_creds_dict.get(None, 'admin')
291             __apply_ansible_playbook(ansible_config, os_creds, vm_dict,
292                                      image_dict, flavor_dict)
293
294         # Return to original directory
295         os.chdir(orig_cwd)
296
297     return True
298
299
300 def __apply_ansible_playbook(ansible_config, os_creds, vm_dict, image_dict,
301                              flavor_dict):
302     """
303     Applies an Ansible configuration setting
304     :param ansible_config: the configuration settings
305     :param os_creds: the OpenStack credentials object
306     :param vm_dict: the dictionary of newly instantiated VMs where the name is
307                     the key
308     :param image_dict: the dictionary of newly instantiated images where the
309                        name is the key
310     :param flavor_dict: the dictionary of newly instantiated flavors where the
311                         name is the key
312     """
313     if ansible_config:
314         (remote_user, floating_ips, private_key_filepath,
315          proxy_settings) = __get_connection_info(
316             ansible_config, vm_dict)
317         if floating_ips:
318             retval = ansible_utils.apply_playbook(
319                 ansible_config['playbook_location'], floating_ips, remote_user,
320                 private_key_filepath,
321                 variables=__get_variables(ansible_config.get('variables'),
322                                           os_creds, vm_dict, image_dict,
323                                           flavor_dict),
324                 proxy_setting=proxy_settings)
325             if retval != 0:
326                 # Not a fatal type of event
327                 logger.warning(
328                     'Unable to apply playbook found at location - %s',
329                     ansible_config.get('playbook_location'))
330
331
332 def __get_connection_info(ansible_config, vm_dict):
333     """
334     Returns a tuple of data required for connecting to the running VMs
335     (remote_user, [floating_ips], private_key_filepath, proxy_settings)
336     :param ansible_config: the configuration settings
337     :param vm_dict: the dictionary of VMs where the VM name is the key
338     :return: tuple where the first element is the user and the second is a list
339              of floating IPs and the third is the
340     private key file location and the fourth is an instance of the
341     snaps.ProxySettings class
342     (note: in order to work, each of the hosts need to have the same sudo_user
343     and private key file location values)
344     """
345     if ansible_config.get('hosts'):
346         hosts = ansible_config['hosts']
347         if len(hosts) > 0:
348             floating_ips = list()
349             remote_user = None
350             pk_file = None
351             proxy_settings = None
352             for host in hosts:
353                 vm = vm_dict.get(host)
354                 if vm:
355                     fip = vm.get_floating_ip()
356                     if fip:
357                         remote_user = vm.get_image_user()
358
359                         if fip:
360                             floating_ips.append(fip.ip)
361                         else:
362                             raise Exception(
363                                 'Could not find floating IP for VM - ' +
364                                 vm.name)
365
366                         pk_file = vm.keypair_settings.private_filepath
367                         proxy_settings = vm.get_os_creds().proxy_settings
368                 else:
369                     logger.error('Could not locate VM with name - ' + host)
370
371             return remote_user, floating_ips, pk_file, proxy_settings
372     return None
373
374
375 def __get_variables(var_config, os_creds, vm_dict, image_dict, flavor_dict):
376     """
377     Returns a dictionary of substitution variables to be used for Ansible
378     templates
379     :param var_config: the variable configuration settings
380     :param os_creds: the OpenStack credentials object
381     :param vm_dict: the dictionary of newly instantiated VMs where the name is
382                     the key
383     :param image_dict: the dictionary of newly instantiated images where the
384                        name is the key
385     :param flavor_dict: the dictionary of newly instantiated flavors where the
386                         name is the key
387     :return: dictionary or None
388     """
389     if var_config and vm_dict and len(vm_dict) > 0:
390         variables = dict()
391         for key, value in var_config.items():
392             value = __get_variable_value(value, os_creds, vm_dict, image_dict,
393                                          flavor_dict)
394             if key and value:
395                 variables[key] = value
396                 logger.info(
397                     "Set Jinga2 variable with key [%s] the value [%s]",
398                     key, value)
399             else:
400                 logger.warning('Key [%s] or Value [%s] must not be None',
401                                str(key), str(value))
402         return variables
403     return None
404
405
406 def __get_variable_value(var_config_values, os_creds, vm_dict, image_dict,
407                          flavor_dict):
408     """
409     Returns the associated variable value for use by Ansible for substitution
410     purposes
411     :param var_config_values: the configuration dictionary
412     :param os_creds: the OpenStack credentials object
413     :param vm_dict: the dictionary of newly instantiated VMs where the name is
414                     the key
415     :param image_dict: the dictionary of newly instantiated images where the
416                        name is the key
417     :param flavor_dict: the dictionary of newly instantiated flavors where the
418                         name is the key
419     :return:
420     """
421     if var_config_values['type'] == 'string':
422         return __get_string_variable_value(var_config_values)
423     if var_config_values['type'] == 'vm-attr':
424         return __get_vm_attr_variable_value(var_config_values, vm_dict)
425     if var_config_values['type'] == 'os_creds':
426         return __get_os_creds_variable_value(var_config_values, os_creds)
427     if var_config_values['type'] == 'port':
428         return __get_vm_port_variable_value(var_config_values, vm_dict)
429     if var_config_values['type'] == 'floating_ip':
430         return __get_vm_fip_variable_value(var_config_values, vm_dict)
431     if var_config_values['type'] == 'image':
432         return __get_image_variable_value(var_config_values, image_dict)
433     if var_config_values['type'] == 'flavor':
434         return __get_flavor_variable_value(var_config_values, flavor_dict)
435     return None
436
437
438 def __get_string_variable_value(var_config_values):
439     """
440     Returns the associated string value
441     :param var_config_values: the configuration dictionary
442     :return: the value contained in the dictionary with the key 'value'
443     """
444     return var_config_values['value']
445
446
447 def __get_vm_attr_variable_value(var_config_values, vm_dict):
448     """
449     Returns the associated value contained on a VM instance
450     :param var_config_values: the configuration dictionary
451     :param vm_dict: the dictionary containing all VMs where the key is the VM's
452                     name
453     :return: the value
454     """
455     vm = vm_dict.get(var_config_values['vm_name'])
456     if vm:
457         if var_config_values['value'] == 'floating_ip':
458             return vm.get_floating_ip().ip
459         if var_config_values['value'] == 'image_user':
460             return vm.get_image_user()
461
462
463 def __get_os_creds_variable_value(var_config_values, os_creds):
464     """
465     Returns the associated OS credentials value
466     :param var_config_values: the configuration dictionary
467     :param os_creds: the credentials
468     :return: the value
469     """
470     logger.info("Retrieving OS Credentials")
471     if os_creds:
472         if var_config_values['value'] == 'username':
473             logger.info("Returning OS username")
474             return os_creds.username
475         elif var_config_values['value'] == 'password':
476             logger.info("Returning OS password")
477             return os_creds.password
478         elif var_config_values['value'] == 'auth_url':
479             logger.info("Returning OS auth_url")
480             return os_creds.auth_url
481         elif var_config_values['value'] == 'project_name':
482             logger.info("Returning OS project_name")
483             return os_creds.project_name
484
485     logger.info("Returning none")
486     return None
487
488
489 def __get_vm_port_variable_value(var_config_values, vm_dict):
490     """
491     Returns the associated OS credentials value
492     :param var_config_values: the configuration dictionary
493     :param vm_dict: the dictionary containing all VMs where the key is the VM's
494                     name
495     :return: the value
496     """
497     port_name = var_config_values.get('port_name')
498     vm_name = var_config_values.get('vm_name')
499
500     if port_name and vm_name:
501         vm = vm_dict.get(vm_name)
502         if vm:
503             port_value_id = var_config_values.get('port_value')
504             if port_value_id:
505                 if port_value_id == 'mac_address':
506                     return vm.get_port_mac(port_name)
507                 if port_value_id == 'ip_address':
508                     return vm.get_port_ip(port_name)
509
510
511 def __get_vm_fip_variable_value(var_config_values, vm_dict):
512     """
513     Returns the floating IP value if found
514     :param var_config_values: the configuration dictionary
515     :param vm_dict: the dictionary containing all VMs where the key is the VM's
516                     name
517     :return: the floating IP string value or None
518     """
519     fip_name = var_config_values.get('fip_name')
520     vm_name = var_config_values.get('vm_name')
521
522     if vm_name:
523         vm = vm_dict.get(vm_name)
524         if vm:
525             fip = vm.get_floating_ip(fip_name)
526             if fip:
527                 return fip.ip
528
529
530 def __get_image_variable_value(var_config_values, image_dict):
531     """
532     Returns the associated image value
533     :param var_config_values: the configuration dictionary
534     :param image_dict: the dictionary containing all images where the key is
535                        the name
536     :return: the value
537     """
538     logger.info("Retrieving image values")
539
540     if image_dict:
541         if var_config_values.get('image_name'):
542             image_creator = image_dict.get(var_config_values['image_name'])
543             if image_creator:
544                 if var_config_values.get('value') and \
545                                 var_config_values['value'] == 'id':
546                     return image_creator.get_image().id
547                 if var_config_values.get('value') and \
548                         var_config_values['value'] == 'user':
549                     return image_creator.image_settings.image_user
550
551     logger.info("Returning none")
552     return None
553
554
555 def __get_flavor_variable_value(var_config_values, flavor_dict):
556     """
557     Returns the associated flavor value
558     :param var_config_values: the configuration dictionary
559     :param flavor_dict: the dictionary containing all flavor creators where the
560                         key is the name
561     :return: the value or None
562     """
563     logger.info("Retrieving flavor values")
564
565     if flavor_dict:
566         if var_config_values.get('flavor_name'):
567             flavor_creator = flavor_dict.get(var_config_values['flavor_name'])
568             if flavor_creator:
569                 if var_config_values.get('value') and \
570                                 var_config_values['value'] == 'id':
571                     return flavor_creator.get_flavor().id
572
573
574 def main(arguments):
575     """
576     Will need to set environment variable ANSIBLE_HOST_KEY_CHECKING=False or
577     Create a file located in /etc/ansible/ansible/cfg or ~/.ansible.cfg
578     containing the following content:
579
580     [defaults]
581     host_key_checking = False
582
583     CWD must be this directory where this script is located.
584
585     :return: To the OS
586     """
587     log_level = logging.INFO
588     if arguments.log_level != 'INFO':
589         log_level = logging.DEBUG
590     logging.basicConfig(level=log_level)
591
592     logger.info('Starting to Deploy')
593
594     # Apply env_file/substitution file to template
595     env = Environment(loader=FileSystemLoader(
596         searchpath=os.path.dirname(arguments.tmplt_file)))
597     template = env.get_template(os.path.basename(arguments.tmplt_file))
598
599     env_dict = dict()
600     if arguments.env_file:
601         env_dict = file_utils.read_yaml(arguments.env_file)
602     output = template.render(**env_dict)
603
604     config = yaml.load(output)
605
606     if config:
607         os_config = config.get('openstack')
608
609         creators = list()
610         vm_dict = dict()
611         images_dict = dict()
612         flavors_dict = dict()
613         os_creds_dict = dict()
614         clean = arguments.clean is not ARG_NOT_SET
615
616         if os_config:
617             os_creds_dict = __get_creds_dict(os_config)
618
619             try:
620                 # Create projects
621                 projects_dict = __create_instances(
622                     os_creds_dict, OpenStackProject, ProjectConfig,
623                     os_config.get('projects'), 'project', clean)
624                 creators.append(projects_dict)
625
626                 # Create users
627                 users_dict = __create_instances(
628                     os_creds_dict, OpenStackUser, UserConfig,
629                     os_config.get('users'), 'user', clean)
630                 creators.append(users_dict)
631
632                 # Associate new users to projects
633                 if not clean:
634                     for project_creator in projects_dict.values():
635                         users = project_creator.project_settings.users
636                         for user_name in users:
637                             user_creator = users_dict.get(user_name)
638                             if user_creator:
639                                 project_creator.assoc_user(
640                                     user_creator.get_user())
641
642                 # Create flavors
643                 flavors_dict = __create_instances(
644                     os_creds_dict, OpenStackFlavor, FlavorConfig,
645                     os_config.get('flavors'), 'flavor', clean, users_dict)
646                 creators.append(flavors_dict)
647
648                 # Create QoS specs
649                 qos_dict = __create_instances(
650                     os_creds_dict, OpenStackQoS, QoSConfig,
651                     os_config.get('qos_specs'), 'qos_spec', clean, users_dict)
652                 creators.append(qos_dict)
653
654                 # Create volume types
655                 vol_type_dict = __create_instances(
656                     os_creds_dict, OpenStackVolumeType, VolumeTypeSettings,
657                     os_config.get('volume_types'), 'volume_type', clean,
658                     users_dict)
659                 creators.append(vol_type_dict)
660
661                 # Create volume types
662                 vol_dict = __create_instances(
663                     os_creds_dict, OpenStackVolume, VolumeSettings,
664                     os_config.get('volumes'), 'volume', clean, users_dict)
665                 creators.append(vol_dict)
666
667                 # Create images
668                 images_dict = __create_instances(
669                     os_creds_dict, OpenStackImage, ImageConfig,
670                     os_config.get('images'), 'image', clean, users_dict)
671                 creators.append(images_dict)
672
673                 # Create networks
674                 creators.append(__create_instances(
675                     os_creds_dict, OpenStackNetwork, NetworkSettings,
676                     os_config.get('networks'), 'network', clean, users_dict))
677
678                 # Create routers
679                 creators.append(__create_instances(
680                     os_creds_dict, OpenStackRouter, RouterConfig,
681                     os_config.get('routers'), 'router', clean, users_dict))
682
683                 # Create keypairs
684                 keypairs_dict = __create_instances(
685                     os_creds_dict, OpenStackKeypair, KeypairConfig,
686                     os_config.get('keypairs'), 'keypair', clean, users_dict)
687                 creators.append(keypairs_dict)
688
689                 # Create security groups
690                 creators.append(__create_instances(
691                     os_creds_dict, OpenStackSecurityGroup,
692                     SecurityGroupSettings,
693                     os_config.get('security_groups'), 'security_group', clean,
694                     users_dict))
695
696                 # Create instance
697                 vm_dict = __create_vm_instances(
698                     os_creds_dict, users_dict, os_config.get('instances'),
699                     images_dict, keypairs_dict,
700                     arguments.clean is not ARG_NOT_SET)
701                 creators.append(vm_dict)
702                 logger.info(
703                     'Completed creating/retrieving all configured instances')
704             except Exception as e:
705                 logger.error(
706                     'Unexpected error deploying environment. Rolling back due'
707                     ' to - ' + str(e))
708                 raise
709
710         # Must enter either block
711         if arguments.clean is not ARG_NOT_SET:
712             # Cleanup Environment
713             __cleanup(creators, arguments.clean_image is not ARG_NOT_SET)
714         elif arguments.deploy is not ARG_NOT_SET:
715             logger.info('Configuring NICs where required')
716             for vm in vm_dict.values():
717                 vm.config_nics()
718             logger.info('Completed NIC configuration')
719
720             # Provision VMs
721             ansible_config = config.get('ansible')
722             if ansible_config and vm_dict:
723                 if not __apply_ansible_playbooks(ansible_config,
724                                                  os_creds_dict, vm_dict,
725                                                  images_dict, flavors_dict,
726                                                  arguments.tmplt_file):
727                     logger.error("Problem applying ansible playbooks")
728     else:
729         logger.error(
730             'Unable to read configuration file - ' + arguments.tmplt_file)
731         exit(1)
732
733     exit(0)
734
735
736 def __cleanup(creators, clean_image=False):
737     for creator_dict in reversed(creators):
738         for key, creator in creator_dict.items():
739             if ((isinstance(creator, OpenStackImage) and clean_image)
740                     or not isinstance(creator, OpenStackImage)):
741                 try:
742                     creator.clean()
743                 except Exception as e:
744                     logger.warning('Error cleaning component - %s', e)
745
746
747 if __name__ == '__main__':
748     # To ensure any files referenced via a relative path will begin from the
749     # directory in which this file resides
750     os.chdir(os.path.dirname(os.path.realpath(__file__)))
751
752     parser = argparse.ArgumentParser()
753     parser.add_argument(
754         '-d', '--deploy', dest='deploy', nargs='?', default=ARG_NOT_SET,
755         help='When used, environment will be deployed and provisioned')
756     parser.add_argument(
757         '-c', '--clean', dest='clean', nargs='?', default=ARG_NOT_SET,
758         help='When used, the environment will be removed')
759     parser.add_argument(
760         '-i', '--clean-image', dest='clean_image', nargs='?',
761         default=ARG_NOT_SET,
762         help='When cleaning, if this is set, the image will be cleaned too')
763     parser.add_argument(
764         '-t', '--tmplt', dest='tmplt_file', required=True,
765         help='The SNAPS deployment template YAML file - REQUIRED')
766     parser.add_argument(
767         '-e', '--env-file', dest='env_file',
768         help='Yaml file containing substitution values to the env file')
769     parser.add_argument(
770         '-l', '--log-level', dest='log_level', default='INFO',
771         help='Logging Level (INFO|DEBUG)')
772     args = parser.parse_args()
773
774     if args.deploy is ARG_NOT_SET and args.clean is ARG_NOT_SET:
775         print(
776             'Must enter either -d for deploy or -c for cleaning up and '
777             'environment')
778         exit(1)
779     if args.deploy is not ARG_NOT_SET and args.clean is not ARG_NOT_SET:
780         print('Cannot enter both options -d/--deploy and -c/--clean')
781         exit(1)
782     main(args)