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