142 lines
6.8 KiB
Python
142 lines
6.8 KiB
Python
import contextlib
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
spec = importlib.util.spec_from_file_location("gate_b_bundle", ROOT / "deploy" / "gate_b_bundle.py")
|
|
bundle = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(bundle)
|
|
|
|
|
|
COMMIT = "a" * 40
|
|
API = {"reference": f"guestops-api:{COMMIT}", "id": "sha256:" + "b" * 64}
|
|
WORKER = {"reference": f"guestops-worker:{COMMIT}", "id": "sha256:" + "c" * 64}
|
|
ORIGIN = "https://sandbox-guestops.futuresens.co.uk"
|
|
|
|
|
|
class GateBBundleTests(unittest.TestCase):
|
|
def fixture(self, directory: str):
|
|
root = Path(directory)
|
|
archive = root / "GuestOps-0.2.1-aaaaaaaaaaaa.tar.gz"
|
|
source = root / "GuestOps-0.2.1-aaaaaaaaaaaa.source.json"
|
|
release = root / "release-record.json"
|
|
metrics = root / "host-metrics.json"
|
|
archive.write_bytes(b"approved source")
|
|
artifact = {
|
|
"name": archive.name,
|
|
"size": archive.stat().st_size,
|
|
"sha256": hashlib.sha256(archive.read_bytes()).hexdigest(),
|
|
}
|
|
source.write_text(json.dumps({"schemaVersion": 1, "version": "0.2.1", "commit": COMMIT, "artifact": artifact}), encoding="utf-8")
|
|
release.write_text(json.dumps({
|
|
"schemaVersion": 1, "version": "0.2.1", "commit": COMMIT,
|
|
"artifact": artifact, "images": {"api": API, "worker": WORKER},
|
|
}), encoding="utf-8")
|
|
release_sha = hashlib.sha256(release.read_bytes()).hexdigest()
|
|
metrics.write_bytes(b'{"cpu":40,"memory":35}')
|
|
|
|
values = {}
|
|
paths = {}
|
|
for name in bundle.RECORD_KEYS:
|
|
value = {"releaseCommit": COMMIT, "releaseRecordSha256": release_sha}
|
|
if name in {
|
|
"debian-host", "persistence", "backup-restore", "google-mailbox",
|
|
"automation", "identity-privacy", "inbox-usability", "incident-support",
|
|
"pilot-findings",
|
|
}:
|
|
value["environment"] = ORIGIN
|
|
if name in {"debian-host", "persistence", "backup-restore"}:
|
|
value["archiveSha256"] = artifact["sha256"]
|
|
value["images"] = {"api": API, "worker": WORKER}
|
|
values[name] = value
|
|
|
|
values["capacity"].update({
|
|
"schemaVersion": 1, "kind": "guestops-read-only-capacity",
|
|
"originHost": "sandbox-guestops.futuresens.co.uk",
|
|
"paths": ["/health/ready", "/api/hotel", "/api/conversations/page"],
|
|
"concurrency": 10, "requests": 100, "successes": 100, "failures": 0,
|
|
"errorRate": 0.0, "latencyMs": {"median": 100, "p95": 250, "maximum": 300},
|
|
})
|
|
pilot_path = root / "pilot-findings.json"
|
|
pilot_path.write_text(json.dumps(values["pilot-findings"]), encoding="utf-8")
|
|
paths["pilot-findings"] = pilot_path
|
|
capacity_path = root / "capacity.json"
|
|
capacity_path.write_text(json.dumps(values["capacity"]), encoding="utf-8")
|
|
paths["capacity"] = capacity_path
|
|
values["pilot-approval"].update({
|
|
"capacity": {
|
|
"reportSha256": hashlib.sha256(capacity_path.read_bytes()).hexdigest(),
|
|
"hostMetricsSha256": hashlib.sha256(metrics.read_bytes()).hexdigest(),
|
|
"observedConcurrency": 10, "observedP95Ms": 250, "observedErrorRate": 0.0,
|
|
},
|
|
"pilot": {"recordSha256": hashlib.sha256(pilot_path.read_bytes()).hexdigest()},
|
|
})
|
|
for name in bundle.RECORD_KEYS - paths.keys():
|
|
path = root / f"{name}.json"
|
|
path.write_text(json.dumps(values[name]), encoding="utf-8")
|
|
paths[name] = path
|
|
records = {name: (paths[name], values[name]) for name in bundle.RECORD_KEYS}
|
|
return archive, source, release, metrics, records
|
|
|
|
@contextlib.contextmanager
|
|
def mocked_individual_validators(self):
|
|
patches = [
|
|
patch.object(bundle.debian_acceptance, "validate_pair"),
|
|
patch.object(bundle.backup_restore_acceptance, "validate"),
|
|
patch.object(bundle.google_acceptance, "validate"),
|
|
patch.object(bundle.automation_acceptance, "validate"),
|
|
patch.object(bundle.identity_privacy_acceptance, "validate"),
|
|
patch.object(bundle.desktop_acceptance, "validate"),
|
|
patch.object(bundle.incident_exercise, "validate"),
|
|
patch.object(bundle.pilot_run, "validate"),
|
|
patch.object(bundle.pilot_approval, "validate"),
|
|
]
|
|
with contextlib.ExitStack() as stack:
|
|
for item in patches:
|
|
stack.enter_context(item)
|
|
yield
|
|
|
|
def test_complete_bundle_is_bound_to_one_release(self):
|
|
with tempfile.TemporaryDirectory() as directory, self.mocked_individual_validators():
|
|
archive, source, release, metrics, records = self.fixture(directory)
|
|
result = bundle.validate_bundle(archive, source, release, records, metrics)
|
|
self.assertTrue(result["validated"])
|
|
self.assertEqual(result["releaseCommit"], COMMIT)
|
|
self.assertEqual(set(result["records"]), bundle.RECORD_KEYS)
|
|
|
|
def test_different_environment_or_checksum_is_rejected(self):
|
|
with tempfile.TemporaryDirectory() as directory, self.mocked_individual_validators():
|
|
archive, source, release, metrics, records = self.fixture(directory)
|
|
records["google-mailbox"][1]["environment"] = "https://other.example.invalid"
|
|
with self.assertRaisesRegex(ValueError, "different environments"):
|
|
bundle.validate_bundle(archive, source, release, records, metrics)
|
|
|
|
with tempfile.TemporaryDirectory() as directory, self.mocked_individual_validators():
|
|
archive, source, release, metrics, records = self.fixture(directory)
|
|
records["pilot-approval"][1]["capacity"]["reportSha256"] = "0" * 64
|
|
with self.assertRaisesRegex(ValueError, "capacity report checksum"):
|
|
bundle.validate_bundle(archive, source, release, records, metrics)
|
|
|
|
def test_capacity_counts_and_latency_are_checked(self):
|
|
report = {
|
|
"schemaVersion": 1, "kind": "guestops-read-only-capacity",
|
|
"releaseCommit": COMMIT, "releaseRecordSha256": "d" * 64,
|
|
"paths": ["/health/ready", "/api/hotel", "/api/conversations/page"],
|
|
"concurrency": 10, "requests": 10, "successes": 9, "failures": 1,
|
|
"errorRate": 0.1, "latencyMs": {"median": 10, "p95": 20, "maximum": 30},
|
|
}
|
|
bundle.validate_capacity(report, COMMIT, "d" * 64)
|
|
report["failures"] = 2
|
|
with self.assertRaisesRegex(ValueError, "do not match requests"):
|
|
bundle.validate_capacity(report, COMMIT, "d" * 64)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|