From 9bbdb2d612603652626b266c29c4135b71622b4f Mon Sep 17 00:00:00 2001 From: Said Boudjelda Date: Wed, 21 Oct 2026 17:07:36 +0200 Subject: [PATCH 1/2] fix(chart): the Kates API's chaos, topology and conformity follow the Kafka namespace and cluster name The backend reads the Kafka cluster it acts on from kates.chaos.kafka.namespace and .cluster (chaos guard, orchestrator, orphan recovery, lease, Prometheus capture) and kates.topology.kafka-namespace and -cluster (topology, cluster alerts, conformity, CDC's fallback). Nothing set them, so they stayed at kafka/krafter. With `kates deploy --kafka-ns X` or `--kafka-name Y`, or kates-platform in any namespace but kafka, chaos refused every plan with "No broker pods found", topology came back empty and every conformity check returned 409. - kates 0.12.1: the configmap sets KATES_CHAOS_KAFKA_NAMESPACE and KATES_TOPOLOGY_KAFKA_NAMESPACE from networkPolicy.kafka.namespace, through tpl, with the policy on or off. It sets KATES_CHAOS_KAFKA_CLUSTER and KATES_TOPOLOGY_KAFKA_CLUSTER from a new kafka.clusterName (default krafter). The defaults are the backend's own, so a default render sets the same values as before. An extraEnv entry still wins over the configmap. The rbac.scope=namespace block that set KATES_TOPOLOGY_KAFKA_NAMESPACE (#439) goes: it rendered the same value, and would now be a duplicate key. - kates deploy sets kafka.clusterName from --kafka-name. It already set networkPolicy.kafka.namespace from the Kafka namespace. - kates-platform 0.8.2: the kates floor moves to 0.12.1, and the umbrella sets kates.kafka.clusterName (krafter) next to the bootstrap address, which names the same cluster. CI's kates-platform render check now also asserts both namespace variables. - MCP caveats dry-run-other-namespace and topology-unread-looks-empty say which chart values set these settings, with anchored refs to the configmap. Four refs into charts/kates are re-pointed past the added lines. --- .github/workflows/ci.yml | 8 ++++++-- README.md | 4 ++-- charts/kates-platform/Chart.yaml | 12 +++++++----- charts/kates-platform/README.md | 5 +++-- charts/kates-platform/values.yaml | 1 + charts/kates/Chart.yaml | 9 ++++++--- charts/kates/README.md | 3 ++- charts/kates/templates/configmap.yaml | 10 ++++++---- charts/kates/values.schema.json | 1 + charts/kates/values.yaml | 8 +++++++- cli/cmd/deploy_components.go | 2 ++ cli/cmd/deploy_kafkaui_test.go | 19 +++++++++++++------ cli/cmd/mcp_caveats.go | 4 ++-- cli/cmd/mcp_chaos_preview.go | 5 +++-- cli/cmd/mcp_tools_chaos.go | 5 ++++- cli/cmd/mcp_tools_cluster.go | 11 ++++++++--- cli/cmd/mcp_tools_scenario.go | 4 ++-- docs/book/appendix-d-versions.md | 4 ++-- 18 files changed, 77 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06de258c..6db95114 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -437,8 +437,12 @@ jobs: run: | fail() { echo "::error::kates-platform in namespace platform: $1"; exit 1; } show() { helm template p charts/kates-platform -n platform --show-only "charts/$1"; } - grep -q 'KATES_KAFKA_BOOTSTRAP_SERVERS: "krafter-kafka-bootstrap.platform.svc:9092"' \ - <<<"$(show kates/templates/configmap.yaml)" || fail "Kates bootstraps from another namespace" + cm=$(show kates/templates/configmap.yaml) + grep -q 'KATES_KAFKA_BOOTSTRAP_SERVERS: "krafter-kafka-bootstrap.platform.svc:9092"' <<<"${cm}" \ + || fail "Kates bootstraps from another namespace" + for v in CHAOS_KAFKA_NAMESPACE TOPOLOGY_KAFKA_NAMESPACE; do + grep -q "KATES_${v}: \"platform\"" <<<"${cm}" || fail "KATES_${v} names another namespace" + done np=$(show kates/templates/networkpolicy.yaml) grep -q 'kubernetes.io/metadata.name: "platform"' <<<"${np}" || fail "no Kates egress to the release namespace" if grep -q 'kubernetes.io/metadata.name: "kafka"' <<<"${np}"; then fail "Kates egress still names kafka"; fi diff --git a/README.md b/README.md index 5b7b43fd..6d715552 100644 --- a/README.md +++ b/README.md @@ -212,8 +212,8 @@ When you install charts with Helm directly: | [`kafka-common`](charts/kafka-common/) | 0.1.0 | | Library chart — the templates the Strimzi charts share (names and labels, rails, Kafka client authentication, the Connect worker spec, KafkaUser, secret sync, NetworkPolicy and monitoring fragments, the Grafana grid, the preflight probe) | | [`kafka-ui`](charts/kafka-ui/) | 0.3.0 | v1.5.0 | A Helm chart for deploying Kafka UI (Kafbat) with Strimzi SCRAM-SHA-512 authentication | | [`kates-chaos`](charts/kates-chaos/) | 2.2.1 | 3.28.0 | Kates Chaos Engineering — wraps the LitmusChaos execution plane (operator, exporter, CRDs) with Kafka-specific RBAC, experiment/engine templating, and monitoring | -| [`kates-platform`](charts/kates-platform/) | 0.8.1 | 1.0.0 | Umbrella chart for the full Kates platform — Kafka, Kates, and supporting infrastructure | -| [`kates`](charts/kates/) | 0.12.0 | 1.27.0 | Kates — Kafka Advanced Testing & Engineering Suite | +| [`kates-platform`](charts/kates-platform/) | 0.8.2 | 1.0.0 | Umbrella chart for the full Kates platform — Kafka, Kates, and supporting infrastructure | +| [`kates`](charts/kates/) | 0.12.1 | 1.27.0 | Kates — Kafka Advanced Testing & Engineering Suite | | [`legacy-kafka`](charts/legacy-kafka/) | 0.2.0 | 3.9.1 | A deliberately old Kafka (2.x on ZooKeeper, or 3.x on KRaft) to migrate FROM — the replication source that the pinned Strimzi release cannot deploy | | [`mirror-maker2`](charts/mirror-maker2/) | 0.11.6 | 4.3.1 | Strimzi KafkaMirrorMaker2 — cross-cluster replication, DR, and cross-version migration (2.x/3.x → 4.x) | | [`monitoring`](charts/monitoring/) | 1.6.0 | 82.4.3 | Kates Monitoring — wraps kube-prometheus-stack with Kates-specific dashboards and configuration | diff --git a/charts/kates-platform/Chart.yaml b/charts/kates-platform/Chart.yaml index 9e0c5080..612dd733 100644 --- a/charts/kates-platform/Chart.yaml +++ b/charts/kates-platform/Chart.yaml @@ -2,10 +2,10 @@ apiVersion: v2 name: kates-platform description: Umbrella chart for the full Kates platform — Kafka, Kates, and supporting infrastructure type: application -# Patch: Kates reaches the Kafka this release installs, in the release -# namespace (kates 0.11.7 renders those values with tpl), and the Helm test pod -# carries the label the brokers admit test pods by. -version: 0.8.1 +# Patch: the kates floor moves to 0.12.1, whose configmap points the Kates +# API's chaos, topology and conformity at networkPolicy.kafka.namespace, which +# this umbrella sets to the release namespace. +version: 0.8.2 appVersion: "1.0.0" keywords: - kafka @@ -20,7 +20,7 @@ dependencies: repository: "file://../kafka-cluster" condition: kafka-cluster.enabled - name: kates - version: ">=0.11.7 <1.0.0" + version: ">=0.12.1 <1.0.0" repository: "file://../kates" condition: kates.enabled - name: apicurio-registry @@ -42,6 +42,8 @@ dependencies: condition: connect-cluster.enabled annotations: artifacthub.io/changes: | + - kind: fixed + description: "In any namespace but kafka, the platform's Kates API reached the brokers but its chaos, cluster topology, cluster alerts and conformity checks still looked for Kafka in namespace kafka: chaos refused every plan with No broker pods found, and every conformity check returned 409. The kates floor moves to 0.12.1, which sets those settings from kates.networkPolicy.kafka.namespace, already the release namespace here, and their cluster name from kates.kafka.clusterName (krafter). kafka-cluster.clusterName other than krafter needs kates.kafka.clusterName and kates.kafka.bootstrapServers set to match. Run `make platform-chart-deps` before upgrading" - kind: fixed description: "Kates could not reach the Kafka the platform installs. The umbrella installs kafka-cluster in its release namespace, but the kates subchart kept its own defaults: bootstrap servers and the Trogdor coordinator in namespace kafka, and NetworkPolicy egress to namespace kafka only; and the platform profile admitted Kates to the brokers only from namespace kates. Installed in kates, as this README does, Kates looked up krafter-kafka-bootstrap.kafka.svc; installed in kafka, the brokers refused it. The umbrella now sets kates.kafka.bootstrapServers, kates.kafka.topicNamespace, kates.trogdor.coordinatorUrl and kates.networkPolicy.kafka.namespace from the release namespace (kates 0.11.7 renders them with tpl), and admits Kates to the brokers from the release namespace, so the platform works in any namespace. A release that set these keys keeps its own values. kafka-cluster.clusterName other than krafter needs kates.kafka.bootstrapServers set to match" - kind: fixed diff --git a/charts/kates-platform/README.md b/charts/kates-platform/README.md index 4ca3ceec..4de0efd8 100644 --- a/charts/kates-platform/README.md +++ b/charts/kates-platform/README.md @@ -27,9 +27,10 @@ Any namespace works: Kafka and Kates both run in the release namespace, and the | `kates.enabled` | `true` | Deploy the Kates application subchart | | `kates.*` | subchart defaults | Full pass-through — see `charts/kates/values.yaml` | | `kates.kafka.bootstrapServers` | `krafter-kafka-bootstrap.{{ .Release.Namespace }}.svc:9092` | The kafka-cluster subchart's bootstrap Service; rename `krafter` with `kafka-cluster.clusterName` | +| `kates.kafka.clusterName` | `krafter` | The Kafka cluster the Kates API's chaos, topology, cluster alerts and conformity act on; rename it with `kafka-cluster.clusterName` | | `kates.kafka.topicNamespace` | `{{ .Release.Namespace }}` | Where INTEGRATION_CDC manages its KafkaTopic CRs | | `kates.trogdor.coordinatorUrl` | `http://trogdor-coordinator.{{ .Release.Namespace }}.svc:8889` | A Trogdor coordinator next to Kafka, if you run one | -| `kates.networkPolicy.kafka.namespace` | `{{ .Release.Namespace }}` | Namespace the Kates NetworkPolicy allows egress to for the brokers and Trogdor | +| `kates.networkPolicy.kafka.namespace` | `{{ .Release.Namespace }}` | Namespace the Kates NetworkPolicy allows egress to for the brokers and Trogdor, and the one the Kates API's chaos, topology, cluster alerts and conformity act in | | `kafka-cluster.networkPolicy.clients` | `kates` from the release namespace | Overrides the platform profile's `kates` grant (namespace `kates`); a list set here replaces it | | `kates.rbac.cdcSecretNamespaces` | `[]` | Namespaces where the Kates API may read the `postgresql` Secret; INTEGRATION_CDC needs its database's namespace here, since the kates ClusterRole grants nothing on Secrets | | `apicurio-registry.enabled` | `false` | Deploy Apicurio Registry (KafkaSQL storage against the `kafka-cluster` subchart) | @@ -40,6 +41,6 @@ Subchart values are validated by each subchart's own `values.schema.json`. ## Version pinning -`Chart.yaml` pins the subcharts with ranges (`>=1.0.10 <2.0.0` for kafka-cluster, `>=0.11.6 <1.0.0` for mirror-maker2, `>=2.1.8 <3.0.0` for connect-cluster, `>=0.11.7 <1.0.0` for kates, `>=0.4.0 <1.0.0` for apicurio-registry); each floor is the version the umbrella was last rendered with. The `file://` repository always resolves to the local working tree, and `Chart.lock` records the exact version at build time. +`Chart.yaml` pins the subcharts with ranges (`>=1.0.10 <2.0.0` for kafka-cluster, `>=0.11.6 <1.0.0` for mirror-maker2, `>=2.1.8 <3.0.0` for connect-cluster, `>=0.12.1 <1.0.0` for kates, `>=0.4.0 <1.0.0` for apicurio-registry); each floor is the version the umbrella was last rendered with. The `file://` repository always resolves to the local working tree, and `Chart.lock` records the exact version at build time. Helm installs whatever sits in `charts/` without checking it against those ranges, so the umbrella checks itself: rendering fails, naming the subchart, while `charts/` holds an enabled subchart outside its range. A `charts/` left from an older build would otherwise install its old subcharts, such as kates 0.9.1, whose ClusterRole read every Secret in the cluster. Run `make platform-chart-deps` to refresh it. diff --git a/charts/kates-platform/values.yaml b/charts/kates-platform/values.yaml index 29751761..7ff04b31 100644 --- a/charts/kates-platform/values.yaml +++ b/charts/kates-platform/values.yaml @@ -17,6 +17,7 @@ kates: enabled: true kafka: bootstrapServers: "krafter-kafka-bootstrap.{{ .Release.Namespace }}.svc:9092" + clusterName: krafter topicNamespace: "{{ .Release.Namespace }}" trogdor: coordinatorUrl: "http://trogdor-coordinator.{{ .Release.Namespace }}.svc:8889" diff --git a/charts/kates/Chart.yaml b/charts/kates/Chart.yaml index b36aa11d..0369ba42 100644 --- a/charts/kates/Chart.yaml +++ b/charts/kates/Chart.yaml @@ -2,9 +2,10 @@ apiVersion: v2 name: kates description: Kates — Kafka Advanced Testing & Engineering Suite type: application -# Minor: rbac.scope=namespace installs with nothing cluster-scoped. The -# default, rbac.scope=cluster, renders what 0.11.7 rendered. -version: 0.12.0 +# Patch: the backend's chaos, topology and conformity settings follow +# networkPolicy.kafka.namespace and the new kafka.clusterName, whose defaults +# are the backend's own (kafka, krafter). +version: 0.12.1 # The non-blocking health endpoints this chart's probes now point at come from # a BUILD-TIME property (quarkus.messaging.health.enabled=false), so they only # exist in the image built from this branch. Keep appVersion and @@ -28,6 +29,8 @@ annotations: artifacthub.io/category: integration-delivery artifacthub.io/license: Apache-2.0 artifacthub.io/changes: | + - kind: fixed + description: "With Kafka outside namespace kafka, or named other than krafter, chaos, the cluster topology, cluster alerts and conformity checks looked for Kafka in kafka/krafter: the configmap never set kates.chaos.kafka.namespace, kates.chaos.kafka.cluster or kates.topology.kafka-cluster, and set kates.topology.kafka-namespace only with rbac.scope=namespace. Chaos refused every plan with No broker pods found, and every conformity check returned 409. The configmap now sets KATES_CHAOS_KAFKA_NAMESPACE and KATES_TOPOLOGY_KAFKA_NAMESPACE from networkPolicy.kafka.namespace (through tpl, with the policy on or off), whatever rbac.scope says, and KATES_CHAOS_KAFKA_CLUSTER and KATES_TOPOLOGY_KAFKA_CLUSTER from kafka.clusterName (new, default krafter). The defaults are the backend's own, so a release that keeps them renders the same settings; an extraEnv entry for these variables still wins. Upgrading: a release that set networkPolicy.kafka.namespace to anything but the namespace Kafka runs in now points chaos and topology there too" - kind: added description: "rbac.scope (default cluster, unchanged) and rbac.kafkaNamespaceRole (default true). rbac.scope=namespace creates no ClusterRole or ClusterRoleBinding; with rbac.kafkaNamespaceRole=false as well, an account with only edit in the release namespace can install the chart. Performance and INTEGRITY runs make no Kubernetes call. It renders one read-only Role and RoleBinding in networkPolicy.kafka.namespace for conformity checks (get kafkas; list kafkanodepools, pods, persistentvolumeclaims, podmonitors and endpointslices), points the check at that namespace (KATES_TOPOLOGY_KAFKA_NAMESPACE), and sets KATES_CHAOS_PROVIDER=noop unless chaos.provider is set. With rbac.kafkaNamespaceRole=false that Role is left out, for an account that may grant those reads to apply from helm template --show-only templates/rbac.yaml. Without node access a conformity check takes the zones for zones, skew and kafka.rackAwareness with source cr from the EndpointSlices of Strimzi's headless Service, so only another zoneLabel leaves them UNKNOWN. A release that sets neither value, as after helm upgrade --reuse-values, keeps scope cluster" - kind: changed diff --git a/charts/kates/README.md b/charts/kates/README.md index 47a878c8..20267001 100644 --- a/charts/kates/README.md +++ b/charts/kates/README.md @@ -38,6 +38,7 @@ All configuration is in [values.yaml](values.yaml). Key sections: | `replicaCount` | `1` | Number of Kates API pods. Must be 1; see [One replica](#one-replica) | | `kafka.bootstrapServers` | `krafter-kafka-bootstrap.kafka.svc:9092` | Kafka bootstrap address. This, `kafka.topicNamespace`, `trogdor.coordinatorUrl` and `networkPolicy.kafka.namespace` go through `tpl`, so a value may name `{{ .Release.Namespace }}`, as kates-platform does | | `kafka.topicNamespace` | `""` | Namespace for CDC test `KafkaTopic` CRs (`""` = auto-detect) | +| `kafka.clusterName` | `krafter` | Strimzi Kafka cluster, in `networkPolicy.kafka.namespace`, that chaos, topology, cluster alerts and conformity act on; `kates deploy` sets it | | `engine.defaultBackend` | `native` | Benchmark engine (`native` or `trogdor`) | | `engine.maxDurationMs` | `7200000` | Maximum planned duration accepted for one run | | `engine.reaperGraceMs` | `300000` | Grace after a `RUNNING` or `STOPPING` run's planned duration before the reaper fails it | @@ -58,7 +59,7 @@ All configuration is in [values.yaml](values.yaml). Key sections: | `ingress.certManager.issuerName` | `""` | cert-manager issuer name | | `ingress.certManager.issuerKind` | `ClusterIssuer` | Issuer kind | | `networkPolicy.enabled` | `true` | Enable NetworkPolicy | -| `networkPolicy.kafka.namespace` | `kafka` | Namespace the brokers (`networkPolicy.kafka.port`) and the Trogdor coordinator (8889) run in; `kates deploy` sets it | +| `networkPolicy.kafka.namespace` | `kafka` | Namespace the brokers (`networkPolicy.kafka.port`) and the Trogdor coordinator (8889) run in; `kates deploy` sets it. The backend's chaos, topology, cluster alerts and conformity act in it too, with the policy on or off | | `networkPolicy.kafka.port` | `9092` | Kafka egress target port | | `networkPolicy.prometheus.namespace` | `monitoring` | Prometheus egress target namespace: the one `prometheus.url` points into | | `networkPolicy.prometheus.port` | `9090` | Prometheus egress target port | diff --git a/charts/kates/templates/configmap.yaml b/charts/kates/templates/configmap.yaml index 43b367e5..8f575c9d 100644 --- a/charts/kates/templates/configmap.yaml +++ b/charts/kates/templates/configmap.yaml @@ -7,6 +7,12 @@ metadata: data: KATES_KAFKA_BOOTSTRAP_SERVERS: {{ tpl .Values.kafka.bootstrapServers . | quote }} KATES_CDC_TOPIC_NAMESPACE: {{ tpl .Values.kafka.topicNamespace . | quote }} + {{- /* Also the namespace rbac.scope=namespace grants the conformity check's Role in. */}} + {{- $kafkaNamespace := tpl (.Values.networkPolicy.kafka.namespace | default "kafka") . }} + KATES_CHAOS_KAFKA_NAMESPACE: {{ $kafkaNamespace | quote }} + KATES_TOPOLOGY_KAFKA_NAMESPACE: {{ $kafkaNamespace | quote }} + KATES_CHAOS_KAFKA_CLUSTER: {{ .Values.kafka.clusterName | default "krafter" | quote }} + KATES_TOPOLOGY_KAFKA_CLUSTER: {{ .Values.kafka.clusterName | default "krafter" | quote }} KATES_KAFKA_SECURITY_PROTOCOL: {{ .Values.kafka.security.protocol | quote }} {{- if .Values.kafka.sasl.enabled }} KATES_KAFKA_SASL_MECHANISM: {{ .Values.kafka.sasl.mechanism | quote }} @@ -180,10 +186,6 @@ data: {{- with .Values.chaos.provider | default (ternary "noop" "" $namespaced) }} KATES_CHAOS_PROVIDER: {{ . | quote }} {{- end }} - {{- if $namespaced }} - {{- /* The namespace the conformity check reads is the one its Role is granted in. */}} - KATES_TOPOLOGY_KAFKA_NAMESPACE: {{ tpl (.Values.networkPolicy.kafka.namespace | default "kafka") . | quote }} - {{- end }} {{- with .Values.faultTolerance }} COM_BMSCOMP_KATES_SERVICE_TOPICSERVICE_CREATETOPIC_TIMEOUT_VALUE: {{ .topicService.timeoutMs | default "35000" | quote }} diff --git a/charts/kates/values.schema.json b/charts/kates/values.schema.json index b1957b33..019fff1c 100644 --- a/charts/kates/values.schema.json +++ b/charts/kates/values.schema.json @@ -341,6 +341,7 @@ "minLength": 0 }, "topicNamespace": { "type": "string" }, + "clusterName": { "type": "string" }, "clientRack": { "type": "string" }, "security": { "type": "object", diff --git a/charts/kates/values.yaml b/charts/kates/values.yaml index 1e31e42d..b8ae9c70 100644 --- a/charts/kates/values.yaml +++ b/charts/kates/values.yaml @@ -355,6 +355,9 @@ kafka: # trogdor.coordinatorUrl and networkPolicy.kafka.namespace go through tpl, # so a value may name {{ .Release.Namespace }}, as kates-platform does. bootstrapServers: "krafter-kafka-bootstrap.kafka.svc:9092" + # -- Name of the Strimzi Kafka cluster in networkPolicy.kafka.namespace that + # chaos, topology, cluster alerts and conformity act on (kates deploy sets it) + clusterName: krafter # -- Namespace where CDC integration test KafkaTopic CRs are managed (empty = auto-detect) topicNamespace: "" # -- Client rack ID for rack-aware consumption @@ -460,7 +463,10 @@ networkPolicy: ports: - port: 8080 protocol: TCP - # -- Kafka egress: the brokers on port and Trogdor on 8889 (kates deploy sets namespace) + # -- The namespace Kafka runs in: egress to the brokers on port and Trogdor + # on 8889, and the namespace the backend's chaos, topology, cluster alerts + # and conformity act in, whether or not the policy is enabled (kates deploy + # sets namespace) kafka: namespace: kafka port: 9092 diff --git a/cli/cmd/deploy_components.go b/cli/cmd/deploy_components.go index 2e34460a..563b67e1 100644 --- a/cli/cmd/deploy_components.go +++ b/cli/cmd/deploy_components.go @@ -1700,7 +1700,9 @@ data: katesArgs = append(katesArgs, "--set", "kafka.bootstrapServers="+katesBootstrap, "--set", "kafka.topicNamespace="+kafkaNS, + // Also the namespace and cluster the API's chaos, topology and conformity act on. "--set", "networkPolicy.kafka.namespace="+kafkaNS, + "--set", "kafka.clusterName="+dc.primary.Name, "--set", fmt.Sprintf("monitoring.enabled=%t", deployWithMonitoring)) katesArgs = append(katesArgs, dc.scrapeArgs("charts/kates")...) katesArgs = append(katesArgs, dc.prometheusArgs()...) diff --git a/cli/cmd/deploy_kafkaui_test.go b/cli/cmd/deploy_kafkaui_test.go index 295d0c36..3c7df2af 100644 --- a/cli/cmd/deploy_kafkaui_test.go +++ b/cli/cmd/deploy_kafkaui_test.go @@ -7,11 +7,12 @@ import ( "testing" ) -// TestDeployCommand_KafkaUIFollowsKafkaName checks that Kafka UI is pointed -// at the primary that --kafka-name names. It read kates detect's -// --cluster-name instead, whose default is krafter, so a primary under any -// other name got a Kafka UI whose KafkaUser and bootstrap address named a -// cluster that does not exist. +// TestDeployCommand_KafkaUIFollowsKafkaName checks that Kafka UI and the +// Kates API are pointed at the primary that --kafka-name names. Kafka UI read +// kates detect's --cluster-name instead, whose default is krafter, so a +// primary under any other name got a Kafka UI whose KafkaUser and bootstrap +// address named a cluster that does not exist; the Kates API's chaos, +// topology and conformity stayed on krafter. func TestDeployCommand_KafkaUIFollowsKafkaName(t *testing.T) { defer func() { deployTopology = "isolated" @@ -58,11 +59,17 @@ func TestDeployCommand_KafkaUIFollowsKafkaName(t *testing.T) { t.Fatalf("runDeploy failed: %v", err) } - var ui string + var ui, kates string for _, c := range helmCommands { if strings.Contains(c, "helm upgrade --install kafka-ui charts/kafka-ui") { ui = c } + if strings.Contains(c, "helm upgrade --install kates charts/kates") { + kates = c + } + } + if !strings.Contains(kates, "--set kafka.clusterName=orders ") { + t.Errorf("the Kates API is not pointed at the primary, orders:\n%s", kates) } if ui == "" { t.Fatalf("Kafka UI was not installed; helm calls:\n%s", strings.Join(helmCommands, "\n")) diff --git a/cli/cmd/mcp_caveats.go b/cli/cmd/mcp_caveats.go index a5e89c98..ba64175f 100644 --- a/cli/cmd/mcp_caveats.go +++ b/cli/cmd/mcp_caveats.go @@ -201,10 +201,10 @@ var mcpCaveatsCore = []mcpCaveat{ mcpAnchoredRef("kates/src/main/resources/application.properties:329-334", "# Prometheus server (for disruption metrics capture)", "kates.prometheus.url=http://monitoring-kube-prometheus-prometheus.monitoring.svc:9090"), - mcpAnchoredRef("charts/kates/values.yaml:408-418", + mcpAnchoredRef("charts/kates/values.yaml:411-421", "# -- Prometheus the backend queries for a disruption's Kafka metrics", `url: "http://monitoring-kube-prometheus-prometheus.monitoring.svc:9090"`), - mcpAnchoredRef("charts/kates/templates/configmap.yaml:37-39", + mcpAnchoredRef("charts/kates/templates/configmap.yaml:43-45", "{{- with .Values.prometheus.url }}", "KATES_PROMETHEUS_URL: {{ . | quote }}"), mcpAnchoredRef("kates/k8s/configmap.yaml:29-33", "# Prometheus, for a disruption's Kafka metrics", diff --git a/cli/cmd/mcp_chaos_preview.go b/cli/cmd/mcp_chaos_preview.go index 17797eee..ef8ad6f6 100644 --- a/cli/cmd/mcp_chaos_preview.go +++ b/cli/cmd/mcp_chaos_preview.go @@ -606,8 +606,9 @@ var mcpDryRunNamespaceRE = regexp.MustCompile(`no broker(?: pod)? in namespace ' // leaves targetNamespace out is in kafka, the FaultSpec default. A named pod // is previewed whatever its namespace (DisruptionSafetyGuard.java:417-423). // The dry run names its namespace in its warning; when that text cannot be -// read, the topology's namespace (kates.topology.kafka-namespace, set apart -// from the dry run's but "kafka" by default for both) stands in. +// read, the topology's namespace (kates.topology.kafka-namespace) stands in: +// a setting apart from the dry run's, but "kafka" by default for both, and the +// kates chart sets both from networkPolicy.kafka.namespace. func mcpInOtherNamespace(ds client.StepPreview, fs *mcpFaultSpecView, ctrl *mcpKRaftControllerIndex) bool { if fs == nil || fs.TargetPod != "" || len(ds.AffectedPods) > 0 { return false diff --git a/cli/cmd/mcp_tools_chaos.go b/cli/cmd/mcp_tools_chaos.go index 7c724a75..f8689174 100644 --- a/cli/cmd/mcp_tools_chaos.go +++ b/cli/cmd/mcp_tools_chaos.go @@ -159,7 +159,8 @@ var mcpCaveatsChaos = []mcpCaveat{ { ID: mcpCaveatDryRunOtherNamespace, Text: "The safety guard refuses a step in any namespace but the one the dry run reads Kafka pods from " + - "(kates.chaos.kafka.namespace, kafka by default) and the Kates API's own. A step in the Kates API's " + + "(kates.chaos.kafka.namespace, kafka by default; the kates chart sets it from networkPolicy.kafka.namespace) " + + "and the Kates API's own. A step in the Kates API's " + "namespace that names no targetPod is previewed as hitting nothing: no pods, a warning that it disrupts " + "no broker, and nothing added to the blast radius. When it runs, it acts in that namespace: on the pod " + "its targetBrokerId names, on every pod its targetLabel matches with targetAll, or else on one of them " + @@ -170,6 +171,8 @@ var mcpCaveatsChaos = []mcpCaveat{ "String katesNamespace = kubeClient.getNamespace();", `"': targetNamespace "`, "impact(spec, kafkaPods).pods()", "this step disrupts no broker", "Impact impact(FaultSpec spec, List kafkaPods)", "return new Impact(List.of(spec.targetPod())"), + mcpAnchoredRef("charts/kates/templates/configmap.yaml:11-12", + "$kafkaNamespace := tpl (.Values.networkPolicy.kafka.namespace", "KATES_CHAOS_KAFKA_NAMESPACE:"), mcpJava + "chaos/PodTargets.java:40-51,58-77,83-105", mcpJava + "chaos/FaultSpec.java:83", "kates/src/main/resources/playbooks/consumer-isolation.yaml:10-12", diff --git a/cli/cmd/mcp_tools_cluster.go b/cli/cmd/mcp_tools_cluster.go index 8eab300d..55f768e3 100644 --- a/cli/cmd/mcp_tools_cluster.go +++ b/cli/cmd/mcp_tools_cluster.go @@ -329,12 +329,17 @@ var mcpCaveatsCluster = []mcpCaveat{ { ID: mcpCaveatTopologyUnreadLooksEmpty, Text: "Node pools and controllers come from the Strimzi KafkaNodePool resources, and their pods, labelled " + - "with the cluster name the backend is configured with (kates.topology.kafka-cluster, default krafter) in " + - "its namespace (kates.topology.kafka-namespace, default kafka). A Kafka cluster with another name matches " + + "with the cluster name the backend is configured with (kates.topology.kafka-cluster, default krafter; the " + + "kates chart sets it from kafka.clusterName) in its namespace (kates.topology.kafka-namespace, default " + + "kafka; the chart sets it from networkPolicy.kafka.namespace). A Kafka cluster with another name matches " + "nothing. The backend reads the pods pool by pool and stops at the first read that fails, logging it and " + "keeping the pods it had, so a pool can show fewer pods than it has, or none. An empty list, or a pool " + "with fewer pods than replicas, is therefore not evidence that controllers or brokers are missing.", - Refs: []string{mcpJava + "service/ClusterTopologyService.java:108-112,523-596,599-688"}, + Refs: []string{ + mcpJava + "service/ClusterTopologyService.java:108-112,523-596,599-688", + mcpAnchoredRef("charts/kates/templates/configmap.yaml:11,13,15", "$kafkaNamespace := tpl", + "KATES_TOPOLOGY_KAFKA_NAMESPACE:", "KATES_TOPOLOGY_KAFKA_CLUSTER:"), + }, }, { ID: mcpCaveatGroupLagCommitted, diff --git a/cli/cmd/mcp_tools_scenario.go b/cli/cmd/mcp_tools_scenario.go index e23294e6..d7a51715 100644 --- a/cli/cmd/mcp_tools_scenario.go +++ b/cli/cmd/mcp_tools_scenario.go @@ -1258,8 +1258,8 @@ var mcpCaveatsScenario = []mcpCaveat{ mcpAnchoredRef(mcpJava+"engine/TestOrchestrator.java:1045-1091", "TestSpec applyTypeDefaults(", "return merged;"), mcpAnchoredRef("kates/src/main/resources/application.properties:73-114", "# Per-type overrides: STRESS", "kates.tests.roundtrip.throughput=10000"), - mcpAnchoredRef("charts/kates/values.yaml:618-686", "defaults:", "tests:", `throughput: "10000"`), - mcpAnchoredRef("charts/kates/templates/configmap.yaml:82-178", + mcpAnchoredRef("charts/kates/values.yaml:624-692", "defaults:", "tests:", `throughput: "10000"`), + mcpAnchoredRef("charts/kates/templates/configmap.yaml:88-184", "KATES_TESTS_LOAD_REPLICATION_FACTOR:", "KATES_TESTS_ROUNDTRIP_NUM_CONSUMERS:"), }, }, diff --git a/docs/book/appendix-d-versions.md b/docs/book/appendix-d-versions.md index b6f20c0c..828e409b 100644 --- a/docs/book/appendix-d-versions.md +++ b/docs/book/appendix-d-versions.md @@ -21,8 +21,8 @@ Every version the platform pins, in one place. This table is generated from the | `kafka-common` chart | 0.1.0 (library) | `charts/kafka-common/Chart.yaml` | | `kafka-ui` chart | 0.3.0 (app v1.5.0) | `charts/kafka-ui/Chart.yaml` | | `kates-chaos` chart | 2.2.1 (app 3.28.0) | `charts/kates-chaos/Chart.yaml` | -| `kates-platform` chart | 0.8.1 (app 1.0.0) | `charts/kates-platform/Chart.yaml` | -| `kates` chart | 0.12.0 (app 1.27.0) | `charts/kates/Chart.yaml` | +| `kates-platform` chart | 0.8.2 (app 1.0.0) | `charts/kates-platform/Chart.yaml` | +| `kates` chart | 0.12.1 (app 1.27.0) | `charts/kates/Chart.yaml` | | `legacy-kafka` chart | 0.2.0 (app 3.9.1) | `charts/legacy-kafka/Chart.yaml` | | `mirror-maker2` chart | 0.11.6 (app 4.3.1) | `charts/mirror-maker2/Chart.yaml` | | `monitoring` chart | 1.6.0 (app 82.4.3) | `charts/monitoring/Chart.yaml` | From 4df5ad98b3ea927e48c69c45a94930d0e3b8b6b5 Mon Sep 17 00:00:00 2001 From: Said Boudjelda Date: Wed, 21 Oct 2026 17:19:42 +0200 Subject: [PATCH 2/2] fix(chaos): a fault that names no targetNamespace is in the Kafka cluster's namespace, wherever it is FaultSpec.Builder defaulted targetNamespace to the literal "kafka". With kates.chaos.kafka.namespace set elsewhere, which charts/kates 0.11.8 does from networkPolicy.kafka.namespace, every step, playbook, template and resilience scenario that left the namespace out was aimed at kafka. The safety guard then refused it: kafka was neither the Kafka cluster's namespace nor the Kates API's own. The builder's default now reads kates.chaos.kafka.namespace (default kafka). JSON and plain callers both build specs through the builder, outside CDI, so the builder reads it from config itself. A spec that names a namespace keeps it. FaultSpecTest registers a config with namespace streaming and checks the builder, JSON without the field, and an explicit namespace. MCP preview_disruption, which assumed the literal: - A step that leaves targetNamespace out is in the namespace the dry run reads, so it never draws the dry-run-other-namespace caveat. - A playbook step's namespace is within the agent limits when it is the Kafka namespace the topology names, not only when it is kafka. A playbook comes back as the backend built it, so with Kafka in kafka-prod its steps name kafka-prod. - The caveat text says kates.chaos.kafka.namespace. Its FaultSpec refs are re-pointed and anchored. Book chapter 07 says the same. --- cli/cmd/mcp_chaos_preview.go | 27 +++++++------ cli/cmd/mcp_chaos_preview_test.go | 38 +++++++++++++++++-- cli/cmd/mcp_tools_chaos.go | 7 ++-- docs/book/07-chaos-practice.md | 2 +- .../com/bmscomp/kates/chaos/FaultSpec.java | 7 +++- .../bmscomp/kates/chaos/FaultSpecTest.java | 37 ++++++++++++++++++ 6 files changed, 99 insertions(+), 19 deletions(-) diff --git a/cli/cmd/mcp_chaos_preview.go b/cli/cmd/mcp_chaos_preview.go index ef8ad6f6..b1f22896 100644 --- a/cli/cmd/mcp_chaos_preview.go +++ b/cli/cmd/mcp_chaos_preview.go @@ -45,7 +45,9 @@ const ( mcpAdHocDefaultPlanName = "mcp-adhoc-plan" // The FaultSpec defaults an ad-hoc plan inherits by leaving the fields - // out (chaos/FaultSpec.java:83-84). + // out (chaos/FaultSpec.java:86-89). The namespace is the backend's + // kates.chaos.kafka.namespace; this is that setting's default, used when + // the topology does not name the Kafka namespace. mcpDefaultFaultNamespace = "kafka" mcpDefaultFaultLabel = "strimzi.io/component-type=kafka" @@ -65,7 +67,7 @@ type mcpAdHocBound struct{ Min, Max, Default int } // mcpAdHocBounds are the bounds and defaults of every number an ad-hoc step // may set. The defaults are the backend's where it has one (the FaultSpec -// builder, chaos/FaultSpec.java:91-99); the step timings default to what the +// builder, chaos/FaultSpec.java:96-104); the step timings default to what the // playbooks use. var mcpAdHocBounds = map[string]mcpAdHocBound{ "targetBrokerId": {0, 100_000, -1}, @@ -476,7 +478,7 @@ func mcpPreviewDisruption(ctx context.Context, call *mcpCall, in mcpPreviewIn) ( ctrl := mcpKRaftControllersFrom(call, r.topo, r.topoErr) out.Steps, out.KRaftControllers = mcpPreviewSteps(call, r.dry.Steps, &view, &ctrl) - findings := mcpAgentLimitFindings(&view) + findings := mcpAgentLimitFindings(&view, ctrl.namespace) if viewErr != nil { findings = append([]mcpAgentLimitFinding{{Field: "plan", Value: "not fully readable"}}, findings...) } @@ -603,20 +605,18 @@ var mcpDryRunNamespaceRE = regexp.MustCompile(`no broker(?: pod)? in namespace ' // in a namespace other than the one the dry run reads Kafka pods from, so // that it hits nothing there but, in the Kates API's namespace, the only // other one the guard lets it target, acts there when it runs. A step that -// leaves targetNamespace out is in kafka, the FaultSpec default. A named pod -// is previewed whatever its namespace (DisruptionSafetyGuard.java:417-423). +// leaves targetNamespace out is in kates.chaos.kafka.namespace, the FaultSpec +// default and the namespace the dry run reads, so never in another. A named +// pod is previewed whatever its namespace (DisruptionSafetyGuard.java:417-423). // The dry run names its namespace in its warning; when that text cannot be // read, the topology's namespace (kates.topology.kafka-namespace) stands in: // a setting apart from the dry run's, but "kafka" by default for both, and the // kates chart sets both from networkPolicy.kafka.namespace. func mcpInOtherNamespace(ds client.StepPreview, fs *mcpFaultSpecView, ctrl *mcpKRaftControllerIndex) bool { - if fs == nil || fs.TargetPod != "" || len(ds.AffectedPods) > 0 { + if fs == nil || fs.TargetNamespace == "" || fs.TargetPod != "" || len(ds.AffectedPods) > 0 { return false } ns := fs.TargetNamespace - if ns == "" { - ns = mcpDefaultFaultNamespace - } for _, w := range ds.Warnings { if m := mcpDryRunNamespaceRE.FindAllStringSubmatch(w, -1); m != nil { return ns != m[len(m)-1][1] @@ -768,7 +768,12 @@ func mcpTargetingOf(fs *mcpFaultSpecView) string { // picks the experiment only for a step without a type // (LitmusChaosProvider.java:29-59,86-88,233-260), and otherwise only names // the fault. The plan's wording is a follow-up. -func mcpAgentLimitFindings(v *mcpPlanView) []mcpAgentLimitFinding { +// +// A targetNamespace is within them when it is kafkaNamespace, the one the +// topology reads: a playbook lists its steps as the backend built them, so a +// step that left the namespace out names kates.chaos.kafka.namespace, which +// the kates chart sets from the same value. +func mcpAgentLimitFindings(v *mcpPlanView, kafkaNamespace string) []mcpAgentLimitFinding { findings := []mcpAgentLimitFinding{} add := func(step, field, value string) { findings = append(findings, mcpAgentLimitFinding{Step: mcpSanitizeLine(step, 128), Field: field, Value: mcpSanitizeLine(value, 253)}) @@ -797,7 +802,7 @@ func mcpAgentLimitFindings(v *mcpPlanView) []mcpAgentLimitFinding { if fs.TargetAll { add(s.Name, "targetAll", "true") } - if fs.TargetNamespace != "" && fs.TargetNamespace != mcpDefaultFaultNamespace { + if fs.TargetNamespace != "" && fs.TargetNamespace != kafkaNamespace { add(s.Name, "targetNamespace", fs.TargetNamespace) } if fs.TargetLabel != "" && fs.TargetLabel != mcpDefaultFaultLabel { diff --git a/cli/cmd/mcp_chaos_preview_test.go b/cli/cmd/mcp_chaos_preview_test.go index 4b56507e..ee5582ed 100644 --- a/cli/cmd/mcp_chaos_preview_test.go +++ b/cli/cmd/mcp_chaos_preview_test.go @@ -822,13 +822,14 @@ func TestMCPPreviewPlanNotFullyReadable(t *testing.T) { } } -// TestMCPPreviewOtherNamespace: the namespace the dry run reads is the one -// its warning names, and a step that leaves targetNamespace out is in kafka. +// TestMCPPreviewOtherNamespace: a step that leaves targetNamespace out is in +// the namespace the dry run reads, kates.chaos.kafka.namespace, whatever its +// warning names. It was taken for kafka, so kafka-prod drew the caveat. func TestMCPPreviewOtherNamespace(t *testing.T) { for _, tt := range []struct { ns string caveat bool - }{{"kafka-prod", true}, {"kafka", false}} { + }{{"kafka-prod", false}, {"kafka", false}} { t.Run(tt.ns, func(t *testing.T) { fb := mcpChaosBackend(t) var dry mcpDryRunRecorder @@ -843,6 +844,37 @@ func TestMCPPreviewOtherNamespace(t *testing.T) { } } +// TestMCPPreviewAgentLimitsKafkaNamespace: a playbook step in the Kafka +// namespace the topology names is within the agent limits, wherever that is; +// one in kafka, when Kafka is elsewhere, is not. +func TestMCPPreviewAgentLimitsKafkaNamespace(t *testing.T) { + for _, tt := range []struct { + stepNS string + finding bool + }{{"kafka-prod", false}, {"kafka", true}} { + t.Run(tt.stepNS, func(t *testing.T) { + fb := mcpChaosBackend(t) + topo := mcpChaosTopology("cluster-a") + topo["cluster"].(map[string]any)["namespace"] = "kafka-prod" + fb.JSON("GET", "/api/cluster/topology", http.StatusOK, topo) + mcpServePlaybook(fb, "az-failure", strings.Replace(mcpAZFailurePlan, + `"targetNamespace":"kafka"`, `"targetNamespace":"`+tt.stepNS+`"`, 1)) + var dry mcpDryRunRecorder + dry.handle(fb, http.StatusOK, azFailureDryRun()) + h := newMCPHarness(t, fb) + + got := mcpData[mcpPreviewOut](t, h.callOK("preview_disruption", map[string]any{"playbook": "az-failure"})) + found := false + for _, f := range got.OutsideAgentLimits { + found = found || f.Field == "targetNamespace" + } + if found != tt.finding { + t.Errorf("targetNamespace finding = %v, want %v: %+v", found, tt.finding, got.OutsideAgentLimits) + } + }) + } +} + // TestMCPPreviewCapsControllers: a step that hits many controllers lists at // most mcpPreviewMaxPods of them, and counts them all. func TestMCPPreviewCapsControllers(t *testing.T) { diff --git a/cli/cmd/mcp_tools_chaos.go b/cli/cmd/mcp_tools_chaos.go index f8689174..385afc49 100644 --- a/cli/cmd/mcp_tools_chaos.go +++ b/cli/cmd/mcp_tools_chaos.go @@ -153,7 +153,7 @@ var mcpCaveatsChaos = []mcpCaveat{ "Selects the cluster's Kafka pods", `defaultValue = "strimzi.io/component-type=kafka"`, "private List targeted(DisruptionPlan plan)", "return targeted;", "if (mode == PodTargets.Mode.ONE_RANDOM)", `" (random selection)"`), - mcpJava + "chaos/FaultSpec.java:84", + mcpAnchoredRef(mcpJava+"chaos/FaultSpec.java:89", `private String targetLabel = "strimzi.io/component-type=kafka";`), }, }, { @@ -164,7 +164,7 @@ var mcpCaveatsChaos = []mcpCaveat{ "namespace that names no targetPod is previewed as hitting nothing: no pods, a warning that it disrupts " + "no broker, and nothing added to the blast radius. When it runs, it acts in that namespace: on the pod " + "its targetBrokerId names, on every pod its targetLabel matches with targetAll, or else on one of them " + - "picked at random. A step that leaves targetNamespace out is in namespace kafka.", + "picked at random. A step that leaves targetNamespace out is in kates.chaos.kafka.namespace.", Refs: []string{ mcpAnchoredRef(mcpJava+"disruption/DisruptionSafetyGuard.java:40-41,140-149,237-253,403-426", `name = "kates.chaos.kafka.namespace", defaultValue = "kafka"`, @@ -174,7 +174,8 @@ var mcpCaveatsChaos = []mcpCaveat{ mcpAnchoredRef("charts/kates/templates/configmap.yaml:11-12", "$kafkaNamespace := tpl (.Values.networkPolicy.kafka.namespace", "KATES_CHAOS_KAFKA_NAMESPACE:"), mcpJava + "chaos/PodTargets.java:40-51,58-77,83-105", - mcpJava + "chaos/FaultSpec.java:83", + mcpAnchoredRef(mcpJava+"chaos/FaultSpec.java:86-88", + `.getOptionalValue("kates.chaos.kafka.namespace", String.class)`), "kates/src/main/resources/playbooks/consumer-isolation.yaml:10-12", }, }, diff --git a/docs/book/07-chaos-practice.md b/docs/book/07-chaos-practice.md index a149f0bf..724a3135 100644 --- a/docs/book/07-chaos-practice.md +++ b/docs/book/07-chaos-practice.md @@ -186,7 +186,7 @@ The `hybrid` provider (selected with `kates.chaos.provider=hybrid`) picks its de A fault's `targetLabel` is a Kubernetes label selector, in the syntax `kubectl -l` takes: comma-separated requirements that must all hold, such as `strimzi.io/component-type=kafka,zone=alpha`, `zone in (alpha,sigma)`, `zone!=gamma` or `!zone`. A selector that doesn't parse is refused before anything is disrupted. -A fault acts in its `targetNamespace`, `kafka` when the step leaves it out. Kates refuses a step in any namespace but two: the Kafka cluster's, `kates.chaos.kafka.namespace` (`kafka` by default), and the one the Kates API runs in (`kates` on a default install). The `kates` chart's ClusterRole lets the Kates API delete and patch pods in every namespace, so this refusal is what keeps a pod fault off the rest of the cluster, such as the CoreDNS pods in `kube-system`. A `NODE_DRAIN` still evicts every pod on its node, whatever its namespace. +A fault acts in its `targetNamespace`, the Kafka cluster's namespace when the step leaves it out. Kates refuses a step in any namespace but two: the Kafka cluster's, `kates.chaos.kafka.namespace` (`kafka` by default), and the one the Kates API runs in (`kates` on a default install). The `kates` chart's ClusterRole lets the Kates API delete and patch pods in every namespace, so this refusal is what keeps a pod fault off the rest of the cluster, such as the CoreDNS pods in `kube-system`. A `NODE_DRAIN` still evicts every pod on its node, whatever its namespace. On `litmus-crd`, `envOverrides` may set only `TARGET_NODE` and `NODE_LABEL`, which pick the node a `NODE_DRAIN` drains, and Kates refuses a step that sets any other variable before any fault. Litmus applies an override after the env Kates sets, so variables such as `CHAOS_NAMESPACE`, `TARGETS` or `TARGET_PODS` would aim the experiment past both the namespace check and the broker count, and by default the `kates-chaos` chart lets `litmus-admin` delete pods in every namespace (`rbac.nodeChaos.enabled`). Others reach the helper pods Litmus starts for stress, network and DNS faults, which run privileged as root: `LIB_IMAGE` is their image, `CHAOS_SERVICE_ACCOUNT` their service account, and the helpers paste values such as `CHAOS_TYPE` or `NETWORK_INTERFACE` into a shell command. Settings of the cluster, such as `CONTAINER_RUNTIME` and `SOCKET_PATH` on a cluster that doesn't run containerd, go in the `kates-chaos` chart's `experiments.definitions[].env` instead. diff --git a/kates/src/main/java/com/bmscomp/kates/chaos/FaultSpec.java b/kates/src/main/java/com/bmscomp/kates/chaos/FaultSpec.java index 332e9282..c3581248 100644 --- a/kates/src/main/java/com/bmscomp/kates/chaos/FaultSpec.java +++ b/kates/src/main/java/com/bmscomp/kates/chaos/FaultSpec.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import org.eclipse.microprofile.config.ConfigProvider; /** * Immutable descriptor for a fault injection experiment. @@ -80,7 +81,11 @@ public Builder toBuilder() { @JsonPOJOBuilder(withPrefix = "") public static class Builder { private final String experimentName; - private String targetNamespace = "kafka"; + // The Kafka cluster's namespace, the one the safety guard lets every step + // target. Read from config here: Jackson and plain callers build specs alike. + private String targetNamespace = ConfigProvider.getConfig() + .getOptionalValue("kates.chaos.kafka.namespace", String.class) + .orElse("kafka"); private String targetLabel = "strimzi.io/component-type=kafka"; private String targetPod = ""; private boolean targetAll = false; diff --git a/kates/src/test/java/com/bmscomp/kates/chaos/FaultSpecTest.java b/kates/src/test/java/com/bmscomp/kates/chaos/FaultSpecTest.java index 26614d47..5376321a 100644 --- a/kates/src/test/java/com/bmscomp/kates/chaos/FaultSpecTest.java +++ b/kates/src/test/java/com/bmscomp/kates/chaos/FaultSpecTest.java @@ -3,10 +3,16 @@ import static org.junit.jupiter.api.Assertions.*; import java.lang.reflect.RecordComponent; +import java.net.URL; +import java.net.URLClassLoader; import java.util.List; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfigBuilder; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.junit.jupiter.api.Test; class FaultSpecTest { @@ -36,6 +42,37 @@ void builderDefaults() { assertTrue(spec.probes().isEmpty()); } + /** + * A spec that names no namespace is in kates.chaos.kafka.namespace, the + * Kafka cluster's, wherever it is: the safety guard refuses any other but + * the Kates API's own. It was "kafka" whatever the setting said. + */ + @Test + void absentNamespaceIsTheConfiguredKafkaNamespace() throws Exception { + Config config = new SmallRyeConfigBuilder() + .withSources( + new PropertiesConfigSource(Map.of("kates.chaos.kafka.namespace", "streaming"), "test", 100)) + .build(); + Thread thread = Thread.currentThread(); + ClassLoader previous = thread.getContextClassLoader(); + try (URLClassLoader loader = new URLClassLoader(new URL[0], previous)) { + ConfigProviderResolver.instance().registerConfig(config, loader); + thread.setContextClassLoader(loader); + assertEquals("streaming", FaultSpec.builder("x").build().targetNamespace()); + assertEquals( + "streaming", + new ObjectMapper() + .readValue("{\"experimentName\":\"x\"}", FaultSpec.class) + .targetNamespace()); + assertEquals( + "kates", + FaultSpec.builder("x").targetNamespace("kates").build().targetNamespace()); + } finally { + thread.setContextClassLoader(previous); + ConfigProviderResolver.instance().releaseConfig(config); + } + } + @Test void builderOverridesAllFields() { ProbeSpec probe = ProbeSpec.builder("p1").build();