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(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'],
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")
373 logging.error("Deployment Failed. Please check log")
376 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
379 logging.info("Executing post deploy configuration")
380 jumphost.configure_bridges(net_settings)
381 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
382 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
384 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
385 'GlobalKnownHostsFile=/dev/null -o ' \
386 'UserKnownHostsFile=/dev/null -o ' \
388 deploy_vars['external_network_cmds'] = \
389 overcloud_deploy.external_network_cmds(net_settings)
390 # TODO(trozet): just parse all ds_opts as deploy vars one time
391 ds_opts = deploy_settings['deploy_options']
392 deploy_vars['gluon'] = ds_opts['gluon']
393 deploy_vars['sdn'] = ds_opts['sdn_controller']
394 for dep_option in 'yardstick', 'dovetail', 'vsperf':
395 if dep_option in ds_opts:
396 deploy_vars[dep_option] = ds_opts[dep_option]
398 deploy_vars[dep_option] = False
399 deploy_vars['dataplane'] = ds_opts['dataplane']
400 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
401 if ds_opts['congress']:
402 deploy_vars['congress_datasources'] = \
403 overcloud_deploy.create_congress_cmds(overcloudrc)
404 deploy_vars['congress'] = True
406 deploy_vars['congress'] = False
407 # TODO(trozet): this is probably redundant with getting external
408 # network info from undercloud.py
409 if 'external' in net_settings.enabled_network_list:
410 ext_cidr = net_settings['networks']['external'][0]['cidr']
412 ext_cidr = net_settings['networks']['admin']['cidr']
413 deploy_vars['external_cidr'] = str(ext_cidr)
414 if ext_cidr.version == 6:
415 deploy_vars['external_network_ipv6'] = True
417 deploy_vars['external_network_ipv6'] = False
418 post_undercloud = os.path.join(args.lib_dir, ansible_path,
419 'post_deploy_undercloud.yml')
420 logging.info("Executing post deploy configuration undercloud playbook")
422 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
423 user='stack', tmp_dir=APEX_TEMP_DIR)
424 logging.info("Post Deploy Undercloud Configuration Complete")
426 logging.error("Post Deploy Undercloud Configuration failed. "
429 # Post deploy overcloud node configuration
430 # TODO(trozet): just parse all ds_opts as deploy vars one time
431 deploy_vars['sfc'] = ds_opts['sfc']
432 deploy_vars['vpn'] = ds_opts['vpn']
433 # TODO(trozet): pull all logs and store in tmp dir in overcloud
435 post_overcloud = os.path.join(args.lib_dir, ansible_path,
436 'post_deploy_overcloud.yml')
437 # Run per overcloud node
438 for node, ip in deploy_vars['overcloud_nodes'].items():
439 logging.info("Executing Post deploy overcloud playbook on "
440 "node {}".format(node))
442 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
443 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
444 logging.info("Post Deploy Overcloud Configuration Complete "
445 "for node {}".format(node))
447 logging.error("Post Deploy Overcloud Configuration failed "
448 "for node {}. Please check log".format(node))
450 logging.info("Apex deployment complete")
451 logging.info("Undercloud IP: {}, please connect by doing "
452 "'opnfv-util undercloud'".format(undercloud.ip))
453 # TODO(trozet): add logging here showing controller VIP and horizon url
454 if __name__ == '__main__':