From: Gulsum Atici Date: Thu, 19 Jan 2023 09:44:06 +0000 (+0300) Subject: Refactoring new_flavor method in Openstack VIM connector X-Git-Tag: v13.0.1~7 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=760d5b9d0d2276649417e2529485c9137beee6c7;p=osm%2FRO.git Refactoring new_flavor method in Openstack VIM connector Change-Id: If852a4c20f41cba2609d9d0d30865649a748e067 Signed-off-by: Gulsum Atici (cherry picked from commit 4415c4cc1eb8032f0d6a5f49fba297992b355c42) --- diff --git a/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py index f4382ad3..acf6be48 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/tests/test_vimconn_openstack.py @@ -32,6 +32,7 @@ import mock from mock import MagicMock, patch from neutronclient.v2_0.client import Client from novaclient import exceptions as nvExceptions +from novaclient.exceptions import ClientException, Conflict from osm_ro_plugin import vimconn from osm_ro_plugin.vimconn import ( VimConnConnectionException, @@ -79,6 +80,34 @@ created_items_all_true = { } +# Variables used in TestNewFlavor Class +flavor_id = "075d2482-5edb-43e3-91b3-234e65b6268a" +name1 = "sample-flavor" +extended = ( + { + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "LARGE", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + }, +) +flavor_data = { + "name": name1, + "ram": 3, + "vcpus": 8, + "disk": 50, + "extended": extended, +} + +flavor_data2 = { + "name": name1, + "ram": 3, + "vcpus": 8, + "disk": 50, +} + + class TestSfcOperations(unittest.TestCase): @mock.patch("logging.getLogger", autospec=True) def setUp(self, mock_logger): @@ -3071,7 +3100,7 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn._neutron_create_float_ip(param, created_items) self.assertEqual(created_items, expected_created_items) - def test_neutron_create_float_ip_exception_occured(self): + def test_neutron_create_float_ip_exception_occurred(self): """Floating ip could not be created.""" param = { "floatingip": { @@ -3082,14 +3111,14 @@ class TestNewVmInstance(unittest.TestCase): created_items = {} self.vimconn.neutron = CopyingMock() self.vimconn.neutron.create_floatingip.side_effect = Exception( - "Neutron floating ip create exception occured." + "Neutron floating ip create exception occurred." ) with self.assertRaises(VimConnException) as err: self.vimconn._neutron_create_float_ip(param, created_items) self.assertEqual(created_items, {}) self.assertEqual( str(err.exception), - "Exception: Cannot create new floating_ip Neutron floating ip create exception occured.", + "Exception: Cannot create new floating_ip Neutron floating ip create exception occurred.", ) @patch.object(vimconnector, "_neutron_create_float_ip") @@ -4770,7 +4799,7 @@ class TestNewVmInstance(unittest.TestCase): k_id = floating_network_vim_id k = f"floating_ip:{floating_network_vim_id}" self.vimconn.neutron.delete_floatingip.side_effect = ( - nvExceptions.ClientException("Client exception occured.") + nvExceptions.ClientException("Client exception occurred.") ) self.vimconn._delete_floating_ip_by_id(k, k_id, created_items) self.vimconn.neutron.delete_floatingip.assert_called_once_with(k_id) @@ -4782,7 +4811,7 @@ class TestNewVmInstance(unittest.TestCase): }, ) self.vimconn.logger.error.assert_called_once_with( - "Error deleting floating ip: ClientException: Unknown Error (HTTP Client exception occured.)" + "Error deleting floating ip: ClientException: Unknown Error (HTTP Client exception occurred.)" ) def test_delete_floating_ip_by_id_floating_ip_raises_vimconnexception(self): @@ -5530,11 +5559,11 @@ class TestNewVmInstance(unittest.TestCase): mock_extract_items_wth_keep_flag_from_created_items.return_value = created_items mock_sleep = MagicMock() volumes_to_hold = [] - err = ConnectionError("ClientException occured.") + err = ConnectionError("ClientException occurred.") mock_delete_created_items.side_effect = err with self.assertRaises(ConnectionError) as err: self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) - self.assertEqual(str(err), "ClientException occured.") + self.assertEqual(str(err), "ClientException occurred.") mock_reload_connection.assert_called_once() mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) @@ -5564,12 +5593,12 @@ class TestNewVmInstance(unittest.TestCase): created_items = deepcopy(created_items_all_true) mock_extract_items_wth_keep_flag_from_created_items.return_value = created_items volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] - err = ConnectionError("ClientException occured.") + err = ConnectionError("ClientException occurred.") mock_delete_vm_ports_attached_to_network.side_effect = err mock_delete_created_items.side_effect = err with self.assertRaises(ConnectionError) as err: self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) - self.assertEqual(str(err), "ClientException occured.") + self.assertEqual(str(err), "ClientException occurred.") mock_reload_connection.assert_called_once() mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) self.vimconn.nova.servers.delete.assert_not_called() @@ -5599,12 +5628,12 @@ class TestNewVmInstance(unittest.TestCase): created_items = deepcopy(created_items_all_true) mock_extract_items_wth_keep_flag_from_created_items.return_value = created_items volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] - err = VimConnConnectionException("ClientException occured.") + err = VimConnConnectionException("ClientException occurred.") self.vimconn.nova.servers.delete.side_effect = err mock_delete_created_items.side_effect = err with self.assertRaises(VimConnConnectionException) as err: self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) - self.assertEqual(str(err), "ClientException occured.") + self.assertEqual(str(err), "ClientException occurred.") mock_reload_connection.assert_called_once() mock_delete_vm_ports_attached_to_network.assert_called_once_with(created_items) self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) @@ -5635,12 +5664,12 @@ class TestNewVmInstance(unittest.TestCase): mock_extract_items_wth_keep_flag_from_created_items.return_value = created_items mock_sleep = MagicMock() volumes_to_hold = [f"{volume_id}", f"{volume_id2}"] - err = ConnectionError("ClientException occured.") + err = ConnectionError("ClientException occurred.") mock_delete_created_items.return_value = False mock_reload_connection.side_effect = err with self.assertRaises(ConnectionError) as err: self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) - self.assertEqual(str(err), "ClientException occured.") + self.assertEqual(str(err), "ClientException occurred.") mock_reload_connection.assert_called_once() mock_delete_vm_ports_attached_to_network.assert_not_called() self.vimconn.nova.servers.delete.assert_not_called() @@ -5889,5 +5918,1921 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn._extract_items_wth_keep_flag_from_created_items(created_items) +class TestNewFlavor(unittest.TestCase): + @patch("logging.getLogger", autospec=True) + def setUp(self, mock_logger): + # We are disabling the logging of exception not to print them to console. + mock_logger = logging.getLogger() + mock_logger.disabled = True + self.vimconn = vimconnector( + "123", + "openstackvim", + "456", + "789", + "http://dummy.url", + None, + "user", + "pass", + ) + self.vimconn.nova = CopyingMock(autospec=True) + self.flavor1 = CopyingMock(autospec=True, name="sample-flavor") + self.flavor2 = CopyingMock(autospec=True, name="other-flavor") + self.new_flavor = CopyingMock(autospec=True, name="new_flavor") + self.new_flavor.id = "075d2482-5edb-43e3-91b3-234e65b6268a" + self.vimconn.nova.flavors.create.return_value = self.new_flavor + + @staticmethod + def check_if_assert_not_called(mocks: list): + for mocking in mocks: + mocking.assert_not_called() + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_id_memory_vcpu_in_numa_type_vio( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, id, memory, vcpu exist, vim type is VIO, + paired-threads, cores, threads do not exist in numa. + """ + numas = [ + {"id": 0, "memory": 1, "vcpu": [1, 3]}, + {"id": 1, "memory": 2, "vcpu": [2]}, + ] + vcpus = 3 + extra_specs = {} + expected_extra_specs = { + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + "hw:cpu_sockets": "2", + } + self.vimconn.vim_type = "VIO" + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.assertEqual(mock_process_numa_memory.call_count, 2) + self.assertEqual(mock_process_numa_vcpu.call_count, 2) + _call_mock_process_numa_memory = mock_process_numa_memory.call_args_list + self.assertEqual( + _call_mock_process_numa_memory[0].args, + ( + {"id": 0, "memory": 1, "vcpu": [1, 3]}, + 0, + { + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + }, + ), + ) + self.assertEqual( + _call_mock_process_numa_memory[1].args, + ( + {"id": 1, "memory": 2, "vcpu": [2]}, + 1, + { + "hw:cpu_sockets": "2", + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + }, + ), + ) + _call_mock_process_numa_vcpu = mock_process_numa_vcpu.call_args_list + self.assertEqual( + _call_mock_process_numa_vcpu[0].args, + ( + {"id": 0, "memory": 1, "vcpu": [1, 3]}, + 0, + { + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + }, + ), + ) + self.assertEqual( + _call_mock_process_numa_vcpu[1].args, + ( + {"id": 1, "memory": 2, "vcpu": [2]}, + 1, + { + "hw:cpu_sockets": "2", + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + }, + ), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + self.check_if_assert_not_called( + [ + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + ] + ) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_id_memory_vcpu_in_numa_type_openstack( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, id, memory, vcpu exist, vim type is openstack, + paired-threads, cores, threads do not exist in numa. + """ + numas = [ + {"id": 0, "memory": 1, "vcpu": [1, 3]}, + {"id": 1, "memory": 2, "vcpu": [2]}, + ] + vcpus = 3 + extra_specs = {} + expected_extra_specs = { + "hw:numa_nodes": "2", + "hw:cpu_sockets": "2", + } + self.vimconn.vim_type = "openstack" + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.assertEqual(mock_process_numa_memory.call_count, 2) + self.assertEqual(mock_process_numa_vcpu.call_count, 2) + _call_mock_process_numa_memory = mock_process_numa_memory.call_args_list + self.assertEqual( + _call_mock_process_numa_memory[0].args, + ( + {"id": 0, "memory": 1, "vcpu": [1, 3]}, + 0, + {"hw:numa_nodes": "2"}, + ), + ) + self.assertEqual( + _call_mock_process_numa_memory[1].args, + ( + {"id": 1, "memory": 2, "vcpu": [2]}, + 1, + {"hw:cpu_sockets": "2", "hw:numa_nodes": "2"}, + ), + ) + _call_mock_process_numa_vcpu = mock_process_numa_vcpu.call_args_list + self.assertEqual( + _call_mock_process_numa_vcpu[0].args, + ( + {"id": 0, "memory": 1, "vcpu": [1, 3]}, + 0, + {"hw:numa_nodes": "2"}, + ), + ) + self.assertEqual( + _call_mock_process_numa_vcpu[1].args, + ( + {"id": 1, "memory": 2, "vcpu": [2]}, + 1, + {"hw:cpu_sockets": "2", "hw:numa_nodes": "2"}, + ), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + self.check_if_assert_not_called( + [ + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + ] + ) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_id_paired_threads_in_numa_type_openstack_extra_spec_not_empty( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, id, paired-threads exist, vim type is openstack. + vcpus calculation according to paired-threads in numa, there is extra_spec. + .""" + numas = [{"id": 0, "paired-threads": 3}, {"id": 1, "paired-threads": 3}] + extra_specs = {"some-key": "some-value"} + expected_extra_specs = { + "hw:numa_nodes": "2", + "hw:cpu_sockets": "2", + "some-key": "some-value", + } + self.vimconn.vim_type = "openstack" + vcpus = 6 + mock_process_numa_paired_threads.side_effect = [6, 6] + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [mock_process_numa_threads, mock_process_numa_cores] + ) + self.assertEqual(mock_process_numa_memory.call_count, 2) + self.assertEqual(mock_process_numa_vcpu.call_count, 2) + self.assertEqual(mock_process_numa_paired_threads.call_count, 2) + _call_mock_process_numa_paired_threads = ( + mock_process_numa_paired_threads.call_args_list + ) + self.assertEqual( + _call_mock_process_numa_paired_threads[0].args, + ( + {"id": 0, "paired-threads": 3}, + {"hw:cpu_sockets": "2", "hw:numa_nodes": "2", "some-key": "some-value"}, + ), + ) + self.assertEqual( + _call_mock_process_numa_paired_threads[1].args, + ( + {"id": 1, "paired-threads": 3}, + {"hw:cpu_sockets": "2", "hw:numa_nodes": "2", "some-key": "some-value"}, + ), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_id_paired_threads_in_numa_type_vio_extra_spec_not_empty( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, id, paired-threads exist, vim type is VIO. + vcpus calculation according to paired-threads in numa, there is extra_spec. + """ + numas = [{"id": 0, "paired-threads": 3}, {"id": 1, "paired-threads": 3}] + extra_specs = {"some-key": "some-value"} + expected_extra_specs = { + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + "hw:cpu_sockets": "2", + "some-key": "some-value", + } + self.vimconn.vim_type = "VIO" + vcpus = 6 + mock_process_numa_paired_threads.side_effect = [6, 6] + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [mock_process_numa_threads, mock_process_numa_cores] + ) + self.assertEqual(mock_process_numa_paired_threads.call_count, 2) + self.assertEqual(mock_process_numa_memory.call_count, 2) + self.assertEqual(mock_process_numa_vcpu.call_count, 2) + _call_mock_process_numa_paired_threads = ( + mock_process_numa_paired_threads.call_args_list + ) + self.assertEqual( + _call_mock_process_numa_paired_threads[0].args, + ( + {"id": 0, "paired-threads": 3}, + { + "hw:cpu_sockets": "2", + "hw:numa_nodes": "2", + "some-key": "some-value", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + }, + ), + ) + self.assertEqual( + _call_mock_process_numa_paired_threads[1].args, + ( + {"id": 1, "paired-threads": 3}, + { + "hw:cpu_sockets": "2", + "hw:numa_nodes": "2", + "some-key": "some-value", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + }, + ), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_id_cores_in_numa_type_openstack( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, id, cores exist, vim type is openstack. + vcpus calculation according to cores in numa. + """ + numas = [{"id": 0, "cores": 1}, {"id": 1, "cores": 2}] + extra_specs = {} + expected_extra_specs = {"hw:numa_nodes": "2", "hw:cpu_sockets": "2"} + self.vimconn.vim_type = "openstack" + vcpus = 2 + mock_process_numa_cores.side_effect = [1, 2] + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [mock_process_numa_threads, mock_process_numa_paired_threads] + ) + self.assertEqual(mock_process_numa_cores.call_count, 2) + self.assertEqual(mock_process_numa_memory.call_count, 2) + self.assertEqual(mock_process_numa_vcpu.call_count, 2) + _call_mock_process_numa_cores = mock_process_numa_cores.call_args_list + self.assertEqual( + _call_mock_process_numa_cores[0].args, + ({"id": 0, "cores": 1}, {"hw:cpu_sockets": "2", "hw:numa_nodes": "2"}), + ) + self.assertEqual( + _call_mock_process_numa_cores[1].args, + ({"id": 1, "cores": 2}, {"hw:cpu_sockets": "2", "hw:numa_nodes": "2"}), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_id_cores_in_numa_type_vio( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, id, cores exist, vim type is VIO. + vcpus calculation according to cores in numa. + """ + numas = [{"id": 0, "cores": 1}, {"id": 1, "cores": 2}] + extra_specs = {} + expected_extra_specs = { + "hw:numa_nodes": "2", + "hw:cpu_sockets": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + } + self.vimconn.vim_type = "VIO" + vcpus = 2 + mock_process_numa_cores.side_effect = [1, 2] + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [mock_process_numa_threads, mock_process_numa_paired_threads] + ) + self.assertEqual(mock_process_numa_memory.call_count, 2) + self.assertEqual(mock_process_numa_vcpu.call_count, 2) + self.assertEqual(mock_process_numa_cores.call_count, 2) + _call_mock_process_numa_cores = mock_process_numa_cores.call_args_list + self.assertEqual( + _call_mock_process_numa_cores[0].args, + ({"id": 0, "cores": 1}, expected_extra_specs), + ) + self.assertEqual( + _call_mock_process_numa_cores[1].args, + ({"id": 1, "cores": 2}, expected_extra_specs), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_without_numa_id_with_threads_type_vio( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, memory, vcpu, thread exist, vim type is VIO, + vcpus calculation according threads in numa, there are not numa ids. + """ + numas = [ + {"memory": 1, "vcpu": [1, 3], "threads": 3}, + {"memory": 2, "vcpu": [2]}, + ] + extra_specs = {} + expected_extra_specs = { + "hw:numa_nodes": "2", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + "hw:cpu_sockets": "2", + } + self.vimconn.vim_type = "VIO" + vcpus = 3 + mock_process_numa_threads.return_value = vcpus + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [ + mock_process_numa_memory, + mock_process_numa_vcpu, + mock_process_numa_cores, + mock_process_numa_paired_threads, + ] + ) + self.assertEqual(mock_process_numa_threads.call_count, 1) + _call_mock_process_numa_threads = mock_process_numa_threads.call_args_list + self.assertEqual( + _call_mock_process_numa_threads[0].args, + ( + {"memory": 1, "vcpu": [1, 3], "threads": 3}, + expected_extra_specs, + ), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(autospec=True), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_without_numa_id_with_threads_type_openstack( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Process numa parameters, memory, vcpu, thread exist, vim type is openstack, + vcpus calculation according threads in numa, there are not numa ids. + """ + numas = [ + {"memory": 1, "vcpu": [1, 3], "threads": 3}, + {"memory": 2, "vcpu": [2]}, + ] + extra_specs = {} + expected_extra_specs = { + "hw:numa_nodes": "2", + "hw:cpu_sockets": "2", + } + self.vimconn.vim_type = "openstack" + vcpus = 3 + mock_process_numa_threads.return_value = vcpus + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [ + mock_process_numa_memory, + mock_process_numa_vcpu, + mock_process_numa_cores, + mock_process_numa_paired_threads, + ] + ) + self.assertEqual(mock_process_numa_threads.call_count, 1) + _call_mock_process_numa_threads = mock_process_numa_threads.call_args_list + self.assertEqual( + _call_mock_process_numa_threads[0].args, + ( + {"memory": 1, "vcpu": [1, 3], "threads": 3}, + expected_extra_specs, + ), + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_empty_numas_list_type_vio( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Numa list is empty, vim type is VIO.""" + numas = [] + extra_specs = {} + expected_extra_specs = { + "hw:numa_nodes": "0", + "vmware:extra_config": '{"numa.nodeAffinity":"0"}', + "vmware:latency_sensitivity_level": "high", + } + self.vimconn.vim_type = "VIO" + vcpus = 4 + mock_process_numa_threads.return_value = None + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [ + mock_process_numa_memory, + mock_process_numa_vcpu, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_threads, + ] + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "process_numa_paired_threads", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_numa_cores", new_callable=CopyingMock()) + @patch.object(vimconnector, "process_numa_threads", new_callable=CopyingMock()) + def test_process_numa_parameters_of_flavor_empty_numas_list_type_openstack( + self, + mock_process_numa_threads, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_vcpu, + mock_process_numa_memory, + ): + """Numa list is empty, vim type is openstack.""" + numas = [] + extra_specs = {} + expected_extra_specs = {"hw:numa_nodes": "0"} + self.vimconn.vim_type = "openstack" + vcpus = 5 + mock_process_numa_threads.return_value = None + result = self.vimconn._process_numa_parameters_of_flavor( + numas, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.check_if_assert_not_called( + [ + mock_process_numa_memory, + mock_process_numa_vcpu, + mock_process_numa_cores, + mock_process_numa_paired_threads, + mock_process_numa_threads, + ] + ) + self.assertDictEqual(extra_specs, expected_extra_specs) + + def test_process_numa_memory_empty_extra_spec(self): + numa = {"memory": 2, "vcpu": [2]} + node_id = 2 + extra_specs = {} + expected_extra_spec = {"hw:numa_mem.2": 2048} + self.vimconn.process_numa_memory(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_memory_not_exist(self): + numa = {"vcpu": [2]} + node_id = 2 + extra_specs = {"vmware:latency_sensitivity_level": "high"} + self.vimconn.process_numa_memory(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, {"vmware:latency_sensitivity_level": "high"}) + + def test_process_numa_memory_node_id_is_none(self): + numa = {"memory": 2, "vcpu": [2]} + node_id = None + extra_specs = {} + expected_extra_spec = {"hw:numa_mem.None": 2048} + self.vimconn.process_numa_memory(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_memory_node_id_is_int(self): + numa = {"memory": 2, "vcpu": [2]} + node_id = 0 + extra_specs = {} + expected_extra_spec = {"hw:numa_mem.0": 2048} + self.vimconn.process_numa_memory(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_vcpu_empty_extra_spec(self): + numa = {"vcpu": [2]} + node_id = 0 + extra_specs = {} + expected_extra_spec = {"hw:numa_cpus.0": "2"} + self.vimconn.process_numa_vcpu(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_vcpu_not_exist(self): + numa = {"memory": 2} + node_id = 0 + extra_specs = {"vmware:latency_sensitivity_level": "high"} + expected_extra_spec = {"vmware:latency_sensitivity_level": "high"} + self.vimconn.process_numa_vcpu(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_vcpu_empty_node_id(self): + numa = {"vcpu": [2]} + node_id = "" + extra_specs = {} + expected_extra_spec = {"hw:numa_cpus.": "2"} + self.vimconn.process_numa_vcpu(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_vcpu_empty_numa_dict(self): + numa = {} + node_id = 4 + extra_specs = {} + self.vimconn.process_numa_vcpu(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, {}) + + def test_process_numa_vcpu_str_node_id(self): + numa = {"vcpu": [2]} + node_id = "12" + extra_specs = {} + expected_extra_spec = {"hw:numa_cpus.12": "2"} + self.vimconn.process_numa_vcpu(numa, node_id, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + + def test_process_numa_paired_threads_empty_extra_spec(self): + numa = {"id": 0, "paired-threads": 3} + extra_specs = {} + expected_extra_spec = { + "hw:cpu_thread_policy": "require", + "hw:cpu_policy": "dedicated", + } + result = self.vimconn.process_numa_paired_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, 6) + + def test_process_numa_paired_threads_empty_numa(self): + numa = {} + extra_specs = {} + result = self.vimconn.process_numa_paired_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, {}) + self.assertEqual(result, None) + + def test_process_numa_paired_threads_not_exist(self): + numa = {"vcpu": [2]} + extra_specs = {} + result = self.vimconn.process_numa_paired_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, {}) + self.assertEqual(result, None) + + def test_process_numa_paired_threads_str_thread_num(self): + numa = {"id": 0, "paired-threads": "3"} + extra_specs = {} + expected_extra_spec = { + "hw:cpu_thread_policy": "require", + "hw:cpu_policy": "dedicated", + } + result = self.vimconn.process_numa_paired_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, "33") + + def test_process_numa_paired_threads_none_thread_num(self): + numa = {"id": 0, "paired-threads": None} + extra_specs = {} + result = self.vimconn.process_numa_paired_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, {}) + self.assertEqual(result, None) + + def test_process_numa_cores_empty_extra_spec(self): + numa = {"id": 0, "cores": 1} + extra_specs = {} + expected_extra_spec = { + "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": "isolate", + } + result = self.vimconn.process_numa_cores(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, 1) + + def test_process_numa_cores_not_exist(self): + numa = {"id": 0, "paired-threads": 3} + extra_specs = {} + result = self.vimconn.process_numa_cores(numa, extra_specs) + self.assertDictEqual(extra_specs, {}) + self.assertEqual(result, None) + + def test_process_numa_cores_empty_numa(self): + numa = {} + extra_specs = expected_extra_spec = {"some-key": "some-val"} + result = self.vimconn.process_numa_cores(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, None) + + def test_process_numa_cores_none_core_num(self): + numa = {"memory": 1, "cores": None} + extra_specs = {} + result = self.vimconn.process_numa_cores(numa, extra_specs) + self.assertDictEqual(extra_specs, {}) + self.assertEqual(result, None) + + def test_process_numa_cores_string_core_num(self): + numa = {"id": 0, "cores": "1"} + extra_specs = {"some-key": "some-val"} + expected_extra_spec = { + "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": "isolate", + "some-key": "some-val", + } + result = self.vimconn.process_numa_cores(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, "1") + + def test_process_numa_cores_float_core_num(self): + numa = {"memory": 2, "cores": 10.03} + extra_specs = {"some-key": "some-val"} + expected_extra_spec = { + "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": "isolate", + "some-key": "some-val", + } + result = self.vimconn.process_numa_cores(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, 10.03) + + def test_process_numa_threads_empty_extra_spec_int_thread_num(self): + numa = {"memory": 1, "vcpu": [1, 3], "threads": 3} + extra_specs = {} + expected_extra_spec = { + "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": "prefer", + } + result = self.vimconn.process_numa_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, 3) + + def test_process_numa_threads_empty_numa(self): + numa = {} + extra_specs = {"some-key": "some-val"} + expected_extra_spec = {"some-key": "some-val"} + result = self.vimconn.process_numa_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, None) + + def test_process_numa_threads_not_exist(self): + numa = {"memory": 1} + extra_specs = expected_extra_spec = {"some-key": "some-val"} + result = self.vimconn.process_numa_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, None) + + def test_process_numa_threads_str_thread_num(self): + numa = {"vcpu": [1, 3], "threads": "3"} + extra_specs = {} + expected_extra_spec = { + "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": "prefer", + } + result = self.vimconn.process_numa_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, "3") + + def test_process_numa_threads_none_thread_num(self): + numa = {"vcpu": [1, 3], "threads": None} + extra_specs = {} + result = self.vimconn.process_numa_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, {}) + self.assertEqual(result, None) + + def test_process_numa_threads_float_thread_num(self): + numa = {"memory": 1, "vcpu": [1, 3], "threads": 3.3} + extra_specs = {"some-key": "some-val"} + expected_extra_spec = { + "hw:cpu_policy": "dedicated", + "hw:cpu_thread_policy": "prefer", + "some-key": "some-val", + } + result = self.vimconn.process_numa_threads(numa, extra_specs) + self.assertDictEqual(extra_specs, expected_extra_spec) + self.assertEqual(result, 3.3) + + def test_change_the_flavor_name_not_existing_name(self): + """Flavor name does not exist in Openstack flavor list.""" + self.flavor1.name = "sample-flavor-3" + self.flavor2.name = "other-flavor-4" + self.vimconn.nova.flavors.list.return_value = [self.flavor1, self.flavor2] + name = "other-flavor-3" + name_suffix = 3 + flavor_data = {"name": "other-flavor"} + result = self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.assertEqual(result, name) + self.vimconn.nova.flavors.list.assert_called_once() + # Checking whether name_suffix changed or not. + self.assertEqual(name_suffix, 3) + + def test_change_the_flavor_name_existing_name(self): + """Flavor name exists in Openstack flavor list.""" + self.flavor1.name = "other-flavor-6" + self.flavor2.name = "other-flavor-3" + self.vimconn.nova.flavors.list.return_value = [self.flavor1, self.flavor2] + name = "other-flavor-3" + name_suffix = 5 + flavor_data = {"name": "other-flavor"} + expected_result = "other-flavor-7" + result = self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.assertEqual(result, expected_result) + # Checking whether name_suffix changed or not. + self.assertEqual(name_suffix, 5) + self.vimconn.nova.flavors.list.assert_called_once() + + def test_change_the_flavor_name_flavor_data_does_not_have_name(self): + """Flavor data does not have name.""" + self.flavor1.name = "other-flavor-6" + self.flavor2.name = "other-flavor-3" + self.vimconn.nova.flavors.list.return_value = [self.flavor1, self.flavor2] + name = "other-flavor-3" + name_suffix = 5 + flavor_data = {} + with self.assertRaises(KeyError): + self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.vimconn.nova.flavors.list.assert_called_once() + # Checking whether name_suffix changed or not. + self.assertEqual(name_suffix, 5) + + def test_change_the_flavor_name_invalid_name_suffix(self): + """Name suffix is invalid.""" + self.flavor1.name = "other-flavor-6" + self.flavor2.name = "other-flavor-3" + self.vimconn.nova.flavors.list.return_value = [self.flavor1, self.flavor2] + name = "other-flavor-3" + name_suffix = "a" + flavor_data = {"name": "other-flavor"} + with self.assertRaises(TypeError): + self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.vimconn.nova.flavors.list.assert_called_once() + # Checking whether name_suffix changed or not. + self.assertEqual(name_suffix, "a") + + def test_change_the_flavor_name_given_name_is_empty(self): + """Given name is empty string.""" + self.flavor1.name = "other-flavor-6" + self.flavor2.name = "other-flavor-3" + self.vimconn.nova.flavors.list.return_value = [self.flavor1, self.flavor2] + name = "" + name_suffix = 3 + flavor_data = {"name": "other-flavor"} + result = self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.assertEqual(result, "") + self.vimconn.nova.flavors.list.assert_called_once() + # Checking whether name_suffix increased or not. + self.assertEqual(name_suffix, 3) + + def test_change_the_flavor_name_given_name_is_none(self): + """Given name is None.""" + self.flavor1.name = "other-flavor-6" + self.flavor2.name = "other-flavor-3" + self.vimconn.nova.flavors.list.return_value = [self.flavor1, self.flavor2] + name = None + name_suffix = 6 + flavor_data = {"name": "other-flavor"} + result = self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.assertEqual(result, None) + self.vimconn.nova.flavors.list.assert_called_once() + # Checking whether name_suffix increased or not. + self.assertEqual(name_suffix, 6) + + def test_change_the_flavor_name_empty_nova_flavor_list(self): + """Nova flavor list is empty.""" + self.vimconn.nova.flavors.list.return_value = [] + name = "other-flavor-3" + name_suffix = 5 + flavor_data = {"name": "other-flavor"} + result = self.vimconn._change_flavor_name(name, name_suffix, flavor_data) + self.vimconn.nova.flavors.list.assert_called_once() + self.assertEqual(result, name) + # Checking whether name_suffix increased or not. + self.assertEqual(name_suffix, 5) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_with_numa_cpu_mem_vif_disk_quota( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has numas, cpu, mem, vif and disk-io quota.""" + numas = [ + {"memory": 1, "vcpu": [1, 3], "threads": 3}, + {"memory": 2, "vcpu": [2]}, + ] + vcpus = 3 + extended = { + "numas": numas, + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "vif-quota": {"limit": 10}, + "disk-io-quota": {"limit": 50}, + "mempage-size": "LARGE", + } + extra_specs = {} + expected_extra_specs = { + "hw:mem_page_size": "large", + } + mock_process_numa_parameters_of_flavor.return_value = vcpus + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.assertEqual(mock_process_resource_quota.call_count, 4) + mock_process_numa_parameters_of_flavor.assert_called_once_with(numas, {}, vcpus) + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_with_numa_wrong_disk_quota( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has wrong disk quota key.""" + numas = [ + {"memory": 1, "threads": 3}, + {"memory": 2, "vcpu": [2]}, + ] + vcpus = 3 + extended = { + "numas": numas, + "disk-quota": {"limit": 50}, + "mempage-size": "PREFER_LARGE", + } + extra_specs = {} + expected_extra_specs = { + "hw:mem_page_size": "any", + } + mock_process_numa_parameters_of_flavor.return_value = vcpus + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + mock_process_resource_quota.assert_not_called() + mock_process_numa_parameters_of_flavor.assert_called_once_with(numas, {}, vcpus) + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_without_numa_cpu_mem_vif_disk_quota( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has cpu, mem, vif and disk-io quota but not numas.""" + vcpus = 3 + extended = { + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "vif-quota": {"limit": 10}, + "disk-io-quota": {"limit": 50}, + "mempage-size": "SMALL", + } + extra_specs = {} + expected_extra_specs = { + "hw:mem_page_size": "small", + } + mock_process_numa_parameters_of_flavor.return_value = vcpus + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, vcpus) + self.assertEqual(mock_process_resource_quota.call_count, 4) + mock_process_numa_parameters_of_flavor.assert_not_called() + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_with_numa_with_cpu_pinning_mem_policy( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has cpu, mem quota, cpu-pinning and mem-policy.""" + numas = [ + {"memory": 1}, + {"memory": 2, "vcpu": [2]}, + ] + extended = { + "numas": numas, + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "LARGE", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + } + extra_specs = {} + vcpus = 3 + expected_extra_specs = { + "hw:mem_page_size": "large", + "hw:cpu_policy": "dedicated", + "hw:numa_mempolicy": "strict", + } + mock_process_numa_parameters_of_flavor.return_value = 4 + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 4) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_called_once_with(numas, {}, vcpus) + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_without_numa_with_cpu_pinning_mem_policy( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has cpu, mem quota, cpu-pinning and mem-policy but not numas.""" + extended = { + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "LARGE", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + } + extra_specs = {} + vcpus = 3 + expected_extra_specs = { + "hw:mem_page_size": "large", + "hw:cpu_policy": "dedicated", + "hw:numa_mempolicy": "strict", + } + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 3) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_not_called() + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_without_numa_with_wrong_mempage_size( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has wrong mempage-size without numas.""" + extended = { + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "SIZE_2GB", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + } + extra_specs = {} + vcpus = 6 + expected_extra_specs = { + "hw:cpu_policy": "dedicated", + "hw:numa_mempolicy": "strict", + } + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 6) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_not_called() + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_with_numa_with_wrong_mempage_size( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has wrong mempage-size with numas.""" + numas = [ + {"memory": 1}, + {"memory": 2, "vcpu": [2]}, + ] + extended = { + "numas": numas, + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "SIZE_2GB", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + } + extra_specs = {} + mock_process_numa_parameters_of_flavor.return_value = 4 + vcpus = 1 + expected_extra_specs = { + "hw:cpu_policy": "dedicated", + "hw:numa_mempolicy": "strict", + } + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 4) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_called_once_with(numas, {}, vcpus) + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_none_vcpus( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has cpu, mem quota, cpu-pinning and mem-policy, vcpus is None.""" + numas = [ + {"memory": 1}, + {"memory": 2, "vcpu": [2]}, + ] + extended = { + "numas": numas, + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "SIZE_2GB", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + } + mock_process_numa_parameters_of_flavor.return_value = 1 + extra_specs = {} + vcpus = None + expected_extra_specs = { + "hw:cpu_policy": "dedicated", + "hw:numa_mempolicy": "strict", + } + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 1) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_called_once_with(numas, {}, vcpus) + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_none_vcpus_without_numas( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has cpu, mem quota, cpu-pinning and mem-policy, vcpus is None.""" + extended = { + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "SIZE_2GB", + "cpu-pinning-policy": "DEDICATED", + "mem-policy": "STRICT", + } + extra_specs = {"some-key": "some-val"} + vcpus = None + expected_extra_specs = { + "hw:cpu_policy": "dedicated", + "hw:numa_mempolicy": "strict", + "some-key": "some-val", + } + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, None) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_not_called() + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_wrong_cpu_pinning_mem_policy_empty_vcpus( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended has wrong cpu-pinning and mem-policy keys.""" + numas = [ + {"memory": 1}, + {"memory": 2, "vcpu": [2]}, + ] + extended = { + "numas": numas, + "cpu-quota": {"limit": 3}, + "mem-quota": {"limit": 1}, + "mempage-size": "SIZE_2GB", + "cpu-pinning-pol": "DEDICATED", + "mem-pol": "STRICT", + } + mock_process_numa_parameters_of_flavor.return_value = 1 + extra_specs = {} + vcpus = "" + expected_extra_specs = {} + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 1) + self.assertEqual(mock_process_resource_quota.call_count, 2) + mock_process_numa_parameters_of_flavor.assert_called_once_with( + numas, extra_specs, vcpus + ) + self.assertEqual(extra_specs, expected_extra_specs) + + @patch.object( + vimconnector, + "_process_numa_parameters_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "process_resource_quota", new_callable=CopyingMock()) + def test_process_extended_config_of_flavor_empty_extended( + self, mock_process_resource_quota, mock_process_numa_parameters_of_flavor + ): + """Process extended config, extended is empty.""" + extended = {} + extra_specs = {} + vcpus = 2 + result = self.vimconn._process_extended_config_of_flavor( + extended, extra_specs, vcpus + ) + self.assertEqual(result, 2) + self.check_if_assert_not_called( + [mock_process_resource_quota, mock_process_numa_parameters_of_flavor] + ) + self.assertEqual(extra_specs, {}) + + def test_get_flavor_details_empty_flavor_data(self): + flavor_data = {} + expected_result = (64, 1, {}, None) + result = self.vimconn._get_flavor_details(flavor_data) + self.assertEqual(result, expected_result) + + def test_get_flavor_details_flavor_data_has_ram_vcpus_extended(self): + flavor_data = { + "ram": 32, + "vcpus": 3, + "extended": { + "some-key": "some-val", + }, + } + expected_result = (32, 3, {}, {"some-key": "some-val"}) + result = self.vimconn._get_flavor_details(flavor_data) + self.assertEqual(result, expected_result) + + def test_get_flavor_details_flavor_data_is_none(self): + flavor_data = None + with self.assertRaises(AttributeError): + self.vimconn._get_flavor_details(flavor_data) + + def test_get_flavor_details_flavor_data_has_only_extended(self): + flavor_data = { + "extended": { + "some-key": "some-val", + } + } + expected_result = (64, 1, {}, {"some-key": "some-val"}) + result = self.vimconn._get_flavor_details(flavor_data) + self.assertEqual(result, expected_result) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_with_extended_with_extra_specs( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor with using extended parameters and extra specs.""" + name_suffix = 0 + vcpus = 8 + mock_change_flavor_name.return_value = name1 + mock_extended_config_of_flavor.return_value = vcpus + mock_get_flavor_details.return_value = ( + 3, + vcpus, + {"some-key": "some-value"}, + extended, + ) + expected_result = self.new_flavor.id + result = self.vimconn.new_flavor(flavor_data) + self.assertEqual(result, expected_result) + mock_reload_connection.assert_called_once() + self.new_flavor.set_keys.assert_called_once() + mock_get_flavor_details.assert_called_once_with(flavor_data) + mock_change_flavor_name.assert_called_once_with(name1, name_suffix, flavor_data) + mock_extended_config_of_flavor.assert_called_once_with( + extended, {"some-key": "some-value"}, vcpus + ) + self.vimconn.nova.flavors.create.assert_called_once_with( + name=name1, ram=3, vcpus=8, disk=50, ephemeral=0, swap=0, is_public=True + ) + mock_format_exception.assert_not_called() + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_with_extended_without_extra_specs( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor with using extended parameters without extra specs.""" + name_suffix = 0 + vcpus = 8 + mock_change_flavor_name.return_value = name1 + mock_extended_config_of_flavor.return_value = vcpus + mock_get_flavor_details.return_value = (3, vcpus, {}, extended) + expected_result = self.new_flavor.id + result = self.vimconn.new_flavor(flavor_data) + self.assertEqual(result, expected_result) + mock_reload_connection.assert_called_once() + + mock_get_flavor_details.assert_called_once_with(flavor_data) + mock_change_flavor_name.assert_called_once_with(name1, name_suffix, flavor_data) + mock_extended_config_of_flavor.assert_called_once_with(extended, {}, vcpus) + self.vimconn.nova.flavors.create.assert_called_once_with( + name=name1, ram=3, vcpus=vcpus, disk=50, ephemeral=0, swap=0, is_public=True + ) + self.check_if_assert_not_called( + [self.new_flavor.set_keys, mock_format_exception] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_change_name_if_used_false_with_extended( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, change_name_if_used_false, there is extended.""" + vcpus = 8 + mock_get_flavor_details.return_value = (3, vcpus, {}, extended) + mock_extended_config_of_flavor.return_value = 16 + expected_result = self.new_flavor.id + result = self.vimconn.new_flavor(flavor_data, False) + self.assertEqual(result, expected_result) + mock_reload_connection.assert_called_once() + self.assertEqual(mock_get_flavor_details.call_count, 1) + mock_extended_config_of_flavor.assert_called_once_with(extended, {}, vcpus) + self.vimconn.nova.flavors.create.assert_called_once_with( + name=name1, ram=3, vcpus=16, disk=50, ephemeral=0, swap=0, is_public=True + ) + self.check_if_assert_not_called( + [mock_change_flavor_name, mock_format_exception, self.new_flavor.set_keys] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_change_name_if_used_true_without_extended( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor without extended parameters.""" + name_suffix = 0 + mock_change_flavor_name.return_value = name1 + expected_result = self.new_flavor.id + mock_get_flavor_details.return_value = (3, 8, {}, None) + result = self.vimconn.new_flavor(flavor_data2) + self.assertEqual(result, expected_result) + + mock_reload_connection.assert_called_once() + mock_change_flavor_name.assert_called_once_with( + name1, name_suffix, flavor_data2 + ) + self.assertEqual(mock_get_flavor_details.call_count, 1) + + self.vimconn.nova.flavors.create.assert_called_once_with( + name=name1, ram=3, vcpus=8, disk=50, ephemeral=0, swap=0, is_public=True + ) + self.check_if_assert_not_called( + [ + self.new_flavor.set_keys, + mock_extended_config_of_flavor, + mock_format_exception, + ] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_reload_connection_exception( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, reload connection exception occurred.""" + error_msg = "Can not connect to client APIs." + error = nvExceptions.ClientException(error_msg) + mock_change_flavor_name.return_value = name1 + mock_reload_connection.side_effect = error + with self.assertRaises(Exception) as err: + self.vimconn.new_flavor(flavor_data2) + self.assertEqual(str(err.exception), "Can not connect to client APIs.") + self.assertEqual(mock_reload_connection.call_count, 1) + call_mock_format_exception = mock_format_exception.call_args + self.assertEqual( + str(call_mock_format_exception[0][0]), str(ClientException(error_msg)) + ) + self.check_if_assert_not_called( + [ + mock_change_flavor_name, + mock_get_flavor_details, + mock_extended_config_of_flavor, + self.vimconn.nova.flavors.create, + ] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(autospec=True), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_flavor_data_without_name( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, flavor data does not have name.""" + flavor_data3 = { + "ram": 3, + "vcpus": 8, + "disk": 50, + } + error_msg = "name" + self.vimconn.new_flavor(flavor_data3) + mock_format_exception.assert_called_once() + call_mock_format_exception = mock_format_exception.call_args + self.assertEqual( + str(call_mock_format_exception[0][0]), str(KeyError(error_msg)) + ) + self.check_if_assert_not_called( + [ + mock_reload_connection, + mock_change_flavor_name, + mock_get_flavor_details, + mock_extended_config_of_flavor, + self.vimconn.nova.flavors.create, + ] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_change_flavor_name_has_extended_conflict_exception_recovered_in_retry( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, nvExceptions.Conflict occurred and recovered, there is extended config.""" + name_suffix = 0 + error_msg = "Conflict has occurred while creating flavor name." + error2 = nvExceptions.Conflict(error_msg) + mock_change_flavor_name.side_effect = [error2, "sample-flavor-3"] + expected_result = self.new_flavor.id + mock_get_flavor_details.return_value = (3, 8, {}, extended) + mock_extended_config_of_flavor.return_value = 10 + result = self.vimconn.new_flavor(flavor_data2) + self.assertEqual(result, expected_result) + self.assertEqual(mock_reload_connection.call_count, 2) + + mock_change_flavor_name.assert_called_with(name1, name_suffix, flavor_data2) + self.assertEqual(mock_change_flavor_name.call_count, 2) + self.assertEqual(mock_get_flavor_details.call_count, 1) + self.assertEqual(mock_extended_config_of_flavor.call_count, 1) + self.vimconn.nova.flavors.create.assert_called_once_with( + name="sample-flavor-3", + ram=3, + vcpus=10, + disk=50, + ephemeral=0, + swap=0, + is_public=True, + ) + self.check_if_assert_not_called( + [self.new_flavor.set_keys, mock_format_exception] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_change_flavor_name_without_extended_conflict_exception_recovered_in_retry( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, nvExceptions.Conflict occurred and recovered, there is not extended config.""" + name_suffix = 0 + error2 = nvExceptions.Conflict( + "Conflict has occurred while creating flavor name." + ) + mock_change_flavor_name.side_effect = [error2, "sample-flavor-3"] + expected_result = self.new_flavor.id + mock_get_flavor_details.return_value = (3, 8, {}, None) + result = self.vimconn.new_flavor(flavor_data2) + + self.assertEqual(result, expected_result) + self.assertEqual(mock_reload_connection.call_count, 2) + mock_change_flavor_name.assert_called_with(name1, name_suffix, flavor_data2) + self.assertEqual(mock_change_flavor_name.call_count, 2) + self.assertEqual(mock_get_flavor_details.call_count, 1) + + self.vimconn.nova.flavors.create.assert_called_once_with( + name="sample-flavor-3", + ram=3, + vcpus=8, + disk=50, + ephemeral=0, + swap=0, + is_public=True, + ) + self.check_if_assert_not_called( + [ + self.new_flavor.set_keys, + mock_extended_config_of_flavor, + mock_format_exception, + ] + ) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_change_flavor_name_conflict_exception_change_name_if_used_false( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, nvExceptions.Conflict occurred, + change_name_if_used is false.""" + change_name_if_used = False + error_msg = "Conflict has occurred while creating flavor name." + error2 = nvExceptions.Conflict(error_msg) + mock_get_flavor_details.return_value = (4, 8, {}, None) + self.vimconn.nova.flavors.create.side_effect = error2 + with self.assertRaises(Exception) as err: + self.vimconn.new_flavor(flavor_data2, change_name_if_used) + self.assertEqual(str(err.exception), error_msg) + self.assertEqual(type(err.exception), nvExceptions.Conflict) + self.vimconn.nova.flavors.create.assert_called_with( + name="sample-flavor", + ram=4, + vcpus=8, + disk=50, + ephemeral=0, + swap=0, + is_public=True, + ) + self.assertEqual(mock_get_flavor_details.call_count, 3) + self.assertEqual(self.vimconn.nova.flavors.create.call_count, 3) + self.assertEqual(mock_reload_connection.call_count, 3) + self.check_if_assert_not_called( + [mock_change_flavor_name, mock_extended_config_of_flavor] + ) + _call_mock_format_exception = mock_format_exception.call_args + self.assertEqual( + str(_call_mock_format_exception[0][0]), str(Conflict(error_msg)) + ) + self.assertEqual(mock_format_exception.call_count, 3) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_client_exception_occurred_change_name_if_used_true( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, nvExceptions.ClientException occurred, + change_name_if_used is true.""" + error_msg = "Connection failed." + error2 = nvExceptions.ClientException(error_msg) + mock_change_flavor_name.side_effect = [ + "sample-flavor-3", + "sample-flavor-4", + "sample-flavor-5", + ] + mock_get_flavor_details.return_value = (3, 8, {}, None) + self.vimconn.nova.flavors.create.side_effect = error2 + with self.assertRaises(Exception) as err: + self.vimconn.new_flavor(flavor_data2) + self.assertEqual( + str(err.exception), "Conflict has occurred while creating flavor name." + ) + self.assertEqual(type(err.exception), nvExceptions.Conflict) + self.assertEqual(self.vimconn.nova.flavors.create.call_count, 1) + _call_mock_nova_create_flavor = self.vimconn.nova.flavors.create.call_args_list + self.assertEqual( + _call_mock_nova_create_flavor[0][1], + ( + { + "name": "sample-flavor-3", + "ram": 3, + "vcpus": 8, + "disk": 50, + "ephemeral": 0, + "swap": 0, + "is_public": True, + } + ), + ) + + self.assertEqual(mock_reload_connection.call_count, 1) + self.assertEqual(mock_get_flavor_details.call_count, 1) + _call_mock_change_flavor = mock_change_flavor_name.call_args_list + self.assertEqual( + _call_mock_change_flavor[0][0], + ( + "sample-flavor", + 0, + {"name": "sample-flavor", "ram": 3, "vcpus": 8, "disk": 50}, + ), + ) + self.assertEqual(mock_change_flavor_name.call_count, 1) + mock_extended_config_of_flavor.assert_not_called() + call_mock_format_exception = mock_format_exception.call_args + self.assertEqual( + str(call_mock_format_exception[0][0]), str(ClientException(error_msg)) + ) + self.assertEqual(mock_format_exception.call_count, 1) + + @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock()) + @patch.object( + vimconnector, + "_process_extended_config_of_flavor", + new_callable=CopyingMock(), + ) + @patch.object(vimconnector, "_change_flavor_name", new_callable=CopyingMock()) + @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock()) + @patch.object(vimconnector, "_format_exception", new_callable=CopyingMock()) + def test_new_flavor_change_flavor_name_conflict_exception_occurred_change_name_if_used_true( + self, + mock_format_exception, + mock_reload_connection, + mock_change_flavor_name, + mock_extended_config_of_flavor, + mock_get_flavor_details, + ): + """Create new flavor, nvExceptions.Conflict occurred, + change_name_if_used is true.""" + error_msg = "Conflict has occurred while creating flavor name." + error2 = nvExceptions.Conflict(error_msg) + mock_change_flavor_name.side_effect = [ + "sample-flavor-3", + "sample-flavor-4", + "sample-flavor-5", + ] + mock_get_flavor_details.return_value = (3, 8, {}, None) + self.vimconn.nova.flavors.create.side_effect = error2 + with self.assertRaises(Exception) as err: + self.vimconn.new_flavor(flavor_data2) + self.assertEqual(str(err.exception), error_msg) + self.assertEqual(type(err.exception), nvExceptions.Conflict) + self.assertEqual(self.vimconn.nova.flavors.create.call_count, 3) + _call_mock_nova_create_flavor = self.vimconn.nova.flavors.create.call_args_list + self.assertEqual( + _call_mock_nova_create_flavor[0][1], + ( + { + "name": "sample-flavor-3", + "ram": 3, + "vcpus": 8, + "disk": 50, + "ephemeral": 0, + "swap": 0, + "is_public": True, + } + ), + ) + self.assertEqual( + _call_mock_nova_create_flavor[1][1], + ( + { + "name": "sample-flavor-4", + "ram": 3, + "vcpus": 8, + "disk": 50, + "ephemeral": 0, + "swap": 0, + "is_public": True, + } + ), + ) + self.assertEqual( + _call_mock_nova_create_flavor[2][1], + ( + { + "name": "sample-flavor-5", + "ram": 3, + "vcpus": 8, + "disk": 50, + "ephemeral": 0, + "swap": 0, + "is_public": True, + } + ), + ) + + self.assertEqual(mock_reload_connection.call_count, 3) + _call_mock_change_flavor = mock_change_flavor_name.call_args_list + self.assertEqual( + _call_mock_change_flavor[0][0], + ( + "sample-flavor", + 0, + {"name": "sample-flavor", "ram": 3, "vcpus": 8, "disk": 50}, + ), + ) + self.assertEqual( + _call_mock_change_flavor[1][0], + ( + "sample-flavor-3", + 0, + {"name": "sample-flavor", "ram": 3, "vcpus": 8, "disk": 50}, + ), + ) + self.assertEqual( + _call_mock_change_flavor[2][0], + ( + "sample-flavor-4", + 0, + {"name": "sample-flavor", "ram": 3, "vcpus": 8, "disk": 50}, + ), + ) + self.assertEqual(mock_change_flavor_name.call_count, 3) + mock_extended_config_of_flavor.assert_not_called() + call_mock_format_exception = mock_format_exception.call_args + self.assertEqual( + str(call_mock_format_exception[0][0]), str(Conflict(error_msg)) + ) + self.assertEqual(mock_format_exception.call_count, 1) + + if __name__ == "__main__": unittest.main() diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index 8ab3bb0f..d70e2ec2 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py @@ -38,7 +38,7 @@ from pprint import pformat import random import re import time -from typing import Dict, Optional, Tuple +from typing import Dict, List, Optional, Tuple from cinderclient import client as cClient from glanceclient import client as glClient @@ -1227,11 +1227,14 @@ class vimconnector(vimconn.VimConnector): ) as e: self._format_exception(e) - def process_resource_quota(self, quota, prefix, extra_specs): - """ - :param prefix: - :param extra_specs: - :return: + @staticmethod + def process_resource_quota(quota: dict, prefix: str, extra_specs: dict) -> None: + """Process resource quota and fill up extra_specs. + Args: + quota (dict): Keeping the quota of resurces + prefix (str) Prefix + extra_specs (dict) Dict to be filled to be used during flavor creation + """ if "limit" in quota: extra_specs["quota:" + prefix + "_limit"] = quota["limit"] @@ -1243,11 +1246,253 @@ class vimconnector(vimconn.VimConnector): extra_specs["quota:" + prefix + "_shares_level"] = "custom" extra_specs["quota:" + prefix + "_shares_share"] = quota["shares"] - def new_flavor(self, flavor_data, change_name_if_used=True): - """Adds a tenant flavor to openstack VIM - if change_name_if_used is True, it will change name in case of conflict, because it is not supported name - repetition - Returns the flavor identifier + @staticmethod + def process_numa_memory( + numa: dict, node_id: Optional[int], extra_specs: dict + ) -> None: + """Set the memory in extra_specs. + Args: + numa (dict): A dictionary which includes numa information + node_id (int): ID of numa node + extra_specs (dict): To be filled. + + """ + if not numa.get("memory"): + return + memory_mb = numa["memory"] * 1024 + memory = "hw:numa_mem.{}".format(node_id) + extra_specs[memory] = int(memory_mb) + + @staticmethod + def process_numa_vcpu(numa: dict, node_id: int, extra_specs: dict) -> None: + """Set the cpu in extra_specs. + Args: + numa (dict): A dictionary which includes numa information + node_id (int): ID of numa node + extra_specs (dict): To be filled. + + """ + if not numa.get("vcpu"): + return + vcpu = numa["vcpu"] + cpu = "hw:numa_cpus.{}".format(node_id) + vcpu = ",".join(map(str, vcpu)) + extra_specs[cpu] = vcpu + + @staticmethod + def process_numa_paired_threads(numa: dict, extra_specs: dict) -> Optional[int]: + """Fill up extra_specs if numa has paired-threads. + Args: + numa (dict): A dictionary which includes numa information + extra_specs (dict): To be filled. + + Returns: + vcpus (int) Number of virtual cpus + + """ + if not numa.get("paired-threads"): + return + # cpu_thread_policy "require" implies that compute node must have an STM architecture + vcpus = numa["paired-threads"] * 2 + extra_specs["hw:cpu_thread_policy"] = "require" + extra_specs["hw:cpu_policy"] = "dedicated" + return vcpus + + @staticmethod + def process_numa_cores(numa: dict, extra_specs: dict) -> Optional[int]: + """Fill up extra_specs if numa has cores. + Args: + numa (dict): A dictionary which includes numa information + extra_specs (dict): To be filled. + + Returns: + vcpus (int) Number of virtual cpus + + """ + # cpu_thread_policy "isolate" implies that the host must not have an SMT + # architecture, or a non-SMT architecture will be emulated + if not numa.get("cores"): + return + vcpus = numa["cores"] + extra_specs["hw:cpu_thread_policy"] = "isolate" + extra_specs["hw:cpu_policy"] = "dedicated" + return vcpus + + @staticmethod + def process_numa_threads(numa: dict, extra_specs: dict) -> Optional[int]: + """Fill up extra_specs if numa has threads. + Args: + numa (dict): A dictionary which includes numa information + extra_specs (dict): To be filled. + + Returns: + vcpus (int) Number of virtual cpus + + """ + # cpu_thread_policy "prefer" implies that the host may or may not have an SMT architecture + if not numa.get("threads"): + return + vcpus = numa["threads"] + extra_specs["hw:cpu_thread_policy"] = "prefer" + extra_specs["hw:cpu_policy"] = "dedicated" + return vcpus + + def _process_numa_parameters_of_flavor( + self, numas: List, extra_specs: Dict, vcpus: Optional[int] + ) -> int: + """Process numa parameters and fill up extra_specs. + + Args: + numas (list): List of dictionary which includes numa information + extra_specs (dict): To be filled. + vcpus (int) Number of virtual cpus + + Returns: + vcpus (int) Number of virtual cpus + + """ + numa_nodes = len(numas) + extra_specs["hw:numa_nodes"] = str(numa_nodes) + + if self.vim_type == "VIO": + extra_specs["vmware:extra_config"] = '{"numa.nodeAffinity":"0"}' + extra_specs["vmware:latency_sensitivity_level"] = "high" + + for numa in numas: + if "id" in numa: + node_id = numa["id"] + # overwrite ram and vcpus + # check if key "memory" is present in numa else use ram value at flavor + self.process_numa_memory(numa, node_id, extra_specs) + self.process_numa_vcpu(numa, node_id, extra_specs) + + # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/implemented/virt-driver-cpu-thread-pinning.html + extra_specs["hw:cpu_sockets"] = str(numa_nodes) + + if "paired-threads" in numa: + vcpus = self.process_numa_paired_threads(numa, extra_specs) + + elif "cores" in numa: + vcpus = self.process_numa_cores(numa, extra_specs) + + elif "threads" in numa: + vcpus = self.process_numa_threads(numa, extra_specs) + + return vcpus + + def _change_flavor_name( + self, name: str, name_suffix: int, flavor_data: dict + ) -> str: + """Change the flavor name if the name already exists. + + Args: + name (str): Flavor name to be checked + name_suffix (int): Suffix to be appended to name + flavor_data (dict): Flavor dict + + Returns: + name (str): New flavor name to be used + + """ + # Get used names + fl = self.nova.flavors.list() + fl_names = [f.name for f in fl] + + while name in fl_names: + name_suffix += 1 + name = flavor_data["name"] + "-" + str(name_suffix) + + return name + + def _process_extended_config_of_flavor( + self, extended: dict, extra_specs: dict, vcpus: Optional[int] + ) -> int: + """Process the extended dict to fill up extra_specs. + Args: + + extended (dict): Keeping the extra specification of flavor + extra_specs (dict) Dict to be filled to be used during flavor creation + vcpus (int) Number of virtual cpus + + Returns: + vcpus (int) Number of virtual cpus + + """ + quotas = { + "cpu-quota": "cpu", + "mem-quota": "memory", + "vif-quota": "vif", + "disk-io-quota": "disk_io", + } + + page_sizes = { + "LARGE": "large", + "SMALL": "small", + "SIZE_2MB": "2MB", + "SIZE_1GB": "1GB", + "PREFER_LARGE": "any", + } + + policies = { + "cpu-pinning-policy": "hw:cpu_policy", + "cpu-thread-pinning-policy": "hw:cpu_thread_policy", + "mem-policy": "hw:numa_mempolicy", + } + + numas = extended.get("numas") + if numas: + vcpus = self._process_numa_parameters_of_flavor(numas, extra_specs, vcpus) + + for quota, item in quotas.items(): + if quota in extended.keys(): + self.process_resource_quota(extended.get(quota), item, extra_specs) + + # Set the mempage size as specified in the descriptor + if extended.get("mempage-size"): + if extended["mempage-size"] in page_sizes.keys(): + extra_specs["hw:mem_page_size"] = page_sizes[extended["mempage-size"]] + else: + # Normally, validations in NBI should not allow to this condition. + self.logger.debug( + "Invalid mempage-size %s. Will be ignored", + extended.get("mempage-size"), + ) + + for policy, hw_policy in policies.items(): + if extended.get(policy): + extra_specs[hw_policy] = extended[policy].lower() + + return vcpus + + @staticmethod + def _get_flavor_details(flavor_data: dict) -> Tuple: + """Returns the details of flavor + Args: + flavor_data (dict): Dictionary that includes required flavor details + + Returns: + ram, vcpus, extra_specs, extended (tuple): Main items of required flavor + + """ + return ( + flavor_data.get("ram", 64), + flavor_data.get("vcpus", 1), + {}, + flavor_data.get("extended"), + ) + + def new_flavor(self, flavor_data: dict, change_name_if_used: bool = True) -> str: + """Adds a tenant flavor to openstack VIM. + if change_name_if_used is True, it will change name in case of conflict, + because it is not supported name repetition. + + Args: + flavor_data (dict): Flavor details to be processed + change_name_if_used (bool): Change name in case of conflict + + Returns: + flavor_id (str): flavor identifier + """ self.logger.debug("Adding flavor '%s'", str(flavor_data)) retry = 0 @@ -1262,138 +1507,18 @@ class vimconnector(vimconn.VimConnector): self._reload_connection() if change_name_if_used: - # get used names - fl_names = [] - fl = self.nova.flavors.list() - - for f in fl: - fl_names.append(f.name) - - while name in fl_names: - name_suffix += 1 - name = flavor_data["name"] + "-" + str(name_suffix) - - ram = flavor_data.get("ram", 64) - vcpus = flavor_data.get("vcpus", 1) - extra_specs = {} + name = self._change_flavor_name(name, name_suffix, flavor_data) - extended = flavor_data.get("extended") + ram, vcpus, extra_specs, extended = self._get_flavor_details( + flavor_data + ) if extended: - numas = extended.get("numas") - - if numas: - numa_nodes = len(numas) - - extra_specs["hw:numa_nodes"] = str(numa_nodes) - - if self.vim_type == "VIO": - extra_specs[ - "vmware:extra_config" - ] = '{"numa.nodeAffinity":"0"}' - extra_specs["vmware:latency_sensitivity_level"] = "high" - - for numa in numas: - if "id" in numa: - node_id = numa["id"] - - if "memory" in numa: - memory_mb = numa["memory"] * 1024 - memory = "hw:numa_mem.{}".format(node_id) - extra_specs[memory] = int(memory_mb) - - if "vcpu" in numa: - vcpu = numa["vcpu"] - cpu = "hw:numa_cpus.{}".format(node_id) - vcpu = ",".join(map(str, vcpu)) - extra_specs[cpu] = vcpu - - # overwrite ram and vcpus - # check if key "memory" is present in numa else use ram value at flavor - # See for reference: https://specs.openstack.org/openstack/nova-specs/specs/mitaka/ - # implemented/virt-driver-cpu-thread-pinning.html - extra_specs["hw:cpu_sockets"] = str(numa_nodes) - - if "paired-threads" in numa: - vcpus = numa["paired-threads"] * 2 - # cpu_thread_policy "require" implies that the compute node must have an - # STM architecture - extra_specs["hw:cpu_thread_policy"] = "require" - extra_specs["hw:cpu_policy"] = "dedicated" - elif "cores" in numa: - vcpus = numa["cores"] - # cpu_thread_policy "prefer" implies that the host must not have an SMT - # architecture, or a non-SMT architecture will be emulated - extra_specs["hw:cpu_thread_policy"] = "isolate" - extra_specs["hw:cpu_policy"] = "dedicated" - elif "threads" in numa: - vcpus = numa["threads"] - # cpu_thread_policy "prefer" implies that the host may or may not have an SMT - # architecture - extra_specs["hw:cpu_thread_policy"] = "prefer" - extra_specs["hw:cpu_policy"] = "dedicated" - # for interface in numa.get("interfaces",() ): - # if interface["dedicated"]=="yes": - # raise vimconn.VimConnException("Passthrough interfaces are not supported - # for the openstack connector", http_code=vimconn.HTTP_Service_Unavailable) - # #TODO, add the key 'pci_passthrough:alias"="