github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 "math" 8 "net/url" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/utils" 13 "gopkg.in/goose.v1/cinder" 14 "gopkg.in/goose.v1/nova" 15 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/storage" 18 ) 19 20 const ( 21 CinderProviderType = storage.ProviderType("cinder") 22 // autoAssignedMountPoint specifies the value to pass in when 23 // you'd like Cinder to automatically assign a mount point. 24 autoAssignedMountPoint = "" 25 ) 26 27 type cinderProvider struct { 28 newStorageAdapter func(*config.Config) (openstackStorage, error) 29 } 30 31 var _ storage.Provider = (*cinderProvider)(nil) 32 33 var cinderAttempt = utils.AttemptStrategy{ 34 Total: 1 * time.Minute, 35 Delay: 5 * time.Second, 36 } 37 38 // VolumeSource implements storage.Provider. 39 func (p *cinderProvider) VolumeSource(environConfig *config.Config, providerConfig *storage.Config) (storage.VolumeSource, error) { 40 if err := p.ValidateConfig(providerConfig); err != nil { 41 return nil, err 42 } 43 storageAdapter, err := p.newStorageAdapter(environConfig) 44 if err != nil { 45 return nil, err 46 } 47 source := &cinderVolumeSource{ 48 storageAdapter: storageAdapter, 49 envName: environConfig.Name(), 50 } 51 return source, nil 52 } 53 54 // FilesystemSource implements storage.Provider. 55 func (p *cinderProvider) FilesystemSource(environConfig *config.Config, providerConfig *storage.Config) (storage.FilesystemSource, error) { 56 return nil, errors.NotSupportedf("filesystems") 57 } 58 59 // Supports implements storage.Provider. 60 func (p *cinderProvider) Supports(kind storage.StorageKind) bool { 61 switch kind { 62 case storage.StorageKindBlock: 63 return true 64 } 65 return false 66 } 67 68 // Scope implements storage.Provider. 69 func (s *cinderProvider) Scope() storage.Scope { 70 return storage.ScopeEnviron 71 } 72 73 // ValidateConfig implements storage.Provider. 74 func (p *cinderProvider) ValidateConfig(cfg *storage.Config) error { 75 // TODO(axw) 2015-05-01 #1450737 76 // Reject attempts to create non-persistent volumes. 77 return nil 78 } 79 80 // Dynamic implements storage.Provider. 81 func (p *cinderProvider) Dynamic() bool { 82 return true 83 } 84 85 type cinderVolumeSource struct { 86 storageAdapter openstackStorage 87 envName string // non unique, informational only 88 } 89 90 var _ storage.VolumeSource = (*cinderVolumeSource)(nil) 91 92 // CreateVolumes implements storage.VolumeSource. 93 func (s *cinderVolumeSource) CreateVolumes(args []storage.VolumeParams) (_ []storage.Volume, _ []storage.VolumeAttachment, resultErr error) { 94 volumes := make([]storage.Volume, len(args)) 95 for i, arg := range args { 96 volume, err := s.createVolume(arg) 97 if err != nil { 98 return nil, nil, err 99 } 100 volumes[i] = volume 101 102 // If the method exits with an error, be sure to delete any 103 // created volumes so that we're idempotent. We create several 104 // clousures instead of one to take advantage of the loop 105 // parameter. 106 defer func(arg storage.VolumeParams, volume storage.Volume) { 107 if resultErr == nil { 108 return 109 } 110 attachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.Attachment.InstanceId)) 111 if err != nil { 112 logger.Warningf("could not list volumes while cleaning up: %v", err) 113 return 114 } 115 if err := detachVolume( 116 string(arg.Attachment.InstanceId), 117 volume.VolumeId, 118 attachments, 119 s.storageAdapter, 120 ); err != nil { 121 logger.Warningf("could not detach volumes while cleaning up: %v", err) 122 return 123 } 124 if err := s.storageAdapter.DeleteVolume(volume.VolumeId); err != nil { 125 logger.Warningf("could not delete volumes while cleaning up: %v", err) 126 } 127 }(arg, volume) 128 } 129 130 attachmentParams := make([]storage.VolumeAttachmentParams, len(volumes)) 131 for i, volume := range volumes { 132 attachmentParams[i] = *args[i].Attachment 133 attachmentParams[i].VolumeId = volume.VolumeId 134 attachmentParams[i].Volume = volume.Tag 135 } 136 attachments, err := s.AttachVolumes(attachmentParams) 137 if err != nil { 138 return nil, nil, errors.Trace(err) 139 } 140 141 return volumes, attachments, nil 142 } 143 144 func (s *cinderVolumeSource) createVolume(arg storage.VolumeParams) (storage.Volume, error) { 145 if b, ok := arg.Attributes[storage.Persistent]; ok && !b.(bool) { 146 return storage.Volume{}, errors.New("cannot create a non-persistent Cinder volume") 147 } 148 var metadata interface{} 149 if len(arg.ResourceTags) > 0 { 150 metadata = arg.ResourceTags 151 } 152 cinderVolume, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 153 // The Cinder documentation incorrectly states the 154 // size parameter is in GB. It is actually GiB. 155 Size: int(math.Ceil(float64(arg.Size / 1024))), 156 Name: resourceName(arg.Tag, s.envName), 157 // TODO(axw) use the AZ of the initially attached machine. 158 AvailabilityZone: "", 159 Metadata: metadata, 160 }) 161 if err != nil { 162 return storage.Volume{}, errors.Trace(err) 163 } 164 165 // The response may (will?) come back before the volume transitions to, 166 // "creating", in which case it will not have a size or status. Wait for 167 // the volume to transition, so we can record its actual size. 168 cinderVolume, err = s.waitVolume(cinderVolume.ID, func(v *cinder.Volume) (bool, error) { 169 return v.Status != "", nil 170 }) 171 if err != nil { 172 if err := s.DestroyVolumes([]string{cinderVolume.ID}); err != nil { 173 logger.Warningf("destroying volume %s: %s", cinderVolume.ID, err) 174 } 175 return storage.Volume{}, errors.Errorf("waiting for volume to be provisioned: %s", err) 176 } 177 logger.Debugf("created volume: %+v", cinderVolume) 178 return storage.Volume{arg.Tag, cinderToJujuVolumeInfo(cinderVolume)}, nil 179 } 180 181 // DescribeVolumes implements storage.VolumeSource. 182 func (s *cinderVolumeSource) DescribeVolumes(volumeIds []string) ([]storage.VolumeInfo, error) { 183 // In most cases, it is quicker to get all volumes and loop 184 // locally than to make several round-trips to the provider. 185 cinderVolumes, err := s.storageAdapter.GetVolumesSimple() 186 if err != nil { 187 return nil, err 188 } 189 volumesById := make(map[string]*cinder.Volume) 190 for i, volume := range cinderVolumes { 191 volumesById[volume.ID] = &cinderVolumes[i] 192 } 193 volumes := make([]storage.VolumeInfo, len(volumeIds)) 194 for i, volumeId := range volumeIds { 195 cinderVolume, ok := volumesById[volumeId] 196 if !ok { 197 return nil, errors.NotFoundf("volume %q", volumeId) 198 } 199 volumes[i] = cinderToJujuVolumeInfo(cinderVolume) 200 } 201 return volumes, nil 202 } 203 204 // DestroyVolumes implements storage.VolumeSource. 205 func (s *cinderVolumeSource) DestroyVolumes(volumeIds []string) []error { 206 errors := make([]error, len(volumeIds)) 207 for i, volumeId := range volumeIds { 208 if err := s.storageAdapter.DeleteVolume(volumeId); err != nil { 209 errors[i] = err 210 } 211 } 212 return errors 213 } 214 215 // ValidateVolumeParams implements storage.VolumeSource. 216 func (s *cinderVolumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 217 return nil 218 } 219 220 // AttachVolumes implements storage.VolumeSource. 221 func (s *cinderVolumeSource) AttachVolumes(args []storage.VolumeAttachmentParams) ([]storage.VolumeAttachment, error) { 222 attachments := make([]storage.VolumeAttachment, len(args)) 223 for i, arg := range args { 224 attachment, err := s.attachVolume(arg) 225 if err != nil { 226 return nil, errors.Trace(err) 227 } 228 attachments[i] = attachment 229 } 230 return attachments, nil 231 } 232 233 func (s *cinderVolumeSource) attachVolume(arg storage.VolumeAttachmentParams) (storage.VolumeAttachment, error) { 234 // Check to see if the volume is already attached. 235 existingAttachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId)) 236 if err != nil { 237 return storage.VolumeAttachment{}, err 238 } 239 novaAttachment := findAttachment(arg.VolumeId, existingAttachments) 240 if novaAttachment == nil { 241 // A volume must be "available" before it can be attached. 242 if _, err := s.waitVolume(arg.VolumeId, func(v *cinder.Volume) (bool, error) { 243 return v.Status == "available", nil 244 }); err != nil { 245 return storage.VolumeAttachment{}, errors.Annotate(err, "waiting for volume to become available") 246 } 247 novaAttachment, err = s.storageAdapter.AttachVolume( 248 string(arg.InstanceId), 249 arg.VolumeId, 250 autoAssignedMountPoint, 251 ) 252 if err != nil { 253 return storage.VolumeAttachment{}, err 254 } 255 } 256 return storage.VolumeAttachment{ 257 arg.Volume, 258 arg.Machine, 259 storage.VolumeAttachmentInfo{ 260 DeviceName: novaAttachment.Device[len("/dev/"):], 261 }, 262 }, nil 263 } 264 265 func (s *cinderVolumeSource) waitVolume( 266 volumeId string, 267 pred func(*cinder.Volume) (bool, error), 268 ) (*cinder.Volume, error) { 269 for a := cinderAttempt.Start(); a.Next(); { 270 volume, err := s.storageAdapter.GetVolume(volumeId) 271 if err != nil { 272 return nil, errors.Annotate(err, "getting volume") 273 } 274 ok, err := pred(volume) 275 if err != nil { 276 return nil, errors.Trace(err) 277 } 278 if ok { 279 return volume, nil 280 } 281 } 282 return nil, errors.New("timed out") 283 } 284 285 // DetachVolumes implements storage.VolumeSource. 286 func (s *cinderVolumeSource) DetachVolumes(args []storage.VolumeAttachmentParams) error { 287 for _, arg := range args { 288 // Check to see if the volume is already detached. 289 attachments, err := s.storageAdapter.ListVolumeAttachments(string(arg.InstanceId)) 290 if err != nil { 291 return err 292 } 293 if err := detachVolume( 294 string(arg.InstanceId), 295 arg.VolumeId, 296 attachments, 297 s.storageAdapter, 298 ); err != nil { 299 return err 300 } 301 } 302 return nil 303 } 304 305 func cinderToJujuVolumeInfo(volume *cinder.Volume) storage.VolumeInfo { 306 return storage.VolumeInfo{ 307 VolumeId: volume.ID, 308 Size: uint64(volume.Size * 1024), 309 // TODO(axw) there is currently no way to mark a volume as 310 // "delete on termination", so all volumes are persistent. 311 Persistent: true, 312 } 313 } 314 315 func detachVolume(instanceId, volumeId string, attachments []nova.VolumeAttachment, storageAdapter openstackStorage) error { 316 // TODO(axw) verify whether we need to do this find step. From looking at the example 317 // responses in the OpenStack docs, the "attachment ID" is always the same as the 318 // volume ID. So we should just be able to issue a blind detach request, and then 319 // ignore errors that indicate the volume is already detached. 320 if findAttachment(volumeId, attachments) == nil { 321 return nil 322 } 323 return storageAdapter.DetachVolume(instanceId, volumeId) 324 } 325 326 func findAttachment(volId string, attachments []nova.VolumeAttachment) *nova.VolumeAttachment { 327 for _, attachment := range attachments { 328 if attachment.VolumeId == volId { 329 return &attachment 330 } 331 } 332 return nil 333 } 334 335 type openstackStorage interface { 336 GetVolume(volumeId string) (*cinder.Volume, error) 337 GetVolumesSimple() ([]cinder.Volume, error) 338 DeleteVolume(volumeId string) error 339 CreateVolume(cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) 340 AttachVolume(serverId, volumeId, mountPoint string) (*nova.VolumeAttachment, error) 341 DetachVolume(serverId, attachmentId string) error 342 ListVolumeAttachments(serverId string) ([]nova.VolumeAttachment, error) 343 } 344 345 func newOpenstackStorageAdapter(environConfig *config.Config) (openstackStorage, error) { 346 ecfg, err := providerInstance.newConfig(environConfig) 347 if err != nil { 348 return nil, errors.Trace(err) 349 } 350 authClient := authClient(ecfg) 351 if err := authClient.Authenticate(); err != nil { 352 return nil, errors.Trace(err) 353 } 354 355 endpoint := authClient.EndpointsForRegion(ecfg.region())["volume"] 356 endpointUrl, err := url.Parse(endpoint) 357 if err != nil { 358 return nil, errors.Annotate(err, "error parsing endpoint") 359 } 360 361 return &openstackStorageAdapter{ 362 cinderClient{cinder.Basic(endpointUrl, authClient.TenantId(), authClient.Token)}, 363 novaClient{nova.New(authClient)}, 364 }, nil 365 } 366 367 type openstackStorageAdapter struct { 368 cinderClient 369 novaClient 370 } 371 372 type cinderClient struct { 373 *cinder.Client 374 } 375 376 type novaClient struct { 377 *nova.Client 378 } 379 380 // CreateVolume is part of the openstackStorage interface. 381 func (ga *openstackStorageAdapter) CreateVolume(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 382 resp, err := ga.cinderClient.CreateVolume(args) 383 if err != nil { 384 return nil, err 385 } 386 return &resp.Volume, nil 387 } 388 389 // GetVolumesSimple is part of the openstackStorage interface. 390 func (ga *openstackStorageAdapter) GetVolumesSimple() ([]cinder.Volume, error) { 391 resp, err := ga.cinderClient.GetVolumesSimple() 392 if err != nil { 393 return nil, err 394 } 395 return resp.Volumes, nil 396 } 397 398 // GetVolume is part of the openstackStorage interface. 399 func (ga *openstackStorageAdapter) GetVolume(volumeId string) (*cinder.Volume, error) { 400 resp, err := ga.cinderClient.GetVolume(volumeId) 401 if err != nil { 402 return nil, err 403 } 404 return &resp.Volume, nil 405 }