github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/opts_test.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/docker/cli/opts"
    10  	"github.com/docker/docker/api/types"
    11  	"github.com/docker/docker/api/types/container"
    12  	"github.com/docker/docker/api/types/swarm"
    13  	"gotest.tools/v3/assert"
    14  	is "gotest.tools/v3/assert/cmp"
    15  )
    16  
    17  func TestCredentialSpecOpt(t *testing.T) {
    18  	tests := []struct {
    19  		name        string
    20  		in          string
    21  		value       swarm.CredentialSpec
    22  		expectedErr string
    23  	}{
    24  		{
    25  			name:  "empty",
    26  			in:    "",
    27  			value: swarm.CredentialSpec{},
    28  		},
    29  		{
    30  			name:        "no-prefix",
    31  			in:          "noprefix",
    32  			value:       swarm.CredentialSpec{},
    33  			expectedErr: `invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`,
    34  		},
    35  		{
    36  			name:  "config",
    37  			in:    "config://0bt9dmxjvjiqermk6xrop3ekq",
    38  			value: swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
    39  		},
    40  		{
    41  			name:  "file",
    42  			in:    "file://somefile.json",
    43  			value: swarm.CredentialSpec{File: "somefile.json"},
    44  		},
    45  		{
    46  			name:  "registry",
    47  			in:    "registry://testing",
    48  			value: swarm.CredentialSpec{Registry: "testing"},
    49  		},
    50  	}
    51  
    52  	for _, tc := range tests {
    53  		tc := tc
    54  		t.Run(tc.name, func(t *testing.T) {
    55  			var cs credentialSpecOpt
    56  
    57  			err := cs.Set(tc.in)
    58  
    59  			if tc.expectedErr != "" {
    60  				assert.Error(t, err, tc.expectedErr)
    61  			} else {
    62  				assert.NilError(t, err)
    63  			}
    64  
    65  			assert.Equal(t, cs.String(), tc.in)
    66  			assert.DeepEqual(t, cs.Value(), &tc.value)
    67  		})
    68  	}
    69  }
    70  
    71  func TestMemBytesString(t *testing.T) {
    72  	var mem opts.MemBytes = 1048576
    73  	assert.Check(t, is.Equal("1MiB", mem.String()))
    74  }
    75  
    76  func TestMemBytesSetAndValue(t *testing.T) {
    77  	var mem opts.MemBytes
    78  	assert.NilError(t, mem.Set("5kb"))
    79  	assert.Check(t, is.Equal(int64(5120), mem.Value()))
    80  }
    81  
    82  func TestNanoCPUsString(t *testing.T) {
    83  	var cpus opts.NanoCPUs = 6100000000
    84  	assert.Check(t, is.Equal("6.100", cpus.String()))
    85  }
    86  
    87  func TestNanoCPUsSetAndValue(t *testing.T) {
    88  	var cpus opts.NanoCPUs
    89  	assert.NilError(t, cpus.Set("0.35"))
    90  	assert.Check(t, is.Equal(int64(350000000), cpus.Value()))
    91  }
    92  
    93  func TestUint64OptString(t *testing.T) {
    94  	value := uint64(2345678)
    95  	opt := Uint64Opt{value: &value}
    96  	assert.Check(t, is.Equal("2345678", opt.String()))
    97  
    98  	opt = Uint64Opt{}
    99  	assert.Check(t, is.Equal("", opt.String()))
   100  }
   101  
   102  func TestUint64OptSetAndValue(t *testing.T) {
   103  	var opt Uint64Opt
   104  	assert.NilError(t, opt.Set("14445"))
   105  	assert.Check(t, is.Equal(uint64(14445), *opt.Value()))
   106  }
   107  
   108  func TestHealthCheckOptionsToHealthConfig(t *testing.T) {
   109  	dur := time.Second
   110  	opt := healthCheckOptions{
   111  		cmd:           "curl",
   112  		interval:      opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
   113  		timeout:       opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
   114  		startPeriod:   opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
   115  		startInterval: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
   116  		retries:       10,
   117  	}
   118  	config, err := opt.toHealthConfig()
   119  	assert.NilError(t, err)
   120  	assert.Check(t, is.DeepEqual(&container.HealthConfig{
   121  		Test:          []string{"CMD-SHELL", "curl"},
   122  		Interval:      time.Second,
   123  		Timeout:       time.Second,
   124  		StartPeriod:   time.Second,
   125  		StartInterval: time.Second,
   126  		Retries:       10,
   127  	}, config))
   128  }
   129  
   130  func TestHealthCheckOptionsToHealthConfigNoHealthcheck(t *testing.T) {
   131  	opt := healthCheckOptions{
   132  		noHealthcheck: true,
   133  	}
   134  	config, err := opt.toHealthConfig()
   135  	assert.NilError(t, err)
   136  	assert.Check(t, is.DeepEqual(&container.HealthConfig{
   137  		Test: []string{"NONE"},
   138  	}, config))
   139  }
   140  
   141  func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) {
   142  	opt := healthCheckOptions{
   143  		cmd:           "curl",
   144  		noHealthcheck: true,
   145  	}
   146  	_, err := opt.toHealthConfig()
   147  	assert.Error(t, err, "--no-healthcheck conflicts with --health-* options")
   148  }
   149  
   150  func TestResourceOptionsToResourceRequirements(t *testing.T) {
   151  	incorrectOptions := []resourceOptions{
   152  		{
   153  			resGenericResources: []string{"foo=bar", "foo=1"},
   154  		},
   155  		{
   156  			resGenericResources: []string{"foo=bar", "foo=baz"},
   157  		},
   158  		{
   159  			resGenericResources: []string{"foo=bar"},
   160  		},
   161  		{
   162  			resGenericResources: []string{"foo=1", "foo=2"},
   163  		},
   164  	}
   165  
   166  	for _, opt := range incorrectOptions {
   167  		_, err := opt.ToResourceRequirements()
   168  		assert.Check(t, is.ErrorContains(err, ""))
   169  	}
   170  
   171  	correctOptions := []resourceOptions{
   172  		{
   173  			resGenericResources: []string{"foo=1"},
   174  		},
   175  		{
   176  			resGenericResources: []string{"foo=1", "bar=2"},
   177  		},
   178  	}
   179  
   180  	for _, opt := range correctOptions {
   181  		r, err := opt.ToResourceRequirements()
   182  		assert.NilError(t, err)
   183  		assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources)))
   184  	}
   185  }
   186  
   187  func TestToServiceNetwork(t *testing.T) {
   188  	nws := []types.NetworkResource{
   189  		{Name: "aaa-network", ID: "id555"},
   190  		{Name: "mmm-network", ID: "id999"},
   191  		{Name: "zzz-network", ID: "id111"},
   192  	}
   193  
   194  	client := &fakeClient{
   195  		networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
   196  			for _, network := range nws {
   197  				if network.ID == networkID || network.Name == networkID {
   198  					return network, nil
   199  				}
   200  			}
   201  			return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
   202  		},
   203  	}
   204  
   205  	nwo := opts.NetworkOpt{}
   206  	assert.NilError(t, nwo.Set("zzz-network"))
   207  	assert.NilError(t, nwo.Set("mmm-network"))
   208  	assert.NilError(t, nwo.Set("aaa-network"))
   209  
   210  	o := newServiceOptions()
   211  	o.mode = "replicated"
   212  	o.networks = nwo
   213  
   214  	ctx := context.Background()
   215  	flags := newCreateCommand(nil).Flags()
   216  	service, err := o.ToService(ctx, client, flags)
   217  	assert.NilError(t, err)
   218  	assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks))
   219  }
   220  
   221  func TestToServicePidsLimit(t *testing.T) {
   222  	flags := newCreateCommand(nil).Flags()
   223  	opt := newServiceOptions()
   224  	opt.mode = "replicated"
   225  	opt.resources.limitPids = 100
   226  	service, err := opt.ToService(context.Background(), &fakeClient{}, flags)
   227  	assert.NilError(t, err)
   228  	assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100))
   229  }
   230  
   231  func TestToServiceUpdateRollback(t *testing.T) {
   232  	expected := swarm.ServiceSpec{
   233  		UpdateConfig: &swarm.UpdateConfig{
   234  			Parallelism:     23,
   235  			Delay:           34 * time.Second,
   236  			Monitor:         54321 * time.Nanosecond,
   237  			FailureAction:   "pause",
   238  			MaxFailureRatio: 0.6,
   239  			Order:           "stop-first",
   240  		},
   241  		RollbackConfig: &swarm.UpdateConfig{
   242  			Parallelism:     12,
   243  			Delay:           23 * time.Second,
   244  			Monitor:         12345 * time.Nanosecond,
   245  			FailureAction:   "continue",
   246  			MaxFailureRatio: 0.5,
   247  			Order:           "start-first",
   248  		},
   249  	}
   250  
   251  	// Note: in test-situation, the flags are only used to detect if an option
   252  	// was set; the actual value itself is read from the serviceOptions below.
   253  	flags := newCreateCommand(nil).Flags()
   254  	flags.Set("update-parallelism", "23")
   255  	flags.Set("update-delay", "34s")
   256  	flags.Set("update-monitor", "54321ns")
   257  	flags.Set("update-failure-action", "pause")
   258  	flags.Set("update-max-failure-ratio", "0.6")
   259  	flags.Set("update-order", "stop-first")
   260  
   261  	flags.Set("rollback-parallelism", "12")
   262  	flags.Set("rollback-delay", "23s")
   263  	flags.Set("rollback-monitor", "12345ns")
   264  	flags.Set("rollback-failure-action", "continue")
   265  	flags.Set("rollback-max-failure-ratio", "0.5")
   266  	flags.Set("rollback-order", "start-first")
   267  
   268  	o := newServiceOptions()
   269  	o.mode = "replicated"
   270  	o.update = updateOptions{
   271  		parallelism:     23,
   272  		delay:           34 * time.Second,
   273  		monitor:         54321 * time.Nanosecond,
   274  		onFailure:       "pause",
   275  		maxFailureRatio: 0.6,
   276  		order:           "stop-first",
   277  	}
   278  	o.rollback = updateOptions{
   279  		parallelism:     12,
   280  		delay:           23 * time.Second,
   281  		monitor:         12345 * time.Nanosecond,
   282  		onFailure:       "continue",
   283  		maxFailureRatio: 0.5,
   284  		order:           "start-first",
   285  	}
   286  
   287  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   288  	assert.NilError(t, err)
   289  	assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig))
   290  	assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig))
   291  }
   292  
   293  func TestToServiceUpdateRollbackOrder(t *testing.T) {
   294  	flags := newCreateCommand(nil).Flags()
   295  	flags.Set("update-order", "start-first")
   296  	flags.Set("rollback-order", "start-first")
   297  
   298  	o := newServiceOptions()
   299  	o.mode = "replicated"
   300  	o.update = updateOptions{order: "start-first"}
   301  	o.rollback = updateOptions{order: "start-first"}
   302  
   303  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   304  	assert.NilError(t, err)
   305  	assert.Check(t, is.Equal(service.UpdateConfig.Order, o.update.order))
   306  	assert.Check(t, is.Equal(service.RollbackConfig.Order, o.rollback.order))
   307  }
   308  
   309  func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
   310  	opt := serviceOptions{
   311  		mode:        "global",
   312  		maxReplicas: 1,
   313  	}
   314  	_, err := opt.ToServiceMode()
   315  	assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode")
   316  }
   317  
   318  func TestToServiceSysCtls(t *testing.T) {
   319  	o := newServiceOptions()
   320  	o.mode = "replicated"
   321  	o.sysctls.Set("net.ipv4.ip_forward=1")
   322  	o.sysctls.Set("kernel.shmmax=123456")
   323  
   324  	expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"}
   325  	flags := newCreateCommand(nil).Flags()
   326  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   327  	assert.NilError(t, err)
   328  	assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
   329  }