183 lines
6.3 KiB
Go
183 lines
6.3 KiB
Go
package kubernetes
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
)
|
|
|
|
// conditionTime is the cluster's own stamp for "this rollout completed",
|
|
// pinned so assertions can compare nanoseconds exactly.
|
|
var conditionTime = metav1.NewTime(time.Unix(1700000000, 0))
|
|
|
|
func testDeployment(overrides func(*appsv1.Deployment)) *appsv1.Deployment {
|
|
created := metav1.NewTime(time.Now().Add(-2 * time.Hour))
|
|
deployReplicas := int32(3)
|
|
deployment := &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "shop",
|
|
Name: "checkout",
|
|
Generation: 3,
|
|
CreationTimestamp: created,
|
|
Annotations: map[string]string{
|
|
"deployment.kubernetes.io/revision": "7",
|
|
},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{Replicas: &deployReplicas},
|
|
Status: appsv1.DeploymentStatus{
|
|
Replicas: 3,
|
|
UpdatedReplicas: 3,
|
|
AvailableReplicas: 3,
|
|
ReadyReplicas: 3,
|
|
Conditions: []appsv1.DeploymentCondition{
|
|
{
|
|
Type: appsv1.DeploymentProgressing,
|
|
Status: corev1.ConditionTrue,
|
|
Reason: "NewReplicaSetAvailable",
|
|
Message: "ReplicaSet \"checkout-abc\" has successfully progressed.",
|
|
LastUpdateTime: conditionTime,
|
|
LastTransitionTime: conditionTime,
|
|
},
|
|
{
|
|
Type: appsv1.DeploymentAvailable,
|
|
Status: corev1.ConditionTrue,
|
|
LastUpdateTime: conditionTime,
|
|
LastTransitionTime: conditionTime,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if overrides != nil {
|
|
overrides(deployment)
|
|
}
|
|
return deployment
|
|
}
|
|
|
|
func observeDeployment(w *Watcher, deployment *appsv1.Deployment) {
|
|
w.observe(deployment)
|
|
}
|
|
|
|
func TestObserveCompletedRolloutKeepsTimesAcrossResync(t *testing.T) {
|
|
w := &Watcher{rollouts: make(map[string]Rollout), revisions: make(map[string]int64)}
|
|
|
|
observeDeployment(w, testDeployment(nil))
|
|
first, ok := w.rollouts["shop/checkout/7"]
|
|
if !ok {
|
|
t.Fatal("rollout not recorded")
|
|
}
|
|
if first.Phase != "completed" {
|
|
t.Fatalf("phase = %q, want completed", first.Phase)
|
|
}
|
|
if first.CompletedAt == 0 {
|
|
t.Fatal("completed rollout missing CompletedAt")
|
|
}
|
|
// The Progressing condition's update time is the cluster's completion stamp,
|
|
// not "now".
|
|
if first.CompletedAt != conditionTime.UnixNano() {
|
|
t.Fatalf("CompletedAt = %d, want the condition time %d", first.CompletedAt, conditionTime.UnixNano())
|
|
}
|
|
|
|
// A resync ~30s later re-delivers the same deployment. A quiet rollout must
|
|
// keep its times instead of looking perpetually fresh.
|
|
time.Sleep(2 * time.Millisecond)
|
|
observeDeployment(w, testDeployment(nil))
|
|
second := w.rollouts["shop/checkout/7"]
|
|
if second.LastSeen != first.LastSeen {
|
|
t.Fatalf("LastSeen advanced from %d to %d on an unchanged resync", first.LastSeen, second.LastSeen)
|
|
}
|
|
if second.CompletedAt != first.CompletedAt {
|
|
t.Fatalf("CompletedAt re-bumped from %d to %d on an unchanged resync", first.CompletedAt, second.CompletedAt)
|
|
}
|
|
|
|
// Any observable change does advance LastSeen.
|
|
changed := testDeployment(func(d *appsv1.Deployment) {
|
|
d.Status.ReadyReplicas = 2
|
|
})
|
|
observeDeployment(w, changed)
|
|
third := w.rollouts["shop/checkout/7"]
|
|
if third.LastSeen == first.LastSeen {
|
|
t.Fatal("LastSeen did not advance after an observable change")
|
|
}
|
|
}
|
|
|
|
func TestObserveStartedAtPrefersReplicaSetCreation(t *testing.T) {
|
|
w := &Watcher{rollouts: make(map[string]Rollout), revisions: make(map[string]int64)}
|
|
rolloutStart := time.Now().Add(-48 * time.Hour)
|
|
w.observeReplicaSet(&appsv1.ReplicaSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "shop",
|
|
Name: "checkout-abc",
|
|
CreationTimestamp: metav1.NewTime(rolloutStart),
|
|
OwnerReferences: []metav1.OwnerReference{{Kind: "Deployment", Name: "checkout"}},
|
|
Annotations: map[string]string{"deployment.kubernetes.io/revision": "7"},
|
|
},
|
|
})
|
|
|
|
observeDeployment(w, testDeployment(nil))
|
|
rollout := w.rollouts["shop/checkout/7"]
|
|
if rollout.StartedAt != rolloutStart.UnixNano() {
|
|
t.Fatalf("StartedAt = %d, want ReplicaSet creation %d", rollout.StartedAt, rolloutStart.UnixNano())
|
|
}
|
|
}
|
|
|
|
func TestObserveFirstSeenBeforeReplicaSetInformerRecovers(t *testing.T) {
|
|
w := &Watcher{rollouts: make(map[string]Rollout), revisions: make(map[string]int64)}
|
|
// Deployment events can arrive before the ReplicaSet cache has synced; the
|
|
// recorded start must be corrected once the ReplicaSet timestamp appears.
|
|
observeDeployment(w, testDeployment(nil))
|
|
stale := w.rollouts["shop/checkout/7"]
|
|
|
|
rolloutStart := time.Now().Add(-24 * time.Hour)
|
|
w.observeReplicaSet(&appsv1.ReplicaSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "shop",
|
|
Name: "checkout-abc",
|
|
CreationTimestamp: metav1.NewTime(rolloutStart),
|
|
OwnerReferences: []metav1.OwnerReference{{Kind: "Deployment", Name: "checkout"}},
|
|
Annotations: map[string]string{"deployment.kubernetes.io/revision": "7"},
|
|
},
|
|
})
|
|
observeDeployment(w, testDeployment(nil))
|
|
rollout := w.rollouts["shop/checkout/7"]
|
|
if rollout.StartedAt != rolloutStart.UnixNano() {
|
|
t.Fatalf("StartedAt = %d, want corrected ReplicaSet creation %d", rollout.StartedAt, rolloutStart.UnixNano())
|
|
}
|
|
if stale.StartedAt == 0 {
|
|
t.Fatal("first observation should still record a fallback start")
|
|
}
|
|
}
|
|
|
|
func TestListSortsNewestRolloutFirst(t *testing.T) {
|
|
w := &Watcher{rollouts: make(map[string]Rollout), revisions: make(map[string]int64)}
|
|
old := testDeployment(nil)
|
|
old.Annotations["deployment.kubernetes.io/revision"] = "6"
|
|
old.CreationTimestamp = metav1.NewTime(time.Now().Add(-72 * time.Hour))
|
|
recent := testDeployment(nil)
|
|
|
|
observeDeployment(w, old)
|
|
observeDeployment(w, recent)
|
|
|
|
list := w.List()
|
|
if len(list) != 2 {
|
|
t.Fatalf("len(List) = %d, want 2", len(list))
|
|
}
|
|
if list[0].Revision != "7" || list[1].Revision != "6" {
|
|
t.Fatalf("List order = [%s, %s], want newest rollout first", list[0].Revision, list[1].Revision)
|
|
}
|
|
}
|
|
|
|
// Deletions and tombstones must not panic the handlers.
|
|
func TestObserveIgnoresNonDeploymentObjects(t *testing.T) {
|
|
w := &Watcher{rollouts: make(map[string]Rollout), revisions: make(map[string]int64)}
|
|
w.observe(&unstructured.Unstructured{})
|
|
w.observeReplicaSet(runtime.Object(&unstructured.Unstructured{}))
|
|
if len(w.List()) != 0 {
|
|
t.Fatal("no rollouts should be recorded")
|
|
}
|
|
}
|