Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions scripts/resume_schwab_single_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,23 @@ class TemplateDifference(ValueError):
def __init__(self, desired, serving):
super().__init__("template_differs")
fields = ("containers", "containerConcurrency", "timeoutSeconds", "serviceAccountName", "volumes")
self.comparisons = {name: desired.get(name) == serving.get(name) for name in fields}
self.comparisons["other_fields"] = {k: v for k, v in desired.items() if k not in fields} == {k: v for k, v in serving.items() if k not in fields}
self.comparisons = {name: "match" if desired.get(name) == serving.get(name) else "mismatch" for name in fields}
self.comparisons["other_fields"] = "match" if {k: v for k, v in desired.items() if k not in fields} == {k: v for k, v in serving.items() if k not in fields} else "mismatch"
left, right = desired.get("containers", []), serving.get("containers", [])
if len(left) == len(right) == 1:
names = ("image", "env", "resources", "ports", "startupProbe", "command", "args", "name")
self.comparisons.update({"container_" + name: left[0].get(name) == right[0].get(name) for name in names})
self.comparisons["container_other_fields"] = {k: v for k, v in left[0].items() if k not in names} == {k: v for k, v in right[0].items() if k not in names}
self.comparisons.update({"container_" + name: "match" if left[0].get(name) == right[0].get(name) else "mismatch" for name in names})
self.comparisons["container_other_fields"] = "match" if {k: v for k, v in left[0].items() if k not in names} == {k: v for k, v in right[0].items() if k not in names} else "mismatch"


def _runtime_spec(spec):
"""Use the previously verified Cloud Run revision representation rules."""
spec = copy.deepcopy(spec)
spec.pop("servingState", None)
for container in spec["containers"]:
container.pop("name", None)
container["env"] = sorted(container.get("env", []), key=lambda item: item["name"])
return spec


def _command(args):
Expand Down Expand Up @@ -61,7 +71,7 @@ def inspect(environ, *, describe=metadata._describe):
if target != paused or build_live_continuity(target["live_continuity"]).baseline_target_sha256 != runtime_target_fingerprint(target):
raise ValueError("target_mismatch")
desired = before.get("spec", {}).get("template", {}).get("spec")
if desired != revision.get("spec"):
if not isinstance(desired, dict) or _runtime_spec(desired) != _runtime_spec(revision["spec"]):
if not isinstance(desired, dict) or not isinstance(revision.get("spec"), dict):
raise ValueError("template_differs")
raise TemplateDifference(desired, revision["spec"])
Expand Down Expand Up @@ -91,9 +101,9 @@ def execute(environ, *, describe=metadata._describe, command=_command):
scheduler = "unknown"
except Exception:
pass
return {"status": "ready_for_review", "template_matches_serving": True,
"paused": True, "declaration_state": declared_state, "scheduler_run": scheduler,
"cloud_write_attempted": False}
return {"status": "ready_for_review", "template_comparison": "match",
"continuity_state": "PAUSED", "declaration_state": declared_state, "scheduler_run": scheduler,
"cloud_write": "none"}
if mode != "apply" or declared_state != "ACTIVE_LKG":
raise ValueError("active_declaration_required")
run_id = environ.get("GITHUB_RUN_ID", "")
Expand Down Expand Up @@ -126,7 +136,7 @@ def execute(environ, *, describe=metadata._describe, command=_command):
for item in expected_spec["containers"][0]["env"]:
if item.get("name") == "RUNTIME_TARGET_JSON":
item["value"] = value
if staged.get("spec") != expected_spec:
if _runtime_spec(staged.get("spec", {})) != _runtime_spec(expected_spec):
raise ValueError("candidate_configuration_mismatch")
ready = {x.get("type"): x.get("status") for x in staged.get("status", {}).get("conditions", [])}
if ready.get("Ready") != "True":
Expand All @@ -146,13 +156,13 @@ def execute(environ, *, describe=metadata._describe, command=_command):
confirmed = describe("services", SERVICE, PROJECT, REGION)
except Exception:
raise RuntimeError("adoption_readback_unknown_do_not_retry") from None
if (metadata._serving(readback, SERVICE) != candidate or adopted.get("spec") != expected_spec
if (metadata._serving(readback, SERVICE) != candidate or _runtime_spec(adopted.get("spec", {})) != _runtime_spec(expected_spec)
or metadata._serving(confirmed, SERVICE) != candidate
or readback.get("metadata", {}).get("resourceVersion") != confirmed.get("metadata", {}).get("resourceVersion")):
raise ValueError("adoption_readback_mismatch")
return {"status": "active_readback", "template_matches_serving": True,
"paused": False, "declaration_state": declared_state, "cloud_write_attempted": True,
"broker_requested": False, "natural_cycle_observed": False}
return {"status": "active_readback", "template_comparison": "match",
"continuity_state": "ACTIVE_LKG", "declaration_state": declared_state, "cloud_write": "attempted",
"broker_read": "none", "natural_cycle": "not_observed"}


def main():
Expand Down
21 changes: 18 additions & 3 deletions tests/test_resume_schwab_single_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,26 @@ def test_template_difference_exposes_only_fixed_boolean_comparisons():
desired = {"containers": [{"image": "PRIVATE_A", "env": [{"name": "PRIVATE_FIELD"}]}]}
serving = {"containers": [{"image": "PRIVATE_B", "env": [{"name": "PRIVATE_FIELD"}]}]}
result = resume.TemplateDifference(desired, serving).comparisons
assert result["container_image"] is False and result["container_env"] is True
assert all(type(value) is bool for value in result.values())
assert result["container_image"] == "mismatch" and result["container_env"] == "match"
assert all(value in {"match", "mismatch"} for value in result.values())
assert "PRIVATE" not in json.dumps(result)


def test_runtime_comparison_ignores_only_known_server_representation():
original = {"containers": [{"name": "generated", "image": "immutable",
"env": [{"name": "B", "value": "2"}, {"name": "A", "value": "1"}]}],
"servingState": "active", "timeoutSeconds": 300}
equivalent = {"containers": [{"image": "immutable",
"env": [{"name": "A", "value": "1"}, {"name": "B", "value": "2"}]}],
"timeoutSeconds": 300}
assert resume._runtime_spec(original) == resume._runtime_spec(equivalent)
equivalent["containers"][0]["image"] = "other"
assert resume._runtime_spec(original) != resume._runtime_spec(equivalent)
equivalent["containers"][0]["image"] = "immutable"
equivalent["timeoutSeconds"] = 10
assert resume._runtime_spec(original) != resume._runtime_spec(equivalent)


def test_apply_writes_only_state_then_adopts_and_reads_back(case, monkeypatch):
service, revision, target = case
declared = copy.deepcopy(target)
Expand All @@ -68,7 +83,7 @@ def describe(kind, name, *_):
if state["phase"] == "adopted": result["status"]["traffic"] = [{"revisionName": candidate, "percent": 100}]
return result
result = resume.execute({"RESUME_OPERATION": "apply", "GITHUB_RUN_ID": "123"}, describe=describe, command=command)
assert result["status"] == "active_readback" and not result["natural_cycle_observed"]
assert result["status"] == "active_readback" and result["natural_cycle"] == "not_observed"
assert len(state["calls"]) == 2
assert "--no-traffic" in state["calls"][0]
update = next(x for x in state["calls"][0] if x.startswith("--update-env-vars="))
Expand Down
Loading