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)
252 # get global deploy params
253 ha_enabled = deploy_settings['global_params']['ha_enabled']
254 introspect = deploy_settings['global_params'].get('introspect', True)
257 if args.virt_compute_ram is None:
258 compute_ram = args.virt_default_ram
260 compute_ram = args.virt_compute_ram
261 if deploy_settings['deploy_options']['sdn_controller'] == \
262 'opendaylight' and args.virt_default_ram < 12:
264 logging.warning('RAM per controller is too low. OpenDaylight '
265 'requires at least 12GB per controller.')
266 logging.info('Increasing RAM per controller to 12GB')
267 elif args.virt_default_ram < 10:
269 logging.warning('RAM per controller is too low. nosdn '
270 'requires at least 10GB per controller.')
271 logging.info('Increasing RAM per controller to 10GB')
273 control_ram = args.virt_default_ram
274 if ha_enabled and args.virt_compute_nodes < 2:
275 logging.debug('HA enabled, bumping number of compute nodes to 2')
276 args.virt_compute_nodes = 2
277 virt_utils.generate_inventory(args.inventory_file, ha_enabled,
278 num_computes=args.virt_compute_nodes,
279 controller_ram=control_ram * 1024,
280 compute_ram=compute_ram * 1024,
283 inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
285 validate_cross_settings(deploy_settings, net_settings, inventory)
286 ds_opts = deploy_settings['deploy_options']
288 deploy_settings_file = os.path.join(APEX_TEMP_DIR,
289 'apex_deploy_settings.yaml')
290 utils.dump_yaml(utils.dict_objects_to_str(deploy_settings),
291 deploy_settings_file)
292 logging.info("File created: {}".format(deploy_settings_file))
293 network_settings_file = os.path.join(APEX_TEMP_DIR,
294 'apex_network_settings.yaml')
295 utils.dump_yaml(utils.dict_objects_to_str(net_settings),
296 network_settings_file)
297 logging.info("File created: {}".format(network_settings_file))
298 deploy_quickstart(args, deploy_settings_file, network_settings_file,
301 # TODO (trozet): add logic back from:
302 # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
304 'virsh_enabled_networks': net_settings.enabled_network_list
306 utils.run_ansible(ansible_args,
307 os.path.join(args.lib_dir, ANSIBLE_PATH,
308 'deploy_dependencies.yml'))
310 if 'external' in net_settings.enabled_network_list:
313 # create all overcloud VMs
314 build_vms(inventory, net_settings, args.deploy_dir)
316 # Attach interfaces to jumphost for baremetal deployment
317 jump_networks = ['admin']
319 jump_networks.append('external')
320 for network in jump_networks:
321 if network == 'external':
322 # TODO(trozet): enable vlan secondary external networks
323 iface = net_settings['networks'][network][0][
324 'installer_vm']['members'][0]
326 iface = net_settings['networks'][network]['installer_vm'][
328 bridge = "br-{}".format(network)
329 jumphost.attach_interface_to_ovs(bridge, iface, network)
330 instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
331 with open(instackenv_json, 'w') as fh:
332 json.dump(inventory, fh)
334 # Create and configure undercloud
336 root_pw = constants.DEBUG_OVERCLOUD_PW
340 upstream = (os_version != constants.DEFAULT_OS_VERSION or
342 if os_version == 'master':
345 branch = "stable/{}".format(os_version)
347 logging.info("Deploying with upstream artifacts for OpenStack "
348 "{}".format(os_version))
349 args.image_dir = os.path.join(args.image_dir, os_version)
350 upstream_url = constants.UPSTREAM_RDO.replace(
351 constants.DEFAULT_OS_VERSION, os_version)
352 upstream_targets = ['overcloud-full.tar', 'undercloud.qcow2']
353 utils.fetch_upstream_and_unpack(args.image_dir, upstream_url,
355 sdn_image = os.path.join(args.image_dir, 'overcloud-full.qcow2')
356 if ds_opts['sdn_controller'] == 'opendaylight':
357 logging.info("Preparing upstream image with OpenDaylight")
358 oc_builder.inject_opendaylight(
359 odl_version=ds_opts['odl_version'],
361 tmp_dir=APEX_TEMP_DIR
363 # copy undercloud so we don't taint upstream fetch
364 uc_image = os.path.join(args.image_dir, 'undercloud_mod.qcow2')
365 uc_fetch_img = os.path.join(args.image_dir, 'undercloud.qcow2')
366 shutil.copyfile(uc_fetch_img, uc_image)
367 # prep undercloud with required packages
368 uc_builder.add_upstream_packages(uc_image)
369 # add patches from upstream to undercloud and overcloud
370 logging.info('Adding patches to undercloud')
371 patches = deploy_settings['global_params']['patches']
372 c_builder.add_upstream_patches(patches['undercloud'], uc_image,
373 APEX_TEMP_DIR, branch)
374 logging.info('Adding patches to overcloud')
375 c_builder.add_upstream_patches(patches['overcloud'], sdn_image,
376 APEX_TEMP_DIR, branch)
378 sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
379 uc_image = 'undercloud.qcow2'
380 undercloud = uc_lib.Undercloud(args.image_dir,
383 external_network=uc_external,
384 image_name=os.path.basename(uc_image))
387 # Generate nic templates
388 for role in 'compute', 'controller':
389 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
390 args.deploy_dir, APEX_TEMP_DIR)
392 undercloud.configure(net_settings,
393 os.path.join(args.lib_dir, ANSIBLE_PATH,
394 'configure_undercloud.yml'),
397 # Prepare overcloud-full.qcow2
398 logging.info("Preparing Overcloud for deployment...")
399 if os_version != 'ocata':
400 net_data_file = os.path.join(APEX_TEMP_DIR, 'network_data.yaml')
401 net_data = network_data.create_network_data(net_settings,
405 if upstream and args.env_file == 'opnfv-environment.yaml':
406 # Override the env_file if it is defaulted to opnfv
407 # opnfv env file will not work with upstream
408 args.env_file = 'upstream-environment.yaml'
409 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
411 oc_deploy.prep_env(deploy_settings, net_settings, inventory,
412 opnfv_env, net_env_target, APEX_TEMP_DIR)
413 oc_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR,
416 shutil.copyfile(sdn_image, os.path.join(APEX_TEMP_DIR,
417 'overcloud-full.qcow2'))
420 os.path.join(APEX_TEMP_DIR, os.path.basename(opnfv_env))
423 oc_deploy.create_deploy_cmd(deploy_settings, net_settings, inventory,
424 APEX_TEMP_DIR, args.virtual,
425 os.path.basename(opnfv_env),
427 deploy_playbook = os.path.join(args.lib_dir, ANSIBLE_PATH,
428 'deploy_overcloud.yml')
429 virt_env = 'virtual-environment.yaml'
430 bm_env = 'baremetal-environment.yaml'
431 for p_env in virt_env, bm_env:
432 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
433 os.path.join(APEX_TEMP_DIR, p_env))
435 # Start Overcloud Deployment
436 logging.info("Executing Overcloud Deployment...")
438 deploy_vars['virtual'] = args.virtual
439 deploy_vars['debug'] = args.debug
440 deploy_vars['aarch64'] = platform.machine() == 'aarch64'
441 deploy_vars['introspect'] = not (args.virtual or
442 deploy_vars['aarch64'] or
444 deploy_vars['dns_server_args'] = ''
445 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
446 deploy_vars['apex_env_file'] = os.path.basename(opnfv_env)
447 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
448 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
449 deploy_vars['upstream'] = upstream
450 deploy_vars['os_version'] = os_version
451 for dns_server in net_settings['dns_servers']:
452 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
455 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
456 user='stack', tmp_dir=APEX_TEMP_DIR)
457 logging.info("Overcloud deployment complete")
459 logging.error("Deployment Failed. Please check log")
462 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
465 logging.info("Executing post deploy configuration")
466 jumphost.configure_bridges(net_settings)
467 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
468 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
470 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
471 'GlobalKnownHostsFile=/dev/null -o ' \
472 'UserKnownHostsFile=/dev/null -o ' \
474 deploy_vars['external_network_cmds'] = \
475 oc_deploy.external_network_cmds(net_settings)
476 # TODO(trozet): just parse all ds_opts as deploy vars one time
477 deploy_vars['gluon'] = ds_opts['gluon']
478 deploy_vars['sdn'] = ds_opts['sdn_controller']
479 for dep_option in 'yardstick', 'dovetail', 'vsperf':
480 if dep_option in ds_opts:
481 deploy_vars[dep_option] = ds_opts[dep_option]
483 deploy_vars[dep_option] = False
484 deploy_vars['dataplane'] = ds_opts['dataplane']
485 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
486 if ds_opts['congress']:
487 deploy_vars['congress_datasources'] = \
488 oc_deploy.create_congress_cmds(overcloudrc)
489 deploy_vars['congress'] = True
491 deploy_vars['congress'] = False
492 deploy_vars['calipso'] = ds_opts.get('calipso', False)
493 deploy_vars['calipso_ip'] = net_settings['networks']['admin'][
494 'installer_vm']['ip']
495 # TODO(trozet): this is probably redundant with getting external
496 # network info from undercloud.py
497 if 'external' in net_settings.enabled_network_list:
498 ext_cidr = net_settings['networks']['external'][0]['cidr']
500 ext_cidr = net_settings['networks']['admin']['cidr']
501 deploy_vars['external_cidr'] = str(ext_cidr)
502 if ext_cidr.version == 6:
503 deploy_vars['external_network_ipv6'] = True
505 deploy_vars['external_network_ipv6'] = False
506 post_undercloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
507 'post_deploy_undercloud.yml')
508 logging.info("Executing post deploy configuration undercloud playbook")
510 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
511 user='stack', tmp_dir=APEX_TEMP_DIR)
512 logging.info("Post Deploy Undercloud Configuration Complete")
514 logging.error("Post Deploy Undercloud Configuration failed. "
517 # Post deploy overcloud node configuration
518 # TODO(trozet): just parse all ds_opts as deploy vars one time
519 deploy_vars['sfc'] = ds_opts['sfc']
520 deploy_vars['vpn'] = ds_opts['vpn']
521 # TODO(trozet): pull all logs and store in tmp dir in overcloud
523 post_overcloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
524 'post_deploy_overcloud.yml')
525 # Run per overcloud node
526 for node, ip in deploy_vars['overcloud_nodes'].items():
527 logging.info("Executing Post deploy overcloud playbook on "
528 "node {}".format(node))
530 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
531 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
532 logging.info("Post Deploy Overcloud Configuration Complete "
533 "for node {}".format(node))
535 logging.error("Post Deploy Overcloud Configuration failed "
536 "for node {}. Please check log".format(node))
538 logging.info("Apex deployment complete")
539 logging.info("Undercloud IP: {}, please connect by doing "
540 "'opnfv-util undercloud'".format(undercloud.ip))
541 # TODO(trozet): add logging here showing controller VIP and horizon url
544 if __name__ == '__main__':