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

     1  package controlapi
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/docker/swarmkit/api"
    12  	"github.com/docker/swarmkit/identity"
    13  	"github.com/docker/swarmkit/manager/state/store"
    14  	"github.com/docker/swarmkit/testutils"
    15  	gogotypes "github.com/gogo/protobuf/types"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"google.golang.org/grpc/codes"
    19  )
    20  
    21  func createGenericSpec(name, runtime string) *api.ServiceSpec {
    22  	spec := createSpec(name, runtime, 0)
    23  	spec.Task.Runtime = &api.TaskSpec_Generic{
    24  		Generic: &api.GenericRuntimeSpec{
    25  			Kind: runtime,
    26  			Payload: &gogotypes.Any{
    27  				TypeUrl: "com.docker.custom.runtime",
    28  				Value:   []byte{0},
    29  			},
    30  		},
    31  	}
    32  	return spec
    33  }
    34  
    35  func createSpec(name, image string, instances uint64) *api.ServiceSpec {
    36  	return &api.ServiceSpec{
    37  		Annotations: api.Annotations{
    38  			Name: name,
    39  			Labels: map[string]string{
    40  				"common": "yes",
    41  				"unique": name,
    42  			},
    43  		},
    44  		Task: api.TaskSpec{
    45  			Runtime: &api.TaskSpec_Container{
    46  				Container: &api.ContainerSpec{
    47  					Image: image,
    48  				},
    49  			},
    50  		},
    51  		Mode: &api.ServiceSpec_Replicated{
    52  			Replicated: &api.ReplicatedService{
    53  				Replicas: instances,
    54  			},
    55  		},
    56  	}
    57  }
    58  
    59  func createSpecWithDuplicateMounts(name string) *api.ServiceSpec {
    60  	service := createSpec("", "image", 1)
    61  	mounts := []api.Mount{
    62  		{
    63  			Target: "/foo",
    64  			Source: "/mnt/mount1",
    65  		},
    66  		{
    67  			Target: "/foo",
    68  			Source: "/mnt/mount2",
    69  		},
    70  	}
    71  
    72  	service.Task.GetContainer().Mounts = mounts
    73  
    74  	return service
    75  }
    76  
    77  func createSpecWithHostnameTemplate(serviceName, hostnameTmpl string) *api.ServiceSpec {
    78  	service := createSpec(serviceName, "image", 1)
    79  	service.Task.GetContainer().Hostname = hostnameTmpl
    80  	return service
    81  }
    82  
    83  func createSecret(t *testing.T, ts *testServer, secretName, target string) *api.SecretReference {
    84  	secretSpec := createSecretSpec(secretName, []byte(secretName), nil)
    85  	secret := &api.Secret{
    86  		ID:   fmt.Sprintf("ID%v", secretName),
    87  		Spec: *secretSpec,
    88  	}
    89  	err := ts.Store.Update(func(tx store.Tx) error {
    90  		return store.CreateSecret(tx, secret)
    91  	})
    92  	assert.NoError(t, err)
    93  
    94  	return &api.SecretReference{
    95  		SecretName: secret.Spec.Annotations.Name,
    96  		SecretID:   secret.ID,
    97  		Target: &api.SecretReference_File{
    98  			File: &api.FileTarget{
    99  				Name: target,
   100  				UID:  "0",
   101  				GID:  "0",
   102  				Mode: 0666,
   103  			},
   104  		},
   105  	}
   106  }
   107  
   108  func createServiceSpecWithSecrets(serviceName string, secretRefs ...*api.SecretReference) *api.ServiceSpec {
   109  	service := createSpec(serviceName, fmt.Sprintf("image%v", serviceName), 1)
   110  	service.Task.GetContainer().Secrets = secretRefs
   111  
   112  	return service
   113  }
   114  
   115  func createConfig(t *testing.T, ts *testServer, configName, target string) *api.ConfigReference {
   116  	configSpec := createConfigSpec(configName, []byte(configName), nil)
   117  	config := &api.Config{
   118  		ID:   fmt.Sprintf("ID%v", configName),
   119  		Spec: *configSpec,
   120  	}
   121  	err := ts.Store.Update(func(tx store.Tx) error {
   122  		return store.CreateConfig(tx, config)
   123  	})
   124  	assert.NoError(t, err)
   125  
   126  	return &api.ConfigReference{
   127  		ConfigName: config.Spec.Annotations.Name,
   128  		ConfigID:   config.ID,
   129  		Target: &api.ConfigReference_File{
   130  			File: &api.FileTarget{
   131  				Name: target,
   132  				UID:  "0",
   133  				GID:  "0",
   134  				Mode: 0666,
   135  			},
   136  		},
   137  	}
   138  }
   139  
   140  func createServiceSpecWithConfigs(serviceName string, configRefs ...*api.ConfigReference) *api.ServiceSpec {
   141  	service := createSpec(serviceName, fmt.Sprintf("image%v", serviceName), 1)
   142  	service.Task.GetContainer().Configs = configRefs
   143  
   144  	return service
   145  }
   146  
   147  func createService(t *testing.T, ts *testServer, name, image string, instances uint64) *api.Service {
   148  	spec := createSpec(name, image, instances)
   149  	r, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   150  	assert.NoError(t, err)
   151  	return r.Service
   152  }
   153  
   154  func createGenericService(t *testing.T, ts *testServer, name, runtime string) *api.Service {
   155  	spec := createGenericSpec(name, runtime)
   156  	r, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   157  	assert.NoError(t, err)
   158  	return r.Service
   159  }
   160  
   161  func getIngressTargetID(t *testing.T, ts *testServer) string {
   162  	rsp, err := ts.Client.ListNetworks(context.Background(), &api.ListNetworksRequest{})
   163  	assert.NoError(t, err)
   164  	for _, n := range rsp.Networks {
   165  		if n.Spec.Ingress {
   166  			return n.ID
   167  		}
   168  	}
   169  	t.Fatal("unable to find ingress")
   170  	return ""
   171  }
   172  
   173  func TestValidateResources(t *testing.T) {
   174  	bad := []*api.Resources{
   175  		{MemoryBytes: 1},
   176  		{NanoCPUs: 42},
   177  	}
   178  
   179  	good := []*api.Resources{
   180  		{MemoryBytes: 4096 * 1024 * 1024},
   181  		{NanoCPUs: 1e9},
   182  	}
   183  
   184  	for _, b := range bad {
   185  		err := validateResources(b)
   186  		assert.Error(t, err)
   187  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   188  	}
   189  
   190  	for _, g := range good {
   191  		assert.NoError(t, validateResources(g))
   192  	}
   193  }
   194  
   195  func TestValidateResourceRequirements(t *testing.T) {
   196  	bad := []*api.ResourceRequirements{
   197  		{Limits: &api.Resources{MemoryBytes: 1}},
   198  		{Reservations: &api.Resources{MemoryBytes: 1}},
   199  	}
   200  	good := []*api.ResourceRequirements{
   201  		{Limits: &api.Resources{NanoCPUs: 1e9}},
   202  		{Reservations: &api.Resources{NanoCPUs: 1e9}},
   203  	}
   204  	for _, b := range bad {
   205  		err := validateResourceRequirements(b)
   206  		assert.Error(t, err)
   207  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   208  	}
   209  
   210  	for _, g := range good {
   211  		assert.NoError(t, validateResourceRequirements(g))
   212  	}
   213  }
   214  
   215  func TestValidateMode(t *testing.T) {
   216  	negative := -4
   217  	bad := []*api.ServiceSpec{
   218  		// -4 jammed into the replicas field, underflowing the uint64
   219  		{Mode: &api.ServiceSpec_Replicated{Replicated: &api.ReplicatedService{Replicas: uint64(negative)}}},
   220  		{Mode: &api.ServiceSpec_ReplicatedJob{ReplicatedJob: &api.ReplicatedJob{MaxConcurrent: uint64(negative)}}},
   221  		{Mode: &api.ServiceSpec_ReplicatedJob{ReplicatedJob: &api.ReplicatedJob{TotalCompletions: uint64(negative)}}},
   222  		{},
   223  	}
   224  
   225  	good := []*api.ServiceSpec{
   226  		{Mode: &api.ServiceSpec_Replicated{Replicated: &api.ReplicatedService{Replicas: 2}}},
   227  		{Mode: &api.ServiceSpec_Global{}},
   228  		{
   229  			Mode: &api.ServiceSpec_ReplicatedJob{
   230  				ReplicatedJob: &api.ReplicatedJob{
   231  					MaxConcurrent: 3, TotalCompletions: 9,
   232  				},
   233  			},
   234  		},
   235  		{Mode: &api.ServiceSpec_GlobalJob{}},
   236  	}
   237  
   238  	for _, b := range bad {
   239  		err := validateMode(b)
   240  		assert.Error(t, err)
   241  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   242  	}
   243  
   244  	for _, g := range good {
   245  		err := validateMode(g)
   246  		assert.NoError(t, err)
   247  	}
   248  }
   249  
   250  func TestValidateTaskSpec(t *testing.T) {
   251  	type badSource struct {
   252  		s api.TaskSpec
   253  		c codes.Code
   254  	}
   255  
   256  	for _, bad := range []badSource{
   257  		{
   258  			s: api.TaskSpec{
   259  				Runtime: &api.TaskSpec_Container{
   260  					Container: &api.ContainerSpec{},
   261  				},
   262  			},
   263  			c: codes.InvalidArgument,
   264  		},
   265  		{
   266  			s: api.TaskSpec{
   267  				Runtime: &api.TaskSpec_Attachment{
   268  					Attachment: &api.NetworkAttachmentSpec{},
   269  				},
   270  			},
   271  			c: codes.Unimplemented,
   272  		},
   273  		{
   274  			s: createSpec("", "", 0).Task,
   275  			c: codes.InvalidArgument,
   276  		},
   277  		{
   278  			s: createSpec("", "busybox###", 0).Task,
   279  			c: codes.InvalidArgument,
   280  		},
   281  		{
   282  			s: createGenericSpec("name", "").Task,
   283  			c: codes.InvalidArgument,
   284  		},
   285  		{
   286  			s: createGenericSpec("name", "c").Task,
   287  			c: codes.InvalidArgument,
   288  		},
   289  		{
   290  			s: createSpecWithDuplicateMounts("test").Task,
   291  			c: codes.InvalidArgument,
   292  		},
   293  		{
   294  			s: createSpecWithHostnameTemplate("", "{{.Nothing.here}}").Task,
   295  			c: codes.InvalidArgument,
   296  		},
   297  	} {
   298  		err := validateTaskSpec(bad.s)
   299  		assert.Error(t, err)
   300  		assert.Equal(t, bad.c, testutils.ErrorCode(err))
   301  	}
   302  
   303  	for _, good := range []api.TaskSpec{
   304  		createSpec("", "image", 0).Task,
   305  		createGenericSpec("", "custom").Task,
   306  		createSpecWithHostnameTemplate("service", "{{.Service.Name}}-{{.Task.Slot}}").Task,
   307  	} {
   308  		err := validateTaskSpec(good)
   309  		assert.NoError(t, err)
   310  	}
   311  }
   312  
   313  func TestValidateContainerSpec(t *testing.T) {
   314  	type BadSpec struct {
   315  		spec api.TaskSpec
   316  		c    codes.Code
   317  	}
   318  
   319  	bad1 := api.TaskSpec{
   320  		Runtime: &api.TaskSpec_Container{
   321  			Container: &api.ContainerSpec{
   322  				Image: "", // image name should not be empty
   323  			},
   324  		},
   325  	}
   326  
   327  	bad2 := api.TaskSpec{
   328  		Runtime: &api.TaskSpec_Container{
   329  			Container: &api.ContainerSpec{
   330  				Image: "image",
   331  				Mounts: []api.Mount{
   332  					{
   333  						Type:   api.Mount_MountType(0),
   334  						Source: "/data",
   335  						Target: "/data",
   336  					},
   337  					{
   338  						Type:   api.Mount_MountType(0),
   339  						Source: "/data2",
   340  						Target: "/data", // duplicate mount point
   341  					},
   342  				},
   343  			},
   344  		},
   345  	}
   346  
   347  	bad3 := api.TaskSpec{
   348  		Runtime: &api.TaskSpec_Container{
   349  			Container: &api.ContainerSpec{
   350  				Image: "image",
   351  				Healthcheck: &api.HealthConfig{
   352  					Test:        []string{"curl 127.0.0.1:3000"},
   353  					Interval:    gogotypes.DurationProto(time.Duration(-1 * time.Second)), // invalid negative duration
   354  					Timeout:     gogotypes.DurationProto(time.Duration(-1 * time.Second)), // invalid negative duration
   355  					Retries:     -1,                                                       // invalid negative integer
   356  					StartPeriod: gogotypes.DurationProto(time.Duration(-1 * time.Second)), // invalid negative duration
   357  				},
   358  			},
   359  		},
   360  	}
   361  
   362  	for _, bad := range []BadSpec{
   363  		{
   364  			spec: bad1,
   365  			c:    codes.InvalidArgument,
   366  		},
   367  		{
   368  			spec: bad2,
   369  			c:    codes.InvalidArgument,
   370  		},
   371  		{
   372  			spec: bad3,
   373  			c:    codes.InvalidArgument,
   374  		},
   375  	} {
   376  		err := validateContainerSpec(bad.spec)
   377  		assert.Error(t, err)
   378  		assert.Equal(t, bad.c, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   379  	}
   380  
   381  	good1 := api.TaskSpec{
   382  		Runtime: &api.TaskSpec_Container{
   383  			Container: &api.ContainerSpec{
   384  				Image: "image",
   385  				Mounts: []api.Mount{
   386  					{
   387  						Type:   api.Mount_MountType(0),
   388  						Source: "/data",
   389  						Target: "/data",
   390  					},
   391  					{
   392  						Type:   api.Mount_MountType(0),
   393  						Source: "/data2",
   394  						Target: "/data2",
   395  					},
   396  				},
   397  				Healthcheck: &api.HealthConfig{
   398  					Test:        []string{"curl 127.0.0.1:3000"},
   399  					Interval:    gogotypes.DurationProto(time.Duration(1 * time.Second)),
   400  					Timeout:     gogotypes.DurationProto(time.Duration(3 * time.Second)),
   401  					Retries:     5,
   402  					StartPeriod: gogotypes.DurationProto(time.Duration(1 * time.Second)),
   403  				},
   404  			},
   405  		},
   406  	}
   407  
   408  	for _, good := range []api.TaskSpec{good1} {
   409  		err := validateContainerSpec(good)
   410  		assert.NoError(t, err)
   411  	}
   412  }
   413  
   414  func TestValidateServiceSpec(t *testing.T) {
   415  	type BadServiceSpec struct {
   416  		spec *api.ServiceSpec
   417  		c    codes.Code
   418  	}
   419  
   420  	for _, bad := range []BadServiceSpec{
   421  		{
   422  			spec: nil,
   423  			c:    codes.InvalidArgument,
   424  		},
   425  		{
   426  			spec: &api.ServiceSpec{Annotations: api.Annotations{Name: "name"}},
   427  			c:    codes.InvalidArgument,
   428  		},
   429  		{
   430  			spec: createSpec("", "", 1),
   431  			c:    codes.InvalidArgument,
   432  		},
   433  		{
   434  			spec: createSpec("name", "", 1),
   435  			c:    codes.InvalidArgument,
   436  		},
   437  		{
   438  			spec: createSpec("", "image", 1),
   439  			c:    codes.InvalidArgument,
   440  		},
   441  		{
   442  			spec: createSpec(strings.Repeat("longname", 8), "image", 1),
   443  			c:    codes.InvalidArgument,
   444  		},
   445  	} {
   446  		err := validateServiceSpec(bad.spec)
   447  		assert.Error(t, err)
   448  		assert.Equal(t, bad.c, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   449  	}
   450  
   451  	for _, good := range []*api.ServiceSpec{
   452  		createSpec("name", "image", 1),
   453  	} {
   454  		err := validateServiceSpec(good)
   455  		assert.NoError(t, err)
   456  	}
   457  }
   458  
   459  // TestValidateServiceSpecJobsDifference is different from
   460  // TestValidateServiceSpec in that it checks that job-mode services are
   461  // validated differently from regular services.
   462  func TestValidateServiceSpecJobsDifference(t *testing.T) {
   463  	// correctly formed spec should be valid
   464  	cannedSpec := createSpec("name", "image", 1)
   465  	err := validateServiceSpec(cannedSpec)
   466  	assert.NoError(t, err)
   467  
   468  	// Replicated job should not be allowed to have update config
   469  	specReplicatedJobUpdate := cannedSpec.Copy()
   470  	specReplicatedJobUpdate.Mode = &api.ServiceSpec_ReplicatedJob{
   471  		ReplicatedJob: &api.ReplicatedJob{},
   472  	}
   473  	specReplicatedJobUpdate.Update = &api.UpdateConfig{}
   474  	err = validateServiceSpec(specReplicatedJobUpdate)
   475  	assert.Error(t, err)
   476  
   477  	specReplicatedJobNoUpdate := specReplicatedJobUpdate.Copy()
   478  	specReplicatedJobNoUpdate.Update = nil
   479  	err = validateServiceSpec(specReplicatedJobNoUpdate)
   480  	assert.NoError(t, err)
   481  
   482  	// Global job should not be allowed to have update config
   483  	specGlobalJobUpdate := cannedSpec.Copy()
   484  	specGlobalJobUpdate.Mode = &api.ServiceSpec_GlobalJob{
   485  		GlobalJob: &api.GlobalJob{},
   486  	}
   487  	specGlobalJobUpdate.Update = &api.UpdateConfig{}
   488  	err = validateServiceSpec(specGlobalJobUpdate)
   489  	assert.Error(t, err)
   490  
   491  	specGlobalJobNoUpdate := specGlobalJobUpdate.Copy()
   492  	specGlobalJobNoUpdate.Update = nil
   493  	err = validateServiceSpec(specReplicatedJobNoUpdate)
   494  	assert.NoError(t, err)
   495  
   496  	// Replicated service should be allowed to have update config, which should
   497  	// be verified for correctness
   498  	replicatedServiceBrokenUpdate := cannedSpec.Copy()
   499  	replicatedServiceBrokenUpdate.Update = &api.UpdateConfig{
   500  		Delay: -1 * time.Second,
   501  	}
   502  	err = validateServiceSpec(replicatedServiceBrokenUpdate)
   503  	assert.Error(t, err)
   504  
   505  	replicatedServiceCorrectUpdate := replicatedServiceBrokenUpdate.Copy()
   506  	replicatedServiceCorrectUpdate.Update.Delay = time.Second
   507  	err = validateServiceSpec(replicatedServiceCorrectUpdate)
   508  	assert.NoError(t, err)
   509  
   510  	// Global service should be allowed to have update config, which should be
   511  	// verified for correctness
   512  	globalServiceBrokenUpdate := replicatedServiceBrokenUpdate.Copy()
   513  	globalServiceBrokenUpdate.Mode = &api.ServiceSpec_Global{
   514  		Global: &api.GlobalService{},
   515  	}
   516  	err = validateServiceSpec(globalServiceBrokenUpdate)
   517  	assert.Error(t, err)
   518  
   519  	globalServiceCorrectUpdate := globalServiceBrokenUpdate.Copy()
   520  	globalServiceCorrectUpdate.Update.Delay = time.Second
   521  	err = validateServiceSpec(globalServiceCorrectUpdate)
   522  }
   523  
   524  func TestValidateRestartPolicy(t *testing.T) {
   525  	bad := []*api.RestartPolicy{
   526  		{
   527  			Delay:  gogotypes.DurationProto(time.Duration(-1 * time.Second)),
   528  			Window: gogotypes.DurationProto(time.Duration(-1 * time.Second)),
   529  		},
   530  		{
   531  			Delay:  gogotypes.DurationProto(time.Duration(20 * time.Second)),
   532  			Window: gogotypes.DurationProto(time.Duration(-4 * time.Second)),
   533  		},
   534  	}
   535  
   536  	good := []*api.RestartPolicy{
   537  		{
   538  			Delay:  gogotypes.DurationProto(time.Duration(10 * time.Second)),
   539  			Window: gogotypes.DurationProto(time.Duration(1 * time.Second)),
   540  		},
   541  	}
   542  
   543  	for _, b := range bad {
   544  		err := validateRestartPolicy(b)
   545  		assert.Error(t, err)
   546  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   547  	}
   548  
   549  	for _, g := range good {
   550  		assert.NoError(t, validateRestartPolicy(g))
   551  	}
   552  }
   553  
   554  func TestValidateUpdate(t *testing.T) {
   555  	bad := []*api.UpdateConfig{
   556  		{Delay: -1 * time.Second},
   557  		{Delay: -1000 * time.Second},
   558  		{Monitor: gogotypes.DurationProto(time.Duration(-1 * time.Second))},
   559  		{Monitor: gogotypes.DurationProto(time.Duration(-1000 * time.Second))},
   560  		{MaxFailureRatio: -0.1},
   561  		{MaxFailureRatio: 1.1},
   562  	}
   563  
   564  	good := []*api.UpdateConfig{
   565  		{Delay: time.Second},
   566  		{Monitor: gogotypes.DurationProto(time.Duration(time.Second))},
   567  		{MaxFailureRatio: 0.5},
   568  	}
   569  
   570  	for _, b := range bad {
   571  		err := validateUpdate(b)
   572  		assert.Error(t, err)
   573  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   574  	}
   575  
   576  	for _, g := range good {
   577  		assert.NoError(t, validateUpdate(g))
   578  	}
   579  }
   580  
   581  func TestCreateService(t *testing.T) {
   582  	ts := newTestServer(t)
   583  	defer ts.Stop()
   584  	_, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{})
   585  	assert.Error(t, err)
   586  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   587  
   588  	spec := createSpec("name", "image", 1)
   589  	r, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   590  	assert.NoError(t, err)
   591  	assert.NotEmpty(t, r.Service.ID)
   592  
   593  	// test port conflicts
   594  	spec = createSpec("name2", "image", 1)
   595  	spec.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   596  		{PublishedPort: uint32(9000), TargetPort: uint32(9000), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   597  	}}
   598  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   599  	assert.NoError(t, err)
   600  	assert.NotEmpty(t, r.Service.ID)
   601  
   602  	spec2 := createSpec("name3", "image", 1)
   603  	spec2.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   604  		{PublishedPort: uint32(9000), TargetPort: uint32(9000), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   605  	}}
   606  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec2})
   607  	assert.Error(t, err)
   608  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   609  
   610  	// test no port conflicts when no publish port is specified
   611  	spec3 := createSpec("name4", "image", 1)
   612  	spec3.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   613  		{TargetPort: uint32(9000), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   614  	}}
   615  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec3})
   616  	assert.NoError(t, err)
   617  	assert.NotEmpty(t, r.Service.ID)
   618  	spec4 := createSpec("name5", "image", 1)
   619  	spec4.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   620  		{TargetPort: uint32(9001), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   621  	}}
   622  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec4})
   623  	assert.NoError(t, err)
   624  
   625  	// ensure no port conflict when different protocols are used
   626  	spec = createSpec("name6", "image", 1)
   627  	spec.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   628  		{PublishedPort: uint32(9100), TargetPort: uint32(9100), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   629  	}}
   630  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   631  	assert.NoError(t, err)
   632  	assert.NotEmpty(t, r.Service.ID)
   633  
   634  	spec2 = createSpec("name7", "image", 1)
   635  	spec2.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   636  		{PublishedPort: uint32(9100), TargetPort: uint32(9100), Protocol: api.PortConfig_Protocol(api.ProtocolUDP)},
   637  	}}
   638  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec2})
   639  	assert.NoError(t, err)
   640  
   641  	// ensure no port conflict when host ports overlap
   642  	spec = createSpec("name8", "image", 1)
   643  	spec.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   644  		{PublishMode: api.PublishModeHost, PublishedPort: uint32(9101), TargetPort: uint32(9101), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   645  	}}
   646  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   647  	assert.NoError(t, err)
   648  	assert.NotEmpty(t, r.Service.ID)
   649  
   650  	spec2 = createSpec("name9", "image", 1)
   651  	spec2.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   652  		{PublishMode: api.PublishModeHost, PublishedPort: uint32(9101), TargetPort: uint32(9101), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   653  	}}
   654  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec2})
   655  	assert.NoError(t, err)
   656  
   657  	// ensure port conflict when host ports overlaps with ingress port (host port first)
   658  	spec = createSpec("name10", "image", 1)
   659  	spec.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   660  		{PublishMode: api.PublishModeHost, PublishedPort: uint32(9102), TargetPort: uint32(9102), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   661  	}}
   662  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   663  	assert.NoError(t, err)
   664  	assert.NotEmpty(t, r.Service.ID)
   665  
   666  	spec2 = createSpec("name11", "image", 1)
   667  	spec2.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   668  		{PublishMode: api.PublishModeIngress, PublishedPort: uint32(9102), TargetPort: uint32(9102), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   669  	}}
   670  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec2})
   671  	assert.Error(t, err)
   672  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   673  
   674  	// ensure port conflict when host ports overlaps with ingress port (ingress port first)
   675  	spec = createSpec("name12", "image", 1)
   676  	spec.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   677  		{PublishMode: api.PublishModeIngress, PublishedPort: uint32(9103), TargetPort: uint32(9103), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   678  	}}
   679  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   680  	assert.NoError(t, err)
   681  	assert.NotEmpty(t, r.Service.ID)
   682  
   683  	spec2 = createSpec("name13", "image", 1)
   684  	spec2.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
   685  		{PublishMode: api.PublishModeHost, PublishedPort: uint32(9103), TargetPort: uint32(9103), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
   686  	}}
   687  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec2})
   688  	assert.Error(t, err)
   689  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   690  
   691  	// ingress network cannot be attached explicitly
   692  	spec = createSpec("name14", "image", 1)
   693  	spec.Task.Networks = []*api.NetworkAttachmentConfig{{Target: getIngressTargetID(t, ts)}}
   694  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   695  	assert.Error(t, err)
   696  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   697  
   698  	spec = createSpec("notunique", "image", 1)
   699  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   700  	assert.NoError(t, err)
   701  
   702  	r, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
   703  	assert.Error(t, err)
   704  	assert.Equal(t, codes.AlreadyExists, testutils.ErrorCode(err))
   705  
   706  	// Make sure the error contains "name conflicts with an existing object" for
   707  	// backward-compatibility with older clients doing string-matching...
   708  	assert.Contains(t, err.Error(), "name conflicts with an existing object")
   709  }
   710  
   711  func TestSecretValidation(t *testing.T) {
   712  	ts := newTestServer(t)
   713  	defer ts.Stop()
   714  
   715  	// test creating service with a secret that doesn't exist fails
   716  	secretRef := createSecret(t, ts, "secret", "secret.txt")
   717  	secretRef.SecretID = "404"
   718  	secretRef.SecretName = "404"
   719  	serviceSpec := createServiceSpecWithSecrets("service", secretRef)
   720  	_, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   721  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   722  
   723  	// test creating service with a secretRef that has an existing secret
   724  	// but mismatched SecretName fails.
   725  	secretRef1 := createSecret(t, ts, "secret1", "secret1.txt")
   726  	secretRef1.SecretName = "secret2"
   727  	serviceSpec = createServiceSpecWithSecrets("service1", secretRef1)
   728  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   729  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   730  
   731  	// test secret target conflicts
   732  	secretRef2 := createSecret(t, ts, "secret2", "secret2.txt")
   733  	secretRef3 := createSecret(t, ts, "secret3", "secret2.txt")
   734  	serviceSpec = createServiceSpecWithSecrets("service2", secretRef2, secretRef3)
   735  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   736  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   737  
   738  	// test secret target conflicts with same secret and two references
   739  	secretRef3.SecretID = secretRef2.SecretID
   740  	secretRef3.SecretName = secretRef2.SecretName
   741  	serviceSpec = createServiceSpecWithSecrets("service3", secretRef2, secretRef3)
   742  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   743  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   744  
   745  	// test two different secretReferences with using the same secret
   746  	secretRef5 := secretRef2.Copy()
   747  	secretRef5.Target = &api.SecretReference_File{
   748  		File: &api.FileTarget{
   749  			Name: "different-target",
   750  		},
   751  	}
   752  
   753  	serviceSpec = createServiceSpecWithSecrets("service4", secretRef2, secretRef5)
   754  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   755  	assert.NoError(t, err)
   756  
   757  	// test secret References with invalid filenames
   758  	secretRefBlank := createSecret(t, ts, "", "")
   759  
   760  	serviceSpec = createServiceSpecWithSecrets("invalid-blank", secretRefBlank)
   761  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   762  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   763  
   764  	// Test secret References with valid filenames
   765  	// Note: "../secretfile.txt", "../../secretfile.txt" will be rejected
   766  	// by the executor, but controlapi presently doesn't reject those names.
   767  	// Such validation would be platform-specific.
   768  	validFileNames := []string{"file.txt", ".file.txt", "_file-txt_.txt", "../secretfile.txt", "../../secretfile.txt", "file../.txt", "subdir/file.txt", "/file.txt"}
   769  	for i, validName := range validFileNames {
   770  		secretRef := createSecret(t, ts, validName, validName)
   771  
   772  		serviceSpec = createServiceSpecWithSecrets(fmt.Sprintf("valid%v", i), secretRef)
   773  		_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   774  		assert.NoError(t, err)
   775  	}
   776  
   777  	// test secret target conflicts on update
   778  	serviceSpec1 := createServiceSpecWithSecrets("service5", secretRef2, secretRef3)
   779  	// Copy this service, but delete the secrets for creation
   780  	serviceSpec2 := serviceSpec1.Copy()
   781  	serviceSpec2.Task.GetContainer().Secrets = nil
   782  	rs, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec2})
   783  	assert.NoError(t, err)
   784  
   785  	// Attempt to update to the originally intended (conflicting) spec
   786  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
   787  		ServiceID:      rs.Service.ID,
   788  		Spec:           serviceSpec1,
   789  		ServiceVersion: &rs.Service.Meta.Version,
   790  	})
   791  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   792  }
   793  
   794  func TestConfigValidation(t *testing.T) {
   795  	ts := newTestServer(t)
   796  	defer ts.Stop()
   797  
   798  	// test creating service with a config that doesn't exist fails
   799  	configRef := createConfig(t, ts, "config", "config.txt")
   800  	configRef.ConfigID = "404"
   801  	configRef.ConfigName = "404"
   802  	serviceSpec := createServiceSpecWithConfigs("service", configRef)
   803  	_, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   804  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   805  
   806  	// test creating service with a configRef that has an existing config
   807  	// but mismatched ConfigName fails.
   808  	configRef1 := createConfig(t, ts, "config1", "config1.txt")
   809  	configRef1.ConfigName = "config2"
   810  	serviceSpec = createServiceSpecWithConfigs("service1", configRef1)
   811  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   812  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   813  
   814  	// test config target conflicts
   815  	configRef2 := createConfig(t, ts, "config2", "config2.txt")
   816  	configRef3 := createConfig(t, ts, "config3", "config2.txt")
   817  	serviceSpec = createServiceSpecWithConfigs("service2", configRef2, configRef3)
   818  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   819  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   820  
   821  	// test config target conflicts with same config and two references
   822  	configRef3.ConfigID = configRef2.ConfigID
   823  	configRef3.ConfigName = configRef2.ConfigName
   824  	serviceSpec = createServiceSpecWithConfigs("service3", configRef2, configRef3)
   825  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   826  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   827  
   828  	// test two different configReferences with using the same config
   829  	configRef5 := configRef2.Copy()
   830  	configRef5.Target = &api.ConfigReference_File{
   831  		File: &api.FileTarget{
   832  			Name: "different-target",
   833  		},
   834  	}
   835  
   836  	serviceSpec = createServiceSpecWithConfigs("service4", configRef2, configRef5)
   837  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   838  	assert.NoError(t, err)
   839  
   840  	// Test config References with valid filenames
   841  	// TODO(aaronl): Should some of these be disallowed? How can we deal
   842  	// with Windows-style paths on a Linux manager or vice versa?
   843  	validFileNames := []string{"../configfile.txt", "../../configfile.txt", "file../.txt", "subdir/file.txt", "file.txt", ".file.txt", "_file-txt_.txt"}
   844  	for i, validName := range validFileNames {
   845  		configRef := createConfig(t, ts, validName, validName)
   846  
   847  		serviceSpec = createServiceSpecWithConfigs(fmt.Sprintf("valid%v", i), configRef)
   848  		_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec})
   849  		assert.NoError(t, err)
   850  	}
   851  
   852  	// test config references with RuntimeTarget
   853  	configRefCredSpec := createConfig(t, ts, "credentialspec", "credentialspec")
   854  	configRefCredSpec.Target = &api.ConfigReference_Runtime{
   855  		Runtime: &api.RuntimeTarget{},
   856  	}
   857  	serviceSpec = createServiceSpecWithConfigs("runtimetarget", configRefCredSpec)
   858  	serviceSpec.Task.GetContainer().Privileges = &api.Privileges{
   859  		CredentialSpec: &api.Privileges_CredentialSpec{
   860  			Source: &api.Privileges_CredentialSpec_Config{
   861  				Config: configRefCredSpec.ConfigID,
   862  			},
   863  		},
   864  	}
   865  	_, err = ts.Client.CreateService(
   866  		context.Background(), &api.CreateServiceRequest{Spec: serviceSpec},
   867  	)
   868  	assert.NoError(t, err)
   869  
   870  	// test CredentialSpec without ConfigReference
   871  	serviceSpec = createSpec("missingruntimetarget", "imagemissingruntimetarget", 1)
   872  	serviceSpec.Task.GetContainer().Privileges = &api.Privileges{
   873  		CredentialSpec: &api.Privileges_CredentialSpec{
   874  			Source: &api.Privileges_CredentialSpec_Config{
   875  				Config: configRefCredSpec.ConfigID,
   876  			},
   877  		},
   878  	}
   879  	_, err = ts.Client.CreateService(
   880  		context.Background(), &api.CreateServiceRequest{Spec: serviceSpec},
   881  	)
   882  	t.Logf("error when missing configreference: %v", err)
   883  	assert.Error(t, err)
   884  
   885  	// test config target conflicts on update
   886  	serviceSpec1 := createServiceSpecWithConfigs("service5", configRef2, configRef3)
   887  	// Copy this service, but delete the configs for creation
   888  	serviceSpec2 := serviceSpec1.Copy()
   889  	serviceSpec2.Task.GetContainer().Configs = nil
   890  	rs, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: serviceSpec2})
   891  	assert.NoError(t, err)
   892  
   893  	// Attempt to update to the originally intended (conflicting) spec
   894  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
   895  		ServiceID:      rs.Service.ID,
   896  		Spec:           serviceSpec1,
   897  		ServiceVersion: &rs.Service.Meta.Version,
   898  	})
   899  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   900  }
   901  
   902  func TestGetService(t *testing.T) {
   903  	ts := newTestServer(t)
   904  	defer ts.Stop()
   905  	_, err := ts.Client.GetService(context.Background(), &api.GetServiceRequest{})
   906  	assert.Error(t, err)
   907  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   908  
   909  	_, err = ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: "invalid"})
   910  	assert.Error(t, err)
   911  	assert.Equal(t, codes.NotFound, testutils.ErrorCode(err))
   912  
   913  	service := createService(t, ts, "name", "image", 1)
   914  	r, err := ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: service.ID})
   915  	assert.NoError(t, err)
   916  	service.Meta.Version = r.Service.Meta.Version
   917  	assert.Equal(t, service, r.Service)
   918  }
   919  
   920  func TestUpdateService(t *testing.T) {
   921  	ts := newTestServer(t)
   922  	defer ts.Stop()
   923  	service := createService(t, ts, "name", "image", 1)
   924  
   925  	_, err := ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{})
   926  	assert.Error(t, err)
   927  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   928  
   929  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{ServiceID: "invalid", Spec: &service.Spec, ServiceVersion: &api.Version{}})
   930  	assert.Error(t, err)
   931  	assert.Equal(t, codes.NotFound, testutils.ErrorCode(err))
   932  
   933  	// No update options.
   934  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{ServiceID: service.ID, Spec: &service.Spec})
   935  	assert.Error(t, err)
   936  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
   937  
   938  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{ServiceID: service.ID, Spec: &service.Spec, ServiceVersion: &service.Meta.Version})
   939  	assert.NoError(t, err)
   940  
   941  	r, err := ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: service.ID})
   942  	assert.NoError(t, err)
   943  	assert.Equal(t, service.Spec.Annotations.Name, r.Service.Spec.Annotations.Name)
   944  	mode, ok := r.Service.Spec.GetMode().(*api.ServiceSpec_Replicated)
   945  	assert.Equal(t, ok, true)
   946  	assert.True(t, mode.Replicated.Replicas == 1)
   947  
   948  	mode.Replicated.Replicas = 42
   949  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
   950  		ServiceID:      service.ID,
   951  		Spec:           &r.Service.Spec,
   952  		ServiceVersion: &r.Service.Meta.Version,
   953  	})
   954  	assert.NoError(t, err)
   955  
   956  	r, err = ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: service.ID})
   957  	assert.NoError(t, err)
   958  	assert.Equal(t, service.Spec.Annotations.Name, r.Service.Spec.Annotations.Name)
   959  	mode, ok = r.Service.Spec.GetMode().(*api.ServiceSpec_Replicated)
   960  	assert.Equal(t, ok, true)
   961  	assert.True(t, mode.Replicated.Replicas == 42)
   962  
   963  	// mode change not allowed
   964  	r, err = ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: service.ID})
   965  	assert.NoError(t, err)
   966  	r.Service.Spec.Mode = &api.ServiceSpec_Global{
   967  		Global: &api.GlobalService{},
   968  	}
   969  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
   970  		ServiceID:      service.ID,
   971  		Spec:           &r.Service.Spec,
   972  		ServiceVersion: &r.Service.Meta.Version,
   973  	})
   974  	assert.Error(t, err)
   975  	assert.True(t, strings.Contains(err.Error(), errModeChangeNotAllowed.Error()))
   976  
   977  	// Versioning.
   978  	r, err = ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: service.ID})
   979  	assert.NoError(t, err)
   980  	version := &r.Service.Meta.Version
   981  
   982  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
   983  		ServiceID:      service.ID,
   984  		Spec:           &r.Service.Spec,
   985  		ServiceVersion: version,
   986  	})
   987  	assert.NoError(t, err)
   988  
   989  	// Perform an update with the "old" version.
   990  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
   991  		ServiceID:      service.ID,
   992  		Spec:           &r.Service.Spec,
   993  		ServiceVersion: version,
   994  	})
   995  	assert.Error(t, err)
   996  
   997  	// Attempt to update service name; renaming is not implemented
   998  	r.Service.Spec.Annotations.Name = "newname"
   999  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1000  		ServiceID:      service.ID,
  1001  		Spec:           &r.Service.Spec,
  1002  		ServiceVersion: version,
  1003  	})
  1004  	assert.Error(t, err)
  1005  	assert.Equal(t, codes.Unimplemented, testutils.ErrorCode(err))
  1006  
  1007  	// test port conflicts
  1008  	spec2 := createSpec("name2", "image", 1)
  1009  	spec2.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
  1010  		{PublishedPort: uint32(9000), TargetPort: uint32(9000), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
  1011  	}}
  1012  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec2})
  1013  	assert.NoError(t, err)
  1014  
  1015  	spec3 := createSpec("name3", "image", 1)
  1016  	rs, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec3})
  1017  	assert.NoError(t, err)
  1018  
  1019  	spec3.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
  1020  		{PublishedPort: uint32(9000), TargetPort: uint32(9000), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
  1021  	}}
  1022  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1023  		ServiceID:      rs.Service.ID,
  1024  		Spec:           spec3,
  1025  		ServiceVersion: &rs.Service.Meta.Version,
  1026  	})
  1027  	assert.Error(t, err)
  1028  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1029  	spec3.Endpoint = &api.EndpointSpec{Ports: []*api.PortConfig{
  1030  		{PublishedPort: uint32(9001), TargetPort: uint32(9000), Protocol: api.PortConfig_Protocol(api.ProtocolTCP)},
  1031  	}}
  1032  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1033  		ServiceID:      rs.Service.ID,
  1034  		Spec:           spec3,
  1035  		ServiceVersion: &rs.Service.Meta.Version,
  1036  	})
  1037  	assert.NoError(t, err)
  1038  
  1039  	// ingress network cannot be attached explicitly
  1040  	spec4 := createSpec("name4", "image", 1)
  1041  	rs, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec4})
  1042  	assert.NoError(t, err)
  1043  	spec4.Task.Networks = []*api.NetworkAttachmentConfig{{Target: getIngressTargetID(t, ts)}}
  1044  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1045  		ServiceID:      rs.Service.ID,
  1046  		Spec:           spec4,
  1047  		ServiceVersion: &rs.Service.Meta.Version,
  1048  	})
  1049  	assert.Error(t, err)
  1050  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1051  }
  1052  
  1053  func TestServiceUpdateRejectNetworkChange(t *testing.T) {
  1054  	ts := newTestServer(t)
  1055  	defer ts.Stop()
  1056  	spec := createSpec("name1", "image", 1)
  1057  	spec.Networks = []*api.NetworkAttachmentConfig{
  1058  		{
  1059  			Target: "net20",
  1060  		},
  1061  	}
  1062  	cr, err := ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
  1063  	assert.NoError(t, err)
  1064  
  1065  	ur, err := ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: cr.Service.ID})
  1066  	assert.NoError(t, err)
  1067  	service := ur.Service
  1068  
  1069  	service.Spec.Networks[0].Target = "net30"
  1070  
  1071  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1072  		ServiceID:      service.ID,
  1073  		Spec:           &service.Spec,
  1074  		ServiceVersion: &service.Meta.Version,
  1075  	})
  1076  	assert.Error(t, err)
  1077  	assert.True(t, strings.Contains(err.Error(), errNetworkUpdateNotSupported.Error()))
  1078  
  1079  	// Changes to TaskSpec.Networks are allowed
  1080  	spec = createSpec("name2", "image", 1)
  1081  	spec.Task.Networks = []*api.NetworkAttachmentConfig{
  1082  		{
  1083  			Target: "net20",
  1084  		},
  1085  	}
  1086  	cr, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
  1087  	assert.NoError(t, err)
  1088  
  1089  	ur, err = ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: cr.Service.ID})
  1090  	assert.NoError(t, err)
  1091  	service = ur.Service
  1092  
  1093  	service.Spec.Task.Networks[0].Target = "net30"
  1094  
  1095  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1096  		ServiceID:      service.ID,
  1097  		Spec:           &service.Spec,
  1098  		ServiceVersion: &service.Meta.Version,
  1099  	})
  1100  	assert.NoError(t, err)
  1101  
  1102  	// Migrate networks from ServiceSpec.Networks to TaskSpec.Networks
  1103  	spec = createSpec("name3", "image", 1)
  1104  	spec.Networks = []*api.NetworkAttachmentConfig{
  1105  		{
  1106  			Target: "net20",
  1107  		},
  1108  	}
  1109  	cr, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: spec})
  1110  	assert.NoError(t, err)
  1111  
  1112  	ur, err = ts.Client.GetService(context.Background(), &api.GetServiceRequest{ServiceID: cr.Service.ID})
  1113  	assert.NoError(t, err)
  1114  	service = ur.Service
  1115  
  1116  	service.Spec.Task.Networks = spec.Networks
  1117  	service.Spec.Networks = nil
  1118  
  1119  	_, err = ts.Client.UpdateService(context.Background(), &api.UpdateServiceRequest{
  1120  		ServiceID:      service.ID,
  1121  		Spec:           &service.Spec,
  1122  		ServiceVersion: &service.Meta.Version,
  1123  	})
  1124  	assert.NoError(t, err)
  1125  }
  1126  
  1127  func TestRemoveService(t *testing.T) {
  1128  	ts := newTestServer(t)
  1129  	defer ts.Stop()
  1130  	_, err := ts.Client.RemoveService(context.Background(), &api.RemoveServiceRequest{})
  1131  	assert.Error(t, err)
  1132  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1133  
  1134  	service := createService(t, ts, "name", "image", 1)
  1135  	r, err := ts.Client.RemoveService(context.Background(), &api.RemoveServiceRequest{ServiceID: service.ID})
  1136  	assert.NoError(t, err)
  1137  	assert.NotNil(t, r)
  1138  }
  1139  
  1140  func TestValidateEndpointSpec(t *testing.T) {
  1141  	endPointSpec1 := &api.EndpointSpec{
  1142  		Mode: api.ResolutionModeDNSRoundRobin,
  1143  		Ports: []*api.PortConfig{
  1144  			{
  1145  				Name:       "http",
  1146  				TargetPort: 80,
  1147  			},
  1148  		},
  1149  	}
  1150  
  1151  	endPointSpec2 := &api.EndpointSpec{
  1152  		Mode: api.ResolutionModeVirtualIP,
  1153  		Ports: []*api.PortConfig{
  1154  			{
  1155  				Name:          "http",
  1156  				TargetPort:    81,
  1157  				PublishedPort: 8001,
  1158  			},
  1159  			{
  1160  				Name:          "http",
  1161  				TargetPort:    80,
  1162  				PublishedPort: 8000,
  1163  			},
  1164  		},
  1165  	}
  1166  
  1167  	// has duplicated published port, invalid
  1168  	endPointSpec3 := &api.EndpointSpec{
  1169  		Mode: api.ResolutionModeVirtualIP,
  1170  		Ports: []*api.PortConfig{
  1171  			{
  1172  				Name:          "http",
  1173  				TargetPort:    81,
  1174  				PublishedPort: 8001,
  1175  			},
  1176  			{
  1177  				Name:          "http",
  1178  				TargetPort:    80,
  1179  				PublishedPort: 8001,
  1180  			},
  1181  		},
  1182  	}
  1183  
  1184  	// duplicated published port but different protocols, valid
  1185  	endPointSpec4 := &api.EndpointSpec{
  1186  		Mode: api.ResolutionModeVirtualIP,
  1187  		Ports: []*api.PortConfig{
  1188  			{
  1189  				Name:          "dns",
  1190  				TargetPort:    53,
  1191  				PublishedPort: 8002,
  1192  				Protocol:      api.ProtocolTCP,
  1193  			},
  1194  			{
  1195  				Name:          "dns",
  1196  				TargetPort:    53,
  1197  				PublishedPort: 8002,
  1198  				Protocol:      api.ProtocolUDP,
  1199  			},
  1200  		},
  1201  	}
  1202  
  1203  	// multiple randomly assigned published ports
  1204  	endPointSpec5 := &api.EndpointSpec{
  1205  		Mode: api.ResolutionModeVirtualIP,
  1206  		Ports: []*api.PortConfig{
  1207  			{
  1208  				Name:       "http",
  1209  				TargetPort: 80,
  1210  				Protocol:   api.ProtocolTCP,
  1211  			},
  1212  			{
  1213  				Name:       "dns",
  1214  				TargetPort: 53,
  1215  				Protocol:   api.ProtocolUDP,
  1216  			},
  1217  			{
  1218  				Name:       "dns",
  1219  				TargetPort: 53,
  1220  				Protocol:   api.ProtocolTCP,
  1221  			},
  1222  		},
  1223  	}
  1224  
  1225  	err := validateEndpointSpec(endPointSpec1)
  1226  	assert.Error(t, err)
  1227  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1228  
  1229  	err = validateEndpointSpec(endPointSpec2)
  1230  	assert.NoError(t, err)
  1231  
  1232  	err = validateEndpointSpec(endPointSpec3)
  1233  	assert.Error(t, err)
  1234  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1235  
  1236  	err = validateEndpointSpec(endPointSpec4)
  1237  	assert.NoError(t, err)
  1238  
  1239  	err = validateEndpointSpec(endPointSpec5)
  1240  	assert.NoError(t, err)
  1241  }
  1242  
  1243  func TestServiceEndpointSpecUpdate(t *testing.T) {
  1244  	ts := newTestServer(t)
  1245  	defer ts.Stop()
  1246  	spec := &api.ServiceSpec{
  1247  		Annotations: api.Annotations{
  1248  			Name: "name",
  1249  		},
  1250  		Task: api.TaskSpec{
  1251  			Runtime: &api.TaskSpec_Container{
  1252  				Container: &api.ContainerSpec{
  1253  					Image: "image",
  1254  				},
  1255  			},
  1256  		},
  1257  		Mode: &api.ServiceSpec_Replicated{
  1258  			Replicated: &api.ReplicatedService{
  1259  				Replicas: 1,
  1260  			},
  1261  		},
  1262  		Endpoint: &api.EndpointSpec{
  1263  			Ports: []*api.PortConfig{
  1264  				{
  1265  					Name:       "http",
  1266  					TargetPort: 80,
  1267  				},
  1268  			},
  1269  		},
  1270  	}
  1271  
  1272  	r, err := ts.Client.CreateService(context.Background(),
  1273  		&api.CreateServiceRequest{Spec: spec})
  1274  	assert.NoError(t, err)
  1275  	assert.NotNil(t, r)
  1276  
  1277  	// Update the service with duplicate ports
  1278  	spec.Endpoint.Ports = append(spec.Endpoint.Ports, &api.PortConfig{
  1279  		Name:       "fakehttp",
  1280  		TargetPort: 80,
  1281  	})
  1282  	_, err = ts.Client.UpdateService(context.Background(),
  1283  		&api.UpdateServiceRequest{Spec: spec})
  1284  	assert.Error(t, err)
  1285  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1286  }
  1287  
  1288  func TestListServices(t *testing.T) {
  1289  	ts := newTestServer(t)
  1290  	defer ts.Stop()
  1291  	r, err := ts.Client.ListServices(context.Background(), &api.ListServicesRequest{})
  1292  	assert.NoError(t, err)
  1293  	assert.Empty(t, r.Services)
  1294  
  1295  	s1 := createService(t, ts, "name1", "image", 1)
  1296  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{})
  1297  	assert.NoError(t, err)
  1298  	assert.Equal(t, 1, len(r.Services))
  1299  
  1300  	createService(t, ts, "name2", "image", 1)
  1301  	s3 := createGenericService(t, ts, "name3", "my-runtime")
  1302  
  1303  	// List all.
  1304  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{})
  1305  	assert.NoError(t, err)
  1306  	assert.Equal(t, 3, len(r.Services))
  1307  
  1308  	// List by runtime.
  1309  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1310  		Filters: &api.ListServicesRequest_Filters{
  1311  			Runtimes: []string{"container"},
  1312  		},
  1313  	})
  1314  	assert.NoError(t, err)
  1315  	assert.Equal(t, 2, len(r.Services))
  1316  
  1317  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1318  		Filters: &api.ListServicesRequest_Filters{
  1319  			Runtimes: []string{"my-runtime"},
  1320  		},
  1321  	})
  1322  	assert.NoError(t, err)
  1323  	assert.Equal(t, 1, len(r.Services))
  1324  	assert.Equal(t, s3.ID, r.Services[0].ID)
  1325  
  1326  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1327  		Filters: &api.ListServicesRequest_Filters{
  1328  			Runtimes: []string{"invalid"},
  1329  		},
  1330  	})
  1331  	assert.NoError(t, err)
  1332  	assert.Empty(t, r.Services)
  1333  
  1334  	// List with an ID prefix.
  1335  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1336  		Filters: &api.ListServicesRequest_Filters{
  1337  			IDPrefixes: []string{s1.ID[0:4]},
  1338  		},
  1339  	})
  1340  	assert.NoError(t, err)
  1341  	assert.Equal(t, 1, len(r.Services))
  1342  	assert.Equal(t, s1.ID, r.Services[0].ID)
  1343  
  1344  	// List with simple filter.
  1345  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1346  		Filters: &api.ListServicesRequest_Filters{
  1347  			NamePrefixes: []string{"name1"},
  1348  		},
  1349  	})
  1350  	assert.NoError(t, err)
  1351  	assert.Equal(t, 1, len(r.Services))
  1352  
  1353  	// List with union filter.
  1354  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1355  		Filters: &api.ListServicesRequest_Filters{
  1356  			NamePrefixes: []string{"name1", "name2"},
  1357  		},
  1358  	})
  1359  	assert.NoError(t, err)
  1360  	assert.Equal(t, 2, len(r.Services))
  1361  
  1362  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1363  		Filters: &api.ListServicesRequest_Filters{
  1364  			NamePrefixes: []string{"name1", "name2", "name4"},
  1365  		},
  1366  	})
  1367  	assert.NoError(t, err)
  1368  	assert.Equal(t, 2, len(r.Services))
  1369  
  1370  	r, err = ts.Client.ListServices(context.Background(), &api.ListServicesRequest{
  1371  		Filters: &api.ListServicesRequest_Filters{
  1372  			NamePrefixes: []string{"name4"},
  1373  		},
  1374  	})
  1375  	assert.NoError(t, err)
  1376  	assert.Equal(t, 0, len(r.Services))
  1377  
  1378  	// List with filter intersection.
  1379  	r, err = ts.Client.ListServices(context.Background(),
  1380  		&api.ListServicesRequest{
  1381  			Filters: &api.ListServicesRequest_Filters{
  1382  				NamePrefixes: []string{"name1"},
  1383  				IDPrefixes:   []string{s1.ID},
  1384  			},
  1385  		},
  1386  	)
  1387  	assert.NoError(t, err)
  1388  	assert.Equal(t, 1, len(r.Services))
  1389  
  1390  	r, err = ts.Client.ListServices(context.Background(),
  1391  		&api.ListServicesRequest{
  1392  			Filters: &api.ListServicesRequest_Filters{
  1393  				NamePrefixes: []string{"name2"},
  1394  				IDPrefixes:   []string{s1.ID},
  1395  			},
  1396  		},
  1397  	)
  1398  	assert.NoError(t, err)
  1399  	assert.Equal(t, 0, len(r.Services))
  1400  
  1401  	r, err = ts.Client.ListServices(context.Background(),
  1402  		&api.ListServicesRequest{
  1403  			Filters: &api.ListServicesRequest_Filters{
  1404  				NamePrefixes: []string{"name3"},
  1405  				Runtimes:     []string{"my-runtime"},
  1406  			},
  1407  		},
  1408  	)
  1409  	assert.NoError(t, err)
  1410  	assert.Equal(t, 1, len(r.Services))
  1411  
  1412  	// List filter by label.
  1413  	r, err = ts.Client.ListServices(context.Background(),
  1414  		&api.ListServicesRequest{
  1415  			Filters: &api.ListServicesRequest_Filters{
  1416  				Labels: map[string]string{
  1417  					"common": "yes",
  1418  				},
  1419  			},
  1420  		},
  1421  	)
  1422  	assert.NoError(t, err)
  1423  	assert.Equal(t, 3, len(r.Services))
  1424  
  1425  	// Value-less label.
  1426  	r, err = ts.Client.ListServices(context.Background(),
  1427  		&api.ListServicesRequest{
  1428  			Filters: &api.ListServicesRequest_Filters{
  1429  				Labels: map[string]string{
  1430  					"common": "",
  1431  				},
  1432  			},
  1433  		},
  1434  	)
  1435  	assert.NoError(t, err)
  1436  	assert.Equal(t, 3, len(r.Services))
  1437  
  1438  	// Label intersection.
  1439  	r, err = ts.Client.ListServices(context.Background(),
  1440  		&api.ListServicesRequest{
  1441  			Filters: &api.ListServicesRequest_Filters{
  1442  				Labels: map[string]string{
  1443  					"common": "",
  1444  					"unique": "name1",
  1445  				},
  1446  			},
  1447  		},
  1448  	)
  1449  	assert.NoError(t, err)
  1450  	assert.Equal(t, 1, len(r.Services))
  1451  
  1452  	r, err = ts.Client.ListServices(context.Background(),
  1453  		&api.ListServicesRequest{
  1454  			Filters: &api.ListServicesRequest_Filters{
  1455  				Labels: map[string]string{
  1456  					"common": "",
  1457  					"unique": "error",
  1458  				},
  1459  			},
  1460  		},
  1461  	)
  1462  	assert.NoError(t, err)
  1463  	assert.Equal(t, 0, len(r.Services))
  1464  }
  1465  
  1466  func TestListServiceStatuses(t *testing.T) {
  1467  	ts := newTestServer(t)
  1468  	defer ts.Stop()
  1469  
  1470  	// Test listing no services is empty and has no error
  1471  	r, err := ts.Client.ListServiceStatuses(
  1472  		context.Background(),
  1473  		&api.ListServiceStatusesRequest{},
  1474  	)
  1475  	assert.NoError(t, err, "error when listing no services against an empty store")
  1476  	assert.NotNil(t, r, "response against an empty store was nil")
  1477  	assert.Empty(t, r.Statuses, "response statuses was not empty")
  1478  
  1479  	// Test listing services that do not exist. We should still get a response,
  1480  	// but both desired and actual should be 0
  1481  	r, err = ts.Client.ListServiceStatuses(
  1482  		context.Background(),
  1483  		&api.ListServiceStatusesRequest{Services: []string{"foo"}},
  1484  	)
  1485  	assert.NoError(t, err, "error listing services that do not exist")
  1486  	assert.NotNil(t, r, "response for nonexistant services was nil")
  1487  	assert.Len(t, r.Statuses, 1, "expected 1 status")
  1488  	assert.Equal(
  1489  		t, r.Statuses[0],
  1490  		&api.ListServiceStatusesResponse_ServiceStatus{ServiceID: "foo"},
  1491  	)
  1492  
  1493  	// now test that listing service statuses actually works.
  1494  
  1495  	// justRight will be converged
  1496  	justRight := createService(t, ts, "justRight", "image", 3)
  1497  	// notEnough will not have enough tasks in running
  1498  	notEnough := createService(t, ts, "notEnough", "image", 7)
  1499  
  1500  	// no shortcut for creating a global service
  1501  	globalSpec := createSpec("global", "image", 0)
  1502  	globalSpec.Mode = &api.ServiceSpec_Global{Global: &api.GlobalService{}}
  1503  
  1504  	svcResp, svcErr := ts.Client.CreateService(
  1505  		context.Background(), &api.CreateServiceRequest{Spec: globalSpec},
  1506  	)
  1507  	assert.NoError(t, svcErr)
  1508  
  1509  	// global will have the right number of tasks
  1510  	global := svcResp.Service
  1511  
  1512  	global2Spec := globalSpec.Copy()
  1513  	global2Spec.Annotations.Name = "global2"
  1514  
  1515  	svcResp, svcErr = ts.Client.CreateService(
  1516  		context.Background(), &api.CreateServiceRequest{Spec: global2Spec},
  1517  	)
  1518  	assert.NoError(t, svcErr)
  1519  
  1520  	// global2 will not have enough tasks
  1521  	global2 := svcResp.Service
  1522  
  1523  	// over will have too many tasks running (as would be seen in a scale-down
  1524  	over := createService(t, ts, "over", "image", 2)
  1525  
  1526  	// replicatedJob1 will be partly completed
  1527  	replicatedJob1Spec := createSpec("replicatedJob1", "image", 0)
  1528  	replicatedJob1Spec.Mode = &api.ServiceSpec_ReplicatedJob{
  1529  		ReplicatedJob: &api.ReplicatedJob{
  1530  			MaxConcurrent:    2,
  1531  			TotalCompletions: 10,
  1532  		},
  1533  	}
  1534  
  1535  	svcResp, svcErr = ts.Client.CreateService(
  1536  		context.Background(), &api.CreateServiceRequest{Spec: replicatedJob1Spec},
  1537  	)
  1538  	assert.NoError(t, svcErr)
  1539  	assert.NotNil(t, svcResp)
  1540  	replicatedJob1 := svcResp.Service
  1541  
  1542  	// replicatedJob2 has been executed before, and will have tasks from a
  1543  	// previous JobIteration
  1544  	replicatedJob2Spec := replicatedJob1Spec.Copy()
  1545  	replicatedJob2Spec.Annotations.Name = "replicatedJob2"
  1546  	svcResp, svcErr = ts.Client.CreateService(
  1547  		context.Background(), &api.CreateServiceRequest{Spec: replicatedJob2Spec},
  1548  	)
  1549  	assert.NoError(t, svcErr)
  1550  	assert.NotNil(t, svcResp)
  1551  	replicatedJob2 := svcResp.Service
  1552  
  1553  	// globalJob is a partly complete global job
  1554  	globalJobSpec := createSpec("globalJob", "image", 0)
  1555  	globalJobSpec.Mode = &api.ServiceSpec_GlobalJob{
  1556  		GlobalJob: &api.GlobalJob{},
  1557  	}
  1558  	svcResp, svcErr = ts.Client.CreateService(
  1559  		context.Background(), &api.CreateServiceRequest{Spec: globalJobSpec},
  1560  	)
  1561  	assert.NoError(t, svcErr)
  1562  	assert.NotNil(t, svcResp)
  1563  	globalJob := svcResp.Service
  1564  
  1565  	// now create some tasks. use a quick helper function for this
  1566  	createTask := func(s *api.Service, actual api.TaskState, desired api.TaskState, opts ...func(*api.Service, *api.Task)) *api.Task {
  1567  		task := &api.Task{
  1568  			ID:           identity.NewID(),
  1569  			DesiredState: desired,
  1570  			Spec:         s.Spec.Task,
  1571  			Status: api.TaskStatus{
  1572  				State: actual,
  1573  			},
  1574  			ServiceID: s.ID,
  1575  		}
  1576  
  1577  		for _, opt := range opts {
  1578  			opt(s, task)
  1579  		}
  1580  
  1581  		err := ts.Store.Update(func(tx store.Tx) error {
  1582  			return store.CreateTask(tx, task)
  1583  		})
  1584  		assert.NoError(t, err)
  1585  		return task
  1586  	}
  1587  
  1588  	withJobIteration := func(s *api.Service, task *api.Task) {
  1589  		assert.NotNil(t, s.JobStatus)
  1590  		task.JobIteration = &(s.JobStatus.JobIteration)
  1591  	}
  1592  
  1593  	// alias task states for brevity
  1594  	running := api.TaskStateRunning
  1595  	shutdown := api.TaskStateShutdown
  1596  	completed := api.TaskStateCompleted
  1597  	newt := api.TaskStateNew
  1598  	failed := api.TaskStateFailed
  1599  
  1600  	// create 3 running tasks for justRight
  1601  	for i := 0; i < 3; i++ {
  1602  		createTask(justRight, running, running)
  1603  	}
  1604  	// create 2 failed and 2 shutdown tasks
  1605  	for i := 0; i < 2; i++ {
  1606  		createTask(justRight, failed, shutdown)
  1607  		createTask(justRight, shutdown, shutdown)
  1608  	}
  1609  
  1610  	// create 4 tasks for notEnough
  1611  	for i := 0; i < 4; i++ {
  1612  		createTask(notEnough, running, running)
  1613  	}
  1614  	// create 3 tasks in new state
  1615  	for i := 0; i < 3; i++ {
  1616  		createTask(notEnough, newt, running)
  1617  	}
  1618  	// create 1 failed and 1 shutdown task
  1619  	createTask(notEnough, failed, shutdown)
  1620  	createTask(notEnough, shutdown, shutdown)
  1621  
  1622  	// create 2 tasks out of 2 desired for global
  1623  	for i := 0; i < 2; i++ {
  1624  		createTask(global, running, running)
  1625  	}
  1626  	// create 3 shutdown tasks for global
  1627  	for i := 0; i < 3; i++ {
  1628  		createTask(global, shutdown, shutdown)
  1629  	}
  1630  
  1631  	// create 4 out of 5 tasks for global2
  1632  	for i := 0; i < 4; i++ {
  1633  		createTask(global2, running, running)
  1634  	}
  1635  	createTask(global2, newt, running)
  1636  
  1637  	// create 6 failed tasks
  1638  	for i := 0; i < 6; i++ {
  1639  		createTask(global2, failed, shutdown)
  1640  	}
  1641  
  1642  	// create 4 out of 2 tasks. no shutdown or failed tasks.  this would be the
  1643  	// case if you did a call immediately after updating the service, before
  1644  	// the orchestrator had updated the task desired states
  1645  	for i := 0; i < 4; i++ {
  1646  		createTask(over, running, running)
  1647  	}
  1648  
  1649  	// create 2 running tasks for replicatedJob1
  1650  	for i := 0; i < 2; i++ {
  1651  		createTask(replicatedJob1, running, completed, withJobIteration)
  1652  	}
  1653  
  1654  	// create 4 completed tasks for replicatedJob1
  1655  	for i := 0; i < 4; i++ {
  1656  		createTask(replicatedJob1, completed, completed, withJobIteration)
  1657  	}
  1658  
  1659  	// create 10 completed tasks for replicatedJob2
  1660  	for i := 0; i < 10; i++ {
  1661  		createTask(replicatedJob2, completed, completed, withJobIteration)
  1662  	}
  1663  
  1664  	replicatedJob2Spec.Task.ForceUpdate++
  1665  
  1666  	// now update replicatedJob2, so JobIteration gets incremented
  1667  	updateResp, updateErr := ts.Client.UpdateService(
  1668  		context.Background(),
  1669  		&api.UpdateServiceRequest{
  1670  			ServiceID:      replicatedJob2.ID,
  1671  			ServiceVersion: &replicatedJob2.Meta.Version,
  1672  			Spec:           replicatedJob2Spec,
  1673  		},
  1674  	)
  1675  	assert.NoError(t, updateErr)
  1676  	assert.NotNil(t, updateResp)
  1677  	replicatedJob2 = updateResp.Service
  1678  
  1679  	// and create 1 tasks out of 2
  1680  	createTask(replicatedJob2, running, completed, withJobIteration)
  1681  	// and 3 completed already
  1682  	for i := 0; i < 3; i++ {
  1683  		createTask(replicatedJob2, completed, completed, withJobIteration)
  1684  	}
  1685  
  1686  	// create 5 running tasks for globalJob
  1687  	for i := 0; i < 5; i++ {
  1688  		createTask(globalJob, running, completed, withJobIteration)
  1689  	}
  1690  	// create 3 completed tasks
  1691  	for i := 0; i < 3; i++ {
  1692  		createTask(globalJob, completed, completed, withJobIteration)
  1693  	}
  1694  
  1695  	// now, create a service that has already been deleted, but has dangling
  1696  	// tasks
  1697  	goneSpec := createSpec("gone", "image", 3)
  1698  	gone := &api.Service{
  1699  		ID:   identity.NewID(),
  1700  		Spec: *goneSpec,
  1701  	}
  1702  
  1703  	for i := 0; i < 3; i++ {
  1704  		createTask(gone, running, shutdown)
  1705  		createTask(gone, shutdown, shutdown)
  1706  	}
  1707  
  1708  	// now list service statuses
  1709  	r, err = ts.Client.ListServiceStatuses(
  1710  		context.Background(),
  1711  		&api.ListServiceStatusesRequest{Services: []string{
  1712  			justRight.ID, notEnough.ID, global.ID, global2.ID,
  1713  			replicatedJob1.ID, replicatedJob2.ID, globalJob.ID, over.ID, gone.ID,
  1714  		}},
  1715  	)
  1716  	assert.NoError(t, err, "error getting service statuses")
  1717  	assert.NotNil(t, r, "service status response is nil")
  1718  	assert.Len(t, r.Statuses, 9)
  1719  
  1720  	expected := map[string]*api.ListServiceStatusesResponse_ServiceStatus{
  1721  		"justRight": {
  1722  			ServiceID:    justRight.ID,
  1723  			DesiredTasks: 3,
  1724  			RunningTasks: 3,
  1725  		},
  1726  		"notEnough": {
  1727  			ServiceID:    notEnough.ID,
  1728  			DesiredTasks: 7,
  1729  			RunningTasks: 4,
  1730  		},
  1731  		"global": {
  1732  			ServiceID:    global.ID,
  1733  			DesiredTasks: 2,
  1734  			RunningTasks: 2,
  1735  		},
  1736  		"global2": {
  1737  			ServiceID:    global2.ID,
  1738  			DesiredTasks: 5,
  1739  			RunningTasks: 4,
  1740  		},
  1741  		"over": {
  1742  			ServiceID:    over.ID,
  1743  			DesiredTasks: 2,
  1744  			RunningTasks: 4,
  1745  		},
  1746  		"replicatedJob1": {
  1747  			ServiceID:      replicatedJob1.ID,
  1748  			DesiredTasks:   2,
  1749  			RunningTasks:   2,
  1750  			CompletedTasks: 4,
  1751  		},
  1752  		"replicatedJob2": {
  1753  			ServiceID:      replicatedJob2.ID,
  1754  			DesiredTasks:   2,
  1755  			RunningTasks:   1,
  1756  			CompletedTasks: 3,
  1757  		},
  1758  		"globalJob": {
  1759  			ServiceID:      globalJob.ID,
  1760  			DesiredTasks:   5,
  1761  			RunningTasks:   5,
  1762  			CompletedTasks: 3,
  1763  		},
  1764  		"gone": {
  1765  			ServiceID:    gone.ID,
  1766  			DesiredTasks: 0,
  1767  			RunningTasks: 3,
  1768  		},
  1769  	}
  1770  
  1771  	// compare expected and actual values. make sure all are used by keeping
  1772  	// track of which we visited. i borrowed this pattern from
  1773  	// assert.ElementsMatch, which is in a newer version of that library
  1774  	visited := make([]bool, len(expected))
  1775  	for name, expect := range expected {
  1776  		found := false
  1777  		for i := 0; i < len(r.Statuses); i++ {
  1778  			if visited[i] {
  1779  				continue
  1780  			}
  1781  			if reflect.DeepEqual(expect, r.Statuses[i]) {
  1782  				visited[i] = true
  1783  				found = true
  1784  				break
  1785  			}
  1786  		}
  1787  		if !found {
  1788  			t.Errorf("did not find status for %v in response", name)
  1789  		}
  1790  	}
  1791  }
  1792  
  1793  // TestJobService tests that if a job-mode service is created, the necessary
  1794  // fields are all initialized to the correct values. Then, it tests that if a
  1795  // job-mode service is updated, the fields are updated to correct values.
  1796  func TestJobService(t *testing.T) {
  1797  	ts := newTestServer(t)
  1798  	defer ts.Stop()
  1799  
  1800  	// first, create a replicated job mode service spec
  1801  	spec := &api.ServiceSpec{
  1802  		Annotations: api.Annotations{
  1803  			Name: "replicatedjob",
  1804  		},
  1805  		Task: api.TaskSpec{
  1806  			Runtime: &api.TaskSpec_Container{
  1807  				Container: &api.ContainerSpec{
  1808  					Image: "image",
  1809  				},
  1810  			},
  1811  		},
  1812  		Mode: &api.ServiceSpec_ReplicatedJob{
  1813  			ReplicatedJob: &api.ReplicatedJob{
  1814  				MaxConcurrent:    3,
  1815  				TotalCompletions: 9,
  1816  			},
  1817  		},
  1818  	}
  1819  
  1820  	before := gogotypes.TimestampNow()
  1821  	// now, create the service
  1822  	resp, err := ts.Client.CreateService(
  1823  		context.Background(), &api.CreateServiceRequest{
  1824  			Spec: spec,
  1825  		},
  1826  	)
  1827  	after := gogotypes.TimestampNow()
  1828  
  1829  	// ensure there are no errors
  1830  	require.NoError(t, err)
  1831  	// and assert that the response is valid
  1832  	require.NotNil(t, resp)
  1833  	require.NotNil(t, resp.Service)
  1834  	// ensure that the service has a JobStatus set
  1835  	require.NotNil(t, resp.Service.JobStatus, "expected JobStatus to not be nil")
  1836  	// and ensure that JobStatus.JobIteration is set to 0, which is the default
  1837  	require.Equal(
  1838  		t, resp.Service.JobStatus.JobIteration.Index, uint64(0),
  1839  		"expected JobIteration for new replicated job to be 0",
  1840  	)
  1841  	require.NotNil(t, resp.Service.JobStatus.LastExecution)
  1842  	assert.True(t, resp.Service.JobStatus.LastExecution.Compare(before) >= 0,
  1843  		"expected %v to be after %v", resp.Service.JobStatus.LastExecution, before,
  1844  	)
  1845  	assert.True(t, resp.Service.JobStatus.LastExecution.Compare(after) <= 0,
  1846  		"expected %v to be before %v", resp.Service.JobStatus.LastExecution, after,
  1847  	)
  1848  
  1849  	// now, repeat all of the above, but with a global job
  1850  	gspec := spec.Copy()
  1851  	gspec.Annotations.Name = "globaljob"
  1852  	gspec.Mode = &api.ServiceSpec_GlobalJob{
  1853  		GlobalJob: &api.GlobalJob{},
  1854  	}
  1855  	before = gogotypes.TimestampNow()
  1856  	gresp, gerr := ts.Client.CreateService(
  1857  		context.Background(), &api.CreateServiceRequest{
  1858  			Spec: gspec,
  1859  		},
  1860  	)
  1861  	after = gogotypes.TimestampNow()
  1862  
  1863  	require.NoError(t, gerr)
  1864  	require.NotNil(t, gresp)
  1865  	require.NotNil(t, gresp.Service)
  1866  	require.NotNil(t, gresp.Service.JobStatus)
  1867  	require.Equal(
  1868  		t, gresp.Service.JobStatus.JobIteration.Index, uint64(0),
  1869  		"expected JobIteration for new global job to be 0",
  1870  	)
  1871  	require.NotNil(t, gresp.Service.JobStatus.LastExecution)
  1872  	assert.True(t, gresp.Service.JobStatus.LastExecution.Compare(before) >= 0,
  1873  		"expected %v to be after %v", gresp.Service.JobStatus.LastExecution, before,
  1874  	)
  1875  	assert.True(t, gresp.Service.JobStatus.LastExecution.Compare(after) <= 0,
  1876  		"expected %v to be before %v", gresp.Service.JobStatus.LastExecution, after,
  1877  	)
  1878  
  1879  	// now test that updating the service increments the JobIteration
  1880  	spec.Task.ForceUpdate = spec.Task.ForceUpdate + 1
  1881  	before = gogotypes.TimestampNow()
  1882  	uresp, uerr := ts.Client.UpdateService(
  1883  		context.Background(), &api.UpdateServiceRequest{
  1884  			ServiceID:      resp.Service.ID,
  1885  			ServiceVersion: &(resp.Service.Meta.Version),
  1886  			Spec:           spec,
  1887  		},
  1888  	)
  1889  	after = gogotypes.TimestampNow()
  1890  
  1891  	require.NoError(t, uerr)
  1892  	require.NotNil(t, uresp)
  1893  	require.NotNil(t, uresp.Service)
  1894  	require.NotNil(t, uresp.Service.JobStatus)
  1895  	// updating the service should bump the JobStatus.JobIteration.Index by 1
  1896  	require.Equal(
  1897  		t, uresp.Service.JobStatus.JobIteration.Index, uint64(1),
  1898  		"expected JobIteration for updated replicated job to be 1",
  1899  	)
  1900  	require.NotNil(t, uresp.Service.JobStatus.LastExecution)
  1901  	assert.True(t, uresp.Service.JobStatus.LastExecution.Compare(before) >= 0,
  1902  		"expected %v to be after %v", uresp.Service.JobStatus.LastExecution, before,
  1903  	)
  1904  	assert.True(t, uresp.Service.JobStatus.LastExecution.Compare(after) <= 0,
  1905  		"expected %v to be before %v", uresp.Service.JobStatus.LastExecution, after,
  1906  	)
  1907  
  1908  	// rinse and repeat
  1909  	gspec.Task.ForceUpdate = spec.Task.ForceUpdate + 1
  1910  	before = gogotypes.TimestampNow()
  1911  	guresp, guerr := ts.Client.UpdateService(
  1912  		context.Background(), &api.UpdateServiceRequest{
  1913  			ServiceID:      gresp.Service.ID,
  1914  			ServiceVersion: &(gresp.Service.Meta.Version),
  1915  			Spec:           gspec,
  1916  		},
  1917  	)
  1918  	after = gogotypes.TimestampNow()
  1919  
  1920  	require.NoError(t, guerr)
  1921  	require.NotNil(t, guresp)
  1922  	require.NotNil(t, guresp.Service)
  1923  	require.NotNil(t, guresp.Service.JobStatus)
  1924  	require.Equal(
  1925  		t, guresp.Service.JobStatus.JobIteration.Index, uint64(1),
  1926  		"expected JobIteration for updated replicated job to be 1",
  1927  	)
  1928  	require.NotNil(t, guresp.Service.JobStatus.LastExecution)
  1929  	assert.True(t, guresp.Service.JobStatus.LastExecution.Compare(before) >= 0,
  1930  		"expected %v to be after %v", guresp.Service.JobStatus.LastExecution, before,
  1931  	)
  1932  	assert.True(t, guresp.Service.JobStatus.LastExecution.Compare(after) <= 0,
  1933  		"expected %v to be before %v", guresp.Service.JobStatus.LastExecution, after,
  1934  	)
  1935  }
  1936  
  1937  // TestServiceValidateJob tests that calling the service API correctly
  1938  // validates a job-mode service. Some fields, like UpdateConfig, are not valid
  1939  // for job-mode services, and should return an error if they're set
  1940  func TestServiceValidateJob(t *testing.T) {
  1941  	bad := &api.ServiceSpec{
  1942  		Mode:   &api.ServiceSpec_ReplicatedJob{ReplicatedJob: &api.ReplicatedJob{}},
  1943  		Update: &api.UpdateConfig{},
  1944  	}
  1945  
  1946  	err := validateJob(bad)
  1947  	assert.Error(t, err)
  1948  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err))
  1949  
  1950  	good := []*api.ServiceSpec{
  1951  		{
  1952  			Mode: &api.ServiceSpec_ReplicatedJob{
  1953  				ReplicatedJob: &api.ReplicatedJob{
  1954  					MaxConcurrent: 3, TotalCompletions: 9,
  1955  				},
  1956  			},
  1957  		},
  1958  		{Mode: &api.ServiceSpec_GlobalJob{}},
  1959  	}
  1960  
  1961  	for _, g := range good {
  1962  		err := validateJob(g)
  1963  		assert.NoError(t, err)
  1964  	}
  1965  }