github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/service/update_test.go (about)

     1  package service
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/container"
    11  	mounttypes "github.com/docker/docker/api/types/mount"
    12  	"github.com/docker/docker/api/types/swarm"
    13  	"github.com/docker/docker/pkg/testutil/assert"
    14  	"golang.org/x/net/context"
    15  )
    16  
    17  func TestUpdateServiceArgs(t *testing.T) {
    18  	flags := newUpdateCommand(nil).Flags()
    19  	flags.Set("args", "the \"new args\"")
    20  
    21  	spec := &swarm.ServiceSpec{}
    22  	cspec := &spec.TaskTemplate.ContainerSpec
    23  	cspec.Args = []string{"old", "args"}
    24  
    25  	updateService(flags, spec)
    26  	assert.EqualStringSlice(t, cspec.Args, []string{"the", "new args"})
    27  }
    28  
    29  func TestUpdateLabels(t *testing.T) {
    30  	flags := newUpdateCommand(nil).Flags()
    31  	flags.Set("label-add", "toadd=newlabel")
    32  	flags.Set("label-rm", "toremove")
    33  
    34  	labels := map[string]string{
    35  		"toremove": "thelabeltoremove",
    36  		"tokeep":   "value",
    37  	}
    38  
    39  	updateLabels(flags, &labels)
    40  	assert.Equal(t, len(labels), 2)
    41  	assert.Equal(t, labels["tokeep"], "value")
    42  	assert.Equal(t, labels["toadd"], "newlabel")
    43  }
    44  
    45  func TestUpdateLabelsRemoveALabelThatDoesNotExist(t *testing.T) {
    46  	flags := newUpdateCommand(nil).Flags()
    47  	flags.Set("label-rm", "dne")
    48  
    49  	labels := map[string]string{"foo": "theoldlabel"}
    50  	updateLabels(flags, &labels)
    51  	assert.Equal(t, len(labels), 1)
    52  }
    53  
    54  func TestUpdatePlacement(t *testing.T) {
    55  	flags := newUpdateCommand(nil).Flags()
    56  	flags.Set("constraint-add", "node=toadd")
    57  	flags.Set("constraint-rm", "node!=toremove")
    58  
    59  	placement := &swarm.Placement{
    60  		Constraints: []string{"node!=toremove", "container=tokeep"},
    61  	}
    62  
    63  	updatePlacement(flags, placement)
    64  	assert.Equal(t, len(placement.Constraints), 2)
    65  	assert.Equal(t, placement.Constraints[0], "container=tokeep")
    66  	assert.Equal(t, placement.Constraints[1], "node=toadd")
    67  }
    68  
    69  func TestUpdateEnvironment(t *testing.T) {
    70  	flags := newUpdateCommand(nil).Flags()
    71  	flags.Set("env-add", "toadd=newenv")
    72  	flags.Set("env-rm", "toremove")
    73  
    74  	envs := []string{"toremove=theenvtoremove", "tokeep=value"}
    75  
    76  	updateEnvironment(flags, &envs)
    77  	assert.Equal(t, len(envs), 2)
    78  	// Order has been removed in updateEnvironment (map)
    79  	sort.Strings(envs)
    80  	assert.Equal(t, envs[0], "toadd=newenv")
    81  	assert.Equal(t, envs[1], "tokeep=value")
    82  }
    83  
    84  func TestUpdateEnvironmentWithDuplicateValues(t *testing.T) {
    85  	flags := newUpdateCommand(nil).Flags()
    86  	flags.Set("env-add", "foo=newenv")
    87  	flags.Set("env-add", "foo=dupe")
    88  	flags.Set("env-rm", "foo")
    89  
    90  	envs := []string{"foo=value"}
    91  
    92  	updateEnvironment(flags, &envs)
    93  	assert.Equal(t, len(envs), 0)
    94  }
    95  
    96  func TestUpdateEnvironmentWithDuplicateKeys(t *testing.T) {
    97  	// Test case for #25404
    98  	flags := newUpdateCommand(nil).Flags()
    99  	flags.Set("env-add", "A=b")
   100  
   101  	envs := []string{"A=c"}
   102  
   103  	updateEnvironment(flags, &envs)
   104  	assert.Equal(t, len(envs), 1)
   105  	assert.Equal(t, envs[0], "A=b")
   106  }
   107  
   108  func TestUpdateGroups(t *testing.T) {
   109  	flags := newUpdateCommand(nil).Flags()
   110  	flags.Set("group-add", "wheel")
   111  	flags.Set("group-add", "docker")
   112  	flags.Set("group-rm", "root")
   113  	flags.Set("group-add", "foo")
   114  	flags.Set("group-rm", "docker")
   115  
   116  	groups := []string{"bar", "root"}
   117  
   118  	updateGroups(flags, &groups)
   119  	assert.Equal(t, len(groups), 3)
   120  	assert.Equal(t, groups[0], "bar")
   121  	assert.Equal(t, groups[1], "foo")
   122  	assert.Equal(t, groups[2], "wheel")
   123  }
   124  
   125  func TestUpdateDNSConfig(t *testing.T) {
   126  	flags := newUpdateCommand(nil).Flags()
   127  
   128  	// IPv4, with duplicates
   129  	flags.Set("dns-add", "1.1.1.1")
   130  	flags.Set("dns-add", "1.1.1.1")
   131  	flags.Set("dns-add", "2.2.2.2")
   132  	flags.Set("dns-rm", "3.3.3.3")
   133  	flags.Set("dns-rm", "2.2.2.2")
   134  	// IPv6
   135  	flags.Set("dns-add", "2001:db8:abc8::1")
   136  	// Invalid dns record
   137  	assert.Error(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
   138  
   139  	// domains with duplicates
   140  	flags.Set("dns-search-add", "example.com")
   141  	flags.Set("dns-search-add", "example.com")
   142  	flags.Set("dns-search-add", "example.org")
   143  	flags.Set("dns-search-rm", "example.org")
   144  	// Invalid dns search domain
   145  	assert.Error(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
   146  
   147  	flags.Set("dns-option-add", "ndots:9")
   148  	flags.Set("dns-option-rm", "timeout:3")
   149  
   150  	config := &swarm.DNSConfig{
   151  		Nameservers: []string{"3.3.3.3", "5.5.5.5"},
   152  		Search:      []string{"localdomain"},
   153  		Options:     []string{"timeout:3"},
   154  	}
   155  
   156  	updateDNSConfig(flags, &config)
   157  
   158  	assert.Equal(t, len(config.Nameservers), 3)
   159  	assert.Equal(t, config.Nameservers[0], "1.1.1.1")
   160  	assert.Equal(t, config.Nameservers[1], "2001:db8:abc8::1")
   161  	assert.Equal(t, config.Nameservers[2], "5.5.5.5")
   162  
   163  	assert.Equal(t, len(config.Search), 2)
   164  	assert.Equal(t, config.Search[0], "example.com")
   165  	assert.Equal(t, config.Search[1], "localdomain")
   166  
   167  	assert.Equal(t, len(config.Options), 1)
   168  	assert.Equal(t, config.Options[0], "ndots:9")
   169  }
   170  
   171  func TestUpdateMounts(t *testing.T) {
   172  	flags := newUpdateCommand(nil).Flags()
   173  	flags.Set("mount-add", "type=volume,source=vol2,target=/toadd")
   174  	flags.Set("mount-rm", "/toremove")
   175  
   176  	mounts := []mounttypes.Mount{
   177  		{Target: "/toremove", Source: "vol1", Type: mounttypes.TypeBind},
   178  		{Target: "/tokeep", Source: "vol3", Type: mounttypes.TypeBind},
   179  	}
   180  
   181  	updateMounts(flags, &mounts)
   182  	assert.Equal(t, len(mounts), 2)
   183  	assert.Equal(t, mounts[0].Target, "/toadd")
   184  	assert.Equal(t, mounts[1].Target, "/tokeep")
   185  
   186  }
   187  
   188  func TestUpdateMountsWithDuplicateMounts(t *testing.T) {
   189  	flags := newUpdateCommand(nil).Flags()
   190  	flags.Set("mount-add", "type=volume,source=vol4,target=/toadd")
   191  
   192  	mounts := []mounttypes.Mount{
   193  		{Target: "/tokeep1", Source: "vol1", Type: mounttypes.TypeBind},
   194  		{Target: "/toadd", Source: "vol2", Type: mounttypes.TypeBind},
   195  		{Target: "/tokeep2", Source: "vol3", Type: mounttypes.TypeBind},
   196  	}
   197  
   198  	updateMounts(flags, &mounts)
   199  	assert.Equal(t, len(mounts), 3)
   200  	assert.Equal(t, mounts[0].Target, "/tokeep1")
   201  	assert.Equal(t, mounts[1].Target, "/tokeep2")
   202  	assert.Equal(t, mounts[2].Target, "/toadd")
   203  }
   204  
   205  func TestUpdatePorts(t *testing.T) {
   206  	flags := newUpdateCommand(nil).Flags()
   207  	flags.Set("publish-add", "1000:1000")
   208  	flags.Set("publish-rm", "333/udp")
   209  
   210  	portConfigs := []swarm.PortConfig{
   211  		{TargetPort: 333, Protocol: swarm.PortConfigProtocolUDP},
   212  		{TargetPort: 555},
   213  	}
   214  
   215  	err := updatePorts(flags, &portConfigs)
   216  	assert.Equal(t, err, nil)
   217  	assert.Equal(t, len(portConfigs), 2)
   218  	// Do a sort to have the order (might have changed by map)
   219  	targetPorts := []int{int(portConfigs[0].TargetPort), int(portConfigs[1].TargetPort)}
   220  	sort.Ints(targetPorts)
   221  	assert.Equal(t, targetPorts[0], 555)
   222  	assert.Equal(t, targetPorts[1], 1000)
   223  }
   224  
   225  func TestUpdatePortsDuplicate(t *testing.T) {
   226  	// Test case for #25375
   227  	flags := newUpdateCommand(nil).Flags()
   228  	flags.Set("publish-add", "80:80")
   229  
   230  	portConfigs := []swarm.PortConfig{
   231  		{
   232  			TargetPort:    80,
   233  			PublishedPort: 80,
   234  			Protocol:      swarm.PortConfigProtocolTCP,
   235  			PublishMode:   swarm.PortConfigPublishModeIngress,
   236  		},
   237  	}
   238  
   239  	err := updatePorts(flags, &portConfigs)
   240  	assert.Equal(t, err, nil)
   241  	assert.Equal(t, len(portConfigs), 1)
   242  	assert.Equal(t, portConfigs[0].TargetPort, uint32(80))
   243  }
   244  
   245  func TestUpdateHealthcheckTable(t *testing.T) {
   246  	type test struct {
   247  		flags    [][2]string
   248  		initial  *container.HealthConfig
   249  		expected *container.HealthConfig
   250  		err      string
   251  	}
   252  	testCases := []test{
   253  		{
   254  			flags:    [][2]string{{"no-healthcheck", "true"}},
   255  			initial:  &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}, Retries: 10},
   256  			expected: &container.HealthConfig{Test: []string{"NONE"}},
   257  		},
   258  		{
   259  			flags:    [][2]string{{"health-cmd", "cmd1"}},
   260  			initial:  &container.HealthConfig{Test: []string{"NONE"}},
   261  			expected: &container.HealthConfig{Test: []string{"CMD-SHELL", "cmd1"}},
   262  		},
   263  		{
   264  			flags:    [][2]string{{"health-retries", "10"}},
   265  			initial:  &container.HealthConfig{Test: []string{"NONE"}},
   266  			expected: &container.HealthConfig{Retries: 10},
   267  		},
   268  		{
   269  			flags:    [][2]string{{"health-retries", "10"}},
   270  			initial:  &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
   271  			expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
   272  		},
   273  		{
   274  			flags:    [][2]string{{"health-interval", "1m"}},
   275  			initial:  &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
   276  			expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Interval: time.Minute},
   277  		},
   278  		{
   279  			flags:    [][2]string{{"health-cmd", ""}},
   280  			initial:  &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
   281  			expected: &container.HealthConfig{Retries: 10},
   282  		},
   283  		{
   284  			flags:    [][2]string{{"health-retries", "0"}},
   285  			initial:  &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10},
   286  			expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}},
   287  		},
   288  		{
   289  			flags: [][2]string{{"health-cmd", "cmd1"}, {"no-healthcheck", "true"}},
   290  			err:   "--no-healthcheck conflicts with --health-* options",
   291  		},
   292  		{
   293  			flags: [][2]string{{"health-interval", "10m"}, {"no-healthcheck", "true"}},
   294  			err:   "--no-healthcheck conflicts with --health-* options",
   295  		},
   296  		{
   297  			flags: [][2]string{{"health-timeout", "1m"}, {"no-healthcheck", "true"}},
   298  			err:   "--no-healthcheck conflicts with --health-* options",
   299  		},
   300  	}
   301  	for i, c := range testCases {
   302  		flags := newUpdateCommand(nil).Flags()
   303  		for _, flag := range c.flags {
   304  			flags.Set(flag[0], flag[1])
   305  		}
   306  		cspec := &swarm.ContainerSpec{
   307  			Healthcheck: c.initial,
   308  		}
   309  		err := updateHealthcheck(flags, cspec)
   310  		if c.err != "" {
   311  			assert.Error(t, err, c.err)
   312  		} else {
   313  			assert.NilError(t, err)
   314  			if !reflect.DeepEqual(cspec.Healthcheck, c.expected) {
   315  				t.Errorf("incorrect result for test %d, expected health config:\n\t%#v\ngot:\n\t%#v", i, c.expected, cspec.Healthcheck)
   316  			}
   317  		}
   318  	}
   319  }
   320  
   321  func TestUpdateHosts(t *testing.T) {
   322  	flags := newUpdateCommand(nil).Flags()
   323  	flags.Set("host-add", "example.net:2.2.2.2")
   324  	flags.Set("host-add", "ipv6.net:2001:db8:abc8::1")
   325  	// remove with ipv6 should work
   326  	flags.Set("host-rm", "example.net:2001:db8:abc8::1")
   327  	// just hostname should work as well
   328  	flags.Set("host-rm", "example.net")
   329  	// bad format error
   330  	assert.Error(t, flags.Set("host-add", "$example.com$"), "bad format for add-host:")
   331  
   332  	hosts := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 example.net"}
   333  
   334  	updateHosts(flags, &hosts)
   335  	assert.Equal(t, len(hosts), 3)
   336  	assert.Equal(t, hosts[0], "1.2.3.4 example.com")
   337  	assert.Equal(t, hosts[1], "2001:db8:abc8::1 ipv6.net")
   338  	assert.Equal(t, hosts[2], "4.3.2.1 example.org")
   339  }
   340  
   341  func TestUpdatePortsRmWithProtocol(t *testing.T) {
   342  	flags := newUpdateCommand(nil).Flags()
   343  	flags.Set("publish-add", "8081:81")
   344  	flags.Set("publish-add", "8082:82")
   345  	flags.Set("publish-rm", "80")
   346  	flags.Set("publish-rm", "81/tcp")
   347  	flags.Set("publish-rm", "82/udp")
   348  
   349  	portConfigs := []swarm.PortConfig{
   350  		{
   351  			TargetPort:    80,
   352  			PublishedPort: 8080,
   353  			Protocol:      swarm.PortConfigProtocolTCP,
   354  			PublishMode:   swarm.PortConfigPublishModeIngress,
   355  		},
   356  	}
   357  
   358  	err := updatePorts(flags, &portConfigs)
   359  	assert.Equal(t, err, nil)
   360  	assert.Equal(t, len(portConfigs), 2)
   361  	assert.Equal(t, portConfigs[0].TargetPort, uint32(81))
   362  	assert.Equal(t, portConfigs[1].TargetPort, uint32(82))
   363  }
   364  
   365  type secretAPIClientMock struct {
   366  	listResult []swarm.Secret
   367  }
   368  
   369  func (s secretAPIClientMock) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
   370  	return s.listResult, nil
   371  }
   372  func (s secretAPIClientMock) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
   373  	return types.SecretCreateResponse{}, nil
   374  }
   375  func (s secretAPIClientMock) SecretRemove(ctx context.Context, id string) error {
   376  	return nil
   377  }
   378  func (s secretAPIClientMock) SecretInspectWithRaw(ctx context.Context, name string) (swarm.Secret, []byte, error) {
   379  	return swarm.Secret{}, []byte{}, nil
   380  }
   381  func (s secretAPIClientMock) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
   382  	return nil
   383  }
   384  
   385  // TestUpdateSecretUpdateInPlace tests the ability to update the "target" of an secret with "docker service update"
   386  // by combining "--secret-rm" and "--secret-add" for the same secret.
   387  func TestUpdateSecretUpdateInPlace(t *testing.T) {
   388  	apiClient := secretAPIClientMock{
   389  		listResult: []swarm.Secret{
   390  			{
   391  				ID:   "tn9qiblgnuuut11eufquw5dev",
   392  				Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "foo"}},
   393  			},
   394  		},
   395  	}
   396  
   397  	flags := newUpdateCommand(nil).Flags()
   398  	flags.Set("secret-add", "source=foo,target=foo2")
   399  	flags.Set("secret-rm", "foo")
   400  
   401  	secrets := []*swarm.SecretReference{
   402  		{
   403  			File: &swarm.SecretReferenceFileTarget{
   404  				Name: "foo",
   405  				UID:  "0",
   406  				GID:  "0",
   407  				Mode: 292,
   408  			},
   409  			SecretID:   "tn9qiblgnuuut11eufquw5dev",
   410  			SecretName: "foo",
   411  		},
   412  	}
   413  
   414  	updatedSecrets, err := getUpdatedSecrets(apiClient, flags, secrets)
   415  
   416  	assert.Equal(t, err, nil)
   417  	assert.Equal(t, len(updatedSecrets), 1)
   418  	assert.Equal(t, updatedSecrets[0].SecretID, "tn9qiblgnuuut11eufquw5dev")
   419  	assert.Equal(t, updatedSecrets[0].SecretName, "foo")
   420  	assert.Equal(t, updatedSecrets[0].File.Name, "foo2")
   421  }
   422  
   423  func TestUpdateReadOnly(t *testing.T) {
   424  	spec := &swarm.ServiceSpec{}
   425  	cspec := &spec.TaskTemplate.ContainerSpec
   426  
   427  	// Update with --read-only=true, changed to true
   428  	flags := newUpdateCommand(nil).Flags()
   429  	flags.Set("read-only", "true")
   430  	updateService(flags, spec)
   431  	assert.Equal(t, cspec.ReadOnly, true)
   432  
   433  	// Update without --read-only, no change
   434  	flags = newUpdateCommand(nil).Flags()
   435  	updateService(flags, spec)
   436  	assert.Equal(t, cspec.ReadOnly, true)
   437  
   438  	// Update with --read-only=false, changed to false
   439  	flags = newUpdateCommand(nil).Flags()
   440  	flags.Set("read-only", "false")
   441  	updateService(flags, spec)
   442  	assert.Equal(t, cspec.ReadOnly, false)
   443  }