Deprecates the use of '--upstream' argument
[apex.git] / apex / deploy.py
1 #!/usr/bin/env python
2
3 ##############################################################################
4 # Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others.
5 #
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ##############################################################################
11
12 import argparse
13 import json
14 import logging
15 import os
16 import platform
17 import pprint
18 import shutil
19 import sys
20 import tempfile
21
22 import apex.virtual.configure_vm as vm_lib
23 import apex.virtual.utils as virt_utils
24 import apex.builders.common_builder as c_builder
25 import apex.builders.overcloud_builder as oc_builder
26 import apex.builders.undercloud_builder as uc_builder
27 from apex import DeploySettings
28 from apex import Inventory
29 from apex import NetworkEnvironment
30 from apex import NetworkSettings
31 from apex.common import utils
32 from apex.common import constants
33 from apex.common import parsers
34 from apex.common.exceptions import ApexDeployException
35 from apex.network import jumphost
36 from apex.network import network_data
37 from apex.undercloud import undercloud as uc_lib
38 from apex.overcloud import config as oc_cfg
39 from apex.overcloud import deploy as oc_deploy
40
41 APEX_TEMP_DIR = tempfile.mkdtemp(prefix='apex_tmp')
42 SDN_IMAGE = 'overcloud-full-opendaylight.qcow2'
43
44
45 def deploy_quickstart(args, deploy_settings_file, network_settings_file,
46                       inventory_file=None):
47     pass
48
49
50 def validate_cross_settings(deploy_settings, net_settings, inventory):
51     """
52     Used to validate compatibility across settings file.
53     :param deploy_settings: parsed settings for deployment
54     :param net_settings: parsed settings for network
55     :param inventory: parsed inventory file
56     :return: None
57     """
58
59     if deploy_settings['deploy_options']['dataplane'] != 'ovs' and 'tenant' \
60             not in net_settings.enabled_network_list:
61         raise ApexDeployException("Setting a DPDK based dataplane requires"
62                                   "a dedicated NIC for tenant network")
63
64     if 'odl_vpp_routing_node' in deploy_settings['deploy_options']:
65         if deploy_settings['deploy_options']['dataplane'] != 'fdio':
66             raise ApexDeployException("odl_vpp_routing_node should only be set"
67                                       "when dataplane is set to fdio")
68         if deploy_settings['deploy_options'].get('dvr') is True:
69             raise ApexDeployException("odl_vpp_routing_node should only be set"
70                                       "when dvr is not enabled")
71
72     # TODO(trozet): add more checks here like RAM for ODL, etc
73     # check if odl_vpp_netvirt is true and vpp is set
74     # Check if fdio and nosdn:
75     # tenant_nic_mapping_controller_members" ==
76     # "$tenant_nic_mapping_compute_members
77
78
79 def build_vms(inventory, network_settings,
80               template_dir='/usr/share/opnfv-apex'):
81     """
82     Creates VMs and configures vbmc and host
83     :param inventory:
84     :param network_settings:
85     :return:
86     """
87
88     for idx, node in enumerate(inventory['nodes']):
89         name = 'baremetal{}'.format(idx)
90         volume = name + ".qcow2"
91         volume_path = os.path.join(constants.LIBVIRT_VOLUME_PATH, volume)
92         # TODO(trozet): add error checking
93         vm_lib.create_vm(
94             name, volume_path,
95             baremetal_interfaces=network_settings.enabled_network_list,
96             memory=node['memory'], cpus=node['cpu'],
97             macs=node['mac'],
98             template_dir=template_dir)
99         virt_utils.host_setup({name: node['pm_port']})
100
101
102 def create_deploy_parser():
103     deploy_parser = argparse.ArgumentParser()
104     deploy_parser.add_argument('--debug', action='store_true', default=False,
105                                help="Turn on debug messages")
106     deploy_parser.add_argument('-l', '--log-file',
107                                default='./apex_deploy.log',
108                                dest='log_file', help="Log file to log to")
109     deploy_parser.add_argument('-d', '--deploy-settings',
110                                dest='deploy_settings_file',
111                                required=True,
112                                help='File which contains Apex deploy settings')
113     deploy_parser.add_argument('-n', '--network-settings',
114                                dest='network_settings_file',
115                                required=True,
116                                help='File which contains Apex network '
117                                     'settings')
118     deploy_parser.add_argument('-i', '--inventory-file',
119                                dest='inventory_file',
120                                default=None,
121                                help='Inventory file which contains POD '
122                                     'definition')
123     deploy_parser.add_argument('-e', '--environment-file',
124                                dest='env_file',
125                                default='opnfv-environment.yaml',
126                                help='Provide alternate base env file located '
127                                     'in deploy_dir')
128     deploy_parser.add_argument('-v', '--virtual', action='store_true',
129                                default=False,
130                                dest='virtual',
131                                help='Enable virtual deployment')
132     deploy_parser.add_argument('--interactive', action='store_true',
133                                default=False,
134                                help='Enable interactive deployment mode which '
135                                     'requires user to confirm steps of '
136                                     'deployment')
137     deploy_parser.add_argument('--virtual-computes',
138                                dest='virt_compute_nodes',
139                                default=1,
140                                type=int,
141                                help='Number of Virtual Compute nodes to create'
142                                     ' and use during deployment (defaults to 1'
143                                     ' for noha and 2 for ha)')
144     deploy_parser.add_argument('--virtual-cpus',
145                                dest='virt_cpus',
146                                default=4,
147                                type=int,
148                                help='Number of CPUs to use per Overcloud VM in'
149                                     ' a virtual deployment (defaults to 4)')
150     deploy_parser.add_argument('--virtual-default-ram',
151                                dest='virt_default_ram',
152                                default=8,
153                                type=int,
154                                help='Amount of default RAM to use per '
155                                     'Overcloud VM in GB (defaults to 8).')
156     deploy_parser.add_argument('--virtual-compute-ram',
157                                dest='virt_compute_ram',
158                                default=None,
159                                type=int,
160                                help='Amount of RAM to use per Overcloud '
161                                     'Compute VM in GB (defaults to 8). '
162                                     'Overrides --virtual-default-ram arg for '
163                                     'computes')
164     deploy_parser.add_argument('--deploy-dir',
165                                default='/usr/share/opnfv-apex',
166                                help='Directory to deploy from which contains '
167                                     'base config files for deployment')
168     deploy_parser.add_argument('--image-dir',
169                                default='/var/opt/opnfv/images',
170                                help='Directory which contains '
171                                     'base disk images for deployment')
172     deploy_parser.add_argument('--lib-dir',
173                                default='/usr/share/opnfv-apex',
174                                help='Directory path for apex ansible '
175                                     'and third party libs')
176     deploy_parser.add_argument('--quickstart', action='store_true',
177                                default=False,
178                                help='Use tripleo-quickstart to deploy')
179     deploy_parser.add_argument('--upstream', action='store_true',
180                                default=True,
181                                help='Force deployment to use upstream '
182                                     'artifacts. This option is now '
183                                     'deprecated and only upstream '
184                                     'deployments are supported.')
185     deploy_parser.add_argument('--no-fetch', action='store_true',
186                                default=False,
187                                help='Ignore fetching latest upstream and '
188                                     'use what is in cache')
189     return deploy_parser
190
191
192 def validate_deploy_args(args):
193     """
194     Validates arguments for deploy
195     :param args:
196     :return: None
197     """
198
199     logging.debug('Validating arguments for deployment')
200     if args.virtual and args.inventory_file is not None:
201         logging.error("Virtual enabled but inventory file also given")
202         raise ApexDeployException('You should not specify an inventory file '
203                                   'with virtual deployments')
204     elif args.virtual:
205         args.inventory_file = os.path.join(APEX_TEMP_DIR,
206                                            'inventory-virt.yaml')
207     elif os.path.isfile(args.inventory_file) is False:
208         logging.error("Specified inventory file does not exist: {}".format(
209             args.inventory_file))
210         raise ApexDeployException('Specified inventory file does not exist')
211
212     for settings_file in (args.deploy_settings_file,
213                           args.network_settings_file):
214         if os.path.isfile(settings_file) is False:
215             logging.error("Specified settings file does not "
216                           "exist: {}".format(settings_file))
217             raise ApexDeployException('Specified settings file does not '
218                                       'exist: {}'.format(settings_file))
219
220
221 def main():
222     parser = create_deploy_parser()
223     args = parser.parse_args(sys.argv[1:])
224     # FIXME (trozet): this is only needed as a workaround for CI.  Remove
225     # when CI is changed
226     if os.getenv('IMAGES', False):
227         args.image_dir = os.getenv('IMAGES')
228     if args.debug:
229         log_level = logging.DEBUG
230     else:
231         log_level = logging.INFO
232     os.makedirs(os.path.dirname(args.log_file), exist_ok=True)
233     formatter = '%(asctime)s %(levelname)s: %(message)s'
234     logging.basicConfig(filename=args.log_file,
235                         format=formatter,
236                         datefmt='%m/%d/%Y %I:%M:%S %p',
237                         level=log_level)
238     console = logging.StreamHandler()
239     console.setLevel(log_level)
240     console.setFormatter(logging.Formatter(formatter))
241     logging.getLogger('').addHandler(console)
242     utils.install_ansible()
243     validate_deploy_args(args)
244     # Parse all settings
245     deploy_settings = DeploySettings(args.deploy_settings_file)
246     logging.info("Deploy settings are:\n {}".format(pprint.pformat(
247                  deploy_settings)))
248     net_settings = NetworkSettings(args.network_settings_file)
249     logging.info("Network settings are:\n {}".format(pprint.pformat(
250                  net_settings)))
251     os_version = deploy_settings['deploy_options']['os_version']
252     net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
253     net_env = NetworkEnvironment(net_settings, net_env_file,
254                                  os_version=os_version)
255     net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
256     utils.dump_yaml(dict(net_env), net_env_target)
257
258     # get global deploy params
259     ha_enabled = deploy_settings['global_params']['ha_enabled']
260     introspect = deploy_settings['global_params'].get('introspect', True)
261
262     if args.virtual:
263         if args.virt_compute_ram is None:
264             compute_ram = args.virt_default_ram
265         else:
266             compute_ram = args.virt_compute_ram
267         if deploy_settings['deploy_options']['sdn_controller'] == \
268                 'opendaylight' and args.virt_default_ram < 12:
269             control_ram = 12
270             logging.warning('RAM per controller is too low.  OpenDaylight '
271                             'requires at least 12GB per controller.')
272             logging.info('Increasing RAM per controller to 12GB')
273         elif args.virt_default_ram < 10:
274             control_ram = 10
275             logging.warning('RAM per controller is too low.  nosdn '
276                             'requires at least 10GB per controller.')
277             logging.info('Increasing RAM per controller to 10GB')
278         else:
279             control_ram = args.virt_default_ram
280         if ha_enabled and args.virt_compute_nodes < 2:
281             logging.debug('HA enabled, bumping number of compute nodes to 2')
282             args.virt_compute_nodes = 2
283         virt_utils.generate_inventory(args.inventory_file, ha_enabled,
284                                       num_computes=args.virt_compute_nodes,
285                                       controller_ram=control_ram * 1024,
286                                       compute_ram=compute_ram * 1024,
287                                       vcpus=args.virt_cpus
288                                       )
289     inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
290
291     validate_cross_settings(deploy_settings, net_settings, inventory)
292     ds_opts = deploy_settings['deploy_options']
293     if args.quickstart:
294         deploy_settings_file = os.path.join(APEX_TEMP_DIR,
295                                             'apex_deploy_settings.yaml')
296         utils.dump_yaml(utils.dict_objects_to_str(deploy_settings),
297                         deploy_settings_file)
298         logging.info("File created: {}".format(deploy_settings_file))
299         network_settings_file = os.path.join(APEX_TEMP_DIR,
300                                              'apex_network_settings.yaml')
301         utils.dump_yaml(utils.dict_objects_to_str(net_settings),
302                         network_settings_file)
303         logging.info("File created: {}".format(network_settings_file))
304         deploy_quickstart(args, deploy_settings_file, network_settings_file,
305                           args.inventory_file)
306     else:
307         # TODO (trozet): add logic back from:
308         # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
309         ansible_args = {
310             'virsh_enabled_networks': net_settings.enabled_network_list
311         }
312         utils.run_ansible(ansible_args,
313                           os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
314                                        'deploy_dependencies.yml'))
315         uc_external = False
316         if 'external' in net_settings.enabled_network_list:
317             uc_external = True
318         if args.virtual:
319             # create all overcloud VMs
320             build_vms(inventory, net_settings, args.deploy_dir)
321         else:
322             # Attach interfaces to jumphost for baremetal deployment
323             jump_networks = ['admin']
324             if uc_external:
325                 jump_networks.append('external')
326             for network in jump_networks:
327                 if network == 'external':
328                     # TODO(trozet): enable vlan secondary external networks
329                     iface = net_settings['networks'][network][0][
330                         'installer_vm']['members'][0]
331                 else:
332                     iface = net_settings['networks'][network]['installer_vm'][
333                         'members'][0]
334                 bridge = "br-{}".format(network)
335                 jumphost.attach_interface_to_ovs(bridge, iface, network)
336         instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
337         with open(instackenv_json, 'w') as fh:
338             json.dump(inventory, fh)
339
340         # Create and configure undercloud
341         if args.debug:
342             root_pw = constants.DEBUG_OVERCLOUD_PW
343         else:
344             root_pw = None
345
346         if not args.upstream:
347             logging.warning("Using upstream is now required for Apex. "
348                             "Forcing upstream to true")
349         if os_version == 'master':
350             branch = 'master'
351         else:
352             branch = "stable/{}".format(os_version)
353
354         logging.info("Deploying with upstream artifacts for OpenStack "
355                      "{}".format(os_version))
356         args.image_dir = os.path.join(args.image_dir, os_version)
357         upstream_url = constants.UPSTREAM_RDO.replace(
358             constants.DEFAULT_OS_VERSION, os_version)
359         upstream_targets = ['overcloud-full.tar', 'undercloud.qcow2']
360         utils.fetch_upstream_and_unpack(args.image_dir, upstream_url,
361                                         upstream_targets,
362                                         fetch=not args.no_fetch)
363         sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
364         # copy undercloud so we don't taint upstream fetch
365         uc_image = os.path.join(args.image_dir, 'undercloud_mod.qcow2')
366         uc_fetch_img = os.path.join(args.image_dir, 'undercloud.qcow2')
367         shutil.copyfile(uc_fetch_img, uc_image)
368         # prep undercloud with required packages
369         uc_builder.add_upstream_packages(uc_image)
370         # add patches from upstream to undercloud and overcloud
371         logging.info('Adding patches to undercloud')
372         patches = deploy_settings['global_params']['patches']
373         c_builder.add_upstream_patches(patches['undercloud'], uc_image,
374                                        APEX_TEMP_DIR, branch)
375
376         # Create/Start Undercloud VM
377         undercloud = uc_lib.Undercloud(args.image_dir,
378                                        args.deploy_dir,
379                                        root_pw=root_pw,
380                                        external_network=uc_external,
381                                        image_name=os.path.basename(uc_image),
382                                        os_version=os_version)
383         undercloud.start()
384         undercloud_admin_ip = net_settings['networks'][
385             constants.ADMIN_NETWORK]['installer_vm']['ip']
386
387         if ds_opts['containers']:
388             tag = constants.DOCKER_TAG
389         else:
390             tag = None
391
392         # Generate nic templates
393         for role in 'compute', 'controller':
394             oc_cfg.create_nic_template(net_settings, deploy_settings, role,
395                                        args.deploy_dir, APEX_TEMP_DIR)
396         # Install Undercloud
397         undercloud.configure(net_settings, deploy_settings,
398                              os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
399                                           'configure_undercloud.yml'),
400                              APEX_TEMP_DIR, virtual_oc=args.virtual)
401
402         # Prepare overcloud-full.qcow2
403         logging.info("Preparing Overcloud for deployment...")
404         if os_version != 'ocata':
405             net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
406             net_data = network_data.create_network_data(net_settings,
407                                                         net_data_file)
408         else:
409             net_data = False
410
411         # TODO(trozet): Either fix opnfv env or default to use upstream env
412         if args.env_file == 'opnfv-environment.yaml':
413             # Override the env_file if it is defaulted to opnfv
414             # opnfv env file will not work with upstream
415             args.env_file = 'upstream-environment.yaml'
416         opnfv_env = os.path.join(args.deploy_dir, args.env_file)
417
418         # TODO(trozet): Invoke with containers after Fraser migration
419         # oc_deploy.prep_env(deploy_settings, net_settings, inventory,
420         #                    opnfv_env, net_env_target, APEX_TEMP_DIR)
421
422         shutil.copyfile(
423             opnfv_env,
424             os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
425         )
426         patched_containers = oc_deploy.prep_image(
427             deploy_settings, net_settings, sdn_image, APEX_TEMP_DIR,
428             root_pw=root_pw, docker_tag=tag, patches=patches['overcloud'])
429
430         oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
431                                     APEX_TEMP_DIR, args.virtual,
432                                     os.path.basename(opnfv_env),
433                                     net_data=net_data)
434         # Prepare undercloud with containers
435         docker_playbook = os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
436                                        'prepare_overcloud_containers.yml')
437         if ds_opts['containers']:
438             ceph_version = constants.CEPH_VERSION_MAP[ds_opts['os_version']]
439             ceph_docker_image = "ceph/daemon:tag-build-master-" \
440                                 "{}-centos-7".format(ceph_version)
441             logging.info("Preparing Undercloud with Docker containers")
442             if patched_containers:
443                 oc_builder.archive_docker_patches(APEX_TEMP_DIR)
444             container_vars = dict()
445             container_vars['apex_temp_dir'] = APEX_TEMP_DIR
446             container_vars['patched_docker_services'] = list(
447                 patched_containers)
448             container_vars['container_tag'] = constants.DOCKER_TAG
449             container_vars['stackrc'] = 'source /home/stack/stackrc'
450             container_vars['sdn'] = ds_opts['sdn_controller']
451             container_vars['undercloud_ip'] = undercloud_admin_ip
452             container_vars['os_version'] = os_version
453             container_vars['ceph_docker_image'] = ceph_docker_image
454             container_vars['sdn_env_file'] = \
455                 oc_deploy.get_docker_sdn_file(ds_opts)
456             try:
457                 utils.run_ansible(container_vars, docker_playbook,
458                                   host=undercloud.ip, user='stack',
459                                   tmp_dir=APEX_TEMP_DIR)
460                 logging.info("Container preparation complete")
461             except Exception:
462                 logging.error("Unable to complete container prep on "
463                               "Undercloud")
464                 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
465                 raise
466
467         deploy_playbook = os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
468                                        'deploy_overcloud.yml')
469         virt_env = 'virtual-environment.yaml'
470         bm_env = 'baremetal-environment.yaml'
471         for p_env in virt_env, bm_env:
472             shutil.copyfile(os.path.join(args.deploy_dir, p_env),
473                             os.path.join(APEX_TEMP_DIR, p_env))
474
475         # Start Overcloud Deployment
476         logging.info("Executing Overcloud Deployment...")
477         deploy_vars = dict()
478         deploy_vars['virtual'] = args.virtual
479         deploy_vars['debug'] = args.debug
480         deploy_vars['aarch64'] = platform.machine() == 'aarch64'
481         deploy_vars['introspect'] = not (args.virtual or
482                                          deploy_vars['aarch64'] or
483                                          not introspect)
484         deploy_vars['dns_server_args'] = ''
485         deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
486         deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
487         deploy_vars['stackrc'] = 'source /home/stack/stackrc'
488         deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
489         deploy_vars['undercloud_ip'] = undercloud_admin_ip
490         deploy_vars['ha_enabled'] = ha_enabled
491         deploy_vars['os_version'] = os_version
492         deploy_vars['http_proxy'] = net_settings.get('http_proxy', '')
493         deploy_vars['https_proxy'] = net_settings.get('https_proxy', '')
494         for dns_server in net_settings['dns_servers']:
495             deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
496                 dns_server)
497         try:
498             utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
499                               user='stack', tmp_dir=APEX_TEMP_DIR)
500             logging.info("Overcloud deployment complete")
501         except Exception:
502             logging.error("Deployment Failed.  Please check deploy log as "
503                           "well as mistral logs in "
504                           "{}".format(os.path.join(APEX_TEMP_DIR,
505                                                    'mistral_logs.tar.gz')))
506             raise
507         finally:
508             os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
509
510         # Post install
511         logging.info("Executing post deploy configuration")
512         jumphost.configure_bridges(net_settings)
513         nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
514         deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
515             nova_output)
516         deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
517                                      'GlobalKnownHostsFile=/dev/null -o ' \
518                                      'UserKnownHostsFile=/dev/null -o ' \
519                                      'LogLevel=error'
520         deploy_vars['external_network_cmds'] = \
521             oc_deploy.external_network_cmds(net_settings, deploy_settings)
522         # TODO(trozet): just parse all ds_opts as deploy vars one time
523         deploy_vars['gluon'] = ds_opts['gluon']
524         deploy_vars['sdn'] = ds_opts['sdn_controller']
525         for dep_option in 'yardstick', 'dovetail', 'vsperf':
526             if dep_option in ds_opts:
527                 deploy_vars[dep_option] = ds_opts[dep_option]
528             else:
529                 deploy_vars[dep_option] = False
530         deploy_vars['dataplane'] = ds_opts['dataplane']
531         overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
532         if ds_opts['congress']:
533             deploy_vars['congress_datasources'] = \
534                 oc_deploy.create_congress_cmds(overcloudrc)
535             deploy_vars['congress'] = True
536         else:
537             deploy_vars['congress'] = False
538         deploy_vars['calipso'] = ds_opts.get('calipso', False)
539         deploy_vars['calipso_ip'] = undercloud_admin_ip
540         # overcloudrc.v3 removed and set as default in queens and later
541         if os_version == 'pike':
542             deploy_vars['overcloudrc_files'] = ['overcloudrc',
543                                                 'overcloudrc.v3']
544         else:
545             deploy_vars['overcloudrc_files'] = ['overcloudrc']
546
547         post_undercloud = os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
548                                        'post_deploy_undercloud.yml')
549         logging.info("Executing post deploy configuration undercloud playbook")
550         try:
551             utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
552                               user='stack', tmp_dir=APEX_TEMP_DIR)
553             logging.info("Post Deploy Undercloud Configuration Complete")
554         except Exception:
555             logging.error("Post Deploy Undercloud Configuration failed.  "
556                           "Please check log")
557             raise
558         # Post deploy overcloud node configuration
559         # TODO(trozet): just parse all ds_opts as deploy vars one time
560         deploy_vars['sfc'] = ds_opts['sfc']
561         deploy_vars['vpn'] = ds_opts['vpn']
562         deploy_vars['l2gw'] = ds_opts.get('l2gw')
563         deploy_vars['sriov'] = ds_opts.get('sriov')
564         deploy_vars['tacker'] = ds_opts.get('tacker')
565         # TODO(trozet): pull all logs and store in tmp dir in overcloud
566         # playbook
567         post_overcloud = os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
568                                       'post_deploy_overcloud.yml')
569         # Run per overcloud node
570         for node, ip in deploy_vars['overcloud_nodes'].items():
571             logging.info("Executing Post deploy overcloud playbook on "
572                          "node {}".format(node))
573             try:
574                 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
575                                   user='heat-admin', tmp_dir=APEX_TEMP_DIR)
576                 logging.info("Post Deploy Overcloud Configuration Complete "
577                              "for node {}".format(node))
578             except Exception:
579                 logging.error("Post Deploy Overcloud Configuration failed "
580                               "for node {}. Please check log".format(node))
581                 raise
582         logging.info("Apex deployment complete")
583         logging.info("Undercloud IP: {}, please connect by doing "
584                      "'opnfv-util undercloud'".format(undercloud.ip))
585         # TODO(trozet): add logging here showing controller VIP and horizon url
586
587
588 if __name__ == '__main__':
589     main()