github.com/ali-iotechsys/cli@v20.10.0+incompatible/cli/command/stack/kubernetes/watcher_test.go (about) 1 package kubernetes 2 3 import ( 4 "testing" 5 6 apiv1beta1 "github.com/docker/compose-on-kubernetes/api/compose/v1beta1" 7 composelabels "github.com/docker/compose-on-kubernetes/api/labels" 8 "gotest.tools/v3/assert" 9 apiv1 "k8s.io/api/core/v1" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/labels" 12 "k8s.io/apimachinery/pkg/runtime" 13 "k8s.io/apimachinery/pkg/runtime/schema" 14 "k8s.io/apimachinery/pkg/runtime/serializer" 15 "k8s.io/apimachinery/pkg/watch" 16 k8stesting "k8s.io/client-go/testing" 17 ) 18 19 var podsResource = apiv1.SchemeGroupVersion.WithResource("pods") 20 var podKind = apiv1.SchemeGroupVersion.WithKind("Pod") 21 var stacksResource = apiv1beta1.SchemeGroupVersion.WithResource("stacks") 22 var stackKind = apiv1beta1.SchemeGroupVersion.WithKind("Stack") 23 24 type testPodAndStackRepository struct { 25 fake *k8stesting.Fake 26 } 27 28 func (r *testPodAndStackRepository) stackListWatchForNamespace(ns string) *testStackListWatch { 29 return &testStackListWatch{fake: r.fake, ns: ns} 30 } 31 func (r *testPodAndStackRepository) podListWatchForNamespace(ns string) *testPodListWatch { 32 return &testPodListWatch{fake: r.fake, ns: ns} 33 } 34 35 func newTestPodAndStackRepository(initialPods []apiv1.Pod, initialStacks []apiv1beta1.Stack, podWatchHandler, stackWatchHandler k8stesting.WatchReactionFunc) *testPodAndStackRepository { 36 var scheme = runtime.NewScheme() 37 var codecs = serializer.NewCodecFactory(scheme) 38 metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 39 apiv1.AddToScheme(scheme) 40 apiv1beta1.AddToScheme(scheme) 41 42 o := k8stesting.NewObjectTracker(scheme, codecs.UniversalDecoder()) 43 for _, obj := range initialPods { 44 obj := obj 45 if err := o.Add(&obj); err != nil { 46 panic(err) 47 } 48 } 49 for _, obj := range initialStacks { 50 obj := obj 51 if err := o.Add(&obj); err != nil { 52 panic(err) 53 } 54 } 55 fakePtr := &k8stesting.Fake{} 56 fakePtr.AddReactor("*", "*", k8stesting.ObjectReaction(o)) 57 if podWatchHandler != nil { 58 fakePtr.AddWatchReactor(podsResource.Resource, podWatchHandler) 59 } 60 if stackWatchHandler != nil { 61 fakePtr.AddWatchReactor(stacksResource.Resource, stackWatchHandler) 62 } 63 fakePtr.AddWatchReactor("*", k8stesting.DefaultWatchReactor(watch.NewFake(), nil)) 64 return &testPodAndStackRepository{fake: fakePtr} 65 } 66 67 type testStackListWatch struct { 68 fake *k8stesting.Fake 69 ns string 70 } 71 72 func (s *testStackListWatch) List(opts metav1.ListOptions) (*apiv1beta1.StackList, error) { 73 obj, err := s.fake.Invokes(k8stesting.NewListAction(stacksResource, stackKind, s.ns, opts), &apiv1beta1.StackList{}) 74 75 if obj == nil { 76 return nil, err 77 } 78 79 label, _, _ := k8stesting.ExtractFromListOptions(opts) 80 if label == nil { 81 label = labels.Everything() 82 } 83 list := &apiv1beta1.StackList{} 84 for _, item := range obj.(*apiv1beta1.StackList).Items { 85 if label.Matches(labels.Set(item.Labels)) { 86 list.Items = append(list.Items, item) 87 } 88 } 89 return list, err 90 } 91 func (s *testStackListWatch) Watch(opts metav1.ListOptions) (watch.Interface, error) { 92 return s.fake.InvokesWatch(k8stesting.NewWatchAction(stacksResource, s.ns, opts)) 93 } 94 95 type testPodListWatch struct { 96 fake *k8stesting.Fake 97 ns string 98 } 99 100 func (p *testPodListWatch) List(opts metav1.ListOptions) (*apiv1.PodList, error) { 101 obj, err := p.fake.Invokes(k8stesting.NewListAction(podsResource, podKind, p.ns, opts), &apiv1.PodList{}) 102 103 if obj == nil { 104 return nil, err 105 } 106 107 label, _, _ := k8stesting.ExtractFromListOptions(opts) 108 if label == nil { 109 label = labels.Everything() 110 } 111 list := &apiv1.PodList{} 112 for _, item := range obj.(*apiv1.PodList).Items { 113 if label.Matches(labels.Set(item.Labels)) { 114 list.Items = append(list.Items, item) 115 } 116 } 117 return list, err 118 119 } 120 func (p *testPodListWatch) Watch(opts metav1.ListOptions) (watch.Interface, error) { 121 return p.fake.InvokesWatch(k8stesting.NewWatchAction(podsResource, p.ns, opts)) 122 } 123 124 func TestDeployWatchOk(t *testing.T) { 125 stack := apiv1beta1.Stack{ 126 ObjectMeta: metav1.ObjectMeta{Name: "test-stack", Namespace: "test-ns"}, 127 } 128 129 serviceNames := []string{"svc1", "svc2"} 130 testRepo := newTestPodAndStackRepository(nil, []apiv1beta1.Stack{stack}, func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) { 131 res := watch.NewFake() 132 go func() { 133 pod1 := &apiv1.Pod{ 134 ObjectMeta: metav1.ObjectMeta{ 135 Name: "test1", 136 Namespace: "test-ns", 137 Labels: composelabels.ForService("test-stack", "svc1"), 138 }, 139 Status: apiv1.PodStatus{ 140 Phase: apiv1.PodRunning, 141 Conditions: []apiv1.PodCondition{ 142 { 143 Type: apiv1.PodReady, 144 Status: apiv1.ConditionTrue, 145 }, 146 }, 147 }, 148 } 149 pod2 := &apiv1.Pod{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: "test2", 152 Namespace: "test-ns", 153 Labels: composelabels.ForService("test-stack", "svc2"), 154 }, 155 Status: apiv1.PodStatus{ 156 Phase: apiv1.PodRunning, 157 Conditions: []apiv1.PodCondition{ 158 { 159 Type: apiv1.PodReady, 160 Status: apiv1.ConditionTrue, 161 }, 162 }, 163 }, 164 } 165 res.Add(pod1) 166 res.Add(pod2) 167 }() 168 169 return true, res, nil 170 }, nil) 171 172 testee := &deployWatcher{ 173 stacks: testRepo.stackListWatchForNamespace("test-ns"), 174 pods: testRepo.podListWatchForNamespace("test-ns"), 175 } 176 177 statusUpdates := make(chan serviceStatus) 178 go func() { 179 for range statusUpdates { 180 } 181 }() 182 defer close(statusUpdates) 183 err := testee.Watch(stack.Name, serviceNames, statusUpdates) 184 assert.NilError(t, err) 185 } 186 187 func TestDeployReconcileFailure(t *testing.T) { 188 stack := apiv1beta1.Stack{ 189 ObjectMeta: metav1.ObjectMeta{Name: "test-stack", Namespace: "test-ns"}, 190 } 191 192 serviceNames := []string{"svc1", "svc2"} 193 testRepo := newTestPodAndStackRepository(nil, []apiv1beta1.Stack{stack}, nil, func(action k8stesting.Action) (handled bool, ret watch.Interface, err error) { 194 res := watch.NewFake() 195 go func() { 196 sfailed := stack 197 sfailed.Status = apiv1beta1.StackStatus{ 198 Phase: apiv1beta1.StackFailure, 199 Message: "test error", 200 } 201 res.Modify(&sfailed) 202 }() 203 204 return true, res, nil 205 }) 206 207 testee := &deployWatcher{ 208 stacks: testRepo.stackListWatchForNamespace("test-ns"), 209 pods: testRepo.podListWatchForNamespace("test-ns"), 210 } 211 212 statusUpdates := make(chan serviceStatus) 213 go func() { 214 for range statusUpdates { 215 } 216 }() 217 defer close(statusUpdates) 218 err := testee.Watch(stack.Name, serviceNames, statusUpdates) 219 assert.ErrorContains(t, err, "Failure: test error") 220 }