github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/api/server/router/volume/volume_routes_test.go (about) 1 package volume 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/http/httptest" 9 "testing" 10 11 "gotest.tools/v3/assert" 12 13 "github.com/docker/docker/api/server/httputils" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/filters" 16 "github.com/docker/docker/api/types/volume" 17 "github.com/docker/docker/errdefs" 18 "github.com/docker/docker/volume/service/opts" 19 ) 20 21 func callGetVolume(v *volumeRouter, name string) (*httptest.ResponseRecorder, error) { 22 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 23 vars := map[string]string{"name": name} 24 req := httptest.NewRequest("GET", fmt.Sprintf("/volumes/%s", name), nil) 25 resp := httptest.NewRecorder() 26 27 err := v.getVolumeByName(ctx, resp, req, vars) 28 return resp, err 29 } 30 31 func callListVolumes(v *volumeRouter) (*httptest.ResponseRecorder, error) { 32 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 33 vars := map[string]string{} 34 req := httptest.NewRequest("GET", "/volumes", nil) 35 resp := httptest.NewRecorder() 36 37 err := v.getVolumesList(ctx, resp, req, vars) 38 return resp, err 39 } 40 41 func TestGetVolumeByNameNotFoundNoSwarm(t *testing.T) { 42 v := &volumeRouter{ 43 backend: &fakeVolumeBackend{}, 44 cluster: &fakeClusterBackend{}, 45 } 46 47 _, err := callGetVolume(v, "notReal") 48 49 assert.Assert(t, err != nil) 50 assert.Assert(t, errdefs.IsNotFound(err)) 51 } 52 53 func TestGetVolumeByNameNotFoundNotManager(t *testing.T) { 54 v := &volumeRouter{ 55 backend: &fakeVolumeBackend{}, 56 cluster: &fakeClusterBackend{swarm: true}, 57 } 58 59 _, err := callGetVolume(v, "notReal") 60 61 assert.Assert(t, err != nil) 62 assert.Assert(t, errdefs.IsNotFound(err)) 63 } 64 65 func TestGetVolumeByNameNotFound(t *testing.T) { 66 v := &volumeRouter{ 67 backend: &fakeVolumeBackend{}, 68 cluster: &fakeClusterBackend{swarm: true, manager: true}, 69 } 70 71 _, err := callGetVolume(v, "notReal") 72 73 assert.Assert(t, err != nil) 74 assert.Assert(t, errdefs.IsNotFound(err)) 75 } 76 77 func TestGetVolumeByNameFoundRegular(t *testing.T) { 78 v := &volumeRouter{ 79 backend: &fakeVolumeBackend{ 80 volumes: map[string]*volume.Volume{ 81 82 "volume1": { 83 Name: "volume1", 84 }, 85 }, 86 }, 87 cluster: &fakeClusterBackend{swarm: true, manager: true}, 88 } 89 90 _, err := callGetVolume(v, "volume1") 91 assert.NilError(t, err) 92 } 93 94 func TestGetVolumeByNameFoundSwarm(t *testing.T) { 95 v := &volumeRouter{ 96 backend: &fakeVolumeBackend{}, 97 cluster: &fakeClusterBackend{ 98 swarm: true, 99 manager: true, 100 volumes: map[string]*volume.Volume{ 101 "volume1": { 102 Name: "volume1", 103 }, 104 }, 105 }, 106 } 107 108 _, err := callGetVolume(v, "volume1") 109 assert.NilError(t, err) 110 } 111 func TestListVolumes(t *testing.T) { 112 v := &volumeRouter{ 113 backend: &fakeVolumeBackend{ 114 volumes: map[string]*volume.Volume{ 115 "v1": {Name: "v1"}, 116 "v2": {Name: "v2"}, 117 }, 118 }, 119 cluster: &fakeClusterBackend{ 120 swarm: true, 121 manager: true, 122 volumes: map[string]*volume.Volume{ 123 "v3": {Name: "v3"}, 124 "v4": {Name: "v4"}, 125 }, 126 }, 127 } 128 129 resp, err := callListVolumes(v) 130 assert.NilError(t, err) 131 d := json.NewDecoder(resp.Result().Body) 132 respVols := volume.ListResponse{} 133 assert.NilError(t, d.Decode(&respVols)) 134 135 assert.Assert(t, respVols.Volumes != nil) 136 assert.Equal(t, len(respVols.Volumes), 4, "volumes %v", respVols.Volumes) 137 } 138 139 func TestListVolumesNoSwarm(t *testing.T) { 140 v := &volumeRouter{ 141 backend: &fakeVolumeBackend{ 142 volumes: map[string]*volume.Volume{ 143 "v1": {Name: "v1"}, 144 "v2": {Name: "v2"}, 145 }, 146 }, 147 cluster: &fakeClusterBackend{}, 148 } 149 150 _, err := callListVolumes(v) 151 assert.NilError(t, err) 152 } 153 154 func TestListVolumesNoManager(t *testing.T) { 155 v := &volumeRouter{ 156 backend: &fakeVolumeBackend{ 157 volumes: map[string]*volume.Volume{ 158 "v1": {Name: "v1"}, 159 "v2": {Name: "v2"}, 160 }, 161 }, 162 cluster: &fakeClusterBackend{swarm: true}, 163 } 164 165 resp, err := callListVolumes(v) 166 assert.NilError(t, err) 167 168 d := json.NewDecoder(resp.Result().Body) 169 respVols := volume.ListResponse{} 170 assert.NilError(t, d.Decode(&respVols)) 171 172 assert.Equal(t, len(respVols.Volumes), 2) 173 assert.Equal(t, len(respVols.Warnings), 0) 174 } 175 176 func TestCreateRegularVolume(t *testing.T) { 177 b := &fakeVolumeBackend{} 178 c := &fakeClusterBackend{ 179 swarm: true, 180 manager: true, 181 } 182 v := &volumeRouter{ 183 backend: b, 184 cluster: c, 185 } 186 187 volumeCreate := volume.CreateOptions{ 188 Name: "vol1", 189 Driver: "foodriver", 190 } 191 192 buf := bytes.Buffer{} 193 e := json.NewEncoder(&buf) 194 e.Encode(volumeCreate) 195 196 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 197 req := httptest.NewRequest("POST", "/volumes/create", &buf) 198 req.Header.Add("Content-Type", "application/json") 199 200 resp := httptest.NewRecorder() 201 err := v.postVolumesCreate(ctx, resp, req, nil) 202 203 assert.NilError(t, err) 204 205 respVolume := volume.Volume{} 206 207 assert.NilError(t, json.NewDecoder(resp.Result().Body).Decode(&respVolume)) 208 209 assert.Equal(t, respVolume.Name, "vol1") 210 assert.Equal(t, respVolume.Driver, "foodriver") 211 212 assert.Equal(t, 1, len(b.volumes)) 213 assert.Equal(t, 0, len(c.volumes)) 214 } 215 216 func TestCreateSwarmVolumeNoSwarm(t *testing.T) { 217 b := &fakeVolumeBackend{} 218 c := &fakeClusterBackend{} 219 220 v := &volumeRouter{ 221 backend: b, 222 cluster: c, 223 } 224 225 volumeCreate := volume.CreateOptions{ 226 ClusterVolumeSpec: &volume.ClusterVolumeSpec{}, 227 Name: "volCluster", 228 Driver: "someCSI", 229 } 230 231 buf := bytes.Buffer{} 232 json.NewEncoder(&buf).Encode(volumeCreate) 233 234 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 235 req := httptest.NewRequest("POST", "/volumes/create", &buf) 236 req.Header.Add("Content-Type", "application/json") 237 238 resp := httptest.NewRecorder() 239 err := v.postVolumesCreate(ctx, resp, req, nil) 240 241 assert.Assert(t, err != nil) 242 assert.Assert(t, errdefs.IsUnavailable(err)) 243 } 244 245 func TestCreateSwarmVolumeNotManager(t *testing.T) { 246 b := &fakeVolumeBackend{} 247 c := &fakeClusterBackend{swarm: true} 248 249 v := &volumeRouter{ 250 backend: b, 251 cluster: c, 252 } 253 254 volumeCreate := volume.CreateOptions{ 255 ClusterVolumeSpec: &volume.ClusterVolumeSpec{}, 256 Name: "volCluster", 257 Driver: "someCSI", 258 } 259 260 buf := bytes.Buffer{} 261 json.NewEncoder(&buf).Encode(volumeCreate) 262 263 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 264 req := httptest.NewRequest("POST", "/volumes/create", &buf) 265 req.Header.Add("Content-Type", "application/json") 266 267 resp := httptest.NewRecorder() 268 err := v.postVolumesCreate(ctx, resp, req, nil) 269 270 assert.Assert(t, err != nil) 271 assert.Assert(t, errdefs.IsUnavailable(err)) 272 } 273 274 func TestCreateVolumeCluster(t *testing.T) { 275 b := &fakeVolumeBackend{} 276 c := &fakeClusterBackend{ 277 swarm: true, 278 manager: true, 279 } 280 281 v := &volumeRouter{ 282 backend: b, 283 cluster: c, 284 } 285 286 volumeCreate := volume.CreateOptions{ 287 ClusterVolumeSpec: &volume.ClusterVolumeSpec{}, 288 Name: "volCluster", 289 Driver: "someCSI", 290 } 291 292 buf := bytes.Buffer{} 293 json.NewEncoder(&buf).Encode(volumeCreate) 294 295 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 296 req := httptest.NewRequest("POST", "/volumes/create", &buf) 297 req.Header.Add("Content-Type", "application/json") 298 299 resp := httptest.NewRecorder() 300 err := v.postVolumesCreate(ctx, resp, req, nil) 301 302 assert.NilError(t, err) 303 304 respVolume := volume.Volume{} 305 306 assert.NilError(t, json.NewDecoder(resp.Result().Body).Decode(&respVolume)) 307 308 assert.Equal(t, respVolume.Name, "volCluster") 309 assert.Equal(t, respVolume.Driver, "someCSI") 310 311 assert.Equal(t, 0, len(b.volumes)) 312 assert.Equal(t, 1, len(c.volumes)) 313 } 314 315 func TestUpdateVolume(t *testing.T) { 316 b := &fakeVolumeBackend{} 317 c := &fakeClusterBackend{ 318 swarm: true, 319 manager: true, 320 volumes: map[string]*volume.Volume{ 321 "vol1": { 322 Name: "vo1", 323 ClusterVolume: &volume.ClusterVolume{ 324 ID: "vol1", 325 }, 326 }, 327 }, 328 } 329 330 v := &volumeRouter{ 331 backend: b, 332 cluster: c, 333 } 334 335 volumeUpdate := volume.UpdateOptions{ 336 Spec: &volume.ClusterVolumeSpec{}, 337 } 338 339 buf := bytes.Buffer{} 340 json.NewEncoder(&buf).Encode(volumeUpdate) 341 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 342 req := httptest.NewRequest("POST", "/volumes/vol1/update?version=0", &buf) 343 req.Header.Add("Content-Type", "application/json") 344 345 resp := httptest.NewRecorder() 346 347 err := v.putVolumesUpdate(ctx, resp, req, map[string]string{"name": "vol1"}) 348 assert.NilError(t, err) 349 350 assert.Equal(t, c.volumes["vol1"].ClusterVolume.Meta.Version.Index, uint64(1)) 351 } 352 353 func TestUpdateVolumeNoSwarm(t *testing.T) { 354 b := &fakeVolumeBackend{} 355 c := &fakeClusterBackend{} 356 357 v := &volumeRouter{ 358 backend: b, 359 cluster: c, 360 } 361 362 volumeUpdate := volume.UpdateOptions{ 363 Spec: &volume.ClusterVolumeSpec{}, 364 } 365 366 buf := bytes.Buffer{} 367 json.NewEncoder(&buf).Encode(volumeUpdate) 368 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 369 req := httptest.NewRequest("POST", "/volumes/vol1/update?version=0", &buf) 370 req.Header.Add("Content-Type", "application/json") 371 372 resp := httptest.NewRecorder() 373 374 err := v.putVolumesUpdate(ctx, resp, req, map[string]string{"name": "vol1"}) 375 assert.Assert(t, err != nil) 376 assert.Assert(t, errdefs.IsUnavailable(err)) 377 } 378 379 func TestUpdateVolumeNotFound(t *testing.T) { 380 b := &fakeVolumeBackend{} 381 c := &fakeClusterBackend{ 382 swarm: true, 383 manager: true, 384 volumes: map[string]*volume.Volume{}, 385 } 386 387 v := &volumeRouter{ 388 backend: b, 389 cluster: c, 390 } 391 392 volumeUpdate := volume.UpdateOptions{ 393 Spec: &volume.ClusterVolumeSpec{}, 394 } 395 396 buf := bytes.Buffer{} 397 json.NewEncoder(&buf).Encode(volumeUpdate) 398 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 399 req := httptest.NewRequest("POST", "/volumes/vol1/update?version=0", &buf) 400 req.Header.Add("Content-Type", "application/json") 401 402 resp := httptest.NewRecorder() 403 404 err := v.putVolumesUpdate(ctx, resp, req, map[string]string{"name": "vol1"}) 405 assert.Assert(t, err != nil) 406 assert.Assert(t, errdefs.IsNotFound(err)) 407 } 408 409 func TestVolumeRemove(t *testing.T) { 410 b := &fakeVolumeBackend{ 411 volumes: map[string]*volume.Volume{ 412 "vol1": { 413 Name: "vol1", 414 }, 415 }, 416 } 417 c := &fakeClusterBackend{swarm: true, manager: true} 418 419 v := &volumeRouter{ 420 backend: b, 421 cluster: c, 422 } 423 424 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 425 req := httptest.NewRequest("DELETE", "/volumes/vol1", nil) 426 resp := httptest.NewRecorder() 427 428 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 429 assert.NilError(t, err) 430 assert.Equal(t, len(b.volumes), 0) 431 } 432 433 func TestVolumeRemoveSwarm(t *testing.T) { 434 b := &fakeVolumeBackend{} 435 c := &fakeClusterBackend{ 436 swarm: true, 437 manager: true, 438 volumes: map[string]*volume.Volume{ 439 "vol1": { 440 Name: "vol1", 441 ClusterVolume: &volume.ClusterVolume{}, 442 }, 443 }, 444 } 445 446 v := &volumeRouter{ 447 backend: b, 448 cluster: c, 449 } 450 451 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 452 req := httptest.NewRequest("DELETE", "/volumes/vol1", nil) 453 resp := httptest.NewRecorder() 454 455 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 456 assert.NilError(t, err) 457 assert.Equal(t, len(c.volumes), 0) 458 } 459 460 func TestVolumeRemoveNotFoundNoSwarm(t *testing.T) { 461 b := &fakeVolumeBackend{} 462 c := &fakeClusterBackend{} 463 v := &volumeRouter{ 464 backend: b, 465 cluster: c, 466 } 467 468 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 469 req := httptest.NewRequest("DELETE", "/volumes/vol1", nil) 470 resp := httptest.NewRecorder() 471 472 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 473 assert.Assert(t, err != nil) 474 assert.Assert(t, errdefs.IsNotFound(err), err.Error()) 475 } 476 477 func TestVolumeRemoveNotFoundNoManager(t *testing.T) { 478 b := &fakeVolumeBackend{} 479 c := &fakeClusterBackend{swarm: true} 480 v := &volumeRouter{ 481 backend: b, 482 cluster: c, 483 } 484 485 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 486 req := httptest.NewRequest("DELETE", "/volumes/vol1", nil) 487 resp := httptest.NewRecorder() 488 489 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 490 assert.Assert(t, err != nil) 491 assert.Assert(t, errdefs.IsNotFound(err)) 492 } 493 494 func TestVolumeRemoveFoundNoSwarm(t *testing.T) { 495 b := &fakeVolumeBackend{ 496 volumes: map[string]*volume.Volume{ 497 "vol1": { 498 Name: "vol1", 499 }, 500 }, 501 } 502 c := &fakeClusterBackend{} 503 504 v := &volumeRouter{ 505 backend: b, 506 cluster: c, 507 } 508 509 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 510 req := httptest.NewRequest("DELETE", "/volumes/vol1", nil) 511 resp := httptest.NewRecorder() 512 513 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 514 assert.NilError(t, err) 515 assert.Equal(t, len(b.volumes), 0) 516 } 517 518 func TestVolumeRemoveNoSwarmInUse(t *testing.T) { 519 b := &fakeVolumeBackend{ 520 volumes: map[string]*volume.Volume{ 521 "inuse": { 522 Name: "inuse", 523 }, 524 }, 525 } 526 c := &fakeClusterBackend{} 527 v := &volumeRouter{ 528 backend: b, 529 cluster: c, 530 } 531 532 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 533 req := httptest.NewRequest("DELETE", "/volumes/inuse", nil) 534 resp := httptest.NewRecorder() 535 536 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "inuse"}) 537 assert.Assert(t, err != nil) 538 assert.Assert(t, errdefs.IsConflict(err)) 539 } 540 541 func TestVolumeRemoveSwarmForce(t *testing.T) { 542 b := &fakeVolumeBackend{} 543 c := &fakeClusterBackend{ 544 swarm: true, 545 manager: true, 546 volumes: map[string]*volume.Volume{ 547 "vol1": { 548 Name: "vol1", 549 ClusterVolume: &volume.ClusterVolume{}, 550 Options: map[string]string{"mustforce": "yes"}, 551 }, 552 }, 553 } 554 555 v := &volumeRouter{ 556 backend: b, 557 cluster: c, 558 } 559 560 ctx := context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 561 req := httptest.NewRequest("DELETE", "/volumes/vol1", nil) 562 resp := httptest.NewRecorder() 563 564 err := v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 565 566 assert.Assert(t, err != nil) 567 assert.Assert(t, errdefs.IsConflict(err)) 568 569 ctx = context.WithValue(context.Background(), httputils.APIVersionKey{}, clusterVolumesVersion) 570 req = httptest.NewRequest("DELETE", "/volumes/vol1?force=1", nil) 571 resp = httptest.NewRecorder() 572 573 err = v.deleteVolumes(ctx, resp, req, map[string]string{"name": "vol1"}) 574 575 assert.NilError(t, err) 576 assert.Equal(t, len(b.volumes), 0) 577 assert.Equal(t, len(c.volumes), 0) 578 } 579 580 type fakeVolumeBackend struct { 581 volumes map[string]*volume.Volume 582 } 583 584 func (b *fakeVolumeBackend) List(_ context.Context, _ filters.Args) ([]*volume.Volume, []string, error) { 585 volumes := []*volume.Volume{} 586 for _, v := range b.volumes { 587 volumes = append(volumes, v) 588 } 589 return volumes, nil, nil 590 } 591 592 func (b *fakeVolumeBackend) Get(_ context.Context, name string, _ ...opts.GetOption) (*volume.Volume, error) { 593 if v, ok := b.volumes[name]; ok { 594 return v, nil 595 } 596 return nil, errdefs.NotFound(fmt.Errorf("volume %s not found", name)) 597 } 598 599 func (b *fakeVolumeBackend) Create(_ context.Context, name, driverName string, _ ...opts.CreateOption) (*volume.Volume, error) { 600 if _, ok := b.volumes[name]; ok { 601 // TODO(dperny): return appropriate error type 602 return nil, fmt.Errorf("already exists") 603 } 604 605 v := &volume.Volume{ 606 Name: name, 607 Driver: driverName, 608 } 609 if b.volumes == nil { 610 b.volumes = map[string]*volume.Volume{ 611 name: v, 612 } 613 } else { 614 b.volumes[name] = v 615 } 616 617 return v, nil 618 } 619 620 func (b *fakeVolumeBackend) Remove(_ context.Context, name string, o ...opts.RemoveOption) error { 621 removeOpts := &opts.RemoveConfig{} 622 for _, opt := range o { 623 opt(removeOpts) 624 } 625 626 if v, ok := b.volumes[name]; !ok { 627 if !removeOpts.PurgeOnError { 628 return errdefs.NotFound(fmt.Errorf("volume %s not found", name)) 629 } 630 } else if v.Name == "inuse" { 631 return errdefs.Conflict(fmt.Errorf("volume in use")) 632 } 633 634 delete(b.volumes, name) 635 636 return nil 637 } 638 639 func (b *fakeVolumeBackend) Prune(_ context.Context, _ filters.Args) (*types.VolumesPruneReport, error) { 640 return nil, nil 641 } 642 643 type fakeClusterBackend struct { 644 swarm bool 645 manager bool 646 idCount int 647 volumes map[string]*volume.Volume 648 } 649 650 func (c *fakeClusterBackend) checkSwarm() error { 651 if !c.swarm { 652 return errdefs.Unavailable(fmt.Errorf("this node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again")) 653 } else if !c.manager { 654 return errdefs.Unavailable(fmt.Errorf("this node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager")) 655 } 656 657 return nil 658 } 659 660 func (c *fakeClusterBackend) IsManager() bool { 661 return c.swarm && c.manager 662 } 663 664 func (c *fakeClusterBackend) GetVolume(nameOrID string) (volume.Volume, error) { 665 if err := c.checkSwarm(); err != nil { 666 return volume.Volume{}, err 667 } 668 669 if v, ok := c.volumes[nameOrID]; ok { 670 return *v, nil 671 } 672 return volume.Volume{}, errdefs.NotFound(fmt.Errorf("volume %s not found", nameOrID)) 673 } 674 675 func (c *fakeClusterBackend) GetVolumes(options volume.ListOptions) ([]*volume.Volume, error) { 676 if err := c.checkSwarm(); err != nil { 677 return nil, err 678 } 679 680 volumes := []*volume.Volume{} 681 682 for _, v := range c.volumes { 683 volumes = append(volumes, v) 684 } 685 return volumes, nil 686 } 687 688 func (c *fakeClusterBackend) CreateVolume(volumeCreate volume.CreateOptions) (*volume.Volume, error) { 689 if err := c.checkSwarm(); err != nil { 690 return nil, err 691 } 692 693 if _, ok := c.volumes[volumeCreate.Name]; ok { 694 // TODO(dperny): return appropriate already exists error 695 return nil, fmt.Errorf("already exists") 696 } 697 698 v := &volume.Volume{ 699 Name: volumeCreate.Name, 700 Driver: volumeCreate.Driver, 701 Labels: volumeCreate.Labels, 702 Options: volumeCreate.DriverOpts, 703 Scope: "global", 704 } 705 706 v.ClusterVolume = &volume.ClusterVolume{ 707 ID: fmt.Sprintf("cluster_%d", c.idCount), 708 Spec: *volumeCreate.ClusterVolumeSpec, 709 } 710 711 c.idCount = c.idCount + 1 712 if c.volumes == nil { 713 c.volumes = map[string]*volume.Volume{ 714 v.Name: v, 715 } 716 } else { 717 c.volumes[v.Name] = v 718 } 719 720 return v, nil 721 } 722 723 func (c *fakeClusterBackend) RemoveVolume(nameOrID string, force bool) error { 724 if err := c.checkSwarm(); err != nil { 725 return err 726 } 727 728 v, ok := c.volumes[nameOrID] 729 if !ok { 730 return errdefs.NotFound(fmt.Errorf("volume %s not found", nameOrID)) 731 } 732 733 if _, mustforce := v.Options["mustforce"]; mustforce && !force { 734 return errdefs.Conflict(fmt.Errorf("volume %s must be force removed", nameOrID)) 735 } 736 737 delete(c.volumes, nameOrID) 738 739 return nil 740 } 741 742 func (c *fakeClusterBackend) UpdateVolume(nameOrID string, version uint64, _ volume.UpdateOptions) error { 743 if err := c.checkSwarm(); err != nil { 744 return err 745 } 746 747 if v, ok := c.volumes[nameOrID]; ok { 748 if v.ClusterVolume.Meta.Version.Index != version { 749 return fmt.Errorf("wrong version") 750 } 751 v.ClusterVolume.Meta.Version.Index = v.ClusterVolume.Meta.Version.Index + 1 752 // for testing, we don't actually need to change anything about the 753 // volume object. let's just increment the version so we can see the 754 // call happened. 755 } else { 756 return errdefs.NotFound(fmt.Errorf("volume %q not found", nameOrID)) 757 } 758 759 return nil 760 }