Add OSM-MON integration tests 78/12978/3
authorDario Faccin <dario.faccin@canonical.com>
Wed, 15 Feb 2023 09:52:55 +0000 (10:52 +0100)
committerbeierlm <mark.beierl@canonical.com>
Fri, 17 Feb 2023 15:08:04 +0000 (16:08 +0100)
Change-Id: I3199869880d0c9ce0784dcc623c844dd39f1180a
Signed-off-by: Dario Faccin <dario.faccin@canonical.com>
installers/charm/osm-mon/src/charm.py
installers/charm/osm-mon/tests/integration/test_charm.py [new file with mode: 0644]
installers/charm/osm-mon/tests/unit/test_charm.py
installers/charm/osm-mon/tox.ini

index 176f896..e07d607 100755 (executable)
@@ -151,9 +151,7 @@ class OsmMonCharm(CharmBase):
     def _on_get_debug_mode_information_action(self, event: ActionEvent) -> None:
         """Handler for the get-debug-mode-information action event."""
         if not self.debug_mode.started:
-            event.fail(
-                "debug-mode has not started. Hint: juju config mon debug-mode=true"
-            )
+            event.fail("debug-mode has not started. Hint: juju config mon debug-mode=true")
             return
 
         debug_info = {
@@ -179,13 +177,9 @@ class OsmMonCharm(CharmBase):
             # Action events
             self.on.get_debug_mode_information_action: self._on_get_debug_mode_information_action,
         }
-        for relation in [
-            self.on[rel_name] for rel_name in ["mongodb", "prometheus", "keystone"]
-        ]:
+        for relation in [self.on[rel_name] for rel_name in ["mongodb", "prometheus", "keystone"]]:
             event_handler_mapping[relation.relation_changed] = self._on_config_changed
-            event_handler_mapping[
-                relation.relation_broken
-            ] = self._on_required_relation_broken
+            event_handler_mapping[relation.relation_broken] = self._on_required_relation_broken
 
         for event, handler in event_handler_mapping.items():
             self.framework.observe(event, handler)
@@ -219,9 +213,7 @@ class OsmMonCharm(CharmBase):
         if missing_relations:
             relations_str = ", ".join(missing_relations)
             one_relation_missing = len(missing_relations) == 1
-            error_msg = (
-                f'need {relations_str} relation{"" if one_relation_missing else "s"}'
-            )
+            error_msg = f'need {relations_str} relation{"" if one_relation_missing else "s"}'
             logger.warning(error_msg)
             raise CharmError(error_msg)
 
@@ -236,9 +228,7 @@ class OsmMonCharm(CharmBase):
         environment = {
             # General configuration
             "OSMMON_GLOBAL_LOGLEVEL": self.config["log-level"],
-            "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": self.config[
-                "openstack-default-granularity"
-            ],
+            "OSMMON_OPENSTACK_DEFAULT_GRANULARITY": self.config["openstack-default-granularity"],
             "OSMMON_GLOBAL_REQUEST_TIMEOUT": self.config["global-request-timeout"],
             "OSMMON_COLLECTOR_INTERVAL": self.config["collector-interval"],
             "OSMMON_EVALUATOR_INTERVAL": self.config["evaluator-interval"],
diff --git a/installers/charm/osm-mon/tests/integration/test_charm.py b/installers/charm/osm-mon/tests/integration/test_charm.py
new file mode 100644 (file)
index 0000000..12c261b
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3
+# Copyright 2022 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact: legal@canonical.com
+#
+# To get in touch with the maintainers, please contact:
+# osm-charmers@lists.launchpad.net
+#
+# Learn more about testing at: https://juju.is/docs/sdk/testing
+
+import asyncio
+import logging
+import shlex
+from pathlib import Path
+
+import pytest
+import yaml
+from pytest_operator.plugin import OpsTest
+
+logger = logging.getLogger(__name__)
+
+METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
+MON_APP = METADATA["name"]
+KAFKA_CHARM = "kafka-k8s"
+KAFKA_APP = "kafka"
+KEYSTONE_CHARM = "osm-keystone"
+KEYSTONE_APP = "keystone"
+MARIADB_CHARM = "charmed-osm-mariadb-k8s"
+MARIADB_APP = "mariadb"
+MONGO_DB_CHARM = "mongodb-k8s"
+MONGO_DB_APP = "mongodb"
+PROMETHEUS_CHARM = "osm-prometheus"
+PROMETHEUS_APP = "prometheus"
+ZOOKEEPER_CHARM = "zookeeper-k8s"
+ZOOKEEPER_APP = "zookeeper"
+VCA_CHARM = "osm-vca-integrator"
+VCA_APP = "vca"
+APPS = [KAFKA_APP, ZOOKEEPER_APP, KEYSTONE_APP, MONGO_DB_APP, MARIADB_APP, PROMETHEUS_APP, MON_APP]
+
+
+@pytest.mark.abort_on_fail
+async def test_mon_is_deployed(ops_test: OpsTest):
+    charm = await ops_test.build_charm(".")
+    resources = {"mon-image": METADATA["resources"]["mon-image"]["upstream-source"]}
+
+    await asyncio.gather(
+        ops_test.model.deploy(
+            charm, resources=resources, application_name=MON_APP, series="focal"
+        ),
+        ops_test.model.deploy(KAFKA_CHARM, application_name=KAFKA_APP, channel="stable"),
+        ops_test.model.deploy(MONGO_DB_CHARM, application_name=MONGO_DB_APP, channel="stable"),
+        ops_test.model.deploy(MARIADB_CHARM, application_name=MARIADB_APP, channel="stable"),
+        ops_test.model.deploy(PROMETHEUS_CHARM, application_name=PROMETHEUS_APP, channel="stable"),
+        ops_test.model.deploy(ZOOKEEPER_CHARM, application_name=ZOOKEEPER_APP, channel="stable"),
+    )
+    cmd = f"juju deploy {KEYSTONE_CHARM} {KEYSTONE_APP} --resource keystone-image=opensourcemano/keystone:12"
+    await ops_test.run(*shlex.split(cmd), check=True)
+
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS,
+        )
+    assert ops_test.model.applications[MON_APP].status == "blocked"
+    unit = ops_test.model.applications[MON_APP].units[0]
+    assert unit.workload_status_message == "need kafka, mongodb, prometheus, keystone relations"
+
+    logger.info("Adding relations for other components")
+    await ops_test.model.add_relation(KAFKA_APP, ZOOKEEPER_APP)
+    await ops_test.model.add_relation(MARIADB_APP, KEYSTONE_APP)
+
+    logger.info("Adding relations")
+    await ops_test.model.add_relation(MON_APP, MONGO_DB_APP)
+    await ops_test.model.add_relation(MON_APP, KAFKA_APP)
+    await ops_test.model.add_relation(MON_APP, KEYSTONE_APP)
+    await ops_test.model.add_relation(MON_APP, PROMETHEUS_APP)
+
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS,
+            status="active",
+        )
+
+
+@pytest.mark.abort_on_fail
+async def test_mon_scales_up(ops_test: OpsTest):
+    logger.info("Scaling up osm-mon")
+    expected_units = 3
+    assert len(ops_test.model.applications[MON_APP].units) == 1
+    await ops_test.model.applications[MON_APP].scale(expected_units)
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=[MON_APP], status="active", wait_for_exact_units=expected_units
+        )
+
+
+@pytest.mark.abort_on_fail
+@pytest.mark.parametrize(
+    "relation_to_remove", [KAFKA_APP, MONGO_DB_APP, PROMETHEUS_APP, KEYSTONE_APP]
+)
+async def test_mon_blocks_without_relation(ops_test: OpsTest, relation_to_remove):
+    logger.info("Removing relation: %s", relation_to_remove)
+    # mongoDB relation is named "database"
+    local_relation = relation_to_remove
+    if relation_to_remove == MONGO_DB_APP:
+        local_relation = "database"
+    await asyncio.gather(
+        ops_test.model.applications[relation_to_remove].remove_relation(local_relation, MON_APP)
+    )
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(apps=[MON_APP])
+    assert ops_test.model.applications[MON_APP].status == "blocked"
+    for unit in ops_test.model.applications[MON_APP].units:
+        assert unit.workload_status_message == f"need {relation_to_remove} relation"
+    await ops_test.model.add_relation(MON_APP, relation_to_remove)
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS,
+            status="active",
+        )
+
+
+@pytest.mark.abort_on_fail
+async def test_mon_action_debug_mode_disabled(ops_test: OpsTest):
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS,
+            status="active",
+        )
+    logger.info("Running action 'get-debug-mode-information'")
+    action = (
+        await ops_test.model.applications[MON_APP]
+        .units[0]
+        .run_action("get-debug-mode-information")
+    )
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(apps=[MON_APP])
+    status = await ops_test.model.get_action_status(uuid_or_prefix=action.entity_id)
+    assert status[action.entity_id] == "failed"
+
+
+@pytest.mark.abort_on_fail
+async def test_mon_action_debug_mode_enabled(ops_test: OpsTest):
+    await ops_test.model.applications[MON_APP].set_config({"debug-mode": "true"})
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS,
+            status="active",
+        )
+    logger.info("Running action 'get-debug-mode-information'")
+    # list of units is not ordered
+    unit_id = list(
+        filter(
+            lambda x: (x.entity_id == f"{MON_APP}/0"), ops_test.model.applications[MON_APP].units
+        )
+    )[0]
+    action = await unit_id.run_action("get-debug-mode-information")
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(apps=[MON_APP])
+    status = await ops_test.model.get_action_status(uuid_or_prefix=action.entity_id)
+    message = await ops_test.model.get_action_output(action_uuid=action.entity_id)
+    assert status[action.entity_id] == "completed"
+    assert "command" in message
+    assert "password" in message
+
+
+@pytest.mark.abort_on_fail
+async def test_mon_integration_vca(ops_test: OpsTest):
+    await asyncio.gather(
+        ops_test.model.deploy(VCA_CHARM, application_name=VCA_APP, channel="beta"),
+    )
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=[VCA_APP],
+        )
+    controllers = (Path.home() / ".local/share/juju/controllers.yaml").read_text()
+    accounts = (Path.home() / ".local/share/juju/accounts.yaml").read_text()
+    public_key = (Path.home() / ".local/share/juju/ssh/juju_id_rsa.pub").read_text()
+    await ops_test.model.applications[VCA_APP].set_config(
+        {
+            "controllers": controllers,
+            "accounts": accounts,
+            "public-key": public_key,
+            "k8s-cloud": "microk8s",
+        }
+    )
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS + [VCA_APP],
+            status="active",
+        )
+    await ops_test.model.add_relation(MON_APP, VCA_APP)
+    async with ops_test.fast_forward():
+        await ops_test.model.wait_for_idle(
+            apps=APPS + [VCA_APP],
+            status="active",
+        )
index 3ea173a..5c9fd99 100644 (file)
@@ -37,6 +37,7 @@ def harness(mocker: MockerFixture):
     mocker.patch("charm.KubernetesServicePatch", lambda x, y: None)
     harness = Harness(OsmMonCharm)
     harness.begin()
+    harness.container_pebble_ready(container_name)
     yield harness
     harness.cleanup()
 
@@ -77,13 +78,13 @@ def _add_relations(harness: Harness):
     # Add kafka relation
     relation_id = harness.add_relation("kafka", "kafka")
     harness.add_relation_unit(relation_id, "kafka/0")
-    harness.update_relation_data(relation_id, "kafka", {"host": "kafka", "port": 9092})
+    harness.update_relation_data(relation_id, "kafka", {"host": "kafka", "port": "9092"})
     relation_ids.append(relation_id)
     # Add prometheus relation
     relation_id = harness.add_relation("prometheus", "prometheus")
     harness.add_relation_unit(relation_id, "prometheus/0")
     harness.update_relation_data(
-        relation_id, "prometheus", {"hostname": "prometheus", "port": 9090}
+        relation_id, "prometheus", {"hostname": "prometheus", "port": "9090"}
     )
     relation_ids.append(relation_id)
     # Add keystone relation
index 56c095b..4fe914c 100644 (file)
@@ -21,7 +21,7 @@
 [tox]
 skipsdist=True
 skip_missing_interpreters = True
-envlist = lint, unit
+envlist = lint, unit, integration
 
 [vars]
 src_path = {toxinidir}/src/
@@ -85,8 +85,8 @@ commands =
 description = Run integration tests
 deps =
     pytest
-    juju
+    juju<3
     pytest-operator
     -r{toxinidir}/requirements.txt
 commands =
-    pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs}
+    pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} --cloud microk8s