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