diff --git a/chart/templates/skyhook-crd.yaml b/chart/templates/skyhook-crd.yaml index ea894570..b85764ef 100644 --- a/chart/templates/skyhook-crd.yaml +++ b/chart/templates/skyhook-crd.yaml @@ -431,8 +431,8 @@ spec: concert without needing to delete to ad in discovery of the issue. type: boolean podNonInterruptLabels: - description: PodNonInteruptLabels are a set of labels we want to monitor - pods for whether they Interruptible + description: PodNonInterruptLabels are a set of labels we want to + monitor pods for whether they Interruptible properties: matchExpressions: description: matchExpressions is a list of label selector requirements. diff --git a/k8s-tests/chainsaw/skyhook/interrupt-grouping/assert.yaml b/k8s-tests/chainsaw/skyhook/interrupt-grouping/assert.yaml index cd6c3e74..7a0bb994 100644 --- a/k8s-tests/chainsaw/skyhook/interrupt-grouping/assert.yaml +++ b/k8s-tests/chainsaw/skyhook/interrupt-grouping/assert.yaml @@ -18,16 +18,9 @@ # LICENSE END # - - - - - - kind: Pod apiVersion: v1 metadata: - generateName: interrupt-grouping-dax-1.2.3-apply- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt-grouping @@ -61,7 +54,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: interrupt-grouping-dax-1.2.3-config- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt-grouping @@ -92,7 +84,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: interrupt-grouping-interrupt-service- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt-grouping @@ -132,7 +123,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: interrupt-grouping-dax-1.2.3-post-interrupt- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt-grouping diff --git a/k8s-tests/chainsaw/skyhook/interrupt/assert.yaml b/k8s-tests/chainsaw/skyhook/interrupt/assert.yaml index 80e43937..7e4d8d30 100644 --- a/k8s-tests/chainsaw/skyhook/interrupt/assert.yaml +++ b/k8s-tests/chainsaw/skyhook/interrupt/assert.yaml @@ -18,17 +18,10 @@ # LICENSE END # - - - - - - --- kind: Pod apiVersion: v1 metadata: - generateName: interrupt-invalid-1.2.3-uninstall- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt @@ -63,7 +56,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: interrupt-jason-1.3.2-apply- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt @@ -126,7 +118,6 @@ data: kind: Pod apiVersion: v1 metadata: - generateName: interrupt-interrupt-service- namespace: skyhook labels: skyhook.nvidia.com/name: interrupt diff --git a/k8s-tests/chainsaw/skyhook/interrupt/pod.yaml b/k8s-tests/chainsaw/skyhook/interrupt/pod.yaml index 6f73545b..78b0725a 100644 --- a/k8s-tests/chainsaw/skyhook/interrupt/pod.yaml +++ b/k8s-tests/chainsaw/skyhook/interrupt/pod.yaml @@ -18,12 +18,6 @@ # LICENSE END # - - - - - - --- apiVersion: v1 kind: Pod @@ -64,8 +58,8 @@ metadata: name: invalid-package namespace: skyhook labels: - skyhook.nvidia.com/package: invalid-1.2.3 - skyhook.nvidia.com/name: interrupt + skyhook.nvidia.com/package: invalid-1.2.3 + skyhook.nvidia.com/name: interrupt annotations: skyhook.nvidia.com/package: '{"name":"invalid","version":"1.2.3","stage":"apply","skyhook":"interrupt","image":"ghcr.io/nvidia/skyhook/agentless"}' spec: diff --git a/k8s-tests/chainsaw/skyhook/interrupt/skyhook.yaml b/k8s-tests/chainsaw/skyhook/interrupt/skyhook.yaml index 671ce2f0..c1fdf861 100644 --- a/k8s-tests/chainsaw/skyhook/interrupt/skyhook.yaml +++ b/k8s-tests/chainsaw/skyhook/interrupt/skyhook.yaml @@ -18,12 +18,6 @@ # LICENSE END # - - - - - - --- apiVersion: skyhook.nvidia.com/v1alpha1 kind: Skyhook diff --git a/k8s-tests/chainsaw/skyhook/simple-skyhook/assert.yaml b/k8s-tests/chainsaw/skyhook/simple-skyhook/assert.yaml index b7403f78..29f90256 100644 --- a/k8s-tests/chainsaw/skyhook/simple-skyhook/assert.yaml +++ b/k8s-tests/chainsaw/skyhook/simple-skyhook/assert.yaml @@ -18,17 +18,10 @@ # LICENSE END # - - - - - - --- kind: Pod apiVersion: v1 metadata: - generateName: simple-skyhook-spencer-3.2.3-apply- namespace: skyhook labels: skyhook.nvidia.com/name: simple-skyhook diff --git a/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update-no-packages.yaml b/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update-no-packages.yaml index dfe607e0..e5050f2b 100644 --- a/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update-no-packages.yaml +++ b/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update-no-packages.yaml @@ -18,17 +18,10 @@ # LICENSE END # - - - - - - --- kind: Pod apiVersion: v1 metadata: - generateName: uninstall-upgrade-skyhook-dogs-1.2.5-uninstall- namespace: skyhook labels: skyhook.nvidia.com/name: uninstall-upgrade-skyhook @@ -63,7 +56,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: uninstall-upgrade-skyhook-nullptr-2.0.1-uninstall- namespace: skyhook labels: skyhook.nvidia.com/name: uninstall-upgrade-skyhook diff --git a/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update.yaml b/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update.yaml index ad51a84f..0bbe725e 100644 --- a/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update.yaml +++ b/k8s-tests/chainsaw/skyhook/uninstall-upgrade-skyhook/assert-update.yaml @@ -18,17 +18,10 @@ # LICENSE END # - - - - - - --- kind: Pod apiVersion: v1 metadata: - generateName: uninstall-upgrade-skyhook-cats-6.2.0-uninstall- namespace: skyhook labels: skyhook.nvidia.com/name: uninstall-upgrade-skyhook @@ -77,7 +70,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: uninstall-upgrade-skyhook-dogs-1.2.6-uninstall- namespace: skyhook labels: skyhook.nvidia.com/name: uninstall-upgrade-skyhook @@ -112,7 +104,6 @@ spec: kind: Pod apiVersion: v1 metadata: - generateName: uninstall-upgrade-skyhook-nullptr-2.0.1-upgrade- namespace: skyhook labels: skyhook.nvidia.com/name: uninstall-upgrade-skyhook diff --git a/k8s-tests/chainsaw/skyhook/validate-packages/assert-update.yaml b/k8s-tests/chainsaw/skyhook/validate-packages/assert-update.yaml index 90d92109..9e4dcc2b 100644 --- a/k8s-tests/chainsaw/skyhook/validate-packages/assert-update.yaml +++ b/k8s-tests/chainsaw/skyhook/validate-packages/assert-update.yaml @@ -18,12 +18,6 @@ # LICENSE END # - - - - - - apiVersion: v1 kind: Node metadata: @@ -73,24 +67,24 @@ status: observedGeneration: 3 nodeState: (values(@)): - - invalid-image|3.2.3: - name: invalid-image - state: complete - version: '3.2.3' + - invalid-env|5.4.3: image: ghcr.io/nvidia/skyhook/agentless + name: invalid-env stage: config - invalid-resources|1.2.3: - name: invalid-resources state: complete - version: '1.2.3' + version: 5.4.3 + invalid-image|3.2.3: image: ghcr.io/nvidia/skyhook/agentless + name: invalid-image stage: config - invalid-env|5.4.3: - name: invalid-env state: complete - version: '5.4.3' + version: 3.2.3 + invalid-resources|1.2.3: image: ghcr.io/nvidia/skyhook/agentless + name: invalid-resources stage: config + state: complete + version: 1.2.3 nodeStatus: # grab values should be one and is complete (values(@)): diff --git a/operator/Makefile b/operator/Makefile index b4a30f52..ee1813c9 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -140,7 +140,7 @@ $(REPORTING): mkdir -p $@ .PHONY: test -test:: reporting manifests generate fmt vet lint helm-tests e2e-tests unit-tests ## Run all tests. +test:: reporting manifests generate fmt vet lint unit-tests e2e-tests helm-tests ## Run all tests. ifndef CI ## we double define test so we can do thing different if in ci vs local @@ -166,7 +166,7 @@ unit-tests: reporting manifests generate envtest ginkgo kill ## Run unit tests. ## https://kyverno.github.io/chainsaw/latest/configuration/file/ CHAINSAW_ARGS:=--exec-timeout 30s --parallel 2 -e2e-tests: chainsaw run ## Run end to end tests. +e2e-tests: chainsaw install run ## Run end to end tests. ## requires a cluster to be running with access ## locally use kind to create clusters ## in ci, the plan current is to have a real cluster, and create a node pool for testing diff --git a/operator/api/v1alpha1/skyhook_types.go b/operator/api/v1alpha1/skyhook_types.go index 4076d859..1db6ca2a 100644 --- a/operator/api/v1alpha1/skyhook_types.go +++ b/operator/api/v1alpha1/skyhook_types.go @@ -52,8 +52,8 @@ type SkyhookSpec struct { //+kubebuilder:default=false Pause bool `json:"pause,omitempty"` - // PodNonInteruptLabels are a set of labels we want to monitor pods for whether they Interruptible - PodNonInteruptLabels metav1.LabelSelector `json:"podNonInterruptLabels,omitempty"` + // PodNonInterruptLabels are a set of labels we want to monitor pods for whether they Interruptible + PodNonInterruptLabels metav1.LabelSelector `json:"podNonInterruptLabels,omitempty"` // NodeSelector are a set of labels we want to monitor nodes for applying packages too NodeSelector metav1.LabelSelector `json:"nodeSelectors,omitempty"` @@ -466,6 +466,7 @@ func (left *NodeState) Equal(right *NodeState) bool { return reflect.DeepEqual(left, right) } +// GetComplete returns a list of packages that are complete func (ns *NodeState) GetComplete(packages Packages, interrupt map[string][]*Interrupt, config map[string][]string) []string { ret := make([]string, 0) @@ -488,6 +489,7 @@ func (ns *NodeState) GetComplete(packages Packages, interrupt map[string][]*Inte return ret } +// IsPackageComplete checks if a package is complete func (ns *NodeState) IsPackageComplete(_package Package, interrupt map[string][]*Interrupt, config map[string][]string) bool { packageStatus, found := (*ns)[_package.GetUniqueName()] @@ -504,6 +506,7 @@ func (ns *NodeState) IsPackageComplete(_package Package, interrupt map[string][] return false } +// ProgressSkipped checks if a package is skipped and should be progressed to complete func (ns *NodeState) ProgressSkipped(packages Packages, interrupt map[string][]*Interrupt, config map[string][]string) bool { ret := false for _, s := range *ns { @@ -574,8 +577,8 @@ type State string const ( METADATA_PREFIX string = "skyhook.nvidia.com" StateComplete State = "complete" - StateInProgress State = "in_progress" - StateSkipped State = "skipped" + StateInProgress State = "in_progress" // this means its actually running, pod started + StateSkipped State = "skipped" // this means this package, stage are skipped mostly for some parts of the lifecycle StateErroring State = "erroring" StateUnknown State = "unknown" ) diff --git a/operator/api/v1alpha1/zz_generated.deepcopy.go b/operator/api/v1alpha1/zz_generated.deepcopy.go index 6c29ceae..b912688f 100644 --- a/operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/operator/api/v1alpha1/zz_generated.deepcopy.go @@ -1,25 +1,5 @@ //go:build !ignore_autogenerated -/* - * LICENSE START - * - * Copyright (c) NVIDIA CORPORATION. All rights reserved. - * - * 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. - * - * LICENSE END - */ - // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 @@ -278,7 +258,7 @@ func (in *SkyhookList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SkyhookSpec) DeepCopyInto(out *SkyhookSpec) { *out = *in - in.PodNonInteruptLabels.DeepCopyInto(&out.PodNonInteruptLabels) + in.PodNonInterruptLabels.DeepCopyInto(&out.PodNonInterruptLabels) in.NodeSelector.DeepCopyInto(&out.NodeSelector) in.InterruptionBudget.DeepCopyInto(&out.InterruptionBudget) if in.Packages != nil { diff --git a/operator/config/crd/bases/skyhook.nvidia.com_skyhooks.yaml b/operator/config/crd/bases/skyhook.nvidia.com_skyhooks.yaml index 959fd951..2224a108 100644 --- a/operator/config/crd/bases/skyhook.nvidia.com_skyhooks.yaml +++ b/operator/config/crd/bases/skyhook.nvidia.com_skyhooks.yaml @@ -437,8 +437,8 @@ spec: concert without needing to delete to ad in discovery of the issue. type: boolean podNonInterruptLabels: - description: PodNonInteruptLabels are a set of labels we want to monitor - pods for whether they Interruptible + description: PodNonInterruptLabels are a set of labels we want to + monitor pods for whether they Interruptible properties: matchExpressions: description: matchExpressions is a list of label selector requirements. diff --git a/operator/internal/controller/skyhook_controller.go b/operator/internal/controller/skyhook_controller.go index b68542ce..221fdd9b 100644 --- a/operator/internal/controller/skyhook_controller.go +++ b/operator/internal/controller/skyhook_controller.go @@ -23,6 +23,8 @@ package controller import ( "cmp" "context" + "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -1127,7 +1129,7 @@ func (r *SkyhookReconciler) HandleFinalizer(ctx context.Context, skyhook *skyhoo // HasNonInterruptWork returns true if pods are running on the node that are either packages, or matches the SCR selector func (r *SkyhookReconciler) HasNonInterruptWork(ctx context.Context, skyhookNode wrapper.SkyhookNode) (bool, error) { - selector, err := metav1.LabelSelectorAsSelector(&skyhookNode.GetSkyhook().Spec.PodNonInteruptLabels) + selector, err := metav1.LabelSelectorAsSelector(&skyhookNode.GetSkyhook().Spec.PodNonInterruptLabels) if err != nil { return false, fmt.Errorf("error creating selector: %w", err) } @@ -1439,8 +1441,8 @@ func (r *SkyhookReconciler) CreateInterruptPodForPackage(_interrupt *v1alpha1.In return &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: strings.ToLower(fmt.Sprintf("%s-interrupt-%s-", skyhook.Name, _interrupt.Type)), //TODO - RFC 1123 subdomain must consist of lower case alphanumeric - Namespace: r.opts.Namespace, + Name: generatePodName(fmt.Sprintf("%s-interrupt-%s", skyhook.Name, _interrupt.Type), nodeName), + Namespace: r.opts.Namespace, Labels: map[string]string{ fmt.Sprintf("%s/name", v1alpha1.METADATA_PREFIX): skyhook.Name, fmt.Sprintf("%s/package", v1alpha1.METADATA_PREFIX): fmt.Sprintf("%s-%s", _package.Name, _package.Version), @@ -1537,6 +1539,23 @@ func getAgentImage(opts SkyhookOperatorOptions, _package *v1alpha1.Package) stri return opts.AgentImage } +// generatePodName generates a pod name that is unique for a given node, skyhook, package, and stage +// namePrefix is the prefix of the pod name (should be unique) +func generatePodName(namePrefix, nodeName string) string { + // max name of pod is 63 characters + // so we need to truncate the name if it's too long + + unique := sha256.Sum256([]byte(namePrefix + nodeName)) + uniqueStr := hex.EncodeToString(unique[:])[:8] + + maxlen := 63 - len(uniqueStr) - 1 + if len(namePrefix) > maxlen { + namePrefix = namePrefix[:maxlen] + } + + return strings.ToLower(fmt.Sprintf("%s-%s", namePrefix, uniqueStr)) +} + // CreatePodFromPackage creates a pod spec for a skyhook pod for a given package func (r *SkyhookReconciler) CreatePodFromPackage(_package *v1alpha1.Package, skyhook *wrapper.Skyhook, nodeName string, stage v1alpha1.Stage) *corev1.Pod { volumes := []corev1.Volume{ @@ -1607,8 +1626,8 @@ func (r *SkyhookReconciler) CreatePodFromPackage(_package *v1alpha1.Package, sky pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: strings.ToLower(fmt.Sprintf("%s-%s-%s-%s-", skyhook.Name, _package.Name, _package.Version, stage)), - Namespace: r.opts.Namespace, + Name: generatePodName(fmt.Sprintf("%s-%s-%s-%s", skyhook.Name, _package.Name, _package.Version, stage), nodeName), + Namespace: r.opts.Namespace, Labels: map[string]string{ fmt.Sprintf("%s/name", v1alpha1.METADATA_PREFIX): skyhook.Name, fmt.Sprintf("%s/package", v1alpha1.METADATA_PREFIX): fmt.Sprintf("%s-%s", _package.Name, _package.Version), diff --git a/operator/internal/controller/skyhook_controller_test.go b/operator/internal/controller/skyhook_controller_test.go index 1a3212fd..002bd4c1 100644 --- a/operator/internal/controller/skyhook_controller_test.go +++ b/operator/internal/controller/skyhook_controller_test.go @@ -31,6 +31,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -844,4 +845,143 @@ var _ = Describe("skyhook controller tests", func() { } Expect(found_toleration).To(BeTrue()) }) + + It("should generate deterministic pod names", func() { + // Setup basic test data + skyhook := &wrapper.Skyhook{ + Skyhook: &v1alpha1.Skyhook{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-skyhook", + }, + }, + } + + package1 := &v1alpha1.Package{ + PackageRef: v1alpha1.PackageRef{ + Name: "test-package", + Version: "1.2.3", + }, + } + + package2 := &v1alpha1.Package{ + PackageRef: v1alpha1.PackageRef{ + Name: "test-package", + Version: "1.2.4", + }, + } + + nodeName := "test-node" + nodeName2 := "test-node-2" + + // Create a function to generate the namePrefix in the same way the controller does + createNamePrefix := func(skyhookName, pkgName, pkgVersion, stage string) string { + return fmt.Sprintf("%s-%s-%s-%s", skyhookName, pkgName, pkgVersion, stage) + } + + // Test 1: Deterministic behavior (same inputs = same output) + prefix1 := createNamePrefix(skyhook.Name, package1.Name, package1.Version, string(v1alpha1.StageApply)) + name1 := generatePodName(prefix1, nodeName) + name2 := generatePodName(prefix1, nodeName) + Expect(name1).To(Equal(name2), "Generated pod names should be deterministic") + + // Test 2: Uniqueness with different inputs + // Different stage + prefixApply := createNamePrefix(skyhook.Name, package1.Name, package1.Version, string(v1alpha1.StageApply)) + prefixConfig := createNamePrefix(skyhook.Name, package1.Name, package1.Version, string(v1alpha1.StageConfig)) + nameApply := generatePodName(prefixApply, nodeName) + nameConfig := generatePodName(prefixConfig, nodeName) + Expect(nameApply).NotTo(Equal(nameConfig), "Different stages should produce different pod names") + + // Different package version + prefix2 := createNamePrefix(skyhook.Name, package2.Name, package2.Version, string(v1alpha1.StageApply)) + nameVersion1 := generatePodName(prefix1, nodeName) + nameVersion2 := generatePodName(prefix2, nodeName) + Expect(nameVersion1).NotTo(Equal(nameVersion2), "Different package versions should produce different pod names") + + // Different node + nameNode1 := generatePodName(prefix1, nodeName) + nameNode2 := generatePodName(prefix1, nodeName2) + Expect(nameNode1).NotTo(Equal(nameNode2), "Different nodes should produce different pod names") + + // Test for uninstall pods with timestamp + uninstallPrefix1 := fmt.Sprintf("%s-uninstall-123456789", prefixApply) + uninstallPrefix2 := fmt.Sprintf("%s-uninstall-987654321", prefixApply) + uninstallName1 := generatePodName(uninstallPrefix1, nodeName) + uninstallName2 := generatePodName(uninstallPrefix2, nodeName) + Expect(uninstallName1).NotTo(Equal(uninstallName2), "Uninstall pods with different timestamps should have different names") + Expect(uninstallName1).NotTo(Equal(nameApply), "Uninstall pod name should be different from regular pod name") + + // Test 3: Length constraints + longSkyhookName := "this-is-a-very-long-skyhook-name-that-exceeds-kubernetes-naming-limits-by-a-significant-margin" + longPackageName := "this-is-a-very-long-package-name-that-also-exceeds-kubernetes-naming-limits" + longPackageVersion := "1.2.3.4.5.6.7.8.9.10" + longPrefix := createNamePrefix(longSkyhookName, longPackageName, longPackageVersion, string(v1alpha1.StageApply)) + longName := generatePodName(longPrefix, "node1") + Expect(len(longName)).To(BeNumerically("<=", 63), "Pod name should not exceed Kubernetes 63 character limit") + Expect(longName).To(MatchRegexp(`-[0-9a-f]+$`), "Pod name should end with a hash component") + }) + + It("should correctly identify if a pod matches a package", func() { + + // Create a test package + testPackage := &v1alpha1.Package{ + PackageRef: v1alpha1.PackageRef{ + Name: "test-package", + Version: "1.2.3", + }, + Image: "test-image:1.2.3", + Resources: v1alpha1.ResourceRequirements{ + CPURequest: resource.MustParse("100m"), + CPULimit: resource.MustParse("200m"), + MemoryRequest: resource.MustParse("64Mi"), + MemoryLimit: resource.MustParse("128Mi"), + }, + } + + // Create a test skyhook + testSkyhook := &wrapper.Skyhook{ + Skyhook: &v1alpha1.Skyhook{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-skyhook", + }, + }, + } + + // Stage to test + testStage := v1alpha1.StageApply + + // Create actual pods that would be created by the operator functions + // First using CreatePodFromPackage + actualPod := operator.CreatePodFromPackage(testPackage, testSkyhook, "test-node", testStage) + + // Verify that the pod matches the package according to PodMatchesPackage + matches := operator.PodMatchesPackage(testPackage, *actualPod, testSkyhook, testStage) + Expect(matches).To(BeTrue(), "PodMatchesPackage should recognize the pod it created") + + // Now let's modify the package version and see if it correctly identifies non-matches + modifiedPackage := testPackage.DeepCopy() + modifiedPackage.Version = "1.2.4" + + matches = operator.PodMatchesPackage(modifiedPackage, *actualPod, testSkyhook, testStage) + Expect(matches).To(BeFalse(), "PodMatchesPackage should not match when package version changed") + + // Test with different stage + matches = operator.PodMatchesPackage(testPackage, *actualPod, testSkyhook, v1alpha1.StageConfig) + Expect(matches).To(BeFalse(), "PodMatchesPackage should not match when stage changed") + + // Test with interrupt pods + interruptPod := operator.CreateInterruptPodForPackage( + &v1alpha1.Interrupt{ + Type: v1alpha1.REBOOT, + }, + "argEncode", + testPackage, + testSkyhook, + "test-node", + ) + + // Verify that the interrupt pod matches the package + matches = operator.PodMatchesPackage(testPackage, *interruptPod, testSkyhook, testStage) + Expect(matches).To(BeTrue(), "PodMatchesPackage should recognize the interrupt pod it created") + }) })