github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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  
   186  func TestToServiceNetwork(t *testing.T) {
   187  	nws := []types.NetworkResource{
   188  		{Name: "aaa-network", ID: "id555"},
   189  		{Name: "mmm-network", ID: "id999"},
   190  		{Name: "zzz-network", ID: "id111"},
   191  	}
   192  
   193  	client := &fakeClient{
   194  		networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
   195  			for _, network := range nws {
   196  				if network.ID == networkID || network.Name == networkID {
   197  					return network, nil
   198  				}
   199  			}
   200  			return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
   201  		},
   202  	}
   203  
   204  	nwo := opts.NetworkOpt{}
   205  	assert.NilError(t, nwo.Set("zzz-network"))
   206  	assert.NilError(t, nwo.Set("mmm-network"))
   207  	assert.NilError(t, nwo.Set("aaa-network"))
   208  
   209  	o := newServiceOptions()
   210  	o.mode = "replicated"
   211  	o.networks = nwo
   212  
   213  	ctx := context.Background()
   214  	flags := newCreateCommand(nil).Flags()
   215  	service, err := o.ToService(ctx, client, flags)
   216  	assert.NilError(t, err)
   217  	assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks))
   218  }
   219  
   220  func TestToServicePidsLimit(t *testing.T) {
   221  	flags := newCreateCommand(nil).Flags()
   222  	opt := newServiceOptions()
   223  	opt.mode = "replicated"
   224  	opt.resources.limitPids = 100
   225  	service, err := opt.ToService(context.Background(), &fakeClient{}, flags)
   226  	assert.NilError(t, err)
   227  	assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100))
   228  }
   229  
   230  func TestToServiceUpdateRollback(t *testing.T) {
   231  	expected := swarm.ServiceSpec{
   232  		UpdateConfig: &swarm.UpdateConfig{
   233  			Parallelism:     23,
   234  			Delay:           34 * time.Second,
   235  			Monitor:         54321 * time.Nanosecond,
   236  			FailureAction:   "pause",
   237  			MaxFailureRatio: 0.6,
   238  			Order:           "stop-first",
   239  		},
   240  		RollbackConfig: &swarm.UpdateConfig{
   241  			Parallelism:     12,
   242  			Delay:           23 * time.Second,
   243  			Monitor:         12345 * time.Nanosecond,
   244  			FailureAction:   "continue",
   245  			MaxFailureRatio: 0.5,
   246  			Order:           "start-first",
   247  		},
   248  	}
   249  
   250  	// Note: in test-situation, the flags are only used to detect if an option
   251  	// was set; the actual value itself is read from the serviceOptions below.
   252  	flags := newCreateCommand(nil).Flags()
   253  	flags.Set("update-parallelism", "23")
   254  	flags.Set("update-delay", "34s")
   255  	flags.Set("update-monitor", "54321ns")
   256  	flags.Set("update-failure-action", "pause")
   257  	flags.Set("update-max-failure-ratio", "0.6")
   258  	flags.Set("update-order", "stop-first")
   259  
   260  	flags.Set("rollback-parallelism", "12")
   261  	flags.Set("rollback-delay", "23s")
   262  	flags.Set("rollback-monitor", "12345ns")
   263  	flags.Set("rollback-failure-action", "continue")
   264  	flags.Set("rollback-max-failure-ratio", "0.5")
   265  	flags.Set("rollback-order", "start-first")
   266  
   267  	o := newServiceOptions()
   268  	o.mode = "replicated"
   269  	o.update = updateOptions{
   270  		parallelism:     23,
   271  		delay:           34 * time.Second,
   272  		monitor:         54321 * time.Nanosecond,
   273  		onFailure:       "pause",
   274  		maxFailureRatio: 0.6,
   275  		order:           "stop-first",
   276  	}
   277  	o.rollback = updateOptions{
   278  		parallelism:     12,
   279  		delay:           23 * time.Second,
   280  		monitor:         12345 * time.Nanosecond,
   281  		onFailure:       "continue",
   282  		maxFailureRatio: 0.5,
   283  		order:           "start-first",
   284  	}
   285  
   286  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   287  	assert.NilError(t, err)
   288  	assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig))
   289  	assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig))
   290  }
   291  
   292  func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
   293  	opt := serviceOptions{
   294  		mode:        "global",
   295  		maxReplicas: 1,
   296  	}
   297  	_, err := opt.ToServiceMode()
   298  	assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode")
   299  }
   300  
   301  func TestToServiceSysCtls(t *testing.T) {
   302  	o := newServiceOptions()
   303  	o.mode = "replicated"
   304  	o.sysctls.Set("net.ipv4.ip_forward=1")
   305  	o.sysctls.Set("kernel.shmmax=123456")
   306  
   307  	expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"}
   308  	flags := newCreateCommand(nil).Flags()
   309  	service, err := o.ToService(context.Background(), &fakeClient{}, flags)
   310  	assert.NilError(t, err)
   311  	assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
   312  }