From 86925080956890e995d19ca0eb90fff5c003f09d Mon Sep 17 00:00:00 2001 From: Gabriel Cocenza Date: Wed, 6 Dec 2023 12:25:43 -0300 Subject: [PATCH] Smoke test for cou plan (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Smoke test for cou plan check the stdout for the following scenarios: * plan with backup * plan without backup * Use absolute path instead of relative for snap file * - add log about cou executable path * - use pathlib instead of os. - use the full snap path to not use the python package when using the snap. * - cou wrapper to run commands - not remove the cou python package on tearDown. * - add error log message if subprocess fail. * Explicitly pass the model name - Using juju switch it’s not possible because it uses JUJU_MODEL and python libjuju doesn't support this env var. - Running without explicitly passing the model can make cou run in a different model that it’s not on the recently deployed by zaza. * - remove comment to skip test. * - apply suggestions * - added custom exception for install commands - snap_installed logic inside configure_executable_path * - remove get_or_create_libjuju_thread and clean_up_libjuju_thread - use COU_DATA to create local share folder. --------- Co-authored-by: TQ --- .github/workflows/check.yaml | 2 +- tests/functional/tests/smoke.py | 156 ++++++++++++++++++++++++++++++ tests/functional/tests/tests.yaml | 1 + tox.ini | 7 +- 4 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 tests/functional/tests/smoke.py diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index ae168fa2..d00710a3 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -79,4 +79,4 @@ jobs: with: name: SNAP_FILE - name: Run func tests - run: TEST_SNAP=charmed-openstack-upgrader.snap tox -e func + run: TEST_SNAP=$GITHUB_WORKSPACE/charmed-openstack-upgrader.snap tox -e func diff --git a/tests/functional/tests/smoke.py b/tests/functional/tests/smoke.py new file mode 100644 index 00000000..5c7bf5a4 --- /dev/null +++ b/tests/functional/tests/smoke.py @@ -0,0 +1,156 @@ +import logging +import os +import unittest +from pathlib import Path +from subprocess import CalledProcessError, CompletedProcess, check_call, run + +import zaza + +from cou.utils import COU_DATA + +log = logging.getLogger(__name__) + + +class FuncSmokeException(Exception): + """Default Func Smoke exception.""" + + +class SmokeTest(unittest.TestCase): + """COU smoke functional tests.""" + + @classmethod + def setUpClass(cls) -> None: + cls.create_local_share_folder() + cls.model_name = zaza.model.get_juju_model() + cls.configure_executable_path() + + @classmethod + def tearDownClass(cls) -> None: + cls.remove_snap_package() + + def create_local_share_folder() -> None: + """Create the .local/share/ folder if does not exist.""" + COU_DATA.mkdir(parents=True, exist_ok=True) + + @classmethod + def configure_executable_path(cls) -> None: + cls.snap_installed = False + cou_snap = os.environ.get("TEST_SNAP") + if cou_snap: + cls.install_snap_package(cou_snap) + cls.exc_path = "/snap/bin/cou" + cls.snap_installed = True + else: + # functest already installs cou as python package + log.warning("using cou as python package") + cls.exc_path = os.getenv("TEST_PYTHON_PACKAGE") + "/bin/cou" + + log.info("Using cou path: %s", cls.exc_path) + + @classmethod + def install_snap_package(cls, cou_snap: str) -> None: + """Install cou snap package. + + :param cou_snap: Path to the cou snap. + :type cou_snap: str + """ + log.info("Installing %s", cou_snap) + assert Path(cou_snap).is_file(), f"{cou_snap} is not file" + + # install the snap + cls.snap_install_commands( + ["sudo", "snap", "install", "--dangerous", cou_snap], + "Cannot install the cou snap. Please check your permission", + ) + + # connect interfaces + interfaces_to_connect = [ + "juju-client-observe", + "dot-local-share-cou", + "ssh-public-keys", + ] + for interface in interfaces_to_connect: + cls.snap_install_commands( + [ + "sudo", + "snap", + "connect", + f"charmed-openstack-upgrader:{interface}", + "snapd", + ], + f"Cannot connect the interface: {interface}", + ) + + # make the cou alias + cls.snap_install_commands( + ["sudo", "snap", "alias", "charmed-openstack-upgrader.cou", "cou"], + "Cannot create the cou alias", + ) + + # check that the executable path exists + assert Path("/snap/bin/cou").exists(), "Cannot find the cou executable snap path." + cls.exc_path = "/snap/bin/cou" + + def snap_install_commands(cmd: list[str], custom_err_msg: str): + """Commands to run and install the cou snap. + + :param cmd: The command to be executed. + :type cmd: list[str] + :param custom_err_msg: Custom error message if the command fails. + :type custom_err_msg: str + :raises FuncSmokeException: When the command fails. + """ + try: + check_call(cmd) + except CalledProcessError as err: + raise FuncSmokeException(custom_err_msg) from err + + @classmethod + def remove_snap_package(cls) -> None: + """Remove cou package.""" + if cls.snap_installed: + log.info("Removing snap package cou") + check_call(["sudo", "snap", "remove", "charmed-openstack-upgrader", "--purge"]) + + def cou(self, cmd: list[str]) -> CompletedProcess: + """Run cou commands. + + :param cmd: Command to run. + :type cmd: list[str] + :return: Response of the command. + :rtype: CompletedProcess + """ + return run([self.exc_path] + cmd, capture_output=True, text=True) + + def test_plan_default(self) -> None: + """Test plan with backup.""" + result = self.cou(["plan", "--model", self.model_name]).stdout + expected_plan = ( + "Upgrade cloud from 'ussuri' to 'victoria'\n" + "\tVerify that all OpenStack applications are in idle state\n" + "\tBackup mysql databases\n" + "\tControl Plane principal(s) upgrade plan\n" + "\t\tUpgrade plan for 'designate-bind' to victoria\n" + "\t\t\tUpgrade software packages of 'designate-bind' " + "from the current APT repositories\n" + "\t\t\tUpgrade 'designate-bind' to the new channel: 'victoria/stable'\n" + "\t\t\tWait 300s for app designate-bind to reach the idle state.\n" + "\t\t\tCheck if the workload of 'designate-bind' has been upgraded\n" + ) + self.assertIn(expected_plan, result) + + def test_plan_no_backup(self) -> None: + """Test plan with no backup.""" + result = self.cou(["plan", "--model", self.model_name, "--no-backup"]).stdout + expected_plan = ( + "Upgrade cloud from 'ussuri' to 'victoria'\n" + "\tVerify that all OpenStack applications are in idle state\n" + "\tControl Plane principal(s) upgrade plan\n" + "\t\tUpgrade plan for 'designate-bind' to victoria\n" + "\t\t\tUpgrade software packages of 'designate-bind' " + "from the current APT repositories\n" + "\t\t\tUpgrade 'designate-bind' to the new channel: 'victoria/stable'\n" + "\t\t\tWait 300s for app designate-bind to reach the idle state.\n" + "\t\t\tCheck if the workload of 'designate-bind' has been upgraded\n" + ) + self.assertIn(expected_plan, result) diff --git a/tests/functional/tests/tests.yaml b/tests/functional/tests/tests.yaml index f5a548a5..87f6784a 100644 --- a/tests/functional/tests/tests.yaml +++ b/tests/functional/tests/tests.yaml @@ -1,5 +1,6 @@ tests: - tests.functional.tests.model.COUModelTest + - tests.functional.tests.smoke.SmokeTest - openstack: - tests.functional.tests.backup.BackupTest gate_bundles: diff --git a/tox.ini b/tox.ini index 0a98c9bc..5a2105d3 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,9 @@ skip_missing_interpreters = True [testenv] basepython = python3 -setenv = PYTHONPATH={toxinidir} -passenv = USER +setenv = + PYTHONPATH={toxinidir} + TEST_PYTHON_PACKAGE={envdir} [testenv:dev-environment] envdir = {toxinidir}/.venv @@ -58,6 +59,7 @@ deps = .[functests] passenv = TEST_* OS_* + USER commands = functest-run-suite --smoke {posargs:--keep-faulty-model} @@ -67,5 +69,6 @@ deps = .[functests] passenv = TEST_* OS_* + USER commands = functest-run-suite {posargs:--keep-faulty-model}