3 ##############################################################################
4 # Copyright (c) 2017 Tim Rozet (trozet@redhat.com) and others.
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 ##############################################################################
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
41 APEX_TEMP_DIR = tempfile.mkdtemp(prefix='apex_tmp')
42 SDN_IMAGE = 'overcloud-full-opendaylight.qcow2'
45 def deploy_quickstart(args, deploy_settings_file, network_settings_file,
50 def validate_cross_settings(deploy_settings, net_settings, inventory):
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
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")
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")
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
79 def build_vms(inventory, network_settings,
80 template_dir='/usr/share/opnfv-apex'):
82 Creates VMs and configures vbmc and host
84 :param network_settings:
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
95 baremetal_interfaces=network_settings.enabled_network_list,
96 memory=node['memory'], cpus=node['cpu'],
98 template_dir=template_dir)
99 virt_utils.host_setup({name: node['pm_port']})
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',
112 help='File which contains Apex deploy settings')
113 deploy_parser.add_argument('-n', '--network-settings',
114 dest='network_settings_file',
116 help='File which contains Apex network '
118 deploy_parser.add_argument('-i', '--inventory-file',
119 dest='inventory_file',
121 help='Inventory file which contains POD '
123 deploy_parser.add_argument('-e', '--environment-file',
125 default='opnfv-environment.yaml',
126 help='Provide alternate base env file located '
128 deploy_parser.add_argument('-v', '--virtual', action='store_true',
131 help='Enable virtual deployment')
132 deploy_parser.add_argument('--interactive', action='store_true',
134 help='Enable interactive deployment mode which '
135 'requires user to confirm steps of '
137 deploy_parser.add_argument('--virtual-computes',
138 dest='virt_compute_nodes',
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',
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',
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',
160 help='Amount of RAM to use per Overcloud '
161 'Compute VM in GB (defaults to 8). '
162 'Overrides --virtual-default-ram arg for '
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',
178 help='Use tripleo-quickstart to deploy')
179 deploy_parser.add_argument('--upstream', action='store_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',
187 help='Ignore fetching latest upstream and '
188 'use what is in cache')
192 def validate_deploy_args(args):
194 Validates arguments for deploy
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')
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')
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))
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
226 if os.getenv('IMAGES', False):
227 args.image_dir = os.getenv('IMAGES')
229 log_level = logging.DEBUG
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,
236 datefmt='%m/%d/%Y %I:%M:%S %p',
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)
245 deploy_settings = DeploySettings(args.deploy_settings_file)
246 logging.info("Deploy settings are:\n {}".format(pprint.pformat(
248 net_settings = NetworkSettings(args.network_settings_file)
249 logging.info("Network settings are:\n {}".format(pprint.pformat(
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)
258 # get global deploy params
259 ha_enabled = deploy_settings['global_params']['ha_enabled']
260 introspect = deploy_settings['global_params'].get('introspect', True)
263 if args.virt_compute_ram is None:
264 compute_ram = args.virt_default_ram
266 compute_ram = args.virt_compute_ram
267 if deploy_settings['deploy_options']['sdn_controller'] == \
268 'opendaylight' and args.virt_default_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:
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')
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,
289 inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
291 validate_cross_settings(deploy_settings, net_settings, inventory)
292 ds_opts = deploy_settings['deploy_options']
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,
307 # TODO (trozet): add logic back from:
308 # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
310 'virsh_enabled_networks': net_settings.enabled_network_list
312 utils.run_ansible(ansible_args,
313 os.path.join(args.lib_dir, constants.ANSIBLE_PATH,
314 'deploy_dependencies.yml'))
316 if 'external' in net_settings.enabled_network_list:
319 # create all overcloud VMs
320 build_vms(inventory, net_settings, args.deploy_dir)
322 # Attach interfaces to jumphost for baremetal deployment
323 jump_networks = ['admin']
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]
332 iface = net_settings['networks'][network]['installer_vm'][
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)
340 # Create and configure undercloud
342 root_pw = constants.DEBUG_OVERCLOUD_PW
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':
352 branch = "stable/{}".format(os_version)
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,
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)
376 # Create/Start Undercloud VM
377 undercloud = uc_lib.Undercloud(args.image_dir,
380 external_network=uc_external,
381 image_name=os.path.basename(uc_image),
382 os_version=os_version)
384 undercloud_admin_ip = net_settings['networks'][
385 constants.ADMIN_NETWORK]['installer_vm']['ip']
387 if ds_opts['containers']:
388 tag = constants.DOCKER_TAG
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)
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)
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,
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)
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)
424 os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
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'])
430 oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
431 APEX_TEMP_DIR, args.virtual,
432 os.path.basename(opnfv_env),
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(
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)
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")
462 logging.error("Unable to complete container prep on "
464 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
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))
475 # Start Overcloud Deployment
476 logging.info("Executing Overcloud Deployment...")
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
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(
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")
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')))
508 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
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(
516 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
517 'GlobalKnownHostsFile=/dev/null -o ' \
518 'UserKnownHostsFile=/dev/null -o ' \
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]
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
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',
545 deploy_vars['overcloudrc_files'] = ['overcloudrc']
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")
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")
555 logging.error("Post Deploy Undercloud Configuration failed. "
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
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))
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))
579 logging.error("Post Deploy Overcloud Configuration failed "
580 "for node {}. Please check log".format(node))
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
588 if __name__ == '__main__':