1 ##############################################################################
2 # Copyright (c) 2016 Dan Radez (dradez@redhat.com) (Red Hat)
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
14 from mock import patch
15 from mock import MagicMock
16 from mock import mock_open
17 from io import StringIO
19 from apex.common import constants as con
20 from apex.common.exceptions import ApexDeployException
21 from apex.overcloud.deploy import build_sdn_env_list
22 from apex.overcloud.deploy import create_deploy_cmd
23 from apex.overcloud.deploy import prep_image
24 from apex.overcloud.deploy import make_ssh_key
25 from apex.overcloud.deploy import prep_env
26 from apex.overcloud.deploy import generate_ceph_key
27 from apex.overcloud.deploy import prep_storage_env
28 from apex.overcloud.deploy import prep_sriov_env
29 from apex.overcloud.deploy import external_network_cmds
30 from apex.overcloud.deploy import create_congress_cmds
31 from apex.overcloud.deploy import SDN_FILE_MAP
32 from apex.overcloud.deploy import get_docker_sdn_file
34 from nose.tools import (
35 assert_regexp_matches,
43 class TestOvercloudDeploy(unittest.TestCase):
46 """This method is run once for each class before any tests are run"""
49 def teardown_class(cls):
50 """This method is run once for each class _after_ all tests are run"""
53 """This method is run once before _each_ test method is executed"""
56 """This method is run once after _each_ test method is executed"""
58 def test_build_sdn_env_list(self):
59 ds = {'sdn_controller': 'opendaylight'}
60 sdn_map = {'opendaylight': 'test'}
61 res = '/usr/share/openstack-tripleo-heat-templates/environments/test'
62 assert_equal(build_sdn_env_list(ds, sdn_map), [res])
64 def test_build_sdn_env_list_dict(self):
65 ds = {'opendaylight': True,
66 'sdn_controller': None}
67 sdn_map = {'opendaylight': {}}
68 assert_equal(build_sdn_env_list(ds, sdn_map), [])
70 def test_build_sdn_env_list_tuple(self):
71 ds = {'opendaylight': 'test',
72 'sdn_controller': None}
73 sdn_map = {'opendaylight': ('test', 'test')}
74 res = '/usr/share/openstack-tripleo-heat-templates/environments/test'
75 assert_equal(build_sdn_env_list(ds, sdn_map), [res])
77 def test_build_sdn_env_list_with_default(self):
78 ds = {'sdn_controller': 'opendaylight',
80 prefix = '/usr/share/openstack-tripleo-heat-templates/environments'
81 res = [os.path.join(prefix, 'neutron-opendaylight.yaml'),
82 os.path.join(prefix, 'neutron-bgpvpn-opendaylight.yaml')]
83 assert_equal(build_sdn_env_list(ds, SDN_FILE_MAP), res)
85 @patch('apex.overcloud.deploy.prep_sriov_env')
86 @patch('apex.overcloud.deploy.prep_storage_env')
87 @patch('apex.overcloud.deploy.build_sdn_env_list')
88 @patch('builtins.open', mock_open())
89 def test_create_deploy_cmd(self, mock_sdn_list, mock_prep_storage,
91 mock_sdn_list.return_value = []
92 ds = {'deploy_options':
101 'global_params': MagicMock()}
103 ns = {'ntp': ['ntp']}
105 inv.get_node_counts.return_value = (3, 2)
107 result_cmd = create_deploy_cmd(ds, ns, inv, '/tmp', virt)
108 assert_in('--ntp-server ntp', result_cmd)
109 assert_in('enable_tacker.yaml', result_cmd)
110 assert_in('enable_congress.yaml', result_cmd)
111 assert_in('enable_barometer.yaml', result_cmd)
112 assert_in('virtual-environment.yaml', result_cmd)
113 assert_in('--control-scale 3', result_cmd)
114 assert_in('--compute-scale 2', result_cmd)
116 @patch('apex.overcloud.deploy.prep_sriov_env')
117 @patch('apex.overcloud.deploy.prep_storage_env')
118 @patch('builtins.open', mock_open())
119 def test_create_deploy_cmd_containers_sdn(self, mock_prep_storage,
121 ds = {'deploy_options':
128 'sdn_controller': 'opendaylight',
131 'global_params': MagicMock()}
133 ns = {'ntp': ['ntp']}
135 inv.get_node_counts.return_value = (3, 2)
137 result_cmd = create_deploy_cmd(ds, ns, inv, '/tmp', virt)
138 assert_in('--ntp-server ntp', result_cmd)
139 assert_not_in('enable_tacker.yaml', result_cmd)
140 assert_not_in('enable_congress.yaml', result_cmd)
141 assert_not_in('enable_barometer.yaml', result_cmd)
142 assert_in('virtual-environment.yaml', result_cmd)
143 assert_in('--control-scale 3', result_cmd)
144 assert_in('--compute-scale 2', result_cmd)
145 assert_in('docker-images.yaml', result_cmd)
146 assert_in('sdn-images.yaml', result_cmd)
147 assert_in('/usr/share/openstack-tripleo-heat-templates/environments'
148 '/docker.yaml', result_cmd)
149 assert_in('/usr/share/openstack-tripleo-heat-templates/environments/'
150 'storage-environment.yaml', result_cmd)
151 assert_in('/usr/share/openstack-tripleo-heat-templates/environments'
152 '/services-docker/neutron-opendaylight.yaml', result_cmd)
154 @patch('apex.overcloud.deploy.prep_sriov_env')
155 @patch('apex.overcloud.deploy.prep_storage_env')
156 @patch('apex.overcloud.deploy.build_sdn_env_list')
157 @patch('builtins.open', mock_open())
158 def test_create_deploy_cmd_no_ha_bm(self, mock_sdn_list,
159 mock_prep_storage, mock_prep_sriov):
160 mock_sdn_list.return_value = []
161 ds = {'deploy_options': MagicMock(),
162 'global_params': MagicMock()}
163 ds['global_params'].__getitem__.side_effect = \
164 lambda i: False if i == 'ha_enabled' else MagicMock()
165 ns = {'ntp': ['ntp']}
167 inv.get_node_counts.return_value = (3, 2)
169 result_cmd = create_deploy_cmd(ds, ns, inv, '/tmp', virt)
170 assert_in('--ntp-server ntp', result_cmd)
171 assert_in('--control-scale 1', result_cmd)
172 assert_in('--compute-scale 2', result_cmd)
173 assert_in('baremetal-environment.yaml', result_cmd)
174 assert_not_in('enable_tacker.yaml', result_cmd)
175 assert_not_in('enable_congress.yaml', result_cmd)
176 assert_not_in('enable_barometer.yaml', result_cmd)
178 @patch('apex.overcloud.deploy.prep_sriov_env')
179 @patch('apex.overcloud.deploy.prep_storage_env')
180 @patch('apex.overcloud.deploy.build_sdn_env_list')
181 def test_create_deploy_cmd_raises(self, mock_sdn_list, mock_prep_storage,
183 mock_sdn_list.return_value = []
184 ds = {'deploy_options': MagicMock(),
185 'global_params': MagicMock()}
188 inv.get_node_counts.return_value = (0, 0)
190 assert_raises(ApexDeployException, create_deploy_cmd,
191 ds, ns, inv, '/tmp', virt)
193 @patch('apex.overcloud.deploy.virt_utils')
194 @patch('apex.overcloud.deploy.shutil')
195 @patch('apex.overcloud.deploy.os.path')
196 @patch('builtins.open', mock_open())
197 def test_prep_image(self, mock_os_path, mock_shutil, mock_virt_utils):
198 ds_opts = {'dataplane': 'fdio',
199 'sdn_controller': 'opendaylight',
200 'odl_version': 'master',
202 ds = {'deploy_options': MagicMock(),
203 'global_params': MagicMock()}
204 ds['deploy_options'].__getitem__.side_effect = \
205 lambda i: ds_opts.get(i, MagicMock())
207 prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test')
208 mock_virt_utils.virt_customize.assert_called()
210 @patch('apex.overcloud.deploy.virt_utils')
211 @patch('apex.overcloud.deploy.shutil')
212 @patch('apex.overcloud.deploy.os.path')
213 @patch('builtins.open', mock_open())
214 def test_prep_image_sdn_false(self, mock_os_path, mock_shutil,
216 ds_opts = {'dataplane': 'fdio',
217 'sdn_controller': False}
218 ds = {'deploy_options': MagicMock(),
219 'global_params': MagicMock()}
220 ds['deploy_options'].__getitem__.side_effect = \
221 lambda i: ds_opts.get(i, MagicMock())
223 prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test')
224 mock_virt_utils.virt_customize.assert_called()
226 @patch('apex.overcloud.deploy.virt_utils')
227 @patch('apex.overcloud.deploy.shutil')
228 @patch('apex.overcloud.deploy.os.path')
229 @patch('builtins.open', mock_open())
230 def test_prep_image_sdn_odl(self, mock_os_path, mock_shutil,
232 ds_opts = {'dataplane': 'ovs',
233 'sdn_controller': 'opendaylight',
234 'odl_version': con.DEFAULT_ODL_VERSION,
235 'odl_vpp_netvirt': True}
236 ds = {'deploy_options': MagicMock(),
237 'global_params': MagicMock()}
238 ds['deploy_options'].__getitem__.side_effect = \
239 lambda i: ds_opts.get(i, MagicMock())
240 ds['deploy_options'].__contains__.side_effect = \
241 lambda i: True if i in ds_opts else MagicMock()
243 prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test')
244 mock_virt_utils.virt_customize.assert_called()
246 @patch('apex.overcloud.deploy.c_builder')
247 @patch('apex.overcloud.deploy.oc_builder')
248 @patch('apex.overcloud.deploy.virt_utils')
249 @patch('apex.overcloud.deploy.shutil')
250 @patch('apex.overcloud.deploy.os.path')
251 @patch('builtins.open', mock_open())
252 def test_prep_image_sdn_odl_upstream_containers_patches(
253 self, mock_os_path, mock_shutil, mock_virt_utils,
254 mock_oc_builder, mock_c_builder):
255 ds_opts = {'dataplane': 'ovs',
256 'sdn_controller': 'opendaylight',
257 'odl_version': con.DEFAULT_ODL_VERSION,
258 'odl_vpp_netvirt': True}
259 ds = {'deploy_options': MagicMock(),
260 'global_params': MagicMock()}
261 ds['deploy_options'].__getitem__.side_effect = \
262 lambda i: ds_opts.get(i, MagicMock())
263 ds['deploy_options'].__contains__.side_effect = \
264 lambda i: True if i in ds_opts else MagicMock()
266 mock_c_builder.add_upstream_patches.return_value = ['nova-api']
267 patches = ['dummy_nova_patch']
268 rv = prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test',
269 docker_tag='latest', patches=patches, upstream=True)
270 mock_oc_builder.inject_opendaylight.assert_called
271 mock_virt_utils.virt_customize.assert_called()
272 mock_c_builder.add_upstream_patches.assert_called
273 self.assertListEqual(sorted(rv), ['nova-api', 'opendaylight'])
275 @patch('apex.overcloud.deploy.virt_utils')
276 @patch('apex.overcloud.deploy.shutil')
277 @patch('apex.overcloud.deploy.os.path')
278 @patch('builtins.open', mock_open())
279 def test_prep_image_sdn_odl_not_def(self, mock_os_path,
280 mock_shutil, mock_virt_utils):
281 ds_opts = {'dataplane': 'ovs',
282 'sdn_controller': 'opendaylight',
283 'odl_version': 'uncommon'}
284 ds = {'deploy_options': MagicMock(),
285 'global_params': MagicMock()}
286 ds['deploy_options'].__getitem__.side_effect = \
287 lambda i: ds_opts.get(i, MagicMock())
289 prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test')
290 mock_virt_utils.virt_customize.assert_called()
292 @patch('apex.overcloud.deploy.virt_utils')
293 @patch('apex.overcloud.deploy.shutil')
294 @patch('apex.overcloud.deploy.os.path')
295 @patch('builtins.open', mock_open())
296 def test_prep_image_sdn_ovn(self, mock_os_path, mock_shutil,
298 ds_opts = {'dataplane': 'ovs',
299 'sdn_controller': 'ovn'}
300 ds = {'deploy_options': MagicMock(),
301 'global_params': MagicMock()}
302 ds['deploy_options'].__getitem__.side_effect = \
303 lambda i: ds_opts.get(i, MagicMock())
305 prep_image(ds, ns, 'undercloud.qcow2', '/tmp', root_pw='test')
306 mock_virt_utils.virt_customize.assert_called()
308 @patch('apex.overcloud.deploy.os.path.isfile')
309 def test_prep_image_no_image(self, mock_isfile):
310 mock_isfile.return_value = False
311 assert_raises(ApexDeployException, prep_image,
312 {}, {}, 'undercloud.qcow2', '/tmp')
314 def test_make_ssh_key(self):
315 priv, pub = make_ssh_key()
316 assert_in('-----BEGIN PRIVATE KEY-----', priv)
317 assert_in('ssh-rsa', pub)
319 @patch('apex.overcloud.deploy.fileinput')
320 @patch('apex.overcloud.deploy.shutil')
321 def test_prep_env(self, mock_shutil, mock_fileinput):
322 mock_fileinput.input.return_value = \
323 ['CloudDomain', 'replace_private_key', 'replace_public_key',
324 'opendaylight::vpp_routing_node', 'ControllerExtraConfig',
325 'NovaComputeExtraConfig', 'ComputeKernelArgs', 'HostCpusList',
326 'ComputeExtraConfigPre', 'resource_registry',
327 'NovaSchedulerDefaultFilters']
328 ds = {'deploy_options':
329 {'sdn_controller': 'opendaylight',
330 'odl_vpp_routing_node': 'test',
331 'dataplane': 'ovs_dpdk',
333 'performance': {'Compute': {'vpp': {'main-core': 'test',
334 'corelist-workers': 'test'},
335 'ovs': {'dpdk_cores': 'test'},
336 'kernel': {'test': 'test'}},
337 'Controller': {'vpp': 'test'}}}}
338 ns = {'domain_name': 'test.domain',
341 {'nic_mapping': {'controller':
342 {'members': ['tenant_nic']},
344 {'members': ['tenant_nic']}}},
346 [{'nic_mapping': {'controller':
347 {'members': ['ext_nic']},
349 {'members': ['ext_nic']}}}]}}
353 saved_stdout = sys.stdout
357 prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
358 output = out.getvalue().strip()
359 assert_in('CloudDomain: test.domain', output)
360 assert_in('ssh-rsa', output)
361 assert_in('ComputeKernelArgs: \'test=test \'', output)
362 assert_in('fdio::vpp_cpu_main_core: \'test\'', output)
365 sys.stdout = saved_stdout
367 @patch('apex.overcloud.deploy.fileinput')
368 @patch('apex.overcloud.deploy.shutil')
369 def test_prep_env_round_two(self, mock_shutil, mock_fileinput):
370 mock_fileinput.input.return_value = \
371 ['NeutronVPPAgentPhysnets']
372 ds = {'deploy_options':
373 {'sdn_controller': False,
376 'performance': {'Compute': {},
378 ns = {'domain_name': 'test.domain',
381 {'nic_mapping': {'controller':
382 {'members': ['tenant_nic']},
384 {'members': ['tenant_nic']}}},
386 [{'nic_mapping': {'controller':
387 {'members': ['ext_nic']},
389 {'members': ['ext_nic']}}}]}}
393 saved_stdout = sys.stdout
397 prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
398 output = out.getvalue().strip()
399 assert_in('NeutronVPPAgentPhysnets: \'datacentre:tenant_nic\'',
401 assert_in('NeutronVPPAgentPhysnets', output)
404 sys.stdout = saved_stdout
406 @patch('apex.overcloud.deploy.fileinput')
407 @patch('apex.overcloud.deploy.shutil')
408 def test_prep_env_round_three(self, mock_shutil, mock_fileinput):
409 mock_fileinput.input.return_value = \
410 ['OS::TripleO::Services::NeutronDhcpAgent',
411 'NeutronDhcpAgentsPerNetwork', 'ComputeServices']
412 ds = {'deploy_options':
413 {'sdn_controller': 'opendaylight',
417 ns = {'domain_name': 'test.domain',
420 {'nic_mapping': {'controller':
421 {'members': ['tenant_nic']},
423 {'members': ['tenant_nic']}}},
425 [{'nic_mapping': {'controller':
426 {'members': ['ext_nic']},
428 {'members': ['ext_nic']}}}]}}
430 inv.get_node_counts.return_value = (3, 2)
433 saved_stdout = sys.stdout
437 prep_env(ds, ns, inv, 'opnfv-env.yml', '/net-env.yml', '/tmp')
438 output = out.getvalue().strip()
439 assert_in('NeutronDhcpAgentsPerNetwork: 2', output)
442 sys.stdout = saved_stdout
444 def test_generate_ceph_key(self):
445 assert_equal(len(generate_ceph_key()), 40)
447 @patch('apex.overcloud.deploy.generate_ceph_key')
448 @patch('apex.overcloud.deploy.fileinput')
449 @patch('apex.overcloud.deploy.os.path.isfile')
450 @patch('builtins.open', mock_open())
451 def test_prep_storage_env(self, mock_isfile, mock_fileinput,
453 mock_fileinput.input.return_value = \
454 ['CephClusterFSID', 'CephMonKey', 'CephAdminKey', 'random_key']
455 ds = {'deploy_options': {
456 'ceph_device': '/dev/sdx',
460 prep_storage_env(ds, ns, virtual=False, tmp_dir='/tmp')
462 @patch('apex.overcloud.deploy.utils.edit_tht_env')
463 @patch('apex.overcloud.deploy.generate_ceph_key')
464 @patch('apex.overcloud.deploy.fileinput')
465 @patch('apex.overcloud.deploy.os.path.isfile')
466 @patch('builtins.open', mock_open())
467 def test_prep_storage_env_containers(self, mock_isfile, mock_fileinput,
468 mock_ceph_key, mock_edit_tht):
469 mock_fileinput.input.return_value = \
470 ['CephClusterFSID', 'CephMonKey', 'CephAdminKey', 'random_key']
471 ds = {'deploy_options': {
472 'ceph_device': '/dev/sdx',
474 'os_version': 'master'
475 }, 'global_params': {'ha_enabled': False}}
476 ns = {'networks': {con.ADMIN_NETWORK: {'installer_vm':
477 {'ip': '192.0.2.1'}}}
479 prep_storage_env(ds, ns, virtual=True, tmp_dir='/tmp')
481 'DockerCephDaemonImage':
482 '192.0.2.1:8787/ceph/daemon:tag-build-master-luminous-centos'
484 'CephPoolDefaultSize': 1,
485 'CephAnsibleExtraConfig': {
486 'centos_package_dependencies': [],
487 'ceph_osd_docker_memory_limit': '1g',
488 'ceph_mds_docker_memory_limit': '1g'
490 'CephPoolDefaultPgNum': 32,
491 'CephAnsibleDisksConfig': {
492 'devices': ['/dev/sdx'],
494 'osd_scenario': 'collocated'
497 mock_edit_tht.assert_called_with('/tmp/storage-environment.yaml',
498 'parameter_defaults',
501 @patch('apex.overcloud.deploy.os.path.isfile')
502 @patch('builtins.open', mock_open())
503 def test_prep_storage_env_raises(self, mock_isfile):
504 mock_isfile.return_value = False
505 ds = {'deploy_options': MagicMock()}
507 assert_raises(ApexDeployException, prep_storage_env, ds,
508 ns, virtual=False, tmp_dir='/tmp')
510 @patch('apex.overcloud.deploy.generate_ceph_key')
511 @patch('apex.overcloud.deploy.fileinput')
512 @patch('apex.overcloud.deploy.os.path.isfile')
513 @patch('builtins.open', mock_open())
514 def test_prep_sriov_env(self, mock_isfile, mock_fileinput, mock_ceph_key):
515 ds = {'deploy_options':
516 {'sdn_controller': 'opendaylight',
520 saved_stdout = sys.stdout
524 mock_fileinput.input.return_value = \
525 ['# NovaSchedulerDefaultFilters',
526 '# NovaSchedulerAvailableFilters',
527 '#NeutronPhysicalDevMappings: "datacentre:ens20f2"',
528 '#NeutronSriovNumVFs: \"ens20f2:5\"',
529 '#NovaPCIPassthrough:',
530 '# - devname: \"ens20f2\"',
531 '# physical_network: \"datacentre\"']
532 prep_sriov_env(ds, '/tmp')
533 output = out.getvalue().strip()
534 assert_in('NovaSchedulerDefaultFilters', output)
535 assert_in('NovaSchedulerAvailableFilters', output)
536 assert_in('NeutronPhysicalDevMappings: \"nfv_sriov:xxx\"', output)
537 assert_in('NeutronSriovNumVFs: \"xxx:8\"', output)
538 assert_in('NovaPCIPassthrough:', output)
539 assert_in('- devname: \"xxx\"', output)
540 assert_in('physical_network: \"nfv_sriov\"', output)
543 sys.stdout = saved_stdout
545 @patch('apex.overcloud.deploy.os.path.isfile')
546 @patch('builtins.open', mock_open())
547 def test_prep_sriov_env_raises(self, mock_isfile):
548 ds_opts = {'sriov': True}
549 ds = {'deploy_options': MagicMock()}
550 ds['deploy_options'].__getitem__.side_effect = \
551 lambda i: ds_opts.get(i, MagicMock())
552 mock_isfile.return_value = False
553 ds = {'deploy_options': MagicMock()}
554 assert_raises(ApexDeployException, prep_sriov_env, ds, '/tmp')
556 def test_external_network_cmds(self):
559 ns_dict = {'networks':
560 {'external': [{'floating_ip_range': (0, 1),
562 {'compute': {'vlan': 'native'}},
566 ns.enabled_network_list = ['external']
567 ns.__getitem__.side_effect = lambda i: ns_dict.get(i, MagicMock())
568 cmds = ' '.join(external_network_cmds(ns))
569 assert_in('--external', cmds)
570 assert_in('--allocation-pool start=0,end=1', cmds)
571 assert_in('--gateway gw', cmds)
572 assert_in('--network external', cmds)
574 def test_external_network_cmds_no_ext(self):
580 {'introspection_range': (0, 1),
582 {'compute': {'vlan': '123'}},
586 ns.enabled_network_list = ['admin']
587 ns.__getitem__.side_effect = lambda i: ns_dict.get(i, MagicMock())
588 external_network_cmds(ns)
589 cmds = ' '.join(external_network_cmds(ns))
590 assert_in('--external', cmds)
591 assert_in('--allocation-pool start=0,end=1', cmds)
592 assert_in('--network external', cmds)
593 assert_in('--provider-network-type vlan', cmds)
595 @patch('apex.overcloud.deploy.parsers')
596 def test_create_congress_cmds(self, mock_parsers):
597 assert_greater(len(create_congress_cmds('overcloud_file')), 0)
599 @patch('apex.overcloud.deploy.parsers.parse_overcloudrc')
600 def test_create_congress_cmds_raises(self, mock_parsers):
601 mock_parsers.return_value.__getitem__.side_effect = KeyError()
602 assert_raises(KeyError, create_congress_cmds, 'overcloud_file')
604 def test_get_docker_sdn_file(self):
605 ds_opts = {'ha_enabled': True,
611 'sdn_controller': 'opendaylight'
613 output = get_docker_sdn_file(ds_opts)
614 self.assertEqual(output,
615 ('/usr/share/openstack-tripleo-heat-templates'
616 '/environments/services-docker/neutron-opendaylight'