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