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