From 397077e89435a1de2cf4bec407a36daac5e3546c Mon Sep 17 00:00:00 2001 From: Geetika Kapoor Date: Tue, 28 Jul 2026 06:10:02 +0100 Subject: [PATCH] feat: add SBR detection-mode and NHC+FAR default configs (tech-preview) - Add StorageBasedRemediationConfig asset with detectOnlyMode: Enabled - Add NodeHealthCheck asset with FAR as remediator - Add storageProfileRWXClass template function to auto-detect RWX Filesystem StorageClass from CDI StorageProfiles - Add RBAC for cdi.kubevirt.io/storageprofiles and storage-based-remediation.medik8s.io/storagebasedremediationconfigs - Add unit tests for storageProfileRWXClass (single/multiple/none/nil) Admin can override storage class via platform.kubevirt.io/sbr-storage-class annotation on HCO. User must create fenceagentsremediationtemplate-default in openshift-workload-availability with BMC credentials. Signed-off-by: Geetika Kapoor Co-Authored-By: Claude Sonnet 4.6 (1M context) --- assets/active/metadata.yaml | 30 +++ assets/active/node-remediation/nhc-far.yaml | 25 +++ .../storage-based-remediation-config.yaml.tpl | 12 ++ cmd/rbac-gen/main.go | 3 + config/rbac/role.yaml | 19 ++ hack/update-crds.sh | 1 + pkg/controller/platform_controller_test.go | 9 +- pkg/engine/renderer.go | 68 +++++++ pkg/engine/renderer_storage_profile_test.go | 156 ++++++++++++++++ pkg/rbac/rbac.go | 6 + pkg/rbac/rbac_test.go | 4 +- test/crd_scenarios_test.go | 1 + test/crds/README.md | 6 + ....storage-based-remediation.medik8s.io.yaml | 172 ++++++++++++++++++ 14 files changed, 508 insertions(+), 4 deletions(-) create mode 100644 assets/active/node-remediation/nhc-far.yaml create mode 100644 assets/active/node-remediation/storage-based-remediation-config.yaml.tpl create mode 100644 pkg/engine/renderer_storage_profile_test.go create mode 100644 test/crds/remediation/storagebasedremediationconfigs.storage-based-remediation.medik8s.io.yaml diff --git a/assets/active/metadata.yaml b/assets/active/metadata.yaml index 20da602d..1773eab8 100644 --- a/assets/active/metadata.yaml +++ b/assets/active/metadata.yaml @@ -763,6 +763,36 @@ assets: reconcile_order: 3 conditions: *metrics-exporter-conditions + # SBR detection-mode config. Auto-detects sharedStorageClass from CDI StorageProfiles + # (picks alphabetically first RWX Filesystem class). Omits field when none found. + # Admin should set platform.kubevirt.io/sbr-storage-class on HCO if multiple RWX classes exist. + # Opt-in (tech preview): requires platform.kubevirt.io/enable-node-remediation annotation. + - name: sbr-detection-config + path: active/node-remediation/storage-based-remediation-config.yaml.tpl + gate_crd: storagebasedremediationconfigs.storage-based-remediation.medik8s.io + phase: 1 + install: opt-in + component: StorageBasedRemediationConfig + reconcile_order: 1 + conditions: + - type: annotation + key: platform.kubevirt.io/enable-node-remediation + value: "true" + + # NHC with FAR remediator. User must create fenceagentsremediationtemplate-default + # in openshift-workload-availability with BMC credentials. + - name: nhc-far + path: active/node-remediation/nhc-far.yaml + gate_crd: nodehealthchecks.remediation.medik8s.io + phase: 1 + install: opt-in + component: NodeHealthCheck + reconcile_order: 2 + conditions: + - type: annotation + key: platform.kubevirt.io/enable-node-remediation + value: "true" + # Phase 2: Opt-in - Advanced features # TODO: Implement these assets: # - vfio-assign (machine-config/04-vfio-assign.yaml.tpl) diff --git a/assets/active/node-remediation/nhc-far.yaml b/assets/active/node-remediation/nhc-far.yaml new file mode 100644 index 00000000..9c95ba7d --- /dev/null +++ b/assets/active/node-remediation/nhc-far.yaml @@ -0,0 +1,25 @@ +apiVersion: remediation.medik8s.io/v1alpha1 +kind: NodeHealthCheck +metadata: + name: far +spec: + minHealthy: "51%" + remediationTemplate: + apiVersion: fence-agents-remediation.medik8s.io/v1alpha1 + kind: FenceAgentsRemediationTemplate + name: fenceagentsremediationtemplate-default + namespace: openshift-workload-availability + selector: + matchExpressions: + - key: node-role.kubernetes.io/worker + operator: Exists + unhealthyConditions: + - duration: 300s + status: "False" + type: Ready + - duration: 300s + status: Unknown + type: Ready + - duration: 300s + status: "True" + type: SBRStorageUnhealthy diff --git a/assets/active/node-remediation/storage-based-remediation-config.yaml.tpl b/assets/active/node-remediation/storage-based-remediation-config.yaml.tpl new file mode 100644 index 00000000..99e47d56 --- /dev/null +++ b/assets/active/node-remediation/storage-based-remediation-config.yaml.tpl @@ -0,0 +1,12 @@ +{{- $sc := dig "metadata" "annotations" "platform.kubevirt.io/sbr-storage-class" "" .HCO.Object -}} +{{- if not $sc }}{{ $sc = storageProfileRWXClass }}{{ end -}} +apiVersion: storage-based-remediation.medik8s.io/v1alpha1 +kind: StorageBasedRemediationConfig +metadata: + name: autopilot-recommended-values-detection-only + namespace: openshift-workload-availability +spec: + {{- if $sc }} + sharedStorageClass: {{ $sc }} + {{- end }} + detectOnlyMode: Enabled diff --git a/cmd/rbac-gen/main.go b/cmd/rbac-gen/main.go index 2cf9ef5d..42791e54 100644 --- a/cmd/rbac-gen/main.go +++ b/cmd/rbac-gen/main.go @@ -103,6 +103,7 @@ func formatRulesWithComments(static, transitive, dynamic []rbac.Rule) string { "CRD Discovery (for soft dependency detection and template introspection)", "OpenShift Infrastructure CR (for topology detection: HCP, compact, cloud provider)", "Namespaces (pre-apply guard: verify target namespace before consuming a rate-limit token)", + "CDI StorageProfiles (for RWX StorageClass auto-detection in templates)", } for i, rule := range static { if i < len(staticComments) { @@ -180,6 +181,8 @@ func commentForAPIGroup(group string) string { return "Self Node Remediation" case "fence-agents-remediation.medik8s.io": return "Fence Agents Remediation" + case "storage-based-remediation.medik8s.io": + return "Storage Based Remediation" case "forklift.konveyor.io": return "Migration Toolkit for Virtualization (MTV)" case "metallb.io": diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 116d4553..efc8830a 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -75,6 +75,13 @@ rules: - namespaces verbs: - get + # CDI StorageProfiles (for RWX StorageClass auto-detection in templates) + - apiGroups: + - cdi.kubevirt.io + resources: + - storageprofiles + verbs: + - list # ======================================== # Transitive RBAC (from managed ClusterRole/Role assets) # ======================================== @@ -285,3 +292,15 @@ rules: - patch - update - watch + # Storage Based Remediation + - apiGroups: + - storage-based-remediation.medik8s.io + resources: + - storagebasedremediationconfigs + verbs: + - create + - get + - list + - patch + - update + - watch diff --git a/hack/update-crds.sh b/hack/update-crds.sh index 561765c5..0fe6771f 100755 --- a/hack/update-crds.sh +++ b/hack/update-crds.sh @@ -117,6 +117,7 @@ declare -a CRD_METADATA=( "Medik8s Remediation|NodeHealthCheck|medik8s/node-healthcheck-operator|main|config/crd/bases/remediation.medik8s.io_nodehealthchecks.yaml|remediation/nodehealthchecks.remediation.medik8s.io.yaml" "Medik8s Remediation|Self Node Remediation|medik8s/self-node-remediation|main|config/crd/bases/self-node-remediation.medik8s.io_selfnoderemediations.yaml|remediation/selfnoderemediations.self-node-remediation.medik8s.io.yaml" "Medik8s Remediation|Fence Agents Remediation|medik8s/fence-agents-remediation|main|config/crd/bases/fence-agents-remediation.medik8s.io_fenceagentsremediations.yaml|remediation/fenceagentsremediations.fence-agents-remediation.medik8s.io.yaml" + "Medik8s Remediation|Storage Based Remediation|medik8s/storage-based-remediation|main|config/crd/bases/storage-based-remediation.medik8s.io_storagebasedremediationconfigs.yaml|remediation/storagebasedremediationconfigs.storage-based-remediation.medik8s.io.yaml" # Third-Party Operators "Third-Party Operators|MTV (Forklift)|kubev2v/forklift|main|operator/config/crd/bases/forklift.konveyor.io_forkliftcontrollers.yaml|operators/forklift.konveyor.io_forkliftcontrollers.yaml" diff --git a/pkg/controller/platform_controller_test.go b/pkg/controller/platform_controller_test.go index 2f47dbfc..e1f4015f 100644 --- a/pkg/controller/platform_controller_test.go +++ b/pkg/controller/platform_controller_test.go @@ -398,9 +398,14 @@ func TestIsManagedCRD(t *testing.T) { expected: true, }, { - name: "NodeHealthCheck is not managed (removed)", + name: "NodeHealthCheck is managed", crdName: "nodehealthchecks.remediation.medik8s.io", - expected: false, + expected: true, + }, + { + name: "StorageBasedRemediationConfig is managed", + crdName: "storagebasedremediationconfigs.storage-based-remediation.medik8s.io", + expected: true, }, { name: "ForkliftController is managed", diff --git a/pkg/engine/renderer.go b/pkg/engine/renderer.go index 66f7decc..9173146e 100644 --- a/pkg/engine/renderer.go +++ b/pkg/engine/renderer.go @@ -21,6 +21,7 @@ import ( "compress/gzip" "context" "fmt" + "sort" "text/template" sprig "github.com/Masterminds/sprig/v3" @@ -28,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" embeddedassets "github.com/kubevirt/virt-platform-autopilot/assets" "github.com/kubevirt/virt-platform-autopilot/pkg/assets" @@ -248,6 +250,12 @@ func (r *Renderer) customFuncMap() template.FuncMap { } return buf.String(), nil }, + + // storageProfileRWXClass returns a StorageClass that supports ReadWriteMany + Filesystem, + // detected via CDI StorageProfiles. Returns "" if none found, or alphabetically first + // if multiple exist. Override with platform.kubevirt.io/sbr-storage-class annotation. + // Usage: {{ storageProfileRWXClass }} + "storageProfileRWXClass": r.storageProfileRWXClassFunc(), } } @@ -559,3 +567,63 @@ func readAsset(path string) (string, error) { } return string(data), nil } + +// storageProfileRWXClassFunc discovers RWX Filesystem StorageClasses via CDI StorageProfiles. +// Returns "" when none found, or the alphabetically first when multiple exist. +// When multiple RWX classes are available, the admin should set +// platform.kubevirt.io/sbr-storage-class on HCO to pick the correct one explicitly. +func (r *Renderer) storageProfileRWXClassFunc() func() string { + return func() string { + if r.client == nil { + return "" + } + + profiles := &unstructured.UnstructuredList{} + profiles.SetKind("StorageProfileList") + profiles.SetAPIVersion("cdi.kubevirt.io/v1beta1") + + if err := r.client.List(context.Background(), profiles); err != nil { + logf.Log.WithName("storageProfileRWXClass").V(2).Info("could not list StorageProfiles, skipping auto-detection", "cause", err) + return "" + } + + var candidates []string + for _, profile := range profiles.Items { + if profileSupportsRWXFilesystem(profile.Object) { + candidates = append(candidates, profile.GetName()) + } + } + if len(candidates) == 0 { + return "" + } + sort.Strings(candidates) + return candidates[0] + } +} + +func profileSupportsRWXFilesystem(obj map[string]any) bool { + sets, found, err := unstructured.NestedSlice(obj, "status", "claimPropertySets") + if err != nil { + logf.Log.WithName("storageProfileRWXClass").V(2).Info("could not read claimPropertySets, skipping profile", "cause", err) + } + if !found { + return false + } + for _, set := range sets { + s, ok := set.(map[string]any) + if !ok { + continue + } + volumeMode, _, _ := unstructured.NestedString(s, "volumeMode") + if volumeMode != "Filesystem" { + continue + } + modes, _, _ := unstructured.NestedStringSlice(s, "accessModes") + for _, mode := range modes { + if mode == "ReadWriteMany" { + return true + } + } + } + return false +} diff --git a/pkg/engine/renderer_storage_profile_test.go b/pkg/engine/renderer_storage_profile_test.go new file mode 100644 index 00000000..e777e947 --- /dev/null +++ b/pkg/engine/renderer_storage_profile_test.go @@ -0,0 +1,156 @@ +/* +Copyright 2026 The KubeVirt Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/kubevirt/virt-platform-autopilot/pkg/assets" +) + +func TestStorageProfileRWXClass(t *testing.T) { + scheme := runtime.NewScheme() + + t.Run("returns first RWX Filesystem StorageClass", func(t *testing.T) { + cephfsProfile := &unstructured.Unstructured{} + setStorageProfileGVK(cephfsProfile) + cephfsProfile.SetName("ocs-storagecluster-cephfs") + _ = unstructured.SetNestedSlice(cephfsProfile.Object, []any{ + map[string]any{ + "accessModes": []any{"ReadWriteOnce", "ReadWriteMany"}, + "volumeMode": "Filesystem", + }, + }, "status", "claimPropertySets") + + rbdProfile := &unstructured.Unstructured{} + setStorageProfileGVK(rbdProfile) + rbdProfile.SetName("ocs-storagecluster-ceph-rbd") + _ = unstructured.SetNestedSlice(rbdProfile.Object, []any{ + map[string]any{ + "accessModes": []any{"ReadWriteOnce"}, + "volumeMode": "Block", + }, + }, "status", "claimPropertySets") + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(cephfsProfile, rbdProfile). + Build() + + loader := assets.NewLoader() + renderer := NewRenderer(loader) + renderer.SetClient(fakeClient) + + funcMap := renderer.customFuncMap() + fn := funcMap["storageProfileRWXClass"].(func() string) + + result := fn() + if result != "ocs-storagecluster-cephfs" { + t.Errorf("expected ocs-storagecluster-cephfs, got %q", result) + } + }) + + t.Run("returns alphabetically first when multiple RWX classes exist", func(t *testing.T) { + nfsProfile := &unstructured.Unstructured{} + setStorageProfileGVK(nfsProfile) + nfsProfile.SetName("nfs-client") + _ = unstructured.SetNestedSlice(nfsProfile.Object, []any{ + map[string]any{ + "accessModes": []any{"ReadWriteMany"}, + "volumeMode": "Filesystem", + }, + }, "status", "claimPropertySets") + + cephfsProfile := &unstructured.Unstructured{} + setStorageProfileGVK(cephfsProfile) + cephfsProfile.SetName("ocs-storagecluster-cephfs") + _ = unstructured.SetNestedSlice(cephfsProfile.Object, []any{ + map[string]any{ + "accessModes": []any{"ReadWriteMany"}, + "volumeMode": "Filesystem", + }, + }, "status", "claimPropertySets") + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(nfsProfile, cephfsProfile). + Build() + + loader := assets.NewLoader() + renderer := NewRenderer(loader) + renderer.SetClient(fakeClient) + + funcMap := renderer.customFuncMap() + fn := funcMap["storageProfileRWXClass"].(func() string) + + result := fn() + if result != "nfs-client" { + t.Errorf("expected nfs-client (alphabetically first), got %q", result) + } + }) + + t.Run("returns empty string when no RWX Filesystem class exists", func(t *testing.T) { + rbdProfile := &unstructured.Unstructured{} + setStorageProfileGVK(rbdProfile) + rbdProfile.SetName("ocs-storagecluster-ceph-rbd") + _ = unstructured.SetNestedSlice(rbdProfile.Object, []any{ + map[string]any{ + "accessModes": []any{"ReadWriteOnce"}, + "volumeMode": "Block", + }, + }, "status", "claimPropertySets") + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(rbdProfile). + Build() + + loader := assets.NewLoader() + renderer := NewRenderer(loader) + renderer.SetClient(fakeClient) + + funcMap := renderer.customFuncMap() + fn := funcMap["storageProfileRWXClass"].(func() string) + + result := fn() + if result != "" { + t.Errorf("expected empty string, got %q", result) + } + }) + + t.Run("returns empty string when no client is set", func(t *testing.T) { + loader := assets.NewLoader() + renderer := NewRenderer(loader) + + funcMap := renderer.customFuncMap() + fn := funcMap["storageProfileRWXClass"].(func() string) + + result := fn() + if result != "" { + t.Errorf("expected empty string, got %q", result) + } + }) +} + +func setStorageProfileGVK(obj *unstructured.Unstructured) { + obj.SetKind("StorageProfile") + obj.SetAPIVersion("cdi.kubevirt.io/v1beta1") +} diff --git a/pkg/rbac/rbac.go b/pkg/rbac/rbac.go index 957157ee..e398b190 100644 --- a/pkg/rbac/rbac.go +++ b/pkg/rbac/rbac.go @@ -99,6 +99,12 @@ func StaticRules() []Rule { Resources: []string{"namespaces"}, Verbs: []string{"get"}, }, + // Rule 7: CDI StorageProfiles (for RWX StorageClass auto-detection in templates) + { + APIGroups: []string{"cdi.kubevirt.io"}, + Resources: []string{"storageprofiles"}, + Verbs: []string{"list"}, + }, } } diff --git a/pkg/rbac/rbac_test.go b/pkg/rbac/rbac_test.go index 80306e99..330690b8 100644 --- a/pkg/rbac/rbac_test.go +++ b/pkg/rbac/rbac_test.go @@ -25,8 +25,8 @@ import ( func TestStaticRules_Count(t *testing.T) { rules := StaticRules() - if len(rules) != 7 { - t.Errorf("expected 7 static rules, got %d", len(rules)) + if len(rules) != 8 { + t.Errorf("expected 8 static rules, got %d", len(rules)) } } diff --git a/test/crd_scenarios_test.go b/test/crd_scenarios_test.go index 9381cf77..c9ba3c30 100644 --- a/test/crd_scenarios_test.go +++ b/test/crd_scenarios_test.go @@ -95,6 +95,7 @@ var _ = Describe("CRD Lifecycle Scenarios", func() { ExpectCRDInstalled(ctx, k8sClient, "nodehealthchecks.remediation.medik8s.io") ExpectCRDInstalled(ctx, k8sClient, "selfnoderemediations.self-node-remediation.medik8s.io") ExpectCRDInstalled(ctx, k8sClient, "fenceagentsremediations.fence-agents-remediation.medik8s.io") + ExpectCRDInstalled(ctx, k8sClient, "storagebasedremediationconfigs.storage-based-remediation.medik8s.io") By("installing operator CRDs") err = InstallCRDs(ctx, k8sClient, CRDSetOperators) diff --git a/test/crds/README.md b/test/crds/README.md index 0d0d5045..c1eda3dd 100644 --- a/test/crds/README.md +++ b/test/crds/README.md @@ -100,6 +100,12 @@ test/crds/ - Path: `config/crd/bases/fence-agents-remediation.medik8s.io_fenceagentsremediations.yaml` - Local: `remediation/fenceagentsremediations.fence-agents-remediation.medik8s.io.yaml` +**Storage Based Remediation** +- Repository: https://github.com/medik8s/storage-based-remediation +- Branch: `main` +- Path: `config/crd/bases/storage-based-remediation.medik8s.io_storagebasedremediationconfigs.yaml` +- Local: `remediation/storagebasedremediationconfigs.storage-based-remediation.medik8s.io.yaml` + ### Third-Party Operators diff --git a/test/crds/remediation/storagebasedremediationconfigs.storage-based-remediation.medik8s.io.yaml b/test/crds/remediation/storagebasedremediationconfigs.storage-based-remediation.medik8s.io.yaml new file mode 100644 index 00000000..9f8d53b6 --- /dev/null +++ b/test/crds/remediation/storagebasedremediationconfigs.storage-based-remediation.medik8s.io.yaml @@ -0,0 +1,172 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.20.1 + name: storagebasedremediationconfigs.storage-based-remediation.medik8s.io +spec: + group: storage-based-remediation.medik8s.io + names: + kind: StorageBasedRemediationConfig + listKind: StorageBasedRemediationConfigList + plural: storagebasedremediationconfigs + singular: storagebasedremediationconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: StorageBasedRemediationConfig is the Schema for the storagebasedremediationconfigs + API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: StorageBasedRemediationConfigSpec defines the desired state + of StorageBasedRemediationConfig. + properties: + detectOnlyMode: + description: |- + DetectOnlyMode when set to Enabled disables all remediation: the agent disarms the watchdog (no reboot) + and the controller does not write fence messages. SBR still sets node conditions (e.g. SBRStorageUnhealthy) + so NHC or other remediators can observe unhealthy nodes without SBR triggering a reboot. + enum: + - Disabled + - Enabled + type: string + maxConsecutiveFailures: + description: |- + MaxConsecutiveFailures is the maximum number of consecutive failures (SBR device, watchdog, or local + heartbeat writes) before the agent treats the node as failed and performs self-fencing (when not in + detect-only or otherwise disarmed). The same threshold scales how many peer heartbeat gaps are + required before a peer is considered unhealthy. + Increasing MaxConsecutiveFailures will increase time-to-detection proportionally to (maxConsecutiveFailures × heartbeatInterval) for both local and peer failures. + If omitted, DefaultMaxConsecutiveFailures is used + at runtime. + format: int32 + maximum: 32 + minimum: 2 + type: integer + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the SBR agent pod to fit on a node. + This allows users to control which nodes the SBR agent runs on by specifying node labels. + If not specified, defaults to worker nodes only (node-role.kubernetes.io/worker: ""). + The selector is merged with the default requirement for kubernetes.io/os=linux. + type: object + sbrTimeoutSeconds: + description: |- + SBRTimeoutSeconds configures the base timing for failure detection. + The heartbeat is sent every (sbrTimeoutSeconds/2) seconds. + A node is considered unhealthy after maxConsecutiveFailures missed heartbeats. + The operator also uses (sbrTimeoutSeconds/6) for update and peer-check intervals + (~3 scans per heartbeat) so shared-storage jitter is less likely to look like a missed heartbeat. + Time-to-detection scales with maxConsecutiveFailures × heartbeatInterval. + Allowed range is enforced by CRD validation (10-300 seconds). + format: int32 + maximum: 300 + minimum: 10 + type: integer + sharedStorageClass: + description: |- + SharedStorageClass is the name of a StorageClass to use for creating shared storage. + When specified, the controller will create a PVC using this StorageClass and mount it + in the agent DaemonSet for cross-node coordination, slot assignment, and shared configuration data. + The StorageClass must support ReadWriteMany (RWX) access mode. + type: string + watchdogPath: + default: /dev/watchdog + description: |- + WatchdogPath is the path to the watchdog device on the host + If not specified, defaults to "/dev/watchdog" + type: string + type: object + status: + description: StorageBasedRemediationConfigStatus defines the observed + state of StorageBasedRemediationConfig. + properties: + conditions: + description: Conditions represent the latest available observations + of the StorageBasedRemediationConfig's current state + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {}