pkg/k8score/workload_rollout_test.go
Rama main · réplica de lectura del repositorio original.
package k8score
import (
"context"
"encoding/json"
"strings"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
dynamicfake "k8s.io/client-go/dynamic/fake"
clienttesting "k8s.io/client-go/testing"
"github.com/skyhook-io/radar/pkg/rollouts"
)
func newFakeRolloutClient(t *testing.T, objs ...runtime.Object) *dynamicfake.FakeDynamicClient {
t.Helper()
scheme := runtime.NewScheme()
listKinds := map[schema.GroupVersionResource]string{
rollouts.GVR: "RolloutList",
{Group: "apps", Version: "v1", Resource: "replicasets"}: "ReplicaSetList",
}
return dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listKinds, objs...)
}
func rolloutObject(namespace, name, image string, mutate func(map[string]any)) *unstructured.Unstructured {
ro := &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Rollout",
"metadata": map[string]any{
"namespace": namespace,
"name": name,
"uid": "ro-uid",
},
"spec": map[string]any{
"replicas": int64(3),
"strategy": map[string]any{
"canary": map[string]any{"steps": []any{map[string]any{"setWeight": int64(50)}}},
},
"template": map[string]any{
"metadata": map[string]any{"labels": map[string]any{"app": name}},
"spec": map[string]any{
"containers": []any{map[string]any{"name": "app", "image": image}},
},
},
},
"status": map[string]any{},
}}
if mutate != nil {
mutate(ro.Object)
}
return ro
}
func rolloutReplicaSet(namespace, name, revision, podHash, image string) *unstructured.Unstructured {
return &unstructured.Unstructured{Object: map[string]any{
"apiVersion": "apps/v1",
"kind": "ReplicaSet",
"metadata": map[string]any{
"namespace": namespace,
"name": name,
"annotations": map[string]any{rollouts.RevisionAnnotation: revision},
"labels": map[string]any{rollouts.PodTemplateHashLabel: podHash},
"ownerReferences": []any{map[string]any{
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Rollout",
"name": "web",
"uid": "ro-uid",
}},
},
"spec": map[string]any{
"replicas": int64(3),
"template": map[string]any{
"metadata": map[string]any{"labels": map[string]any{
"app": "web",
rollouts.PodTemplateHashLabel: podHash,
}},
"spec": map[string]any{
"containers": []any{map[string]any{"name": "app", "image": image}},
},
},
},
}}
}
func lastPatchBody(t *testing.T, client *dynamicfake.FakeDynamicClient) map[string]any {
t.Helper()
actions := client.Actions()
for i := len(actions) - 1; i >= 0; i-- {
pa, ok := actions[i].(clienttesting.PatchAction)
if !ok {
continue
}
var body map[string]any
if err := json.Unmarshal(pa.GetPatch(), &body); err != nil {
t.Fatalf("patch not JSON: %v", err)
}
return body
}
t.Fatalf("no patch recorded; actions=%v", actions)
return nil
}
func TestRestartWorkload_RolloutUsesRestartAt(t *testing.T) {
client := newFakeRolloutClient(t, rolloutObject("prod", "web", "web:v1", nil))
m := NewWorkloadManager(client, nil)
if err := m.RestartWorkload(context.Background(), "Rollout", "prod", "web"); err != nil {
t.Fatalf("RestartWorkload: %v", err)
}
spec, ok := lastPatchBody(t, client)["spec"].(map[string]any)
if !ok {
t.Fatal("patch has no spec")
}
if spec[rollouts.RestartAtField] == nil {
t.Errorf("spec.%s not set: %v", rollouts.RestartAtField, spec)
}
if _, touched := spec["template"]; touched {
t.Errorf("patched spec.template, which re-runs the whole canary: %v", spec)
}
}
// The Rollout branch must not need discovery — pkg/rollouts carries its own GVR,
// so these verbs work before the CRD is cached.
func TestRolloutOperationsWorkWithoutDiscovery(t *testing.T) {
client := newFakeRolloutClient(t,
rolloutObject("prod", "web", "web:v2", func(o map[string]any) {
o["status"] = map[string]any{"currentPodHash": "hash2", "stableRS": "hash1"}
}),
rolloutReplicaSet("prod", "web-1", "1", "hash1", "web:v1"),
rolloutReplicaSet("prod", "web-2", "2", "hash2", "web:v2"),
)
m := NewWorkloadManager(client, nil)
revisions, err := m.ListWorkloadRevisions(context.Background(), "rollouts", "prod", "web")
if err != nil {
t.Fatalf("ListWorkloadRevisions: %v", err)
}
if len(revisions) != 2 {
t.Fatalf("got %d revisions, want 2", len(revisions))
}
if revisions[0].Number != 2 {
t.Errorf("not newest-first: %d", revisions[0].Number)
}
if !revisions[0].IsCurrent || revisions[0].IsStable {
t.Errorf("rev 2 current=%v stable=%v, want current only", revisions[0].IsCurrent, revisions[0].IsStable)
}
if revisions[1].IsCurrent || !revisions[1].IsStable {
t.Errorf("rev 1 current=%v stable=%v, want stable only", revisions[1].IsCurrent, revisions[1].IsStable)
}
if err := m.RollbackWorkload(context.Background(), "Rollout", "prod", "web", 1); err != nil {
t.Fatalf("RollbackWorkload: %v", err)
}
patches := 0
for _, action := range client.Actions() {
if _, ok := action.(clienttesting.PatchAction); ok {
patches++
}
}
if patches == 0 {
t.Fatal("rollback issued no patch")
}
}
func TestRollbackWorkload_RolloutStripsPodTemplateHash(t *testing.T) {
client := newFakeRolloutClient(t,
rolloutObject("prod", "web", "web:v2", func(o map[string]any) {
o["status"] = map[string]any{"currentPodHash": "hash2"}
}),
rolloutReplicaSet("prod", "web-1", "1", "hash1", "web:v1"),
rolloutReplicaSet("prod", "web-2", "2", "hash2", "web:v2"),
)
m := NewWorkloadManager(client, nil)
if err := m.RollbackWorkload(context.Background(), "Rollout", "prod", "web", 1); err != nil {
t.Fatalf("RollbackWorkload: %v", err)
}
for _, action := range client.Actions() {
pa, ok := action.(clienttesting.PatchAction)
if !ok {
continue
}
if strings.Contains(string(pa.GetPatch()), rollouts.PodTemplateHashLabel) {
t.Fatalf("rollback patch leaks %s: %s", rollouts.PodTemplateHashLabel, pa.GetPatch())
}
}
}
func TestNormalizeWorkloadKind_Rollout(t *testing.T) {
for _, in := range []string{"Rollout", "rollout", "rollouts"} {
if got := NormalizeWorkloadKind(in); got != "rollouts" {
t.Errorf("NormalizeWorkloadKind(%q) = %q, want rollouts", in, got)
}
}
}