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 validate_deploy_args(args)
239 deploy_settings = DeploySettings(args.deploy_settings_file)
240 logging.info("Deploy settings are:\n {}".format(pprint.pformat(
242 net_settings = NetworkSettings(args.network_settings_file)
243 logging.info("Network settings are:\n {}".format(pprint.pformat(
245 os_version = deploy_settings['deploy_options']['os_version']
246 net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
247 net_env = NetworkEnvironment(net_settings, net_env_file,
248 os_version=os_version)
249 net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
250 utils.dump_yaml(dict(net_env), net_env_target)
251 ha_enabled = deploy_settings['global_params']['ha_enabled']
253 if args.virt_compute_ram is None:
254 compute_ram = args.virt_default_ram
256 compute_ram = args.virt_compute_ram
257 if deploy_settings['deploy_options']['sdn_controller'] == \
258 'opendaylight' and args.virt_default_ram < 12:
260 logging.warning('RAM per controller is too low. OpenDaylight '
261 'requires at least 12GB per controller.')
262 logging.info('Increasing RAM per controller to 12GB')
263 elif args.virt_default_ram < 10:
265 logging.warning('RAM per controller is too low. nosdn '
266 'requires at least 10GB per controller.')
267 logging.info('Increasing RAM per controller to 10GB')
269 control_ram = args.virt_default_ram
270 if ha_enabled and args.virt_compute_nodes < 2:
271 logging.debug('HA enabled, bumping number of compute nodes to 2')
272 args.virt_compute_nodes = 2
273 virt_utils.generate_inventory(args.inventory_file, ha_enabled,
274 num_computes=args.virt_compute_nodes,
275 controller_ram=control_ram * 1024,
276 compute_ram=compute_ram * 1024,
279 inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
281 validate_cross_settings(deploy_settings, net_settings, inventory)
282 ds_opts = deploy_settings['deploy_options']
284 deploy_settings_file = os.path.join(APEX_TEMP_DIR,
285 'apex_deploy_settings.yaml')
286 utils.dump_yaml(utils.dict_objects_to_str(deploy_settings),
287 deploy_settings_file)
288 logging.info("File created: {}".format(deploy_settings_file))
289 network_settings_file = os.path.join(APEX_TEMP_DIR,
290 'apex_network_settings.yaml')
291 utils.dump_yaml(utils.dict_objects_to_str(net_settings),
292 network_settings_file)
293 logging.info("File created: {}".format(network_settings_file))
294 deploy_quickstart(args, deploy_settings_file, network_settings_file,
297 # TODO (trozet): add logic back from:
298 # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
300 'virsh_enabled_networks': net_settings.enabled_network_list
302 utils.run_ansible(ansible_args,
303 os.path.join(args.lib_dir, ANSIBLE_PATH,
304 'deploy_dependencies.yml'))
306 if 'external' in net_settings.enabled_network_list:
309 # create all overcloud VMs
310 build_vms(inventory, net_settings, args.deploy_dir)
312 # Attach interfaces to jumphost for baremetal deployment
313 jump_networks = ['admin']
315 jump_networks.append('external')
316 for network in jump_networks:
317 if network == 'external':
318 # TODO(trozet): enable vlan secondary external networks
319 iface = net_settings['networks'][network][0][
320 'installer_vm']['members'][0]
322 iface = net_settings['networks'][network]['installer_vm'][
324 bridge = "br-{}".format(network)
325 jumphost.attach_interface_to_ovs(bridge, iface, network)
326 instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
327 with open(instackenv_json, 'w') as fh:
328 json.dump(inventory, fh)
330 # Create and configure undercloud
332 root_pw = constants.DEBUG_OVERCLOUD_PW
336 upstream = (os_version != constants.DEFAULT_OS_VERSION or
338 if os_version == 'master':
341 branch = "stable/{}".format(os_version)
343 logging.info("Deploying with upstream artifacts for OpenStack "
344 "{}".format(os_version))
345 args.image_dir = os.path.join(args.image_dir, os_version)
346 upstream_url = constants.UPSTREAM_RDO.replace(
347 constants.DEFAULT_OS_VERSION, os_version)
348 upstream_targets = ['overcloud-full.tar', 'undercloud.qcow2']
349 utils.fetch_upstream_and_unpack(args.image_dir, upstream_url,
351 sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
352 if ds_opts['sdn_controller'] == 'opendaylight':
353 logging.info("Preparing upstream image with OpenDaylight")
354 oc_builder.inject_opendaylight(
355 odl_version=ds_opts['odl_version'],
357 tmp_dir=APEX_TEMP_DIR
359 # copy undercloud so we don't taint upstream fetch
360 uc_image = os.path.join(args.image_dir, 'undercloud_mod.qcow2')
361 uc_fetch_img = os.path.join(args.image_dir, 'undercloud.qcow2')
362 shutil.copyfile(uc_fetch_img, uc_image)
363 # prep undercloud with required packages
364 uc_builder.add_upstream_packages(uc_image)
365 # add patches from upstream to undercloud and overcloud
366 logging.info('Adding patches to undercloud')
367 patches = deploy_settings['global_params']['patches']
368 c_builder.add_upstream_patches(patches['undercloud'], uc_image,
369 APEX_TEMP_DIR, branch)
370 logging.info('Adding patches to overcloud')
371 c_builder.add_upstream_patches(patches['overcloud'], sdn_image,
372 APEX_TEMP_DIR, branch)
374 sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
375 uc_image = 'undercloud.qcow2'
376 undercloud = uc_lib.Undercloud(args.image_dir,
379 external_network=uc_external,
380 image_name=os.path.basename(uc_image))
383 # Generate nic templates
384 for role in 'compute', 'controller':
385 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
386 args.deploy_dir, APEX_TEMP_DIR)
388 undercloud.configure(net_settings,
389 os.path.join(args.lib_dir, ANSIBLE_PATH,
390 'configure_undercloud.yml'),
393 # Prepare overcloud-full.qcow2
394 logging.info("Preparing Overcloud for deployment...")
395 if os_version != 'ocata':
396 net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
397 net_data = network_data.create_network_data(net_settings,
401 if upstream and args.env_file == 'opnfv-environment.yaml':
402 # Override the env_file if it is defaulted to opnfv
403 # opnfv env file will not work with upstream
404 args.env_file = 'upstream-environment.yaml'
405 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
407 oc_deploy.prep_env(deploy_settings, net_settings, inventory,
408 opnfv_env, net_env_target, APEX_TEMP_DIR)
409 oc_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR,
412 shutil.copyfile(sdn_image, os.path.join(APEX_TEMP_DIR,
413 'overcloud-full.qcow2'))
416 os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
419 oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
420 APEX_TEMP_DIR, args.virtual,
421 os.path.basename(opnfv_env),
423 deploy_playbook = os.path.join(args.lib_dir, ANSIBLE_PATH,
424 'deploy_overcloud.yml')
425 virt_env = 'virtual-environment.yaml'
426 bm_env = 'baremetal-environment.yaml'
427 for p_env in virt_env, bm_env:
428 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
429 os.path.join(APEX_TEMP_DIR, p_env))
431 # Start Overcloud Deployment
432 logging.info("Executing Overcloud Deployment...")
434 deploy_vars['virtual'] = args.virtual
435 deploy_vars['debug'] = args.debug
436 deploy_vars['aarch64'] = platform.machine() == 'aarch64'
437 deploy_vars['dns_server_args'] = ''
438 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
439 deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
440 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
441 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
442 deploy_vars['upstream'] = upstream
443 deploy_vars['os_version'] = os_version
444 for dns_server in net_settings['dns_servers']:
445 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
448 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
449 user='stack', tmp_dir=APEX_TEMP_DIR)
450 logging.info("Overcloud deployment complete")
452 logging.error("Deployment Failed. Please check log")
455 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
458 logging.info("Executing post deploy configuration")
459 jumphost.configure_bridges(net_settings)
460 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
461 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
463 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
464 'GlobalKnownHostsFile=/dev/null -o ' \
465 'UserKnownHostsFile=/dev/null -o ' \
467 deploy_vars['external_network_cmds'] = \
468 oc_deploy.external_network_cmds(net_settings)
469 # TODO(trozet): just parse all ds_opts as deploy vars one time
470 deploy_vars['gluon'] = ds_opts['gluon']
471 deploy_vars['sdn'] = ds_opts['sdn_controller']
472 for dep_option in 'yardstick', 'dovetail', 'vsperf':
473 if dep_option in ds_opts:
474 deploy_vars[dep_option] = ds_opts[dep_option]
476 deploy_vars[dep_option] = False
477 deploy_vars['dataplane'] = ds_opts['dataplane']
478 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
479 if ds_opts['congress']:
480 deploy_vars['congress_datasources'] = \
481 oc_deploy.create_congress_cmds(overcloudrc)
482 deploy_vars['congress'] = True
484 deploy_vars['congress'] = False
485 deploy_vars['calipso'] = ds_opts.get('calipso', False)
486 deploy_vars['calipso_ip'] = net_settings['networks']['admin'][
487 'installer_vm']['ip']
488 # TODO(trozet): this is probably redundant with getting external
489 # network info from undercloud.py
490 if 'external' in net_settings.enabled_network_list:
491 ext_cidr = net_settings['networks']['external'][0]['cidr']
493 ext_cidr = net_settings['networks']['admin']['cidr']
494 deploy_vars['external_cidr'] = str(ext_cidr)
495 if ext_cidr.version == 6:
496 deploy_vars['external_network_ipv6'] = True
498 deploy_vars['external_network_ipv6'] = False
499 post_undercloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
500 'post_deploy_undercloud.yml')
501 logging.info("Executing post deploy configuration undercloud playbook")
503 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
504 user='stack', tmp_dir=APEX_TEMP_DIR)
505 logging.info("Post Deploy Undercloud Configuration Complete")
507 logging.error("Post Deploy Undercloud Configuration failed. "
510 # Post deploy overcloud node configuration
511 # TODO(trozet): just parse all ds_opts as deploy vars one time
512 deploy_vars['sfc'] = ds_opts['sfc']
513 deploy_vars['vpn'] = ds_opts['vpn']
514 # TODO(trozet): pull all logs and store in tmp dir in overcloud
516 post_overcloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
517 'post_deploy_overcloud.yml')
518 # Run per overcloud node
519 for node, ip in deploy_vars['overcloud_nodes'].items():
520 logging.info("Executing Post deploy overcloud playbook on "
521 "node {}".format(node))
523 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
524 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
525 logging.info("Post Deploy Overcloud Configuration Complete "
526 "for node {}".format(node))
528 logging.error("Post Deploy Overcloud Configuration failed "
529 "for node {}. Please check log".format(node))
531 logging.info("Apex deployment complete")
532 logging.info("Undercloud IP: {}, please connect by doing "
533 "'opnfv-util undercloud'".format(undercloud.ip))
534 # TODO(trozet): add logging here showing controller VIP and horizon url
537 if __name__ == '__main__':