Feature 10239: Distributed VCA

- Add vca_id in all calls that invoke libjuju. This is for being able to
talk to the default VCA or the VCA associated to the VIM
- Add store.py: Abstraction to talk to the database.
  - DBMongoStore: Use the db from common to talk to the database
  - MotorStore: Use motor, an asynchronous mongodb client to talk to the
database
- Add vca/connection.py: Represents the data needed to connect the VCA
- Add EnvironConfig in config.py: Class to get the environment config,
and avoid LCM from passing that

Change-Id: I28625e0c56ce408114022c83d4b7cacbb649434c
Signed-off-by: David Garcia <david.garcia@canonical.com>
diff --git a/n2vc/tests/unit/test_config.py b/n2vc/tests/unit/test_config.py
new file mode 100644
index 0000000..9a4af07
--- /dev/null
+++ b/n2vc/tests/unit/test_config.py
@@ -0,0 +1,58 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+
+from unittest import TestCase
+from unittest.mock import patch
+
+
+from n2vc.config import EnvironConfig, ModelConfig, MODEL_CONFIG_KEYS
+
+
+def generate_os_environ_dict(config, prefix):
+    return {f"{prefix}{k.upper()}": v for k, v in config.items()}
+
+
+class TestEnvironConfig(TestCase):
+    def setUp(self):
+        self.config = {"host": "1.2.3.4", "port": "17070", "k8s_cloud": "k8s"}
+
+    @patch("os.environ.items")
+    def test_environ_config_lcm(self, mock_environ_items):
+        envs = generate_os_environ_dict(self.config, "OSMLCM_VCA_")
+        envs["not_valid_env"] = "something"
+        mock_environ_items.return_value = envs.items()
+        config = EnvironConfig()
+        self.assertEqual(config, self.config)
+
+    @patch("os.environ.items")
+    def test_environ_config_mon(self, mock_environ_items):
+        envs = generate_os_environ_dict(self.config, "OSMMON_VCA_")
+        envs["not_valid_env"] = "something"
+        mock_environ_items.return_value = envs.items()
+        config = EnvironConfig()
+        self.assertEqual(config, self.config)
+
+
+class TestModelConfig(TestCase):
+    def setUp(self):
+        self.config = {
+            f'model_config_{model_key.replace("-", "_")}': "somevalue"
+            for model_key in MODEL_CONFIG_KEYS
+        }
+        self.config["model_config_invalid"] = "something"
+        self.model_config = {model_key: "somevalue" for model_key in MODEL_CONFIG_KEYS}
+
+    def test_model_config(self):
+        model_config = ModelConfig(self.config)
+        self.assertEqual(model_config, self.model_config)
diff --git a/n2vc/tests/unit/test_connection.py b/n2vc/tests/unit/test_connection.py
new file mode 100644
index 0000000..c7f0bb4
--- /dev/null
+++ b/n2vc/tests/unit/test_connection.py
@@ -0,0 +1,68 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+
+import asyncio
+from unittest import TestCase
+from unittest.mock import Mock, patch
+
+
+from n2vc.tests.unit.utils import AsyncMock
+from n2vc.vca import connection
+
+
+class TestConnection(TestCase):
+    def setUp(self):
+        self.loop = asyncio.get_event_loop()
+        self.store = AsyncMock()
+
+    def test_load_from_store(self):
+        self.loop.run_until_complete(connection.get_connection(self.store, "vim_id"))
+
+        self.store.get_vca_connection_data.assert_called_once()
+
+    def test_cloud_properties(self):
+        conn = self.loop.run_until_complete(
+            connection.get_connection(self.store, "vim_id")
+        )
+        conn._data = Mock()
+        conn._data.lxd_cloud = "name"
+        conn._data.k8s_cloud = "name"
+        conn._data.lxd_credentials = "credential"
+        conn._data.k8s_credentials = "credential"
+
+        self.assertEqual(conn.lxd_cloud.name, "name")
+        self.assertEqual(conn.lxd_cloud.credential_name, "credential")
+        self.assertEqual(conn.k8s_cloud.name, "name")
+        self.assertEqual(conn.k8s_cloud.credential_name, "credential")
+
+    @patch("n2vc.vca.connection.EnvironConfig")
+    @patch("n2vc.vca.connection_data.base64_to_cacert")
+    def test_load_from_env(self, mock_base64_to_cacert, mock_env):
+        mock_base64_to_cacert.return_value = "cacert"
+        mock_env.return_value = {
+            "endpoints": "1.2.3.4:17070",
+            "user": "user",
+            "secret": "secret",
+            "cacert": "cacert",
+            "pubkey": "pubkey",
+            "cloud": "cloud",
+            "credentials": "credentials",
+            "k8s_cloud": "k8s_cloud",
+            "k8s_credentials": "k8s_credentials",
+            "model_config": {},
+            "api-proxy": "api_proxy",
+        }
+        self.store.get_vca_endpoints.return_value = ["1.2.3.5:17070"]
+        self.loop.run_until_complete(connection.get_connection(self.store))
+        self.store.get_vca_connection_data.assert_not_called()
diff --git a/n2vc/tests/unit/test_juju_watcher.py b/n2vc/tests/unit/test_juju_watcher.py
index d333b33..5f81274 100644
--- a/n2vc/tests/unit/test_juju_watcher.py
+++ b/n2vc/tests/unit/test_juju_watcher.py
@@ -45,6 +45,7 @@
     def test_model_watcher(self, allwatcher):
         tests = Deltas
         allwatcher.return_value = FakeWatcher()
+        n2vc = AsyncMock()
         for test in tests:
             with self.assertRaises(asyncio.TimeoutError):
                 allwatcher.return_value.delta_to_return = [test.delta]
@@ -55,12 +56,12 @@
                         test.filter.entity_type,
                         timeout=0,
                         db_dict={"something"},
-                        n2vc=self.n2vc,
+                        n2vc=n2vc,
+                        vca_id=None,
                     )
                 )
 
-            self.assertEqual(self.n2vc.last_written_values, test.db.data)
-            self.n2vc.last_written_values = None
+            n2vc.write_app_status_to_db.assert_called()
 
     @mock.patch("n2vc.juju_watcher.asyncio.wait")
     def test_wait_for(self, wait):
diff --git a/n2vc/tests/unit/test_k8s_juju_conn.py b/n2vc/tests/unit/test_k8s_juju_conn.py
index f454380..208c849 100644
--- a/n2vc/tests/unit/test_k8s_juju_conn.py
+++ b/n2vc/tests/unit/test_k8s_juju_conn.py
@@ -23,172 +23,58 @@
 from n2vc.exceptions import (
     MethodNotImplemented,
     K8sException,
-    N2VCBadArgumentsException,
 )
+from n2vc.vca.connection_data import ConnectionData
 
 
 class K8sJujuConnTestCase(asynctest.TestCase):
-    @asynctest.mock.patch("juju.controller.Controller.update_endpoints")
-    @asynctest.mock.patch("juju.client.connector.Connector.connect")
-    @asynctest.mock.patch("juju.controller.Controller.connection")
-    @asynctest.mock.patch("n2vc.k8s_juju_conn.base64_to_cacert")
     @asynctest.mock.patch("n2vc.k8s_juju_conn.Libjuju")
+    @asynctest.mock.patch("n2vc.k8s_juju_conn.MotorStore")
+    @asynctest.mock.patch("n2vc.k8s_juju_conn.get_connection")
+    @asynctest.mock.patch("n2vc.vca.connection_data.base64_to_cacert")
     def setUp(
         self,
-        mock_libjuju=None,
         mock_base64_to_cacert=None,
-        mock_connection=None,
-        mock_connect=None,
-        mock_update_endpoints=None,
+        mock_get_connection=None,
+        mock_store=None,
+        mock_libjuju=None,
     ):
         self.loop = asyncio.get_event_loop()
-        mock_libjuju.return_value = AsyncMock()
-        db = Mock()
-        vca_config = {
-            "secret": "secret",
-            "api_proxy": "api_proxy",
-            "cloud": "cloud",
-            "k8s_cloud": "k8s_cloud",
-            "user": "user",
-            "host": "1.1.1.1",
-            "port": 17070,
-            "ca_cert": "cacert",
-        }
-
+        self.db = Mock()
+        mock_base64_to_cacert.return_value = """
+    -----BEGIN CERTIFICATE-----
+    SOMECERT
+    -----END CERTIFICATE-----"""
+        mock_libjuju.return_value = Mock()
+        mock_store.return_value = AsyncMock()
+        mock_vca_connection = Mock()
+        mock_get_connection.return_value = mock_vca_connection
+        mock_vca_connection.data.return_value = ConnectionData(
+            **{
+                "endpoints": ["1.2.3.4:17070"],
+                "user": "user",
+                "secret": "secret",
+                "cacert": "cacert",
+                "pubkey": "pubkey",
+                "lxd-cloud": "cloud",
+                "lxd-credentials": "credentials",
+                "k8s-cloud": "k8s_cloud",
+                "k8s-credentials": "k8s_credentials",
+                "model-config": {},
+                "api-proxy": "api_proxy",
+            }
+        )
         logging.disable(logging.CRITICAL)
 
         self.k8s_juju_conn = K8sJujuConnector(
             fs=fslocal.FsLocal(),
-            db=db,
+            db=self.db,
             log=None,
             loop=self.loop,
-            vca_config=vca_config,
             on_update_db=None,
         )
-
-
-class K8sJujuConnInitSuccessTestCase(asynctest.TestCase):
-    def setUp(
-        self,
-    ):
-        logging.disable(logging.CRITICAL)
-
-    @asynctest.mock.patch("juju.controller.Controller.update_endpoints")
-    @asynctest.mock.patch("juju.client.connector.Connector.connect")
-    @asynctest.mock.patch("juju.controller.Controller.connection")
-    @asynctest.mock.patch("n2vc.k8s_juju_conn.base64_to_cacert")
-    @asynctest.mock.patch("n2vc.libjuju.Libjuju.__init__")
-    def test_success(
-        self,
-        mock_libjuju=None,
-        mock_base64_to_cacert=None,
-        mock_connection=None,
-        mock_connect=None,
-        mock_update_endpoints=None,
-    ):
-        mock_libjuju.return_value = None
-        loop = asyncio.get_event_loop()
-        log = logging.getLogger()
-        db = Mock()
-        vca_config = {
-            "secret": "secret",
-            "cloud": "cloud",
-            "k8s_cloud": "k8s_cloud",
-            "user": "user",
-            "host": "1.1.1.1",
-            "port": 17070,
-            "ca_cert": "cacert",
-        }
-        K8sJujuConnector(
-            fs=fslocal.FsLocal(),
-            db=db,
-            log=log,
-            loop=self.loop,
-            vca_config=vca_config,
-            on_update_db=None,
-        )
-
-        mock_libjuju.assert_called_once_with(
-            endpoint="1.1.1.1:17070",
-            api_proxy=None,  # Not needed for k8s charms
-            model_config={},
-            username="user",
-            password="secret",
-            cacert=mock_base64_to_cacert.return_value,
-            loop=loop,
-            log=log,
-            db=db,
-        )
-
-
-class K8sJujuConnectorInitFailureTestCase(asynctest.TestCase):
-    def setUp(
-        self,
-    ):
-        self.loop = asyncio.get_event_loop()
-        logging.disable(logging.CRITICAL)
-        self.vca_config = {
-            "secret": "secret",
-            "api_proxy": "api_proxy",
-            "cloud": "cloud",
-            "k8s_cloud": "k8s_cloud",
-            "user": "user",
-            "host": "1.1.1.1",
-            "port": 17070,
-            "ca_cert": "cacert",
-        }
-
-    def test_missing_vca_config_host(self):
-        db = Mock()
-        self.vca_config.pop("host")
-        with self.assertRaises(N2VCBadArgumentsException):
-            self.k8s_juju_conn = K8sJujuConnector(
-                fs=fslocal.FsLocal(),
-                db=db,
-                log=None,
-                loop=self.loop,
-                vca_config=self.vca_config,
-                on_update_db=None,
-            )
-
-    def test_missing_vca_config_user(self):
-        db = Mock()
-        self.vca_config.pop("user")
-        with self.assertRaises(N2VCBadArgumentsException):
-            self.k8s_juju_conn = K8sJujuConnector(
-                fs=fslocal.FsLocal(),
-                db=db,
-                log=None,
-                loop=self.loop,
-                vca_config=self.vca_config,
-                on_update_db=None,
-            )
-
-    def test_missing_vca_config_secret(self):
-        db = Mock()
-        self.vca_config.pop("secret")
-        with self.assertRaises(N2VCBadArgumentsException):
-            self.k8s_juju_conn = K8sJujuConnector(
-                fs=fslocal.FsLocal(),
-                db=db,
-                log=None,
-                loop=self.loop,
-                vca_config=self.vca_config,
-                on_update_db=None,
-            )
-
-    def test_missing_vca_config_ca_cert(self):
-        db = Mock()
-        self.vca_config.pop("ca_cert")
-        with self.assertRaises(N2VCBadArgumentsException):
-            self.k8s_juju_conn = K8sJujuConnector(
-                fs=fslocal.FsLocal(),
-                db=db,
-                log=None,
-                loop=self.loop,
-                vca_config=self.vca_config,
-                on_update_db=None,
-            )
+        self.k8s_juju_conn._store.get_vca_id.return_value = None
+        self.k8s_juju_conn.libjuju = Mock()
 
 
 @asynctest.mock.patch("n2vc.kubectl.Kubectl.get_default_storage_class")
@@ -344,11 +230,7 @@
             )
         )
         self.assertEqual(mock_chdir.call_count, 2)
-        self.k8s_juju_conn.libjuju.add_model.assert_called_once_with(
-            model_name=self.kdu_instance,
-            cloud_name=self.cluster_uuid,
-            credential_name="cred-{}".format(self.cluster_uuid),
-        )
+        self.k8s_juju_conn.libjuju.add_model.assert_called_once()
         self.k8s_juju_conn.libjuju.deploy.assert_called_once_with(
             "local:{}".format(self.local_bundle),
             model_name=self.kdu_instance,
@@ -368,11 +250,7 @@
                 timeout=1800,
             )
         )
-        self.k8s_juju_conn.libjuju.add_model.assert_called_once_with(
-            model_name=self.kdu_instance,
-            cloud_name=self.cluster_uuid,
-            credential_name="cred-{}".format(self.cluster_uuid),
-        )
+        self.k8s_juju_conn.libjuju.add_model.assert_called_once()
         self.k8s_juju_conn.libjuju.deploy.assert_called_once_with(
             self.cs_bundle,
             model_name=self.kdu_instance,
@@ -392,11 +270,7 @@
                 timeout=1800,
             )
         )
-        self.k8s_juju_conn.libjuju.add_model.assert_called_once_with(
-            model_name=self.kdu_instance,
-            cloud_name=self.cluster_uuid,
-            credential_name="cred-{}".format(self.cluster_uuid),
-        )
+        self.k8s_juju_conn.libjuju.add_model.assert_called_once()
         self.k8s_juju_conn.libjuju.deploy.assert_called_once_with(
             self.http_bundle,
             model_name=self.kdu_instance,
@@ -415,11 +289,7 @@
                 timeout=1800,
             )
         )
-        self.k8s_juju_conn.libjuju.add_model.assert_called_once_with(
-            model_name=self.kdu_instance,
-            cloud_name=self.cluster_uuid,
-            credential_name="cred-{}".format(self.cluster_uuid),
-        )
+        self.k8s_juju_conn.libjuju.add_model.assert_called_once()
         self.k8s_juju_conn.libjuju.deploy.assert_called_once_with(
             self.cs_bundle,
             model_name=self.kdu_instance,
@@ -458,11 +328,7 @@
                 timeout=1800,
             )
         )
-        self.k8s_juju_conn.libjuju.add_model.assert_called_once_with(
-            model_name=self.kdu_instance,
-            cloud_name=self.cluster_uuid,
-            credential_name="cred-{}".format(self.cluster_uuid),
-        )
+        self.k8s_juju_conn.libjuju.add_model.assert_called_once()
         self.k8s_juju_conn.libjuju.deploy.assert_called_once_with(
             self.cs_bundle,
             model_name=self.kdu_instance,
@@ -500,11 +366,7 @@
                     timeout=1800,
                 )
             )
-        self.k8s_juju_conn.libjuju.add_model.assert_called_once_with(
-            model_name=self.kdu_instance,
-            cloud_name=self.cluster_uuid,
-            credential_name="cred-{}".format(self.cluster_uuid),
-        )
+        self.k8s_juju_conn.libjuju.add_model.assert_called_once()
         self.k8s_juju_conn.libjuju.deploy.assert_called_once_with(
             "local:{}".format(self.local_bundle),
             model_name=self.kdu_instance,
diff --git a/n2vc/tests/unit/test_libjuju.py b/n2vc/tests/unit/test_libjuju.py
index 29bcb7b..fde6817 100644
--- a/n2vc/tests/unit/test_libjuju.py
+++ b/n2vc/tests/unit/test_libjuju.py
@@ -15,12 +15,12 @@
 import asyncio
 import asynctest
 import tempfile
-from unittest import mock
+from unittest.mock import Mock, patch
 import juju
 import kubernetes
 from juju.errors import JujuAPIError
 import logging
-from .utils import FakeN2VC, FakeMachine, FakeApplication
+from .utils import FakeMachine, FakeApplication
 from n2vc.libjuju import Libjuju
 from n2vc.exceptions import (
     JujuControllerFailedConnecting,
@@ -33,122 +33,46 @@
     JujuError,
 )
 from n2vc.k8s_juju_conn import generate_rbac_id
+from n2vc.tests.unit.utils import AsyncMock
+from n2vc.vca.connection import Connection
+from n2vc.vca.connection_data import ConnectionData
 
 
+cacert = """-----BEGIN CERTIFICATE-----
+SOMECERT
+-----END CERTIFICATE-----"""
+
+
+@asynctest.mock.patch("n2vc.libjuju.Controller")
 class LibjujuTestCase(asynctest.TestCase):
-    @asynctest.mock.patch("juju.controller.Controller.update_endpoints")
-    @asynctest.mock.patch("juju.client.connector.Connector.connect")
-    @asynctest.mock.patch("juju.controller.Controller.connection")
-    @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_api_endpoints_db")
+    @asynctest.mock.patch("n2vc.vca.connection_data.base64_to_cacert")
     def setUp(
         self,
-        mock__get_api_endpoints_db=None,
-        mock_connection=None,
-        mock_connect=None,
-        mock_update_endpoints=None,
+        mock_base64_to_cacert=None,
     ):
-        loop = asyncio.get_event_loop()
-        n2vc = FakeN2VC()
-        mock__get_api_endpoints_db.return_value = ["127.0.0.1:17070"]
-        endpoints = "127.0.0.1:17070"
-        username = "admin"
-        password = "secret"
-        cacert = """
-    -----BEGIN CERTIFICATE-----
-    SOMECERT
-    -----END CERTIFICATE-----"""
-        self.libjuju = Libjuju(
-            endpoints,
-            "192.168.0.155:17070",
-            username,
-            password,
-            cacert,
-            loop,
-            log=None,
-            db={"get_one": []},
-            n2vc=n2vc,
+        self.loop = asyncio.get_event_loop()
+        self.db = Mock()
+        mock_base64_to_cacert.return_value = cacert
+        Connection._load_vca_connection_data = Mock()
+        vca_connection = Connection(AsyncMock())
+        vca_connection._data = ConnectionData(
+            **{
+                "endpoints": ["1.2.3.4:17070"],
+                "user": "user",
+                "secret": "secret",
+                "cacert": "cacert",
+                "pubkey": "pubkey",
+                "lxd-cloud": "cloud",
+                "lxd-credentials": "credentials",
+                "k8s-cloud": "k8s_cloud",
+                "k8s-credentials": "k8s_credentials",
+                "model-config": {},
+                "api-proxy": "api_proxy",
+            }
         )
         logging.disable(logging.CRITICAL)
-        loop.run_until_complete(self.libjuju.disconnect())
-
-
-@asynctest.mock.patch("n2vc.libjuju.Libjuju._create_health_check_task")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju._update_api_endpoints_db")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_api_endpoints_db")
-class LibjujuInitTestCase(asynctest.TestCase):
-    def setUp(self):
-        self.loop = asyncio.get_event_loop()
-        self.n2vc = FakeN2VC()
-        self.endpoint = "192.168.100.100:17070"
-        self.username = "admin"
-        self.password = "secret"
-        self.cacert = """
-    -----BEGIN CERTIFICATE-----
-    SOMECERT
-    -----END CERTIFICATE-----"""
-
-    def test_endpoint_not_in_db(
-        self,
-        mock__get_api_endpoints_db,
-        mock_update_endpoints,
-        mock_create_health_check_task,
-    ):
-        mock__get_api_endpoints_db.return_value = ["another_ip"]
-        Libjuju(
-            self.endpoint,
-            "192.168.0.155:17070",
-            self.username,
-            self.password,
-            self.cacert,
-            self.loop,
-            log=None,
-            db={"get_one": []},
-            n2vc=self.n2vc,
-        )
-        mock_update_endpoints.assert_called_once_with([self.endpoint])
-        mock__get_api_endpoints_db.assert_called_once()
-
-    def test_endpoint_in_db(
-        self,
-        mock__get_api_endpoints_db,
-        mock_update_endpoints,
-        mock_create_health_check_task,
-    ):
-        mock__get_api_endpoints_db.return_value = [self.endpoint, "another_ip"]
-        Libjuju(
-            self.endpoint,
-            "192.168.0.155:17070",
-            self.username,
-            self.password,
-            self.cacert,
-            self.loop,
-            log=None,
-            db={"get_one": []},
-            n2vc=self.n2vc,
-        )
-        mock_update_endpoints.assert_not_called()
-        mock__get_api_endpoints_db.assert_called_once()
-
-    def test_no_db_endpoints(
-        self,
-        mock__get_api_endpoints_db,
-        mock_update_endpoints,
-        mock_create_health_check_task,
-    ):
-        mock__get_api_endpoints_db.return_value = None
-        Libjuju(
-            self.endpoint,
-            "192.168.0.155:17070",
-            self.username,
-            self.password,
-            self.cacert,
-            self.loop,
-            log=None,
-            db={"get_one": []},
-            n2vc=self.n2vc,
-        )
-        mock_update_endpoints.assert_called_once_with([self.endpoint])
-        mock__get_api_endpoints_db.assert_called_once()
+        self.libjuju = Libjuju(vca_connection, self.loop)
+        self.loop.run_until_complete(self.libjuju.disconnect())
 
 
 @asynctest.mock.patch("juju.controller.Controller.connect")
@@ -156,41 +80,34 @@
     "juju.controller.Controller.api_endpoints",
     new_callable=asynctest.CoroutineMock(return_value=["127.0.0.1:17070"]),
 )
-@asynctest.mock.patch("n2vc.libjuju.Libjuju._update_api_endpoints_db")
 class GetControllerTest(LibjujuTestCase):
     def setUp(self):
         super(GetControllerTest, self).setUp()
 
-    def test_diff_endpoint(
-        self, mock__update_api_endpoints_db, mock_api_endpoints, mock_connect
-    ):
+    def test_diff_endpoint(self, mock_api_endpoints, mock_connect):
         self.libjuju.endpoints = []
         controller = self.loop.run_until_complete(self.libjuju.get_controller())
-        mock__update_api_endpoints_db.assert_called_once_with(["127.0.0.1:17070"])
         self.assertIsInstance(controller, juju.controller.Controller)
 
     @asynctest.mock.patch("n2vc.libjuju.Libjuju.disconnect_controller")
     def test_exception(
         self,
         mock_disconnect_controller,
-        mock__update_api_endpoints_db,
         mock_api_endpoints,
         mock_connect,
     ):
         self.libjuju.endpoints = []
-        mock__update_api_endpoints_db.side_effect = Exception()
+
+        mock_connect.side_effect = Exception()
         controller = None
         with self.assertRaises(JujuControllerFailedConnecting):
             controller = self.loop.run_until_complete(self.libjuju.get_controller())
         self.assertIsNone(controller)
         mock_disconnect_controller.assert_called_once()
 
-    def test_same_endpoint_get_controller(
-        self, mock__update_api_endpoints_db, mock_api_endpoints, mock_connect
-    ):
+    def test_same_endpoint_get_controller(self, mock_api_endpoints, mock_connect):
         self.libjuju.endpoints = ["127.0.0.1:17070"]
         controller = self.loop.run_until_complete(self.libjuju.get_controller())
-        mock__update_api_endpoints_db.assert_not_called()
         self.assertIsInstance(controller, juju.controller.Controller)
 
 
@@ -231,9 +148,7 @@
         mock_model_exists.return_value = True
 
         # This should not raise an exception
-        self.loop.run_until_complete(
-            self.libjuju.add_model("existing_model", "cloud")
-        )
+        self.loop.run_until_complete(self.libjuju.add_model("existing_model", "cloud"))
 
         mock_disconnect_controller.assert_called()
 
@@ -251,7 +166,7 @@
         mock_get_controller.return_value = juju.controller.Controller()
 
         self.loop.run_until_complete(
-            self.libjuju.add_model("nonexisting_model", "cloud")
+            self.libjuju.add_model("nonexisting_model", Mock())
         )
 
         mock_add_model.assert_called_once()
@@ -1863,7 +1778,7 @@
         mock_configuration.key_file = None
         self.token = None
         self.cert_data = None
-        with mock.patch.object(self.libjuju.log, "debug") as mock_debug:
+        with patch.object(self.libjuju.log, "debug") as mock_debug:
             credential = self.libjuju.get_k8s_cloud_credential(
                 mock_configuration,
                 self.cert_data,
diff --git a/n2vc/tests/unit/test_n2vc_juju_conn.py b/n2vc/tests/unit/test_n2vc_juju_conn.py
index e5e26be..d89de3f 100644
--- a/n2vc/tests/unit/test_n2vc_juju_conn.py
+++ b/n2vc/tests/unit/test_n2vc_juju_conn.py
@@ -22,143 +22,145 @@
 from n2vc.n2vc_juju_conn import N2VCJujuConnector
 from osm_common import fslocal
 from n2vc.exceptions import (
-    JujuK8sProxycharmNotSupported,
     N2VCBadArgumentsException,
     N2VCException,
 )
+from n2vc.tests.unit.utils import AsyncMock
+from n2vc.vca.connection_data import ConnectionData
 
 
 class N2VCJujuConnTestCase(asynctest.TestCase):
-    @asynctest.mock.patch("n2vc.libjuju.Libjuju._create_health_check_task")
-    @asynctest.mock.patch("juju.controller.Controller.update_endpoints")
-    @asynctest.mock.patch("juju.client.connector.Connector.connect")
-    @asynctest.mock.patch("juju.controller.Controller.connection")
-    @asynctest.mock.patch("n2vc.libjuju.Libjuju._get_api_endpoints_db")
+    @asynctest.mock.patch("n2vc.n2vc_juju_conn.MotorStore")
+    @asynctest.mock.patch("n2vc.n2vc_juju_conn.get_connection")
+    @asynctest.mock.patch("n2vc.vca.connection_data.base64_to_cacert")
     def setUp(
         self,
-        mock__get_api_endpoints_db=None,
-        mock_connection=None,
-        mock_connect=None,
-        mock_update_endpoints=None,
-        mock__create_health_check_task=None,
+        mock_base64_to_cacert=None,
+        mock_get_connection=None,
+        mock_store=None,
     ):
-        mock__get_api_endpoints_db.return_value = ["2.2.2.2:17070"]
-        loop = asyncio.get_event_loop()
-        db = {}
-        vca_config = {
-            "secret": "secret",
-            "api_proxy": "api_proxy",
-            "cloud": "cloud",
-            "k8s_cloud": "k8s_cloud",
-        }
-
+        self.loop = asyncio.get_event_loop()
+        self.db = Mock()
+        mock_base64_to_cacert.return_value = """
+    -----BEGIN CERTIFICATE-----
+    SOMECERT
+    -----END CERTIFICATE-----"""
+        mock_store.return_value = AsyncMock()
+        mock_vca_connection = Mock()
+        mock_get_connection.return_value = mock_vca_connection
+        mock_vca_connection.data.return_value = ConnectionData(
+            **{
+                "endpoints": ["1.2.3.4:17070"],
+                "user": "user",
+                "secret": "secret",
+                "cacert": "cacert",
+                "pubkey": "pubkey",
+                "lxd-cloud": "cloud",
+                "lxd-credentials": "credentials",
+                "k8s-cloud": "k8s_cloud",
+                "k8s-credentials": "k8s_credentials",
+                "model-config": {},
+                "api-proxy": "api_proxy",
+            }
+        )
         logging.disable(logging.CRITICAL)
 
         N2VCJujuConnector.get_public_key = Mock()
         self.n2vc = N2VCJujuConnector(
-            db=db,
+            db=self.db,
             fs=fslocal.FsLocal(),
             log=None,
-            loop=loop,
-            url="2.2.2.2:17070",
-            username="admin",
-            vca_config=vca_config,
+            loop=self.loop,
             on_update_db=None,
         )
         N2VCJujuConnector.get_public_key.assert_not_called()
+        self.n2vc.libjuju = Mock()
 
 
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_metrics")
 class GetMetricssTest(N2VCJujuConnTestCase):
     def setUp(self):
         super(GetMetricssTest, self).setUp()
+        self.n2vc.libjuju.get_metrics = AsyncMock()
 
-    def test_success(self, mock_get_metrics):
+    def test_success(self):
         _ = self.loop.run_until_complete(self.n2vc.get_metrics("model", "application"))
-        mock_get_metrics.assert_called_once()
+        self.n2vc.libjuju.get_metrics.assert_called_once()
 
-    def test_except(self, mock_get_metrics):
-        mock_get_metrics.side_effect = Exception()
+    def test_except(self):
+        self.n2vc.libjuju.get_metrics.side_effect = Exception()
         with self.assertRaises(Exception):
             _ = self.loop.run_until_complete(
                 self.n2vc.get_metrics("model", "application")
             )
-        mock_get_metrics.assert_called_once()
+        self.n2vc.libjuju.get_metrics.assert_called_once()
 
 
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_controller")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_model")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_executed_actions")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_actions")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.get_application_configs")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju._get_application")
 class UpdateVcaStatusTest(N2VCJujuConnTestCase):
     def setUp(self):
         super(UpdateVcaStatusTest, self).setUp()
+        self.n2vc.libjuju.get_controller = AsyncMock()
+        self.n2vc.libjuju.get_model = AsyncMock()
+        self.n2vc.libjuju.get_executed_actions = AsyncMock()
+        self.n2vc.libjuju.get_actions = AsyncMock()
+        self.n2vc.libjuju.get_application_configs = AsyncMock()
+        self.n2vc.libjuju._get_application = AsyncMock()
 
     def test_success(
         self,
-        mock_get_application,
-        mock_get_application_configs,
-        mock_get_actions,
-        mock_get_executed_actions,
-        mock_get_model,
-        mock_get_controller,
     ):
-        self.loop.run_until_complete(self.n2vc.update_vca_status(
-            {"model": {"applications": {"app": {"actions": {}}}}}))
-        mock_get_executed_actions.assert_called_once()
-        mock_get_actions.assert_called_once()
-        mock_get_application_configs.assert_called_once()
+        self.loop.run_until_complete(
+            self.n2vc.update_vca_status(
+                {"model": {"applications": {"app": {"actions": {}}}}}
+            )
+        )
+        self.n2vc.libjuju.get_executed_actions.assert_called_once()
+        self.n2vc.libjuju.get_actions.assert_called_once()
+        self.n2vc.libjuju.get_application_configs.assert_called_once()
 
-    def test_exception(
-        self,
-        mock_get_application,
-        mock_get_application_configs,
-        mock_get_actions,
-        mock_get_executed_actions,
-        mock_get_model,
-        mock_get_controller,
-    ):
-        mock_get_model.return_value = None
-        mock_get_executed_actions.side_effect = Exception()
+    def test_exception(self):
+        self.n2vc.libjuju.get_model.return_value = None
+        self.n2vc.libjuju.get_executed_actions.side_effect = Exception()
         with self.assertRaises(Exception):
-            self.loop.run_until_complete(self.n2vc.update_vca_status(
-                {"model": {"applications": {"app": {"actions": {}}}}}))
-            mock_get_executed_actions.assert_not_called()
-            mock_get_actions.assert_not_called_once()
-            mock_get_application_configs.assert_not_called_once()
+            self.loop.run_until_complete(
+                self.n2vc.update_vca_status(
+                    {"model": {"applications": {"app": {"actions": {}}}}}
+                )
+            )
+            self.n2vc.libjuju.get_executed_actions.assert_not_called()
+            self.n2vc.libjuju.get_actions.assert_not_called_once()
+            self.n2vc.libjuju.get_application_configs.assert_not_called_once()
 
 
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.model_exists")
 @asynctest.mock.patch("osm_common.fslocal.FsLocal.file_exists")
 @asynctest.mock.patch(
     "osm_common.fslocal.FsLocal.path", new_callable=asynctest.PropertyMock, create=True
 )
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.deploy_charm")
-@asynctest.mock.patch("n2vc.libjuju.Libjuju.add_model")
 class K8sProxyCharmsTest(N2VCJujuConnTestCase):
     def setUp(self):
         super(K8sProxyCharmsTest, self).setUp()
+        self.n2vc.libjuju.model_exists = AsyncMock()
+        self.n2vc.libjuju.add_model = AsyncMock()
+        self.n2vc.libjuju.deploy_charm = AsyncMock()
+        self.n2vc.libjuju.model_exists.return_value = False
 
     def test_success(
-        self, mock_add_model, mock_deploy_charm, mock_path, mock_file_exists, mock_model_exists
+        self,
+        mock_path,
+        mock_file_exists,
     ):
-        mock_model_exists.return_value = None
         mock_file_exists.return_value = True
         mock_path.return_value = "/path"
         ee_id = self.loop.run_until_complete(
             self.n2vc.install_k8s_proxy_charm(
-                "charm", "nsi-id.ns-id.vnf-id.vdu", "////path/", {},
+                "charm",
+                "nsi-id.ns-id.vnf-id.vdu",
+                "////path/",
+                {},
             )
         )
 
-        mock_add_model.assert_called_once_with(
-            "ns-id-k8s",
-            cloud_name=self.n2vc.k8s_cloud,
-            credential_name=self.n2vc.k8s_cloud
-        )
-        mock_deploy_charm.assert_called_once_with(
+        self.n2vc.libjuju.add_model.assert_called_once()
+        self.n2vc.libjuju.deploy_charm.assert_called_once_with(
             model_name="ns-id-k8s",
             application_name="app-vnf-vnf-id-vdu-vdu",
             path="/path/path/",
@@ -170,74 +172,70 @@
         )
         self.assertEqual(ee_id, "ns-id-k8s.app-vnf-vnf-id-vdu-vdu.k8s")
 
-    @asynctest.mock.patch(
-        "n2vc.n2vc_juju_conn.N2VCJujuConnector.k8s_cloud",
-        new_callable=asynctest.PropertyMock,
-        create=True,
-    )
-    def test_no_k8s_cloud(
+    def test_no_artifact_path(
         self,
-        mock_k8s_cloud,
-        mock_add_model,
-        mock_deploy_charm,
         mock_path,
         mock_file_exists,
-        mock_model_exists,
-    ):
-        mock_k8s_cloud.return_value = None
-        with self.assertRaises(JujuK8sProxycharmNotSupported):
-            ee_id = self.loop.run_until_complete(
-                self.n2vc.install_k8s_proxy_charm(
-                    "charm", "nsi-id.ns-id.vnf-id.vdu", "/path/", {},
-                )
-            )
-            self.assertIsNone(ee_id)
-
-    def test_no_artifact_path(
-        self, mock_add_model, mock_deploy_charm, mock_path, mock_file_exists, mock_model_exists,
     ):
         with self.assertRaises(N2VCBadArgumentsException):
             ee_id = self.loop.run_until_complete(
                 self.n2vc.install_k8s_proxy_charm(
-                    "charm", "nsi-id.ns-id.vnf-id.vdu", "", {},
+                    "charm",
+                    "nsi-id.ns-id.vnf-id.vdu",
+                    "",
+                    {},
                 )
             )
             self.assertIsNone(ee_id)
 
     def test_no_db(
-        self, mock_add_model, mock_deploy_charm, mock_path, mock_file_exists, mock_model_exists,
+        self,
+        mock_path,
+        mock_file_exists,
     ):
         with self.assertRaises(N2VCBadArgumentsException):
             ee_id = self.loop.run_until_complete(
                 self.n2vc.install_k8s_proxy_charm(
-                    "charm", "nsi-id.ns-id.vnf-id.vdu", "/path/", None,
+                    "charm",
+                    "nsi-id.ns-id.vnf-id.vdu",
+                    "/path/",
+                    None,
                 )
             )
             self.assertIsNone(ee_id)
 
     def test_file_not_exists(
-        self, mock_add_model, mock_deploy_charm, mock_path, mock_file_exists, mock_model_exists,
+        self,
+        mock_path,
+        mock_file_exists,
     ):
         mock_file_exists.return_value = False
         with self.assertRaises(N2VCBadArgumentsException):
             ee_id = self.loop.run_until_complete(
                 self.n2vc.install_k8s_proxy_charm(
-                    "charm", "nsi-id.ns-id.vnf-id.vdu", "/path/", {},
+                    "charm",
+                    "nsi-id.ns-id.vnf-id.vdu",
+                    "/path/",
+                    {},
                 )
             )
             self.assertIsNone(ee_id)
 
     def test_exception(
-        self, mock_add_model, mock_deploy_charm, mock_path, mock_file_exists, mock_model_exists,
+        self,
+        mock_path,
+        mock_file_exists,
     ):
-        mock_model_exists.return_value = None
         mock_file_exists.return_value = True
         mock_path.return_value = "/path"
-        mock_deploy_charm.side_effect = Exception()
+        self.n2vc.libjuju.deploy_charm.side_effect = Exception()
         with self.assertRaises(N2VCException):
             ee_id = self.loop.run_until_complete(
                 self.n2vc.install_k8s_proxy_charm(
-                    "charm", "nsi-id.ns-id.vnf-id.vdu", "path/", {},
+                    "charm",
+                    "nsi-id.ns-id.vnf-id.vdu",
+                    "path/",
+                    {},
                 )
             )
             self.assertIsNone(ee_id)
diff --git a/n2vc/tests/unit/test_store.py b/n2vc/tests/unit/test_store.py
new file mode 100644
index 0000000..c7aa2d6
--- /dev/null
+++ b/n2vc/tests/unit/test_store.py
@@ -0,0 +1,288 @@
+# Copyright 2020 Canonical Ltd.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+#     Unless required by applicable law or agreed to in writing, software
+#     distributed under the License is distributed on an "AS IS" BASIS,
+#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#     See the License for the specific language governing permissions and
+#     limitations under the License.
+
+import asyncio
+from base64 import b64decode
+from unittest import TestCase
+from unittest.mock import Mock, patch
+
+
+from n2vc.store import DbMongoStore, MotorStore
+from n2vc.vca.connection_data import ConnectionData
+from n2vc.tests.unit.utils import AsyncMock
+from osm_common.dbmongo import DbException
+
+
+class TestDbMongoStore(TestCase):
+    def setUp(self):
+        self.store = DbMongoStore(Mock())
+        self.loop = asyncio.get_event_loop()
+
+    @patch("n2vc.vca.connection_data.base64_to_cacert")
+    def test_get_vca_connection_data(self, mock_base64_to_cacert):
+        mock_base64_to_cacert.return_value = "cacert"
+        conn_data = {
+            "endpoints": ["1.2.3.4:17070"],
+            "user": "admin",
+            "secret": "1234",
+            "cacert": "cacert",
+            "pubkey": "pubkey",
+            "lxd-cloud": "lxd-cloud",
+            "lxd-credentials": "lxd-credentials",
+            "k8s-cloud": "k8s-cloud",
+            "k8s-credentials": "k8s-credentials",
+            "model-config": {},
+            "api-proxy": None,
+        }
+        db_get_one = conn_data.copy()
+        db_get_one.update({"schema_version": "1.1", "_id": "id"})
+        self.store.db.get_one.return_value = db_get_one
+        connection_data = self.loop.run_until_complete(
+            self.store.get_vca_connection_data("vca_id")
+        )
+        self.assertTrue(
+            all(
+                connection_data.__dict__[k.replace("-", "_")] == v
+                for k, v in conn_data.items()
+            )
+        )
+
+    def test_update_vca_endpoints(self):
+        endpoints = ["1.2.3.4:17070"]
+        self.store.db.get_one.side_effect = [None, {"api_endpoints": []}]
+        self.store.db.create.side_effect = DbException("already exists")
+        self.loop.run_until_complete(self.store.update_vca_endpoints(endpoints))
+        self.assertEqual(self.store.db.get_one.call_count, 2)
+        Mock()
+        self.store.db.set_one.assert_called_once_with(
+            "vca", {"_id": "juju"}, {"api_endpoints": endpoints}
+        )
+
+    def test_update_vca_endpoints_exception(self):
+        endpoints = ["1.2.3.4:17070"]
+        self.store.db.get_one.side_effect = [None, None]
+        self.store.db.create.side_effect = DbException("already exists")
+        with self.assertRaises(DbException):
+            self.loop.run_until_complete(self.store.update_vca_endpoints(endpoints))
+        self.assertEqual(self.store.db.get_one.call_count, 2)
+        self.store.db.set_one.assert_not_called()
+
+    def test_update_vca_endpoints_with_vca_id(self):
+        endpoints = ["1.2.3.4:17070"]
+        self.store.db.get_one.return_value = {}
+        self.loop.run_until_complete(
+            self.store.update_vca_endpoints(endpoints, "vca_id")
+        )
+        self.store.db.get_one.assert_called_once_with("vca", q_filter={"_id": "vca_id"})
+        self.store.db.replace.assert_called_once_with(
+            "vca", "vca_id", {"endpoints": endpoints}
+        )
+
+    def test_get_vca_endpoints(self):
+        endpoints = ["1.2.3.4:17070"]
+        db_data = {"api_endpoints": endpoints}
+        db_returns = [db_data, None]
+        expected_returns = [endpoints, []]
+        returns = []
+        self.store._get_juju_info = Mock()
+        self.store._get_juju_info.side_effect = db_returns
+        for _ in range(len(db_returns)):
+            e = self.loop.run_until_complete(self.store.get_vca_endpoints())
+            returns.append(e)
+        self.assertEqual(expected_returns, returns)
+
+    @patch("n2vc.vca.connection_data.base64_to_cacert")
+    def test_get_vca_endpoints_with_vca_id(self, mock_base64_to_cacert):
+        expected_endpoints = ["1.2.3.4:17070"]
+        mock_base64_to_cacert.return_value = "cacert"
+        self.store.get_vca_connection_data = Mock()
+        self.store.get_vca_connection_data.return_value = ConnectionData(
+            **{
+                "endpoints": expected_endpoints,
+                "user": "admin",
+                "secret": "1234",
+                "cacert": "cacert",
+            }
+        )
+        endpoints = self.loop.run_until_complete(self.store.get_vca_endpoints("vca_id"))
+        self.store.get_vca_connection_data.assert_called_with("vca_id")
+        self.assertEqual(expected_endpoints, endpoints)
+
+    def test_get_vca_id(self):
+        self.assertIsNone(self.loop.run_until_complete(self.store.get_vca_id()))
+
+    def test_get_vca_id_with_vim_id(self):
+        self.store.db.get_one.return_value = {"vca": "vca_id"}
+        vca_id = self.loop.run_until_complete(self.store.get_vca_id("vim_id"))
+        self.store.db.get_one.assert_called_once_with(
+            "vim_accounts", q_filter={"_id": "vim_id"}, fail_on_empty=False
+        )
+        self.assertEqual(vca_id, "vca_id")
+
+
+class TestMotorStore(TestCase):
+    def setUp(self):
+        self.store = MotorStore("uri")
+        self.vca_collection = Mock()
+        self.vca_collection.find_one = AsyncMock()
+        self.vca_collection.insert_one = AsyncMock()
+        self.vca_collection.replace_one = AsyncMock()
+        self.admin_collection = Mock()
+        self.admin_collection.find_one = AsyncMock()
+        self.admin_collection.insert_one = AsyncMock()
+        self.admin_collection.replace_one = AsyncMock()
+        self.vim_accounts_collection = Mock()
+        self.vim_accounts_collection.find_one = AsyncMock()
+        self.store._client = {
+            "osm": {
+                "vca": self.vca_collection,
+                "admin": self.admin_collection,
+                "vim_accounts": self.vim_accounts_collection,
+            }
+        }
+        self.store._config = {"database_commonkey": "osm"}
+        # self.store.decrypt_fields = Mock()
+        self.loop = asyncio.get_event_loop()
+
+    @patch("n2vc.vca.connection_data.base64_to_cacert")
+    def test_get_vca_connection_data(self, mock_base64_to_cacert):
+        mock_base64_to_cacert.return_value = "cacert"
+        conn_data = {
+            "endpoints": ["1.2.3.4:17070"],
+            "user": "admin",
+            "secret": "1234",
+            "cacert": "cacert",
+            "pubkey": "pubkey",
+            "lxd-cloud": "lxd-cloud",
+            "lxd-credentials": "lxd-credentials",
+            "k8s-cloud": "k8s-cloud",
+            "k8s-credentials": "k8s-credentials",
+            "model-config": {},
+            "api-proxy": None,
+        }
+        db_find_one = conn_data.copy()
+        db_find_one.update({"schema_version": "1.1", "_id": "id"})
+        self.vca_collection.find_one.return_value = db_find_one
+        self.store.decrypt_fields = AsyncMock()
+        connection_data = self.loop.run_until_complete(
+            self.store.get_vca_connection_data("vca_id")
+        )
+        self.assertTrue(
+            all(
+                connection_data.__dict__[k.replace("-", "_")] == v
+                for k, v in conn_data.items()
+            )
+        )
+
+    @patch("n2vc.vca.connection_data.base64_to_cacert")
+    def test_get_vca_connection_data_exception(self, mock_base64_to_cacert):
+        mock_base64_to_cacert.return_value = "cacert"
+        self.vca_collection.find_one.return_value = None
+        with self.assertRaises(Exception):
+            self.loop.run_until_complete(self.store.get_vca_connection_data("vca_id"))
+
+    def test_update_vca_endpoints(self):
+        endpoints = ["1.2.3.4:17070"]
+        self.admin_collection.find_one.side_effect = [None, {"api_endpoints": []}]
+        self.admin_collection.insert_one.side_effect = DbException("already exists")
+        self.loop.run_until_complete(self.store.update_vca_endpoints(endpoints))
+        self.assertEqual(self.admin_collection.find_one.call_count, 2)
+        self.admin_collection.replace_one.assert_called_once_with(
+            {"_id": "juju"}, {"api_endpoints": ["1.2.3.4:17070"]}
+        )
+
+    def test_get_vca_connection_data_with_id(self):
+        secret = "e7b253af37785045d1ca08b8d929e556"
+        encrypted_secret = "kI46kRJh828ExSNpr16OG/q5a5/qTsE0bsHrv/W/2/g="
+        cacert = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQ4ekNDQWx1Z0F3SUJBZ0lVRWlzTTBoQWxiYzQ0Z1ZhZWh6bS80ZUsyNnRZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0lURU5NQXNHQTFVRUNoTUVTblZxZFRFUU1BNEdBMVVFQXhNSGFuVnFkUzFqWVRBZUZ3MHlNVEEwTWpNeApNRFV3TXpSYUZ3MHpNVEEwTWpNeE1EVTFNelJhTUNFeERUQUxCZ05WQkFvVEJFcDFhblV4RURBT0JnTlZCQU1UCkIycDFhblV0WTJFd2dnR2lNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJqd0F3Z2dHS0FvSUJnUUNhTmFvNGZab2gKTDJWYThtdy9LdCs3RG9tMHBYTlIvbEUxSHJyVmZvbmZqZFVQV01zSHpTSjJZZXlXcUNSd3BiaHlLaE82N1c1dgpUY2RsV3Y3WGFLTGtsdVkraDBZY3BQT3BFTmZZYmxrNGk0QkV1L0wzYVY5MFFkUFFrMG94S01CS2R5QlBNZVNNCkJmS2pPWXdyOGgzM0ZWUWhmVkJnMXVGZ2tGaDdTamNuNHczUFdvc1BCMjNiVHBCbGR3VE9zemN4Qm9TaDNSVTkKTzZjb3lQdDdEN0drOCtHRlA3RGRUQTdoV1RkaUM4cDBkeHp2RUNmY0psMXNFeFEyZVprS1QvVzZyelNtVDhUTApCM0ErM1FDRDhEOEVsQU1IVy9zS25SeHphYU8welpNVmVlQnRnNlFGZ1F3M0dJMGo2ZTY0K2w3VExoOW8wSkZVCjdpUitPY01xUzVDY0NROGpWV3JPSk9Xc2dEbDZ4T2FFREczYnR5SVJHY29jbVcvcEZFQjNZd1A2S1BRTUIrNXkKWDdnZExEWmFGRFBVakZmblhkMnhHdUZlMnpRTDNVbXZEUkZuUlBBaW02QlpQbWo1OFh2emFhZXROa3lyaUZLZwp4Z0Z1dVpTcDUwV2JWdjF0MkdzOTMrRE53NlhFZHRFYnlWWUNBa28xTTY0MkozczFnN3NoQnRFQ0F3RUFBYU1qCk1DRXdEZ1lEVlIwUEFRSC9CQVFEQWdLa01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUwKQlFBRGdnR0JBRXYxM2o2ZGFVbDBqeERPSnNTV1ZJZS9JdXNXVTRpN2ZXSWlqMHAwRU1GNS9LTE8yemRndTR5SQoreVd2T3N5aVFPanEzMlRYVlo2bTRDSnBkR1dGVE5HK2lLdXVOU3M0N3g3Q3dmVUNBWm5VVzhyamd3ZWJyS3BmCkJMNEVQcTZTcW0rSmltN0VPankyMWJkY2cyUXdZb3A3eUhvaHcveWEvL0l6RTMzVzZxNHlJeEFvNDBVYUhPTEMKTGtGbnNVYitjcFZBeFlPZGp6bjFzNWhnclpuWXlETEl3WmtIdFdEWm94alUzeC9jdnZzZ1FzLytzTWYrRFU4RgpZMkJKRHJjQ1VQM2xzclc0QVpFMFplZkEwOTlncFEvb3dSN0REYnMwSjZUeFM4NGt6Tldjc1FuWnRraXZheHJNClkyVHNnaWVndFExVFdGRWpxLy9sUFV4emJCdmpnd1FBZm5CQXZGeVNKejdTa0VuVm5rUXJGaUlUQVArTHljQVIKMlg4UFI2ZGI1bEt0SitBSENDM3kvZmNQS2k0ZzNTL3djeXRRdmdvOXJ6ODRFalp5YUNTaGJXNG9jNzNrMS9RcAowQWtHRDU0ZGVDWWVPYVJNbW96c0w3ZzdxWkpFekhtODdOcVBYSy9EZFoweWNxaVFhMXY2T3QxNjdXNUlzMUkzCjBWb0IzUzloSlE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCgo="  # noqa: E501
+        encrypted_cacert = "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"  # noqa: E501
+
+        self.vca_collection.find_one.return_value = {
+            "_id": "2ade7f0e-9b58-4dbd-93a3-4ec076185d39",
+            "schema_version": "1.11",
+            "endpoints": [],
+            "user": "admin",
+            "secret": encrypted_secret,
+            "cacert": encrypted_cacert,
+        }
+        self.admin_collection.find_one.return_value = {
+            "serial": b"l+U3HDp9td+UjQ+AN+Ypj/Uh7n3C+rMJueQNNxkIpWI="
+        }
+        connection_data = self.loop.run_until_complete(
+            self.store.get_vca_connection_data("vca_id")
+        )
+        self.assertEqual(connection_data.endpoints, [])
+        self.assertEqual(connection_data.user, "admin")
+        self.assertEqual(connection_data.secret, secret)
+        self.assertEqual(
+            connection_data.cacert, b64decode(cacert.encode("utf-8")).decode("utf-8")
+        )
+
+    def test_update_vca_endpoints_exception(self):
+        endpoints = ["1.2.3.4:17070"]
+        self.admin_collection.find_one.side_effect = [None, None]
+        self.admin_collection.insert_one.side_effect = DbException("already exists")
+        with self.assertRaises(DbException):
+            self.loop.run_until_complete(self.store.update_vca_endpoints(endpoints))
+        self.assertEqual(self.admin_collection.find_one.call_count, 2)
+        self.admin_collection.replace_one.assert_not_called()
+
+    def test_update_vca_endpoints_with_vca_id(self):
+        endpoints = ["1.2.3.4:17070"]
+        self.vca_collection.find_one.return_value = {}
+        self.loop.run_until_complete(
+            self.store.update_vca_endpoints(endpoints, "vca_id")
+        )
+        self.vca_collection.find_one.assert_called_once_with({"_id": "vca_id"})
+        self.vca_collection.replace_one.assert_called_once_with(
+            {"_id": "vca_id"}, {"endpoints": endpoints}
+        )
+
+    def test_get_vca_endpoints(self):
+        endpoints = ["1.2.3.4:17070"]
+        db_data = {"api_endpoints": endpoints}
+        db_returns = [db_data, None]
+        expected_returns = [endpoints, []]
+        returns = []
+        self.admin_collection.find_one.side_effect = db_returns
+        for _ in range(len(db_returns)):
+            e = self.loop.run_until_complete(self.store.get_vca_endpoints())
+            returns.append(e)
+        self.assertEqual(expected_returns, returns)
+
+    @patch("n2vc.vca.connection_data.base64_to_cacert")
+    def test_get_vca_endpoints_with_vca_id(self, mock_base64_to_cacert):
+        expected_endpoints = ["1.2.3.4:17070"]
+        mock_base64_to_cacert.return_value = "cacert"
+        self.store.get_vca_connection_data = AsyncMock()
+        self.store.get_vca_connection_data.return_value = ConnectionData(
+            **{
+                "endpoints": expected_endpoints,
+                "user": "admin",
+                "secret": "1234",
+                "cacert": "cacert",
+            }
+        )
+        endpoints = self.loop.run_until_complete(self.store.get_vca_endpoints("vca_id"))
+        self.store.get_vca_connection_data.assert_called_with("vca_id")
+        self.assertEqual(expected_endpoints, endpoints)
+
+    def test_get_vca_id(self):
+        self.assertIsNone(self.loop.run_until_complete((self.store.get_vca_id())))
+
+    def test_get_vca_id_with_vim_id(self):
+        self.vim_accounts_collection.find_one.return_value = {"vca": "vca_id"}
+        vca_id = self.loop.run_until_complete(self.store.get_vca_id("vim_id"))
+        self.vim_accounts_collection.find_one.assert_called_once_with({"_id": "vim_id"})
+        self.assertEqual(vca_id, "vca_id")
diff --git a/n2vc/tests/unit/test_utils.py b/n2vc/tests/unit/test_utils.py
index c5ab84f..bffbc29 100644
--- a/n2vc/tests/unit/test_utils.py
+++ b/n2vc/tests/unit/test_utils.py
@@ -14,7 +14,7 @@
 
 from unittest import TestCase
 
-from n2vc.utils import Dict, EntityType, JujuStatusToOSM, N2VCDeploymentStatus, DB_DATA
+from n2vc.utils import Dict, EntityType, JujuStatusToOSM, N2VCDeploymentStatus
 from juju.machine import Machine
 from juju.application import Application
 from juju.action import Action
@@ -84,8 +84,3 @@
                 osm_status = status["osm"]
                 self.assertTrue(juju_status in JujuStatusToOSM[entity_type])
                 self.assertEqual(osm_status, JujuStatusToOSM[entity_type][juju_status])
-
-    def test_db_data(self):
-        self.assertEqual(DB_DATA.api_endpoints.table, "admin")
-        self.assertEqual(DB_DATA.api_endpoints.filter, {"_id": "juju"})
-        self.assertEqual(DB_DATA.api_endpoints.key, "api_endpoints")
diff --git a/n2vc/tests/unit/utils.py b/n2vc/tests/unit/utils.py
index a727072..2f107a7 100644
--- a/n2vc/tests/unit/utils.py
+++ b/n2vc/tests/unit/utils.py
@@ -82,7 +82,18 @@
         detailed_status: str,
         vca_status: str,
         entity_type: str,
+        vca_id: str = None,
     ):
+        """
+        Write application status to database
+
+        :param: db_dict: DB dictionary
+        :param: status: Status of the application
+        :param: detailed_status: Detailed status
+        :param: vca_status: VCA status
+        :param: entity_type: Entity type ("application", "machine, and "action")
+        :param: vca_id: Id of the VCA. If None, the default VCA will be used.
+        """
         self.last_written_values = Dict(
             {
                 "n2vc_status": status,