"flavor": Ns._process_flavor_params,
"vdu": Ns._process_vdu_params,
"affinity-or-anti-affinity-group": Ns._process_affinity_group_params,
+ "shared-volumes": Ns._process_shared_volumes_params,
}
self.db_path_map = {
"net": "vld",
"flavor": "flavor",
"vdu": "vdur",
"affinity-or-anti-affinity-group": "affinity-or-anti-affinity-group",
+ "shared-volumes": "shared-volumes",
}
def init_db(self, target_version):
flavor_data_name = flavor_data.copy()
flavor_data_name["name"] = target_flavor["name"]
extra_dict["params"] = {"flavor_data": flavor_data_name}
-
return extra_dict
@staticmethod
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":
+ if item.get("key") == "keep-volume" and item.get("value").lower() == "true":
return True
return False
+ @staticmethod
+ def is_shared_volume(
+ virtual_storage_desc: Dict[str, Any], vnfd_id: str
+ ) -> (str, bool):
+ """Function to decide if the volume type is multi attached or not .
+
+ Args:
+ virtual_storage_desc (Dict[str, Any]): virtual storage description dictionary
+ vnfd_id (str): vnfd id
+
+ Returns:
+ bool (True/False)
+ name (str) New name if it is a multiattach disk
+ """
+
+ if vdu_storage_requirements := virtual_storage_desc.get(
+ "vdu-storage-requirements", {}
+ ):
+ for item in vdu_storage_requirements:
+ if (
+ item.get("key") == "multiattach"
+ and item.get("value").lower() == "true"
+ ):
+ name = f"shared-{virtual_storage_desc['id']}-{vnfd_id}"
+ return name, True
+ return virtual_storage_desc["id"], False
+
@staticmethod
def _sort_vdu_interfaces(target_vdu: dict) -> None:
"""Sort the interfaces according to position number.
"size": root_disk["size-of-storage"],
"keep": Ns.is_volume_keeping_required(root_disk),
}
-
disk_list.append(persistent_root_disk[vsd["id"]])
break
persistent_root_disk: dict,
persistent_ordinary_disk: dict,
disk_list: list,
+ vnf_id: str = None,
) -> None:
"""Fill the disk list by adding persistent ordinary disks.
== "persistent-storage:persistent-storage"
and disk["id"] not in persistent_root_disk.keys()
):
+ name, multiattach = Ns.is_shared_volume(disk, vnf_id)
persistent_ordinary_disk[disk["id"]] = {
+ "name": name,
"size": disk["size-of-storage"],
"keep": Ns.is_volume_keeping_required(disk),
+ "multiattach": multiattach,
}
disk_list.append(persistent_ordinary_disk[disk["id"]])
image_text = ns_preffix + ":image." + target_vdu["ns-image-id"]
extra_dict = {"depends_on": [image_text]}
net_list = []
-
persistent_root_disk = {}
persistent_ordinary_disk = {}
vdu_instantiation_volumes_list = []
disk_list = []
vnfd_id = vnfr["vnfd-id"]
vnfd = db.get_one("vnfds", {"_id": vnfd_id})
-
# If the position info is provided for all the interfaces, it will be sorted
# according to position number ascendingly.
if all(
)
# Add the persistent non-root disks to disk_list
Ns._add_persistent_ordinary_disks_to_disk_list(
- target_vdu, persistent_root_disk, persistent_ordinary_disk, disk_list
+ target_vdu,
+ persistent_root_disk,
+ persistent_ordinary_disk,
+ disk_list,
+ vnfd["id"],
)
affinity_group_list = Ns._prepare_vdu_affinity_group_list(
"availability_zone_index": None, # TODO
"availability_zone_list": None, # TODO
}
+ return extra_dict
+ @staticmethod
+ def _process_shared_volumes_params(
+ target_shared_volume: Dict[str, Any],
+ indata: Dict[str, Any],
+ vim_info: Dict[str, Any],
+ target_record_id: str,
+ **kwargs: Dict[str, Any],
+ ) -> Dict[str, Any]:
+ extra_dict = {}
+ shared_volume_data = {
+ "size": target_shared_volume["size-of-storage"],
+ "name": target_shared_volume["id"],
+ "type": target_shared_volume["type-of-storage"],
+ }
+ extra_dict["params"] = shared_volume_data
return extra_dict
@staticmethod
extra_dict["params"] = {
"affinity_group_data": affinity_group_data,
}
-
return extra_dict
@staticmethod
vim_details = {}
vim_details_text = existing_vdu["vim_info"][target_id].get("vim_details", None)
+
if vim_details_text:
vim_details = yaml.safe_load(f"{vim_details_text}")
process_params = None
vdu2cloud_init = indata.get("cloud_init_content") or {}
ro_nsr_public_key = db_ro_nsr["public_key"]
-
# According to the type of item, the path, the target_list,
# the existing_list and the method to process params are set
db_path = self.db_path_map[item]
)
target_list = target_vnf.get(db_path, []) if target_vnf else []
existing_list = vnfr.get(db_path, [])
- elif item in ("image", "flavor", "affinity-or-anti-affinity-group"):
+ elif item in (
+ "image",
+ "flavor",
+ "affinity-or-anti-affinity-group",
+ "shared-volumes",
+ ):
db_record = "nsrs:{}:{}".format(nsr_id, db_path)
target_list = indata.get(item, [])
existing_list = db_nsr.get(item, [])
else:
raise NsException("Item not supported: {}", item)
-
# ensure all the target_list elements has an "id". If not assign the index as id
if target_list is None:
target_list = []
for target_index, tl in enumerate(target_list):
if tl and not tl.get("id"):
tl["id"] = str(target_index)
-
# step 1 items (networks,vdus,...) to be deleted/updated
for item_index, existing_item in enumerate(existing_list):
target_item = next(
(t for t in target_list if t["id"] == existing_item["id"]),
None,
)
-
for target_vim, existing_viminfo in existing_item.get(
"vim_info", {}
).items():
# step 2 items (networks,vdus,...) to be created
for target_item in target_list:
item_index = -1
-
for item_index, existing_item in enumerate(existing_list):
if existing_item["id"] == target_item["id"]:
break
}
)
self.logger.debug("calculate_diff_items kwargs={}".format(kwargs))
-
extra_dict = process_params(
target_item,
indata,
changes_list = []
# NS vld, image and flavor
- for item in ["net", "image", "flavor", "affinity-or-anti-affinity-group"]:
+ for item in [
+ "net",
+ "image",
+ "flavor",
+ "affinity-or-anti-affinity-group",
+ "shared-volumes",
+ ]:
self.logger.debug("process NS={} {}".format(nsr_id, item))
diff_items, task_index = self.calculate_diff_items(
indata=indata,
created = False
created_items = {}
target_vim = self.my_vims[ro_task["target_id"]]
-
try:
created = True
params = task["params"]
)
affinity_group["affinity_group_id"] = affinity_group_id
-
vim_vm_id, created_items = target_vim.new_vminstance(**params_copy)
interfaces = [iface["vim_id"] for iface in params_copy["net_list"]]
return "FAILED", ro_vim_item_update
+class VimInteractionSharedVolume(VimInteractionBase):
+ def delete(self, ro_task, task_index):
+ task = ro_task["tasks"][task_index]
+ task_id = task["task_id"]
+ shared_volume_vim_id = ro_task["vim_info"]["vim_id"]
+ ro_vim_item_update_ok = {
+ "vim_status": "DELETED",
+ "created": False,
+ "vim_message": "DELETED",
+ "vim_id": None,
+ }
+ try:
+ if shared_volume_vim_id:
+ target_vim = self.my_vims[ro_task["target_id"]]
+ target_vim.delete_shared_volumes(shared_volume_vim_id)
+ except vimconn.VimConnNotFoundException:
+ ro_vim_item_update_ok["vim_message"] = "already deleted"
+ except vimconn.VimConnException as e:
+ self.logger.error(
+ "ro_task={} vim={} del-shared-volume={}: {}".format(
+ ro_task["_id"], ro_task["target_id"], shared_volume_vim_id, e
+ )
+ )
+ ro_vim_item_update = {
+ "vim_status": "VIM_ERROR",
+ "vim_message": "Error while deleting: {}".format(e),
+ }
+
+ return "FAILED", ro_vim_item_update
+
+ self.logger.debug(
+ "task={} {} del-shared-volume={} {}".format(
+ task_id,
+ ro_task["target_id"],
+ shared_volume_vim_id,
+ ro_vim_item_update_ok.get("vim_message", ""),
+ )
+ )
+
+ return "DONE", ro_vim_item_update_ok
+
+ def new(self, ro_task, task_index, task_depends):
+ task = ro_task["tasks"][task_index]
+ task_id = task["task_id"]
+ created = False
+ created_items = {}
+ target_vim = self.my_vims[ro_task["target_id"]]
+
+ try:
+ shared_volume_name = None
+ shared_volume_vim_id = None
+ shared_volume_data = None
+
+ if task.get("params"):
+ shared_volume_data = task["params"]
+
+ if shared_volume_data:
+ self.logger.info(
+ f"Creating the new shared_volume for {shared_volume_data}\n"
+ )
+ (
+ shared_volume_name,
+ shared_volume_vim_id,
+ ) = target_vim.new_shared_volumes(shared_volume_data)
+ created = True
+ created_items[shared_volume_vim_id] = shared_volume_name
+
+ ro_vim_item_update = {
+ "vim_id": shared_volume_vim_id,
+ "vim_status": "DONE",
+ "created": created,
+ "created_items": created_items,
+ "vim_details": None,
+ "vim_message": None,
+ }
+ self.logger.debug(
+ "task={} {} new-shared-volume={} created={}".format(
+ task_id, ro_task["target_id"], shared_volume_vim_id, created
+ )
+ )
+
+ return "DONE", ro_vim_item_update
+ except (vimconn.VimConnException, NsWorkerException) as e:
+ self.logger.error(
+ "task={} vim={} new-shared-volume:"
+ " {}".format(task_id, ro_task["target_id"], e)
+ )
+ ro_vim_item_update = {
+ "vim_status": "VIM_ERROR",
+ "created": created,
+ "vim_message": str(e),
+ }
+
+ return "FAILED", ro_vim_item_update
+
+
class VimInteractionFlavor(VimInteractionBase):
def delete(self, ro_task, task_index):
task = ro_task["tasks"][task_index]
created = False
created_items = {}
target_vim = self.my_vims[ro_task["target_id"]]
-
try:
# FIND
vim_flavor_id = None
self.db = db
self.item2class = {
"net": VimInteractionNet(self.db, self.my_vims, self.db_vims, self.logger),
+ "shared-volumes": VimInteractionSharedVolume(
+ self.db, self.my_vims, self.db_vims, self.logger
+ ),
"vdu": VimInteractionVdu(self.db, self.my_vims, self.db_vims, self.logger),
"image": VimInteractionImage(
self.db, self.my_vims, self.db_vims, self.logger
lock_object = LockRenew.add_lock_object(
"ro_tasks", ro_task, self
)
-
if task["action"] == "DELETE":
(
new_status,
{
"size": "10",
"keep": False,
+ "multiattach": False,
+ "name": "persistent-volume2",
}
]
self.ns._add_persistent_ordinary_disks_to_disk_list(
VimInteractionMigration,
VimInteractionNet,
VimInteractionResize,
+ VimInteractionSharedVolume,
)
from osm_ro_plugin.vimconn import VimConnConnectionException, VimConnException
instance.refresh(ro_task)
+class TestVimInteractionSharedVolume(unittest.TestCase):
+ def setUp(self):
+ module_name = "osm_ro_plugin"
+ self.target_vim = MagicMock(name=f"{module_name}.vimconn.VimConnector")
+ self.task_depends = None
+
+ patches = [patch(f"{module_name}.vimconn.VimConnector", self.target_vim)]
+
+ # Enabling mocks and add cleanups
+ for mock in patches:
+ mock.start()
+ self.addCleanup(mock.stop)
+
+ def test__new_shared_volume_ok(self):
+ """
+ create a shared volume with attributes set in params
+ """
+ db = "test_db"
+ logger = "test_logger"
+ my_vims = "test-vim"
+ db_vims = {
+ 0: {
+ "config": {},
+ },
+ }
+
+ instance = VimInteractionSharedVolume(db, logger, my_vims, db_vims)
+ with patch.object(instance, "my_vims", [self.target_vim]), patch.object(
+ instance, "logger", logging
+ ), patch.object(instance, "db_vims", db_vims):
+ ro_task = {
+ "target_id": 0,
+ "tasks": {
+ "task_index_1": {
+ "target_id": 0,
+ "action_id": "123456",
+ "nsr_id": "654321",
+ "task_id": "123456:1",
+ "status": "SCHEDULED",
+ "action": "CREATE",
+ "item": "test_item",
+ "target_record": "test_target_record",
+ "target_record_id": "test_target_record_id",
+ # values coming from extra_dict
+ "params": {
+ "shared_volume_data": {
+ "size": "10",
+ "name": "shared-volume",
+ "type": "multiattach",
+ }
+ },
+ "find_params": {},
+ "depends_on": "test_depends_on",
+ },
+ },
+ }
+ task_index = "task_index_1"
+ self.target_vim.new_shared_volumes.return_value = ("", "shared-volume")
+ result = instance.new(ro_task, task_index, self.task_depends)
+ self.assertEqual(result[0], "DONE")
+ self.assertEqual(result[1].get("vim_id"), "shared-volume")
+ self.assertEqual(result[1].get("created"), True)
+ self.assertEqual(result[1].get("vim_status"), "DONE")
+
+ def test__new_shared_volume_failed(self):
+ """
+ create a shared volume with attributes set in params failed
+ """
+ db = "test_db"
+ logger = "test_logger"
+ my_vims = "test-vim"
+ db_vims = {
+ 0: {
+ "config": {},
+ },
+ }
+
+ instance = VimInteractionSharedVolume(db, logger, my_vims, db_vims)
+ with patch.object(instance, "my_vims", [self.target_vim]), patch.object(
+ instance, "logger", logging
+ ), patch.object(instance, "db_vims", db_vims):
+ ro_task = {
+ "target_id": 0,
+ "tasks": {
+ "task_index_1": {
+ "target_id": 0,
+ "action_id": "123456",
+ "nsr_id": "654321",
+ "task_id": "123456:1",
+ "status": "SCHEDULED",
+ "action": "CREATE",
+ "item": "test_item",
+ "target_record": "test_target_record",
+ "target_record_id": "test_target_record_id",
+ # values coming from extra_dict
+ "params": {
+ "shared_volume_data": {
+ "size": "10",
+ "name": "shared-volume",
+ "type": "multiattach",
+ }
+ },
+ "find_params": {},
+ "depends_on": "test_depends_on",
+ },
+ },
+ }
+ task_index = "task_index_1"
+ self.target_vim.new_shared_volumes.side_effect = VimConnException(
+ "Connection failed."
+ )
+ result = instance.new(ro_task, task_index, self.task_depends)
+ self.assertEqual(result[0], "FAILED")
+ self.assertEqual(result[1].get("vim_message"), "Connection failed.")
+ self.assertEqual(result[1].get("created"), False)
+ self.assertEqual(result[1].get("vim_status"), "VIM_ERROR")
+
+ def test__delete_shared_volume_ok(self):
+ """
+ Delete a shared volume with attributes set in params
+ """
+ db = "test_db"
+ logger = "test_logger"
+ my_vims = "test-vim"
+ db_vims = {
+ 0: {
+ "config": {},
+ },
+ }
+
+ instance = VimInteractionSharedVolume(db, logger, my_vims, db_vims)
+ with patch.object(instance, "my_vims", [self.target_vim]), patch.object(
+ instance, "logger", logging
+ ), patch.object(instance, "db_vims", db_vims):
+ ro_task = {
+ "target_id": 0,
+ "tasks": {
+ "task_index_3": {
+ "target_id": 0,
+ "task_id": "123456:1",
+ },
+ },
+ "vim_info": {
+ "created": False,
+ "created_items": None,
+ "vim_id": "sample_shared_volume_id_3",
+ "vim_name": "sample_shared_volume_3",
+ "vim_status": None,
+ "vim_details": "some-details",
+ "vim_message": None,
+ "refresh_at": None,
+ },
+ }
+
+ task_index = "task_index_3"
+ self.target_vim.delete_shared_volumes.return_value = True
+ result = instance.delete(ro_task, task_index)
+ self.assertEqual(result[0], "DONE")
+ self.assertEqual(result[1].get("vim_id"), None)
+ self.assertEqual(result[1].get("created"), False)
+ self.assertEqual(result[1].get("vim_status"), "DELETED")
+
+ def test__delete_shared_volume_failed(self):
+ """
+ Delete a shared volume with attributes set in params failed
+ """
+ db = "test_db"
+ logger = "test_logger"
+ my_vims = "test-vim"
+ db_vims = {
+ 0: {
+ "config": {},
+ },
+ }
+
+ instance = VimInteractionSharedVolume(db, logger, my_vims, db_vims)
+ with patch.object(instance, "my_vims", [self.target_vim]), patch.object(
+ instance, "logger", logging
+ ), patch.object(instance, "db_vims", db_vims):
+ ro_task = {
+ "_id": "122436:1",
+ "target_id": 0,
+ "tasks": {
+ "task_index_3": {
+ "target_id": 0,
+ "task_id": "123456:1",
+ },
+ },
+ "vim_info": {
+ "created": False,
+ "created_items": None,
+ "vim_id": "sample_shared_volume_id_3",
+ "vim_name": "sample_shared_volume_3",
+ "vim_status": None,
+ "vim_details": "some-details",
+ "vim_message": None,
+ "refresh_at": None,
+ },
+ }
+
+ task_index = "task_index_3"
+ self.target_vim.delete_shared_volumes.side_effect = VimConnException(
+ "Connection failed."
+ )
+ result = instance.delete(ro_task, task_index)
+ self.assertEqual(result[0], "FAILED")
+ self.assertEqual(
+ result[1].get("vim_message"), "Error while deleting: Connection failed."
+ )
+ self.assertEqual(result[1].get("vim_status"), "VIM_ERROR")
+
+
class TestVimInteractionAffinityGroup(unittest.TestCase):
def setUp(self):
module_name = "osm_ro_plugin"
},
"image": deploy_item_list,
"flavor": deploy_item_list,
+ "shared-volumes": deploy_item_list,
"ns": {
"type": "object",
"properties": {
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,
mocking.assert_not_called()
-class Status:
- def __init__(self, s):
+class Volume:
+ def __init__(self, s, type="__DEFAULT__", name="", id=""):
self.status = s
-
- def __str__(self):
- return self.status
+ self.volume_type = type
+ self.name = name
+ self.id = id
class CopyingMock(MagicMock):
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
_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
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(
{"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(
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(
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(
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 = {
version = self.config.get("microversion")
if not version:
- version = "2.1"
+ version = "2.60"
# addedd region_name to keystone, nova, neutron and cinder to support distributed cloud for Wind River
# Titanium cloud and StarlingX
created_items[volume_txt] = True
block_device_mapping["vd" + chr(base_disk_index)] = volume.id
+ def new_shared_volumes(self, shared_volume_data) -> (str, str):
+ try:
+ volume = self.cinder.volumes.create(
+ size=shared_volume_data["size"],
+ name=shared_volume_data["name"],
+ volume_type="multiattach",
+ )
+ return (volume.name, volume.id)
+ except (ConnectionError, KeyError) as e:
+ self._format_exception(e)
+
+ def _prepare_shared_volumes(
+ self,
+ name: str,
+ disk: dict,
+ base_disk_index: int,
+ block_device_mapping: dict,
+ existing_vim_volumes: list,
+ created_items: dict,
+ ):
+ volumes = {volume.name: volume.id for volume in self.cinder.volumes.list()}
+ if volumes.get(disk["name"]):
+ sv_id = volumes[disk["name"]]
+ volume = self.cinder.volumes.get(sv_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 _prepare_non_root_persistent_volumes(
self,
name: str,
# Non-root persistent volumes
# Disk may include only vim_volume_id or only vim_id."
key_id = "vim_volume_id" if "vim_volume_id" in disk.keys() else "vim_id"
-
if disk.get(key_id):
# Use existing persistent volume
block_device_mapping["vd" + chr(base_disk_index)] = disk[key_id]
existing_vim_volumes.append({"id": disk[key_id]})
-
else:
- # Create persistent volume
+ volume_name = f"{name}vd{chr(base_disk_index)}"
volume = self.cinder.volumes.create(
size=disk["size"],
- name=name + "vd" + chr(base_disk_index),
+ name=volume_name,
# Make sure volume is in the same AZ as the VM to be attached to
availability_zone=vm_av_zone,
)
elapsed_time (int): Time spent while waiting
"""
-
while elapsed_time < volume_timeout:
for created_item in created_items:
v, volume_id = (
created_item.split(":")[1],
)
if v == "volume":
- if self.cinder.volumes.get(volume_id).status != "available":
+ volume = self.cinder.volumes.get(volume_id)
+ if (
+ volume.volume_type == "multiattach"
+ and volume.status == "in-use"
+ ):
+ return elapsed_time
+ elif volume.status != "available":
break
else:
# All ready: break from while
while elapsed_time < volume_timeout:
for volume in existing_vim_volumes:
- if self.cinder.volumes.get(volume["id"]).status != "available":
+ v = self.cinder.volumes.get(volume["id"])
+ if v.volume_type == "multiattach" and v.status == "in-use":
+ return elapsed_time
+ elif v.status != "available":
break
else: # all ready: break from while
break
base_disk_index = ord("b")
boot_volume_id = None
elapsed_time = 0
-
for disk in disk_list:
if "image_id" in disk:
# Root persistent volume
existing_vim_volumes=existing_vim_volumes,
created_items=created_items,
)
+ elif disk.get("multiattach"):
+ self._prepare_shared_volumes(
+ name=name,
+ disk=disk,
+ base_disk_index=base_disk_index,
+ block_device_mapping=block_device_mapping,
+ existing_vim_volumes=existing_vim_volumes,
+ created_items=created_items,
+ )
else:
# Non-root persistent volume
self._prepare_non_root_persistent_volumes(
server_group_id,
)
)
-
# Create VM
server = self.nova.servers.create(
name=name,
except Exception as e:
self.logger.error("Error deleting port: {}: {}".format(type(e).__name__, e))
+ def delete_shared_volumes(self, shared_volume_vim_id: str) -> bool:
+ """Cinder delete volume by id.
+ Args:
+ shared_volume_vim_id (str): ID of shared volume in VIM
+ """
+ try:
+ if self.cinder.volumes.get(shared_volume_vim_id).status != "available":
+ return True
+
+ else:
+ self.cinder.volumes.delete(shared_volume_vim_id)
+
+ except Exception as e:
+ self.logger.error(
+ "Error deleting volume: {}: {}".format(type(e).__name__, e)
+ )
+
def _delete_volumes_by_id_wth_cinder(
self, k: str, k_id: str, volumes_to_hold: list, created_items: dict
) -> bool:
try:
k_item, k_id = self._get_item_name_id(k)
-
if k_item == "volume":
unavailable_vol = self._delete_volumes_by_id_wth_cinder(
k, k_id, volumes_to_hold, created_items
--- /dev/null
+#######################################################################################
+# 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 10972: Support of volume multi-attach
+ Support of volume multi-attach for Openstack based VIMs (server groups)
\ No newline at end of file