github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/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  		retries:     10,
   116  	}
   117  	config, err := opt.toHealthConfig()
   118  	assert.NilError(t, err)
   119  	assert.Check(t, is.DeepEqual(&container.HealthConfig{
   120  		Test:        []string{"CMD-SHELL", "curl"},
   121  		Interval:    time.Second,
   122  		Timeout:     time.Second,
   123  		StartPeriod: time.Second,
   124  		Retries:     10,
   125  	}, config))
   126  }
   127  
   128  func TestHealthCheckOptionsToHealthConfigNoHealthcheck(t *testing.T) {
   129  	opt := healthCheckOptions{
   130  		noHealthcheck: true,
   131  	}
   132  	config, err := opt.toHealthConfig()
   133  	assert.NilError(t, err)
   134  	assert.Check(t, is.DeepEqual(&container.HealthConfig{
   135  		Test: []string{"NONE"},
   136  	}, config))
   137  }
   138  
   139  func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) {
   140  	opt := healthCheckOptions{
   141  		cmd:           "curl",
   142  		noHealthcheck: true,
   143  	}
   144  	_, err := opt.toHealthConfig()
   145  	assert.Error(t, err, "--no-healthcheck conflicts with --health-* options")
   146  }
   147  
   148  func TestResourceOptionsToResourceRequirements(t *testing.T) {
   149  	incorrectOptions := []resourceOptions{
   150  		{
   151  			resGenericResources: []string{"foo=bar", "foo=1"},
   152  		},
   153  		{
   154  			resGenericResources: []string{"foo=bar", "foo=baz"},
   155  		},
   156  		{
   157  			resGenericResources: []string{"foo=bar"},
   158  		},
   159  		{
   160  			resGenericResources: []string{"foo=1", "foo=2"},
   161  		},
   162  	}
   163  
   164  	for _, opt := range incorrectOptions {
   165  		_, err := opt.ToResourceRequirements()
   166  		assert.Check(t, is.ErrorContains(err, ""))
   167  	}
   168  
   169  	correctOptions := []resourceOptions{
   170  		{
   171  			resGenericResources: []string{"foo=1"},
   172  		},
   173  		{
   174  			resGenericResources: []string{"foo=1", "bar=2"},
   175  		},
   176  	}
   177  
   178  	for _, opt := range correctOptions {
   179  		r, err := opt.ToResourceRequirements()
   180  		assert.NilError(t, err)
   181  		assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources)))
   182  	}
   183  }
   184  
   185  func TestToServiceNetwork(t *testing.T) {
   186  	nws := []types.NetworkResource{
   187  		{Name: "aaa-network", ID: "id555"},
   188  		{Name: "mmm-network", ID: "id999"},
   189  		{Name: "zzz-network", ID: "id111"},
   190  	}
   191  
   192  	client := &fakeClient{
   193  		networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
   194  			for _, network := range nws {
   195  				if network.ID == networkID || network.Name == networkID {
   196  					return network, nil
   197  				}
   198  			}
   199  			return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
   200  		},
   201  	}
   202  
   203  	nwo := opts.NetworkOpt{}
   204  	assert.NilError(t, nwo.Set("zzz-network"))
   205  	assert.NilError(t, nwo.Set("mmm-network"))
   206  	assert.NilError(t, nwo.Set("aaa-network"))
   207  
   208  	o := newServiceOptions()
   209  	o.mode = "replicated"
   210  	o.networks = nwo
   211  
   212  	ctx := context.Background()
   213  	flags := newCreateCommand(nil).Flags()
   214  	service, err := o.ToService(ctx, client, flags)
   215  	assert.NilError(t, err)
   216  	assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks))
   217  }
   218  
   219  func TestToServicePidsLimit(t *testing.T) {
   220  	flags := newCreateCommand(nil).Flags()
   221  	opt := newServiceOptions()
   222  	opt.mode = "replicated"
   223  	opt.resources.limitPids = 100
   224  	service, err := opt.ToService(context.Background(), &fakeClient{}, flags)
   225  	assert.NilError(t, err)
   226  	assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100))
   227  }
   228  
   229  func TestToServiceUpdateRollback(t *testing.T) {
   230  	expected := swarm.ServiceSpec{
   231  		UpdateConfig: &swarm.UpdateConfig{
   232  			Parallelism:     23,
   233  			Delay:           34 * time.Second,
   234  			Monitor:         54321 * time.Nanosecond,
   235  			FailureAction:   "pause",
   236  			MaxFailureRatio: 0.6,
   237  			Order:           "stop-first",
   238  		},
   239  		RollbackConfig: &swarm.UpdateConfig{
   240  			Parallelism:     12,
   241  			Delay:           23 * time.Second,
   242  			Monitor:         12345 * time.Nanosecond,
   243  			FailureAction:   "continue",
   244  			MaxFailureRatio: 0.5,
   245  			Order:           "start-first",
   246  		},
   247  	}
   248  
   249  	// Note: in test-situation, the flags are only used to detect if an option
   250  	// was set; the actual value itself is read from the serviceOptions below.
   251  	flags := newCreateCommand(nil).Flags()
   252  	flags.Set("update-parallelism", "23")
   253  	flags.Set("update-delay", "34s")
   254  	flags.Set("update-monitor", "54321ns")
   255  	flags.Set("update-failure-action", "pause")
   256  	flags.Set("update-max-failure-ratio", "0.6")
   257  	flags.Set("update-order", "stop-first")
   258  
   259  	flags.Set("rollback-parallelism", "12")
   260  	flags.Set("rollback-delay", "23s")
   261  	flags.Set("rollback-monitor", "12345ns")
   262  	flags.Set("rollback-failure-action", "continue")
   263  	flags.Set("rollback-max-failure-ratio", "0.5")
   264  	flags.Set("rollback-order", "start-first")
   265  
   266  	o := newServiceOptions()
   267  	o.mode = "replicated"
   268  	o.update = updateOptions{
   269  		parallelism:     23,
   270  		delay:           34 * time.Second,
   271  		monitor:         54321 * time.Nanosecond,
   272  		onFailure:       "pause",
   273  		maxFailureRatio: 0.6,
   274  		order:           "stop-first",
   275  	}
   276  	o.rollback = updateOptions{
   277  		parallelism:     12,
   278  		delay:           23 * time.Second,
   279  		monitor:         12345 * time.Nanosecond,
   280  		onFailure:       "continue",
   281  		maxFailureRatio: 0.5,
   282  		order:           "start-first",
   283  	}
   284  
   285  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   286  	assert.NilError(t, err)
   287  	assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig))
   288  	assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig))
   289  }
   290  
   291  func TestToServiceUpdateRollbackOrder(t *testing.T) {
   292  	flags := newCreateCommand(nil).Flags()
   293  	flags.Set("update-order", "start-first")
   294  	flags.Set("rollback-order", "start-first")
   295  
   296  	o := newServiceOptions()
   297  	o.mode = "replicated"
   298  	o.update = updateOptions{order: "start-first"}
   299  	o.rollback = updateOptions{order: "start-first"}
   300  
   301  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   302  	assert.NilError(t, err)
   303  	assert.Check(t, is.Equal(service.UpdateConfig.Order, o.update.order))
   304  	assert.Check(t, is.Equal(service.RollbackConfig.Order, o.rollback.order))
   305  }
   306  
   307  func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
   308  	opt := serviceOptions{
   309  		mode:        "global",
   310  		maxReplicas: 1,
   311  	}
   312  	_, err := opt.ToServiceMode()
   313  	assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode")
   314  }
   315  
   316  func TestToServiceSysCtls(t *testing.T) {
   317  	o := newServiceOptions()
   318  	o.mode = "replicated"
   319  	o.sysctls.Set("net.ipv4.ip_forward=1")
   320  	o.sysctls.Set("kernel.shmmax=123456")
   321  
   322  	expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"}
   323  	flags := newCreateCommand(nil).Flags()
   324  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   325  	assert.NilError(t, err)
   326  	assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
   327  }