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