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  }