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.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(prefix='apex_tmp')
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'],
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 utils.run_ansible(ansible_args,
284 os.path.join(args.lib_dir, ANSIBLE_PATH,
285 'deploy_dependencies.yml'))
287 if 'external' in net_settings.enabled_network_list:
290 # create all overcloud VMs
291 build_vms(inventory, net_settings, args.deploy_dir)
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,
320 external_network=uc_external)
323 # Generate nic templates
324 for role in 'compute', 'controller':
325 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
326 args.deploy_dir, APEX_TEMP_DIR)
328 undercloud.configure(net_settings,
329 os.path.join(args.lib_dir, ANSIBLE_PATH,
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['aarch64'] = platform.machine() == 'aarch64'
358 deploy_vars['dns_server_args'] = ''
359 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
360 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
361 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
362 for dns_server in net_settings['dns_servers']:
363 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
366 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
367 user='stack', tmp_dir=APEX_TEMP_DIR)
368 logging.info("Overcloud deployment complete")
370 logging.error("Deployment Failed. Please check log")
373 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
376 logging.info("Executing post deploy configuration")
377 jumphost.configure_bridges(net_settings)
378 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
379 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
381 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
382 'GlobalKnownHostsFile=/dev/null -o ' \
383 'UserKnownHostsFile=/dev/null -o ' \
385 deploy_vars['external_network_cmds'] = \
386 overcloud_deploy.external_network_cmds(net_settings)
387 # TODO(trozet): just parse all ds_opts as deploy vars one time
388 ds_opts = deploy_settings['deploy_options']
389 deploy_vars['gluon'] = ds_opts['gluon']
390 deploy_vars['sdn'] = ds_opts['sdn_controller']
391 for dep_option in 'yardstick', 'dovetail', 'vsperf':
392 if dep_option in ds_opts:
393 deploy_vars[dep_option] = ds_opts[dep_option]
395 deploy_vars[dep_option] = False
396 deploy_vars['dataplane'] = ds_opts['dataplane']
397 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
398 if ds_opts['congress']:
399 deploy_vars['congress_datasources'] = \
400 overcloud_deploy.create_congress_cmds(overcloudrc)
401 deploy_vars['congress'] = True
403 deploy_vars['congress'] = False
404 # TODO(trozet): this is probably redundant with getting external
405 # network info from undercloud.py
406 if 'external' in net_settings.enabled_network_list:
407 ext_cidr = net_settings['networks']['external'][0]['cidr']
409 ext_cidr = net_settings['networks']['admin']['cidr']
410 deploy_vars['external_cidr'] = str(ext_cidr)
411 if ext_cidr.version == 6:
412 deploy_vars['external_network_ipv6'] = True
414 deploy_vars['external_network_ipv6'] = False
415 post_undercloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
416 'post_deploy_undercloud.yml')
417 logging.info("Executing post deploy configuration undercloud playbook")
419 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
420 user='stack', tmp_dir=APEX_TEMP_DIR)
421 logging.info("Post Deploy Undercloud Configuration Complete")
423 logging.error("Post Deploy Undercloud Configuration failed. "
426 # Post deploy overcloud node configuration
427 # TODO(trozet): just parse all ds_opts as deploy vars one time
428 deploy_vars['sfc'] = ds_opts['sfc']
429 deploy_vars['vpn'] = ds_opts['vpn']
430 # TODO(trozet): pull all logs and store in tmp dir in overcloud
432 post_overcloud = os.path.join(args.lib_dir, ANSIBLE_PATH,
433 'post_deploy_overcloud.yml')
434 # Run per overcloud node
435 for node, ip in deploy_vars['overcloud_nodes'].items():
436 logging.info("Executing Post deploy overcloud playbook on "
437 "node {}".format(node))
439 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
440 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
441 logging.info("Post Deploy Overcloud Configuration Complete "
442 "for node {}".format(node))
444 logging.error("Post Deploy Overcloud Configuration failed "
445 "for node {}. Please check log".format(node))
447 logging.info("Apex deployment complete")
448 logging.info("Undercloud IP: {}, please connect by doing "
449 "'opnfv-util undercloud'".format(undercloud.ip))
450 # TODO(trozet): add logging here showing controller VIP and horizon url
451 if __name__ == '__main__':