Commit 2cff2af2 authored by Konstantinos Togias's avatar Konstantinos Togias
Browse files

Merge branch 'feat/compute-resource-support' into 'refactor/tfsdk-architecture-alignment'

feat: Compute resource (required) support for container type

See merge request !206
parents 37a19d6f 29bfaa4a
Loading
Loading
Loading
Loading
+4 −0
Changes for ci/README.md: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -54,3 +54,7 @@ The downstream trigger disables inheritance of SDK default variables and passes
only the explicit SDK artifact coordinates. Job-only paths such as TMPDIR must
be established by the owning project. Candidate rc1 exposed this boundary issue
and was not published; rc2 retains a new immutable tag instead of moving rc1.

Packaged container chart checks render canonical CPU millicores and decimal MB
requests, including omission and absence of invented limits. Provider regressions
exercise the same mapping through activation and update.
+17 −5
Changes for ci/verify.py: 17 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -182,13 +182,23 @@ def charts():
    chart = Path(openop_tfsdk.__file__).parent / "contract/adapters/charts/container"
    helm = helm_binary()
    image = "example.invalid/fixture@sha256:" + "a" * 64
    for name, service_type, ports in [
        ("none", "ClusterIP", []),
        ("internal", "ClusterIP", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}]),
        ("external", "NodePort", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}]),
    for name, service_type, ports, requests in [
        ("none", "ClusterIP", [], {}),
        ("internal", "ClusterIP", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}], {}),
        ("external", "NodePort", [{"name": "p80-tcp", "port": 80, "protocol": "TCP"}], {}),
        ("requests", "ClusterIP", [], {"cpu": "250m", "memory": "257M"}),
    ]:
        values = OUT / f"chart-{name}.json"
        write_json(values, {"image": image, "serviceType": service_type, "ports": ports})
        expected_resources = {"requests": requests} if requests else {}
        write_json(
            values,
            {
                "image": image,
                "serviceType": service_type,
                "ports": ports,
                "resources": expected_resources,
            },
        )
        run(helm, "lint", chart, "--strict", "-f", values)
        raw = subprocess.check_output(
            [
@@ -208,6 +218,8 @@ def charts():
        deployment = objects["Deployment"]
        spec = deployment["spec"]["template"]["spec"]["containers"][0]
        assert spec["image"] == image
        assert spec.get("resources", {}) == expected_resources
        assert "limits" not in spec.get("resources", {})
        assert spec.get("ports", []) == [
            {"name": p["name"], "containerPort": p["port"], "protocol": p["protocol"]}
            for p in ports
+4 −0
Changes for src/openop_tfsdk/contract/adapters/charts/container/templates/deployment.yaml: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -25,3 +25,7 @@ spec:
              protocol: {{ .protocol }}
            {{- end }}
          {{- end }}
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
+2 −0
Changes for src/openop_tfsdk/contract/adapters/charts/container/values.yaml: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -3,3 +3,5 @@ image: ""
# Each port: {name, port, protocol}.
ports: []
serviceType: ClusterIP
# Kubernetes container resources (requests/limits). Empty when no compute intent.
resources: {}
+17 −0
Changes for src/openop_tfsdk/contract/adapters/kubernetes.py: 17 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -99,10 +99,27 @@ def _chart_args(unit) -> list:
        raise ValidationError(
            "kubernetes container does not support mixed internal and external ports"
        )
    compute = (
        getattr(unit.resource_requirements, "compute", None) if unit.resource_requirements else None
    )
    requests: dict[str, str] = {}
    if compute is not None:
        cpu_millicores = getattr(compute, "cpu_millicores", None)
        memory_mb = getattr(compute, "memory_mb", None)
        if cpu_millicores is not None and cpu_millicores < 0:
            raise ValidationError("container compute requests must be nonnegative")
        if memory_mb is not None and memory_mb < 0:
            raise ValidationError("container compute requests must be nonnegative")
        if cpu_millicores is not None and cpu_millicores > 0:
            requests["cpu"] = f"{cpu_millicores}m"
        if memory_mb is not None and memory_mb > 0:
            requests["memory"] = f"{memory_mb}M"
    resources = {"requests": requests} if requests else {}
    values = {
        "image": unit.artifact_ref,
        "ports": list(ports.values()),
        "serviceType": "NodePort" if external else "ClusterIP",
        "resources": resources,
    }
    return [CONTAINER_CHART] + [
        arg for key, value in values.items() for arg in ("--set-json", f"{key}={json.dumps(value)}")
Loading