Fix Bug 2199 Fixing ns update operation for KNF instances 40/12540/6
authoraticig <gulsum.atici@canonical.com>
Fri, 9 Sep 2022 22:56:58 +0000 (01:56 +0300)
committergarciadeblas <gerardo.garciadeblas@telefonica.com>
Thu, 24 Nov 2022 14:12:20 +0000 (15:12 +0100)
This patch enables ns update operations on KNF instances which are deployed with Helm chart
and using .charm zipped files.

Change-Id: I6c598868ae81243ef598ae5b7df88e26a5b870c0
Signed-off-by: aticig <gulsum.atici@canonical.com>
osm_lcm/lcm_utils.py
osm_lcm/ns.py
osm_lcm/tests/test_lcm_utils.py
osm_lcm/tests/test_ns.py

index 7dac350..956e44f 100644 (file)
@@ -19,6 +19,7 @@
 import asyncio
 import checksumdir
 from collections import OrderedDict
+import hashlib
 import os
 import shutil
 import traceback
@@ -200,6 +201,54 @@ class LcmBase:
         # except DbException as e:
         #     self.logger.error("Updating {} _id={} with '{}'. Error: {}".format(item, _id, _desc, e))
 
+    @staticmethod
+    def calculate_charm_hash(zipped_file):
+        """Calculate the hash of charm files which ends with .charm
+
+        Args:
+            zipped_file (str): Existing charm package full path
+
+        Returns:
+            hex digest  (str): The hash of the charm file
+        """
+        filehash = hashlib.md5()
+        with open(zipped_file, mode="rb") as file:
+            contents = file.read()
+            filehash.update(contents)
+            return filehash.hexdigest()
+
+    @staticmethod
+    def compare_charm_hash(current_charm, target_charm):
+        """Compare the existing charm and the target charm if the charms
+        are given as zip files ends with .charm
+
+        Args:
+            current_charm (str): Existing charm package full path
+            target_charm  (str): Target charm package full path
+
+        Returns:
+            True/False (bool): if charm has changed it returns True
+        """
+        return LcmBase.calculate_charm_hash(
+            current_charm
+        ) != LcmBase.calculate_charm_hash(target_charm)
+
+    @staticmethod
+    def compare_charmdir_hash(current_charm_dir, target_charm_dir):
+        """Compare the existing charm and the target charm if the charms
+        are given as directories
+
+        Args:
+            current_charm_dir (str): Existing charm package directory path
+            target_charm_dir  (str): Target charm package directory path
+
+        Returns:
+            True/False (bool): if charm has changed it returns True
+        """
+        return checksumdir.dirhash(current_charm_dir) != checksumdir.dirhash(
+            target_charm_dir
+        )
+
     def check_charm_hash_changed(
         self, current_charm_path: str, target_charm_path: str
     ) -> bool:
@@ -214,25 +263,30 @@ class LcmBase:
             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):
+        try:
+            # Check if the charm artifacts are available
+            current_charm = self.fs.path + current_charm_path
+            target_charm = self.fs.path + target_charm_path
 
-                return True
+            if os.path.exists(current_charm) and os.path.exists(target_charm):
 
-            return False
+                # Compare the hash of .charm files
+                if current_charm.endswith(".charm"):
+                    return LcmBase.compare_charm_hash(current_charm, target_charm)
 
-        else:
-            raise LcmException(
-                "Charm artifact {} does not exist in the VNF Package".format(
-                    self.fs.path + target_charm_path
+                # Compare the hash of charm folders
+                return LcmBase.compare_charmdir_hash(current_charm, target_charm)
+
+            else:
+                raise LcmException(
+                    "Charm artifact {} does not exist in the VNF Package".format(
+                        self.fs.path + target_charm_path
+                    )
                 )
-            )
+        except (IOError, OSError, TypeError) as error:
+            self.logger.debug(traceback.format_exc())
+            self.logger.error(f"{error} occured while checking the charm hashes")
+            raise LcmException(error)
 
     @staticmethod
     def get_charm_name(charm_metadata_file: str) -> str:
index 9885f40..968e1f3 100644 (file)
@@ -5905,8 +5905,16 @@ class NsLcm(LcmBase):
                             ee_id = vca_deployed.get("ee_id")
 
                             step = "Getting descriptor config"
+                            if current_vnfd.get("kdu"):
+
+                                search_key = "kdu_name"
+                            else:
+                                search_key = "vnfd_id"
+
+                            entity_id = vca_deployed.get(search_key)
+
                             descriptor_config = get_configuration(
-                                current_vnfd, current_vnfd["id"]
+                                current_vnfd, entity_id
                             )
 
                             if "execution-environment-list" in descriptor_config:
index 6067b2d..71ebab1 100644 (file)
@@ -16,7 +16,8 @@
 # contact: alfonso.tiernosepulveda@telefonica.com
 ##
 import logging
-from unittest.mock import Mock, patch, MagicMock
+import tempfile
+from unittest.mock import Mock, patch, MagicMock, mock_open
 from unittest import TestCase
 
 from osm_common.msgkafka import MsgKafka
@@ -29,6 +30,10 @@ import yaml
 from zipfile import BadZipfile
 
 
+tmpdir = tempfile.mkdtemp()[1]
+tmpfile = tempfile.mkstemp()[1]
+
+
 class TestLcmBase(TestCase):
 
     test_nsr_id = "f48163a6-c807-47bc-9682-f72caef5af85"
@@ -52,6 +57,7 @@ class TestLcmBase(TestCase):
         self.my_ns = LcmBase(self.msg, self.logger)
         self.my_ns.fs = self.fs
         self.my_ns.db = self.db
+        self.hexdigest = "031edd7d41651593c5fe5c006f"
 
     def test_get_charm_name_successfully(self):
         instance = self.my_ns
@@ -245,3 +251,196 @@ class TestLcmBase(TestCase):
                 self.assertEqual(mock_charm_path.call_count, 1)
                 self.assertEqual(mock_metadata_file.call_count, 1)
                 self.assertEqual(mock_charm_name.call_count, 1)
+
+    @patch("builtins.open", new_callable=mock_open(read_data="charm content"))
+    @patch("osm_lcm.lcm_utils.hashlib")
+    def test_calculate_charm_hash_sucessfully(self, mock_hashlib, mocking_open):
+        """Calculate charm hash successfully."""
+        charm = tmpfile
+        hexdigest = self.hexdigest
+        mock_file_hash = MagicMock()
+        mock_hashlib.md5.return_value = mock_file_hash
+        mock_file_hash.hexdigest.return_value = hexdigest
+        result = LcmBase.calculate_charm_hash(charm)
+        self.assertEqual(result, hexdigest)
+        self.assertEqual(mocking_open.call_count, 1)
+        self.assertEqual(mock_file_hash.update.call_count, 1)
+        self.assertEqual(mock_file_hash.hexdigest.call_count, 1)
+        self.assertEqual(mock_hashlib.md5.call_count, 1)
+
+    @patch("builtins.open", new_callable=mock_open(read_data="charm content"))
+    @patch("osm_lcm.lcm_utils.hashlib")
+    def test_calculate_charm_hash_open_raises(self, mock_hashlib, mocking_open):
+        """builtins.open raises exception."""
+        charm = tmpfile
+        hexdigest = self.hexdigest
+        mock_file_hash = MagicMock()
+        mock_hashlib.md5.return_value = mock_file_hash
+        mock_file_hash.hexdigest.return_value = hexdigest
+        mocking_open.side_effect = IOError
+        with self.assertRaises(IOError):
+            LcmBase.calculate_charm_hash(charm)
+        self.assertEqual(mocking_open.call_count, 1)
+        mock_file_hash.update.assert_not_called()
+        mock_file_hash.hexdigest.assert_not_called()
+        self.assertEqual(mock_hashlib.md5.call_count, 1)
+
+    @patch("builtins.open", new_callable=mock_open(read_data="charm content"))
+    @patch("osm_lcm.lcm_utils.hashlib")
+    def test_calculate_charm_filehash_update_raises(self, mock_hashlib, mocking_open):
+        """Filehash update raises exception."""
+        charm = tmpfile
+        hexdigest = self.hexdigest
+        mock_file_hash = MagicMock()
+        mock_file_hash.update.side_effect = Exception
+        mock_hashlib.md5.return_value = mock_file_hash
+        mock_file_hash.hexdigest.return_value = hexdigest
+        with self.assertRaises(Exception):
+            LcmBase.calculate_charm_hash(charm)
+        self.assertEqual(mocking_open.call_count, 1)
+        self.assertEqual(mock_file_hash.update.call_count, 1)
+        mock_file_hash.hexdigest.assert_not_called()
+        self.assertEqual(mock_hashlib.md5.call_count, 1)
+
+    @patch("builtins.open", new_callable=mock_open(read_data="charm content"))
+    @patch("osm_lcm.lcm_utils.hashlib")
+    def test_calculate_charm_filehash_hexdigest_raises(
+        self, mock_hashlib, mocking_open
+    ):
+        """Filehash hexdigest raises exception."""
+        charm = tmpfile
+        mock_file_hash = MagicMock()
+        mock_hashlib.md5.return_value = mock_file_hash
+        mock_file_hash.hexdigest.side_effect = Exception
+        with self.assertRaises(Exception):
+            LcmBase.calculate_charm_hash(charm)
+        self.assertEqual(mocking_open.call_count, 1)
+        self.assertEqual(mock_file_hash.update.call_count, 1)
+        mock_file_hash.hexdigest.assert_called_once()
+        self.assertEqual(mock_hashlib.md5.call_count, 1)
+        mock_file_hash.update.assert_called_once()
+
+    @patch("builtins.open", new_callable=mock_open(read_data="charm content"))
+    @patch("osm_lcm.lcm_utils.hashlib")
+    def test_calculate_charm_filehash_hashlib_md5_raises(
+        self, mock_hashlib, mocking_open
+    ):
+        """Filehash hashlib md5 raises exception."""
+        charm = tmpfile
+        mock_hashlib.md5.side_effect = Exception
+        with self.assertRaises(Exception):
+            LcmBase.calculate_charm_hash(charm)
+        self.assertEqual(mock_hashlib.md5.call_count, 1)
+        mocking_open.assert_not_called()
+
+    @patch("builtins.open", new_callable=mock_open(read_data="charm content"))
+    @patch("osm_lcm.lcm_utils.hashlib")
+    def test_calculate_charm_hash_file_does_not_exist(self, mock_hashlib, mocking_open):
+        """Calculate charm hash, charm file does not exist."""
+        file = None
+        mock_file_hash = MagicMock()
+        mock_hashlib.md5.return_value = mock_file_hash
+        mocking_open.side_effect = FileNotFoundError
+        with self.assertRaises(FileNotFoundError):
+            LcmBase.calculate_charm_hash(file)
+        self.assertEqual(mocking_open.call_count, 1)
+        mock_file_hash.update.assert_not_called()
+        mock_file_hash.hexdigest.assert_not_called()
+        self.assertEqual(mock_hashlib.md5.call_count, 1)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.calculate_charm_hash")
+    def test_compare_charm_hash_charm_changed(self, mock_calculate_charm_hash):
+        """Compare charm hash, charm files are different."""
+        output = True
+        charm1, charm2 = tmpfile, tmpfile
+        mock_calculate_charm_hash.side_effect = [
+            self.hexdigest,
+            "0dd7d4173747593c5fe5c006f",
+        ]
+        result = LcmBase.compare_charm_hash(charm1, charm2)
+        self.assertEqual(output, result)
+        self.assertEqual(mock_calculate_charm_hash.call_count, 2)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.calculate_charm_hash")
+    def test_compare_charm_hash_charm_is_same(self, mock_calculate_charm_hash):
+        """Compare charm hash, charm files are same."""
+        output = False
+        charm1 = charm2 = tmpfile
+        mock_calculate_charm_hash.side_effect = [
+            self.hexdigest,
+            self.hexdigest,
+        ]
+        result = LcmBase.compare_charm_hash(charm1, charm2)
+        self.assertEqual(output, result)
+        self.assertEqual(mock_calculate_charm_hash.call_count, 2)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.calculate_charm_hash")
+    def test_compare_charm_hash_one_charm_is_not_valid(self, mock_calculate_charm_hash):
+        """Compare charm hash, one charm file is not valid."""
+        charm1, charm2 = tmpdir, None
+        mock_calculate_charm_hash.side_effect = [
+            self.hexdigest,
+            FileNotFoundError,
+        ]
+
+        with self.assertRaises(FileNotFoundError):
+            LcmBase.compare_charm_hash(charm1, charm2)
+        self.assertEqual(mock_calculate_charm_hash.call_count, 2)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.calculate_charm_hash")
+    def test_compare_charm_hash_both_charms_are_not_valid(
+        self, mock_calculate_charm_hash
+    ):
+        """Compare charm hash, both charm files are not valid."""
+        charm1, charm2 = None, None
+        mock_calculate_charm_hash.side_effect = [IOError, IOError]
+        with self.assertRaises(IOError):
+            LcmBase.compare_charm_hash(charm1, charm2)
+        self.assertEqual(mock_calculate_charm_hash.call_count, 1)
+
+    @patch("osm_lcm.lcm_utils.checksumdir")
+    def test_compare_charmdir_charm_changed(self, mock_checksum):
+        """Compare charm directory hash, charms are changed."""
+        expected_output = True
+        charm_dir1, charm_dir2 = tmpdir, tmpdir
+        mock_checksum.dirhash.side_effect = [
+            self.hexdigest,
+            "031eddtrtr651593c5fe5c006f",
+        ]
+        result = LcmBase.compare_charmdir_hash(charm_dir1, charm_dir2)
+        self.assertEqual(expected_output, result)
+        self.assertEqual(mock_checksum.dirhash.call_count, 2)
+
+    @patch("osm_lcm.lcm_utils.checksumdir")
+    def test_compare_charmdir_charm_is_same(self, mock_checksum):
+        """Compare charm directory hash, charms are same."""
+        expected_output = False
+        charm_dir1 = charm_dir2 = tmpdir
+        mock_checksum.dirhash.side_effect = [
+            self.hexdigest,
+            self.hexdigest,
+        ]
+        result = LcmBase.compare_charmdir_hash(charm_dir1, charm_dir2)
+        self.assertEqual(expected_output, result)
+        self.assertEqual(mock_checksum.dirhash.call_count, 2)
+
+    @patch("osm_lcm.lcm_utils.checksumdir")
+    def test_compare_charmdir_one_charmdir_is_not_valid(self, mock_checksum):
+        """Compare charm directory hash, one charm directory is not valid."""
+        charm_dir1, charm_dir2 = tmpdir, None
+        mock_checksum.dirhash.side_effect = [
+            self.hexdigest,
+            FileNotFoundError,
+        ]
+        with self.assertRaises(FileNotFoundError):
+            LcmBase.compare_charmdir_hash(charm_dir1, charm_dir2)
+        self.assertEqual(mock_checksum.dirhash.call_count, 2)
+
+    @patch("osm_lcm.lcm_utils.checksumdir")
+    def test_compare_charmdir_both_charmdirs_are_not_valid(self, mock_checksum):
+        """Compare charm directory hash, both charm directories are not valid."""
+        charm_dir1, charm_dir2 = None, None
+        mock_checksum.dirhash.side_effect = [FileNotFoundError, FileNotFoundError]
+        with self.assertRaises(FileNotFoundError):
+            LcmBase.compare_charmdir_hash(charm_dir1, charm_dir2)
+        self.assertEqual(mock_checksum.dirhash.call_count, 1)
index 2004701..56d7ffa 100644 (file)
@@ -18,6 +18,7 @@
 
 import asynctest  # pip3 install asynctest --user
 import asyncio
+from copy import deepcopy
 import yaml
 import copy
 from os import getenv
@@ -75,6 +76,18 @@ lcm_config = {
         "ng": True,
     },
 }
+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"
+update_fs = Mock(autospec=True)
+update_fs.path.__add__ = Mock()
+update_fs.path.side_effect = ["/", "/", "/", "/"]
+update_fs.sync.side_effect = [None, None]
+
+
+def callable(a):
+    return a
 
 
 class TestMyNS(asynctest.TestCase):
@@ -87,7 +100,7 @@ class TestMyNS(asynctest.TestCase):
         params={},
         machine_spec={},
         callback=None,
-        *callback_args
+        *callback_args,
     ):
         if callback:
             for status, message in (
@@ -943,747 +956,570 @@ 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:3/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:3/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 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"]
+    @patch("osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed")
+    @patch(
+        "osm_lcm.ns.NsLcm._ns_charm_upgrade", new_callable=asynctest.Mock(autospec=True)
+    )
+    @patch("osm_lcm.data_utils.vnfd.find_software_version")
+    @patch("osm_lcm.lcm_utils.check_juju_bundle_existence")
+    async def test_update_change_vnfpkg_sw_version_not_changed(
+        self,
+        mock_juju_bundle,
+        mock_software_version,
+        mock_charm_upgrade,
+        mock_charm_hash,
+    ):
+        """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": []},
+        )
 
-            instance = self.my_ns
-            expected_result = False
+        self.db.set_one(
+            "vnfds_revisions",
+            q_filter={"_id": vnfd_id + ":1"},
+            update_dict={"_admin.revision": 1, "kdu": []},
+        )
 
-            with patch.object(instance, "fs", fs), patch(
-                "checksumdir.dirhash", mock_checksumdir.dirhash
-            ), patch("os.path.exists", mock_path.exists):
+        self.db.set_one("vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1})
 
-                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_charm_hash.return_value = True
+        mock_software_version.side_effect = ["1.0", "1.0"]
 
-            mock_reset()
+        task = asyncio.Future()
+        task.set_result(("COMPLETED", "some_output"))
+        mock_charm_upgrade.return_value = task
 
-        with self.subTest(i=4, t="Check charm hash, Hash has changed"):
-            # Testing method check_charm_hash_changed
+        instance = self.my_ns
+        fs = deepcopy(update_fs)
+        instance.fs = fs
 
-            current_path, target_path = "/tmp/charm1", "/tmp/charm2"
-            fs = Mock(autospec=True)
-            fs.path.__add__ = Mock()
-            fs.path.side_effect = ["/", "/", "/", "/"]
+        expected_operation_state = "COMPLETED"
+        expected_operation_error = ""
+        expected_vnfr_revision = 3
+        expected_ns_state = "INSTANTIATED"
+        expected_ns_operational_state = "running"
 
-            mock_path = Mock(autospec=True)
-            mock_path.exists.side_effect = [True, True]
+        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(
+            f"{vnfd_id}:1/hackfest_3charmed_vnfd/charms/simple",
+            f"{vnfd_id}:3/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)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed")
+    @patch(
+        "osm_lcm.ns.NsLcm._ns_charm_upgrade", new_callable=asynctest.Mock(autospec=True)
+    )
+    @patch("osm_lcm.data_utils.vnfd.find_software_version")
+    @patch("osm_lcm.lcm_utils.check_juju_bundle_existence")
+    async def test_update_change_vnfpkg_vnfd_revision_not_changed(
+        self,
+        mock_juju_bundle,
+        mock_software_version,
+        mock_charm_upgrade,
+        mock_charm_hash,
+    ):
+        """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_checksumdir = Mock(autospec=True)
-            mock_checksumdir.dirhash.side_effect = ["hash_value", "another_hash_value"]
+        mock_charm_hash.return_value = True
 
-            instance = self.my_ns
-            expected_result = True
+        task = asyncio.Future()
+        task.set_result(("COMPLETED", "some_output"))
+        mock_charm_upgrade.return_value = task
 
-            with patch.object(instance, "fs", fs), patch(
-                "checksumdir.dirhash", mock_checksumdir.dirhash
-            ), patch("os.path.exists", mock_path.exists):
+        instance = self.my_ns
 
-                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)
+        expected_operation_state = "COMPLETED"
+        expected_operation_error = ""
+        expected_vnfr_revision = 1
+        expected_ns_state = "INSTANTIATED"
+        expected_ns_operational_state = "running"
 
-            mock_reset()
+        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()
+        update_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)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed")
+    @patch(
+        "osm_lcm.ns.NsLcm._ns_charm_upgrade", new_callable=asynctest.Mock(autospec=True)
+    )
+    @patch("osm_lcm.data_utils.vnfd.find_software_version")
+    @patch("osm_lcm.lcm_utils.check_juju_bundle_existence")
+    async def test_update_change_vnfpkg_charm_is_not_changed(
+        self,
+        mock_juju_bundle,
+        mock_software_version,
+        mock_charm_upgrade,
+        mock_charm_hash,
+    ):
+        """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, "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.return_value = False
+        mock_software_version.side_effect = ["1.0", "1.0"]
+
+        instance = self.my_ns
+        fs = deepcopy(update_fs)
+        instance.fs = fs
+        expected_operation_state = "COMPLETED"
+        expected_operation_error = ""
+        expected_vnfr_revision = 3
+        expected_ns_state = "INSTANTIATED"
+        expected_ns_operational_state = "running"
+
+        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(
+            f"{vnfd_id}:1/hackfest_3charmed_vnfd/charms/simple",
+            f"{vnfd_id}:3/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)
+
+    @patch("osm_lcm.lcm_utils.check_juju_bundle_existence")
+    @patch("osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed")
+    @patch(
+        "osm_lcm.ns.NsLcm._ns_charm_upgrade", new_callable=asynctest.Mock(autospec=True)
+    )
+    @patch("osm_lcm.lcm_utils.get_charm_artifact_path")
+    async def test_update_change_vnfpkg_sw_version_changed(
+        self, mock_charm_artifact, mock_charm_upgrade, mock_charm_hash, mock_juju_bundle
+    ):
+        """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", "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.return_value = False
 
-        with self.subTest(i=5, t="Check charm hash, Charm path does not exists"):
-            # Testing method check_charm_hash_changed
+        mock_charm_artifact.side_effect = [
+            f"{vnfd_id}:1/hackfest_3charmed_vnfd/charms/simple",
+            f"{vnfd_id}:3/hackfest_3charmed_vnfd/charms/simple",
+        ]
 
-            current_path, target_path = "/tmp/charm1", "/tmp/charm2"
-            fs = Mock(autospec=True)
-            fs.path.__add__ = Mock()
-            fs.path.side_effect = ["/", "/", "/", "/"]
+        instance = self.my_ns
+        fs = deepcopy(update_fs)
+        instance.fs = fs
+        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"
+
+        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)
+
+    @patch("osm_lcm.lcm_utils.check_juju_bundle_existence")
+    @patch("osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed")
+    @patch(
+        "osm_lcm.ns.NsLcm._ns_charm_upgrade", new_callable=asynctest.Mock(autospec=True)
+    )
+    @patch("osm_lcm.data_utils.vnfd.find_software_version")
+    async def test_update_change_vnfpkg_juju_bundle_exists(
+        self,
+        mock_software_version,
+        mock_charm_upgrade,
+        mock_charm_hash,
+        mock_juju_bundle,
+    ):
+        """Update type: CHANGE_VNFPKG, latest_vnfd revision changed
+        Charm package exists, sw-version not changed, juju-bundle exists"""
+        # Upgrade is not allowed with juju bundles, this will cause TypeError
+        self.db.set_one(
+            "vnfds",
+            q_filter={"_id": vnfd_id},
+            update_dict={
+                "_admin.revision": 5,
+                "software-version": "1.0",
+                "kdu": [{"kdu_name": "native-kdu", "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": [{"kdu_name": "native-kdu", "juju-bundle": "stable/native-kdu"}],
+            },
+        )
+        self.db.set_one(
+            "nsrs",
+            q_filter={"_id": nsr_id},
+            update_dict={
+                "_admin.deployed.VCA.0.kdu_name": "native-kdu",
+            },
+        )
+        self.db.set_one("vnfrs", q_filter={"_id": vnfr_id}, update_dict={"revision": 1})
+
+        mock_charm_hash.side_effect = [True]
+        mock_software_version.side_effect = ["1.0", "1.0"]
+        mock_juju_bundle.return_value = True
+        instance = self.my_ns
+        fs = deepcopy(update_fs)
+        instance.fs = fs
+
+        expected_vnfr_revision = 1
+        expected_ns_state = "INSTANTIATED"
+        expected_ns_operational_state = "running"
+
+        await instance.update(nsr_id, nslcmop_id)
+        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_upgrade.assert_not_called()
+        mock_charm_hash.assert_not_called()
+        self.assertEqual(return_ns_state, expected_ns_state)
+        self.assertEqual(return_ns_operational_state, expected_ns_operational_state)
+        self.assertEqual(return_vnfr_revision, expected_vnfr_revision)
+
+    @patch("osm_lcm.lcm_utils.LcmBase.check_charm_hash_changed")
+    @patch(
+        "osm_lcm.ns.NsLcm._ns_charm_upgrade", new_callable=asynctest.Mock(autospec=True)
+    )
+    async def test_update_change_vnfpkg_charm_upgrade_failed(
+        self, mock_charm_upgrade, mock_charm_hash
+    ):
+        """ "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_path = Mock(autospec=True)
-            mock_path.exists.side_effect = [True, False]
+        mock_charm_hash.return_value = True
 
-            mock_checksumdir = Mock(autospec=True)
-            mock_checksumdir.dirhash.side_effect = ["hash_value", "hash_value"]
+        task = asyncio.Future()
+        task.set_result(("FAILED", "some_error"))
+        mock_charm_upgrade.return_value = task
 
-            instance = self.my_ns
+        instance = self.my_ns
+        fs = deepcopy(update_fs)
+        instance.fs = fs
+        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(
-                "checksumdir.dirhash", mock_checksumdir.dirhash
-            ), patch("os.path.exists", mock_path.exists):
+        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)
+
+    def test_ns_update_find_sw_version_vnfd_not_includes(self):
+        """Find software version, VNFD does not have software version"""
+
+        db_vnfd = self.db.get_one("vnfds", {"_id": vnfd_id})
+        expected_result = "1.0"
+        result = find_software_version(db_vnfd)
+        self.assertEqual(result, expected_result, "Default sw version should be 1.0")
+
+    def test_ns_update_find_sw_version_vnfd_includes(self):
+        """Find software version, VNFD includes software version"""
+
+        db_vnfd = self.db.get_one("vnfds", {"_id": vnfd_id})
+        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")
+
+    @patch("os.path.exists")
+    @patch("osm_lcm.lcm_utils.LcmBase.compare_charmdir_hash")
+    @patch("osm_lcm.lcm_utils.LcmBase.compare_charm_hash")
+    def test_ns_update_check_charm_hash_not_changed(
+        self, mock_compare_charm_hash, mock_compare_charmdir_hash, mock_path_exists
+    ):
+        """Check charm hash, Hash did not change"""
 
-                with self.assertRaises(LcmException):
+        current_path, target_path = "/tmp/charm1", "/tmp/charm1"
 
-                    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)
+        fs = Mock()
+        fs.path.__add__ = Mock()
+        fs.path.side_effect = [current_path, target_path]
+        fs.path.__add__.side_effect = [current_path, target_path]
 
-            mock_reset()
+        mock_path_exists.side_effect = [True, True]
 
-        with self.subTest(i=6, t="Check juju bundle existence"):
-            # Testing method check_juju_bundle_existence
+        mock_compare_charmdir_hash.return_value = callable(False)
+        mock_compare_charm_hash.return_value = callable(False)
 
-            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"}
-            )
+        instance = self.my_ns
+        instance.fs = fs
+        expected_result = False
 
-            expected_result = None
-            result = check_juju_bundle_existence(test_vnfd1)
-            self.assertEqual(result, expected_result, "Wrong juju bundle name")
+        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_compare_charmdir_hash.call_count, 1)
+        self.assertEqual(mock_compare_charm_hash.call_count, 0)
 
-            expected_result = "stable/native-kdu"
-            result = check_juju_bundle_existence(test_vnfd2)
-            self.assertEqual(result, expected_result, "Wrong juju bundle name")
+    @patch("os.path.exists")
+    @patch("osm_lcm.lcm_utils.LcmBase.compare_charmdir_hash")
+    @patch("osm_lcm.lcm_utils.LcmBase.compare_charm_hash")
+    def test_ns_update_check_charm_hash_changed(
+        self, mock_compare_charm_hash, mock_compare_charmdir_hash, mock_path_exists
+    ):
+        """Check charm hash, Hash has changed"""
+
+        current_path, target_path = "/tmp/charm1", "/tmp/charm2"
+
+        fs = Mock()
+        fs.path.__add__ = Mock()
+        fs.path.side_effect = [current_path, target_path, current_path, target_path]
+        fs.path.__add__.side_effect = [
+            current_path,
+            target_path,
+            current_path,
+            target_path,
+        ]
 
-        with self.subTest(i=7, t="Check charm artifacts"):
-            # Testing method check_juju_bundle_existence
+        mock_path_exists.side_effect = [True, True]
+        mock_compare_charmdir_hash.return_value = callable(True)
+        mock_compare_charm_hash.return_value = callable(True)
+
+        instance = self.my_ns
+        instance.fs = fs
+        expected_result = True
+
+        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_compare_charmdir_hash.call_count, 1)
+        self.assertEqual(mock_compare_charm_hash.call_count, 0)
+
+    @patch("os.path.exists")
+    @patch("osm_lcm.lcm_utils.LcmBase.compare_charmdir_hash")
+    @patch("osm_lcm.lcm_utils.LcmBase.compare_charm_hash")
+    def test_ns_update_check_no_charm_path(
+        self, mock_compare_charm_hash, mock_compare_charmdir_hash, mock_path_exists
+    ):
+        """Check charm hash, Charm path does not exist"""
+
+        current_path, target_path = "/tmp/charm1", "/tmp/charm2"
+
+        fs = Mock()
+        fs.path.__add__ = Mock()
+        fs.path.side_effect = [current_path, target_path, current_path, target_path]
+        fs.path.__add__.side_effect = [
+            current_path,
+            target_path,
+            current_path,
+            target_path,
+        ]
 
-            base_folder = {
-                "folder": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77",
-                "pkg-dir": "hackfest_3charmed_vnfd",
-            }
-            charm_name = "simple"
-            charm_type = "lxc_proxy_charm"
-            revision = 3
+        mock_path_exists.side_effect = [True, False]
 
-            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")
+        mock_compare_charmdir_hash.return_value = callable(False)
+        mock_compare_charm_hash.return_value = callable(False)
+        instance = self.my_ns
+        instance.fs = fs
 
-            # SOL004 packages
-            base_folder = {
-                "folder": "7637bcf8-cf14-42dc-ad70-c66fcf1e6e77",
-            }
-            charm_name = "basic"
-            charm_type = ""
-            revision = ""
+        with self.assertRaises(LcmException):
+            instance.check_charm_hash_changed(current_path, target_path)
+            self.assertEqual(mock_path_exists.call_count, 2)
+            self.assertEqual(mock_compare_charmdir_hash.call_count, 0)
+            self.assertEqual(mock_compare_charm_hash.call_count, 0)
 
-            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")
+    def test_ns_update_check_juju_bundle_existence_bundle_exists(self):
+        """Check juju bundle existence"""
+        test_vnfd2 = self.db.get_one(
+            "vnfds", {"_id": "d96b1cdf-5ad6-49f7-bf65-907ada989293"}
+        )
+        expected_result = "stable/native-kdu"
+        result = check_juju_bundle_existence(test_vnfd2)
+        self.assertEqual(result, expected_result, "Wrong juju bundle name")
+
+    def test_ns_update_check_juju_bundle_existence_bundle_does_not_exist(self):
+        """Check juju bundle existence"""
+        test_vnfd1 = self.db.get_one("vnfds", {"_id": vnfd_id})
+        expected_result = None
+        result = check_juju_bundle_existence(test_vnfd1)
+        self.assertEqual(result, expected_result, "Wrong juju bundle name")
+
+    def test_ns_update_check_juju_bundle_existence_empty_vnfd(self):
+        """Check juju bundle existence"""
+        test_vnfd1 = {}
+        expected_result = None
+        result = check_juju_bundle_existence(test_vnfd1)
+        self.assertEqual(result, expected_result, "Wrong juju bundle name")
+
+    def test_ns_update_check_juju_bundle_existence_invalid_vnfd(self):
+        """Check juju bundle existence"""
+        test_vnfd1 = [{"_id": vnfd_id}]
+        with self.assertRaises(AttributeError):
+            check_juju_bundle_existence(test_vnfd1)
+
+    def test_ns_update_check_juju_charm_artifacts_base_folder_wth_pkgdir(self):
+        """Check charm artifacts"""
+        base_folder = {
+            "folder": vnfd_id,
+            "pkg-dir": "hackfest_3charmed_vnfd",
+        }
+        charm_name = "simple"
+        charm_type = "lxc_proxy_charm"
+        revision = 3
+        expected_result = f"{vnfd_id}: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")
+
+    def test_ns_update_check_juju_charm_artifacts_base_folder_wthout_pkgdir(self):
+        """Check charm artifacts, SOL004 packages"""
+        base_folder = {
+            "folder": vnfd_id,
+        }
+        charm_name = "basic"
+        charm_type, revision = "", ""
+        expected_result = f"{vnfd_id}/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__":