github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/scheduler/constraint_test.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/docker/swarmkit/api"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  var (
    12  	task1 *api.Task
    13  	ni    *NodeInfo
    14  )
    15  
    16  func setupEnv() {
    17  	task1 = &api.Task{
    18  		ID:           "id1",
    19  		DesiredState: api.TaskStateRunning,
    20  		ServiceAnnotations: api.Annotations{
    21  			Name: "name1",
    22  		},
    23  
    24  		Spec: api.TaskSpec{
    25  			Runtime: &api.TaskSpec_Container{
    26  				Container: &api.ContainerSpec{
    27  					Command: []string{"sh", "-c", "sleep 5"},
    28  					Image:   "alpine",
    29  				},
    30  			},
    31  		},
    32  
    33  		Status: api.TaskStatus{
    34  			State: api.TaskStateAssigned,
    35  		},
    36  	}
    37  
    38  	ni = &NodeInfo{
    39  		Node: &api.Node{
    40  			ID: "nodeid-1",
    41  			Spec: api.NodeSpec{
    42  				Annotations: api.Annotations{
    43  					Labels: make(map[string]string),
    44  				},
    45  				DesiredRole: api.NodeRoleWorker,
    46  			},
    47  			Description: &api.NodeDescription{
    48  				Engine: &api.EngineDescription{
    49  					Labels: make(map[string]string),
    50  				},
    51  			},
    52  			Status: api.NodeStatus{
    53  				State: api.NodeStatus_READY,
    54  				Addr:  "186.17.9.41",
    55  			},
    56  		},
    57  		Tasks:                     make(map[string]*api.Task),
    58  		ActiveTasksCountByService: make(map[string]int),
    59  	}
    60  }
    61  
    62  func TestConstraintSetTask(t *testing.T) {
    63  	setupEnv()
    64  	f := ConstraintFilter{}
    65  	assert.False(t, f.SetTask(task1))
    66  
    67  	task1.Spec.Placement = &api.Placement{
    68  		Constraints: []string{"node.hostname == node-2", "node.labels.security != low"},
    69  	}
    70  	assert.True(t, f.SetTask(task1))
    71  
    72  	task1.Spec.Placement = &api.Placement{
    73  		Constraints: []string{"node.id == nodeid-2", "engine.labels.operatingsystem != ubuntu"},
    74  	}
    75  	assert.True(t, f.SetTask(task1))
    76  }
    77  
    78  func TestWrongSyntax(t *testing.T) {
    79  	setupEnv()
    80  	f := ConstraintFilter{}
    81  	task1.Spec.Placement = &api.Placement{
    82  		Constraints: []string{"node.abc.bcd == high"},
    83  	}
    84  	require.True(t, f.SetTask(task1))
    85  	assert.False(t, f.Check(ni))
    86  
    87  	task1.Spec.Placement = &api.Placement{
    88  		Constraints: []string{"node.abc.bcd != high"},
    89  	}
    90  	require.True(t, f.SetTask(task1))
    91  	assert.False(t, f.Check(ni))
    92  }
    93  
    94  func TestNodeHostname(t *testing.T) {
    95  	setupEnv()
    96  	f := ConstraintFilter{}
    97  	task1.Spec.Placement = &api.Placement{
    98  		Constraints: []string{"node.hostname != node-1"},
    99  	}
   100  	require.True(t, f.SetTask(task1))
   101  
   102  	// the node without hostname passes constraint
   103  	assert.True(t, f.Check(ni))
   104  
   105  	// add a not matching hostname
   106  	ni.Description.Hostname = "node-2"
   107  	assert.True(t, f.Check(ni))
   108  
   109  	// matching engine name
   110  	ni.Description.Hostname = "node-1"
   111  	assert.False(t, f.Check(ni))
   112  
   113  	// case insensitive
   114  	ni.Node.Description.Hostname = "NODe-1"
   115  	assert.False(t, f.Check(ni))
   116  }
   117  
   118  func TestNodeIP(t *testing.T) {
   119  	setupEnv()
   120  	f := ConstraintFilter{}
   121  
   122  	type testcase struct {
   123  		constraints    []string
   124  		requireVerdict bool
   125  		assertVerdict  bool
   126  	}
   127  
   128  	testFunc := func(tc testcase) {
   129  		task1.Spec.Placement = &api.Placement{
   130  			Constraints: tc.constraints,
   131  		}
   132  		require.Equal(t, f.SetTask(task1), tc.requireVerdict)
   133  		if tc.requireVerdict {
   134  			assert.Equal(t, f.Check(ni), tc.assertVerdict)
   135  		}
   136  	}
   137  
   138  	ipv4tests := []testcase{
   139  		{[]string{"node.ip == 186.17.9.41"}, true, true},
   140  		{[]string{"node.ip != 186.17.9.41"}, true, false},
   141  		{[]string{"node.ip == 186.17.9.42"}, true, false},
   142  		{[]string{"node.ip == 186.17.9.4/24"}, true, true},
   143  		{[]string{"node.ip == 186.17.8.41/24"}, true, false},
   144  		// invalid CIDR format
   145  		{[]string{"node.ip == 186.17.9.41/34"}, true, false},
   146  		// malformed IP
   147  		{[]string{"node.ip != 266.17.9.41"}, true, false},
   148  		// zero
   149  		{[]string{"node.ip != 0.0.0.0"}, true, true},
   150  		// invalid input, detected by SetTask
   151  		{[]string{"node.ip == "}, false, true},
   152  		// invalid input, not detected by SetTask
   153  		{[]string{"node.ip == not_ip_addr"}, true, false},
   154  	}
   155  
   156  	for _, tc := range ipv4tests {
   157  		testFunc(tc)
   158  	}
   159  
   160  	// IPv6 address
   161  	ni.Status.Addr = "2001:db8::2"
   162  	ipv6tests := []testcase{
   163  		{[]string{"node.ip == 2001:db8::2"}, true, true},
   164  		// same IPv6 address, different format
   165  		{[]string{"node.ip == 2001:db8:0::2"}, true, true},
   166  		{[]string{"node.ip != 2001:db8::2/128"}, true, false},
   167  		{[]string{"node.ip == 2001:db8::/64"}, true, true},
   168  		{[]string{"node.ip == 2001:db9::/64"}, true, false},
   169  		{[]string{"node.ip != 2001:db9::/64"}, true, true},
   170  	}
   171  
   172  	for _, tc := range ipv6tests {
   173  		testFunc(tc)
   174  	}
   175  
   176  	// node doesn't have address
   177  	ni.Status.Addr = ""
   178  	edgetests := []testcase{
   179  		{[]string{"node.ip == 0.0.0.0"}, true, false},
   180  		{[]string{"node.ip != 0.0.0.0"}, true, true},
   181  	}
   182  
   183  	for _, tc := range edgetests {
   184  		testFunc(tc)
   185  	}
   186  }
   187  
   188  func TestNodeID(t *testing.T) {
   189  	setupEnv()
   190  	f := ConstraintFilter{}
   191  	task1.Spec.Placement = &api.Placement{
   192  		Constraints: []string{"node.id == nodeid-1"},
   193  	}
   194  	require.True(t, f.SetTask(task1))
   195  	assert.True(t, f.Check(ni))
   196  
   197  	// full text match, cannot be longer
   198  	task1.Spec.Placement = &api.Placement{
   199  		Constraints: []string{"node.id == nodeid-1-extra"},
   200  	}
   201  	require.True(t, f.SetTask(task1))
   202  	assert.False(t, f.Check(ni))
   203  
   204  	// cannot be shorter
   205  	task1.Spec.Placement = &api.Placement{
   206  		Constraints: []string{"node.id == nodeid-"},
   207  	}
   208  	require.True(t, f.SetTask(task1))
   209  	assert.False(t, f.Check(ni))
   210  }
   211  
   212  func TestNodeRole(t *testing.T) {
   213  	setupEnv()
   214  	f := ConstraintFilter{}
   215  	task1.Spec.Placement = &api.Placement{
   216  		Constraints: []string{"node.role == worker"},
   217  	}
   218  	require.True(t, f.SetTask(task1))
   219  	assert.True(t, f.Check(ni))
   220  
   221  	task1.Spec.Placement = &api.Placement{
   222  		Constraints: []string{"node.role == manager"},
   223  	}
   224  	require.True(t, f.SetTask(task1))
   225  	assert.False(t, f.Check(ni))
   226  
   227  	// no such role as worker-manage
   228  	task1.Spec.Placement = &api.Placement{
   229  		Constraints: []string{"node.role == worker-manager"},
   230  	}
   231  	require.True(t, f.SetTask(task1))
   232  	assert.False(t, f.Check(ni))
   233  }
   234  
   235  func TestNodePlatform(t *testing.T) {
   236  	setupEnv()
   237  	f := ConstraintFilter{}
   238  	task1.Spec.Placement = &api.Placement{
   239  		Constraints: []string{"node.platform.os == linux"},
   240  	}
   241  	require.True(t, f.SetTask(task1))
   242  	//node info doesn't have platform yet
   243  	assert.False(t, f.Check(ni))
   244  
   245  	ni.Node.Description.Platform = &api.Platform{
   246  		Architecture: "x86_64",
   247  		OS:           "linux",
   248  	}
   249  	assert.True(t, f.Check(ni))
   250  
   251  	ni.Node.Description.Platform = &api.Platform{
   252  		Architecture: "x86_64",
   253  		OS:           "windows",
   254  	}
   255  	assert.False(t, f.Check(ni))
   256  
   257  	task1.Spec.Placement = &api.Placement{
   258  		Constraints: []string{"node.platform.arch == amd64"},
   259  	}
   260  	require.True(t, f.SetTask(task1))
   261  	assert.False(t, f.Check(ni))
   262  
   263  	task1.Spec.Placement = &api.Placement{
   264  		Constraints: []string{"node.platform.arch != amd64"},
   265  	}
   266  	require.True(t, f.SetTask(task1))
   267  	assert.True(t, f.Check(ni))
   268  }
   269  
   270  func TestNodeLabel(t *testing.T) {
   271  	setupEnv()
   272  	f := ConstraintFilter{}
   273  	task1.Spec.Placement = &api.Placement{
   274  		Constraints: []string{"node.labels.security == high"},
   275  	}
   276  	require.True(t, f.SetTask(task1))
   277  	assert.False(t, f.Check(ni))
   278  
   279  	// engine label is not node label
   280  	ni.Description.Engine.Labels["security"] = "high"
   281  	assert.False(t, f.Check(ni))
   282  
   283  	ni.Spec.Annotations.Labels["security"] = "high"
   284  	assert.True(t, f.Check(ni))
   285  }
   286  
   287  func TestEngineLabel(t *testing.T) {
   288  	setupEnv()
   289  	f := ConstraintFilter{}
   290  	task1.Spec.Placement = &api.Placement{
   291  		Constraints: []string{"engine.labels.disk != ssd"},
   292  	}
   293  	require.True(t, f.SetTask(task1))
   294  	// no such label matches !=
   295  	assert.True(t, f.Check(ni))
   296  
   297  	// node label is not engine label
   298  	ni.Spec.Annotations.Labels["disk"] = "ssd"
   299  	assert.True(t, f.Check(ni))
   300  
   301  	ni.Description.Engine.Labels["disk"] = "ssd"
   302  	assert.False(t, f.Check(ni))
   303  
   304  	// extra label doesn't interfere
   305  	ni.Description.Engine.Labels["memory"] = "large"
   306  	assert.False(t, f.Check(ni))
   307  }
   308  
   309  func TestMultipleConstraints(t *testing.T) {
   310  	setupEnv()
   311  	f := ConstraintFilter{}
   312  	task1.Spec.Placement = &api.Placement{
   313  		Constraints: []string{"node.hostname == node-1", "engine.labels.operatingsystem != Ubuntu 14.04"},
   314  	}
   315  	require.True(t, f.SetTask(task1))
   316  	assert.False(t, f.Check(ni))
   317  
   318  	ni.Description.Hostname = "node-1"
   319  	assert.True(t, f.Check(ni))
   320  
   321  	// set node operating system
   322  	ni.Node.Description.Engine.Labels["operatingsystem"] = "Ubuntu 14.04"
   323  	assert.False(t, f.Check(ni))
   324  
   325  	// case insensitive
   326  	ni.Node.Description.Engine.Labels["operatingsystem"] = "ubuntu 14.04"
   327  	assert.False(t, f.Check(ni))
   328  
   329  	ni.Node.Description.Engine.Labels["operatingsystem"] = "ubuntu 15.04"
   330  	assert.True(t, f.Check(ni))
   331  
   332  	// add one more label requirement to task
   333  	task1.Spec.Placement = &api.Placement{
   334  		Constraints: []string{"node.hostname == node-1",
   335  			"engine.labels.operatingsystem != Ubuntu 14.04",
   336  			"node.labels.security == high"},
   337  	}
   338  	require.True(t, f.SetTask(task1))
   339  	assert.False(t, f.Check(ni))
   340  
   341  	// add label to Spec.Annotations.Labels
   342  	ni.Spec.Annotations.Labels["security"] = "low"
   343  	assert.False(t, f.Check(ni))
   344  	ni.Spec.Annotations.Labels["security"] = "high"
   345  	assert.True(t, f.Check(ni))
   346  
   347  	// extra label doesn't interfere
   348  	ni.Description.Engine.Labels["memory"] = "large"
   349  	assert.True(t, f.Check(ni))
   350  }