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 }