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