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