From: aticig Date: Sat, 3 Sep 2022 15:15:20 +0000 (+0300) Subject: Feature 10936 Keeping persistent volume of VNF X-Git-Tag: release-v13.0-start~1 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FRO.git;a=commitdiff_plain;h=2f4ab6c311722e3a060f5b34ed419be318916a1b Feature 10936 Keeping persistent volume of VNF Change-Id: I3be4b402c9776f145d59b5c40d645f8ab92d34c6 Signed-off-by: aticig --- diff --git a/NG-RO/osm_ng_ro/ns.py b/NG-RO/osm_ng_ro/ns.py index c6f918b2..a4c7b65c 100644 --- a/NG-RO/osm_ng_ro/ns.py +++ b/NG-RO/osm_ng_ro/ns.py @@ -1030,6 +1030,7 @@ class Ns(object): persistent_root_disk[vsd["id"]] = { "image_id": vdu.get("sw-image-desc"), "size": root_disk.get("size-of-storage"), + "keep": Ns.is_volume_keeping_required(root_disk), } disk_list.append(persistent_root_disk[vsd["id"]]) @@ -1073,9 +1074,29 @@ class Ns(object): if disk["id"] not in persistent_disk.keys(): persistent_disk[disk["id"]] = { "size": disk.get("size-of-storage"), + "keep": Ns.is_volume_keeping_required(disk), } disk_list.append(persistent_disk[disk["id"]]) + @staticmethod + def is_volume_keeping_required(virtual_storage_desc: Dict[str, Any]) -> bool: + """Function to decide keeping persistent volume + upon VDU deletion. + + Args: + virtual_storage_desc (Dict[str, Any]): virtual storage description dictionary + + Returns: + bool (True/False) + """ + + if not virtual_storage_desc.get("vdu-storage-requirements"): + return False + for item in virtual_storage_desc.get("vdu-storage-requirements", {}): + if item.get("key") == "keep-volume" and item.get("value") == "true": + return True + return False + @staticmethod def _sort_vdu_interfaces(target_vdu: dict) -> None: """Sort the interfaces according to position number. @@ -1397,13 +1418,13 @@ class Ns(object): if vdu["name"] == target_vdu["vdu-name"]: for vsd in vnfd.get("virtual-storage-desc", ()): root_disk = Ns._select_persistent_root_disk(vsd, vdu) - if not root_disk: continue persistent_root_disk[vsd["id"]] = { "image_id": vdu.get("sw-image-desc"), "size": root_disk["size-of-storage"], + "keep": Ns.is_volume_keeping_required(root_disk), } disk_list.append(persistent_root_disk[vsd["id"]]) @@ -1434,6 +1455,7 @@ class Ns(object): ): persistent_ordinary_disk[disk["id"]] = { "size": disk["size-of-storage"], + "keep": Ns.is_volume_keeping_required(disk), } disk_list.append(persistent_ordinary_disk[disk["id"]]) diff --git a/NG-RO/osm_ng_ro/tests/test_ns.py b/NG-RO/osm_ng_ro/tests/test_ns.py index f4a1c87a..97f072bd 100644 --- a/NG-RO/osm_ng_ro/tests/test_ns.py +++ b/NG-RO/osm_ng_ro/tests/test_ns.py @@ -71,6 +71,9 @@ vnfd_wth_persistent_storage = { "id": "persistent-root-volume", "type-of-storage": "persistent-storage:persistent-storage", "size-of-storage": "10", + "vdu-storage-requirements": [ + {"key": "keep-volume", "value": "true"}, + ], }, { "id": "ephemeral-volume", @@ -144,6 +147,9 @@ target_vdu_wth_persistent_storage = { "id": "persistent-root-volume", "size-of-storage": "10", "type-of-storage": "persistent-storage:persistent-storage", + "vdu-storage-requirements": [ + {"key": "keep-volume", "value": "true"}, + ], }, { "id": "ephemeral-volume", @@ -3239,32 +3245,48 @@ class TestProcessVduParams(unittest.TestCase): self.ns = Ns() self.logger = CopyingMock(autospec=True) - def test_find_persistent_root_volumes_empty_instantiation_vol_list(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_empty_instantiation_vol_list( + self, mock_volume_keeping_required + ): """Find persistent root volume, instantiation_vol_list is empty.""" vnfd = deepcopy(vnfd_wth_persistent_storage) target_vdu = target_vdu_wth_persistent_storage vdu_instantiation_volumes_list = [] disk_list = [] + mock_volume_keeping_required.return_value = True + expected_root_disk = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + "vdu-storage-requirements": [{"key": "keep-volume", "value": "true"}], + } expected_persist_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, } } expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": True, }, ] persist_root_disk = self.ns.find_persistent_root_volumes( vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(persist_root_disk, expected_persist_root_disk) + mock_volume_keeping_required.assert_called_once_with(expected_root_disk) self.assertEqual(disk_list, expected_disk_list) self.assertEqual(len(disk_list), 1) - def test_find_persistent_root_volumes_always_selects_first_vsd_as_root(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_always_selects_first_vsd_as_root( + self, mock_volume_keeping_required + ): """Find persistent root volume, always selects the first vsd as root volume.""" vnfd = deepcopy(vnfd_wth_persistent_storage) vnfd["vdu"][0]["virtual-storage-desc"] = [ @@ -3275,26 +3297,38 @@ class TestProcessVduParams(unittest.TestCase): target_vdu = target_vdu_wth_persistent_storage vdu_instantiation_volumes_list = [] disk_list = [] + mock_volume_keeping_required.return_value = True + expected_root_disk = { + "id": "persistent-volume2", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + } expected_persist_root_disk = { "persistent-volume2": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, } } expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": True, }, ] persist_root_disk = self.ns.find_persistent_root_volumes( vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(persist_root_disk, expected_persist_root_disk) + mock_volume_keeping_required.assert_called_once_with(expected_root_disk) self.assertEqual(disk_list, expected_disk_list) self.assertEqual(len(disk_list), 1) - def test_find_persistent_root_volumes_empty_size_of_storage(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_empty_size_of_storage( + self, mock_volume_keeping_required + ): """Find persistent root volume, size of storage is empty.""" vnfd = deepcopy(vnfd_wth_persistent_storage) vnfd["virtual-storage-desc"][0]["size-of-storage"] = "" @@ -3310,34 +3344,54 @@ class TestProcessVduParams(unittest.TestCase): vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(persist_root_disk, None) + mock_volume_keeping_required.assert_not_called() self.assertEqual(disk_list, []) - def test_find_persistent_root_empty_disk_list(self): - """Find persistent root volume, empty disk list.""" + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_keeping_is_not_required( + self, mock_volume_keeping_required + ): + """Find persistent root volume, volume keeping is not required.""" vnfd = deepcopy(vnfd_wth_persistent_storage) + vnfd["virtual-storage-desc"][1]["vdu-storage-requirements"] = [ + {"key": "keep-volume", "value": "false"}, + ] target_vdu = target_vdu_wth_persistent_storage vdu_instantiation_volumes_list = [] disk_list = [] + mock_volume_keeping_required.return_value = False + expected_root_disk = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + "vdu-storage-requirements": [{"key": "keep-volume", "value": "false"}], + } expected_persist_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": False, } } expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, ] persist_root_disk = self.ns.find_persistent_root_volumes( vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(persist_root_disk, expected_persist_root_disk) + mock_volume_keeping_required.assert_called_once_with(expected_root_disk) self.assertEqual(disk_list, expected_disk_list) self.assertEqual(len(disk_list), 1) - def test_find_persistent_root_volumes_target_vdu_mismatch(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_target_vdu_mismatch( + self, mock_volume_keeping_required + ): """Find persistent root volume, target vdu name is not matching.""" vnfd = deepcopy(vnfd_wth_persistent_storage) vnfd["vdu"][0]["name"] = "Several_Volumes-VM" @@ -3348,10 +3402,14 @@ class TestProcessVduParams(unittest.TestCase): vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(result, None) + mock_volume_keeping_required.assert_not_called() self.assertEqual(disk_list, []) self.assertEqual(len(disk_list), 0) - def test_find_persistent_root_volumes_with_instantiation_vol_list(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_with_instantiation_vol_list( + self, mock_volume_keeping_required + ): """Find persistent root volume, existing volume needs to be used.""" vnfd = deepcopy(vnfd_wth_persistent_storage) target_vdu = target_vdu_wth_persistent_storage @@ -3378,10 +3436,14 @@ class TestProcessVduParams(unittest.TestCase): vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(persist_root_disk, expected_persist_root_disk) + mock_volume_keeping_required.assert_not_called() self.assertEqual(disk_list, expected_disk_list) self.assertEqual(len(disk_list), 1) - def test_find_persistent_root_volumes_invalid_instantiation_params(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_root_volumes_invalid_instantiation_params( + self, mock_volume_keeping_required + ): """Find persistent root volume, existing volume id keyword is invalid.""" vnfd = deepcopy(vnfd_wth_persistent_storage) target_vdu = target_vdu_wth_persistent_storage @@ -3396,50 +3458,64 @@ class TestProcessVduParams(unittest.TestCase): self.ns.find_persistent_root_volumes( vnfd, target_vdu, vdu_instantiation_volumes_list, disk_list ) - + mock_volume_keeping_required.assert_not_called() self.assertEqual(disk_list, []) self.assertEqual(len(disk_list), 0) + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") def test_find_persistent_volumes_vdu_wth_persistent_root_disk_wthout_inst_vol_list( - self, + self, mock_volume_keeping_required ): """Find persistent ordinary volume, there is persistent root disk and instatiation volume list is empty.""" persistent_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": False, } } - + mock_volume_keeping_required.return_value = False target_vdu = target_vdu_wth_persistent_storage vdu_instantiation_volumes_list = [] disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, ] - + expected_disk = { + "id": "persistent-volume2", + "size-of-storage": "10", + "type-of-storage": "persistent-storage:persistent-storage", + } expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, { "size": "10", + "keep": False, }, ] self.ns.find_persistent_volumes( persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(disk_list, expected_disk_list) + mock_volume_keeping_required.assert_called_once_with(expected_disk) - def test_find_persistent_volumes_vdu_wth_inst_vol_list(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_volumes_vdu_wth_inst_vol_list( + self, mock_volume_keeping_required + ): """Find persistent ordinary volume, vim-volume-id is given as instantiation parameter.""" persistent_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": False, } } vdu_instantiation_volumes_list = [ @@ -3453,12 +3529,14 @@ class TestProcessVduParams(unittest.TestCase): { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, ] expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, { "vim_volume_id": vim_volume_id, @@ -3468,35 +3546,47 @@ class TestProcessVduParams(unittest.TestCase): persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(disk_list, expected_disk_list) + mock_volume_keeping_required.assert_not_called() - def test_find_persistent_volumes_vdu_wthout_persistent_storage(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_volumes_vdu_wthout_persistent_storage( + self, mock_volume_keeping_required + ): """Find persistent ordinary volume, there is not any persistent disk.""" persistent_root_disk = {} vdu_instantiation_volumes_list = [] + mock_volume_keeping_required.return_value = False target_vdu = target_vdu_wthout_persistent_storage disk_list = [] self.ns.find_persistent_volumes( persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(disk_list, disk_list) + mock_volume_keeping_required.assert_not_called() + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") def test_find_persistent_volumes_vdu_wth_persistent_root_disk_wthout_ordinary_disk( - self, + self, mock_volume_keeping_required ): """There is persistent root disk, but there is not ordinary persistent disk.""" persistent_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": False, } } vdu_instantiation_volumes_list = [] + mock_volume_keeping_required.return_value = False target_vdu = deepcopy(target_vdu_wth_persistent_storage) target_vdu["virtual-storages"] = [ { "id": "persistent-root-volume", "size-of-storage": "10", "type-of-storage": "persistent-storage:persistent-storage", + "vdu-storage-requirements": [ + {"key": "keep-volume", "value": "true"}, + ], }, { "id": "ephemeral-volume", @@ -3508,16 +3598,22 @@ class TestProcessVduParams(unittest.TestCase): { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, ] self.ns.find_persistent_volumes( persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(disk_list, disk_list) + mock_volume_keeping_required.assert_not_called() - def test_find_persistent_volumes_wth_inst_vol_list_disk_id_mismatch(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_find_persistent_volumes_wth_inst_vol_list_disk_id_mismatch( + self, mock_volume_keeping_required + ): """Find persistent ordinary volume, volume id is not persistent_root_disk dict, vim-volume-id is given as instantiation parameter but disk id is not matching.""" + mock_volume_keeping_required.return_value = True vdu_instantiation_volumes_list = [ { "vim-volume-id": vim_volume_id, @@ -3528,29 +3624,87 @@ class TestProcessVduParams(unittest.TestCase): "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": False, } } disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, ] expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, }, { "size": "10", + "keep": True, }, ] - + expected_disk = { + "id": "persistent-volume2", + "size-of-storage": "10", + "type-of-storage": "persistent-storage:persistent-storage", + } target_vdu = target_vdu_wth_persistent_storage self.ns.find_persistent_volumes( persistent_root_disk, target_vdu, vdu_instantiation_volumes_list, disk_list ) self.assertEqual(disk_list, expected_disk_list) + mock_volume_keeping_required.assert_called_once_with(expected_disk) + + def test_is_volume_keeping_required_true(self): + """Volume keeping is required.""" + virtual_storage_descriptor = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + "vdu-storage-requirements": [ + {"key": "keep-volume", "value": "true"}, + ], + } + result = self.ns.is_volume_keeping_required(virtual_storage_descriptor) + self.assertEqual(result, True) + + def test_is_volume_keeping_required_false(self): + """Volume keeping is not required.""" + virtual_storage_descriptor = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + "vdu-storage-requirements": [ + {"key": "keep-volume", "value": "false"}, + ], + } + result = self.ns.is_volume_keeping_required(virtual_storage_descriptor) + self.assertEqual(result, False) + + def test_is_volume_keeping_required_wthout_vdu_storage_reqirement(self): + """Volume keeping is not required, vdu-storage-requirements key does not exist.""" + virtual_storage_descriptor = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + } + result = self.ns.is_volume_keeping_required(virtual_storage_descriptor) + self.assertEqual(result, False) + + def test_is_volume_keeping_required_wrong_keyword(self): + """vdu-storage-requirements key to indicate keeping-volume is wrong.""" + virtual_storage_descriptor = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + "vdu-storage-requirements": [ + {"key": "hold-volume", "value": "true"}, + ], + } + result = self.ns.is_volume_keeping_required(virtual_storage_descriptor) + self.assertEqual(result, False) def test_sort_vdu_interfaces_position_all_wth_positions(self): """Interfaces are sorted according to position, all have positions.""" @@ -4459,10 +4613,11 @@ class TestProcessVduParams(unittest.TestCase): self.assertDictEqual(cloud_config, expected_cloud_config) @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") - def test_add_persistent_root_disk_to_disk_list( - self, mock_select_persistent_root_disk + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_add_persistent_root_disk_to_disk_list_keep_false( + self, mock_volume_keeping_required, mock_select_persistent_root_disk ): - """Add persistent root disk to disk_list""" + """Add persistent root disk to disk_list, keep volume set to False.""" root_disk = { "id": "persistent-root-volume", "type-of-storage": "persistent-storage:persistent-storage", @@ -4474,10 +4629,12 @@ class TestProcessVduParams(unittest.TestCase): target_vdu = deepcopy(target_vdu_wth_persistent_storage) persistent_root_disk = {} disk_list = [] + mock_volume_keeping_required.return_value = False expected_disk_list = [ { "image_id": "ubuntu20.04", "size": "10", + "keep": False, } ] self.ns._add_persistent_root_disk_to_disk_list( @@ -4485,10 +4642,12 @@ class TestProcessVduParams(unittest.TestCase): ) self.assertEqual(disk_list, expected_disk_list) mock_select_persistent_root_disk.assert_called_once() + mock_volume_keeping_required.assert_called_once() @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") def test_add_persistent_root_disk_to_disk_list_select_persistent_root_disk_raises( - self, mock_select_persistent_root_disk + self, mock_volume_keeping_required, mock_select_persistent_root_disk ): """Add persistent root disk to disk_list""" root_disk = { @@ -4508,35 +4667,86 @@ class TestProcessVduParams(unittest.TestCase): ) self.assertEqual(disk_list, []) mock_select_persistent_root_disk.assert_called_once() + mock_volume_keeping_required.assert_not_called() - def test_add_persistent_ordinary_disk_to_disk_list(self): - """Add persistent ordinary disk to disk_list""" + @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_add_persistent_root_disk_to_disk_list_keep_true( + self, mock_volume_keeping_required, mock_select_persistent_root_disk + ): + """Add persistent root disk, keeo volume set to True.""" + vnfd = deepcopy(vnfd_wth_persistent_storage) target_vdu = deepcopy(target_vdu_wth_persistent_storage) + mock_volume_keeping_required.return_value = True + root_disk = { + "id": "persistent-root-volume", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + "vdu-storage-requirements": [ + {"key": "keep-volume", "value": "true"}, + ], + } + mock_select_persistent_root_disk.return_value = root_disk + persistent_root_disk = {} + disk_list = [] + expected_disk_list = [ + { + "image_id": "ubuntu20.04", + "size": "10", + "keep": True, + } + ] + self.ns._add_persistent_root_disk_to_disk_list( + vnfd, target_vdu, persistent_root_disk, disk_list + ) + self.assertEqual(disk_list, expected_disk_list) + mock_volume_keeping_required.assert_called_once_with(root_disk) + + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_add_persistent_ordinary_disk_to_disk_list( + self, mock_volume_keeping_required + ): + """Add persistent ordinary disk, keeo volume set to True.""" + target_vdu = deepcopy(target_vdu_wth_persistent_storage) + mock_volume_keeping_required.return_value = False persistent_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, } } + ordinary_disk = { + "id": "persistent-volume2", + "type-of-storage": "persistent-storage:persistent-storage", + "size-of-storage": "10", + } persistent_ordinary_disk = {} disk_list = [] expected_disk_list = [ { "size": "10", + "keep": False, } ] self.ns._add_persistent_ordinary_disks_to_disk_list( target_vdu, persistent_root_disk, persistent_ordinary_disk, disk_list ) self.assertEqual(disk_list, expected_disk_list) + mock_volume_keeping_required.assert_called_once_with(ordinary_disk) - def test_add_persistent_ordinary_disk_to_disk_list_vsd_id_in_root_disk_dict(self): + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") + def test_add_persistent_ordinary_disk_to_disk_list_vsd_id_in_root_disk_dict( + self, mock_volume_keeping_required + ): """Add persistent ordinary disk, vsd id is in root_disk dict.""" target_vdu = deepcopy(target_vdu_wth_persistent_storage) + mock_volume_keeping_required.return_value = False persistent_root_disk = { "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, }, "persistent-volume2": { "size": "10", @@ -4549,10 +4759,12 @@ class TestProcessVduParams(unittest.TestCase): target_vdu, persistent_root_disk, persistent_ordinary_disk, disk_list ) self.assertEqual(disk_list, []) + mock_volume_keeping_required.assert_not_called() @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") def test_add_persistent_root_disk_to_disk_list_vnfd_wthout_persistent_storage( - self, mock_select_persistent_root_disk + self, mock_volume_keeping_required, mock_select_persistent_root_disk ): """VNFD does not have persistent storage.""" vnfd = deepcopy(vnfd_wthout_persistent_storage) @@ -4565,10 +4777,12 @@ class TestProcessVduParams(unittest.TestCase): ) self.assertEqual(disk_list, []) self.assertEqual(mock_select_persistent_root_disk.call_count, 2) + mock_volume_keeping_required.assert_not_called() @patch("osm_ng_ro.ns.Ns._select_persistent_root_disk") + @patch("osm_ng_ro.ns.Ns.is_volume_keeping_required") def test_add_persistent_root_disk_to_disk_list_wthout_persistent_root_disk( - self, mock_select_persistent_root_disk + self, mock_volume_keeping_required, mock_select_persistent_root_disk ): """Persistent_root_disk dict is empty.""" vnfd = deepcopy(vnfd_wthout_persistent_storage) @@ -4581,6 +4795,7 @@ class TestProcessVduParams(unittest.TestCase): ) self.assertEqual(disk_list, []) self.assertEqual(mock_select_persistent_root_disk.call_count, 2) + mock_volume_keeping_required.assert_not_called() def test_prepare_vdu_affinity_group_list_invalid_extra_dict(self): """Invalid extra dict.""" @@ -4979,6 +5194,7 @@ class TestProcessVduParams(unittest.TestCase): "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, } } mock_find_persistent_root_volumes.return_value = persistent_root_disk @@ -5053,6 +5269,7 @@ class TestProcessVduParams(unittest.TestCase): "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, } } mock_find_persistent_root_volumes.return_value = persistent_root_disk @@ -5126,6 +5343,7 @@ class TestProcessVduParams(unittest.TestCase): "persistent-root-volume": { "image_id": "ubuntu20.04", "size": "10", + "keep": True, } } mock_find_persistent_root_volumes.return_value = persistent_root_disk 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 bdd6bed9..c0e1c6ed 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 @@ -2242,7 +2242,10 @@ class TestNewVmInstance(unittest.TestCase): self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) self.vimconn.cinder.volumes.create.assert_not_called() - def test_prepare_persistent_non_root_volumes_vim_using_volume_id(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_non_root_volumes_vim_using_volume_id( + self, mock_update_block_device_mapping + ): """Existing persistent non root volume with vim_volume_id.""" vm_av_zone = ["nova"] base_disk_index = ord("b") @@ -2264,8 +2267,12 @@ class TestNewVmInstance(unittest.TestCase): self.assertDictEqual(block_device_mapping, expected_block_device_mapping) self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) self.vimconn.cinder.volumes.create.assert_not_called() + mock_update_block_device_mapping.assert_not_called() - def test_prepare_persistent_root_volumes_using_vim_id(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_root_volumes_using_vim_id( + self, mock_update_block_device_mapping + ): """Existing persistent root volume with vim_id.""" vm_av_zone = ["nova"] base_disk_index = ord("a") @@ -2289,8 +2296,12 @@ class TestNewVmInstance(unittest.TestCase): self.assertDictEqual(block_device_mapping, expected_block_device_mapping) self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) self.vimconn.cinder.volumes.create.assert_not_called() + mock_update_block_device_mapping.assert_not_called() - def test_prepare_persistent_non_root_volumes_using_vim_id(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_non_root_volumes_using_vim_id( + self, mock_update_block_device_mapping + ): """Existing persistent root volume with vim_id.""" vm_av_zone = ["nova"] base_disk_index = ord("b") @@ -2314,8 +2325,12 @@ class TestNewVmInstance(unittest.TestCase): self.assertDictEqual(block_device_mapping, expected_block_device_mapping) self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) self.vimconn.cinder.volumes.create.assert_not_called() + mock_update_block_device_mapping.assert_not_called() - def test_prepare_persistent_root_volumes_create(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_root_volumes_create( + self, mock_update_block_device_mapping + ): """Create persistent root volume.""" self.vimconn.cinder.volumes.create.return_value.id = volume_id2 vm_av_zone = ["nova"] @@ -2325,7 +2340,51 @@ class TestNewVmInstance(unittest.TestCase): existing_vim_volumes = [] created_items = {} expected_boot_vol_id = volume_id2 - expected_block_device_mapping = {"vda": volume_id2} + boot_volume_id = self.vimconn._prepare_persistent_root_volumes( + name, + vm_av_zone, + disk, + base_disk_index, + block_device_mapping, + existing_vim_volumes, + created_items, + ) + self.assertEqual(boot_volume_id, expected_boot_vol_id) + self.vimconn.cinder.volumes.create.assert_called_once_with( + size=10, + name="basicvmvda", + imageRef=image_id, + availability_zone=["nova"], + ) + mock_update_block_device_mapping.assert_called_once() + _call_mock_update_block_device_mapping = ( + mock_update_block_device_mapping.call_args_list + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["block_device_mapping"], + block_device_mapping, + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["base_disk_index"], 97 + ) + self.assertEqual(_call_mock_update_block_device_mapping[0].kwargs["disk"], disk) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["created_items"], {} + ) + + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_root_volumes_create_with_keep( + self, mock_update_block_device_mapping + ): + """Create persistent root volume, disk has keep parameter.""" + self.vimconn.cinder.volumes.create.return_value.id = volume_id2 + vm_av_zone = ["nova"] + base_disk_index = ord("a") + disk = {"size": 10, "image_id": image_id, "keep": True} + block_device_mapping = {} + existing_vim_volumes = [] + created_items = {} + expected_boot_vol_id = volume_id2 expected_existing_vim_volumes = [] boot_volume_id = self.vimconn._prepare_persistent_root_volumes( name, @@ -2337,7 +2396,6 @@ class TestNewVmInstance(unittest.TestCase): created_items, ) self.assertEqual(boot_volume_id, expected_boot_vol_id) - self.assertDictEqual(block_device_mapping, expected_block_device_mapping) self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) self.vimconn.cinder.volumes.create.assert_called_once_with( size=10, @@ -2345,9 +2403,26 @@ class TestNewVmInstance(unittest.TestCase): imageRef=image_id, availability_zone=["nova"], ) - self.assertEqual(created_items, {f"volume:{volume_id2}": True}) + mock_update_block_device_mapping.assert_called_once() + _call_mock_update_block_device_mapping = ( + mock_update_block_device_mapping.call_args_list + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["block_device_mapping"], + block_device_mapping, + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["base_disk_index"], 97 + ) + self.assertEqual(_call_mock_update_block_device_mapping[0].kwargs["disk"], disk) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["created_items"], {} + ) - def test_prepare_persistent_non_root_volumes_create(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_non_root_volumes_create( + self, mock_update_block_device_mapping + ): """Create persistent non-root volume.""" self.vimconn.cinder = CopyingMock() self.vimconn.cinder.volumes.create.return_value.id = volume_id2 @@ -2357,7 +2432,6 @@ class TestNewVmInstance(unittest.TestCase): block_device_mapping = {} existing_vim_volumes = [] created_items = {} - expected_block_device_mapping = {"vda": volume_id2} expected_existing_vim_volumes = [] self.vimconn._prepare_non_root_persistent_volumes( name, @@ -2369,14 +2443,74 @@ class TestNewVmInstance(unittest.TestCase): created_items, ) - self.assertDictEqual(block_device_mapping, expected_block_device_mapping) self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) self.vimconn.cinder.volumes.create.assert_called_once_with( size=10, name="basicvmvda", availability_zone=["nova"] ) - self.assertEqual(created_items, {f"volume:{volume_id2}": True}) + mock_update_block_device_mapping.assert_called_once() + _call_mock_update_block_device_mapping = ( + mock_update_block_device_mapping.call_args_list + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["block_device_mapping"], + block_device_mapping, + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["base_disk_index"], 97 + ) + self.assertEqual(_call_mock_update_block_device_mapping[0].kwargs["disk"], disk) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["created_items"], {} + ) + + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_non_root_volumes_create_with_keep( + self, mock_update_block_device_mapping + ): + """Create persistent non-root volume.""" + self.vimconn.cinder = CopyingMock() + self.vimconn.cinder.volumes.create.return_value.id = volume_id2 + vm_av_zone = ["nova"] + base_disk_index = ord("a") + disk = {"size": 10, "keep": True} + block_device_mapping = {} + existing_vim_volumes = [] + created_items = {} + expected_existing_vim_volumes = [] + self.vimconn._prepare_non_root_persistent_volumes( + name, + disk, + vm_av_zone, + block_device_mapping, + base_disk_index, + existing_vim_volumes, + created_items, + ) + + self.assertEqual(existing_vim_volumes, expected_existing_vim_volumes) + self.vimconn.cinder.volumes.create.assert_called_once_with( + size=10, name="basicvmvda", availability_zone=["nova"] + ) + mock_update_block_device_mapping.assert_called_once() + _call_mock_update_block_device_mapping = ( + mock_update_block_device_mapping.call_args_list + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["block_device_mapping"], + block_device_mapping, + ) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["base_disk_index"], 97 + ) + self.assertEqual(_call_mock_update_block_device_mapping[0].kwargs["disk"], disk) + self.assertEqual( + _call_mock_update_block_device_mapping[0].kwargs["created_items"], {} + ) - def test_prepare_persistent_root_volumes_create_raise_exception(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_root_volumes_create_raise_exception( + self, mock_update_block_device_mapping + ): """Create persistent root volume raise exception.""" self.vimconn.cinder.volumes.create.side_effect = Exception vm_av_zone = ["nova"] @@ -2408,8 +2542,12 @@ class TestNewVmInstance(unittest.TestCase): self.assertEqual(existing_vim_volumes, []) self.assertEqual(block_device_mapping, {}) self.assertEqual(created_items, {}) + mock_update_block_device_mapping.assert_not_called() - def test_prepare_persistent_non_root_volumes_create_raise_exception(self): + @patch.object(vimconnector, "update_block_device_mapping") + def test_prepare_persistent_non_root_volumes_create_raise_exception( + self, mock_update_block_device_mapping + ): """Create persistent non-root volume raise exception.""" self.vimconn.cinder.volumes.create.side_effect = Exception vm_av_zone = ["nova"] @@ -2436,6 +2574,7 @@ class TestNewVmInstance(unittest.TestCase): self.assertEqual(existing_vim_volumes, []) self.assertEqual(block_device_mapping, {}) self.assertEqual(created_items, {}) + mock_update_block_device_mapping.assert_not_called() @patch("time.sleep") def test_wait_for_created_volumes_availability_volume_status_available( @@ -2650,7 +2789,6 @@ class TestNewVmInstance(unittest.TestCase): mock_created_vol_availability.return_value = 10 mock_existing_vol_availability.return_value = 15 self.vimconn.cinder = CopyingMock() - self.vimconn._prepare_disk_for_vminstance( name, existing_vim_volumes, @@ -3986,6 +4124,7 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.neutron.update_port.assert_not_called() @patch("time.time") + @patch.object(vimconnector, "remove_keep_tag_from_persistent_volumes") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_prepare_network_for_vminstance") @patch.object(vimconnector, "_create_user_data") @@ -4006,6 +4145,7 @@ class TestNewVmInstance(unittest.TestCase): mock_create_user_data, mock_prepare_network_for_vm_instance, mock_reload_connection, + mock_remove_keep_flag_from_persistent_volumes, mock_time, ): """New VM instance creation is successful.""" @@ -4077,10 +4217,12 @@ class TestNewVmInstance(unittest.TestCase): created_items={}, vm_start_time=time_return_value, ) + mock_remove_keep_flag_from_persistent_volumes.assert_not_called() mock_delete_vm_instance.assert_not_called() mock_format_exception.assert_not_called() @patch("time.time") + @patch.object(vimconnector, "remove_keep_tag_from_persistent_volumes") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_prepare_network_for_vminstance") @patch.object(vimconnector, "_create_user_data") @@ -4101,6 +4243,7 @@ class TestNewVmInstance(unittest.TestCase): mock_create_user_data, mock_prepare_network_for_vm_instance, mock_reload_connection, + mock_remove_keep_flag_from_persistent_volumes, mock_time, ): """New VM instance creation failed because of user data creation failure.""" @@ -4111,6 +4254,8 @@ class TestNewVmInstance(unittest.TestCase): mock_get_vm_availability_zone.return_value = "nova" + mock_remove_keep_flag_from_persistent_volumes.return_value = {} + self.vimconn.nova.servers.create.return_value = self.server mock_time.return_value = time_return_value @@ -4145,12 +4290,14 @@ class TestNewVmInstance(unittest.TestCase): mock_time.assert_not_called() mock_update_port_security.assert_not_called() mock_prepare_external_network.assert_not_called() + mock_remove_keep_flag_from_persistent_volumes.assert_called_once_with({}) mock_delete_vm_instance.assert_called_once_with(None, {}) mock_format_exception.assert_called_once() arg = mock_format_exception.call_args[0][0] self.assertEqual(str(arg), "User data could not be retrieved.") @patch("time.time") + @patch.object(vimconnector, "remove_keep_tag_from_persistent_volumes") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_prepare_network_for_vminstance") @patch.object(vimconnector, "_create_user_data") @@ -4171,6 +4318,7 @@ class TestNewVmInstance(unittest.TestCase): mock_create_user_data, mock_prepare_network_for_vm_instance, mock_reload_connection, + mock_remove_keep_flag_from_persistent_volumes, mock_time, ): """New VM instance creation, external network connection has failed as floating @@ -4184,6 +4332,8 @@ class TestNewVmInstance(unittest.TestCase): mock_time.return_value = time_return_value + mock_remove_keep_flag_from_persistent_volumes.return_value = {} + mock_prepare_external_network.side_effect = VimConnException( "Can not create floating ip." ) @@ -4244,12 +4394,14 @@ class TestNewVmInstance(unittest.TestCase): created_items={}, vm_start_time=time_return_value, ) + mock_remove_keep_flag_from_persistent_volumes.assert_called_once_with({}) mock_delete_vm_instance.assert_called_once_with(self.server.id, {}) mock_format_exception.assert_called_once() arg = mock_format_exception.call_args[0][0] self.assertEqual(str(arg), "Can not create floating ip.") @patch("time.time") + @patch.object(vimconnector, "remove_keep_tag_from_persistent_volumes") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_prepare_network_for_vminstance") @patch.object(vimconnector, "_create_user_data") @@ -4270,6 +4422,7 @@ class TestNewVmInstance(unittest.TestCase): mock_create_user_data, mock_prepare_network_for_vm_instance, mock_reload_connection, + mock_remove_keep_flag_from_persistent_volumes, mock_time, ): """New VM creation with affinity group.""" @@ -4339,10 +4492,12 @@ class TestNewVmInstance(unittest.TestCase): created_items={}, vm_start_time=time_return_value, ) + mock_remove_keep_flag_from_persistent_volumes.assert_not_called() mock_delete_vm_instance.assert_not_called() mock_format_exception.assert_not_called() @patch("time.time") + @patch.object(vimconnector, "remove_keep_tag_from_persistent_volumes") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_prepare_network_for_vminstance") @patch.object(vimconnector, "_create_user_data") @@ -4363,6 +4518,7 @@ class TestNewVmInstance(unittest.TestCase): mock_create_user_data, mock_prepare_network_for_vm_instance, mock_reload_connection, + mock_remove_keep_flag_from_persistent_volumes, mock_time, ): """New VM(server) creation failed.""" @@ -4377,6 +4533,8 @@ class TestNewVmInstance(unittest.TestCase): mock_time.return_value = time_return_value + mock_remove_keep_flag_from_persistent_volumes.return_value = {} + self.vimconn.new_vminstance( name, description, @@ -4429,12 +4587,14 @@ class TestNewVmInstance(unittest.TestCase): mock_time.assert_not_called() mock_update_port_security.assert_not_called() mock_prepare_external_network.assert_not_called() + mock_remove_keep_flag_from_persistent_volumes.assert_called_once_with({}) mock_delete_vm_instance.assert_called_once_with(None, {}) mock_format_exception.assert_called_once() arg = mock_format_exception.call_args[0][0] self.assertEqual(str(arg), "Server could not be created.") @patch("time.time") + @patch.object(vimconnector, "remove_keep_tag_from_persistent_volumes") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_prepare_network_for_vminstance") @patch.object(vimconnector, "_create_user_data") @@ -4455,6 +4615,7 @@ class TestNewVmInstance(unittest.TestCase): mock_create_user_data, mock_prepare_network_for_vm_instance, mock_reload_connection, + mock_remove_keep_flag_from_persistent_volumes, mock_time, ): """Connection to Cloud API has failed.""" @@ -4463,6 +4624,7 @@ class TestNewVmInstance(unittest.TestCase): mock_get_vm_availability_zone.return_value = "nova" self.vimconn.nova.servers.create.return_value = self.server mock_time.return_value = time_return_value + mock_remove_keep_flag_from_persistent_volumes.return_value = {} self.vimconn.new_vminstance( name, @@ -4489,6 +4651,7 @@ class TestNewVmInstance(unittest.TestCase): mock_time.assert_not_called() mock_update_port_security.assert_not_called() mock_prepare_external_network.assert_not_called() + mock_remove_keep_flag_from_persistent_volumes.assert_called_once_with({}) mock_delete_vm_instance.assert_called_once_with(None, {}) @patch.object(vimconnector, "_delete_ports_by_id_wth_neutron") @@ -5260,6 +5423,7 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.logger.error.assert_not_called() @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5270,10 +5434,12 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): vm_id = f"{virtual_mac_id}" 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}"] mock_delete_created_items.return_value = False self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) @@ -5285,8 +5451,96 @@ class TestNewVmInstance(unittest.TestCase): ) mock_sleep.assert_not_called() mock_format_exception.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) + + @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_created_items_has_keep_flag( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, + mock_sleep, + ): + """Created_items includes items which has keep flag.""" + vm_id = f"{virtual_mac_id}" + initial_created_items = { + f"port{port_id}": True, + f"floating_ip{floating_network_vim_id}": None, + f"volume{volume_id}keep": True, + f"volume{volume_id2}keep": True, + } + created_items = { + f"port{port_id}": True, + f"floating_ip{floating_network_vim_id}": None, + } + mock_extract_items_wth_keep_flag_from_created_items.return_value = created_items + volumes_to_hold = [] + mock_delete_created_items.return_value = False + self.vimconn.delete_vminstance(vm_id, initial_created_items, volumes_to_hold) + 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) + mock_delete_created_items.assert_called_once_with( + created_items, volumes_to_hold, False + ) + mock_sleep.assert_not_called() + mock_format_exception.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + initial_created_items + ) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") + @patch.object(vimconnector, "_format_exception") + @patch.object(vimconnector, "_reload_connection") + @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") + @patch.object(vimconnector, "_delete_created_items") + def test_delete_vminstance_extract_items_wth_keep_raises( + self, + mock_delete_created_items, + mock_delete_vm_ports_attached_to_network, + mock_reload_connection, + mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, + mock_sleep, + ): + """extract_items_wth_keep_flag_from_created_items raises AttributeError.""" + vm_id = f"{virtual_mac_id}" + initial_created_items = { + f"port{port_id}": True, + f"floating_ip{floating_network_vim_id}": None, + f"volume{volume_id}keep": True, + f"volume{volume_id2}keep": True, + } + + mock_extract_items_wth_keep_flag_from_created_items.side_effect = AttributeError + volumes_to_hold = [] + mock_delete_created_items.return_value = False + with self.assertRaises(AttributeError): + self.vimconn.delete_vminstance( + vm_id, initial_created_items, volumes_to_hold + ) + mock_reload_connection.assert_not_called() + mock_delete_vm_ports_attached_to_network.assert_not_called() + self.vimconn.nova.servers.delete.assert_not_called() + mock_delete_created_items.assert_not_called() + mock_sleep.assert_not_called() + mock_format_exception.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + initial_created_items + ) + + @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5297,11 +5551,13 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """Delete creted items raises exception.""" vm_id = f"{virtual_mac_id}" created_items = deepcopy(created_items_all_true) + mock_extract_items_wth_keep_flag_from_created_items.return_value = created_items mock_sleep = MagicMock() volumes_to_hold = [] err = ConnectionError("ClientException occured.") @@ -5314,8 +5570,12 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) mock_delete_created_items.assert_called_once() mock_sleep.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5326,11 +5586,13 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """Delete vm ports raises exception.""" vm_id = f"{virtual_mac_id}" 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.") mock_delete_vm_ports_attached_to_network.side_effect = err @@ -5343,8 +5605,12 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.nova.servers.delete.assert_not_called() mock_delete_created_items.assert_not_called() mock_sleep.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5355,11 +5621,13 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """Nova server delete raises exception.""" vm_id = f"{virtual_mac_id}" 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.") self.vimconn.nova.servers.delete.side_effect = err @@ -5372,8 +5640,12 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.nova.servers.delete.assert_called_once_with(vm_id) mock_delete_created_items.assert_not_called() mock_sleep.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5384,11 +5656,13 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """Reload connection raises exception.""" vm_id = f"{virtual_mac_id}" created_items = deepcopy(created_items_all_true) + 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.") @@ -5402,8 +5676,12 @@ class TestNewVmInstance(unittest.TestCase): self.vimconn.nova.servers.delete.assert_not_called() mock_delete_created_items.assert_not_called() mock_sleep.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5414,12 +5692,14 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """created_items and volumes_to_hold are None.""" vm_id = f"{virtual_mac_id}" created_items = None volumes_to_hold = None + mock_extract_items_wth_keep_flag_from_created_items.return_value = {} mock_delete_created_items.return_value = False self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) mock_reload_connection.assert_called_once() @@ -5428,8 +5708,10 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_created_items.assert_called_once_with({}, [], False) mock_sleep.assert_not_called() mock_format_exception.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with({}) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5440,11 +5722,13 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """vm_id is None.""" vm_id = None 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}"] mock_delete_created_items.side_effect = [True, True, False] self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) @@ -5454,8 +5738,12 @@ class TestNewVmInstance(unittest.TestCase): self.assertEqual(mock_delete_created_items.call_count, 3) self.assertEqual(mock_sleep.call_count, 2) mock_format_exception.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) @patch("time.sleep") + @patch.object(vimconnector, "_extract_items_wth_keep_flag_from_created_items") @patch.object(vimconnector, "_format_exception") @patch.object(vimconnector, "_reload_connection") @patch.object(vimconnector, "_delete_vm_ports_attached_to_network") @@ -5466,11 +5754,13 @@ class TestNewVmInstance(unittest.TestCase): mock_delete_vm_ports_attached_to_network, mock_reload_connection, mock_format_exception, + mock_extract_items_wth_keep_flag_from_created_items, mock_sleep, ): """Delete created items always return True.""" vm_id = None 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}"] mock_delete_created_items.side_effect = [True] * 1800 self.vimconn.delete_vminstance(vm_id, created_items, volumes_to_hold) @@ -5480,6 +5770,153 @@ class TestNewVmInstance(unittest.TestCase): self.assertEqual(mock_delete_created_items.call_count, 1800) self.assertEqual(mock_sleep.call_count, 1800) mock_format_exception.assert_not_called() + mock_extract_items_wth_keep_flag_from_created_items.assert_called_once_with( + created_items + ) + + def test_remove_keep_tag_from_persistent_volumes_keep_flag_exists(self): + """Keep flag exists in created items.""" + created_items = { + f"port:{port_id}": True, + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}:keep": True, + f"volume:{volume_id2}:keep": True, + } + expected_result = { + f"port:{port_id}": True, + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"volume:{volume_id2}": True, + } + result = self.vimconn.remove_keep_tag_from_persistent_volumes(created_items) + self.assertDictEqual(result, expected_result) + + def test_remove_keep_tag_from_persistent_volumes_without_keep_flag(self): + """Keep flag does not exist in created items.""" + created_items = { + f"port:{port_id}": True, + f"floating_ip:{floating_network_vim_id}": True, + f"volume:{volume_id}": True, + f"volume:{volume_id2}": True, + } + result = self.vimconn.remove_keep_tag_from_persistent_volumes(created_items) + self.assertDictEqual(result, created_items) + + def test_update_block_device_mapping_empty_volume(self): + """""" + volume = "" + block_device_mapping = {} + base_disk_index = 100 + disk = {} + created_items = {} + with self.assertRaises(VimConnException) as err: + self.vimconn.update_block_device_mapping( + volume, block_device_mapping, base_disk_index, disk, created_items + ) + self.assertEqual(str(err), "Volume is empty.") + self.assertEqual(block_device_mapping, {}) + self.assertEqual(created_items, {}) + + def test_update_block_device_mapping_invalid_volume(self): + """""" + volume = "Volume-A" + block_device_mapping = {} + base_disk_index = 100 + disk = {} + created_items = {} + with self.assertRaises(VimConnException) as err: + self.vimconn.update_block_device_mapping( + volume, block_device_mapping, base_disk_index, disk, created_items + ) + self.assertEqual( + str(err), "Created volume is not valid, does not have id attribute." + ) + self.assertEqual(block_device_mapping, {}) + self.assertEqual(created_items, {}) + + def test_update_block_device_mapping(self): + """""" + volume = MagicMock(autospec=True) + volume.id = volume_id + block_device_mapping = {} + base_disk_index = 100 + disk = {} + created_items = {} + self.vimconn.update_block_device_mapping( + volume, block_device_mapping, base_disk_index, disk, created_items + ) + self.assertEqual( + block_device_mapping, {"vdd": "ac408b73-b9cc-4a6a-a270-82cc4811bd4a"} + ) + self.assertEqual( + created_items, {"volume:ac408b73-b9cc-4a6a-a270-82cc4811bd4a": True} + ) + + def test_update_block_device_mapping_with_keep_flag(self): + """""" + volume = MagicMock(autospec=True) + volume.id = volume_id + block_device_mapping = {} + base_disk_index = 100 + disk = {"size": 10, "keep": True} + created_items = {} + self.vimconn.update_block_device_mapping( + volume, block_device_mapping, base_disk_index, disk, created_items + ) + self.assertEqual( + block_device_mapping, {"vdd": "ac408b73-b9cc-4a6a-a270-82cc4811bd4a"} + ) + self.assertEqual( + created_items, {"volume:ac408b73-b9cc-4a6a-a270-82cc4811bd4a:keep": True} + ) + + def test_extract_items_with_keep_flag_item_has_keep_flag(self): + created_items = deepcopy(created_items_all_true) + created_items[f"volume:{volume_id2}:keep"] = True + result = self.vimconn._extract_items_wth_keep_flag_from_created_items( + created_items + ) + self.assertEqual(result, deepcopy(created_items_all_true)) + + def test_extract_items_with_keep_flag_no_item_wth_keep_flag(self): + created_items = deepcopy(created_items_all_true) + result = self.vimconn._extract_items_wth_keep_flag_from_created_items( + created_items + ) + self.assertEqual(result, deepcopy(created_items_all_true)) + + def test_extract_items_with_keep_flag_all_items_are_already_deleted(self): + created_items = { + f"port:{port_id}": None, + f"floating_ip:{floating_network_vim_id}": None, + f"volume:{volume_id}:keep": None, + f"volume:{volume_id2}:keep": None, + } + expected_result = { + f"port:{port_id}": None, + f"floating_ip:{floating_network_vim_id}": None, + } + result = self.vimconn._extract_items_wth_keep_flag_from_created_items( + created_items + ) + self.assertEqual(result, expected_result) + + def test_extract_items_with_keep_flag_without_semicolon(self): + created_items = { + f"port{port_id}": True, + f"floating_ip{floating_network_vim_id}": None, + f"volume{volume_id}keep": True, + f"volume{volume_id2}keep": True, + } + result = self.vimconn._extract_items_wth_keep_flag_from_created_items( + created_items + ) + self.assertEqual(result, {}) + + def test_extract_items_with_keep_flag_invalid_type_created_items(self): + created_items = [{f"port{port_id}": True}, {f"volume{volume_id2}keep": True}] + with self.assertRaises(AttributeError): + self.vimconn._extract_items_wth_keep_flag_from_created_items(created_items) if __name__ == "__main__": diff --git a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py index b18cf783..3d5eb774 100644 --- a/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py +++ b/RO-VIM-openstack/osm_rovim_openstack/vimconn_openstack.py @@ -1954,11 +1954,46 @@ class vimconnector(vimconn.VimConnector): availability_zone=vm_av_zone, ) boot_volume_id = volume.id - created_items["volume:" + str(volume.id)] = True - block_device_mapping["vd" + chr(base_disk_index)] = volume.id + self.update_block_device_mapping( + volume=volume, + block_device_mapping=block_device_mapping, + base_disk_index=base_disk_index, + disk=disk, + created_items=created_items, + ) return boot_volume_id + @staticmethod + def update_block_device_mapping( + volume: object, + block_device_mapping: dict, + base_disk_index: int, + disk: dict, + created_items: dict, + ) -> None: + """Add volume information to block device mapping dict. + Args: + volume (object): Created volume object + block_device_mapping (dict): Block device details + base_disk_index (int): Disk index + disk (dict): Disk details + created_items (dict): All created items belongs to VM + """ + if not volume: + raise vimconn.VimConnException("Volume is empty.") + + if not hasattr(volume, "id"): + raise vimconn.VimConnException( + "Created volume is not valid, does not have id attribute." + ) + + volume_txt = "volume:" + str(volume.id) + if disk.get("keep"): + volume_txt += ":keep" + created_items[volume_txt] = True + block_device_mapping["vd" + chr(base_disk_index)] = volume.id + def _prepare_non_root_persistent_volumes( self, name: str, @@ -1998,8 +2033,13 @@ class vimconnector(vimconn.VimConnector): # Make sure volume is in the same AZ as the VM to be attached to availability_zone=vm_av_zone, ) - created_items["volume:" + str(volume.id)] = True - block_device_mapping["vd" + chr(base_disk_index)] = volume.id + self.update_block_device_mapping( + volume=volume, + block_device_mapping=block_device_mapping, + base_disk_index=base_disk_index, + disk=disk, + created_items=created_items, + ) def _wait_for_created_volumes_availability( self, elapsed_time: int, created_items: dict @@ -2017,7 +2057,10 @@ class vimconnector(vimconn.VimConnector): while elapsed_time < volume_timeout: for created_item in created_items: - v, _, volume_id = created_item.partition(":") + v, volume_id = ( + created_item.split(":")[0], + created_item.split(":")[1], + ) if v == "volume": if self.cinder.volumes.get(volume_id).status != "available": break @@ -2478,6 +2521,7 @@ class vimconnector(vimconn.VimConnector): the method delete_vminstance and action_vminstance. Can be used to store created ports, volumes, etc. Format is vimconnector dependent, but do not use nested dictionaries and a value of None should be the same as not present. + """ self.logger.debug( "new_vminstance input: image='%s' flavor='%s' nics='%s'", @@ -2589,6 +2633,10 @@ class vimconnector(vimconn.VimConnector): server_id = server.id try: + created_items = self.remove_keep_tag_from_persistent_volumes( + created_items + ) + self.delete_vminstance(server_id, created_items) except Exception as e2: @@ -2596,6 +2644,21 @@ class vimconnector(vimconn.VimConnector): self._format_exception(e) + @staticmethod + def remove_keep_tag_from_persistent_volumes(created_items: Dict) -> Dict: + """Removes the keep flag from persistent volumes. So, those volumes could be removed. + + Args: + created_items (dict): All created items belongs to VM + + Returns: + updated_created_items (dict): Dict which does not include keep flag for volumes. + + """ + return { + key.replace(":keep", ""): value for (key, value) in created_items.items() + } + def get_vminstance(self, vm_id): """Returns the VM instance information from VIM""" # self.logger.debug("Getting VM from VIM") @@ -2802,6 +2865,22 @@ class vimconnector(vimconn.VimConnector): return keep_waiting + @staticmethod + def _extract_items_wth_keep_flag_from_created_items(created_items: dict) -> dict: + """Remove the volumes which has key flag from created_items + + Args: + created_items (dict): All created items belongs to VM + + Returns: + created_items (dict): Persistent volumes eliminated created_items + """ + return { + key: value + for (key, value) in created_items.items() + if len(key.split(":")) == 2 + } + def delete_vminstance( self, vm_id: str, created_items: dict = None, volumes_to_hold: list = None ) -> None: @@ -2817,6 +2896,10 @@ class vimconnector(vimconn.VimConnector): volumes_to_hold = [] try: + created_items = self._extract_items_wth_keep_flag_from_created_items( + created_items + ) + self._reload_connection() # Delete VM ports attached to the networks before the virtual machine diff --git a/releasenotes/notes/feauture_10936-d0301da2e7d933de.yaml b/releasenotes/notes/feauture_10936-d0301da2e7d933de.yaml new file mode 100644 index 00000000..1910c5d8 --- /dev/null +++ b/releasenotes/notes/feauture_10936-d0301da2e7d933de.yaml @@ -0,0 +1,25 @@ +####################################################################################### +# Copyright ETSI Contributors and Others. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +####################################################################################### +--- +features: + - | + Feature 10936 - Keeping Persistent Volume of VNF + Keeping created persistent volumes when the NS terminated according to + vdu-storage-requirements configuration in VNFD. + + +