github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/cinder.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package openstack 5 6 import ( 7 "fmt" 8 "math" 9 "net/url" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/schema" 15 "github.com/juju/utils" 16 "gopkg.in/goose.v2/cinder" 17 gooseerrors "gopkg.in/goose.v2/errors" 18 "gopkg.in/goose.v2/identity" 19 "gopkg.in/goose.v2/nova" 20 21 "github.com/juju/juju/core/instance" 22 "github.com/juju/juju/environs/context" 23 "github.com/juju/juju/environs/tags" 24 "github.com/juju/juju/provider/common" 25 "github.com/juju/juju/storage" 26 ) 27 28 const ( 29 CinderProviderType = storage.ProviderType("cinder") 30 31 cinderVolumeType = "volume-type" 32 33 // autoAssignedMountPoint specifies the value to pass in when 34 // you'd like Cinder to automatically assign a mount point. 35 autoAssignedMountPoint = "" 36 37 volumeStatusAvailable = "available" 38 volumeStatusDeleting = "deleting" 39 volumeStatusError = "error" 40 volumeStatusInUse = "in-use" 41 ) 42 43 var cinderConfigFields = schema.Fields{ 44 cinderVolumeType: schema.String(), 45 } 46 47 var cinderConfigChecker = schema.FieldMap( 48 cinderConfigFields, 49 schema.Defaults{ 50 cinderVolumeType: schema.Omit, 51 }, 52 ) 53 54 type cinderConfig struct { 55 volumeType string 56 } 57 58 func newCinderConfig(attrs map[string]interface{}) (*cinderConfig, error) { 59 out, err := cinderConfigChecker.Coerce(attrs, nil) 60 if err != nil { 61 return nil, errors.Annotate(err, "validating Cinder storage config") 62 } 63 coerced := out.(map[string]interface{}) 64 volumeType, _ := coerced[cinderVolumeType].(string) 65 cinderConfig := &cinderConfig{ 66 volumeType: volumeType, 67 } 68 return cinderConfig, nil 69 } 70 71 // StorageProviderTypes implements storage.ProviderRegistry. 72 func (env *Environ) StorageProviderTypes() ([]storage.ProviderType, error) { 73 var types []storage.ProviderType 74 if _, err := env.cinderProvider(); err == nil { 75 types = append(types, CinderProviderType) 76 } else if !errors.IsNotSupported(err) { 77 return nil, errors.Trace(err) 78 } 79 return types, nil 80 } 81 82 // StorageProvider implements storage.ProviderRegistry. 83 func (env *Environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) { 84 if t != CinderProviderType { 85 return nil, errors.NotFoundf("storage provider %q", t) 86 } 87 return env.cinderProvider() 88 } 89 90 func (env *Environ) cinderProvider() (*cinderProvider, error) { 91 storageAdapter, err := newOpenstackStorage(env) 92 if err != nil { 93 return nil, errors.Trace(err) 94 } 95 return &cinderProvider{ 96 storageAdapter: storageAdapter, 97 envName: env.name, 98 modelUUID: env.uuid, 99 namespace: env.namespace, 100 }, nil 101 } 102 103 var newOpenstackStorage = func(env *Environ) (OpenstackStorage, error) { 104 env.ecfgMutex.Lock() 105 defer env.ecfgMutex.Unlock() 106 107 client := env.clientUnlocked 108 if env.volumeURL == nil { 109 url, err := getVolumeEndpointURL(client, env.cloud.Region) 110 if errors.IsNotFound(err) { 111 // No volume endpoint found; Cinder is not supported. 112 return nil, errors.NotSupportedf("volumes") 113 } else if err != nil { 114 return nil, errors.Trace(err) 115 } 116 env.volumeURL = url 117 logger.Debugf("volume URL: %v", url) 118 } 119 120 cinderCl := cinderClient{cinder.Basic(env.volumeURL, client.TenantId(), client.Token)} 121 122 cloudSpec := env.cloud 123 if len(cloudSpec.CACertificates) > 0 { 124 cinderCl = cinderClient{cinder.BasicTLSConfig( 125 env.volumeURL, 126 client.TenantId(), 127 client.Token, 128 tlsConfig(cloudSpec.CACertificates)), 129 } 130 } 131 132 return &openstackStorageAdapter{ 133 cinderCl, 134 novaClient{env.novaUnlocked}, 135 }, nil 136 } 137 138 type cinderProvider struct { 139 storageAdapter OpenstackStorage 140 envName string 141 modelUUID string 142 namespace instance.Namespace 143 } 144 145 var _ storage.Provider = (*cinderProvider)(nil) 146 147 var cinderAttempt = utils.AttemptStrategy{ 148 Total: 1 * time.Minute, 149 Delay: 5 * time.Second, 150 } 151 152 // VolumeSource implements storage.Provider. 153 func (p *cinderProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) { 154 if err := p.ValidateConfig(providerConfig); err != nil { 155 return nil, err 156 } 157 source := &cinderVolumeSource{ 158 storageAdapter: p.storageAdapter, 159 envName: p.envName, 160 modelUUID: p.modelUUID, 161 namespace: p.namespace, 162 } 163 return source, nil 164 } 165 166 // FilesystemSource implements storage.Provider. 167 func (p *cinderProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) { 168 return nil, errors.NotSupportedf("filesystems") 169 } 170 171 // Supports implements storage.Provider. 172 func (p *cinderProvider) Supports(kind storage.StorageKind) bool { 173 switch kind { 174 case storage.StorageKindBlock: 175 return true 176 } 177 return false 178 } 179 180 // Scope implements storage.Provider. 181 func (s *cinderProvider) Scope() storage.Scope { 182 return storage.ScopeEnviron 183 } 184 185 // ValidateConfig implements storage.Provider. 186 func (p *cinderProvider) ValidateConfig(cfg *storage.Config) error { 187 // TODO(axw) 2015-05-01 #1450737 188 // Reject attempts to create non-persistent volumes. 189 _, err := newCinderConfig(cfg.Attrs()) 190 return errors.Trace(err) 191 } 192 193 // Dynamic implements storage.Provider. 194 func (p *cinderProvider) Dynamic() bool { 195 return true 196 } 197 198 // Releasable is defined on the Provider interface. 199 func (*cinderProvider) Releasable() bool { 200 return true 201 } 202 203 // DefaultPools implements storage.Provider. 204 func (p *cinderProvider) DefaultPools() []*storage.Config { 205 return nil 206 } 207 208 type cinderVolumeSource struct { 209 storageAdapter OpenstackStorage 210 envName string // non unique, informational only 211 modelUUID string 212 namespace instance.Namespace 213 } 214 215 var _ storage.VolumeSource = (*cinderVolumeSource)(nil) 216 217 // CreateVolumes implements storage.VolumeSource. 218 func (s *cinderVolumeSource) CreateVolumes(ctx context.ProviderCallContext, args []storage.VolumeParams) ([]storage.CreateVolumesResult, error) { 219 results := make([]storage.CreateVolumesResult, len(args)) 220 for i, arg := range args { 221 volume, err := s.createVolume(arg) 222 if err != nil { 223 results[i].Error = errors.Trace(err) 224 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 225 // If it is an unauthorised error, no need to continue since we will 100% fail... 226 break 227 } 228 continue 229 } 230 results[i].Volume = volume 231 } 232 return results, nil 233 } 234 235 func (s *cinderVolumeSource) createVolume(arg storage.VolumeParams) (*storage.Volume, error) { 236 cinderConfig, err := newCinderConfig(arg.Attributes) 237 if err != nil { 238 return nil, errors.Trace(err) 239 } 240 241 var metadata interface{} 242 if len(arg.ResourceTags) > 0 { 243 metadata = arg.ResourceTags 244 } 245 cinderVolume, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 246 // The Cinder documentation incorrectly states the 247 // size parameter is in GB. It is actually GiB. 248 Size: int(math.Ceil(float64(arg.Size / 1024))), 249 Name: resourceName(s.namespace, s.envName, arg.Tag.String()), 250 VolumeType: cinderConfig.volumeType, 251 // TODO(axw) use the AZ of the initially attached machine. 252 AvailabilityZone: "", 253 Metadata: metadata, 254 }) 255 if err != nil { 256 return nil, errors.Trace(err) 257 } 258 259 // The response may (will?) come back before the volume transitions to 260 // "creating", in which case it will not have a size or status. Wait for 261 // the volume to transition, so we can record its actual size. 262 volumeId := cinderVolume.ID 263 cinderVolume, err = waitVolume(s.storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) { 264 return v.Status != "", nil 265 }) 266 if err != nil { 267 if err := s.storageAdapter.DeleteVolume(volumeId); err != nil { 268 logger.Warningf("destroying volume %s: %s", volumeId, err) 269 } 270 return nil, errors.Errorf("waiting for volume to be provisioned: %s", err) 271 } 272 logger.Debugf("created volume: %+v", cinderVolume) 273 return &storage.Volume{arg.Tag, cinderToJujuVolumeInfo(cinderVolume)}, nil 274 } 275 276 // ListVolumes is specified on the storage.VolumeSource interface. 277 func (s *cinderVolumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) { 278 cinderVolumes, err := modelCinderVolumes(s.storageAdapter, s.modelUUID) 279 if err != nil { 280 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 281 return nil, errors.Trace(err) 282 } 283 return volumeInfoToVolumeIds(cinderToJujuVolumeInfos(cinderVolumes)), nil 284 } 285 286 // modelCinderVolumes returns all of the cinder volumes for the model. 287 func modelCinderVolumes(storageAdapter OpenstackStorage, modelUUID string) ([]cinder.Volume, error) { 288 return cinderVolumes(storageAdapter, func(v *cinder.Volume) bool { 289 return v.Metadata[tags.JujuModel] == modelUUID 290 }) 291 } 292 293 // controllerCinderVolumes returns all of the cinder volumes for the model. 294 func controllerCinderVolumes(storageAdapter OpenstackStorage, controllerUUID string) ([]cinder.Volume, error) { 295 return cinderVolumes(storageAdapter, func(v *cinder.Volume) bool { 296 return v.Metadata[tags.JujuController] == controllerUUID 297 }) 298 } 299 300 // cinderVolumes returns all of the cinder volumes matching the given predicate. 301 func cinderVolumes(storageAdapter OpenstackStorage, pred func(*cinder.Volume) bool) ([]cinder.Volume, error) { 302 allCinderVolumes, err := storageAdapter.GetVolumesDetail() 303 if err != nil { 304 return nil, err 305 } 306 var matching []cinder.Volume 307 for _, v := range allCinderVolumes { 308 if pred(&v) { 309 matching = append(matching, v) 310 } 311 } 312 return matching, nil 313 } 314 315 func volumeInfoToVolumeIds(volumes []storage.VolumeInfo) []string { 316 volumeIds := make([]string, len(volumes)) 317 for i, volume := range volumes { 318 volumeIds[i] = volume.VolumeId 319 } 320 return volumeIds 321 } 322 323 // DescribeVolumes implements storage.VolumeSource. 324 func (s *cinderVolumeSource) DescribeVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]storage.DescribeVolumesResult, error) { 325 // In most cases, it is quicker to get all volumes and loop 326 // locally than to make several round-trips to the provider. 327 cinderVolumes, err := s.storageAdapter.GetVolumesDetail() 328 if err != nil { 329 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 330 return nil, errors.Trace(err) 331 } 332 volumesById := make(map[string]*cinder.Volume) 333 for i, volume := range cinderVolumes { 334 volumesById[volume.ID] = &cinderVolumes[i] 335 } 336 results := make([]storage.DescribeVolumesResult, len(volumeIds)) 337 for i, volumeId := range volumeIds { 338 cinderVolume, ok := volumesById[volumeId] 339 if !ok { 340 results[i].Error = errors.NotFoundf("volume %q", volumeId) 341 continue 342 } 343 info := cinderToJujuVolumeInfo(cinderVolume) 344 results[i].VolumeInfo = &info 345 } 346 return results, nil 347 } 348 349 // DestroyVolumes implements storage.VolumeSource. 350 func (s *cinderVolumeSource) DestroyVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { 351 return foreachVolume(ctx, s.storageAdapter, volumeIds, destroyVolume), nil 352 } 353 354 // ReleaseVolumes implements storage.VolumeSource. 355 func (s *cinderVolumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volumeIds []string) ([]error, error) { 356 return foreachVolume(ctx, s.storageAdapter, volumeIds, releaseVolume), nil 357 } 358 359 func foreachVolume(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, volumeIds []string, f func(context.ProviderCallContext, OpenstackStorage, string) error) []error { 360 var wg sync.WaitGroup 361 wg.Add(len(volumeIds)) 362 results := make([]error, len(volumeIds)) 363 for i, volumeId := range volumeIds { 364 go func(i int, volumeId string) { 365 defer wg.Done() 366 results[i] = f(ctx, storageAdapter, volumeId) 367 }(i, volumeId) 368 } 369 wg.Wait() 370 return results 371 } 372 373 func destroyVolume(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, volumeId string) error { 374 logger.Debugf("destroying volume %q", volumeId) 375 // Volumes must not be in-use when destroying. A volume may 376 // still be in-use when the instance it is attached to is 377 // in the process of being terminated. 378 var issuedDetach bool 379 volume, err := waitVolume(storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) { 380 switch v.Status { 381 default: 382 // Not ready for deletion; keep waiting. 383 return false, nil 384 case volumeStatusAvailable, volumeStatusDeleting, volumeStatusError: 385 return true, nil 386 case volumeStatusInUse: 387 // Detach below. 388 break 389 } 390 // Volume is still attached, so detach it. 391 if !issuedDetach { 392 args := make([]storage.VolumeAttachmentParams, len(v.Attachments)) 393 for i, a := range v.Attachments { 394 args[i].VolumeId = volumeId 395 args[i].InstanceId = instance.Id(a.ServerId) 396 } 397 if len(args) > 0 { 398 results := detachVolumes(ctx, storageAdapter, args) 399 for _, err := range results { 400 if err != nil { 401 return false, errors.Trace(err) 402 } 403 } 404 } 405 issuedDetach = true 406 } 407 return false, nil 408 }) 409 if err != nil { 410 if errors.IsNotFound(err) { 411 // The volume wasn't found; nothing 412 // to destroy, so we're done. 413 return nil 414 } 415 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 416 return errors.Trace(err) 417 } 418 if volume.Status == volumeStatusDeleting { 419 // Already being deleted, nothing to do. 420 return nil 421 } 422 if err := storageAdapter.DeleteVolume(volumeId); err != nil { 423 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 424 return errors.Trace(err) 425 } 426 return nil 427 } 428 429 func releaseVolume(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, volumeId string) error { 430 logger.Debugf("releasing volume %q", volumeId) 431 _, err := waitVolume(storageAdapter, volumeId, func(v *cinder.Volume) (bool, error) { 432 switch v.Status { 433 case volumeStatusAvailable, volumeStatusError: 434 return true, nil 435 case volumeStatusDeleting: 436 return false, errors.New("volume is being deleted") 437 case volumeStatusInUse: 438 return false, errors.New("volume still in-use") 439 } 440 // Not ready for releasing; keep waiting. 441 return false, nil 442 }) 443 if err != nil { 444 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 445 return errors.Annotatef(err, "cannot release volume %q", volumeId) 446 } 447 // Drop the model and controller tags from the volume. 448 tags := map[string]string{ 449 tags.JujuModel: "", 450 tags.JujuController: "", 451 } 452 _, err = storageAdapter.SetVolumeMetadata(volumeId, tags) 453 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 454 return errors.Annotate(err, "tagging volume") 455 } 456 457 // ValidateVolumeParams implements storage.VolumeSource. 458 func (s *cinderVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 459 _, err := newCinderConfig(params.Attributes) 460 return errors.Trace(err) 461 } 462 463 // AttachVolumes implements storage.VolumeSource. 464 func (s *cinderVolumeSource) AttachVolumes(ctx context.ProviderCallContext, args []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 465 results := make([]storage.AttachVolumesResult, len(args)) 466 for i, arg := range args { 467 attachment, err := s.attachVolume(arg) 468 if err != nil { 469 results[i].Error = errors.Trace(err) 470 if denial := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denial { 471 // We do not want to continue here as we'll 100% fail if we got unauthorised error. 472 break 473 } 474 continue 475 } 476 results[i].VolumeAttachment = attachment 477 } 478 return results, nil 479 } 480 481 func (s *cinderVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (*storage.VolumeAttachment, error) { 482 // Check to see if the volume is already attached. 483 existingAttachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId)) 484 if err != nil { 485 return nil, err 486 } 487 novaAttachment := findAttachment(arg.VolumeId, existingAttachments) 488 if novaAttachment == nil { 489 // A volume must be "available" before it can be attached. 490 if _, err := waitVolume(s.storageAdapter, arg.VolumeId, func(v *cinder.Volume) (bool, error) { 491 return v.Status == "available", nil 492 }); err != nil { 493 return nil, errors.Annotate(err, "waiting for volume to become available") 494 } 495 novaAttachment, err = s.storageAdapter.AttachVolume( 496 string(arg.InstanceId), 497 arg.VolumeId, 498 autoAssignedMountPoint, 499 ) 500 if err != nil { 501 return nil, err 502 } 503 } 504 if novaAttachment.Device == nil { 505 return nil, errors.Errorf("device not assigned to volume attachment") 506 } 507 return &storage.VolumeAttachment{ 508 arg.Volume, 509 arg.Machine, 510 storage.VolumeAttachmentInfo{ 511 DeviceName: (*novaAttachment.Device)[len("/dev/"):], 512 }, 513 }, nil 514 } 515 516 // ImportVolume is part of the storage.VolumeImporter interface. 517 func (s *cinderVolumeSource) ImportVolume(ctx context.ProviderCallContext, volumeId string, resourceTags map[string]string) (storage.VolumeInfo, error) { 518 volume, err := s.storageAdapter.GetVolume(volumeId) 519 if err != nil { 520 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 521 return storage.VolumeInfo{}, errors.Annotate(err, "getting volume") 522 } 523 if volume.Status != volumeStatusAvailable { 524 return storage.VolumeInfo{}, errors.Errorf( 525 "cannot import volume %q with status %q", volumeId, volume.Status, 526 ) 527 } 528 if _, err := s.storageAdapter.SetVolumeMetadata(volumeId, resourceTags); err != nil { 529 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 530 return storage.VolumeInfo{}, errors.Annotatef(err, "tagging volume %q", volumeId) 531 } 532 return cinderToJujuVolumeInfo(volume), nil 533 } 534 535 func waitVolume( 536 storageAdapter OpenstackStorage, 537 volumeId string, 538 pred func(*cinder.Volume) (bool, error), 539 ) (*cinder.Volume, error) { 540 for a := cinderAttempt.Start(); a.Next(); { 541 volume, err := storageAdapter.GetVolume(volumeId) 542 if err != nil { 543 return nil, errors.Annotate(err, "getting volume") 544 } 545 ok, err := pred(volume) 546 if err != nil { 547 return nil, errors.Trace(err) 548 } 549 if ok { 550 return volume, nil 551 } 552 } 553 return nil, errors.New("timed out") 554 } 555 556 // DetachVolumes implements storage.VolumeSource. 557 func (s *cinderVolumeSource) DetachVolumes(ctx context.ProviderCallContext, args []storage.VolumeAttachmentParams) ([]error, error) { 558 return detachVolumes(ctx, s.storageAdapter, args), nil 559 } 560 561 func detachVolumes(ctx context.ProviderCallContext, storageAdapter OpenstackStorage, args []storage.VolumeAttachmentParams) []error { 562 results := make([]error, len(args)) 563 for i, arg := range args { 564 if err := detachVolume( 565 string(arg.InstanceId), 566 arg.VolumeId, 567 storageAdapter, 568 ); err != nil { 569 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 570 results[i] = errors.Annotatef( 571 err, "detaching volume %s from server %s", 572 arg.VolumeId, arg.InstanceId, 573 ) 574 continue 575 } 576 } 577 return results 578 } 579 580 func cinderToJujuVolumeInfos(volumes []cinder.Volume) []storage.VolumeInfo { 581 out := make([]storage.VolumeInfo, len(volumes)) 582 for i, v := range volumes { 583 out[i] = cinderToJujuVolumeInfo(&v) 584 } 585 return out 586 } 587 588 func cinderToJujuVolumeInfo(volume *cinder.Volume) storage.VolumeInfo { 589 return storage.VolumeInfo{ 590 VolumeId: volume.ID, 591 Size: uint64(volume.Size * 1024), 592 Persistent: true, 593 } 594 } 595 596 func detachVolume(instanceId, volumeId string, storageAdapter OpenstackStorage) error { 597 err := storageAdapter.DetachVolume(instanceId, volumeId) 598 if err != nil && !errors.IsNotFound(err) { 599 return errors.Trace(err) 600 } 601 // The volume was successfully detached, or was 602 // already detached (i.e. NotFound error case). 603 return nil 604 } 605 606 func findAttachment(volId string, attachments []nova.VolumeAttachment) *nova.VolumeAttachment { 607 for _, attachment := range attachments { 608 if attachment.VolumeId == volId { 609 return &attachment 610 } 611 } 612 return nil 613 } 614 615 type OpenstackStorage interface { 616 GetVolume(volumeId string) (*cinder.Volume, error) 617 GetVolumesDetail() ([]cinder.Volume, error) 618 DeleteVolume(volumeId string) error 619 CreateVolume(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) 620 AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error) 621 DetachVolume(serverId, attachmentId string) error 622 ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error) 623 SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error) 624 } 625 626 type endpointResolver interface { 627 Authenticate() error 628 IsAuthenticated() bool 629 EndpointsForRegion(region string) identity.ServiceURLs 630 } 631 632 func getVolumeEndpointURL(client endpointResolver, region string) (*url.URL, error) { 633 if !client.IsAuthenticated() { 634 if err := authenticateClient(client); err != nil { 635 return nil, errors.Trace(err) 636 } 637 } 638 endpointMap := client.EndpointsForRegion(region) 639 // The cinder openstack charm appends 'v2' to the type for the v2 api. 640 endpoint, ok := endpointMap["volumev2"] 641 if !ok { 642 logger.Debugf(`endpoint "volumev2" not found for %q region, trying "volume"`, region) 643 endpoint, ok = endpointMap["volume"] 644 if !ok { 645 return nil, errors.NotFoundf(`endpoint "volume" in region %q`, region) 646 } 647 } 648 return url.Parse(endpoint) 649 } 650 651 type openstackStorageAdapter struct { 652 cinderClient 653 novaClient 654 } 655 656 type cinderClient struct { 657 *cinder.Client 658 } 659 660 type novaClient struct { 661 *nova.Client 662 } 663 664 // CreateVolume is part of the OpenstackStorage interface. 665 func (ga *openstackStorageAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 666 resp, err := ga.cinderClient.CreateVolume(args) 667 if err != nil { 668 return nil, err 669 } 670 return &resp.Volume, nil 671 } 672 673 // GetVolumesDetail is part of the OpenstackStorage interface. 674 func (ga *openstackStorageAdapter) GetVolumesDetail() ([]cinder.Volume, error) { 675 resp, err := ga.cinderClient.GetVolumesDetail() 676 if err != nil { 677 return nil, err 678 } 679 return resp.Volumes, nil 680 } 681 682 // GetVolume is part of the OpenstackStorage interface. 683 func (ga *openstackStorageAdapter) GetVolume(volumeId string) (*cinder.Volume, error) { 684 resp, err := ga.cinderClient.GetVolume(volumeId) 685 if err != nil { 686 if gooseerrors.IsNotFound(err) { 687 return nil, errors.NotFoundf("volume %q", volumeId) 688 } 689 return nil, err 690 } 691 return &resp.Volume, nil 692 } 693 694 // SetVolumeMetadata is part of the OpenstackStorage interface. 695 func (ga *openstackStorageAdapter) SetVolumeMetadata(volumeId string, metadata map[string]string) (map[string]string, error) { 696 return ga.cinderClient.SetVolumeMetadata(volumeId, metadata) 697 } 698 699 // DeleteVolume is part of the OpenstackStorage interface. 700 func (ga *openstackStorageAdapter) DeleteVolume(volumeId string) error { 701 if err := ga.cinderClient.DeleteVolume(volumeId); err != nil { 702 if gooseerrors.IsNotFound(err) { 703 return errors.NotFoundf("volume %q", volumeId) 704 } 705 return err 706 } 707 return nil 708 } 709 710 // DetachVolume is part of the OpenstackStorage interface. 711 func (ga *openstackStorageAdapter) DetachVolume(serverId, attachmentId string) error { 712 if err := ga.novaClient.DetachVolume(serverId, attachmentId); err != nil { 713 if gooseerrors.IsNotFound(err) { 714 return errors.NewNotFound(nil, 715 fmt.Sprintf("volume %q is not attached to server %q", 716 attachmentId, serverId, 717 ), 718 ) 719 } 720 return err 721 } 722 return nil 723 }