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 iface = net_settings['networks'][network]['installer_vm'][
296 bridge = "br-{}".format(network)
297 jumphost.attach_interface_to_ovs(bridge, iface, network)
298 # Dump all settings out to temp bash files to be sourced
299 instackenv_json = os.path.join(APEX_TEMP_DIR, 'instackenv.json')
300 with open(instackenv_json, 'w') as fh:
301 json.dump(inventory, fh)
303 # Create and configure undercloud
305 root_pw = constants.DEBUG_OVERCLOUD_PW
308 undercloud = uc_lib.Undercloud(args.image_dir,
310 external_network=uc_external)
313 # Generate nic templates
314 for role in 'compute', 'controller':
315 oc_cfg.create_nic_template(net_settings, deploy_settings, role,
316 args.deploy_dir, APEX_TEMP_DIR)
318 undercloud.configure(net_settings,
319 os.path.join(args.lib_dir,
321 'configure_undercloud.yml'),
324 # Prepare overcloud-full.qcow2
325 logging.info("Preparing Overcloud for deployment...")
326 sdn_image = os.path.join(args.image_dir, SDN_IMAGE)
327 overcloud_deploy.prep_image(deploy_settings, sdn_image, APEX_TEMP_DIR,
329 opnfv_env = os.path.join(args.deploy_dir, args.env_file)
330 overcloud_deploy.prep_env(deploy_settings, net_settings, opnfv_env,
331 net_env_target, APEX_TEMP_DIR)
332 overcloud_deploy.create_deploy_cmd(deploy_settings, net_settings,
333 inventory, APEX_TEMP_DIR,
334 args.virtual, args.env_file)
335 deploy_playbook = os.path.join(args.lib_dir, ansible_path,
336 'deploy_overcloud.yml')
337 virt_env = 'virtual-environment.yaml'
338 bm_env = 'baremetal-environment.yaml'
339 for p_env in virt_env, bm_env:
340 shutil.copyfile(os.path.join(args.deploy_dir, p_env),
341 os.path.join(APEX_TEMP_DIR, p_env))
343 # Start Overcloud Deployment
344 logging.info("Executing Overcloud Deployment...")
346 deploy_vars['virtual'] = args.virtual
347 deploy_vars['debug'] = args.debug
348 deploy_vars['dns_server_args'] = ''
349 deploy_vars['apex_temp_dir'] = APEX_TEMP_DIR
350 deploy_vars['stackrc'] = 'source /home/stack/stackrc'
351 deploy_vars['overcloudrc'] = 'source /home/stack/overcloudrc'
352 for dns_server in net_settings['dns_servers']:
353 deploy_vars['dns_server_args'] += " --dns-nameserver {}".format(
356 utils.run_ansible(deploy_vars, deploy_playbook, host=undercloud.ip,
357 user='stack', tmp_dir=APEX_TEMP_DIR)
358 logging.info("Overcloud deployment complete")
359 os.remove(os.path.join(APEX_TEMP_DIR, 'overcloud-full.qcow2'))
361 logging.error("Deployment Failed. Please check log")
365 logging.info("Executing post deploy configuration")
366 jumphost.configure_bridges(net_settings)
367 nova_output = os.path.join(APEX_TEMP_DIR, 'nova_output')
368 deploy_vars['overcloud_nodes'] = parsers.parse_nova_output(
370 deploy_vars['SSH_OPTIONS'] = '-o StrictHostKeyChecking=no -o ' \
371 'GlobalKnownHostsFile=/dev/null -o ' \
372 'UserKnownHostsFile=/dev/null -o ' \
374 deploy_vars['external_network_cmds'] = \
375 overcloud_deploy.external_network_cmds(net_settings)
376 # TODO(trozet): just parse all ds_opts as deploy vars one time
377 ds_opts = deploy_settings['deploy_options']
378 deploy_vars['gluon'] = ds_opts['gluon']
379 deploy_vars['sdn'] = ds_opts['sdn_controller']
380 for dep_option in 'yardstick', 'dovetail', 'vsperf':
381 if dep_option in ds_opts:
382 deploy_vars[dep_option] = ds_opts[dep_option]
384 deploy_vars[dep_option] = False
385 deploy_vars['dataplane'] = ds_opts['dataplane']
386 overcloudrc = os.path.join(APEX_TEMP_DIR, 'overcloudrc')
387 if ds_opts['congress']:
388 deploy_vars['congress_datasources'] = \
389 overcloud_deploy.create_congress_cmds(overcloudrc)
390 deploy_vars['congress'] = True
392 deploy_vars['congress'] = False
393 # TODO(trozet): this is probably redundant with getting external
394 # network info from undercloud.py
395 if 'external' in net_settings.enabled_network_list:
396 ext_cidr = net_settings['networks']['external'][0]['cidr']
398 ext_cidr = net_settings['networks']['admin']['cidr']
399 deploy_vars['external_cidr'] = str(ext_cidr)
400 if ext_cidr.version == 6:
401 deploy_vars['external_network_ipv6'] = True
403 deploy_vars['external_network_ipv6'] = False
404 post_undercloud = os.path.join(args.lib_dir, ansible_path,
405 'post_deploy_undercloud.yml')
406 logging.info("Executing post deploy configuration undercloud playbook")
408 utils.run_ansible(deploy_vars, post_undercloud, host=undercloud.ip,
409 user='stack', tmp_dir=APEX_TEMP_DIR)
410 logging.info("Post Deploy Undercloud Configuration Complete")
412 logging.error("Post Deploy Undercloud Configuration failed. "
415 # Post deploy overcloud node configuration
416 # TODO(trozet): just parse all ds_opts as deploy vars one time
417 deploy_vars['sfc'] = ds_opts['sfc']
418 deploy_vars['vpn'] = ds_opts['vpn']
419 # TODO(trozet): pull all logs and store in tmp dir in overcloud
421 post_overcloud = os.path.join(args.lib_dir, ansible_path,
422 'post_deploy_overcloud.yml')
423 # Run per overcloud node
424 for node, ip in deploy_vars['overcloud_nodes'].items():
425 logging.info("Executing Post deploy overcloud playbook on "
426 "node {}".format(node))
428 utils.run_ansible(deploy_vars, post_overcloud, host=ip,
429 user='heat-admin', tmp_dir=APEX_TEMP_DIR)
430 logging.info("Post Deploy Overcloud Configuration Complete "
431 "for node {}".format(node))
433 logging.error("Post Deploy Overcloud Configuration failed "
434 "for node {}. Please check log".format(node))
436 logging.info("Apex deployment complete")
437 logging.info("Undercloud IP: {}, please connect by doing "
438 "'opnfv-util undercloud'".format(undercloud.ip))
439 # TODO(trozet): add logging here showing controller VIP and horizon url
440 if __name__ == '__main__':