Tushar Gupta has proposed merging ~tushar5526/lpbuildbot-worker:add-support-for-creating-base-image-flavors-for-testing into lpbuildbot-worker:main.
Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~tushar5526/lpbuildbot-worker/+git/lpbuildbot-worker/+merge/477795 -- Your team Launchpad code reviewers is requested to review the proposed merge of ~tushar5526/lpbuildbot-worker:add-support-for-creating-base-image-flavors-for-testing into lpbuildbot-worker:main.
diff --git a/README.md b/README.md index 559d71b..47355fa 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,36 @@ SLAVE_PREFIXCMD[1]="" # prefix command, i.e. nice, linux32, dchr ### Run the worker `sudo /etc/init.d/buildslave start` + +# Development Guide + +Create a new virtual environment: + +``` +python -m venv <path-to-virtual-env> +source <path-to-virtual-env>/bin/activate +``` + +Install the required dependencies + +``` +pip install -r charm/requirements-dev.txt +``` + +### Running the tests + +You can run the tests using tox. Install `tox` and install the required python interpreters you want to test on using `pyenv`. + +Once you have the necessary python versions installed. Enable them globally so `tox` discovers them. For example, you can run: + + ``` + pyenv global 3.6 3.7 .. + ``` + +Test files are present in `charms/tests`. Finally, run tests using tox. + +``` +cd charms +tox +tox -e py36 py37 # if you want to run test for specific versions +``` diff --git a/charm/config.yaml b/charm/config.yaml index abf0721..2a06651 100644 --- a/charm/config.yaml +++ b/charm/config.yaml @@ -7,6 +7,11 @@ options: default: xenial description: > Space-separated list of Ubuntu series for which to maintain workers. + series-flavors: + type: string + default: "" + description: > + Space-separated list of {series}-{flavors} that needs to be maintained. Supported flavors are focal-postgres-14. manager-host: type: string default: diff --git a/charm/requirements.txt b/charm/requirements.txt index e352ba9..755e629 100644 --- a/charm/requirements.txt +++ b/charm/requirements.txt @@ -1,2 +1,2 @@ jinja2 -ops >= 1.2.0 +ops==1.5.2 diff --git a/charm/src/charm.py b/charm/src/charm.py index 2fe3960..763f252 100755 --- a/charm/src/charm.py +++ b/charm/src/charm.py @@ -28,6 +28,16 @@ class BlockedOnConfig(Exception): super().__init__("Waiting for {} to be set".format(name)) +class InvalidFlavorValueFormat(Exception): + def __init__(self, flavor): + super().__init__( + "Invalid flavor format {}. Flavors should follow the \ +format (series)-(flavor).".format( + flavor + ) + ) + + class LPBuildbotWorkerCharm(CharmBase): _stored = StoredState() @@ -191,6 +201,7 @@ class LPBuildbotWorkerCharm(CharmBase): def _make_workers(self): ubuntu_series = set(self._require_config("ubuntu-series").split()) + series_flavors = set(self.config.get("series-flavors").split()) manager_host = self._require_config("manager-host") manager_port = self._require_config("manager-port") buildbot_password = self._require_config("buildbot-password") @@ -200,42 +211,30 @@ class LPBuildbotWorkerCharm(CharmBase): self._run_as_buildbot( ["mkdir", "-m755", "-p", str(workers_path)], check=True ) - current_images = self._list_lxd_images() for series in ubuntu_series: - self._set_maintenance_step("Making worker for {}".format(series)) - base_path = workers_path / "{}-lxd-worker".format(series) - if not base_path.exists(): - self._run_as_buildbot( - ["mkdir", "-m755", "-p", str(base_path)], check=True - ) - lp_path = base_path / "devel" - dependencies_path = base_path / "dependencies" - download_cache_path = dependencies_path / "download-cache" - if not lp_path.exists(): - self._run_as_buildbot( - [ - "git", - "clone", - "https://git.launchpad.net/launchpad", - str(lp_path), - ], - check=True, - ) - if not download_cache_path.exists(): - self._run_as_buildbot( - [ - "git", - "clone", - "https://git.launchpad.net/lp-source-dependencies", - str(download_cache_path), - ], - check=True, - ) - if "lptests-{}".format(series) not in current_images: - self._run_as_buildbot( - ["create-lp-tests-lxd", series, str(base_path)], check=True + base_path = self._setup_launchpad_working_dir(series, workers_path) + self._create_lxd_image(series, base_path, current_images) + + for series_flavor in series_flavors: + try: + series, flavor = series_flavor.split("-", 1) + except ValueError: + raise InvalidFlavorValueFormat(series_flavor) + + # build flavor for a series if the series is active + if series not in ubuntu_series: + logger.info( + "Skipping {} as series {} is not active".format( + series_flavor, series + ) ) + continue + + base_path = self._setup_launchpad_working_dir( + series, workers_path, flavor + ) + self._create_lxd_image(series, base_path, current_images, flavor) self._render_template( "buildbot.tac.j2", @@ -286,6 +285,53 @@ class LPBuildbotWorkerCharm(CharmBase): self._stored.ubuntu_series = ubuntu_series + def _setup_launchpad_working_dir(self, series, workers_path, flavor=""): + flavor_suffix = "-" + flavor if flavor else "" + self._set_maintenance_step( + "Making worker for {}{}".format(series, flavor_suffix) + ) + base_path = workers_path / "{}{}-lxd-worker".format( + series, flavor_suffix + ) + if not base_path.exists(): + self._run_as_buildbot( + ["mkdir", "-m755", "-p", str(base_path)], check=True + ) + lp_path = base_path / "devel" + dependencies_path = base_path / "dependencies" + download_cache_path = dependencies_path / "download-cache" + if not lp_path.exists(): + self._run_as_buildbot( + [ + "git", + "clone", + "https://git.launchpad.net/launchpad", + str(lp_path), + ], + check=True, + ) + if not download_cache_path.exists(): + self._run_as_buildbot( + [ + "git", + "clone", + "https://git.launchpad.net/lp-source-dependencies", + str(download_cache_path), + ], + check=True, + ) + return base_path + + def _create_lxd_image(self, series, base_path, current_images, flavor=""): + if ( + "lptests-{}{}".format(series, "-" + flavor if flavor else "") + not in current_images + ): + self._run_as_buildbot( + ["create-lp-tests-lxd", series, str(base_path), flavor], + check=True, + ) + def _on_install(self, event): self._install_lpbuildbot_worker() self._install_lxd() diff --git a/charm/tests/test_charm.py b/charm/tests/test_charm.py index f3efcd0..bc3ed1c 100644 --- a/charm/tests/test_charm.py +++ b/charm/tests/test_charm.py @@ -12,7 +12,11 @@ import pytest from ops.model import ActiveStatus, MaintenanceStatus from ops.testing import Harness -from charm import BlockedOnConfig, LPBuildbotWorkerCharm +from charm import ( + BlockedOnConfig, + InvalidFlavorValueFormat, + LPBuildbotWorkerCharm, +) # The "fs" fixture is a fake filesystem from pyfakefs; the "fp" fixture is # from pytest-subprocess. @@ -224,7 +228,7 @@ def test_make_workers_requires_config(harness, fs, fp, empty_key): pytest.raises(BlockedOnConfig, harness.charm._make_workers) -def test_make_workers(harness, fs, fp, fake_user): +def test_make_only_vanilla_ubuntu_workers(harness, fs, fp, fake_user): fs.add_real_directory(harness.charm.charm_dir / "templates") fp.keep_last_process(True) fp.register( @@ -287,6 +291,7 @@ def test_make_workers(harness, fs, fp, fake_user): "create-lp-tests-lxd", "xenial", "/var/lib/buildbot/slaves/xenial-lxd-worker", + "", ], ["update-rc.d", "buildslave", "defaults"], ["service", "buildslave", "restart"], @@ -303,6 +308,151 @@ def test_make_workers(harness, fs, fp, fake_user): assert harness.charm._stored.ubuntu_series == {"xenial"} +def test_make_both_vanilla_ubuntu_and_flavors_workers( + harness, fs, fp, fake_user +): + fs.add_real_directory(harness.charm.charm_dir / "templates") + fp.keep_last_process(True) + fp.register( + ["sudo", "-Hu", "buildbot", "lxc", "image", "list", "-f", "json"], + stdout=json.dumps({}), + ) + fp.register([fp.any()]) + harness._update_config( + { + "manager-host": "manager.example.com", + "buildbot-password": "secret", + "ubuntu-series": "focal", + # focal-postgres14 should be allowed as focal + # series is enabled. xenial-postgres14 should be + # skipped + "series-flavors": "focal-postgres14 xenial-postgres14", + } + ) + + harness.charm._make_workers() + + # we can only access the final status that was set + assert list(fp.calls) == [ + [ + "sudo", + "-Hu", + "buildbot", + "mkdir", + "-m755", + "-p", + "/var/lib/buildbot/slaves", + ], + ["sudo", "-Hu", "buildbot", "lxc", "image", "list", "-f", "json"], + [ + "sudo", + "-Hu", + "buildbot", + "mkdir", + "-m755", + "-p", + "/var/lib/buildbot/slaves/focal-lxd-worker", + ], + [ + "sudo", + "-Hu", + "buildbot", + "git", + "clone", + "https://git.launchpad.net/launchpad", + "/var/lib/buildbot/slaves/focal-lxd-worker/devel", + ], + [ + "sudo", + "-Hu", + "buildbot", + "git", + "clone", + "https://git.launchpad.net/lp-source-dependencies", + "/var/lib/buildbot/slaves/focal-lxd-worker/" + "dependencies/download-cache", + ], + [ + "sudo", + "-Hu", + "buildbot", + "create-lp-tests-lxd", + "focal", + "/var/lib/buildbot/slaves/focal-lxd-worker", + "", + ], + [ + "sudo", + "-Hu", + "buildbot", + "mkdir", + "-m755", + "-p", + "/var/lib/buildbot/slaves/focal-postgres14-lxd-worker", + ], + [ + "sudo", + "-Hu", + "buildbot", + "git", + "clone", + "https://git.launchpad.net/launchpad", + "/var/lib/buildbot/slaves/focal-postgres14-lxd-worker/devel", + ], + [ + "sudo", + "-Hu", + "buildbot", + "git", + "clone", + "https://git.launchpad.net/lp-source-dependencies", + "/var/lib/buildbot/slaves/focal-postgres14-lxd-worker/" + "dependencies/download-cache", + ], + [ + "sudo", + "-Hu", + "buildbot", + "create-lp-tests-lxd", + "focal", + "/var/lib/buildbot/slaves/focal-postgres14-lxd-worker", + "postgres14", + ], + ["update-rc.d", "buildslave", "defaults"], + ["service", "buildslave", "restart"], + ] + buildbot_tac = ( + Path("/var/lib/buildbot/slaves/buildbot.tac").read_text().splitlines() + ) + assert "basedir = '/var/lib/buildbot/slaves'" in buildbot_tac + assert "manager_host = 'manager.example.com'" in buildbot_tac + assert "manager_port = 9989" in buildbot_tac + assert "password = 'secret'" in buildbot_tac + buildslave = Path("/etc/default/buildslave").read_text() + assert 'SLAVE_BASEDIR[1]="/var/lib/buildbot/slaves"' in buildslave + assert harness.charm._stored.ubuntu_series == {"focal"} + + +def test_raise_error_workers(harness, fs, fp, fake_user): + fs.add_real_directory(harness.charm.charm_dir / "templates") + fp.keep_last_process(True) + fp.register( + ["sudo", "-Hu", "buildbot", "lxc", "image", "list", "-f", "json"], + stdout=json.dumps({}), + ) + fp.register([fp.any()]) + harness._update_config( + { + "manager-host": "manager.example.com", + "buildbot-password": "secret", + "ubuntu-series": "focal", + "series-flavors": "postgres14", + } + ) + + pytest.raises(InvalidFlavorValueFormat, harness.charm._make_workers) + + def test_make_workers_deletes_obsolete_workers(harness, fs, fp, fake_user): fs.add_real_directory(harness.charm.charm_dir / "templates") fp.keep_last_process(True) diff --git a/create-lp-tests-lxd b/create-lp-tests-lxd index fdd54fe..5ba9ba4 100755 --- a/create-lp-tests-lxd +++ b/create-lp-tests-lxd @@ -5,6 +5,7 @@ import os import shlex import subprocess import sys +from collections import defaultdict from datetime import datetime from os.path import expanduser from pathlib import Path @@ -57,6 +58,22 @@ LXD_HOSTS_CONTENT = ( ) +class Flavor: + def __init__(self, extra_ppas=None, extra_lp_deb_dependencies=None): + self.extra_ppas = list(extra_ppas) if extra_ppas else [] + self.extra_lp_deb_dependencies = ( + list(extra_lp_deb_dependencies) + if extra_lp_deb_dependencies + else [] + ) + + +FLAVORS = defaultdict(Flavor) +FLAVORS["postgres14"] = Flavor( + ["ppa:launchpad/postgresql-ports"], ["launchpad-database-dependencies-14"] +) + + def _put_file(container, source, destination): with open(source) as source_file: container.files.put(destination, source_file.read()) @@ -157,7 +174,7 @@ def create_new_instance(client, image_name, series, code_directory): return container -def install_code(container, series, directory): +def install_code(container, series, directory, flavor_name=""): print("Configuring apt") _exec(container, ["apt-get", "update"]) _exec(container, ["apt-get", "upgrade", "-y"]) @@ -165,12 +182,31 @@ def install_code(container, series, directory): container, ["apt-get", "install", "-y", "software-properties-common"] ) + if flavor_name and flavor_name not in FLAVORS.keys(): + raise ValueError( + "Flavor {} is not supported. \ + Please provide one of the supported flavors - {}".format( + flavor_name, ", ".join(FLAVORS.keys()) + ) + ) + + flavor = FLAVORS[flavor_name] + + for ppa in flavor.extra_ppas: + _exec(container, ["add-apt-repository", "-y", "{}".format(ppa)]) + for apt_repository in APT_REPOSITORIES: repository = apt_repository.format(distro=series) _exec(container, ["add-apt-repository", "-y", "{}".format(repository)]) _exec(container, ["apt-get", "update"]) + print("Installing flavor apt dependencies") + _exec( + container, + ["apt-get", "install", "-y"] + flavor.extra_lp_deb_dependencies, + ) + print("Installing Launchpad apt dependencies") _exec( container, @@ -284,9 +320,17 @@ if __name__ == "__main__": parser.add_argument( "directory", type=str, help="Directory to mount code from." ) - + parser.add_argument( + "flavor", + type=str, + help="Series flavor to build. Possible values: {}".format( + ", ".join(FLAVORS.keys()) + ), + ) args = parser.parse_args() - image_name = "lptests-{}".format(args.series) + image_name = "lptests-{}{}".format( + args.series, "-" + args.flavor if args.flavor else "" + ) code_directory = args.directory if not Path(code_directory).exists(): @@ -309,6 +353,6 @@ if __name__ == "__main__": container = create_new_instance( client, image_name, args.series, code_directory ) - install_code(container, args.series, code_directory) + install_code(container, args.series, code_directory, args.flavor) publish_image(container, image_name) remove_build_container(container)
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp