From 9450e444d8a73d9babc8571bc5f1769aac8e62e1 Mon Sep 17 00:00:00 2001 From: Paul Larson Date: Mon, 3 Jun 2024 10:39:55 -0400 Subject: [PATCH] Add a testflinger_common module with example usage (#275) * Add a testflinger_common module with example usage * Add the testflinger_common project to what gets installed by the charm * Make it easier to test new branches of the charm with the config option --- .../testflinger-agent-charm/src/charm.py | 2 + agent/pyproject.toml | 1 + agent/testflinger_agent/agent.py | 28 +++-- common/README.rst | 8 ++ common/poetry.lock | 117 ++++++++++++++++++ common/pyproject.toml | 23 ++++ common/testflinger_common/__init__.py | 0 common/testflinger_common/enums.py | 45 +++++++ 8 files changed, 214 insertions(+), 10 deletions(-) create mode 100644 common/README.rst create mode 100644 common/poetry.lock create mode 100644 common/pyproject.toml create mode 100644 common/testflinger_common/__init__.py create mode 100644 common/testflinger_common/enums.py diff --git a/agent/charms/testflinger-agent-charm/src/charm.py b/agent/charms/testflinger-agent-charm/src/charm.py index 7fb222f1..825b1661 100755 --- a/agent/charms/testflinger-agent-charm/src/charm.py +++ b/agent/charms/testflinger-agent-charm/src/charm.py @@ -142,6 +142,7 @@ def update_repos(self): repo_path = f"{self._stored.agent_path}/testflinger" repo = Repo.clone_from( url=self._stored.testflinger_repo, + branch=self._stored.testflinger_branch, to_path=repo_path, no_checkout=True, depth=1, @@ -151,6 +152,7 @@ def update_repos(self): f"origin/{self._stored.testflinger_branch}", "--", "agent", + "common", "device-connectors", ) # Install the agent and device-connectors diff --git a/agent/pyproject.toml b/agent/pyproject.toml index 30b095a0..b8d3dde0 100644 --- a/agent/pyproject.toml +++ b/agent/pyproject.toml @@ -11,6 +11,7 @@ influxdb = "^5.3.2" pyyaml = "^6.0.1" requests = "^2.31.0" voluptuous = "^0.14.2" +testflinger-common = { path = "../common" } [tool.poetry.dev-dependencies] pytest = "^8.1.2" diff --git a/agent/testflinger_agent/agent.py b/agent/testflinger_agent/agent.py index 59a0af9a..527b665b 100644 --- a/agent/testflinger_agent/agent.py +++ b/agent/testflinger_agent/agent.py @@ -22,6 +22,7 @@ from testflinger_agent.job import TestflingerJob from testflinger_agent.errors import TFServerError from testflinger_agent.config import ATTACHMENTS_DIR +from testflinger_common.enums import JobState, TestPhase try: # attempt importing a tarfile filter, to check if filtering is supported @@ -168,7 +169,11 @@ def unpack_attachments(self, job_data: dict, cwd: Path): # (so there is no interference with existing processes, especially # provisioning or firmware update, which are triggered when these # sections are not empty) - for phase in ("provision", "firmware_update", "test"): + for phase in ( + TestPhase.PROVISION, + TestPhase.FIRMWARE_UPDATE, + TestPhase.TEST, + ): phase_str = f"{phase}_data" try: phase_data = job_data[phase_str] @@ -185,12 +190,12 @@ def unpack_attachments(self, job_data: dict, cwd: Path): def process_jobs(self): """Coordinate checking for new jobs and handling them if they exists""" TEST_PHASES = [ - "setup", - "provision", - "firmware_update", - "test", - "allocate", - "reserve", + TestPhase.SETUP, + TestPhase.PROVISION, + TestPhase.FIRMWARE_UPDATE, + TestPhase.TEST, + TestPhase.ALLOCATE, + TestPhase.RESERVE, ] # First, see if we have any old results that we couldn't send last time @@ -233,7 +238,10 @@ def process_jobs(self): for phase in TEST_PHASES: # First make sure the job hasn't been cancelled - if self.client.check_job_state(job.job_id) == "cancelled": + if ( + self.client.check_job_state(job.job_id) + == JobState.CANCELLED + ): logger.info("Job cancellation was requested, exiting.") break self.client.post_job_state(job.job_id, phase) @@ -253,7 +261,7 @@ def process_jobs(self): logger.exception(e) finally: # Always run the cleanup, even if the job was cancelled - job.run_test_phase("cleanup", rundir) + job.run_test_phase(TestPhase.CLEANUP, rundir) # clear job id self.client.post_agent_data({"job_id": ""}) @@ -266,7 +274,7 @@ def process_jobs(self): logger.exception(e) results_basedir = self.client.config.get("results_basedir") shutil.move(rundir, results_basedir) - self.set_agent_state("waiting") + self.set_agent_state(JobState.WAITING) self.check_restart() if self.check_offline(): diff --git a/common/README.rst b/common/README.rst new file mode 100644 index 00000000..fcecdbdc --- /dev/null +++ b/common/README.rst @@ -0,0 +1,8 @@ +Testflinger Common +================== + +The testflinger_common module may be used to store code that is useful to +multiple projects so that it only needs to be defined once. + +There is no standalone script for testflinger_common as it is only intended +to be imported and used by other projects. diff --git a/common/poetry.lock b/common/poetry.lock new file mode 100644 index 00000000..5ba5f810 --- /dev/null +++ b/common/poetry.lock @@ -0,0 +1,117 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.2.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, + {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "strenum" +version = "0.4.15" +description = "An Enum that inherits from str." +optional = false +python-versions = "*" +files = [ + {file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"}, + {file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"}, +] + +[package.extras] +docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"] +release = ["twine"] +test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "0e72c0b44c0538a40a051916179dbe2f0d3b16d1cc4abf67674e1b91120835df" diff --git a/common/pyproject.toml b/common/pyproject.toml new file mode 100644 index 00000000..6c83eba1 --- /dev/null +++ b/common/pyproject.toml @@ -0,0 +1,23 @@ +[tool.setuptools] +packages = ["testflinger_common"] + +[tool.poetry] +name = "testflinger-common" +description = "Testflinger common modules" +readme = "README.rst" +version = "1.1.0" +authors = [] + +[tool.poetry.dependencies] +python = "^3.8" # specify your Python version requirement here +strenum = "^0.4.15" + +[tool.poetry.dev-dependencies] +pytest = "^8.1.2" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 79 diff --git a/common/testflinger_common/__init__.py b/common/testflinger_common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/common/testflinger_common/enums.py b/common/testflinger_common/enums.py new file mode 100644 index 00000000..067267fb --- /dev/null +++ b/common/testflinger_common/enums.py @@ -0,0 +1,45 @@ +# Copyright (C) 2024 Canonical +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +""" +Job State and Test Phase Enums +""" + +from strenum import StrEnum + + +class JobState(StrEnum): + WAITING = "waiting" + SETUP = "setup" + PROVISION = "provision" + FIRMWARE_UPDATE = "firmware_update" + TEST = "test" + ALLOCATE = "allocate" + ALLOCATED = "allocated" + RESERVE = "reserve" + CLEANUP = "cleanup" + CANCELLED = "cancelled" + COMPLETED = "completed" + + +class TestPhase(StrEnum): + SETUP = "setup" + PROVISION = "provision" + FIRMWARE_UPDATE = "firmware_update" + TEST = "test" + ALLOCATE = "allocate" + RESERVE = "reserve" + CLEANUP = "cleanup" +