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,
68 template_dir='/usr/share/opnfv-apex'):
70 Creates VMs and configures vbmc and host
72 :param network_settings:
76 for idx, node in enumerate(inventory['nodes']):
77 name = 'baremetal{}'.format(idx)
78 volume = name + ".qcow2"
79 volume_path = os.path.join(constants.LIBVIRT_VOLUME_PATH, volume)
80 # TODO(trozet): add back aarch64
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',
128 help='Number of Virtual Compute nodes to create'
129 ' and use during deployment (defaults to 1'
130 ' for noha and 2 for ha)')
131 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',
139 help='Amount of default RAM to use per '
140 'Overcloud VM in GB (defaults to 8).')
141 deploy_parser.add_argument('--virtual-compute-ram',
142 dest='virt_compute_ram',
144 help='Amount of RAM to use per Overcloud '
145 'Compute VM in GB (defaults to 8). '
146 'Overrides --virtual-default-ram arg for '
148 deploy_parser.add_argument('--deploy-dir',
149 default='/usr/share/opnfv-apex',
150 help='Directory to deploy from which contains '
151 'base config files for deployment')
152 deploy_parser.add_argument('--image-dir',
153 default='/var/opt/opnfv/images',
154 help='Directory which contains '
155 'base disk images for deployment')
156 deploy_parser.add_argument('--lib-dir',
157 default='/usr/share/opnfv-apex',
158 help='Directory path for apex ansible '
159 'and third party libs')
160 deploy_parser.add_argument('--quickstart', action='store_true',
162 help='Use tripleo-quickstart to deploy')
166 def validate_deploy_args(args):
168 Validates arguments for deploy
173 logging.debug('Validating arguments for deployment')
174 if args.virtual and args.inventory_file is not None:
175 logging.error("Virtual enabled but inventory file also given")
176 raise ApexDeployException('You should not specify an inventory file '
177 'with virtual deployments')
179 args.inventory_file = os.path.join(APEX_TEMP_DIR,
180 'inventory-virt.yaml')
181 elif os.path.isfile(args.inventory_file) is False:
182 logging.error("Specified inventory file does not exist: {}".format(
183 args.inventory_file))
184 raise ApexDeployException('Specified inventory file does not exist')
186 for settings_file in (args.deploy_settings_file,
187 args.network_settings_file):
188 if os.path.isfile(settings_file) is False:
189 logging.error("Specified settings file does not "
190 "exist: {}".format(settings_file))
191 raise ApexDeployException('Specified settings file does not '
192 'exist: {}'.format(settings_file))
196 parser = create_deploy_parser()
197 args = parser.parse_args(sys.argv[1:])
198 # FIXME (trozet): this is only needed as a workaround for CI. Remove
200 if os.getenv('IMAGES', False):
201 args.image_dir = os.getenv('IMAGES')
203 log_level = logging.DEBUG
205 log_level = logging.INFO
206 os.makedirs(os.path.dirname(args.log_file), exist_ok=True)
207 formatter = '%(asctime)s %(levelname)s: %(message)s'
208 logging.basicConfig(filename=args.log_file,
210 datefmt='%m/%d/%Y %I:%M:%S %p',
212 console = logging.StreamHandler()
213 console.setLevel(log_level)
214 console.setFormatter(logging.Formatter(formatter))
215 logging.getLogger('').addHandler(console)
216 validate_deploy_args(args)
218 deploy_settings = DeploySettings(args.deploy_settings_file)
219 logging.info("Deploy settings are:\n {}".format(pprint.pformat(
221 net_settings = NetworkSettings(args.network_settings_file)
222 logging.info("Network settings are:\n {}".format(pprint.pformat(
224 net_env_file = os.path.join(args.deploy_dir, constants.NET_ENV_FILE)
225 net_env = NetworkEnvironment(net_settings, net_env_file)
226 net_env_target = os.path.join(APEX_TEMP_DIR, constants.NET_ENV_FILE)
227 utils.dump_yaml(dict(net_env), net_env_target)
228 ha_enabled = deploy_settings['global_params']['ha_enabled']
230 if args.virt_compute_ram is None:
231 compute_ram = args.virt_default_ram
233 compute_ram = args.virt_compute_ram
234 if deploy_settings['deploy_options']['sdn_controller'] == \
235 'opendaylight' and args.virt_default_ram < 12:
237 logging.warning('RAM per controller is too low. OpenDaylight '
238 'requires at least 12GB per controller.')
239 logging.info('Increasing RAM per controller to 12GB')
240 elif args.virt_default_ram < 10:
242 logging.warning('RAM per controller is too low. nosdn '
243 'requires at least 10GB per controller.')
244 logging.info('Increasing RAM per controller to 10GB')
246 control_ram = args.virt_default_ram
247 if ha_enabled and args.virt_compute_nodes < 2:
248 logging.debug('HA enabled, bumping number of compute nodes to 2')
249 args.virt_compute_nodes = 2
250 virt_utils.generate_inventory(args.inventory_file, ha_enabled,
251 num_computes=args.virt_compute_nodes,
252 controller_ram=control_ram * 1024,
253 compute_ram=compute_ram * 1024,
256 inventory = Inventory(args.inventory_file, ha_enabled, args.virtual)
258 validate_cross_settings(deploy_settings, net_settings, inventory)
261 deploy_settings_file = os.path.join(APEX_TEMP_DIR,
262 'apex_deploy_settings.yaml')
263 utils.dump_yaml(utils.dict_objects_to_str(deploy_settings),
264 deploy_settings_file)
265 logging.info("File created: {}".format(deploy_settings_file))
266 network_settings_file = os.path.join(APEX_TEMP_DIR,
267 'apex_network_settings.yaml')
268 utils.dump_yaml(utils.dict_objects_to_str(net_settings),
269 network_settings_file)
270 logging.info("File created: {}".format(network_settings_file))
271 deploy_quickstart(args, deploy_settings_file, network_settings_file,
274 # TODO (trozet): add logic back from:
275 # Iedb75994d35b5dc1dd5d5ce1a57277c8f3729dfd (FDIO DVR)
277 'virsh_enabled_networks': net_settings.enabled_network_list
279 ansible_path = os.path.join(args.lib_dir, ANSIBLE_PATH)
280 utils.run_ansible(ansible_args,
281 os.path.join(args.lib_dir,
283 'deploy_dependencies.yml'))
285 if 'external' in net_settings.enabled_network_list:
288 # create all overcloud VMs
289 build_vms(inventory, net_settings, args.deploy_dir)
291 # Attach interfaces to jumphost for baremetal deployment
292 jump_networks = ['admin']
294 jump_networks.append('external')
295 for network in jump_networks:
296 if network == 'external':
297 # TODO(trozet): enable vlan secondary external networks
298 iface = net_settings['networks'][network][0][
299 'installer_vm']['members'][0]
301 iface = net_settings['networks'][network]['installer_vm'][
303 bridge = "br-{}".format(network)
304 jumphost.attach_interface_to_ovs(bridge, iface, network)
305 # Dump all settings out to temp bash files to be sourced
306 instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
307 with open(instackenv_json, 'w') as fh:
308 json.dump(inventory, fh)
310 # Create and configure undercloud
312 root_pw = constants.DEBUG_OVERCLOUD_PW
315 undercloud = uc_lib.Undercloud(args.image_dir,
318 external_network=uc_external)
321 # Generate nic templates
322 for role in 'compute', 'controller':
323 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
324 args.deploy_dir, APEX_TEMP_DIR)
326 undercloud.configure(net_settings,
327 os.path.join(args.lib_dir,
329 'configure_undercloud.yml'),
332 # Prepare overcloud-full.qcow2
333 logging.info("Preparing Overcloud for deployment...")
334 sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
335 overcloud_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR,
337 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
338 overcloud_deploy.prep_env(deploy_settings, net_settings, opnfv_env,
339 net_env_target, APEX_TEMP_DIR)
340 overcloud_deploy.create_deploy_cmd(deploy_settings, net_settings,
341 inventory, APEX_TEMP_DIR,
342 args.virtual, args.env_file)
343 deploy_playbook = os.path.join(args.lib_dir, ansible_path,
344 'deploy_overcloud.yml')
345 virt_env = 'virtual-environment.yaml'
346 bm_env = 'baremetal-environment.yaml'
347 for p_env in virt_env, bm_env:
348 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
349 os.path.join(APEX_TEMP_DIR, p_env))
351 # Start Overcloud Deployment
352 logging.info("Executing Overcloud Deployment...")
354 deploy_vars['virtual'] = args.virtual
355 deploy_vars['debug'] = args.debug
356 deploy_vars['dns_server_args'] = ''
357 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
358 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
359 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
360 for dns_server in net_settings['dns_servers']:
361 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
364 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
365 user='stack', tmp_dir=APEX_TEMP_DIR)
366 logging.info("Overcloud deployment complete")
367 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
369 logging.error("Deployment Failed. Please check log")
373 logging.info("Executing post deploy configuration")
374 jumphost.configure_bridges(net_settings)
375 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
376 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
378 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
379 'GlobalKnownHostsFile=/dev/null -o ' \
380 'UserKnownHostsFile=/dev/null -o ' \
382 deploy_vars['external_network_cmds'] = \
383 overcloud_deploy.external_network_cmds(net_settings)
384 # TODO(trozet): just parse all ds_opts as deploy vars one time
385 ds_opts = deploy_settings['deploy_options']
386 deploy_vars['gluon'] = ds_opts['gluon']
387 deploy_vars['sdn'] = ds_opts['sdn_controller']
388 for dep_option in 'yardstick', 'dovetail', 'vsperf':
389 if dep_option in ds_opts:
390 deploy_vars[dep_option] = ds_opts[dep_option]
392 deploy_vars[dep_option] = False
393 deploy_vars['dataplane'] = ds_opts['dataplane']
394 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
395 if ds_opts['congress']:
396 deploy_vars['congress_datasources'] = \
397 overcloud_deploy.create_congress_cmds(overcloudrc)
398 deploy_vars['congress'] = True
400 deploy_vars['congress'] = False
401 # TODO(trozet): this is probably redundant with getting external
402 # network info from undercloud.py
403 if 'external' in net_settings.enabled_network_list:
404 ext_cidr = net_settings['networks']['external'][0]['cidr']
406 ext_cidr = net_settings['networks']['admin']['cidr']
407 deploy_vars['external_cidr'] = str(ext_cidr)
408 if ext_cidr.version == 6:
409 deploy_vars['external_network_ipv6'] = True
411 deploy_vars['external_network_ipv6'] = False
412 post_undercloud = os.path.join(args.lib_dir, ansible_path,
413 'post_deploy_undercloud.yml')
414 logging.info("Executing post deploy configuration undercloud playbook")
416 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
417 user='stack', tmp_dir=APEX_TEMP_DIR)
418 logging.info("Post Deploy Undercloud Configuration Complete")
420 logging.error("Post Deploy Undercloud Configuration failed. "
423 # Post deploy overcloud node configuration
424 # TODO(trozet): just parse all ds_opts as deploy vars one time
425 deploy_vars['sfc'] = ds_opts['sfc']
426 deploy_vars['vpn'] = ds_opts['vpn']
427 # TODO(trozet): pull all logs and store in tmp dir in overcloud
429 post_overcloud = os.path.join(args.lib_dir, ansible_path,
430 'post_deploy_overcloud.yml')
431 # Run per overcloud node
432 for node, ip in deploy_vars['overcloud_nodes'].items():
433 logging.info("Executing Post deploy overcloud playbook on "
434 "node {}".format(node))
436 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
437 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
438 logging.info("Post Deploy Overcloud Configuration Complete "
439 "for node {}".format(node))
441 logging.error("Post Deploy Overcloud Configuration failed "
442 "for node {}. Please check log".format(node))
444 logging.info("Apex deployment complete")
445 logging.info("Undercloud IP: {}, please connect by doing "
446 "'opnfv-util undercloud'".format(undercloud.ip))
447 # TODO(trozet): add logging here showing controller VIP and horizon url
448 if __name__ == '__main__':