github.com/docker/compose-on-kubernetes@v0.5.0/internal/convert/placement_test.go (about) 1 package convert 2 3 import ( 4 "reflect" 5 "sort" 6 "testing" 7 8 "github.com/docker/compose-on-kubernetes/api/compose/latest" 9 "github.com/docker/compose-on-kubernetes/internal/stackresources" 10 . "github.com/docker/compose-on-kubernetes/internal/test/builders" 11 "github.com/stretchr/testify/assert" 12 apiv1 "k8s.io/api/core/v1" 13 ) 14 15 func TestToPodWithPlacement(t *testing.T) { 16 s := Stack("demo", 17 WithService("redis", 18 Image("redis:alpine"), 19 Deploy(Placement( 20 Constraints( 21 OperatingSystem("linux", "=="), 22 Architecture("amd64", "=="), 23 ConstraintHostname("node01", "=="), 24 WithMatchLabel("label1", "value1", "=="), 25 WithMatchLabel("label2.subpath", "value2", "!="), 26 ), 27 )), 28 ), 29 ) 30 stack, err := StackToStack(*s, loadBalancerServiceStrategy{}, stackresources.EmptyStackState) 31 assert.NoError(t, err) 32 expectedRequirements := []apiv1.NodeSelectorRequirement{ 33 {Key: "beta.kubernetes.io/os", Operator: apiv1.NodeSelectorOpIn, Values: []string{"linux"}}, 34 {Key: "beta.kubernetes.io/arch", Operator: apiv1.NodeSelectorOpIn, Values: []string{"amd64"}}, 35 {Key: "kubernetes.io/hostname", Operator: apiv1.NodeSelectorOpIn, Values: []string{"node01"}}, 36 {Key: "label1", Operator: apiv1.NodeSelectorOpIn, Values: []string{"value1"}}, 37 {Key: "label2.subpath", Operator: apiv1.NodeSelectorOpNotIn, Values: []string{"value2"}}, 38 } 39 40 requirements := stack.Deployments["redis"].Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions 41 42 sort.Slice(expectedRequirements, func(i, j int) bool { return expectedRequirements[i].Key < expectedRequirements[j].Key }) 43 sort.Slice(requirements, func(i, j int) bool { return requirements[i].Key < requirements[j].Key }) 44 45 assert.EqualValues(t, expectedRequirements, requirements) 46 } 47 48 type keyValue struct { 49 key string 50 value string 51 } 52 53 func kv(key, value string) keyValue { 54 return keyValue{key: key, value: value} 55 } 56 57 func makeExpectedAffinity(kvs ...keyValue) *apiv1.Affinity { 58 59 var matchExpressions []apiv1.NodeSelectorRequirement 60 for _, kv := range kvs { 61 matchExpressions = append( 62 matchExpressions, 63 apiv1.NodeSelectorRequirement{ 64 Key: kv.key, 65 Operator: apiv1.NodeSelectorOpIn, 66 Values: []string{kv.value}, 67 }, 68 ) 69 } 70 return &apiv1.Affinity{ 71 NodeAffinity: &apiv1.NodeAffinity{ 72 RequiredDuringSchedulingIgnoredDuringExecution: &apiv1.NodeSelector{ 73 NodeSelectorTerms: []apiv1.NodeSelectorTerm{ 74 { 75 MatchExpressions: matchExpressions, 76 }, 77 }, 78 }, 79 }, 80 } 81 } 82 83 func TestNodeAfinity(t *testing.T) { 84 cases := []struct { 85 name string 86 source *latest.Constraints 87 expected *apiv1.Affinity 88 }{ 89 { 90 name: "nil", 91 expected: makeExpectedAffinity( 92 kv(kubernetesOs, "linux"), 93 kv(kubernetesArch, "amd64"), 94 ), 95 }, 96 { 97 name: "hostname", 98 source: &latest.Constraints{ 99 Hostname: &latest.Constraint{ 100 Operator: "==", 101 Value: "test", 102 }, 103 }, 104 expected: makeExpectedAffinity( 105 kv(kubernetesHostname, "test"), 106 kv(kubernetesOs, "linux"), 107 kv(kubernetesArch, "amd64"), 108 ), 109 }, 110 { 111 name: "os", 112 source: &latest.Constraints{ 113 OperatingSystem: &latest.Constraint{ 114 Operator: "==", 115 Value: "windows", 116 }, 117 }, 118 expected: makeExpectedAffinity( 119 kv(kubernetesOs, "windows"), 120 kv(kubernetesArch, "amd64"), 121 ), 122 }, 123 { 124 name: "arch", 125 source: &latest.Constraints{ 126 Architecture: &latest.Constraint{ 127 Operator: "==", 128 Value: "arm64", 129 }, 130 }, 131 expected: makeExpectedAffinity( 132 kv(kubernetesArch, "arm64"), 133 kv(kubernetesOs, "linux"), 134 ), 135 }, 136 { 137 name: "custom-labels", 138 source: &latest.Constraints{ 139 MatchLabels: map[string]latest.Constraint{ 140 kubernetesArch: { 141 Operator: "==", 142 Value: "arm64", 143 }, 144 kubernetesOs: { 145 Operator: "==", 146 Value: "windows", 147 }, 148 }, 149 }, 150 151 expected: makeExpectedAffinity( 152 kv(kubernetesArch, "arm64"), 153 kv(kubernetesOs, "windows"), 154 ), 155 }, 156 } 157 for _, c := range cases { 158 t.Run(c.name, func(t *testing.T) { 159 result, err := toNodeAffinity(c.source) 160 assert.NoError(t, err) 161 assert.True(t, nodeAffinityMatch(c.expected, result)) 162 }) 163 } 164 } 165 166 func nodeSelectorRequirementsToMap(source []apiv1.NodeSelectorRequirement, result map[string]apiv1.NodeSelectorRequirement) { 167 for _, t := range source { 168 result[t.Key] = t 169 } 170 } 171 172 func nodeAffinityMatch(expected, actual *apiv1.Affinity) bool { 173 expectedTerms := expected.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms 174 actualTerms := actual.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms 175 expectedExpressions := make(map[string]apiv1.NodeSelectorRequirement) 176 expectedFields := make(map[string]apiv1.NodeSelectorRequirement) 177 actualExpressions := make(map[string]apiv1.NodeSelectorRequirement) 178 actualFields := make(map[string]apiv1.NodeSelectorRequirement) 179 for _, v := range expectedTerms { 180 nodeSelectorRequirementsToMap(v.MatchExpressions, expectedExpressions) 181 nodeSelectorRequirementsToMap(v.MatchFields, expectedFields) 182 } 183 for _, v := range actualTerms { 184 nodeSelectorRequirementsToMap(v.MatchExpressions, actualExpressions) 185 nodeSelectorRequirementsToMap(v.MatchFields, actualFields) 186 } 187 return reflect.DeepEqual(expectedExpressions, actualExpressions) && reflect.DeepEqual(expectedFields, actualFields) 188 }