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),
386 os_version=os_version)
389 # Generate nic templates
390 for role in 'compute', 'controller':
391 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
392 args.deploy_dir, APEX_TEMP_DIR)
394 undercloud.configure(net_settings, deploy_settings,
395 os.path.join(args.lib_dir, ANSIBLE_PATH,
396 'configure_undercloud.yml'),
399 # Prepare overcloud-full.qcow2
400 logging.info("Preparing Overcloud for deployment...")
401 if os_version != 'ocata':
402 net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
403 net_data = network_data.create_network_data(net_settings,
407 if upstream and args.env_file == 'opnfv-environment.yaml':
408 # Override the env_file if it is defaulted to opnfv
409 # opnfv env file will not work with upstream
410 args.env_file = 'upstream-environment.yaml'
411 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
413 oc_deploy.prep_env(deploy_settings, net_settings, inventory,
414 opnfv_env, net_env_target, APEX_TEMP_DIR)
415 oc_deploy.prep_image(deploy_settings, net_settings, sdn_image,
416 APEX_TEMP_DIR, root_pw=root_pw)
418 shutil.copyfile(sdn_image, os.path.join(APEX_TEMP_DIR,
419 'overcloud-full.qcow2'))
422 os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
425 oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
426 APEX_TEMP_DIR, args.virtual,
427 os.path.basename(opnfv_env),
429 deploy_playbook = os.path.join(args.lib_dir, ANSIBLE_PATH,
430 'deploy_overcloud.yml')
431 virt_env = 'virtual-environment.yaml'
432 bm_env = 'baremetal-environment.yaml'
433 for p_env in virt_env, bm_env:
434 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
435 os.path.join(APEX_TEMP_DIR, p_env))
437 # Start Overcloud Deployment
438 logging.info("Executing Overcloud Deployment...")
440 deploy_vars['virtual'] = args.virtual
441 deploy_vars['debug'] = args.debug
442 deploy_vars['aarch64'] = platform.machine() == 'aarch64'
443 deploy_vars['introspect'] = not (args.virtual or
444 deploy_vars['aarch64'] or
446 deploy_vars['dns_server_args'] = ''
447 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
448 deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
449 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
450 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
451 deploy_vars['upstream'] = upstream
452 deploy_vars['os_version'] = os_version
453 deploy_vars['http_proxy'] = net_settings.get('http_proxy', '')
454 deploy_vars['https_proxy'] = net_settings.get('https_proxy', '')
455 for dns_server in net_settings['dns_servers']:
456 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
459 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
460 user='stack', tmp_dir=APEX_TEMP_DIR)
461 logging.info("Overcloud deployment complete")
463 logging.error("Deployment Failed. Please check log")
466 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
469 logging.info("Executing post deploy configuration")
470 jumphost.configure_bridges(net_settings)
471 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
472 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
474 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
475 'GlobalKnownHostsFile=/dev/null -o ' \
476 'UserKnownHostsFile=/dev/null -o ' \
478 deploy_vars['external_network_cmds'] = \
479 oc_deploy.external_network_cmds(net_settings)
480 # TODO(trozet): just parse all ds_opts as deploy vars one time
481 deploy_vars['gluon'] = ds_opts['gluon']
482 deploy_vars['sdn'] = ds_opts['sdn_controller']
483 for dep_option in 'yardstick', 'dovetail', 'vsperf':
484 if dep_option in ds_opts:
485 deploy_vars[dep_option] = ds_opts[dep_option]
487 deploy_vars[dep_option] = False
488 deploy_vars['dataplane'] = ds_opts['dataplane']
489 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
490 if ds_opts['congress']:
491 deploy_vars['congress_datasources'] = \
492 oc_deploy.create_congress_cmds(overcloudrc)
493 deploy_vars['congress'] = True
495 deploy_vars['congress'] = False
496 deploy_vars['calipso'] = ds_opts.get('calipso', False)
497 deploy_vars['calipso_ip'] = net_settings['networks']['admin'][
498 'installer_vm']['ip']
499 # TODO(trozet): this is probably redundant with getting external
500 # network info from undercloud.py
501 if 'external' in net_settings.enabled_network_list:
502 ext_cidr = net_settings['networks']['external'][0]['cidr']
504 ext_cidr = net_settings['networks']['admin']['cidr']
505 deploy_vars['external_cidr'] = str(ext_cidr)
506 if ext_cidr.version == 6:
507 deploy_vars['external_network_ipv6'] = True
509 deploy_vars['external_network_ipv6'] = False
510 post_undercloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
511 'post_deploy_undercloud.yml')
512 logging.info("Executing post deploy configuration undercloud playbook")
514 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
515 user='stack', tmp_dir=APEX_TEMP_DIR)
516 logging.info("Post Deploy Undercloud Configuration Complete")
518 logging.error("Post Deploy Undercloud Configuration failed. "
521 # Post deploy overcloud node configuration
522 # TODO(trozet): just parse all ds_opts as deploy vars one time
523 deploy_vars['sfc'] = ds_opts['sfc']
524 deploy_vars['vpn'] = ds_opts['vpn']
525 deploy_vars['l2gw'] = ds_opts.get('l2gw')
526 deploy_vars['sriov'] = ds_opts.get('sriov')
527 # TODO(trozet): pull all logs and store in tmp dir in overcloud
529 post_overcloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
530 'post_deploy_overcloud.yml')
531 # Run per overcloud node
532 for node, ip in deploy_vars['overcloud_nodes'].items():
533 logging.info("Executing Post deploy overcloud playbook on "
534 "node {}".format(node))
536 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
537 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
538 logging.info("Post Deploy Overcloud Configuration Complete "
539 "for node {}".format(node))
541 logging.error("Post Deploy Overcloud Configuration failed "
542 "for node {}. Please check log".format(node))
544 logging.info("Apex deployment complete")
545 logging.info("Undercloud IP: {}, please connect by doing "
546 "'opnfv-util undercloud'".format(undercloud.ip))
547 # TODO(trozet): add logging here showing controller VIP and horizon url
550 if __name__ == '__main__':