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