Update from master part 2
[osm/RO.git] / RO-VIM-openstack / osm_rovim_openstack / tests / test_vimconn_openstack.py
index 1de45fd..2e1ddc6 100644 (file)
@@ -68,6 +68,7 @@ ip_addr1 = "20.3.4.5"
 volume_id = "ac408b73-b9cc-4a6a-a270-82cc4811bd4a"
 volume_id2 = "o4e0e83-b9uu-4akk-a234-89cc4811bd4a"
 volume_id3 = "44e0e83-t9uu-4akk-a234-p9cc4811bd4a"
+volume_id4 = "91bf5674-5b85-41d1-aa3b-4848e2691088"
 virtual_mac_id = "64e0e83-t9uu-4akk-a234-p9cc4811bd4a"
 created_items_all_true = {
     f"floating_ip:{floating_network_vim_id}": True,
@@ -103,12 +104,17 @@ flavor_data2 = {
 }
 
 
-class Status:
-    def __init__(self, s):
-        self.status = s
+def check_if_assert_not_called(mocks: list):
+    for mocking in mocks:
+        mocking.assert_not_called()
+
 
-    def __str__(self):
-        return self.status
+class Volume:
+    def __init__(self, s, type="__DEFAULT__", name="", id=""):
+        self.status = s
+        self.volume_type = type
+        self.name = name
+        self.id = id
 
 
 class CopyingMock(MagicMock):
@@ -354,7 +360,7 @@ class TestNewVmInstance(unittest.TestCase):
         self.vimconn._prepare_port_dict_mac_ip_addr(net, port_dict)
         self.assertDictEqual(port_dict, result_dict)
 
-    def test_prepare_port_dict_mac_ip_addr_no_mac_and_ip(self):
+    def test_prepare_port_dict_mac_ip_addr_empty_net(self):
         """mac address and ip address does not exist."""
         net = {}
         port_dict = {}
@@ -362,6 +368,67 @@ class TestNewVmInstance(unittest.TestCase):
         self.vimconn._prepare_port_dict_mac_ip_addr(net, port_dict)
         self.assertDictEqual(port_dict, result_dict)
 
+    def test_prepare_port_dict_mac_ip_addr_dual(self):
+        """mac address, ipv4 and ipv6 addresses exist."""
+        net = {
+            "mac_address": mac_address,
+            "ip_address": ["10.0.1.5", "2345:0425:2CA1:0000:0000:0567:5673:23b5"],
+        }
+        port_dict = {}
+        result_dict = {
+            "mac_address": mac_address,
+            "fixed_ips": [
+                {"ip_address": "10.0.1.5"},
+                {"ip_address": "2345:0425:2CA1:0000:0000:0567:5673:23b5"},
+            ],
+        }
+        self.vimconn._prepare_port_dict_mac_ip_addr(net, port_dict)
+        self.assertDictEqual(port_dict, result_dict)
+
+    def test_prepare_port_dict_mac_ip_addr_dual_ip_addr_is_not_list(self):
+        """mac address, ipv4 and ipv6 addresses exist."""
+        net = {
+            "mac_address": mac_address,
+            "ip_address": "10.0.1.5",
+        }
+        port_dict = {}
+        result_dict = {
+            "mac_address": mac_address,
+            "fixed_ips": [
+                {"ip_address": "10.0.1.5"},
+            ],
+        }
+        self.vimconn._prepare_port_dict_mac_ip_addr(net, port_dict)
+        self.assertDictEqual(port_dict, result_dict)
+
+    def test_prepare_port_dict_mac_ip_addr_dual_net_without_ip_addr(self):
+        """mac address, ipv4 and ipv6 addresses exist."""
+        net = {
+            "mac_address": mac_address,
+            "ip_address": [],
+        }
+        port_dict = {}
+        result_dict = {
+            "mac_address": mac_address,
+        }
+        self.vimconn._prepare_port_dict_mac_ip_addr(net, port_dict)
+        self.assertDictEqual(port_dict, result_dict)
+
+    def test_prepare_port_dict_mac_ip_addr_dual_net_without_mac_addr(self):
+        """mac address, ipv4 and ipv6 addresses exist."""
+        net = {
+            "ip_address": ["10.0.1.5", "2345:0425:2CA1:0000:0000:0567:5673:23b5"],
+        }
+        port_dict = {}
+        result_dict = {
+            "fixed_ips": [
+                {"ip_address": "10.0.1.5"},
+                {"ip_address": "2345:0425:2CA1:0000:0000:0567:5673:23b5"},
+            ],
+        }
+        self.vimconn._prepare_port_dict_mac_ip_addr(net, port_dict)
+        self.assertDictEqual(port_dict, result_dict)
+
     def test_create_new_port(self):
         """new port has id and mac address."""
         new_port = {
@@ -374,7 +441,7 @@ class TestNewVmInstance(unittest.TestCase):
         net, port_dict, created_items = {}, {}, {}
         expected_result = new_port
         expected_net = {
-            "mac_adress": mac_address,
+            "mac_address": mac_address,
             "vim_id": port_id,
         }
         expected_created_items = {f"port:{port_id}": True}
@@ -1246,6 +1313,43 @@ class TestNewVmInstance(unittest.TestCase):
         self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes)
         self.vimconn.cinder.volumes.create.assert_not_called()
 
+    @patch.object(vimconnector, "update_block_device_mapping")
+    def test__prepare_shared_volumes_vim_using_volume_id(
+        self, mock_update_block_device_mapping
+    ):
+        """Existing persistent non root volume with vim_volume_id.
+        class Volume:
+            def __init__(self, s, type="__DEFAULT__", name="", id=""):
+                self.status = s
+                self.volume_type = type
+                self.name = name
+                self.id = id
+        volumes = {"shared-volume": volume_id4}
+
+        The device mappeing BEFORE is: {}
+        The device mappeing AFTER is: {'vdb': '8ca50cc6-a779-4513-a1f3-900b8b3987d2'}
+        """
+        base_disk_index = ord("b")
+        disk = {"name": "shared-volume"}
+        block_device_mapping = {}
+        existing_vim_volumes = []
+        created_items = {}
+        expected_block_device_mapping = {}
+        self.vimconn.cinder.volumes.list.return_value = [
+            Volume("avaible", "multiattach", "shared-volume", volume_id4)
+        ]
+        self.vimconn.cinder.volumes.get.return_value.id = volume_id4
+        self.vimconn._prepare_shared_volumes(
+            name,
+            disk,
+            base_disk_index,
+            block_device_mapping,
+            existing_vim_volumes,
+            created_items,
+        )
+        self.vimconn.cinder.volumes.get.assert_called_with(volume_id4)
+        self.assertDictEqual(block_device_mapping, expected_block_device_mapping)
+
     @patch.object(vimconnector, "update_block_device_mapping")
     def test_prepare_persistent_non_root_volumes_vim_using_volume_id(
         self, mock_update_block_device_mapping
@@ -1511,6 +1615,22 @@ class TestNewVmInstance(unittest.TestCase):
             _call_mock_update_block_device_mapping[0].kwargs["created_items"], {}
         )
 
+    @patch.object(vimconnector, "update_block_device_mapping")
+    def test_new_shared_volumes(self, mock_update_block_device_mapping):
+        """Create shared volume."""
+        self.vimconn.cinder = CopyingMock()
+        self.vimconn.cinder.volumes.create.return_value.id = volume_id4
+        shared_volume_data = {"size": 10, "name": "shared-volume"}
+        self.vimconn.cinder.volumes.create.side_effect = [
+            Volume("avaible", "multiattach", "shared-volume", volume_id4)
+        ]
+        result = self.vimconn.new_shared_volumes(shared_volume_data)
+        self.vimconn.cinder.volumes.create.assert_called_once_with(
+            size=10, name="shared-volume", volume_type="multiattach"
+        )
+        self.assertEqual(result[0], "shared-volume")
+        self.assertEqual(result[1], volume_id4)
+
     @patch.object(vimconnector, "update_block_device_mapping")
     def test_prepare_persistent_root_volumes_create_raise_exception(
         self, mock_update_block_device_mapping
@@ -1623,9 +1743,9 @@ class TestNewVmInstance(unittest.TestCase):
             f"volume:{volume_id3}": True,
         }
         self.vimconn.cinder.volumes.get.side_effect = [
-            Status("processing"),
-            Status("available"),
-            Status("available"),
+            Volume("processing"),
+            Volume("available"),
+            Volume("available"),
         ]
 
         result = self.vimconn._wait_for_created_volumes_availability(
@@ -1650,9 +1770,9 @@ class TestNewVmInstance(unittest.TestCase):
             {"id": "44e0e83-b9uu-4akk-t234-p9cc4811bd4a"},
         ]
         self.vimconn.cinder.volumes.get.side_effect = [
-            Status("processing"),
-            Status("available"),
-            Status("available"),
+            Volume("processing"),
+            Volume("available", "multiattach"),
+            Volume("available"),
         ]
 
         result = self.vimconn._wait_for_existing_volumes_availability(
@@ -1676,8 +1796,8 @@ class TestNewVmInstance(unittest.TestCase):
         elapsed_time = 1805
         created_items = {f"volume:{volume_id2}": True}
         self.vimconn.cinder.volumes.get.side_effect = [
-            Status("processing"),
-            Status("processing"),
+            Volume("processing"),
+            Volume("processing"),
         ]
         with patch("time.sleep", mock_sleep):
             result = self.vimconn._wait_for_created_volumes_availability(
@@ -1695,8 +1815,8 @@ class TestNewVmInstance(unittest.TestCase):
         elapsed_time = 1805
         existing_vim_volumes = [{"id": volume_id2}]
         self.vimconn.cinder.volumes.get.side_effect = [
-            Status("processing"),
-            Status("processing"),
+            Volume("processing"),
+            Volume("processing"),
         ]
 
         result = self.vimconn._wait_for_existing_volumes_availability(
@@ -3884,6 +4004,15 @@ class TestNewVmInstance(unittest.TestCase):
         self.vimconn.logger.error.assert_not_called()
         self.assertEqual(created_items, expected_created_items)
 
+    def test_delete_shared_volumes(self):
+        """cinder delete shared volumes"""
+        shared_volume_vim_id = volume_id4
+        self.vimconn.cinder.volumes.get.return_value.status = "available"
+        self.vimconn.delete_shared_volumes(shared_volume_vim_id)
+        self.vimconn.cinder.volumes.get.assert_called_once_with(shared_volume_vim_id)
+        self.vimconn.cinder.volumes.delete.assert_called_once_with(shared_volume_vim_id)
+        self.vimconn.logger.error.assert_not_called()
+
     def test_delete_volumes_by_id_with_cinder_get_volume_raise_exception(self):
         """cinder get volume raises exception."""
         created_items = {
@@ -4777,7 +4906,6 @@ class TestNewVmInstance(unittest.TestCase):
         self.assertDictEqual(result, created_items)
 
     def test_update_block_device_mapping_empty_volume(self):
-        """"""
         volume = ""
         block_device_mapping = {}
         base_disk_index = 100
@@ -4792,7 +4920,6 @@ class TestNewVmInstance(unittest.TestCase):
         self.assertEqual(created_items, {})
 
     def test_update_block_device_mapping_invalid_volume(self):
-        """"""
         volume = "Volume-A"
         block_device_mapping = {}
         base_disk_index = 100
@@ -4809,7 +4936,6 @@ class TestNewVmInstance(unittest.TestCase):
         self.assertEqual(created_items, {})
 
     def test_update_block_device_mapping(self):
-        """"""
         volume = MagicMock(autospec=True)
         volume.id = volume_id
         block_device_mapping = {}
@@ -4827,7 +4953,6 @@ class TestNewVmInstance(unittest.TestCase):
         )
 
     def test_update_block_device_mapping_with_keep_flag(self):
-        """"""
         volume = MagicMock(autospec=True)
         volume.id = volume_id
         block_device_mapping = {}
@@ -4892,6 +5017,67 @@ class TestNewVmInstance(unittest.TestCase):
         with self.assertRaises(AttributeError):
             self.vimconn._extract_items_wth_keep_flag_from_created_items(created_items)
 
+    @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock())
+    def test_get_monitoring_data(self, mock_reload_conection):
+        servers = ["server1", "server2"]
+        ports = {"ports": ["port1", "port2"]}
+        self.vimconn.nova.servers.list.return_value = servers
+        self.vimconn.neutron.list_ports.return_value = ports
+        result = self.vimconn.get_monitoring_data()
+        self.assertTupleEqual(result, (servers, ports))
+        mock_reload_conection.assert_called_once()
+        self.vimconn.nova.servers.list.assert_called_once_with(detailed=True)
+        self.vimconn.neutron.list_ports.assert_called_once()
+
+    @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock())
+    def test_get_monitoring_data_reload_connection_raises(self, mock_reload_conection):
+        mock_reload_conection.side_effect = VimConnNotFoundException(
+            "Connection object not found."
+        )
+        with self.assertRaises(VimConnException) as err:
+            result = self.vimconn.get_monitoring_data()
+            self.assertTupleEqual(result, None)
+        self.assertEqual(
+            str(err.exception.args[0]),
+            "Exception in monitoring while getting VMs and ports status: Connection object not found.",
+        )
+        mock_reload_conection.assert_called_once()
+        check_if_assert_not_called(
+            [self.vimconn.nova.servers.list, self.vimconn.neutron.list_ports]
+        )
+
+    @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock())
+    def test_get_monitoring_data_server_list_raises(self, mock_reload_conection):
+        self.vimconn.nova.servers.list.side_effect = VimConnConnectionException(
+            "Can not connect to Cloud API."
+        )
+        with self.assertRaises(VimConnException) as err:
+            result = self.vimconn.get_monitoring_data()
+            self.assertTupleEqual(result, None)
+        self.assertEqual(
+            str(err.exception.args[0]),
+            "Exception in monitoring while getting VMs and ports status: Can not connect to Cloud API.",
+        )
+        mock_reload_conection.assert_called_once()
+        self.vimconn.nova.servers.list.assert_called_once_with(detailed=True)
+        self.vimconn.neutron.list_ports.assert_not_called()
+
+    @patch.object(vimconnector, "_reload_connection", new_callable=CopyingMock())
+    def test_get_monitoring_data_list_ports_raises(self, mock_reload_conection):
+        self.vimconn.neutron.list_ports.side_effect = VimConnConnectionException(
+            "Can not connect to Cloud API."
+        )
+        with self.assertRaises(VimConnException) as err:
+            result = self.vimconn.get_monitoring_data()
+            self.assertTupleEqual(result, None)
+        self.assertEqual(
+            str(err.exception.args[0]),
+            "Exception in monitoring while getting VMs and ports status: Can not connect to Cloud API.",
+        )
+        mock_reload_conection.assert_called_once()
+        self.vimconn.nova.servers.list.assert_called_once_with(detailed=True)
+        self.vimconn.neutron.list_ports.assert_called_once()
+
 
 class TestNewFlavor(unittest.TestCase):
     @patch("logging.getLogger", autospec=True)
@@ -4916,11 +5102,6 @@ class TestNewFlavor(unittest.TestCase):
         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_vio_numa_nodes", new_callable=CopyingMock())
     @patch.object(vimconnector, "process_numa_memory", new_callable=CopyingMock())
     @patch.object(vimconnector, "process_numa_vcpu", new_callable=CopyingMock())
@@ -5003,7 +5184,7 @@ class TestNewFlavor(unittest.TestCase):
             ),
         )
         self.assertDictEqual(extra_specs, expected_extra_specs)
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_process_numa_threads,
                 mock_process_numa_cores,
@@ -5082,7 +5263,7 @@ class TestNewFlavor(unittest.TestCase):
             ),
         )
         self.assertDictEqual(extra_specs, expected_extra_specs)
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_process_numa_threads,
                 mock_process_numa_cores,
@@ -5124,9 +5305,7 @@ class TestNewFlavor(unittest.TestCase):
         mock_process_numa_paired_threads.side_effect = [6, 6]
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
 
-        self.check_if_assert_not_called(
-            [mock_process_numa_threads, mock_process_numa_cores]
-        )
+        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)
@@ -5182,9 +5361,7 @@ class TestNewFlavor(unittest.TestCase):
         self.vimconn.vim_type = "VIO"
         mock_process_numa_paired_threads.side_effect = [4, 4]
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
-        self.check_if_assert_not_called(
-            [mock_process_numa_threads, mock_process_numa_cores]
-        )
+        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)
@@ -5252,7 +5429,7 @@ class TestNewFlavor(unittest.TestCase):
         mock_process_numa_cores.side_effect = [1, 2]
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
 
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [mock_process_numa_threads, mock_process_numa_paired_threads]
         )
         self.assertEqual(mock_process_numa_cores.call_count, 2)
@@ -5301,7 +5478,7 @@ class TestNewFlavor(unittest.TestCase):
         self.vimconn.vim_type = "VIO"
         mock_process_numa_cores.side_effect = [1, 2]
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [mock_process_numa_threads, mock_process_numa_paired_threads]
         )
         self.assertEqual(mock_process_numa_memory.call_count, 2)
@@ -5366,7 +5543,7 @@ class TestNewFlavor(unittest.TestCase):
         self.vimconn.vim_type = "VIO"
         mock_process_numa_threads.return_value = 3
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_process_numa_memory,
                 mock_process_numa_vcpu,
@@ -5425,7 +5602,7 @@ class TestNewFlavor(unittest.TestCase):
         mock_process_numa_threads.return_value = 3
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
 
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_process_numa_memory,
                 mock_process_numa_vcpu,
@@ -5470,7 +5647,7 @@ class TestNewFlavor(unittest.TestCase):
         expected_extra_specs = {"hw:numa_nodes": "0"}
         self.vimconn.vim_type = "VIO"
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_process_numa_memory,
                 mock_process_numa_vcpu,
@@ -5509,7 +5686,7 @@ class TestNewFlavor(unittest.TestCase):
         mock_process_numa_threads.return_value = None
         self.vimconn._process_numa_parameters_of_flavor(numas, extra_specs)
 
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_process_numa_memory,
                 mock_process_numa_vcpu,
@@ -6146,7 +6323,7 @@ class TestNewFlavor(unittest.TestCase):
         extended = {}
         extra_specs = {}
         self.vimconn._process_extended_config_of_flavor(extended, extra_specs)
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [mock_process_resource_quota, mock_process_numa_parameters_of_flavor]
         )
         self.assertEqual(extra_specs, {})
@@ -6258,9 +6435,7 @@ class TestNewFlavor(unittest.TestCase):
         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]
-        )
+        check_if_assert_not_called([self.new_flavor.set_keys, mock_format_exception])
 
     @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock())
     @patch.object(
@@ -6291,7 +6466,7 @@ class TestNewFlavor(unittest.TestCase):
         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(
+        check_if_assert_not_called(
             [mock_change_flavor_name, mock_format_exception, self.new_flavor.set_keys]
         )
 
@@ -6327,7 +6502,7 @@ class TestNewFlavor(unittest.TestCase):
         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(
+        check_if_assert_not_called(
             [
                 self.new_flavor.set_keys,
                 mock_extended_config_of_flavor,
@@ -6365,7 +6540,7 @@ class TestNewFlavor(unittest.TestCase):
         self.assertEqual(
             str(call_mock_format_exception[0][0]), str(ClientException(error_msg))
         )
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_change_flavor_name,
                 mock_get_flavor_details,
@@ -6404,7 +6579,7 @@ class TestNewFlavor(unittest.TestCase):
         self.assertEqual(
             str(call_mock_format_exception[0][0]), str(KeyError(error_msg))
         )
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 mock_reload_connection,
                 mock_change_flavor_name,
@@ -6454,9 +6629,7 @@ class TestNewFlavor(unittest.TestCase):
             swap=0,
             is_public=True,
         )
-        self.check_if_assert_not_called(
-            [self.new_flavor.set_keys, mock_format_exception]
-        )
+        check_if_assert_not_called([self.new_flavor.set_keys, mock_format_exception])
 
     @patch.object(vimconnector, "_get_flavor_details", new_callable=CopyingMock())
     @patch.object(
@@ -6498,7 +6671,7 @@ class TestNewFlavor(unittest.TestCase):
             swap=0,
             is_public=True,
         )
-        self.check_if_assert_not_called(
+        check_if_assert_not_called(
             [
                 self.new_flavor.set_keys,
                 mock_extended_config_of_flavor,
@@ -6546,7 +6719,7 @@ class TestNewFlavor(unittest.TestCase):
         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(
+        check_if_assert_not_called(
             [mock_change_flavor_name, mock_extended_config_of_flavor]
         )
         _call_mock_format_exception = mock_format_exception.call_args
@@ -6739,7 +6912,6 @@ class TestNewFlavor(unittest.TestCase):
         numa_nodes = 0
         extra_specs = {"hw:numa_nodes": "0"}
         expected_extra_spec = {
-            "vmware:extra_config": '{"numa.nodeAffinity":"0"}',
             "vmware:latency_sensitivity_level": "high",
             "hw:numa_nodes": "0",
         }
@@ -6771,7 +6943,6 @@ class TestNewFlavor(unittest.TestCase):
         expected_extra_spec = {
             "vmware:latency_sensitivity_level": "high",
             "hw:numa_nodes": "None",
-            "vmware:extra_config": '{"numa.nodeAffinity":"0"}',
         }
         self.vimconn.process_vio_numa_nodes(numa_nodes, extra_specs)
         self.assertDictEqual(extra_specs, expected_extra_spec)