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

     1  package controlapi
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/docker/swarmkit/api"
    10  	"github.com/docker/swarmkit/manager/state/store"
    11  	"github.com/docker/swarmkit/testutils"
    12  	"github.com/stretchr/testify/assert"
    13  	"google.golang.org/grpc/codes"
    14  )
    15  
    16  func createConfigSpec(name string, data []byte, labels map[string]string) *api.ConfigSpec {
    17  	return &api.ConfigSpec{
    18  		Annotations: api.Annotations{Name: name, Labels: labels},
    19  		Data:        data,
    20  	}
    21  }
    22  
    23  func TestValidateConfigSpec(t *testing.T) {
    24  	type BadServiceSpec struct {
    25  		spec *api.ServiceSpec
    26  		c    codes.Code
    27  	}
    28  
    29  	for _, badName := range []string{
    30  		"",
    31  		".",
    32  		"-",
    33  		"_",
    34  		".name",
    35  		"name.",
    36  		"-name",
    37  		"name-",
    38  		"_name",
    39  		"name_",
    40  		"/a",
    41  		"a/",
    42  		"a/b",
    43  		"..",
    44  		"../a",
    45  		"a/..",
    46  		"withexclamation!",
    47  		"with space",
    48  		"with\nnewline",
    49  		"with@splat",
    50  		"with:colon",
    51  		"with;semicolon",
    52  		"snowman☃",
    53  		strings.Repeat("a", 65),
    54  	} {
    55  		err := validateConfigSpec(createConfigSpec(badName, []byte("valid config"), nil))
    56  		assert.Error(t, err)
    57  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
    58  	}
    59  
    60  	for _, badSpec := range []*api.ConfigSpec{
    61  		nil,
    62  		createConfigSpec("validName", nil, nil),
    63  	} {
    64  		err := validateConfigSpec(badSpec)
    65  		assert.Error(t, err)
    66  		assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
    67  	}
    68  
    69  	for _, goodName := range []string{
    70  		"0",
    71  		"a",
    72  		"A",
    73  		"name-with--dashes",
    74  		"name.with..dots",
    75  		"name_with__underscores",
    76  		"name.with-all_special",
    77  		"02624name035with1699numbers015125",
    78  		strings.Repeat("a", 64),
    79  	} {
    80  		err := validateConfigSpec(createConfigSpec(goodName, []byte("valid config"), nil))
    81  		assert.NoError(t, err)
    82  	}
    83  
    84  	for _, good := range []*api.ConfigSpec{
    85  		createConfigSpec("validName", []byte("☃\n\t\r\x00 dg09236l;kajdgaj5%#9836[Q@!$]"), nil),
    86  		createConfigSpec("validName", []byte("valid config"), nil),
    87  		createConfigSpec("createName", make([]byte, 1), nil), // 1 byte
    88  	} {
    89  		err := validateConfigSpec(good)
    90  		assert.NoError(t, err)
    91  	}
    92  }
    93  
    94  func TestCreateConfig(t *testing.T) {
    95  	ts := newTestServer(t)
    96  	defer ts.Stop()
    97  
    98  	// ---- creating a config with an invalid spec fails, thus checking that CreateConfig validates the spec ----
    99  	_, err := ts.Client.CreateConfig(context.Background(), &api.CreateConfigRequest{Spec: createConfigSpec("", nil, nil)})
   100  	assert.Error(t, err)
   101  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   102  
   103  	// ---- creating a config with a valid spec succeeds, and returns a config that reflects the config in the store
   104  	// exactly
   105  	data := []byte("config")
   106  	creationSpec := createConfigSpec("name", data, nil)
   107  	validSpecRequest := api.CreateConfigRequest{Spec: creationSpec}
   108  
   109  	resp, err := ts.Client.CreateConfig(context.Background(), &validSpecRequest)
   110  	assert.NoError(t, err)
   111  	assert.NotNil(t, resp)
   112  	assert.NotNil(t, resp.Config)
   113  	assert.Equal(t, *creationSpec, resp.Config.Spec)
   114  
   115  	// for sanity, check that the stored config still has the config data
   116  	var storedConfig *api.Config
   117  	ts.Store.View(func(tx store.ReadTx) {
   118  		storedConfig = store.GetConfig(tx, resp.Config.ID)
   119  	})
   120  	assert.NotNil(t, storedConfig)
   121  	assert.Equal(t, data, storedConfig.Spec.Data)
   122  
   123  	// ---- creating a config with the same name, even if it's the exact same spec, fails due to a name conflict ----
   124  	_, err = ts.Client.CreateConfig(context.Background(), &validSpecRequest)
   125  	assert.Error(t, err)
   126  	assert.Equal(t, codes.AlreadyExists, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   127  }
   128  
   129  func TestGetConfig(t *testing.T) {
   130  	ts := newTestServer(t)
   131  	defer ts.Stop()
   132  
   133  	// ---- getting a config without providing an ID results in an InvalidArgument ----
   134  	_, err := ts.Client.GetConfig(context.Background(), &api.GetConfigRequest{})
   135  	assert.Error(t, err)
   136  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   137  
   138  	// ---- getting a non-existent config fails with NotFound ----
   139  	_, err = ts.Client.GetConfig(context.Background(), &api.GetConfigRequest{ConfigID: "12345"})
   140  	assert.Error(t, err)
   141  	assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   142  
   143  	// ---- getting an existing config returns the config ----
   144  	config := configFromConfigSpec(createConfigSpec("name", []byte("data"), nil))
   145  	err = ts.Store.Update(func(tx store.Tx) error {
   146  		return store.CreateConfig(tx, config)
   147  	})
   148  	assert.NoError(t, err)
   149  
   150  	resp, err := ts.Client.GetConfig(context.Background(), &api.GetConfigRequest{ConfigID: config.ID})
   151  	assert.NoError(t, err)
   152  	assert.NotNil(t, resp)
   153  	assert.NotNil(t, resp.Config)
   154  	assert.Equal(t, config, resp.Config)
   155  }
   156  
   157  func TestUpdateConfig(t *testing.T) {
   158  	ts := newTestServer(t)
   159  	defer ts.Stop()
   160  
   161  	// Add a config to the store to update
   162  	config := configFromConfigSpec(createConfigSpec("name", []byte("data"), map[string]string{"mod2": "0", "mod4": "0"}))
   163  	err := ts.Store.Update(func(tx store.Tx) error {
   164  		return store.CreateConfig(tx, config)
   165  	})
   166  	assert.NoError(t, err)
   167  
   168  	// updating a config without providing an ID results in an InvalidArgument
   169  	_, err = ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{})
   170  	assert.Error(t, err)
   171  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   172  
   173  	// getting a non-existent config fails with NotFound
   174  	_, err = ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{ConfigID: "1234adsaa", ConfigVersion: &api.Version{Index: 1}})
   175  	assert.Error(t, err)
   176  	assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   177  
   178  	// updating an existing config's data returns an error
   179  	config.Spec.Data = []byte{1}
   180  	resp, err := ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{
   181  		ConfigID:      config.ID,
   182  		Spec:          &config.Spec,
   183  		ConfigVersion: &config.Meta.Version,
   184  	})
   185  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   186  
   187  	// updating an existing config's Name returns an error
   188  	config.Spec.Data = nil
   189  	config.Spec.Annotations.Name = "AnotherName"
   190  	resp, err = ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{
   191  		ConfigID:      config.ID,
   192  		Spec:          &config.Spec,
   193  		ConfigVersion: &config.Meta.Version,
   194  	})
   195  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   196  
   197  	// updating the config with the original spec succeeds
   198  	config.Spec.Data = []byte("data")
   199  	config.Spec.Annotations.Name = "name"
   200  	assert.NotNil(t, config.Spec.Data)
   201  	resp, err = ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{
   202  		ConfigID:      config.ID,
   203  		Spec:          &config.Spec,
   204  		ConfigVersion: &config.Meta.Version,
   205  	})
   206  	assert.NoError(t, err)
   207  	assert.NotNil(t, resp)
   208  	assert.NotNil(t, resp.Config)
   209  
   210  	// updating an existing config's labels returns the config
   211  	newLabels := map[string]string{"mod2": "0", "mod4": "0", "mod6": "0"}
   212  	config.Spec.Annotations.Labels = newLabels
   213  	config.Spec.Data = nil
   214  	resp, err = ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{
   215  		ConfigID:      config.ID,
   216  		Spec:          &config.Spec,
   217  		ConfigVersion: &resp.Config.Meta.Version,
   218  	})
   219  	assert.NoError(t, err)
   220  	assert.NotNil(t, resp)
   221  	assert.NotNil(t, resp.Config)
   222  	assert.Equal(t, []byte("data"), resp.Config.Spec.Data)
   223  	assert.Equal(t, resp.Config.Spec.Annotations.Labels, newLabels)
   224  
   225  	// updating a config with nil data and correct name succeeds again
   226  	config.Spec.Data = nil
   227  	config.Spec.Annotations.Name = "name"
   228  	resp, err = ts.Client.UpdateConfig(context.Background(), &api.UpdateConfigRequest{
   229  		ConfigID:      config.ID,
   230  		Spec:          &config.Spec,
   231  		ConfigVersion: &resp.Config.Meta.Version,
   232  	})
   233  	assert.NoError(t, err)
   234  	assert.NotNil(t, resp)
   235  	assert.NotNil(t, resp.Config)
   236  	assert.Equal(t, []byte("data"), resp.Config.Spec.Data)
   237  	assert.Equal(t, resp.Config.Spec.Annotations.Labels, newLabels)
   238  }
   239  
   240  func TestRemoveUnusedConfig(t *testing.T) {
   241  	ts := newTestServer(t)
   242  	defer ts.Stop()
   243  
   244  	// removing a config without providing an ID results in an InvalidArgument
   245  	_, err := ts.Client.RemoveConfig(context.Background(), &api.RemoveConfigRequest{})
   246  	assert.Error(t, err)
   247  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   248  
   249  	// removing a config that exists succeeds
   250  	config := configFromConfigSpec(createConfigSpec("name", []byte("data"), nil))
   251  	err = ts.Store.Update(func(tx store.Tx) error {
   252  		return store.CreateConfig(tx, config)
   253  	})
   254  	assert.NoError(t, err)
   255  
   256  	resp, err := ts.Client.RemoveConfig(context.Background(), &api.RemoveConfigRequest{ConfigID: config.ID})
   257  	assert.NoError(t, err)
   258  	assert.Equal(t, api.RemoveConfigResponse{}, *resp)
   259  
   260  	// ---- it was really removed because attempting to remove it again fails with a NotFound ----
   261  	_, err = ts.Client.RemoveConfig(context.Background(), &api.RemoveConfigRequest{ConfigID: config.ID})
   262  	assert.Error(t, err)
   263  	assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   264  
   265  }
   266  
   267  func TestRemoveUsedConfig(t *testing.T) {
   268  	ts := newTestServer(t)
   269  	defer ts.Stop()
   270  
   271  	// Create two configs
   272  	data := []byte("config")
   273  	creationSpec := createConfigSpec("configID1", data, nil)
   274  	resp, err := ts.Client.CreateConfig(context.Background(), &api.CreateConfigRequest{Spec: creationSpec})
   275  	assert.NoError(t, err)
   276  	creationSpec2 := createConfigSpec("configID2", data, nil)
   277  	resp2, err := ts.Client.CreateConfig(context.Background(), &api.CreateConfigRequest{Spec: creationSpec2})
   278  	assert.NoError(t, err)
   279  
   280  	// Create a service that uses a config
   281  	service := createSpec("service1", "image", 1)
   282  	configRefs := []*api.ConfigReference{
   283  		{
   284  			ConfigName: resp.Config.Spec.Annotations.Name,
   285  			ConfigID:   resp.Config.ID,
   286  			Target: &api.ConfigReference_File{
   287  				File: &api.FileTarget{
   288  					Name: "target.txt",
   289  				},
   290  			},
   291  		},
   292  	}
   293  	service.Task.GetContainer().Configs = configRefs
   294  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: service})
   295  	assert.NoError(t, err)
   296  
   297  	service2 := createSpec("service2", "image", 1)
   298  	service2.Task.GetContainer().Configs = configRefs
   299  	_, err = ts.Client.CreateService(context.Background(), &api.CreateServiceRequest{Spec: service2})
   300  	assert.NoError(t, err)
   301  
   302  	// removing a config that exists but is in use fails
   303  	_, err = ts.Client.RemoveConfig(context.Background(), &api.RemoveConfigRequest{ConfigID: resp.Config.ID})
   304  	assert.Equal(t, codes.InvalidArgument, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   305  	assert.Regexp(t, "service[1-2], service[1-2]", testutils.ErrorDesc(err))
   306  
   307  	// removing a config that exists but is not in use succeeds
   308  	_, err = ts.Client.RemoveConfig(context.Background(), &api.RemoveConfigRequest{ConfigID: resp2.Config.ID})
   309  	assert.NoError(t, err)
   310  
   311  	// it was really removed because attempting to remove it again fails with a NotFound
   312  	_, err = ts.Client.RemoveConfig(context.Background(), &api.RemoveConfigRequest{ConfigID: resp2.Config.ID})
   313  	assert.Error(t, err)
   314  	assert.Equal(t, codes.NotFound, testutils.ErrorCode(err), testutils.ErrorDesc(err))
   315  }
   316  
   317  func TestListConfigs(t *testing.T) {
   318  	s := newTestServer(t)
   319  
   320  	listConfigs := func(req *api.ListConfigsRequest) map[string]*api.Config {
   321  		resp, err := s.Client.ListConfigs(context.Background(), req)
   322  		assert.NoError(t, err)
   323  		assert.NotNil(t, resp)
   324  
   325  		byName := make(map[string]*api.Config)
   326  		for _, config := range resp.Configs {
   327  			byName[config.Spec.Annotations.Name] = config
   328  		}
   329  		return byName
   330  	}
   331  
   332  	// ---- Listing configs when there are no configs returns an empty list but no error ----
   333  	result := listConfigs(&api.ListConfigsRequest{})
   334  	assert.Len(t, result, 0)
   335  
   336  	// ---- Create a bunch of configs in the store so we can test filtering ----
   337  	allListableNames := []string{"aaa", "aab", "abc", "bbb", "bac", "bbc", "ccc", "cac", "cbc", "ddd"}
   338  	configNamesToID := make(map[string]string)
   339  	for i, configName := range allListableNames {
   340  		config := configFromConfigSpec(createConfigSpec(configName, []byte("config"), map[string]string{
   341  			"mod2": fmt.Sprintf("%d", i%2),
   342  			"mod4": fmt.Sprintf("%d", i%4),
   343  		}))
   344  		err := s.Store.Update(func(tx store.Tx) error {
   345  			return store.CreateConfig(tx, config)
   346  		})
   347  		assert.NoError(t, err)
   348  		configNamesToID[configName] = config.ID
   349  	}
   350  
   351  	// ---- build up our list of expectations for what configs get filtered ----
   352  
   353  	type listTestCase struct {
   354  		desc     string
   355  		expected []string
   356  		filter   *api.ListConfigsRequest_Filters
   357  	}
   358  
   359  	listConfigTestCases := []listTestCase{
   360  		{
   361  			desc:     "no filter: all the available configs are returned",
   362  			expected: allListableNames,
   363  			filter:   nil,
   364  		},
   365  		{
   366  			desc:     "searching for something that doesn't match returns an empty list",
   367  			expected: nil,
   368  			filter:   &api.ListConfigsRequest_Filters{Names: []string{"aa"}},
   369  		},
   370  		{
   371  			desc:     "multiple name filters are or-ed together",
   372  			expected: []string{"aaa", "bbb", "ccc"},
   373  			filter:   &api.ListConfigsRequest_Filters{Names: []string{"aaa", "bbb", "ccc"}},
   374  		},
   375  		{
   376  			desc:     "multiple name prefix filters are or-ed together",
   377  			expected: []string{"aaa", "aab", "bbb", "bbc"},
   378  			filter:   &api.ListConfigsRequest_Filters{NamePrefixes: []string{"aa", "bb"}},
   379  		},
   380  		{
   381  			desc:     "multiple ID prefix filters are or-ed together",
   382  			expected: []string{"aaa", "bbb"},
   383  			filter: &api.ListConfigsRequest_Filters{IDPrefixes: []string{
   384  				configNamesToID["aaa"], configNamesToID["bbb"]},
   385  			},
   386  		},
   387  		{
   388  			desc:     "name prefix, name, and ID prefix filters are or-ed together",
   389  			expected: []string{"aaa", "aab", "bbb", "bbc", "ccc", "ddd"},
   390  			filter: &api.ListConfigsRequest_Filters{
   391  				Names:        []string{"aaa", "ccc"},
   392  				NamePrefixes: []string{"aa", "bb"},
   393  				IDPrefixes:   []string{configNamesToID["aaa"], configNamesToID["ddd"]},
   394  			},
   395  		},
   396  		{
   397  			desc:     "all labels in the label map must be matched",
   398  			expected: []string{allListableNames[0], allListableNames[4], allListableNames[8]},
   399  			filter: &api.ListConfigsRequest_Filters{
   400  				Labels: map[string]string{
   401  					"mod2": "0",
   402  					"mod4": "0",
   403  				},
   404  			},
   405  		},
   406  		{
   407  			desc: "name prefix, name, and ID prefix filters are or-ed together, but the results must match all labels in the label map",
   408  			// + indicates that these would be selected with the name/id/prefix filtering, and 0/1 at the end indicate the mod2 value:
   409  			// +"aaa"0, +"aab"1, "abc"0, +"bbb"1, "bac"0, +"bbc"1, +"ccc"0, "cac"1, "cbc"0, +"ddd"1
   410  			expected: []string{"aaa", "ccc"},
   411  			filter: &api.ListConfigsRequest_Filters{
   412  				Names:        []string{"aaa", "ccc"},
   413  				NamePrefixes: []string{"aa", "bb"},
   414  				IDPrefixes:   []string{configNamesToID["aaa"], configNamesToID["ddd"]},
   415  				Labels: map[string]string{
   416  					"mod2": "0",
   417  				},
   418  			},
   419  		},
   420  	}
   421  
   422  	// ---- run the filter tests ----
   423  
   424  	for _, expectation := range listConfigTestCases {
   425  		result := listConfigs(&api.ListConfigsRequest{Filters: expectation.filter})
   426  		assert.Len(t, result, len(expectation.expected), expectation.desc)
   427  		for _, name := range expectation.expected {
   428  			assert.Contains(t, result, name, expectation.desc)
   429  			assert.NotNil(t, result[name], expectation.desc)
   430  			assert.Equal(t, configNamesToID[name], result[name].ID, expectation.desc)
   431  			assert.NotNil(t, result[name].Spec.Data)
   432  		}
   433  	}
   434  }