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