From dffa6217777142746ed9b5c9a7eaab7c0d8716be Mon Sep 17 00:00:00 2001 From: aticig Date: Tue, 12 Apr 2022 15:27:53 +0300 Subject: [PATCH] Feature 10908: LCM process NS update request This feature updates the running VNF instance in a network service. Change-Id: I5531cdf1b977aff3508da5f105accff34d6817e0 Signed-off-by: aticig --- osm_lcm/data_utils/vnfd.py | 22 + osm_lcm/lcm.py | 9 + osm_lcm/lcm_utils.py | 93 ++++ osm_lcm/ns.py | 374 +++++++++++++ osm_lcm/tests/test_db_descriptors.py | 311 ++++++++++- osm_lcm/tests/test_ns.py | 750 +++++++++++++++++++++++++++ requirements.in | 1 + requirements.txt | 12 +- 8 files changed, 1566 insertions(+), 6 deletions(-) diff --git a/osm_lcm/data_utils/vnfd.py b/osm_lcm/data_utils/vnfd.py index f8c11ca..ffcb582 100644 --- a/osm_lcm/data_utils/vnfd.py +++ b/osm_lcm/data_utils/vnfd.py @@ -169,3 +169,25 @@ def get_juju_ee_ref(vnfd, entity_id): get_configuration(vnfd, entity_id).get("execution-environment-list", []), lambda ee: "juju" in ee, ) + + +def find_software_version(vnfd: dict) -> str: + """Find the sotware version in the VNFD descriptors + + Args: + vnfd (dict): Descriptor as a dictionary + + Returns: + software-version (str) + """ + + default_sw_version = "1.0" + + if vnfd.get("vnfd"): + vnfd = vnfd["vnfd"] + + if vnfd.get("software-version"): + return vnfd["software-version"] + + else: + return default_sw_version diff --git a/osm_lcm/lcm.py b/osm_lcm/lcm.py index f8b204a..326847c 100644 --- a/osm_lcm/lcm.py +++ b/osm_lcm/lcm.py @@ -439,6 +439,14 @@ class Lcm: task = asyncio.ensure_future(self.ns.action(nsr_id, nslcmop_id)) self.lcm_tasks.register("ns", nsr_id, nslcmop_id, "ns_action", task) return + elif command == "update": + # self.logger.debug("Update NS {}".format(nsr_id)) + nslcmop = params + nslcmop_id = nslcmop["_id"] + nsr_id = nslcmop["nsInstanceId"] + task = asyncio.ensure_future(self.ns.update(nsr_id, nslcmop_id)) + self.lcm_tasks.register("ns", nsr_id, nslcmop_id, "ns_update", task) + return elif command == "scale": # self.logger.debug("Update NS {}".format(nsr_id)) nslcmop = params @@ -474,6 +482,7 @@ class Lcm: "instantiated", "scaled", "actioned", + "updated", ): # "scaled-cooldown-time" return elif topic == "nsi": # netslice LCM processes (instantiate, terminate, etc) diff --git a/osm_lcm/lcm_utils.py b/osm_lcm/lcm_utils.py index 16d5b33..235d485 100644 --- a/osm_lcm/lcm_utils.py +++ b/osm_lcm/lcm_utils.py @@ -17,7 +17,9 @@ ## import asyncio +import checksumdir from collections import OrderedDict +import os from time import time from osm_lcm.data_utils.database.database import Database from osm_lcm.data_utils.filesystem.filesystem import Filesystem @@ -80,6 +82,63 @@ def get_iterable(in_dict, in_key): return in_dict[in_key] +def check_juju_bundle_existence(vnfd: dict) -> str: + """Checks the existence of juju-bundle in the descriptor + + Args: + vnfd: Descriptor as a dictionary + + Returns: + Juju bundle if dictionary has juju-bundle else None + + """ + if vnfd.get("vnfd"): + vnfd = vnfd["vnfd"] + + for kdu in vnfd.get("kdu", []): + return kdu.get("juju-bundle", None) + + +def get_charm_artifact_path(base_folder, charm_name, charm_type, revision=str()) -> str: + """Finds the charm artifact paths + + Args: + base_folder: Main folder which will be looked up for charm + charm_name: Charm name + charm_type: Type of charm native_charm, lxc_proxy_charm or k8s_proxy_charm + revision: vnf package revision number if there is + + Returns: + artifact_path: (str) + + """ + extension = "" + if revision: + extension = ":" + str(revision) + + if base_folder.get("pkg-dir"): + artifact_path = "{}/{}/{}/{}".format( + base_folder["folder"] + extension, + base_folder["pkg-dir"], + "charms" + if charm_type in ("native_charm", "lxc_proxy_charm", "k8s_proxy_charm") + else "helm-charts", + charm_name, + ) + + else: + # For SOL004 packages + artifact_path = "{}/Scripts/{}/{}".format( + base_folder["folder"] + extension, + "charms" + if charm_type in ("native_charm", "lxc_proxy_charm", "k8s_proxy_charm") + else "helm-charts", + charm_name, + ) + + return artifact_path + + def populate_dict(target_dict, key_list, value): """ Update target_dict creating nested dictionaries with the key_list. Last key_list item is asigned the value. @@ -124,6 +183,40 @@ class LcmBase: # except DbException as e: # self.logger.error("Updating {} _id={} with '{}'. Error: {}".format(item, _id, _desc, e)) + def check_charm_hash_changed( + self, current_charm_path: str, target_charm_path: str + ) -> bool: + """Find the target charm has changed or not by checking the hash of + old and new charm packages + + Args: + current_charm_path (str): Existing charm package artifact path + target_charm_path (str): Target charm package artifact path + + Returns: + True/False (bool): if charm has changed it returns True + + """ + # Check if the charm artifacts are available + if os.path.exists(self.fs.path + current_charm_path) and os.path.exists( + self.fs.path + target_charm_path + ): + # Compare the hash of charm folders + if checksumdir.dirhash( + self.fs.path + current_charm_path + ) != checksumdir.dirhash(self.fs.path + target_charm_path): + + return True + + return False + + else: + raise LcmException( + "Charm artifact {} does not exist in the VNF Package".format( + self.fs.path + target_charm_path + ) + ) + class TaskRegistry(LcmBase): """ diff --git a/osm_lcm/ns.py b/osm_lcm/ns.py index 7bd6a92..af0a1eb 100644 --- a/osm_lcm/ns.py +++ b/osm_lcm/ns.py @@ -17,6 +17,7 @@ ## import asyncio +import shutil from typing import Any, Dict, List import yaml import logging @@ -55,6 +56,8 @@ from osm_lcm.lcm_utils import ( deep_get, get_iterable, populate_dict, + check_juju_bundle_existence, + get_charm_artifact_path, ) from osm_lcm.data_utils.nsd import ( get_ns_configuration_relation_list, @@ -78,6 +81,7 @@ from osm_lcm.data_utils.vnfd import ( get_number_of_instances, get_juju_ee_ref, get_kdu_resource_profile, + find_software_version, ) from osm_lcm.data_utils.list_utils import find_in_list from osm_lcm.data_utils.vnfr import get_osm_params, get_vdur_index, get_kdur @@ -118,6 +122,7 @@ class NsLcm(LcmBase): timeout_ns_terminate = 1800 # default global timeout for un deployment a ns timeout_charm_delete = 10 * 60 timeout_primitive = 30 * 60 # timeout for primitive execution + timeout_ns_update = 30 * 60 # timeout for ns update timeout_progress_primitive = ( 10 * 60 ) # timeout for some progress in a primitive execution @@ -5292,6 +5297,375 @@ class NsLcm(LcmBase): self.lcm_tasks.remove("ns", nsr_id, nslcmop_id, "ns_action") return nslcmop_operation_state, detailed_status + async def _ns_charm_upgrade( + self, + ee_id, + charm_id, + charm_type, + path, + timeout: float = None, + ) -> (str, str): + """This method upgrade charms in VNF instances + + Args: + ee_id: Execution environment id + path: Local path to the charm + charm_id: charm-id + charm_type: Charm type can be lxc-proxy-charm, native-charm or k8s-proxy-charm + timeout: (Float) Timeout for the ns update operation + + Returns: + result: (str, str) COMPLETED/FAILED, details + """ + try: + charm_type = charm_type or "lxc_proxy_charm" + output = await self.vca_map[charm_type].upgrade_charm( + ee_id=ee_id, + path=path, + charm_id=charm_id, + charm_type=charm_type, + timeout=timeout or self.timeout_ns_update, + ) + + if output: + return "COMPLETED", output + + except (LcmException, asyncio.CancelledError): + raise + + except Exception as e: + + self.logger.debug("Error upgrading charm {}".format(path)) + + return "FAILED", "Error upgrading charm {}: {}".format(path, e) + + async def update(self, nsr_id, nslcmop_id): + """Update NS according to different update types + + This method performs upgrade of VNF instances then updates the revision + number in VNF record + + Args: + nsr_id: Network service will be updated + nslcmop_id: ns lcm operation id + + Returns: + It may raise DbException, LcmException, N2VCException, K8sException + + """ + # Try to lock HA task here + task_is_locked_by_me = self.lcm_tasks.lock_HA("ns", "nslcmops", nslcmop_id) + if not task_is_locked_by_me: + return + + logging_text = "Task ns={} update={} ".format(nsr_id, nslcmop_id) + self.logger.debug(logging_text + "Enter") + + # Set the required variables to be filled up later + db_nsr = None + db_nslcmop_update = {} + vnfr_update = {} + nslcmop_operation_state = None + db_nsr_update = {} + error_description_nslcmop = "" + exc = None + change_type = "" + detailed_status = "" + + try: + # wait for any previous tasks in process + step = "Waiting for previous operations to terminate" + await self.lcm_tasks.waitfor_related_HA("ns", "nslcmops", nslcmop_id) + self._write_ns_status( + nsr_id=nsr_id, + ns_state=None, + current_operation="UPDATING", + current_operation_id=nslcmop_id, + ) + + step = "Getting nslcmop from database" + db_nslcmop = self.db.get_one( + "nslcmops", {"_id": nslcmop_id}, fail_on_empty=False + ) + update_type = db_nslcmop["operationParams"]["updateType"] + + step = "Getting nsr from database" + db_nsr = self.db.get_one("nsrs", {"_id": nsr_id}) + old_operational_status = db_nsr["operational-status"] + db_nsr_update["operational-status"] = "updating" + self.update_db_2("nsrs", nsr_id, db_nsr_update) + nsr_deployed = db_nsr["_admin"].get("deployed") + + if update_type == "CHANGE_VNFPKG": + + # Get the input parameters given through update request + vnf_instance_id = db_nslcmop["operationParams"][ + "changeVnfPackageData" + ].get("vnfInstanceId") + + vnfd_id = db_nslcmop["operationParams"]["changeVnfPackageData"].get( + "vnfdId" + ) + timeout_seconds = db_nslcmop["operationParams"].get("timeout_ns_update") + + step = "Getting vnfr from database" + db_vnfr = self.db.get_one( + "vnfrs", {"_id": vnf_instance_id}, fail_on_empty=False + ) + + step = "Getting vnfds from database" + # Latest VNFD + latest_vnfd = self.db.get_one( + "vnfds", {"_id": vnfd_id}, fail_on_empty=False + ) + latest_vnfd_revision = latest_vnfd["_admin"].get("revision") + + # Current VNFD + current_vnf_revision = db_vnfr.get("revision", 1) + current_vnfd = self.db.get_one( + "vnfds_revisions", + {"_id": vnfd_id + ":" + str(current_vnf_revision)}, + fail_on_empty=False, + ) + # Charm artifact paths will be filled up later + ( + current_charm_artifact_path, + target_charm_artifact_path, + charm_artifact_paths, + ) = ([], [], []) + + step = "Checking if revision has changed in VNFD" + if current_vnf_revision != latest_vnfd_revision: + + # There is new revision of VNFD, update operation is required + current_vnfd_path = vnfd_id + ":" + str(current_vnf_revision) + latest_vnfd_path = vnfd_id + + step = "Removing the VNFD packages if they exist in the local path" + shutil.rmtree(self.fs.path + current_vnfd_path, ignore_errors=True) + shutil.rmtree(self.fs.path + latest_vnfd_path, ignore_errors=True) + + step = "Get the VNFD packages from FSMongo" + self.fs.sync(from_path=latest_vnfd_path) + self.fs.sync(from_path=current_vnfd_path) + + step = ( + "Get the charm-type, charm-id, ee-id if there is deployed VCA" + ) + base_folder = latest_vnfd["_admin"]["storage"] + + for charm_index, charm_deployed in enumerate( + get_iterable(nsr_deployed, "VCA") + ): + vnf_index = db_vnfr.get("member-vnf-index-ref") + + # Getting charm-id and charm-type + if charm_deployed.get("member-vnf-index") == vnf_index: + charm_id = self.get_vca_id(db_vnfr, db_nsr) + charm_type = charm_deployed.get("type") + + # Getting ee-id + ee_id = charm_deployed.get("ee_id") + + step = "Getting descriptor config" + descriptor_config = get_configuration( + current_vnfd, current_vnfd["id"] + ) + + if "execution-environment-list" in descriptor_config: + ee_list = descriptor_config.get( + "execution-environment-list", [] + ) + else: + ee_list = [] + + # There could be several charm used in the same VNF + for ee_item in ee_list: + if ee_item.get("juju"): + + step = "Getting charm name" + charm_name = ee_item["juju"].get("charm") + + step = "Setting Charm artifact paths" + current_charm_artifact_path.append( + get_charm_artifact_path( + base_folder, + charm_name, + charm_type, + current_vnf_revision, + ) + ) + target_charm_artifact_path.append( + get_charm_artifact_path( + base_folder, + charm_name, + charm_type, + ) + ) + + charm_artifact_paths = zip( + current_charm_artifact_path, target_charm_artifact_path + ) + + step = "Checking if software version has changed in VNFD" + if find_software_version(current_vnfd) != find_software_version( + latest_vnfd + ): + + step = "Checking if existing VNF has charm" + for current_charm_path, target_charm_path in list( + charm_artifact_paths + ): + if current_charm_path: + raise LcmException( + "Software version change is not supported as VNF instance {} has charm.".format( + vnf_instance_id + ) + ) + + # There is no change in the charm package, then redeploy the VNF + # based on new descriptor + step = "Redeploying VNF" + # This part is in https://osm.etsi.org/gerrit/11943 + + else: + step = "Checking if any charm package has changed or not" + for current_charm_path, target_charm_path in list( + charm_artifact_paths + ): + if ( + current_charm_path + and target_charm_path + and self.check_charm_hash_changed( + current_charm_path, target_charm_path + ) + ): + + step = "Checking whether VNF uses juju bundle" + if check_juju_bundle_existence(current_vnfd): + + raise LcmException( + "Charm upgrade is not supported for the instance which" + " uses juju-bundle: {}".format( + check_juju_bundle_existence(current_vnfd) + ) + ) + + step = "Upgrading Charm" + ( + result, + detailed_status, + ) = await self._ns_charm_upgrade( + ee_id=ee_id, + charm_id=charm_id, + charm_type=charm_type, + path=self.fs.path + target_charm_path, + timeout=timeout_seconds, + ) + + if result == "FAILED": + nslcmop_operation_state = result + error_description_nslcmop = detailed_status + + db_nslcmop_update["detailed-status"] = detailed_status + self.logger.debug( + logging_text + + " step {} Done with result {} {}".format( + step, nslcmop_operation_state, detailed_status + ) + ) + + step = "Updating policies" + # This part is in https://osm.etsi.org/gerrit/11943 + + # If nslcmop_operation_state is None, so any operation is not failed. + if not nslcmop_operation_state: + nslcmop_operation_state = "COMPLETED" + + # If update CHANGE_VNFPKG nslcmop_operation is successful + # vnf revision need to be updated + vnfr_update["revision"] = latest_vnfd_revision + self.update_db_2("vnfrs", db_vnfr["_id"], vnfr_update) + + self.logger.debug( + logging_text + + " task Done with result {} {}".format( + nslcmop_operation_state, detailed_status + ) + ) + elif update_type == "REMOVE_VNF": + # This part is included in https://osm.etsi.org/gerrit/11876 + pass + + # If nslcmop_operation_state is None, so any operation is not failed. + # All operations are executed in overall. + if not nslcmop_operation_state: + nslcmop_operation_state = "COMPLETED" + db_nsr_update["operational-status"] = old_operational_status + + except (DbException, LcmException, N2VCException, K8sException) as e: + self.logger.error(logging_text + "Exit Exception {}".format(e)) + exc = e + except asyncio.CancelledError: + self.logger.error( + logging_text + "Cancelled Exception while '{}'".format(step) + ) + exc = "Operation was cancelled" + except asyncio.TimeoutError: + self.logger.error(logging_text + "Timeout while '{}'".format(step)) + exc = "Timeout" + except Exception as e: + exc = traceback.format_exc() + self.logger.critical( + logging_text + "Exit Exception {} {}".format(type(e).__name__, e), + exc_info=True, + ) + finally: + if exc: + db_nslcmop_update[ + "detailed-status" + ] = ( + detailed_status + ) = error_description_nslcmop = "FAILED {}: {}".format(step, exc) + nslcmop_operation_state = "FAILED" + db_nsr_update["operational-status"] = old_operational_status + if db_nsr: + self._write_ns_status( + nsr_id=nsr_id, + ns_state=db_nsr["nsState"], + current_operation="IDLE", + current_operation_id=None, + other_update=db_nsr_update, + ) + + self._write_op_status( + op_id=nslcmop_id, + stage="", + error_message=error_description_nslcmop, + operation_state=nslcmop_operation_state, + other_update=db_nslcmop_update, + ) + + if nslcmop_operation_state: + try: + await self.msg.aiowrite( + "ns", + "updated", + { + "nsr_id": nsr_id, + "nslcmop_id": nslcmop_id, + "operationState": nslcmop_operation_state, + }, + loop=self.loop, + ) + except Exception as e: + self.logger.error( + logging_text + "kafka_write notification Exception {}".format(e) + ) + self.logger.debug(logging_text + "Exit") + self.lcm_tasks.remove("ns", nsr_id, nslcmop_id, "ns_update") + return nslcmop_operation_state, detailed_status + async def scale(self, nsr_id, nslcmop_id): # Try to lock HA task here task_is_locked_by_me = self.lcm_tasks.lock_HA("ns", "nslcmops", nslcmop_id) diff --git a/osm_lcm/tests/test_db_descriptors.py b/osm_lcm/tests/test_db_descriptors.py index 34491a7..457ea30 100644 --- a/osm_lcm/tests/test_db_descriptors.py +++ b/osm_lcm/tests/test_db_descriptors.py @@ -120,6 +120,38 @@ db_nsds_text = """ db_nslcmops_text = """ --- +- _admin: + created: 1651100375.77829 + modified: 1651100481.36625 + projects_read: + - 7f563445c74147f78e29b193a6da42bb + projects_write: + - 7f563445c74147f78e29b193a6da42bb + worker: a5adf5972b63 + detailed-status: success + _id: 6bd4362f-da74-4bd8-a825-fd00e610c644 + id: 6bd4362f-da74-4bd8-a825-fd00e610c644 + operationState: COMPLETED + queuePosition: 0 + stage: '' + errorMessage: '' + detailedStatus: + statusEnteredTime: 1651100481.36625 + nsInstanceId: 7e3ad9ce-39b8-4636-a661-7870f25bf800 + lcmOperationType: update + startTime: 1651100375.77823 + isAutomaticInvocation: false + operationParams: + updateType: CHANGE_VNFPKG + changeVnfPackageData: + vnfInstanceId: 6421c7c9-d865-4fb4-9a13-d4275d243e01 + vnfdId: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 + lcmOperationType: update + nsInstanceId: f48163a6-c807-47bc-9682-f72caef5af85 + isCancelPending: false + links: + self: "/osm/nslcm/v1/ns_lcm_op_occs/6bd4362f-da74-4bd8-a825-fd00e610c644" + nsInstance: "/osm/nslcm/v1/ns_instances/f48163a6-c807-47bc-9682-f72caef5af85" - _admin: created: 1566823354.4148262 modified: 1566823354.4148262 @@ -315,6 +347,7 @@ db_nsrs_text = """ vdu_count_index: null vdu_id: null vdu_name: null + type: lxc_proxy_charm vnfd_id: hackfest3charmed-vnf - application: alf-c-ab ee_id: f48163a6-c807-47bc-9682-f72caef5af85.alf-c-ab @@ -329,6 +362,7 @@ db_nsrs_text = """ vdu_count_index: null vdu_id: null vdu_name: null + type: lxc_proxy_charm vnfd_id: hackfest3charmed-vnf VCA-model-name: f48163a6-c807-47bc-9682-f72caef5af85 modified: 1566823354.3716335 @@ -861,9 +895,10 @@ db_nsrs_text = """ nsd-name-ref: hackfest3charmed-ns nsd-ref: hackfest3charmed-ns operational-events: [] - operational-status: failed + operational-status: running orchestration-progress: {} resource-orchestrator: osmopenmano + nsState: INSTANTIATED short-name: ALF ssh-authorized-key: null flavor : [{"vcpu-count":1,"memory-mb":1024,"storage-gb":"10","vim_info":[],"name":"mgmtVM-flv","id":"0"}] @@ -1412,6 +1447,196 @@ db_k8sclusters_text = """ vim_account: ea958ba5-4e58-4405-bf42-6e3be15d4c3a """ +db_vnfds_revisions_text = """ +--- +- _admin: + created: 1566823352.7154346 + modified: 1566823353.9295402 + onboardingState: ONBOARDED + operationalState: ENABLED + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + storage: + descriptor: hackfest_3charmed_vnfd/hackfest_3charmed_vnfd.yaml + folder: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 + fs: local + path: /app/storage/ + pkg-dir: hackfest_3charmed_vnfd + zipfile: package.tar.gz + type: vnfd + usageState: NOT_IN_USE + userDefinedData: {} + _id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:1 + id: hackfest3charmed-vnf + description: >- + A VNF consisting of 2 VDUs connected to an internal VL, and one VDU + with cloud-init + product-name: hackfest3charmed-vnf + version: '1.0' + mgmt-cp: vnf-mgmt-ext + virtual-compute-desc: + - id: mgmt-compute + virtual-cpu: + num-virtual-cpu: 1 + virtual-memory: + size: 1 + - id: data-compute + virtual-cpu: + num-virtual-cpu: 1 + virtual-memory: + size: 1 + kdu: + - juju-bundle: stable/native-kdu + name: native-kdu + virtual-storage-desc: + - id: mgmt-storage + block-storage-data: + size-of-storage: 10 + - id: data-storage + block-storage-data: + size-of-storage: 10 + + sw-image-desc: + - id: hackfest3-mgmt + name: hackfest3-mgmt + + vdu: + - id: mgmtVM + name: mgmtVM + cloud-init-file: cloud-config.txt + sw-image-desc: hackfest3-mgmt + virtual-compute-desc: mgmt-compute + virtual-storage-desc: mgmt-storage + int-cpd: + - id: vnf-mgmt + order: 1 + virtual-network-interface-requirement: + - name: mgmtVM-eth0 + virtual-interface: + type: VIRTIO + - id: mgmtVM-internal + int-virtual-link-desc: internal + order: 2 + virtual-network-interface-requirement: + - name: mgmtVM-eth1 + virtual-interface: + type: VIRTIO + - id: dataVM + name: dataVM + sw-image-desc: hackfest3-mgmt + virtual-compute-desc: data-compute + virtual-storage-desc: data-storage + int-cpd: + - id: dataVM-internal + int-virtual-link-desc: internal + order: 1 + virtual-network-interface-requirement: + - name: dataVM-eth1 + virtual-interface: + type: VIRTIO + - id: vnf-data + order: 2 + virtual-network-interface-requirement: + - name: dataVM-eth0 + virtual-interface: + type: VIRTIO + monitoring-parameter: + - id: dataVM_cpu_util + name: dataVM_cpu_util + performance-metric: cpu_utilization + + int-virtual-link-desc: + - id: internal + + ext-cpd: + - id: vnf-mgmt-ext + int-cpd: # Connection to int-cpd + vdu-id: mgmtVM + cpd: vnf-mgmt + - id: vnf-data-ext + int-cpd: # Connection to int-cpd + vdu-id: dataVM + cpd: vnf-data + + df: + - id: hackfest_default + vdu-profile: + - id: mgmtVM + min-number-of-instances: 1 + - id: dataVM + min-number-of-instances: 1 + max-number-of-instances: 10 + instantiation-level: + - id: default + vdu-level: + - vdu-id: mgmtVM + number-of-instances: 1 + - vdu-id: dataVM + number-of-instances: 1 + scaling-aspect: + - id: scale_dataVM + name: scale_dataVM + max-scale-level: 10 + aspect-delta-details: + deltas: + - id: delta1 + vdu-delta: + - id: vdudelta1 + number-of-instances: 1 + scaling-policy: + - name: auto_cpu_util_above_threshold + scaling-type: automatic + enabled: true + threshold-time: 0 + cooldown-time: 60 + scaling-criteria: + - name: cpu_util_above_threshold + scale-in-relational-operation: LE + scale-in-threshold: '15.0000000000' + scale-out-relational-operation: GE + scale-out-threshold: '60.0000000000' + vnf-monitoring-param-ref: dataVM_cpu_util + scaling-config-action: + - trigger: post-scale-out + vnf-config-primitive-name-ref: touch + - trigger: pre-scale-in + vnf-config-primitive-name-ref: touch + lcm-operations-configuration: + operate-vnf-op-config: + day1-2: + - id: hackfest3charmed-vnf + execution-environment-list: + - id: simple-ee + juju: + charm: simple + initial-config-primitive: + - seq: "1" + execution-environment-ref: simple-ee + name: config + parameter: + - name: ssh-hostname + value: + - name: ssh-username + value: ubuntu + - name: ssh-password + value: osm4u + - seq: "2" + execution-environment-ref: simple-ee + name: touch + parameter: + - name: filename + value: + config-primitive: + - name: touch + execution-environment-ref: simple-ee + parameter: + - data-type: STRING + default-value: + name: filename +""" + db_vnfds_text = """ --- - _admin: @@ -1810,6 +2035,89 @@ db_vnfrs_text = """ vim-id: ff181e6d-2597-4244-b40b-bb0174bdfeb6 vnfd-id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 vnfd-ref: hackfest3charmed-vnf +- _admin: + created: 1566823354.3668208 + modified: 1566823354.3668208 + nsState: NOT_INSTANTIATED + projects_read: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + projects_write: + - 25b5aebf-3da1-49ed-99de-1d2b4a86d6e4 + _id: 6421c7c9-d865-4fb4-9a13-d4275d243e01 + additionalParamsForVnf: + touch_filename: /home/ubuntu/first-touch-1 + touch_filename2: /home/ubuntu/second-touch-1 + connection-point: + - connection-point-id: vnf-mgmt + id: vnf-mgmt + name: vnf-mgmt + - connection-point-id: vnf-data + id: vnf-data + name: vnf-data + created-time: 1566823354.36234 + id: 6421c7c9-d865-4fb4-9a13-d4275d243e01 + ip-address: 10.205.1.46 + member-vnf-index-ref: '1' + nsr-id-ref: f48163a6-c807-47bc-9682-f72caef5af85 + vdur: + - _id: f0e7d7ce-2443-4dcb-ad0b-5ab9f3b13d37 + count-index: 0 + interfaces: + - ip-address: 10.205.1.46 + mac-address: fa:16:3e:b4:3e:b1 + mgmt-vnf: true + name: mgmtVM-eth0 + ns-vld-id: mgmt + - ip-address: 192.168.54.2 + mac-address: fa:16:3e:6e:7e:78 + name: mgmtVM-eth1 + vnf-vld-id: internal + internal-connection-point: + - connection-point-id: mgmtVM-internal + id: mgmtVM-internal + name: mgmtVM-internal + ip-address: 10.205.1.46 + name: ALF-1-mgmtVM-1 + status: ACTIVE + status-detailed: null + vdu-id-ref: mgmtVM + vim-id: c2538499-4c30-41c0-acd5-80cb92f48061 + ns-image-id: 0 + ns-flavor-id: 0 + affinity-or-anti-affinity-group-id : [] + - _id: ab453219-2d9a-45c2-864d-2c0788385028 + count-index: 0 + interfaces: + - ip-address: 192.168.54.3 + mac-address: fa:16:3e:d9:7a:5d + name: dataVM-eth0 + vnf-vld-id: internal + - ip-address: 192.168.24.3 + mac-address: fa:16:3e:d1:6c:0d + name: dataVM-xe0 + ns-vld-id: datanet + internal-connection-point: + - connection-point-id: dataVM-internal + id: dataVM-internal + name: dataVM-internal + ip-address: null + name: ALF-1-dataVM-1 + status: ACTIVE + status-detailed: null + vdu-id-ref: dataVM + vim-id: 87973c3f-365d-4227-95c2-7a8abc74349c + ns-image-id: 0 + ns-flavor-id: 0 + affinity-or-anti-affinity-group-id : [] + vim-account-id: ea958ba5-4e58-4405-bf42-6e3be15d4c3a + vld: + - id: internal + name: ALF-internal + status: ACTIVE + status-detailed: null + vim-id: ff181e6d-2597-4244-b40b-bb0174bdfeb6 + vnfd-id: 7637bcf8-cf14-42dc-ad70-c66fcf1e6e77 + vnfd-ref: hackfest3charmed-vnf - _admin: created: 1566823354.3703845 modified: 1566823354.3703845 @@ -2038,6 +2346,7 @@ test_ids = { "ns": "f48163a6-c807-47bc-9682-f72caef5af85", "instantiate": "a639fac7-e0bb-4225-8ecb-c1f8efcc125e", "terminate": "a639fac7-e0bb-4225-ffff-c1f8efcc125e", + "update": "6bd4362f-da74-4bd8-a825-fd00e610c644", }, "TEST-KDU": { "ns": "0bcb701c-ee4d-41ab-8ee6-f4156f7f114d", diff --git a/osm_lcm/tests/test_ns.py b/osm_lcm/tests/test_ns.py index ef87a4c..86e2136 100644 --- a/osm_lcm/tests/test_ns.py +++ b/osm_lcm/tests/test_ns.py @@ -27,7 +27,11 @@ from osm_lcm.lcm_utils import TaskRegistry from osm_lcm.ng_ro import NgRoClient from osm_lcm.data_utils.database.database import Database from osm_lcm.data_utils.filesystem.filesystem import Filesystem +from osm_lcm.data_utils.vnfd import find_software_version +from osm_lcm.lcm_utils import check_juju_bundle_existence, get_charm_artifact_path +from osm_lcm.lcm_utils import LcmException from uuid import uuid4 +from unittest.mock import Mock, patch from osm_lcm.tests import test_db_descriptors as descriptors @@ -168,6 +172,10 @@ class TestMyNS(asynctest.TestCase): self.db.create_list( "vnfds", yaml.load(descriptors.db_vnfds_text, Loader=yaml.Loader) ) + self.db.create_list( + "vnfds_revisions", + yaml.load(descriptors.db_vnfds_revisions_text, Loader=yaml.Loader), + ) self.db.create_list( "nsds", yaml.load(descriptors.db_nsds_text, Loader=yaml.Loader) ) @@ -855,6 +863,748 @@ class TestMyNS(asynctest.TestCase): # self.assertEqual(db_nsr.get("errorDescription "), None, "errorDescription different than None") # self.assertEqual(db_nsr.get("errorDetail"), None, "errorDetail different than None") + # Test update method + + async def test_update(self): + + nsr_id = descriptors.test_ids["TEST-A"]["ns"] + nslcmop_id = descriptors.test_ids["TEST-A"]["update"] + vnfr_id = "6421c7c9-d865-4fb4-9a13-d4275d243e01" + vnfd_id = "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77" + + def mock_reset(): + mock_charm_hash.reset_mock() + mock_juju_bundle.reset_mock() + fs.sync.reset_mock() + mock_charm_upgrade.reset_mock() + mock_software_version.reset_mock() + + with self.subTest( + i=1, + t="Update type: CHANGE_VNFPKG, latest_vnfd revision changed," + "Charm package changed, sw-version is not changed.", + ): + + self.db.set_one( + "vnfds", + q_filter={"_id": vnfd_id}, + update_dict={"_admin.revision": 3, "kdu": []}, + ) + + self.db.set_one( + "vnfds_revisions", + q_filter={"_id": vnfd_id + ":1"}, + update_dict={"_admin.revision": 1, "kdu": []}, + ) + + self.db.set_one( + "vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1} + ) + + mock_charm_hash = Mock(autospec=True) + mock_charm_hash.return_value = True + + mock_juju_bundle = Mock(return_value=None) + + mock_software_version = Mock(autospec=True) + mock_software_version.side_effect = ["1.0", "1.0"] + + mock_charm_upgrade = asynctest.Mock(autospec=True) + task = asyncio.Future() + task.set_result(("COMPLETED", "some_output")) + mock_charm_upgrade.return_value = task + + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + fs.sync.side_effect = [None, None] + + instance = self.my_ns + + expected_operation_state = "COMPLETED" + expected_operation_error = "" + expected_vnfr_revision = 3 + expected_ns_state = "INSTANTIATED" + expected_ns_operational_state = "running" + + with patch.object(instance, "fs", fs), patch( + "osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed", mock_charm_hash + ), patch("osm_lcm.ns.NsLcm._ns_charm_upgrade", mock_charm_upgrade), patch( + "osm_lcm.data_utils.vnfd.find_software_version", mock_software_version + ), patch( + "osm_lcm.lcm_utils.check_juju_bundle_existence", mock_juju_bundle + ): + + await instance.update(nsr_id, nslcmop_id) + return_operation_state = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("operationState") + return_operation_error = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("errorMessage") + return_ns_operational_state = self.db.get_one( + "nsrs", {"_id": nsr_id} + ).get("operational-status") + + return_vnfr_revision = self.db.get_one("vnfrs", {"_id": vnfr_id}).get( + "revision" + ) + + return_ns_state = self.db.get_one("nsrs", {"_id": nsr_id}).get( + "nsState" + ) + + mock_charm_hash.assert_called_with( + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:1/hackfest_3charmed_vnfd/charms/simple", + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77/hackfest_3charmed_vnfd/charms/simple", + ) + + self.assertEqual(fs.sync.call_count, 2) + self.assertEqual(return_ns_state, expected_ns_state) + self.assertEqual(return_operation_state, expected_operation_state) + self.assertEqual(return_operation_error, expected_operation_error) + self.assertEqual( + return_ns_operational_state, expected_ns_operational_state + ) + self.assertEqual(return_vnfr_revision, expected_vnfr_revision) + + mock_reset() + + with self.subTest( + i=2, t="Update type: CHANGE_VNFPKG, latest_vnfd revision not changed" + ): + + self.db.set_one( + "vnfds", q_filter={"_id": vnfd_id}, update_dict={"_admin.revision": 1} + ) + + self.db.set_one( + "vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1} + ) + + mock_charm_hash = Mock(autospec=True) + mock_charm_hash.return_value = True + + mock_juju_bundle = Mock(return_value=None) + mock_software_version = Mock(autospec=True) + + mock_charm_upgrade = asynctest.Mock(autospec=True) + task = asyncio.Future() + task.set_result(("COMPLETED", "some_output")) + mock_charm_upgrade.return_value = task + + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + fs.sync.side_effect = [None, None] + + instance = self.my_ns + + expected_operation_state = "COMPLETED" + expected_operation_error = "" + expected_vnfr_revision = 1 + expected_ns_state = "INSTANTIATED" + expected_ns_operational_state = "running" + + with patch.object(instance, "fs", fs), patch( + "osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed", mock_charm_hash + ), patch("osm_lcm.ns.NsLcm._ns_charm_upgrade", mock_charm_upgrade), patch( + "osm_lcm.lcm_utils.check_juju_bundle_existence", mock_juju_bundle + ): + + await instance.update(nsr_id, nslcmop_id) + + return_operation_state = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("operationState") + + return_operation_error = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("errorMessage") + + return_ns_operational_state = self.db.get_one( + "nsrs", {"_id": nsr_id} + ).get("operational-status") + + return_ns_state = self.db.get_one("nsrs", {"_id": nsr_id}).get( + "nsState" + ) + + return_vnfr_revision = self.db.get_one("vnfrs", {"_id": vnfr_id}).get( + "revision" + ) + + mock_charm_hash.assert_not_called() + mock_software_version.assert_not_called() + mock_juju_bundle.assert_not_called() + mock_charm_upgrade.assert_not_called() + fs.sync.assert_not_called() + + self.assertEqual(return_ns_state, expected_ns_state) + self.assertEqual(return_operation_state, expected_operation_state) + self.assertEqual(return_operation_error, expected_operation_error) + self.assertEqual( + return_ns_operational_state, expected_ns_operational_state + ) + self.assertEqual(return_vnfr_revision, expected_vnfr_revision) + + mock_reset() + + with self.subTest( + i=3, + t="Update type: CHANGE_VNFPKG, latest_vnfd revision changed, " + "Charm package is not changed, sw-version is not changed.", + ): + + self.db.set_one( + "vnfds", q_filter={"_id": vnfd_id}, update_dict={"_admin.revision": 3} + ) + + self.db.set_one( + "vnfds_revisions", + q_filter={"_id": vnfd_id + ":1"}, + update_dict={"_admin.revision": 1}, + ) + + self.db.set_one( + "vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1} + ) + + mock_charm_hash = Mock(autospec=True) + mock_charm_hash.return_value = False + + mock_juju_bundle = Mock(return_value=None) + + mock_software_version = Mock(autospec=True) + + mock_charm_upgrade = asynctest.Mock(autospec=True) + task = asyncio.Future() + task.set_result(("COMPLETED", "some_output")) + mock_charm_upgrade.return_value = task + mock_software_version.side_effect = ["1.0", "1.0"] + + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + fs.sync.side_effect = [None, None] + + instance = self.my_ns + + expected_operation_state = "COMPLETED" + expected_operation_error = "" + expected_vnfr_revision = 3 + expected_ns_state = "INSTANTIATED" + expected_ns_operational_state = "running" + + with patch.object(instance, "fs", fs), patch( + "osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed", mock_charm_hash + ), patch("osm_lcm.ns.NsLcm._ns_charm_upgrade", mock_charm_upgrade), patch( + "osm_lcm.lcm_utils.check_juju_bundle_existence", mock_juju_bundle + ): + + await instance.update(nsr_id, nslcmop_id) + + return_operation_state = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("operationState") + + return_operation_error = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("errorMessage") + + return_ns_operational_state = self.db.get_one( + "nsrs", {"_id": nsr_id} + ).get("operational-status") + + return_vnfr_revision = self.db.get_one("vnfrs", {"_id": vnfr_id}).get( + "revision" + ) + + return_ns_state = self.db.get_one("nsrs", {"_id": nsr_id}).get( + "nsState" + ) + + mock_charm_hash.assert_called_with( + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:1/hackfest_3charmed_vnfd/charms/simple", + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77/hackfest_3charmed_vnfd/charms/simple", + ) + + self.assertEqual(fs.sync.call_count, 2) + self.assertEqual(mock_charm_hash.call_count, 1) + + mock_juju_bundle.assert_not_called() + mock_charm_upgrade.assert_not_called() + + self.assertEqual(return_ns_state, expected_ns_state) + self.assertEqual(return_operation_state, expected_operation_state) + self.assertEqual(return_operation_error, expected_operation_error) + self.assertEqual( + return_ns_operational_state, expected_ns_operational_state + ) + self.assertEqual(return_vnfr_revision, expected_vnfr_revision) + + mock_reset() + + with self.subTest( + i=4, + t="Update type: CHANGE_VNFPKG, latest_vnfd revision changed, " + "Charm package exists, sw-version changed.", + ): + + self.db.set_one( + "vnfds", + q_filter={"_id": vnfd_id}, + update_dict={"_admin.revision": 3, "software-version": "3.0"}, + ) + + self.db.set_one( + "vnfds_revisions", + q_filter={"_id": vnfd_id + ":1"}, + update_dict={"_admin.revision": 1}, + ) + + self.db.set_one( + "vnfrs", + q_filter={"_id": vnfr_id}, + update_dict={"revision": 1}, + ) + + mock_charm_hash = Mock(autospec=True) + mock_charm_hash.return_value = False + + mock_juju_bundle = Mock(return_value=None) + + mock_charm_upgrade = asynctest.Mock(autospec=True) + task = asyncio.Future() + task.set_result(("COMPLETED", "some_output")) + mock_charm_upgrade.return_value = task + + mock_charm_artifact = Mock(autospec=True) + mock_charm_artifact.side_effect = [ + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:1/hackfest_3charmed_vnfd/charms/simple", + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77/hackfest_3charmed_vnfd/charms/simple", + ] + + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + fs.sync.side_effect = [None, None] + + instance = self.my_ns + + expected_operation_state = "FAILED" + expected_operation_error = "FAILED Checking if existing VNF has charm: Software version change is not supported as VNF instance 6421c7c9-d865-4fb4-9a13-d4275d243e01 has charm." + expected_vnfr_revision = 1 + expected_ns_state = "INSTANTIATED" + expected_ns_operational_state = "running" + + with patch.object(instance, "fs", fs), patch( + "osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed", mock_charm_hash + ), patch("osm_lcm.ns.NsLcm._ns_charm_upgrade", mock_charm_upgrade), patch( + "osm_lcm.lcm_utils.get_charm_artifact_path", mock_charm_artifact + ): + + await instance.update(nsr_id, nslcmop_id) + + return_operation_state = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("operationState") + + return_operation_error = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("errorMessage") + + return_ns_operational_state = self.db.get_one( + "nsrs", {"_id": nsr_id} + ).get("operational-status") + + return_vnfr_revision = self.db.get_one("vnfrs", {"_id": vnfr_id}).get( + "revision" + ) + + return_ns_state = self.db.get_one("nsrs", {"_id": nsr_id}).get( + "nsState" + ) + + self.assertEqual(fs.sync.call_count, 2) + mock_charm_hash.assert_not_called() + + mock_juju_bundle.assert_not_called() + mock_charm_upgrade.assert_not_called() + + self.assertEqual(return_ns_state, expected_ns_state) + self.assertEqual(return_operation_state, expected_operation_state) + self.assertEqual(return_operation_error, expected_operation_error) + self.assertEqual( + return_ns_operational_state, expected_ns_operational_state + ) + self.assertEqual(return_vnfr_revision, expected_vnfr_revision) + + mock_reset() + + with self.subTest( + i=5, + t="Update type: CHANGE_VNFPKG, latest_vnfd revision changed," + "Charm package exists, sw-version not changed, juju-bundle exists", + ): + + self.db.set_one( + "vnfds", + q_filter={"_id": vnfd_id}, + update_dict={ + "_admin.revision": 3, + "software-version": "1.0", + "kdu.0.juju-bundle": "stable/native-kdu", + }, + ) + + self.db.set_one( + "vnfds_revisions", + q_filter={"_id": vnfd_id + ":1"}, + update_dict={ + "_admin.revision": 1, + "software-version": "1.0", + "kdu.0.juju-bundle": "stable/native-kdu", + }, + ) + + self.db.set_one( + "vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1} + ) + + mock_charm_hash = Mock(autospec=True) + mock_charm_hash.return_value = True + + mock_charm_artifact = Mock(autospec=True) + mock_charm_artifact.side_effect = [ + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:1/hackfest_3charmed_vnfd/charms/simple", + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77/hackfest_3charmed_vnfd/charms/simple", + ] + + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + fs.sync.side_effect = [None, None] + + instance = self.my_ns + + expected_operation_state = "FAILED" + expected_operation_error = "FAILED Checking whether VNF uses juju bundle: Charm upgrade is not supported for the instance which uses juju-bundle: stable/native-kdu" + expected_vnfr_revision = 1 + expected_ns_state = "INSTANTIATED" + expected_ns_operational_state = "running" + + with patch.object(instance, "fs", fs), patch( + "osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed", mock_charm_hash + ), patch("osm_lcm.lcm_utils.get_charm_artifact_path", mock_charm_artifact): + + await instance.update(nsr_id, nslcmop_id) + + return_operation_state = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("operationState") + + return_operation_error = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("errorMessage") + + return_ns_operational_state = self.db.get_one( + "nsrs", {"_id": nsr_id} + ).get("operational-status") + + return_vnfr_revision = self.db.get_one("vnfrs", {"_id": vnfr_id}).get( + "revision" + ) + + return_ns_state = self.db.get_one("nsrs", {"_id": nsr_id}).get( + "nsState" + ) + + self.assertEqual(fs.sync.call_count, 2) + self.assertEqual(mock_charm_hash.call_count, 1) + self.assertEqual(mock_charm_hash.call_count, 1) + + mock_charm_upgrade.assert_not_called() + + self.assertEqual(return_ns_state, expected_ns_state) + self.assertEqual(return_operation_state, expected_operation_state) + self.assertEqual(return_operation_error, expected_operation_error) + self.assertEqual( + return_ns_operational_state, expected_ns_operational_state + ) + self.assertEqual(return_vnfr_revision, expected_vnfr_revision) + + mock_reset() + + with self.subTest( + i=6, + t="Update type: CHANGE_VNFPKG, latest_vnfd revision changed," + "Charm package exists, sw-version not changed, charm-upgrade failed", + ): + + self.db.set_one( + "vnfds", + q_filter={"_id": vnfd_id}, + update_dict={ + "_admin.revision": 3, + "software-version": "1.0", + "kdu": [], + }, + ) + + self.db.set_one( + "vnfds_revisions", + q_filter={"_id": vnfd_id + ":1"}, + update_dict={ + "_admin.revision": 1, + "software-version": "1.0", + "kdu": [], + }, + ) + + self.db.set_one( + "vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1} + ) + + mock_charm_hash = Mock(autospec=True) + mock_charm_hash.return_value = True + + mock_charm_upgrade = asynctest.Mock(autospec=True) + task = asyncio.Future() + task.set_result(("FAILED", "some_error")) + mock_charm_upgrade.return_value = task + + mock_charm_artifact = Mock(autospec=True) + mock_charm_artifact.side_effect = [ + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:1/hackfest_3charmed_vnfd/charms/simple", + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77/hackfest_3charmed_vnfd/charms/simple", + ] + + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + fs.sync.side_effect = [None, None] + + instance = self.my_ns + + expected_operation_state = "FAILED" + expected_operation_error = "some_error" + expected_vnfr_revision = 1 + expected_ns_state = "INSTANTIATED" + expected_ns_operational_state = "running" + + with patch.object(instance, "fs", fs), patch( + "osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed", mock_charm_hash + ), patch("osm_lcm.ns.NsLcm._ns_charm_upgrade", mock_charm_upgrade): + + await instance.update(nsr_id, nslcmop_id) + + return_operation_state = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("operationState") + + return_operation_error = self.db.get_one( + "nslcmops", {"_id": nslcmop_id} + ).get("errorMessage") + + return_ns_operational_state = self.db.get_one( + "nsrs", {"_id": nsr_id} + ).get("operational-status") + + return_vnfr_revision = self.db.get_one("vnfrs", {"_id": vnfr_id}).get( + "revision" + ) + + return_ns_state = self.db.get_one("nsrs", {"_id": nsr_id}).get( + "nsState" + ) + + self.assertEqual(fs.sync.call_count, 2) + self.assertEqual(mock_charm_hash.call_count, 1) + self.assertEqual(mock_charm_upgrade.call_count, 1) + + self.assertEqual(return_ns_state, expected_ns_state) + self.assertEqual(return_operation_state, expected_operation_state) + self.assertEqual(return_operation_error, expected_operation_error) + self.assertEqual( + return_ns_operational_state, expected_ns_operational_state + ) + self.assertEqual(return_vnfr_revision, expected_vnfr_revision) + + mock_reset() + + def test_ns_update_helper_methods(self): + def mock_reset(): + fs.mock_reset() + mock_path.mock_reset() + mock_checksumdir.mock_reset() + + with self.subTest( + i=1, t="Find software version, VNFD does not have have software version" + ): + # Testing method find_software_version + + db_vnfd = self.db.get_one( + "vnfds", {"_id": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77"} + ) + expected_result = "1.0" + result = find_software_version(db_vnfd) + self.assertEqual( + result, expected_result, "Default sw version should be 1.0" + ) + + with self.subTest( + i=2, t="Find software version, VNFD includes software version" + ): + # Testing method find_software_version + + db_vnfd = self.db.get_one( + "vnfds", {"_id": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77"} + ) + db_vnfd["software-version"] = "3.1" + expected_result = "3.1" + result = find_software_version(db_vnfd) + self.assertEqual(result, expected_result, "VNFD software version is wrong") + + with self.subTest(i=3, t="Check charm hash, Hash has did not change"): + # Testing method check_charm_hash_changed + + current_path, target_path = "/tmp/charm1", "/tmp/charm1" + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + + mock_path = Mock(autospec=True) + mock_path.exists.side_effect = [True, True] + + mock_checksumdir = Mock(autospec=True) + mock_checksumdir.dirhash.side_effect = ["hash_value", "hash_value"] + + instance = self.my_ns + expected_result = False + + with patch.object(instance, "fs", fs), patch( + "checksumdir.dirhash", mock_checksumdir.dirhash + ), patch("os.path.exists", mock_path.exists): + + result = instance.check_charm_hash_changed(current_path, target_path) + self.assertEqual( + result, expected_result, "Wrong charm hash control value" + ) + self.assertEqual(mock_path.exists.call_count, 2) + self.assertEqual(mock_checksumdir.dirhash.call_count, 2) + + mock_reset() + + with self.subTest(i=4, t="Check charm hash, Hash has changed"): + # Testing method check_charm_hash_changed + + current_path, target_path = "/tmp/charm1", "/tmp/charm2" + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + + mock_path = Mock(autospec=True) + mock_path.exists.side_effect = [True, True] + + mock_checksumdir = Mock(autospec=True) + mock_checksumdir.dirhash.side_effect = ["hash_value", "another_hash_value"] + + instance = self.my_ns + expected_result = True + + with patch.object(instance, "fs", fs), patch( + "checksumdir.dirhash", mock_checksumdir.dirhash + ), patch("os.path.exists", mock_path.exists): + + result = instance.check_charm_hash_changed(current_path, target_path) + self.assertEqual( + result, expected_result, "Wrong charm hash control value" + ) + self.assertEqual(mock_path.exists.call_count, 2) + self.assertEqual(mock_checksumdir.dirhash.call_count, 2) + + mock_reset() + + with self.subTest(i=5, t="Check charm hash, Charm path does not exists"): + # Testing method check_charm_hash_changed + + current_path, target_path = "/tmp/charm1", "/tmp/charm2" + fs = Mock(autospec=True) + fs.path.__add__ = Mock() + fs.path.side_effect = ["/", "/", "/", "/"] + + mock_path = Mock(autospec=True) + mock_path.exists.side_effect = [True, False] + + mock_checksumdir = Mock(autospec=True) + mock_checksumdir.dirhash.side_effect = ["hash_value", "hash_value"] + + instance = self.my_ns + + with patch.object(instance, "fs", fs), patch( + "checksumdir.dirhash", mock_checksumdir.dirhash + ), patch("os.path.exists", mock_path.exists): + + with self.assertRaises(LcmException): + + instance.check_charm_hash_changed(current_path, target_path) + self.assertEqual(mock_path.exists.call_count, 2) + self.assertEqual(mock_checksumdir.dirhash.call_count, 0) + + mock_reset() + + with self.subTest(i=6, t="Check juju bundle existence"): + # Testing method check_juju_bundle_existence + + test_vnfd1 = self.db.get_one( + "vnfds", {"_id": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77"} + ) + test_vnfd2 = self.db.get_one( + "vnfds", {"_id": "d96b1cdf-5ad6-49f7-bf65-907ada989293"} + ) + + expected_result = None + result = check_juju_bundle_existence(test_vnfd1) + self.assertEqual(result, expected_result, "Wrong juju bundle name") + + expected_result = "stable/native-kdu" + result = check_juju_bundle_existence(test_vnfd2) + self.assertEqual(result, expected_result, "Wrong juju bundle name") + + with self.subTest(i=7, t="Check charm artifacts"): + # Testing method check_juju_bundle_existence + + base_folder = { + "folder": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77", + "pkg-dir": "hackfest_3charmed_vnfd", + } + charm_name = "simple" + charm_type = "lxc_proxy_charm" + revision = 3 + + expected_result = "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77:3/hackfest_3charmed_vnfd/charms/simple" + result = get_charm_artifact_path( + base_folder, charm_name, charm_type, revision + ) + self.assertEqual(result, expected_result, "Wrong charm artifact path") + + # SOL004 packages + base_folder = { + "folder": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77", + } + charm_name = "basic" + charm_type = "" + revision = "" + + expected_result = ( + "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77/Scripts/helm-charts/basic" + ) + result = get_charm_artifact_path( + base_folder, charm_name, charm_type, revision + ) + self.assertEqual(result, expected_result, "Wrong charm artifact path") + if __name__ == "__main__": asynctest.main() diff --git a/requirements.in b/requirements.in index 6efebb2..0ec21a3 100644 --- a/requirements.in +++ b/requirements.in @@ -14,6 +14,7 @@ aiohttp<3.8 async-timeout<4 +checksumdir grpcio-tools grpclib idna diff --git a/requirements.txt b/requirements.txt index 0756c97..2e90430 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,9 +24,11 @@ attrs==21.4.0 # via aiohttp chardet==4.0.0 # via aiohttp -grpcio==1.43.0 +checksumdir==1.2.0 + # via -r requirements.in +grpcio==1.44.0 # via grpcio-tools -grpcio-tools==1.43.0 +grpcio-tools==1.44.0 # via -r requirements.in grpclib==0.4.2 # via -r requirements.in @@ -40,11 +42,11 @@ idna==3.3 # via # -r requirements.in # yarl -jinja2==3.0.3 +jinja2==3.1.1 # via -r requirements.in -markupsafe==2.0.1 +markupsafe==2.1.1 # via jinja2 -multidict==5.2.0 +multidict==6.0.2 # via # aiohttp # grpclib -- 2.25.1