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 ##############################################################################
21 import apex.virtual.configure_vm as vm_lib
22 import apex.virtual.virtual_utils as virt_utils
23 from apex import DeploySettings
24 from apex import Inventory
25 from apex import NetworkEnvironment
26 from apex import NetworkSettings
27 from apex.common import utils
28 from apex.common import constants
29 from apex.common import parsers
30 from apex.common.exceptions import ApexDeployException
31 from apex.network import jumphost
32 from apex.undercloud import undercloud as uc_lib
33 from apex.overcloud import config as oc_cfg
34 from apex.overcloud import overcloud_deploy
36 APEX_TEMP_DIR = tempfile.mkdtemp()
37 ANSIBLE_PATH = 'ansible/playbooks'
38 SDN_IMAGE = 'overcloud-full-opendaylight.qcow2'
41 def deploy_quickstart(args, deploy_settings_file, network_settings_file,
46 def validate_cross_settings(deploy_settings, net_settings, inventory):
48 Used to validate compatibility across settings file.
49 :param deploy_settings: parsed settings for deployment
50 :param net_settings: parsed settings for network
51 :param inventory: parsed inventory file
55 if deploy_settings['deploy_options']['dataplane'] != 'ovs' and 'tenant' \
56 not in net_settings.enabled_network_list:
57 raise ApexDeployException("Setting a DPDK based dataplane requires"
58 "a dedicated NIC for tenant network")
60 # TODO(trozet): add more checks here like RAM for ODL, etc
61 # check if odl_vpp_netvirt is true and vpp is set
62 # Check if fdio and nosdn:
63 # tenant_nic_mapping_controller_members" ==
64 # "$tenant_nic_mapping_compute_members
67 def build_vms(inventory, network_settings):
69 Creates VMs and configures vbmc and host
71 :param network_settings:
75 for idx, node in enumerate(inventory['nodes']):
76 name = 'baremetal{}'.format(idx)
77 volume = name + ".qcow2"
78 volume_path = os.path.join(constants.LIBVIRT_VOLUME_PATH, volume)
79 # TODO(trozet): add back aarch64
80 # TODO(trozet): add error checking
83 baremetal_interfaces=network_settings.enabled_network_list,
84 memory=node['memory'], cpus=node['cpu'],
85 macs=[node['mac_address']])
86 virt_utils.host_setup({name: node['pm_port']})
89 def create_deploy_parser():
90 deploy_parser = argparse.ArgumentParser()
91 deploy_parser.add_argument('--debug', action='store_true', default=False,
92 help="Turn on debug messages")
93 deploy_parser.add_argument('-l', '--log-file',
94 default='./apex_deploy.log',
95 dest='log_file', help="Log file to log to")
96 deploy_parser.add_argument('-d', '--deploy-settings',
97 dest='deploy_settings_file',
99 help='File which contains Apex deploy settings')
100 deploy_parser.add_argument('-n', '--network-settings',
101 dest='network_settings_file',
103 help='File which contains Apex network '
105 deploy_parser.add_argument('-i', '--inventory-file',
106 dest='inventory_file',
108 help='Inventory file which contains POD '
110 deploy_parser.add_argument('-e', '--environment-file',
112 default='opnfv-environment.yaml',
113 help='Provide alternate base env file')
114 deploy_parser.add_argument('-v', '--virtual', action='store_true',
117 help='Enable virtual deployment')
118 deploy_parser.add_argument('--interactive', action='store_true',
120 help='Enable interactive deployment mode which '
121 'requires user to confirm steps of '
123 deploy_parser.add_argument('--virtual-computes',
124 dest='virt_compute_nodes',
127 help='Number of Virtual Compute nodes to create'
128 ' and use during deployment (defaults to 1'
129 ' for noha and 2 for ha)')
130 deploy_parser.add_argument('--virtual-cpus',
134 help='Number of CPUs to use per Overcloud VM in'
135 ' a virtual deployment (defaults to 4)')
136 deploy_parser.add_argument('--virtual-default-ram',
137 dest='virt_default_ram',
140 help='Amount of default RAM to use per '
141 'Overcloud VM in GB (defaults to 8).')
142 deploy_parser.add_argument('--virtual-compute-ram',
143 dest='virt_compute_ram',
146 help='Amount of RAM to use per Overcloud '
147 'Compute VM in GB (defaults to 8). '
148 'Overrides --virtual-default-ram arg for '
150 deploy_parser.add_argument('--deploy-dir',
151 default='/usr/share/opnfv-apex',
152 help='Directory to deploy from which contains '
153 'base config files for deployment')
154 deploy_parser.add_argument('--image-dir',
155 default='/var/opt/opnfv/images',
156 help='Directory which contains '
157 'base disk images for deployment')
158 deploy_parser.add_argument('--lib-dir',
159 default='/usr/share/opnfv-apex',
160 help='Directory path for apex ansible '
161 'and third party libs')
162 deploy_parser.add_argument('--quickstart', action='store_true',
164 help='Use tripleo-quickstart to deploy')
168 def validate_deploy_args(args):
170 Validates arguments for deploy
175 logging.debug('Validating arguments for deployment')
176 if args.virtual and args.inventory_file is not None:
177 logging.error("Virtual enabled but inventory file also given")
178 raise ApexDeployException('You should not specify an inventory file '
179 'with virtual deployments')
181 args.inventory_file = os.path.join(APEX_TEMP_DIR,
182 'inventory-virt.yaml')
183 elif os.path.isfile(args.inventory_file) is False:
184 logging.error("Specified inventory file does not exist: {}".format(
185 args.inventory_file))
186 raise ApexDeployException('Specified inventory file does not exist')
188 for settings_file in (args.deploy_settings_file,
189 args.network_settings_file):
190 if os.path.isfile(settings_file) is False:
191 logging.error("Specified settings file does not "
192 "exist: {}".format(settings_file))
193 raise ApexDeployException('Specified settings file does not '
194 'exist: {}'.format(settings_file))
198 parser = create_deploy_parser()
199 args = parser.parse_args(sys.argv[1:])
200 # FIXME (trozet): this is only needed as a workaround for CI. Remove
202 if os.getenv('IMAGES', False):
203 args.image_dir = os.getenv('IMAGES')
205 log_level = logging.DEBUG
207 log_level = logging.INFO
208 os.makedirs(os.path.dirname(args.log_file), exist_ok=True)
209 formatter = '%(asctime)s %(levelname)s: %(message)s'
210 logging.basicConfig(filename=args.log_file,
212 datefmt='%m/%d/%Y %I:%M:%S %p',
214 console = logging.StreamHandler()
215 console.setLevel(log_level)
216 console.setFormatter(logging.Formatter(formatter))
217 logging.getLogger('').addHandler(console)
218 validate_deploy_args(args)
220 deploy_settings = DeploySettings(args.deploy_settings_file)
221 logging.info("Deploy settings are:\n {}".format(pprint.pformat(
223 net_settings = NetworkSettings(args.network_settings_file)
224 logging.info("Network settings are:\n {}".format(pprint.pformat(
226 net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
227 net_env = NetworkEnvironment(net_settings, net_env_file)
228 net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
229 utils.dump_yaml(dict(net_env), net_env_target)
230 ha_enabled = deploy_settings['global_params']['ha_enabled']
232 if args.virt_compute_ram is None:
233 compute_ram = args.virt_default_ram
235 compute_ram = args.virt_compute_ram
236 if deploy_settings['deploy_options']['sdn_controller'] == \
237 'opendaylight' and args.virt_default_ram < 12:
239 logging.warning('RAM per controller is too low. OpenDaylight '
240 'requires at least 12GB per controller.')
241 logging.info('Increasing RAM per controller to 12GB')
242 elif args.virt_default_ram < 10:
244 logging.warning('RAM per controller is too low. nosdn '
245 'requires at least 10GB per controller.')
246 logging.info('Increasing RAM per controller to 10GB')
248 control_ram = args.virt_default_ram
249 if ha_enabled and args.virt_compute_nodes < 2:
250 logging.debug('HA enabled, bumping number of compute nodes to 2')
251 args.virt_compute_nodes = 2
252 virt_utils.generate_inventory(args.inventory_file, ha_enabled,
253 num_computes=args.virt_compute_nodes,
254 controller_ram=control_ram * 1024,
255 compute_ram=compute_ram * 1024,
258 inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
260 validate_cross_settings(deploy_settings, net_settings, inventory)
263 deploy_settings_file = os.path.join(APEX_TEMP_DIR,
264 'apex_deploy_settings.yaml')
265 utils.dump_yaml(utils.dict_objects_to_str(deploy_settings),
266 deploy_settings_file)
267 logging.info("File created: {}".format(deploy_settings_file))
268 network_settings_file = os.path.join(APEX_TEMP_DIR,
269 'apex_network_settings.yaml')
270 utils.dump_yaml(utils.dict_objects_to_str(net_settings),
271 network_settings_file)
272 logging.info("File created: {}".format(network_settings_file))
273 deploy_quickstart(args, deploy_settings_file, network_settings_file,
276 # TODO (trozet): add logic back from:
277 # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
279 'virsh_enabled_networks': net_settings.enabled_network_list
281 ansible_path = os.path.join(args.lib_dir, ANSIBLE_PATH)
282 utils.run_ansible(ansible_args,
283 os.path.join(args.lib_dir,
285 'deploy_dependencies.yml'))
287 if 'external' in net_settings.enabled_network_list:
290 # create all overcloud VMs
291 build_vms(inventory, net_settings)
293 # Attach interfaces to jumphost for baremetal deployment
294 jump_networks = ['admin']
296 jump_networks.append('external')
297 for network in jump_networks:
298 if network == 'external':
299 # TODO(trozet): enable vlan secondary external networks
300 iface = net_settings['networks'][network][0][
301 'installer_vm']['members'][0]
303 iface = net_settings['networks'][network]['installer_vm'][
305 bridge = "br-{}".format(network)
306 jumphost.attach_interface_to_ovs(bridge, iface, network)
307 # Dump all settings out to temp bash files to be sourced
308 instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
309 with open(instackenv_json, 'w') as fh:
310 json.dump(inventory, fh)
312 # Create and configure undercloud
314 root_pw = constants.DEBUG_OVERCLOUD_PW
317 undercloud = uc_lib.Undercloud(args.image_dir,
319 external_network=uc_external)
322 # Generate nic templates
323 for role in 'compute', 'controller':
324 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
325 args.deploy_dir, APEX_TEMP_DIR)
327 undercloud.configure(net_settings,
328 os.path.join(args.lib_dir,
330 'configure_undercloud.yml'),
333 # Prepare overcloud-full.qcow2
334 logging.info("Preparing Overcloud for deployment...")
335 sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
336 overcloud_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR,
338 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
339 overcloud_deploy.prep_env(deploy_settings, net_settings, opnfv_env,
340 net_env_target, APEX_TEMP_DIR)
341 overcloud_deploy.create_deploy_cmd(deploy_settings, net_settings,
342 inventory, APEX_TEMP_DIR,
343 args.virtual, args.env_file)
344 deploy_playbook = os.path.join(args.lib_dir, ansible_path,
345 'deploy_overcloud.yml')
346 virt_env = 'virtual-environment.yaml'
347 bm_env = 'baremetal-environment.yaml'
348 for p_env in virt_env, bm_env:
349 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
350 os.path.join(APEX_TEMP_DIR, p_env))
352 # Start Overcloud Deployment
353 logging.info("Executing Overcloud Deployment...")
355 deploy_vars['virtual'] = args.virtual
356 deploy_vars['debug'] = args.debug
357 deploy_vars['dns_server_args'] = ''
358 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
359 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
360 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
361 for dns_server in net_settings['dns_servers']:
362 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
365 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
366 user='stack', tmp_dir=APEX_TEMP_DIR)
367 logging.info("Overcloud deployment complete")
368 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
370 logging.error("Deployment Failed. Please check log")
374 logging.info("Executing post deploy configuration")
375 jumphost.configure_bridges(net_settings)
376 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
377 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
379 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
380 'GlobalKnownHostsFile=/dev/null -o ' \
381 'UserKnownHostsFile=/dev/null -o ' \
383 deploy_vars['external_network_cmds'] = \
384 overcloud_deploy.external_network_cmds(net_settings)
385 # TODO(trozet): just parse all ds_opts as deploy vars one time
386 ds_opts = deploy_settings['deploy_options']
387 deploy_vars['gluon'] = ds_opts['gluon']
388 deploy_vars['sdn'] = ds_opts['sdn_controller']
389 for dep_option in 'yardstick', 'dovetail', 'vsperf':
390 if dep_option in ds_opts:
391 deploy_vars[dep_option] = ds_opts[dep_option]
393 deploy_vars[dep_option] = False
394 deploy_vars['dataplane'] = ds_opts['dataplane']
395 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
396 if ds_opts['congress']:
397 deploy_vars['congress_datasources'] = \
398 overcloud_deploy.create_congress_cmds(overcloudrc)
399 deploy_vars['congress'] = True
401 deploy_vars['congress'] = False
402 # TODO(trozet): this is probably redundant with getting external
403 # network info from undercloud.py
404 if 'external' in net_settings.enabled_network_list:
405 ext_cidr = net_settings['networks']['external'][0]['cidr']
407 ext_cidr = net_settings['networks']['admin']['cidr']
408 deploy_vars['external_cidr'] = str(ext_cidr)
409 if ext_cidr.version == 6:
410 deploy_vars['external_network_ipv6'] = True
412 deploy_vars['external_network_ipv6'] = False
413 post_undercloud = os.path.join(args.lib_dir, ansible_path,
414 'post_deploy_undercloud.yml')
415 logging.info("Executing post deploy configuration undercloud playbook")
417 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
418 user='stack', tmp_dir=APEX_TEMP_DIR)
419 logging.info("Post Deploy Undercloud Configuration Complete")
421 logging.error("Post Deploy Undercloud Configuration failed. "
424 # Post deploy overcloud node configuration
425 # TODO(trozet): just parse all ds_opts as deploy vars one time
426 deploy_vars['sfc'] = ds_opts['sfc']
427 deploy_vars['vpn'] = ds_opts['vpn']
428 # TODO(trozet): pull all logs and store in tmp dir in overcloud
430 post_overcloud = os.path.join(args.lib_dir, ansible_path,
431 'post_deploy_overcloud.yml')
432 # Run per overcloud node
433 for node, ip in deploy_vars['overcloud_nodes'].items():
434 logging.info("Executing Post deploy overcloud playbook on "
435 "node {}".format(node))
437 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
438 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
439 logging.info("Post Deploy Overcloud Configuration Complete "
440 "for node {}".format(node))
442 logging.error("Post Deploy Overcloud Configuration failed "
443 "for node {}. Please check log".format(node))
445 logging.info("Apex deployment complete")
446 logging.info("Undercloud IP: {}, please connect by doing "
447 "'opnfv-util undercloud'".format(undercloud.ip))
448 # TODO(trozet): add logging here showing controller VIP and horizon url
449 if __name__ == '__main__':