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