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 from apex import DeploySettings
25 from apex import Inventory
26 from apex import NetworkEnvironment
27 from apex import NetworkSettings
28 from apex.builders import common_builder as c_builder
29 from apex.builders import overcloud_builder as oc_builder
30 from apex.builders import undercloud_builder as uc_builder
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 ANSIBLE_PATH = 'ansible/playbooks'
43 SDN_IMAGE = 'overcloud-full-opendaylight.qcow2'
46 def deploy_quickstart(args, deploy_settings_file, network_settings_file,
51 def validate_cross_settings(deploy_settings, net_settings, inventory):
53 Used to validate compatibility across settings file.
54 :param deploy_settings: parsed settings for deployment
55 :param net_settings: parsed settings for network
56 :param inventory: parsed inventory file
60 if deploy_settings['deploy_options']['dataplane'] != 'ovs' and 'tenant' \
61 not in net_settings.enabled_network_list:
62 raise ApexDeployException("Setting a DPDK based dataplane requires"
63 "a dedicated NIC for tenant network")
65 if 'odl_vpp_routing_node' in deploy_settings['deploy_options']:
66 if deploy_settings['deploy_options']['dataplane'] != 'fdio':
67 raise ApexDeployException("odl_vpp_routing_node should only be set"
68 "when dataplane is set to fdio")
69 if deploy_settings['deploy_options'].get('dvr') is True:
70 raise ApexDeployException("odl_vpp_routing_node should only be set"
71 "when dvr is not enabled")
73 # TODO(trozet): add more checks here like RAM for ODL, etc
74 # check if odl_vpp_netvirt is true and vpp is set
75 # Check if fdio and nosdn:
76 # tenant_nic_mapping_controller_members" ==
77 # "$tenant_nic_mapping_compute_members
80 def build_vms(inventory, network_settings,
81 template_dir='/usr/share/opnfv-apex'):
83 Creates VMs and configures vbmc and host
85 :param network_settings:
89 for idx, node in enumerate(inventory['nodes']):
90 name = 'baremetal{}'.format(idx)
91 volume = name + ".qcow2"
92 volume_path = os.path.join(constants.LIBVIRT_VOLUME_PATH, volume)
93 # TODO(trozet): add error checking
96 baremetal_interfaces=network_settings.enabled_network_list,
97 memory=node['memory'], cpus=node['cpu'],
99 template_dir=template_dir)
100 virt_utils.host_setup({name: node['pm_port']})
103 def create_deploy_parser():
104 deploy_parser = argparse.ArgumentParser()
105 deploy_parser.add_argument('--debug', action='store_true', default=False,
106 help="Turn on debug messages")
107 deploy_parser.add_argument('-l', '--log-file',
108 default='./apex_deploy.log',
109 dest='log_file', help="Log file to log to")
110 deploy_parser.add_argument('-d', '--deploy-settings',
111 dest='deploy_settings_file',
113 help='File which contains Apex deploy settings')
114 deploy_parser.add_argument('-n', '--network-settings',
115 dest='network_settings_file',
117 help='File which contains Apex network '
119 deploy_parser.add_argument('-i', '--inventory-file',
120 dest='inventory_file',
122 help='Inventory file which contains POD '
124 deploy_parser.add_argument('-e', '--environment-file',
126 default='opnfv-environment.yaml',
127 help='Provide alternate base env file located '
129 deploy_parser.add_argument('-v', '--virtual', action='store_true',
132 help='Enable virtual deployment')
133 deploy_parser.add_argument('--interactive', action='store_true',
135 help='Enable interactive deployment mode which '
136 'requires user to confirm steps of '
138 deploy_parser.add_argument('--virtual-computes',
139 dest='virt_compute_nodes',
142 help='Number of Virtual Compute nodes to create'
143 ' and use during deployment (defaults to 1'
144 ' for noha and 2 for ha)')
145 deploy_parser.add_argument('--virtual-cpus',
149 help='Number of CPUs to use per Overcloud VM in'
150 ' a virtual deployment (defaults to 4)')
151 deploy_parser.add_argument('--virtual-default-ram',
152 dest='virt_default_ram',
155 help='Amount of default RAM to use per '
156 'Overcloud VM in GB (defaults to 8).')
157 deploy_parser.add_argument('--virtual-compute-ram',
158 dest='virt_compute_ram',
161 help='Amount of RAM to use per Overcloud '
162 'Compute VM in GB (defaults to 8). '
163 'Overrides --virtual-default-ram arg for '
165 deploy_parser.add_argument('--deploy-dir',
166 default='/usr/share/opnfv-apex',
167 help='Directory to deploy from which contains '
168 'base config files for deployment')
169 deploy_parser.add_argument('--image-dir',
170 default='/var/opt/opnfv/images',
171 help='Directory which contains '
172 'base disk images for deployment')
173 deploy_parser.add_argument('--lib-dir',
174 default='/usr/share/opnfv-apex',
175 help='Directory path for apex ansible '
176 'and third party libs')
177 deploy_parser.add_argument('--quickstart', action='store_true',
179 help='Use tripleo-quickstart to deploy')
180 deploy_parser.add_argument('--upstream', action='store_true',
182 help='Force deployment to use upstream '
187 def validate_deploy_args(args):
189 Validates arguments for deploy
194 logging.debug('Validating arguments for deployment')
195 if args.virtual and args.inventory_file is not None:
196 logging.error("Virtual enabled but inventory file also given")
197 raise ApexDeployException('You should not specify an inventory file '
198 'with virtual deployments')
200 args.inventory_file = os.path.join(APEX_TEMP_DIR,
201 'inventory-virt.yaml')
202 elif os.path.isfile(args.inventory_file) is False:
203 logging.error("Specified inventory file does not exist: {}".format(
204 args.inventory_file))
205 raise ApexDeployException('Specified inventory file does not exist')
207 for settings_file in (args.deploy_settings_file,
208 args.network_settings_file):
209 if os.path.isfile(settings_file) is False:
210 logging.error("Specified settings file does not "
211 "exist: {}".format(settings_file))
212 raise ApexDeployException('Specified settings file does not '
213 'exist: {}'.format(settings_file))
217 parser = create_deploy_parser()
218 args = parser.parse_args(sys.argv[1:])
219 # FIXME (trozet): this is only needed as a workaround for CI. Remove
221 if os.getenv('IMAGES', False):
222 args.image_dir = os.getenv('IMAGES')
224 log_level = logging.DEBUG
226 log_level = logging.INFO
227 os.makedirs(os.path.dirname(args.log_file), exist_ok=True)
228 formatter = '%(asctime)s %(levelname)s: %(message)s'
229 logging.basicConfig(filename=args.log_file,
231 datefmt='%m/%d/%Y %I:%M:%S %p',
233 console = logging.StreamHandler()
234 console.setLevel(log_level)
235 console.setFormatter(logging.Formatter(formatter))
236 logging.getLogger('').addHandler(console)
237 utils.install_ansible()
238 validate_deploy_args(args)
240 deploy_settings = DeploySettings(args.deploy_settings_file)
241 logging.info("Deploy settings are:\n {}".format(pprint.pformat(
243 net_settings = NetworkSettings(args.network_settings_file)
244 logging.info("Network settings are:\n {}".format(pprint.pformat(
246 os_version = deploy_settings['deploy_options']['os_version']
247 net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
248 net_env = NetworkEnvironment(net_settings, net_env_file,
249 os_version=os_version)
250 net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
251 utils.dump_yaml(dict(net_env), net_env_target)
253 # get global deploy params
254 ha_enabled = deploy_settings['global_params']['ha_enabled']
255 introspect = deploy_settings['global_params'].get('introspect', True)
258 if args.virt_compute_ram is None:
259 compute_ram = args.virt_default_ram
261 compute_ram = args.virt_compute_ram
262 if deploy_settings['deploy_options']['sdn_controller'] == \
263 'opendaylight' and args.virt_default_ram < 12:
265 logging.warning('RAM per controller is too low. OpenDaylight '
266 'requires at least 12GB per controller.')
267 logging.info('Increasing RAM per controller to 12GB')
268 elif args.virt_default_ram < 10:
270 logging.warning('RAM per controller is too low. nosdn '
271 'requires at least 10GB per controller.')
272 logging.info('Increasing RAM per controller to 10GB')
274 control_ram = args.virt_default_ram
275 if ha_enabled and args.virt_compute_nodes < 2:
276 logging.debug('HA enabled, bumping number of compute nodes to 2')
277 args.virt_compute_nodes = 2
278 virt_utils.generate_inventory(args.inventory_file, ha_enabled,
279 num_computes=args.virt_compute_nodes,
280 controller_ram=control_ram * 1024,
281 compute_ram=compute_ram * 1024,
284 inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
286 validate_cross_settings(deploy_settings, net_settings, inventory)
287 ds_opts = deploy_settings['deploy_options']
289 deploy_settings_file = os.path.join(APEX_TEMP_DIR,
290 'apex_deploy_settings.yaml')
291 utils.dump_yaml(utils.dict_objects_to_str(deploy_settings),
292 deploy_settings_file)
293 logging.info("File created: {}".format(deploy_settings_file))
294 network_settings_file = os.path.join(APEX_TEMP_DIR,
295 'apex_network_settings.yaml')
296 utils.dump_yaml(utils.dict_objects_to_str(net_settings),
297 network_settings_file)
298 logging.info("File created: {}".format(network_settings_file))
299 deploy_quickstart(args, deploy_settings_file, network_settings_file,
302 # TODO (trozet): add logic back from:
303 # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
305 'virsh_enabled_networks': net_settings.enabled_network_list
307 utils.run_ansible(ansible_args,
308 os.path.join(args.lib_dir, ANSIBLE_PATH,
309 'deploy_dependencies.yml'))
311 if 'external' in net_settings.enabled_network_list:
314 # create all overcloud VMs
315 build_vms(inventory, net_settings, args.deploy_dir)
317 # Attach interfaces to jumphost for baremetal deployment
318 jump_networks = ['admin']
320 jump_networks.append('external')
321 for network in jump_networks:
322 if network == 'external':
323 # TODO(trozet): enable vlan secondary external networks
324 iface = net_settings['networks'][network][0][
325 'installer_vm']['members'][0]
327 iface = net_settings['networks'][network]['installer_vm'][
329 bridge = "br-{}".format(network)
330 jumphost.attach_interface_to_ovs(bridge, iface, network)
331 instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
332 with open(instackenv_json, 'w') as fh:
333 json.dump(inventory, fh)
335 # Create and configure undercloud
337 root_pw = constants.DEBUG_OVERCLOUD_PW
341 upstream = (os_version != constants.DEFAULT_OS_VERSION or
343 if os_version == 'master':
346 branch = "stable/{}".format(os_version)
348 logging.info("Deploying with upstream artifacts for OpenStack "
349 "{}".format(os_version))
350 args.image_dir = os.path.join(args.image_dir, os_version)
351 upstream_url = constants.UPSTREAM_RDO.replace(
352 constants.DEFAULT_OS_VERSION, os_version)
353 upstream_targets = ['overcloud-full.tar', 'undercloud.qcow2']
354 utils.fetch_upstream_and_unpack(args.image_dir, upstream_url,
356 sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
357 if ds_opts['sdn_controller'] == 'opendaylight':
358 logging.info("Preparing upstream image with OpenDaylight")
359 oc_builder.inject_opendaylight(
360 odl_version=ds_opts['odl_version'],
362 tmp_dir=APEX_TEMP_DIR
364 # copy undercloud so we don't taint upstream fetch
365 uc_image = os.path.join(args.image_dir, 'undercloud_mod.qcow2')
366 uc_fetch_img = os.path.join(args.image_dir, 'undercloud.qcow2')
367 shutil.copyfile(uc_fetch_img, uc_image)
368 # prep undercloud with required packages
369 uc_builder.add_upstream_packages(uc_image)
370 # add patches from upstream to undercloud and overcloud
371 logging.info('Adding patches to undercloud')
372 patches = deploy_settings['global_params']['patches']
373 c_builder.add_upstream_patches(patches['undercloud'], uc_image,
374 APEX_TEMP_DIR, branch)
375 logging.info('Adding patches to overcloud')
376 c_builder.add_upstream_patches(patches['overcloud'], sdn_image,
377 APEX_TEMP_DIR, branch)
379 sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
380 uc_image = 'undercloud.qcow2'
381 undercloud = uc_lib.Undercloud(args.image_dir,
384 external_network=uc_external,
385 image_name=os.path.basename(uc_image))
388 # Generate nic templates
389 for role in 'compute', 'controller':
390 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
391 args.deploy_dir, APEX_TEMP_DIR)
393 undercloud.configure(net_settings, deploy_settings,
394 os.path.join(args.lib_dir, ANSIBLE_PATH,
395 'configure_undercloud.yml'),
398 # Prepare overcloud-full.qcow2
399 logging.info("Preparing Overcloud for deployment...")
400 if os_version != 'ocata':
401 net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
402 net_data = network_data.create_network_data(net_settings,
406 if upstream and args.env_file == 'opnfv-environment.yaml':
407 # Override the env_file if it is defaulted to opnfv
408 # opnfv env file will not work with upstream
409 args.env_file = 'upstream-environment.yaml'
410 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
412 oc_deploy.prep_env(deploy_settings, net_settings, inventory,
413 opnfv_env, net_env_target, APEX_TEMP_DIR)
414 oc_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR,
417 shutil.copyfile(sdn_image, os.path.join(APEX_TEMP_DIR,
418 'overcloud-full.qcow2'))
421 os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
424 oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
425 APEX_TEMP_DIR, args.virtual,
426 os.path.basename(opnfv_env),
428 deploy_playbook = os.path.join(args.lib_dir, ANSIBLE_PATH,
429 'deploy_overcloud.yml')
430 virt_env = 'virtual-environment.yaml'
431 bm_env = 'baremetal-environment.yaml'
432 for p_env in virt_env, bm_env:
433 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
434 os.path.join(APEX_TEMP_DIR, p_env))
436 # Start Overcloud Deployment
437 logging.info("Executing Overcloud Deployment...")
439 deploy_vars['virtual'] = args.virtual
440 deploy_vars['debug'] = args.debug
441 deploy_vars['aarch64'] = platform.machine() == 'aarch64'
442 deploy_vars['introspect'] = not (args.virtual or
443 deploy_vars['aarch64'] or
445 deploy_vars['dns_server_args'] = ''
446 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
447 deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
448 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
449 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
450 deploy_vars['upstream'] = upstream
451 deploy_vars['os_version'] = os_version
452 for dns_server in net_settings['dns_servers']:
453 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
456 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
457 user='stack', tmp_dir=APEX_TEMP_DIR)
458 logging.info("Overcloud deployment complete")
460 logging.error("Deployment Failed. Please check log")
463 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
466 logging.info("Executing post deploy configuration")
467 jumphost.configure_bridges(net_settings)
468 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
469 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
471 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
472 'GlobalKnownHostsFile=/dev/null -o ' \
473 'UserKnownHostsFile=/dev/null -o ' \
475 deploy_vars['external_network_cmds'] = \
476 oc_deploy.external_network_cmds(net_settings)
477 # TODO(trozet): just parse all ds_opts as deploy vars one time
478 deploy_vars['gluon'] = ds_opts['gluon']
479 deploy_vars['sdn'] = ds_opts['sdn_controller']
480 for dep_option in 'yardstick', 'dovetail', 'vsperf':
481 if dep_option in ds_opts:
482 deploy_vars[dep_option] = ds_opts[dep_option]
484 deploy_vars[dep_option] = False
485 deploy_vars['dataplane'] = ds_opts['dataplane']
486 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
487 if ds_opts['congress']:
488 deploy_vars['congress_datasources'] = \
489 oc_deploy.create_congress_cmds(overcloudrc)
490 deploy_vars['congress'] = True
492 deploy_vars['congress'] = False
493 deploy_vars['calipso'] = ds_opts.get('calipso', False)
494 deploy_vars['calipso_ip'] = net_settings['networks']['admin'][
495 'installer_vm']['ip']
496 # TODO(trozet): this is probably redundant with getting external
497 # network info from undercloud.py
498 if 'external' in net_settings.enabled_network_list:
499 ext_cidr = net_settings['networks']['external'][0]['cidr']
501 ext_cidr = net_settings['networks']['admin']['cidr']
502 deploy_vars['external_cidr'] = str(ext_cidr)
503 if ext_cidr.version == 6:
504 deploy_vars['external_network_ipv6'] = True
506 deploy_vars['external_network_ipv6'] = False
507 post_undercloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
508 'post_deploy_undercloud.yml')
509 logging.info("Executing post deploy configuration undercloud playbook")
511 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
512 user='stack', tmp_dir=APEX_TEMP_DIR)
513 logging.info("Post Deploy Undercloud Configuration Complete")
515 logging.error("Post Deploy Undercloud Configuration failed. "
518 # Post deploy overcloud node configuration
519 # TODO(trozet): just parse all ds_opts as deploy vars one time
520 deploy_vars['sfc'] = ds_opts['sfc']
521 deploy_vars['vpn'] = ds_opts['vpn']
522 deploy_vars['l2gw'] = ds_opts.get('l2gw')
523 # TODO(trozet): pull all logs and store in tmp dir in overcloud
525 post_overcloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
526 'post_deploy_overcloud.yml')
527 # Run per overcloud node
528 for node, ip in deploy_vars['overcloud_nodes'].items():
529 logging.info("Executing Post deploy overcloud playbook on "
530 "node {}".format(node))
532 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
533 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
534 logging.info("Post Deploy Overcloud Configuration Complete "
535 "for node {}".format(node))
537 logging.error("Post Deploy Overcloud Configuration failed "
538 "for node {}. Please check log".format(node))
540 logging.info("Apex deployment complete")
541 logging.info("Undercloud IP: {}, please connect by doing "
542 "'opnfv-util undercloud'".format(undercloud.ip))
543 # TODO(trozet): add logging here showing controller VIP and horizon url
546 if __name__ == '__main__':