Loading docs/20260812_1347_simulation-support-for-cridge.md 0 → 100644 +80 −0 Original line number Diff line number Diff line # CRIDGE support for OSOM's tentative-planning simulation contract ## What changed CRIDGE now answers OSOM's "tentative planning" (what-if) dry-run requests instead of timing out. When an operator runs a simulated plan with `simulateAdapters=true`, OSOM's `CROrchestrationService` / `PlanningDay2AdapterSimulator` send deploy/patch/delete requests to CRIDGE's `/PLANNING`-suffixed queues (`CRD.DEPLOY.CR_REQ/PLANNING`, `CRD.PATCH.CR_REQ/PLANNING`, `CRD.DELETE.CR_REQ/PLANNING`) rather than the production queues. Previously nothing consumed those queues, so every simulated plan involving a CR-backed service reported CRIDGE as unreachable (`AdapterInvocationException`, `NO_REPLY`) after ~5s, and the owning service showed as failed. This is a pure addition: a new `org.etsi.osl.cridge.simulation` package with its own routes and beans. **No production class (`CRRouteBuilder`, `KubernetesClientResource`) or production queue property was modified.** Production traffic, queues, and behavior are unchanged. ## New files - `src/main/java/org/etsi/osl/cridge/simulation/CRPlanningRouteBuilder.java` — Camel routes on the three `_PLANNING` queue properties, mirroring `CRRouteBuilder`'s shape, each delegating to `SimulatedKubernetesClientResource`. - `src/main/java/org/etsi/osl/cridge/simulation/SimulatedKubernetesClientResource.java` — `deployCR`/`patchCR`/`deleteCR(headers, crspec)`, same method shapes as production's `KubernetesClientResource` for symmetry, but never touches `KubernetesClient` or the real cluster. Validates the CR spec is well-formed (fabric8 `Serialization.unmarshal`), replies synchronously with production's own string contract (`"OK"` / `"FAIL <msg>"` / `"ERROR <msg>"`), then asynchronously reports resource status/estimates back to OSOM. Deliberately reproduces a pre-existing production naming quirk (`org.etsi.osl.prefixName` is checked under the wrong key, `prefixId`, in `deployCR`/`patchCR`, so both always fall through to `"cr" + resourceId.substring(0,8)`) rather than "fixing" it, so simulated names stay consistent with what production actually assigns today. - `src/main/java/org/etsi/osl/cridge/simulation/PlanningCallbackClient.java` — sends the async "planning resource" callback (`PLANNING.RESOURCE.CREATEORUPDATE`) that actually populates OSOM's plan: resource name/status and cost/duration/energy estimates, using the exact header constants from OSOM's `PlanningResourceCallbackHandler`. Omits unmeasured estimate components rather than zero-padding them (an omitted component means "not measured"; an explicit zero means "measured as zero" — the two are not interchangeable per the OSOM contract). ## Config additions (`application.yml`) - Four new queue properties: `CRD_DEPLOY_CR_REQ_PLANNING`, `CRD_PATCH_CR_REQ_PLANNING`, `CRD_DELETE_CR_REQ_PLANNING`, `PLANNING_RESOURCE_CREATEORUPDATE`. - A `cridge.simulation.*` block of tunables: simulated deploy delay, a local default operation duration (OSOM never sends CRIDGE an `org.etsi.osl.operationDurationSeconds` header, unlike the generic-controller path, so this falls back to 86400s — mirroring OSOM's own `tentative.planning.default-operation-duration-seconds` default), and static placeholder creation/operation/teardown energy and cost figures (clearly labelled as placeholders, not measured data). ## Scoping decisions made with the user - New consumers live in the same CRIDGE process (new routes/beans), not a separate service. - The missing `operationDurationSeconds` header is defaulted locally to 86400s rather than blocking on an OSOM-side fix. - The first version reports static placeholder energy/cost figures alongside duration/status, rather than omitting them. ## Verification performed - `mvn -DskipTests compile` — clean compile of the new package. - Two new unit test classes (plain JUnit 5 + Mockito, no Spring context needed): - `SimulatedKubernetesClientResourceTest` — asserts the `"OK"`/`"FAIL ..."`/`"ERROR ..."` response contract, the `RESERVED`→`AVAILABLE` (create) / `AVAILABLE` (patch) / `UNKNOWN` (delete) status sequence, the reproduced naming quirk, and that a malformed CR spec fails cheaply without ever invoking the callback. - `PlanningCallbackClientTest` — asserts the actual header map sent to `PLANNING.RESOURCE.CREATEORUPDATE` carries the required `org.etsi.osl.serviceOrderId`, echoes `org.etsi.osl.resourceId`, and omits (never zero-pads) unmeasured estimate components; and that a callback with no `serviceOrderId` is skipped rather than sent. - Full suite (`mvn test`): 15/15 tests green — `CridgeIntegrationTest` (6, unchanged) + `PlanningCallbackClientTest` (4, new) + `SimulatedKubernetesClientResourceTest` (5, new). **Not performed in this session:** end-to-end verification against a live broker with both CRIDGE and OSOM running (the plan's `curl`/`jq` checklist against `/replan?simulateAdapters=true`). That requires a running OSOM + ActiveMQ/Artemis environment outside this repo checkout. ## Full plan See `C:\Users\ctranoris\.claude\plans\fluffy-dancing-quasar.md` for the complete approved plan this implementation followed, including the full wire-contract writeup (queues, headers, reply/callback shapes) reverse-engineered from the OSOM source. docs/20260812_1700_honor-operation-duration-header-in-simulation.md 0 → 100644 +50 −0 Original line number Diff line number Diff line # CRIDGE simulation now honours OSOM's operationDurationSeconds header ## Correction to the previous change [20260812_1347_simulation-support-for-cridge.md](20260812_1347_simulation-support-for-cridge.md) built CRIDGE's simulation path on the assumption, confirmed at the time against OSOM source, that OSOM never sent CRIDGE an `org.etsi.osl.operationDurationSeconds` header — so `SimulatedKubernetesClientResource` always sized the operation-phase estimate from a local static default (`cridge.simulation.default-operation-duration-seconds`, 86400s), ignoring the header entirely. Later the same day, OSOM shipped `docs/20260812_1545_operation-window-for-all-planning-sends.md`: every planning send to CRIDGE (create/patch/delete, both simulated and `executeAdaptersForReal`) now carries this header, derived once from the order's requested dates. The user flagged that CRIDGE's simulation code still ignored it. ## What changed `SimulatedKubernetesClientResource`: - Added `operationDurationSecondsFor(headers)`, which reads `org.etsi.osl.operationDurationSeconds` off the inbound request headers (tolerant of it arriving as a `String`, `Long`, etc. off a JMS/Camel exchange) and uses it for the operation-phase estimate. - Falls back to the local `default-operation-duration-seconds` config value only when the header is genuinely absent (older OSOM, or a caller that doesn't send it) — matching how OSOM's own `OperationDurationResolver` falls back to its configured default. - `scheduleCreateCallbacks` (deploy) and `schedulePatchCallback` (patch) now resolve this value per request instead of always using the static default. `application.yml`: updated the `cridge.simulation.*` comment block to describe `default-operation-duration-seconds` as the fallback it actually is, not the figure that's always used. ## Why this matters Per OSOM's own writeup, aggregation across resources in a plan takes `max` of the operation durations on the assumption they're all sized against the *same* order-derived number. A CRIDGE simulation that kept inventing its own 86400s window regardless of what OSOM actually asked for would silently break that assumption on any order whose requested dates implied a different window — the resource's operate-phase energy/cost would be computed over the wrong duration. ## Tests Extended `SimulatedKubernetesClientResourceTest` (now 7 tests, up from 5): - `deployCR_wellFormedSpec_repliesOK_andReportsReservedThenAvailable` now also asserts the fallback default is used when no header is sent. - New `deployCR_withOperationDurationSecondsHeader_usesHeaderWindow_notLocalDefault` — asserts a string-valued header (`"7200"`) is parsed and used verbatim, not the default. - `patchCR_wellFormedSpec_repliesOK_andReportsAvailableOnce` now asserts the fallback default. - New `patchCR_withOperationDurationSecondsHeader_usesHeaderWindow_notLocalDefault`. Full suite: `mvn test` → 17/17 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 4, `SimulatedKubernetesClientResourceTest` 7). docs/20260812_1830_argocd-application-secret-simulation.md 0 → 100644 +80 −0 Original line number Diff line number Diff line # CRIDGE simulator fabricates a Secret resource for ArgoCD K8aaS Applications ## What changed `SimulatedKubernetesClientResource.deployCR` now recognizes one CR shape specially: an ArgoCD `Application` (`apiVersion: argoproj.io/v1alpha1`, `kind: Application`). This is OSL's K8aaS (Kubernetes-as-a-Service) cluster-provisioning pattern — ArgoCD deploys a Terraform-controller-backed Helm chart into `spec.destination.namespace` (in practice the service order id), which eventually writes a kubeconfig-bearing Secret into that same namespace once the real cluster comes up. In production, that eventual Secret is what `NamespaceWatcher` mirrors into the catalog (`KubernetesClientResource.KubernetesSecret2OpensliceResource`) — but only once it actually exists on a real cluster. A simulation never creates anything real, so nothing ever triggers that mirroring, and a tentative plan for a K8aaS order would show only the `Application` resource, missing the Secret a real deployment would also produce. `deployCR` now fabricates that eventual outcome: when the incoming CR is an ArgoCD `Application`, it schedules one **additional** planning-resource callback, alongside the ordinary two it already sends for the CR itself, representing the Secret that would appear in `spec.destination.namespace`. For any other CR kind, behavior is unchanged. ## How it's implemented - `wellFormed(String)` was split: the new `parseCrSpec(String)` returns the parsed `GenericKubernetesResource` (or `null`) so `deployCR` can inspect `apiVersion`/`kind`/`spec` instead of discarding the parse result; `wellFormed` is now a one-line wrapper over it for the callers (`patchCR`) that only need the yes/no answer. - `scheduleArgoCdSecretCallbackIfApplicable(headers, gkr)`: matches `apiVersion`/`kind` exactly, reads `spec.destination.namespace` via fabric8's `gkr.get("spec", "destination", "namespace")` nested-path accessor (skips silently, with a debug log, if that path is absent), and — after the same simulated deploy delay as the Application's own availability callback — calls `planningCallbackClient.report(...)` with: - **`resourceId = null`**: per OSOM's `PlanningResourceCallbackHandler`, a callback with no `org.etsi.osl.resourceId` always creates a *new* `ExpectedResource` in the plan (linked to the owning `ExpectedService` via `serviceId`) rather than updating an existing row — exactly what's needed for a resource distinct from the Application itself. - **name**: `"{applicationName}-secret@{destinationNamespace}[@{currentContextCluster}[@{clusterMasterURL}]]"`, mirroring production's real secret-naming shape (`"{secretName}@{namespace}@{cluster}@{masterURL}"`) as closely as simulation can. The real secret's own name is opaque until the Terraform-controller chart actually creates it, so the Application's own `metadata.name` is the best available stand-in; the cluster/masterURL segments are included only when those (unprefixed) headers are present on the request. - **status**: `AVAILABLE`, matching production — a mirrored `KubernetesSecret` always reports `AVAILABLE` (`KubernetesSecret.toResourceCreate()` hardcodes it), it never goes through a reserved phase. - **estimates**: all omitted (not zero-padded). A mirrored Secret carries no cost/duration/energy of its own in production; it's a passive artifact of the cluster resource, not separately billable. - **Scope: `deployCR` only**, deliberately not `patchCR`/`deleteCR`. A callback with no `resourceId` always creates a *new* resource, so firing this on every `patchCR` of the same Application would invent a duplicate secret row on every re-plan rather than updating the one already there. `deleteCR` is left alone too — the planning callback contract is create/update only, there's no teardown signal for a resource that was never really tracked to begin with. No changes to `PlanningCallbackClient`, `CRPlanningRouteBuilder`, `application.yml`, or any production class — this is confined entirely to `SimulatedKubernetesClientResource`. ## Tests Added to `SimulatedKubernetesClientResourceTest` (now 9, up from 7): - `deployCR_nonArgoCdSpec_doesNotReportAnAdditionalSecretResource` — the existing crontab fixture still produces exactly the two ordinary callbacks and nothing more (`verifyNoMoreInteractions`), pinning "otherwise perform the existing behaviour". - `deployCR_argoCdApplication_alsoReportsAdditionalSecretResourceForDestinationNamespace` — using a new fixture `src/test/resources/argocd-application-cr.yaml` (the exact K8aaS example CR), asserts the two ordinary callbacks plus exactly one additional `resourceId == null`, `AVAILABLE` callback with the expected `"k8s-cluster-secret@f4debc53-...@my-cluster@https://k8s.example.com:6443"` name and no estimates, and nothing further. Full suite (as of this change): `mvn test` → 19/19 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 4, `SimulatedKubernetesClientResourceTest` 9). The additional-secret callback's body was later enriched with `resourceCharacteristic`s — see [20260814_1230_simulated-secret-resource-characteristics.md](20260814_1230_simulated-secret-resource-characteristics.md) for the updated (20/20) count. ## Related Builds on [20260812_1347_simulation-support-for-cridge.md](20260812_1347_simulation-support-for-cridge.md) and [20260812_1700_honor-operation-duration-header-in-simulation.md](20260812_1700_honor-operation-duration-header-in-simulation.md). Followed up by [20260814_1230_simulated-secret-resource-characteristics.md](20260814_1230_simulated-secret-resource-characteristics.md) and [20260814_1300_scope-argocd-secret-simulation-to-k8aas-chart.md](20260814_1300_scope-argocd-secret-simulation-to-k8aas-chart.md). docs/20260814_1230_simulated-secret-resource-characteristics.md 0 → 100644 +68 −0 Original line number Diff line number Diff line # Simulated ArgoCD Secret callback now carries structural resourceCharacteristics ## What changed The additional planning-resource callback that [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md) introduced (fired for ArgoCD K8aaS `Application` CRs, representing the Secret that would eventually land in `spec.destination.namespace`) previously reported only a bare name and `AVAILABLE` status. It now also attaches a `resourceCharacteristic` list to the callback's `ResourceCreate` body, so a tentative plan shows a Secret resource shaped like a real catalog entry — with namespace, cluster, `Kind`, etc. — rather than just a name. This was prompted by being handed three real TMF `Resource` JSON documents pulled from OSL's actual catalog (two `Secret`-category resources, one `ConfigMap`-category resource) as the target shape to approximate. Two decisions on scope, made explicitly before writing any code: - **Wire it into the CRIDGE simulator directly** (enrich the existing callback), rather than producing static example files elsewhere. - **Structural metadata only** — namespace, cluster, `Kind`, apiGroup, the `org.etsi.osl.*` ids. The real examples' `data`, `json`, and `metadata` characteristics carry the secret's actual (or base64-encoded) payload — a kubeconfig, an SSH key pair. A simulation creates nothing real, so there is no genuine payload to report; fabricating credential-shaped placeholder content there would be actively misleading (indistinguishable from a real secret at a glance) rather than a harmless placeholder. `UID` is omitted for the same reason — a real Secret's UID is assigned by the Kubernetes API server at creation time, so simulation has nothing honest to put there. ## How it's implemented - **`PlanningCallbackClient.report(...)` gained a second, 15-argument overload** taking a trailing `Map<String, String> characteristics` (name → value); the original 14-argument signature is now a one-line delegator that passes `null` (unchanged behavior for every other caller). When present, each non-blank entry is added to the `ResourceCreate` via `addResourceCharacteristicItemShort(name, value, EValueType.TEXT.getValue())` — the same method production's own `KubernetesSecret.toResourceCreate()` uses, so the resulting JSON shape (`{"name": ..., "value": {"value": ..., "alias": null}}`) matches production's real characteristics exactly. Blank values are dropped rather than sent as empty characteristics. - **`SimulatedKubernetesClientResource.secretResourceCharacteristicsFor(headers, namespace)`** builds the map: always `Kind=Secret`, `apiGroup=secrets`, `org.etsi.osl.namespace=<destination namespace>`; conditionally (only when present on the inbound request) `currentContextCluster`, `clusterMasterURL`, `org.etsi.osl.serviceOrderId`, `org.etsi.osl.serviceId`, `org.etsi.osl.prefixName`. Wired into `scheduleArgoCdSecretCallbackIfApplicable`, which now calls the new 15-arg `report(...)` overload. No other CRIDGE behavior changed — this only enriches the one existing simulated-secret callback's payload; the reply contract, route wiring, and every other simulated CR kind's behavior are untouched. ## Tests - `PlanningCallbackClientTest.report_withCharacteristics_addsThemAsResourceCharacteristicsOnTheBody` (new): calls the 15-arg overload directly, parses the JSON body sent to `PLANNING.RESOURCE.CREATEORUPDATE`, and asserts the `resourceCharacteristic` array's `name`/`value.value` pairs match what was passed in, and that a blank value is dropped rather than sent. - `SimulatedKubernetesClientResourceTest.deployCR_argoCdApplication_alsoReportsAdditionalSecretResourceForDestinationNamespace` (updated): its second `verify(...)` now targets the 15-arg overload (Mockito treats the two overloads as distinct mockable methods, so the previous 14-arg-shaped verify silently stopped matching once production switched overloads) and captures the characteristics map, asserting `Kind=Secret`, `apiGroup=secrets`, `org.etsi.osl.namespace`, `currentContextCluster`, `clusterMasterURL`, and the three `org.etsi.osl.*` id fields from the test's headers, while also asserting `data`/`json`/`UID` are absent. Full suite: `mvn test` → 20/20 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 5, `SimulatedKubernetesClientResourceTest` 9) — up from 19, `PlanningCallbackClientTest` gaining the one new test. ## Related Builds on [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md), [20260812_1347_simulation-support-for-cridge.md](20260812_1347_simulation-support-for-cridge.md), and [20260812_1700_honor-operation-duration-header-in-simulation.md](20260812_1700_honor-operation-duration-header-in-simulation.md). docs/20260814_1300_scope-argocd-secret-simulation-to-k8aas-chart.md 0 → 100644 +46 −0 Original line number Diff line number Diff line # Simulated ArgoCD Secret callback now scoped to the K8aaS provisioning chart ## What changed The additional planning-resource callback (see [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md)) previously fired for **any** ArgoCD `Application` CR that carried a `spec.destination.namespace`. That was too broad: an `Application` deploying an ordinary workload chart never provisions a cluster and so never produces a kubeconfig Secret as a side effect — fabricating one for it would be wrong, not merely imprecise. `SimulatedKubernetesClientResource` now also requires `spec.source.chart` to equal `provision-k8s-cluster-argocd-tf-controller` (the K8aaS cluster-provisioning chart from the original example) before scheduling the additional Secret callback. Every other ArgoCD `Application` — same `apiVersion`/`kind`, different chart — now falls through to the ordinary two-callback behavior, same as any other CR kind. ## How it's implemented - New constant `ARGOCD_K8AAS_CHART = "provision-k8s-cluster-argocd-tf-controller"`. - New `isProvisionK8sClusterChart(GenericKubernetesResource gkr)`, reading `spec.source.chart` via fabric8's nested-path accessor (`gkr.get("spec", "source", "chart")`) — matching the actual parsed field rather than a raw-text search over `crspec`, so it's robust to YAML/JSON formatting differences (quoting, spacing) a plain substring search would be sensitive to. - `scheduleArgoCdSecretCallbackIfApplicable` now guards on `isArgoCdApplication(gkr) && isProvisionK8sClusterChart(gkr)` instead of `isArgoCdApplication(gkr)` alone. No other behavior changed. ## Tests Added `deployCR_argoCdApplicationWithDifferentChart_doesNotReportAnAdditionalSecretResource` to `SimulatedKubernetesClientResourceTest`: an inline ArgoCD `Application` fixture with the same `apiVersion`/`kind`/`spec.destination.namespace` shape but `spec.source.chart: some-unrelated-workload-chart` produces exactly the two ordinary callbacks and nothing more. The existing positive test (`deployCR_argoCdApplication_alsoReportsAdditionalSecretResourceForDestinationNamespace`, using the `provision-k8s-cluster-argocd-tf-controller` fixture) still passes unchanged, confirming the chart-matching case is unaffected. Full suite: `mvn test` → 21/21 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 5, `SimulatedKubernetesClientResourceTest` 10 — up from 9). ## Related Builds on [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md) and [20260814_1230_simulated-secret-resource-characteristics.md](20260814_1230_simulated-secret-resource-characteristics.md). Loading
docs/20260812_1347_simulation-support-for-cridge.md 0 → 100644 +80 −0 Original line number Diff line number Diff line # CRIDGE support for OSOM's tentative-planning simulation contract ## What changed CRIDGE now answers OSOM's "tentative planning" (what-if) dry-run requests instead of timing out. When an operator runs a simulated plan with `simulateAdapters=true`, OSOM's `CROrchestrationService` / `PlanningDay2AdapterSimulator` send deploy/patch/delete requests to CRIDGE's `/PLANNING`-suffixed queues (`CRD.DEPLOY.CR_REQ/PLANNING`, `CRD.PATCH.CR_REQ/PLANNING`, `CRD.DELETE.CR_REQ/PLANNING`) rather than the production queues. Previously nothing consumed those queues, so every simulated plan involving a CR-backed service reported CRIDGE as unreachable (`AdapterInvocationException`, `NO_REPLY`) after ~5s, and the owning service showed as failed. This is a pure addition: a new `org.etsi.osl.cridge.simulation` package with its own routes and beans. **No production class (`CRRouteBuilder`, `KubernetesClientResource`) or production queue property was modified.** Production traffic, queues, and behavior are unchanged. ## New files - `src/main/java/org/etsi/osl/cridge/simulation/CRPlanningRouteBuilder.java` — Camel routes on the three `_PLANNING` queue properties, mirroring `CRRouteBuilder`'s shape, each delegating to `SimulatedKubernetesClientResource`. - `src/main/java/org/etsi/osl/cridge/simulation/SimulatedKubernetesClientResource.java` — `deployCR`/`patchCR`/`deleteCR(headers, crspec)`, same method shapes as production's `KubernetesClientResource` for symmetry, but never touches `KubernetesClient` or the real cluster. Validates the CR spec is well-formed (fabric8 `Serialization.unmarshal`), replies synchronously with production's own string contract (`"OK"` / `"FAIL <msg>"` / `"ERROR <msg>"`), then asynchronously reports resource status/estimates back to OSOM. Deliberately reproduces a pre-existing production naming quirk (`org.etsi.osl.prefixName` is checked under the wrong key, `prefixId`, in `deployCR`/`patchCR`, so both always fall through to `"cr" + resourceId.substring(0,8)`) rather than "fixing" it, so simulated names stay consistent with what production actually assigns today. - `src/main/java/org/etsi/osl/cridge/simulation/PlanningCallbackClient.java` — sends the async "planning resource" callback (`PLANNING.RESOURCE.CREATEORUPDATE`) that actually populates OSOM's plan: resource name/status and cost/duration/energy estimates, using the exact header constants from OSOM's `PlanningResourceCallbackHandler`. Omits unmeasured estimate components rather than zero-padding them (an omitted component means "not measured"; an explicit zero means "measured as zero" — the two are not interchangeable per the OSOM contract). ## Config additions (`application.yml`) - Four new queue properties: `CRD_DEPLOY_CR_REQ_PLANNING`, `CRD_PATCH_CR_REQ_PLANNING`, `CRD_DELETE_CR_REQ_PLANNING`, `PLANNING_RESOURCE_CREATEORUPDATE`. - A `cridge.simulation.*` block of tunables: simulated deploy delay, a local default operation duration (OSOM never sends CRIDGE an `org.etsi.osl.operationDurationSeconds` header, unlike the generic-controller path, so this falls back to 86400s — mirroring OSOM's own `tentative.planning.default-operation-duration-seconds` default), and static placeholder creation/operation/teardown energy and cost figures (clearly labelled as placeholders, not measured data). ## Scoping decisions made with the user - New consumers live in the same CRIDGE process (new routes/beans), not a separate service. - The missing `operationDurationSeconds` header is defaulted locally to 86400s rather than blocking on an OSOM-side fix. - The first version reports static placeholder energy/cost figures alongside duration/status, rather than omitting them. ## Verification performed - `mvn -DskipTests compile` — clean compile of the new package. - Two new unit test classes (plain JUnit 5 + Mockito, no Spring context needed): - `SimulatedKubernetesClientResourceTest` — asserts the `"OK"`/`"FAIL ..."`/`"ERROR ..."` response contract, the `RESERVED`→`AVAILABLE` (create) / `AVAILABLE` (patch) / `UNKNOWN` (delete) status sequence, the reproduced naming quirk, and that a malformed CR spec fails cheaply without ever invoking the callback. - `PlanningCallbackClientTest` — asserts the actual header map sent to `PLANNING.RESOURCE.CREATEORUPDATE` carries the required `org.etsi.osl.serviceOrderId`, echoes `org.etsi.osl.resourceId`, and omits (never zero-pads) unmeasured estimate components; and that a callback with no `serviceOrderId` is skipped rather than sent. - Full suite (`mvn test`): 15/15 tests green — `CridgeIntegrationTest` (6, unchanged) + `PlanningCallbackClientTest` (4, new) + `SimulatedKubernetesClientResourceTest` (5, new). **Not performed in this session:** end-to-end verification against a live broker with both CRIDGE and OSOM running (the plan's `curl`/`jq` checklist against `/replan?simulateAdapters=true`). That requires a running OSOM + ActiveMQ/Artemis environment outside this repo checkout. ## Full plan See `C:\Users\ctranoris\.claude\plans\fluffy-dancing-quasar.md` for the complete approved plan this implementation followed, including the full wire-contract writeup (queues, headers, reply/callback shapes) reverse-engineered from the OSOM source.
docs/20260812_1700_honor-operation-duration-header-in-simulation.md 0 → 100644 +50 −0 Original line number Diff line number Diff line # CRIDGE simulation now honours OSOM's operationDurationSeconds header ## Correction to the previous change [20260812_1347_simulation-support-for-cridge.md](20260812_1347_simulation-support-for-cridge.md) built CRIDGE's simulation path on the assumption, confirmed at the time against OSOM source, that OSOM never sent CRIDGE an `org.etsi.osl.operationDurationSeconds` header — so `SimulatedKubernetesClientResource` always sized the operation-phase estimate from a local static default (`cridge.simulation.default-operation-duration-seconds`, 86400s), ignoring the header entirely. Later the same day, OSOM shipped `docs/20260812_1545_operation-window-for-all-planning-sends.md`: every planning send to CRIDGE (create/patch/delete, both simulated and `executeAdaptersForReal`) now carries this header, derived once from the order's requested dates. The user flagged that CRIDGE's simulation code still ignored it. ## What changed `SimulatedKubernetesClientResource`: - Added `operationDurationSecondsFor(headers)`, which reads `org.etsi.osl.operationDurationSeconds` off the inbound request headers (tolerant of it arriving as a `String`, `Long`, etc. off a JMS/Camel exchange) and uses it for the operation-phase estimate. - Falls back to the local `default-operation-duration-seconds` config value only when the header is genuinely absent (older OSOM, or a caller that doesn't send it) — matching how OSOM's own `OperationDurationResolver` falls back to its configured default. - `scheduleCreateCallbacks` (deploy) and `schedulePatchCallback` (patch) now resolve this value per request instead of always using the static default. `application.yml`: updated the `cridge.simulation.*` comment block to describe `default-operation-duration-seconds` as the fallback it actually is, not the figure that's always used. ## Why this matters Per OSOM's own writeup, aggregation across resources in a plan takes `max` of the operation durations on the assumption they're all sized against the *same* order-derived number. A CRIDGE simulation that kept inventing its own 86400s window regardless of what OSOM actually asked for would silently break that assumption on any order whose requested dates implied a different window — the resource's operate-phase energy/cost would be computed over the wrong duration. ## Tests Extended `SimulatedKubernetesClientResourceTest` (now 7 tests, up from 5): - `deployCR_wellFormedSpec_repliesOK_andReportsReservedThenAvailable` now also asserts the fallback default is used when no header is sent. - New `deployCR_withOperationDurationSecondsHeader_usesHeaderWindow_notLocalDefault` — asserts a string-valued header (`"7200"`) is parsed and used verbatim, not the default. - `patchCR_wellFormedSpec_repliesOK_andReportsAvailableOnce` now asserts the fallback default. - New `patchCR_withOperationDurationSecondsHeader_usesHeaderWindow_notLocalDefault`. Full suite: `mvn test` → 17/17 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 4, `SimulatedKubernetesClientResourceTest` 7).
docs/20260812_1830_argocd-application-secret-simulation.md 0 → 100644 +80 −0 Original line number Diff line number Diff line # CRIDGE simulator fabricates a Secret resource for ArgoCD K8aaS Applications ## What changed `SimulatedKubernetesClientResource.deployCR` now recognizes one CR shape specially: an ArgoCD `Application` (`apiVersion: argoproj.io/v1alpha1`, `kind: Application`). This is OSL's K8aaS (Kubernetes-as-a-Service) cluster-provisioning pattern — ArgoCD deploys a Terraform-controller-backed Helm chart into `spec.destination.namespace` (in practice the service order id), which eventually writes a kubeconfig-bearing Secret into that same namespace once the real cluster comes up. In production, that eventual Secret is what `NamespaceWatcher` mirrors into the catalog (`KubernetesClientResource.KubernetesSecret2OpensliceResource`) — but only once it actually exists on a real cluster. A simulation never creates anything real, so nothing ever triggers that mirroring, and a tentative plan for a K8aaS order would show only the `Application` resource, missing the Secret a real deployment would also produce. `deployCR` now fabricates that eventual outcome: when the incoming CR is an ArgoCD `Application`, it schedules one **additional** planning-resource callback, alongside the ordinary two it already sends for the CR itself, representing the Secret that would appear in `spec.destination.namespace`. For any other CR kind, behavior is unchanged. ## How it's implemented - `wellFormed(String)` was split: the new `parseCrSpec(String)` returns the parsed `GenericKubernetesResource` (or `null`) so `deployCR` can inspect `apiVersion`/`kind`/`spec` instead of discarding the parse result; `wellFormed` is now a one-line wrapper over it for the callers (`patchCR`) that only need the yes/no answer. - `scheduleArgoCdSecretCallbackIfApplicable(headers, gkr)`: matches `apiVersion`/`kind` exactly, reads `spec.destination.namespace` via fabric8's `gkr.get("spec", "destination", "namespace")` nested-path accessor (skips silently, with a debug log, if that path is absent), and — after the same simulated deploy delay as the Application's own availability callback — calls `planningCallbackClient.report(...)` with: - **`resourceId = null`**: per OSOM's `PlanningResourceCallbackHandler`, a callback with no `org.etsi.osl.resourceId` always creates a *new* `ExpectedResource` in the plan (linked to the owning `ExpectedService` via `serviceId`) rather than updating an existing row — exactly what's needed for a resource distinct from the Application itself. - **name**: `"{applicationName}-secret@{destinationNamespace}[@{currentContextCluster}[@{clusterMasterURL}]]"`, mirroring production's real secret-naming shape (`"{secretName}@{namespace}@{cluster}@{masterURL}"`) as closely as simulation can. The real secret's own name is opaque until the Terraform-controller chart actually creates it, so the Application's own `metadata.name` is the best available stand-in; the cluster/masterURL segments are included only when those (unprefixed) headers are present on the request. - **status**: `AVAILABLE`, matching production — a mirrored `KubernetesSecret` always reports `AVAILABLE` (`KubernetesSecret.toResourceCreate()` hardcodes it), it never goes through a reserved phase. - **estimates**: all omitted (not zero-padded). A mirrored Secret carries no cost/duration/energy of its own in production; it's a passive artifact of the cluster resource, not separately billable. - **Scope: `deployCR` only**, deliberately not `patchCR`/`deleteCR`. A callback with no `resourceId` always creates a *new* resource, so firing this on every `patchCR` of the same Application would invent a duplicate secret row on every re-plan rather than updating the one already there. `deleteCR` is left alone too — the planning callback contract is create/update only, there's no teardown signal for a resource that was never really tracked to begin with. No changes to `PlanningCallbackClient`, `CRPlanningRouteBuilder`, `application.yml`, or any production class — this is confined entirely to `SimulatedKubernetesClientResource`. ## Tests Added to `SimulatedKubernetesClientResourceTest` (now 9, up from 7): - `deployCR_nonArgoCdSpec_doesNotReportAnAdditionalSecretResource` — the existing crontab fixture still produces exactly the two ordinary callbacks and nothing more (`verifyNoMoreInteractions`), pinning "otherwise perform the existing behaviour". - `deployCR_argoCdApplication_alsoReportsAdditionalSecretResourceForDestinationNamespace` — using a new fixture `src/test/resources/argocd-application-cr.yaml` (the exact K8aaS example CR), asserts the two ordinary callbacks plus exactly one additional `resourceId == null`, `AVAILABLE` callback with the expected `"k8s-cluster-secret@f4debc53-...@my-cluster@https://k8s.example.com:6443"` name and no estimates, and nothing further. Full suite (as of this change): `mvn test` → 19/19 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 4, `SimulatedKubernetesClientResourceTest` 9). The additional-secret callback's body was later enriched with `resourceCharacteristic`s — see [20260814_1230_simulated-secret-resource-characteristics.md](20260814_1230_simulated-secret-resource-characteristics.md) for the updated (20/20) count. ## Related Builds on [20260812_1347_simulation-support-for-cridge.md](20260812_1347_simulation-support-for-cridge.md) and [20260812_1700_honor-operation-duration-header-in-simulation.md](20260812_1700_honor-operation-duration-header-in-simulation.md). Followed up by [20260814_1230_simulated-secret-resource-characteristics.md](20260814_1230_simulated-secret-resource-characteristics.md) and [20260814_1300_scope-argocd-secret-simulation-to-k8aas-chart.md](20260814_1300_scope-argocd-secret-simulation-to-k8aas-chart.md).
docs/20260814_1230_simulated-secret-resource-characteristics.md 0 → 100644 +68 −0 Original line number Diff line number Diff line # Simulated ArgoCD Secret callback now carries structural resourceCharacteristics ## What changed The additional planning-resource callback that [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md) introduced (fired for ArgoCD K8aaS `Application` CRs, representing the Secret that would eventually land in `spec.destination.namespace`) previously reported only a bare name and `AVAILABLE` status. It now also attaches a `resourceCharacteristic` list to the callback's `ResourceCreate` body, so a tentative plan shows a Secret resource shaped like a real catalog entry — with namespace, cluster, `Kind`, etc. — rather than just a name. This was prompted by being handed three real TMF `Resource` JSON documents pulled from OSL's actual catalog (two `Secret`-category resources, one `ConfigMap`-category resource) as the target shape to approximate. Two decisions on scope, made explicitly before writing any code: - **Wire it into the CRIDGE simulator directly** (enrich the existing callback), rather than producing static example files elsewhere. - **Structural metadata only** — namespace, cluster, `Kind`, apiGroup, the `org.etsi.osl.*` ids. The real examples' `data`, `json`, and `metadata` characteristics carry the secret's actual (or base64-encoded) payload — a kubeconfig, an SSH key pair. A simulation creates nothing real, so there is no genuine payload to report; fabricating credential-shaped placeholder content there would be actively misleading (indistinguishable from a real secret at a glance) rather than a harmless placeholder. `UID` is omitted for the same reason — a real Secret's UID is assigned by the Kubernetes API server at creation time, so simulation has nothing honest to put there. ## How it's implemented - **`PlanningCallbackClient.report(...)` gained a second, 15-argument overload** taking a trailing `Map<String, String> characteristics` (name → value); the original 14-argument signature is now a one-line delegator that passes `null` (unchanged behavior for every other caller). When present, each non-blank entry is added to the `ResourceCreate` via `addResourceCharacteristicItemShort(name, value, EValueType.TEXT.getValue())` — the same method production's own `KubernetesSecret.toResourceCreate()` uses, so the resulting JSON shape (`{"name": ..., "value": {"value": ..., "alias": null}}`) matches production's real characteristics exactly. Blank values are dropped rather than sent as empty characteristics. - **`SimulatedKubernetesClientResource.secretResourceCharacteristicsFor(headers, namespace)`** builds the map: always `Kind=Secret`, `apiGroup=secrets`, `org.etsi.osl.namespace=<destination namespace>`; conditionally (only when present on the inbound request) `currentContextCluster`, `clusterMasterURL`, `org.etsi.osl.serviceOrderId`, `org.etsi.osl.serviceId`, `org.etsi.osl.prefixName`. Wired into `scheduleArgoCdSecretCallbackIfApplicable`, which now calls the new 15-arg `report(...)` overload. No other CRIDGE behavior changed — this only enriches the one existing simulated-secret callback's payload; the reply contract, route wiring, and every other simulated CR kind's behavior are untouched. ## Tests - `PlanningCallbackClientTest.report_withCharacteristics_addsThemAsResourceCharacteristicsOnTheBody` (new): calls the 15-arg overload directly, parses the JSON body sent to `PLANNING.RESOURCE.CREATEORUPDATE`, and asserts the `resourceCharacteristic` array's `name`/`value.value` pairs match what was passed in, and that a blank value is dropped rather than sent. - `SimulatedKubernetesClientResourceTest.deployCR_argoCdApplication_alsoReportsAdditionalSecretResourceForDestinationNamespace` (updated): its second `verify(...)` now targets the 15-arg overload (Mockito treats the two overloads as distinct mockable methods, so the previous 14-arg-shaped verify silently stopped matching once production switched overloads) and captures the characteristics map, asserting `Kind=Secret`, `apiGroup=secrets`, `org.etsi.osl.namespace`, `currentContextCluster`, `clusterMasterURL`, and the three `org.etsi.osl.*` id fields from the test's headers, while also asserting `data`/`json`/`UID` are absent. Full suite: `mvn test` → 20/20 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 5, `SimulatedKubernetesClientResourceTest` 9) — up from 19, `PlanningCallbackClientTest` gaining the one new test. ## Related Builds on [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md), [20260812_1347_simulation-support-for-cridge.md](20260812_1347_simulation-support-for-cridge.md), and [20260812_1700_honor-operation-duration-header-in-simulation.md](20260812_1700_honor-operation-duration-header-in-simulation.md).
docs/20260814_1300_scope-argocd-secret-simulation-to-k8aas-chart.md 0 → 100644 +46 −0 Original line number Diff line number Diff line # Simulated ArgoCD Secret callback now scoped to the K8aaS provisioning chart ## What changed The additional planning-resource callback (see [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md)) previously fired for **any** ArgoCD `Application` CR that carried a `spec.destination.namespace`. That was too broad: an `Application` deploying an ordinary workload chart never provisions a cluster and so never produces a kubeconfig Secret as a side effect — fabricating one for it would be wrong, not merely imprecise. `SimulatedKubernetesClientResource` now also requires `spec.source.chart` to equal `provision-k8s-cluster-argocd-tf-controller` (the K8aaS cluster-provisioning chart from the original example) before scheduling the additional Secret callback. Every other ArgoCD `Application` — same `apiVersion`/`kind`, different chart — now falls through to the ordinary two-callback behavior, same as any other CR kind. ## How it's implemented - New constant `ARGOCD_K8AAS_CHART = "provision-k8s-cluster-argocd-tf-controller"`. - New `isProvisionK8sClusterChart(GenericKubernetesResource gkr)`, reading `spec.source.chart` via fabric8's nested-path accessor (`gkr.get("spec", "source", "chart")`) — matching the actual parsed field rather than a raw-text search over `crspec`, so it's robust to YAML/JSON formatting differences (quoting, spacing) a plain substring search would be sensitive to. - `scheduleArgoCdSecretCallbackIfApplicable` now guards on `isArgoCdApplication(gkr) && isProvisionK8sClusterChart(gkr)` instead of `isArgoCdApplication(gkr)` alone. No other behavior changed. ## Tests Added `deployCR_argoCdApplicationWithDifferentChart_doesNotReportAnAdditionalSecretResource` to `SimulatedKubernetesClientResourceTest`: an inline ArgoCD `Application` fixture with the same `apiVersion`/`kind`/`spec.destination.namespace` shape but `spec.source.chart: some-unrelated-workload-chart` produces exactly the two ordinary callbacks and nothing more. The existing positive test (`deployCR_argoCdApplication_alsoReportsAdditionalSecretResourceForDestinationNamespace`, using the `provision-k8s-cluster-argocd-tf-controller` fixture) still passes unchanged, confirming the chart-matching case is unaffected. Full suite: `mvn test` → 21/21 green (`CridgeIntegrationTest` 6, `PlanningCallbackClientTest` 5, `SimulatedKubernetesClientResourceTest` 10 — up from 9). ## Related Builds on [20260812_1830_argocd-application-secret-simulation.md](20260812_1830_argocd-application-secret-simulation.md) and [20260814_1230_simulated-secret-resource-characteristics.md](20260814_1230_simulated-secret-resource-characteristics.md).