From 4da569442ca121c786a5ffc3770a468f1bc66d76 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Thu, 29 Aug 2019 12:30:39 -0700 Subject: [PATCH] tests: Add tests for the new lifted module These tests cover most of the module's functions, except for the queue monitor, and the actual execution of the playbooks. --- Makefile | 2 +- lorax.spec | 1 + tests/lifted/__init__.py | 0 tests/lifted/profiles.py | 48 +++++++++++++ tests/lifted/test_config.py | 52 ++++++++++++++ tests/lifted/test_providers.py | 95 +++++++++++++++++++++++++ tests/lifted/test_queue.py | 119 +++++++++++++++++++++++++++++++ tests/lifted/test_upload.py | 126 +++++++++++++++++++++++++++++++++ 8 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 tests/lifted/__init__.py create mode 100644 tests/lifted/profiles.py create mode 100644 tests/lifted/test_config.py create mode 100644 tests/lifted/test_providers.py create mode 100644 tests/lifted/test_queue.py create mode 100644 tests/lifted/test_upload.py diff --git a/Makefile b/Makefile index 13fe2cf1..69992656 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ test: @echo "*** Running tests ***" PYTHONPATH=$(PYTHONPATH):./src/ $(PYTHON) -m nose -v --with-coverage --cover-erase --cover-branches \ --cover-package=pylorax --cover-inclusive \ - ./tests/pylorax/ ./tests/composer/ + ./tests/pylorax/ ./tests/composer/ ./tests/lifted/ coverage3 report -m [ -f "/usr/bin/coveralls" ] && [ -n "$(COVERALLS_REPO_TOKEN)" ] && coveralls || echo diff --git a/lorax.spec b/lorax.spec index 68af01fd..5df4e720 100644 --- a/lorax.spec +++ b/lorax.spec @@ -152,6 +152,7 @@ Requires: python3-rpmfluff Requires: git Requires: xz Requires: createrepo_c +Requires: python3-ansible-runner %{?systemd_requires} BuildRequires: systemd diff --git a/tests/lifted/__init__.py b/tests/lifted/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/lifted/profiles.py b/tests/lifted/profiles.py new file mode 100644 index 00000000..e973d86f --- /dev/null +++ b/tests/lifted/profiles.py @@ -0,0 +1,48 @@ +# +# Copyright (C) 2019 Red Hat, Inc. +# +# 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 2 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 . +# + +# test profile settings for each provider +test_profiles = { + "azure": ["azure-profile", { + "resource_group": "production", + "storage_account_name": "HomerSimpson", + "storage_container": "plastic", + "subscription_id": "SpringfieldNuclear", + "client_id": "DonutGuy", + "secret": "I Like sprinkles", + "tenant": "Bart", + "location": "Springfield" + }], + "dummy": ["dummy-profile", {}], + "openstack": ["openstack-profile", { + "auth_url": "https://localhost/auth/url", + "username": "ChuckBurns", + "password": "Excellent!", + "project_name": "Springfield Nuclear", + "user_domain_name": "chuck.burns.localhost", + "project_domain_name": "springfield.burns.localhost", + "is_public": True + }], + "vsphere": ["vsphere-profile", { + "datacenter": "Lisa's Closet", + "datastore": "storage-crate-alpha", + "host": "marge", + "folder": "the.green.one", + "username": "LisaSimpson", + "password": "EmbraceNothingnes" + }] +} diff --git a/tests/lifted/test_config.py b/tests/lifted/test_config.py new file mode 100644 index 00000000..c509a8f6 --- /dev/null +++ b/tests/lifted/test_config.py @@ -0,0 +1,52 @@ +# +# Copyright (C) 2019 Red Hat, Inc. +# +# 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 2 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 . +# +import unittest + +import lifted.config +import pylorax.api.config + +class ConfigTestCase(unittest.TestCase): + def test_lifted_config(self): + """Test lifted config setup""" + config = pylorax.api.config.configure(test_config=True) + lifted.config.configure(config) + + self.assertTrue(config.get("upload", "providers_dir").startswith(config.get("composer", "share_dir"))) + self.assertTrue(config.get("upload", "queue_dir").startswith(config.get("composer", "lib_dir"))) + self.assertTrue(config.get("upload", "settings_dir").startswith(config.get("composer", "lib_dir"))) + + def test_lifted_sharedir_config(self): + """Test lifted config setup with custom share_dir""" + config = pylorax.api.config.configure(test_config=True) + config.set("composer", "share_dir", "/custom/share/path") + lifted.config.configure(config) + + self.assertEqual(config.get("composer", "share_dir"), "/custom/share/path") + self.assertTrue(config.get("upload", "providers_dir").startswith(config.get("composer", "share_dir"))) + self.assertTrue(config.get("upload", "queue_dir").startswith(config.get("composer", "lib_dir"))) + self.assertTrue(config.get("upload", "settings_dir").startswith(config.get("composer", "lib_dir"))) + + def test_lifted_libdir_config(self): + """Test lifted config setup with custom lib_dir""" + config = pylorax.api.config.configure(test_config=True) + config.set("composer", "lib_dir", "/custom/lib/path") + lifted.config.configure(config) + + self.assertEqual(config.get("composer", "lib_dir"), "/custom/lib/path") + self.assertTrue(config.get("upload", "providers_dir").startswith(config.get("composer", "share_dir"))) + self.assertTrue(config.get("upload", "queue_dir").startswith(config.get("composer", "lib_dir"))) + self.assertTrue(config.get("upload", "settings_dir").startswith(config.get("composer", "lib_dir"))) diff --git a/tests/lifted/test_providers.py b/tests/lifted/test_providers.py new file mode 100644 index 00000000..0c73261f --- /dev/null +++ b/tests/lifted/test_providers.py @@ -0,0 +1,95 @@ +# +# Copyright (C) 2019 Red Hat, Inc. +# +# 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 2 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 . +# +import os +import shutil +import tempfile +import unittest + +import lifted.config +from lifted.providers import list_providers, resolve_provider, resolve_playbook_path, save_settings +from lifted.providers import load_profiles, validate_settings +import pylorax.api.config +from pylorax.sysutils import joinpaths + +from tests.lifted.profiles import test_profiles + +class ProvidersTestCase(unittest.TestCase): + @classmethod + def setUpClass(self): + self.root_dir = tempfile.mkdtemp(prefix="lifted.test.") + self.config = pylorax.api.config.configure(root_dir=self.root_dir, test_config=True) + self.config.set("composer", "share_dir", os.path.realpath("./share/")) + lifted.config.configure(self.config) + + @classmethod + def tearDownClass(self): + shutil.rmtree(self.root_dir) + + def test_list_providers(self): + p = list_providers(self.config["upload"]) + self.assertEqual(p, ['azure', 'dummy', 'openstack', 'vsphere']) + + def test_resolve_provider(self): + for p in list_providers(self.config["upload"]): + print(p) + info = resolve_provider(self.config["upload"], p) + self.assertTrue("display" in info) + self.assertTrue("supported_types" in info) + self.assertTrue("settings-info" in info) + + def test_resolve_playbook_path(self): + for p in list_providers(self.config["upload"]): + print(p) + self.assertTrue(len(resolve_playbook_path(self.config["upload"], p)) > 0) + + def test_resolve_playbook_path_error(self): + with self.assertRaises(RuntimeError): + resolve_playbook_path(self.config["upload"], "foobar") + + def test_validate_settings(self): + for p in list_providers(self.config["upload"]): + print(p) + validate_settings(self.config["upload"], p, test_profiles[p][1]) + + def test_validate_settings_errors(self): + with self.assertRaises(ValueError): + validate_settings(self.config["upload"], "dummy", test_profiles["dummy"][1], image_name="") + + with self.assertRaises(ValueError): + validate_settings(self.config["upload"], "azure", {"wrong-key": "wrong value"}) + + with self.assertRaises(ValueError): + validate_settings(self.config["upload"], "azure", {"secret": False}) + + # TODO - test regex, needs a provider with a regex + + def test_save_settings(self): + """Test saving profiles""" + for p in list_providers(self.config["upload"]): + print(p) + save_settings(self.config["upload"], p, test_profiles[p][0], test_profiles[p][1]) + + profile_dir = joinpaths(self.config.get("upload", "settings_dir"), p, test_profiles[p][0]+".toml") + self.assertTrue(os.path.exists(profile_dir)) + + # This *must* run after test_save_settings, _zz_ ensures that happens + def test_zz_load_profiles(self): + """Test loading profiles""" + for p in list_providers(self.config["upload"]): + print(p) + profile = load_profiles(self.config["upload"], p) + self.assertTrue(test_profiles[p][0] in profile) diff --git a/tests/lifted/test_queue.py b/tests/lifted/test_queue.py new file mode 100644 index 00000000..7491cb06 --- /dev/null +++ b/tests/lifted/test_queue.py @@ -0,0 +1,119 @@ +# +# Copyright (C) 2019 Red Hat, Inc. +# +# 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 2 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 . +# +import os +import shutil +import tempfile +import unittest + +import lifted.config +from lifted.providers import list_providers +from lifted.queue import _write_callback, create_upload, get_all_uploads, get_upload, get_uploads +from lifted.queue import ready_upload, reset_upload, cancel_upload +import pylorax.api.config + +from tests.lifted.profiles import test_profiles + +class QueueTestCase(unittest.TestCase): + @classmethod + def setUpClass(self): + self.root_dir = tempfile.mkdtemp(prefix="lifted.test.") + self.config = pylorax.api.config.configure(root_dir=self.root_dir, test_config=True) + self.config.set("composer", "share_dir", os.path.realpath("./share/")) + lifted.config.configure(self.config) + + self.upload_uuids = [] + + @classmethod + def tearDownClass(self): + shutil.rmtree(self.root_dir) + + # This should run first, it writes uploads to the queue directory + def test_00_create_upload(self): + """Test creating an upload for each provider""" + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1]) + summary = upload.summary() + self.assertEqual(summary["provider_name"], p) + self.assertEqual(summary["image_name"], "test-image") + self.assertTrue(summary["status"], "WAITING") + + self.upload_uuids.append(summary["uuid"]) + self.assertTrue(len(self.upload_uuids) > 0) + self.assertTrue(len(self.upload_uuids), len(list_providers(self.config["upload"]))) + + def test_01_get_all_uploads(self): + """Test listing all the uploads""" + uploads = get_all_uploads(self.config["upload"]) + # Should be one upload per provider + providers = sorted([u.provider_name for u in uploads]) + self.assertEqual(providers, list_providers(self.config["upload"])) + + def test_02_get_upload(self): + """Test listing specific uploads by uuid""" + for uuid in self.upload_uuids: + upload = get_upload(self.config["upload"], uuid) + self.assertTrue(upload.uuid, uuid) + + def test_02_get_upload_error(self): + """Test listing an unknown upload uuid""" + with self.assertRaises(RuntimeError): + get_upload(self.config["upload"], "not-a-valid-uuid") + + def test_03_get_uploads(self): + """Test listing multiple uploads by uuid""" + uploads = get_uploads(self.config["upload"], self.upload_uuids) + uuids = sorted([u.uuid for u in uploads]) + self.assertTrue(uuids, sorted(self.upload_uuids)) + + def test_04_ready_upload(self): + """Test ready_upload""" + ready_upload(self.config["upload"], self.upload_uuids[0], "image-test-path") + upload = get_upload(self.config["upload"], self.upload_uuids[0]) + self.assertEqual(upload.image_path, "image-test-path") + + def test_05_reset_upload(self): + """Test reset_upload""" + # Set the status to FAILED so it can be reset + upload = get_upload(self.config["upload"], self.upload_uuids[0]) + upload.set_status("FAILED", _write_callback(self.config["upload"])) + + reset_upload(self.config["upload"], self.upload_uuids[0]) + upload = get_upload(self.config["upload"], self.upload_uuids[0]) + self.assertEqual(upload.status, "READY") + + def test_06_reset_upload_error(self): + """Test reset_upload raising an error""" + with self.assertRaises(RuntimeError): + reset_upload(self.config["upload"], self.upload_uuids[0]) + + def test_07_cancel_upload(self): + """Test cancel_upload""" + cancel_upload(self.config["upload"], self.upload_uuids[0]) + upload = get_upload(self.config["upload"], self.upload_uuids[0]) + self.assertEqual(upload.status, "CANCELLED") + + def test_08_cancel_upload_error(self): + """Test cancel_upload raises an error""" + # Set the status to CANCELED to make sure the cancel will fail + upload = get_upload(self.config["upload"], self.upload_uuids[0]) + upload.set_status("CANCELLED", _write_callback(self.config["upload"])) + + with self.assertRaises(RuntimeError): + cancel_upload(self.config["upload"], self.upload_uuids[0]) + + # TODO test execute diff --git a/tests/lifted/test_upload.py b/tests/lifted/test_upload.py new file mode 100644 index 00000000..a7ed59bb --- /dev/null +++ b/tests/lifted/test_upload.py @@ -0,0 +1,126 @@ +# +# Copyright (C) 2019 Red Hat, Inc. +# +# 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 2 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 . +# +import os +import shutil +import tempfile +import unittest + +import lifted.config +from lifted.providers import list_providers, resolve_playbook_path, validate_settings +from lifted.upload import Upload +import pylorax.api.config + +from tests.lifted.profiles import test_profiles + +# Helper function for creating Upload object +def create_upload(ucfg, provider_name, image_name, settings, status=None, callback=None): + validate_settings(ucfg, provider_name, settings, image_name) + return Upload( + provider_name=provider_name, + playbook_path=resolve_playbook_path(ucfg, provider_name), + image_name=image_name, + settings=settings, + status=status, + status_callback=callback, + ) + + +class UploadTestCase(unittest.TestCase): + @classmethod + def setUpClass(self): + self.root_dir = tempfile.mkdtemp(prefix="lifted.test.") + self.config = pylorax.api.config.configure(root_dir=self.root_dir, test_config=True) + self.config.set("composer", "share_dir", os.path.realpath("./share/")) + lifted.config.configure(self.config) + + @classmethod + def tearDownClass(self): + shutil.rmtree(self.root_dir) + + def test_new_upload(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="READY") + summary = upload.summary() + self.assertEqual(summary["provider_name"], p) + self.assertEqual(summary["image_name"], "test-image") + self.assertTrue(summary["status"], "WAITING") + + def test_serializable(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="READY") + self.assertEqual(upload.serializable()["settings"], test_profiles[p][1]) + self.assertEqual(upload.serializable()["status"], "READY") + + def test_summary(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="READY") + self.assertEqual(upload.summary()["settings"], test_profiles[p][1]) + self.assertEqual(upload.summary()["status"], "READY") + + def test_set_status(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="READY") + self.assertEqual(upload.summary()["status"], "READY") + upload.set_status("WAITING") + self.assertEqual(upload.summary()["status"], "WAITING") + + def test_ready(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="WAITING") + self.assertEqual(upload.summary()["status"], "WAITING") + upload.ready("test-image-path", status_callback=None) + summary = upload.summary() + self.assertEqual(summary["status"], "READY") + self.assertEqual(summary["image_path"], "test-image-path") + + def test_reset(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="CANCELLED") + upload.ready("test-image-path", status_callback=None) + upload.reset(status_callback=None) + self.assertEqual(upload.status, "READY") + + def test_reset_errors(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="WAITING") + with self.assertRaises(RuntimeError): + upload.reset(status_callback=None) + + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="CANCELLED") + with self.assertRaises(RuntimeError): + upload.reset(status_callback=None) + + def test_cancel(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="WAITING") + upload.cancel() + self.assertEqual(upload.status, "CANCELLED") + + def test_cancel_error(self): + for p in list_providers(self.config["upload"]): + print(p) + upload = create_upload(self.config["upload"], p, "test-image", test_profiles[p][1], status="CANCELLED") + with self.assertRaises(RuntimeError): + upload.cancel()