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