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