github.com/docker/compose-on-kubernetes@v0.5.0/internal/stackresources/diff/stackstatediff_test.go (about) 1 package diff 2 3 import ( 4 "testing" 5 6 "github.com/docker/compose-on-kubernetes/internal/stackresources" 7 "github.com/stretchr/testify/assert" 8 appstypes "k8s.io/api/apps/v1" 9 coretypes "k8s.io/api/core/v1" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 ) 12 13 type resourceOpt func(interface{}) 14 15 func testDeployment(spec string, labels map[string]string, opts ...resourceOpt) *appstypes.Deployment { 16 v := &appstypes.Deployment{ 17 ObjectMeta: metav1.ObjectMeta{ 18 Namespace: "ns", 19 Name: "test", 20 Labels: labels, 21 }, 22 Spec: appstypes.DeploymentSpec{ 23 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": spec}}, 24 }, 25 } 26 for _, o := range opts { 27 o(v) 28 } 29 return v 30 } 31 func testStatefulset(spec string, labels map[string]string, opts ...resourceOpt) *appstypes.StatefulSet { 32 v := &appstypes.StatefulSet{ 33 ObjectMeta: metav1.ObjectMeta{ 34 Namespace: "ns", 35 Name: "test", 36 Labels: labels, 37 }, 38 Spec: appstypes.StatefulSetSpec{ 39 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": spec}}, 40 }, 41 } 42 for _, o := range opts { 43 o(v) 44 } 45 return v 46 } 47 func testDaemonset(spec string, labels map[string]string, opts ...resourceOpt) *appstypes.DaemonSet { 48 v := &appstypes.DaemonSet{ 49 ObjectMeta: metav1.ObjectMeta{ 50 Namespace: "ns", 51 Name: "test", 52 Labels: labels, 53 }, 54 Spec: appstypes.DaemonSetSpec{ 55 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"test": spec}}, 56 }, 57 } 58 for _, o := range opts { 59 o(v) 60 } 61 return v 62 } 63 func testService(spec string, labels map[string]string, opts ...resourceOpt) *coretypes.Service { 64 v := &coretypes.Service{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Namespace: "ns", 67 Name: "test", 68 Labels: labels, 69 }, 70 Spec: coretypes.ServiceSpec{ 71 Selector: map[string]string{"test": spec}, 72 }, 73 } 74 for _, o := range opts { 75 o(v) 76 } 77 return v 78 } 79 80 func testServiceType(t coretypes.ServiceType) resourceOpt { 81 return func(v interface{}) { 82 svc := v.(*coretypes.Service) 83 svc.Spec.Type = t 84 } 85 } 86 87 func testClusterIP(ip string) resourceOpt { 88 return func(v interface{}) { 89 svc := v.(*coretypes.Service) 90 svc.Spec.ClusterIP = ip 91 } 92 } 93 94 func testPodSpecImage(spec *coretypes.PodTemplateSpec, img string) { 95 spec.Spec.InitContainers = []coretypes.Container{ 96 { 97 Image: img, 98 }, 99 } 100 spec.Spec.Containers = []coretypes.Container{ 101 { 102 Image: img, 103 }, 104 } 105 } 106 func testPodSpecUCPTolerations(spec *coretypes.PodTemplateSpec) { 107 spec.Spec.Tolerations = []coretypes.Toleration{ 108 { 109 Key: "com.docker.ucp.orchestrator.kubernetes", 110 Operator: coretypes.TolerationOpExists, 111 }, 112 { 113 Key: "com.docker.ucp.manager", 114 Operator: coretypes.TolerationOpExists, 115 }, 116 } 117 } 118 func withImage(img string) resourceOpt { 119 return func(res interface{}) { 120 switch v := res.(type) { 121 case *appstypes.Deployment: 122 testPodSpecImage(&v.Spec.Template, img) 123 case *appstypes.StatefulSet: 124 testPodSpecImage(&v.Spec.Template, img) 125 case *appstypes.DaemonSet: 126 testPodSpecImage(&v.Spec.Template, img) 127 } 128 } 129 } 130 131 func withUcpTolerations() resourceOpt { 132 return func(res interface{}) { 133 switch v := res.(type) { 134 case *appstypes.Deployment: 135 testPodSpecUCPTolerations(&v.Spec.Template) 136 case *appstypes.StatefulSet: 137 testPodSpecUCPTolerations(&v.Spec.Template) 138 case *appstypes.DaemonSet: 139 testPodSpecUCPTolerations(&v.Spec.Template) 140 } 141 } 142 } 143 144 func newStackStateOrPanic(objects ...interface{}) *stackresources.StackState { 145 res, err := stackresources.NewStackState(objects...) 146 if err != nil { 147 panic(err) 148 } 149 return res 150 } 151 152 func TestStackDiff(t *testing.T) { 153 cases := []struct { 154 name string 155 current *stackresources.StackState 156 desired *stackresources.StackState 157 expected *StackStateDiff 158 }{ 159 { 160 name: "EmptyToEmpty", 161 current: newStackStateOrPanic(), 162 desired: newStackStateOrPanic(), 163 expected: &StackStateDiff{}, 164 }, 165 { 166 name: "EmptyToNonEmpty", 167 current: newStackStateOrPanic(), 168 desired: newStackStateOrPanic( 169 testDeployment("spec", nil), 170 testStatefulset("spec", nil), 171 testDaemonset("spec", nil), 172 testService("spec", nil), 173 ), 174 expected: &StackStateDiff{ 175 DeploymentsToAdd: []appstypes.Deployment{ 176 *testDeployment("spec", nil), 177 }, 178 StatefulsetsToAdd: []appstypes.StatefulSet{ 179 *testStatefulset("spec", nil), 180 }, 181 DaemonsetsToAdd: []appstypes.DaemonSet{ 182 *testDaemonset("spec", nil), 183 }, 184 ServicesToAdd: []coretypes.Service{ 185 *testService("spec", nil), 186 }, 187 }, 188 }, { 189 name: "NonEmptyToEmpty", 190 desired: newStackStateOrPanic(), 191 current: newStackStateOrPanic( 192 testDeployment("spec", nil), 193 testStatefulset("spec", nil), 194 testDaemonset("spec", nil), 195 testService("spec", nil), 196 ), 197 expected: &StackStateDiff{ 198 DeploymentsToDelete: []appstypes.Deployment{ 199 *testDeployment("spec", nil), 200 }, 201 StatefulsetsToDelete: []appstypes.StatefulSet{ 202 *testStatefulset("spec", nil), 203 }, 204 DaemonsetsToDelete: []appstypes.DaemonSet{ 205 *testDaemonset("spec", nil), 206 }, 207 ServicesToDelete: []coretypes.Service{ 208 *testService("spec", nil), 209 }, 210 }, 211 }, { 212 name: "UpdateSpec", 213 current: newStackStateOrPanic( 214 testDeployment("specold", nil), 215 testStatefulset("specold", nil), 216 testDaemonset("specold", nil), 217 testService("specold", nil), 218 ), 219 desired: newStackStateOrPanic( 220 testDeployment("spec", nil), 221 testStatefulset("spec", nil), 222 testDaemonset("spec", nil), 223 testService("spec", nil), 224 ), 225 expected: &StackStateDiff{ 226 DeploymentsToUpdate: []appstypes.Deployment{ 227 *testDeployment("spec", nil), 228 }, 229 StatefulsetsToUpdate: []appstypes.StatefulSet{ 230 *testStatefulset("spec", nil), 231 }, 232 DaemonsetsToUpdate: []appstypes.DaemonSet{ 233 *testDaemonset("spec", nil), 234 }, 235 ServicesToUpdate: []coretypes.Service{ 236 *testService("spec", nil), 237 }, 238 }, 239 }, 240 241 { 242 name: "Same", 243 current: newStackStateOrPanic( 244 testDeployment("spec", map[string]string{"key": "val"}), 245 testStatefulset("spec", map[string]string{"key": "val"}), 246 testDaemonset("spec", map[string]string{"key": "val"}), 247 testService("spec", map[string]string{"key": "val"}), 248 ), 249 desired: newStackStateOrPanic( 250 testDeployment("spec", map[string]string{"key": "val"}), 251 testStatefulset("spec", map[string]string{"key": "val"}), 252 testDaemonset("spec", map[string]string{"key": "val"}), 253 testService("spec", map[string]string{"key": "val"}), 254 ), 255 expected: &StackStateDiff{}, 256 }, 257 { 258 name: "DCT-Image-Patching", 259 current: newStackStateOrPanic( 260 testDeployment("spec", map[string]string{"key": "val"}, withImage("testimg:testtag@test-sha")), 261 testStatefulset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag@test-sha")), 262 testDaemonset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag@test-sha")), 263 testService("spec", map[string]string{"key": "val"}), 264 ), 265 desired: newStackStateOrPanic( 266 testDeployment("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 267 testStatefulset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 268 testDaemonset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 269 testService("spec", map[string]string{"key": "val"}), 270 ), 271 expected: &StackStateDiff{}, 272 }, 273 { 274 name: "image-tag-update", 275 current: newStackStateOrPanic( 276 testDeployment("spec", map[string]string{"key": "val"}, withImage("testimg:oldtag")), 277 testStatefulset("spec", map[string]string{"key": "val"}, withImage("testimg:oldtag")), 278 testDaemonset("spec", map[string]string{"key": "val"}, withImage("testimg:oldtag")), 279 testService("spec", map[string]string{"key": "val"}), 280 ), 281 desired: newStackStateOrPanic( 282 testDeployment("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 283 testStatefulset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 284 testDaemonset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 285 testService("spec", map[string]string{"key": "val"}), 286 ), 287 expected: &StackStateDiff{ 288 DeploymentsToUpdate: []appstypes.Deployment{ 289 *testDeployment("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 290 }, 291 StatefulsetsToUpdate: []appstypes.StatefulSet{ 292 *testStatefulset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 293 }, 294 DaemonsetsToUpdate: []appstypes.DaemonSet{ 295 *testDaemonset("spec", map[string]string{"key": "val"}, withImage("testimg:testtag")), 296 }, 297 }, 298 }, 299 { 300 name: "UCP-Tolerations", 301 current: newStackStateOrPanic( 302 testDeployment("spec", map[string]string{"key": "val"}, withUcpTolerations()), 303 testStatefulset("spec", map[string]string{"key": "val"}, withUcpTolerations()), 304 testDaemonset("spec", map[string]string{"key": "val"}, withUcpTolerations()), 305 testService("spec", map[string]string{"key": "val"}), 306 ), 307 desired: newStackStateOrPanic( 308 testDeployment("spec", map[string]string{"key": "val"}), 309 testStatefulset("spec", map[string]string{"key": "val"}), 310 testDaemonset("spec", map[string]string{"key": "val"}), 311 testService("spec", map[string]string{"key": "val"}), 312 ), 313 expected: &StackStateDiff{}, 314 }, 315 { 316 name: "labels-changes", 317 current: newStackStateOrPanic( 318 testDeployment("spec", map[string]string{"key": "val"}), 319 testStatefulset("spec", map[string]string{"key": "val"}), 320 testDaemonset("spec", map[string]string{"key": "val"}), 321 testService("spec", map[string]string{"key": "val"}), 322 ), 323 desired: newStackStateOrPanic( 324 testDeployment("spec", map[string]string{"key": "val2"}), 325 testStatefulset("spec", map[string]string{"key": "val2"}), 326 testDaemonset("spec", map[string]string{"key": "val2"}), 327 testService("spec", map[string]string{"key": "val2"}), 328 ), 329 expected: &StackStateDiff{ 330 DeploymentsToUpdate: []appstypes.Deployment{ 331 *testDeployment("spec", map[string]string{"key": "val2"}), 332 }, 333 StatefulsetsToUpdate: []appstypes.StatefulSet{ 334 *testStatefulset("spec", map[string]string{"key": "val2"}), 335 }, 336 DaemonsetsToUpdate: []appstypes.DaemonSet{ 337 *testDaemonset("spec", map[string]string{"key": "val2"}), 338 }, 339 ServicesToUpdate: []coretypes.Service{ 340 *testService("spec", map[string]string{"key": "val2"}), 341 }, 342 }, 343 }, 344 { 345 name: "service-headless-to-cluster-ip", 346 current: newStackStateOrPanic( 347 testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP(coretypes.ClusterIPNone)), 348 ), 349 desired: newStackStateOrPanic( 350 testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP("")), 351 ), 352 expected: &StackStateDiff{ 353 ServicesToDelete: []coretypes.Service{ 354 *testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP(coretypes.ClusterIPNone)), 355 }, 356 ServicesToAdd: []coretypes.Service{ 357 *testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP("")), 358 }, 359 }, 360 }, 361 { 362 name: "service-cluster-ip-to-headless", 363 current: newStackStateOrPanic( 364 testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP("1.2.3.4")), 365 ), 366 desired: newStackStateOrPanic( 367 testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP(coretypes.ClusterIPNone)), 368 ), 369 expected: &StackStateDiff{ 370 ServicesToDelete: []coretypes.Service{ 371 *testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP("1.2.3.4")), 372 }, 373 ServicesToAdd: []coretypes.Service{ 374 *testService("spec", nil, testServiceType(coretypes.ServiceTypeClusterIP), testClusterIP(coretypes.ClusterIPNone)), 375 }, 376 }, 377 }, 378 } 379 380 for _, c := range cases { 381 t.Run(c.name, func(t *testing.T) { 382 assert.Equal(t, c.expected, ComputeDiff(c.current, c.desired)) 383 }) 384 } 385 }