github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/disks.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gce 5 6 import ( 7 "fmt" 8 "strings" 9 "sync" 10 11 "github.com/juju/collections/set" 12 "github.com/juju/errors" 13 "github.com/juju/utils" 14 15 "github.com/juju/juju/environs/context" 16 "github.com/juju/juju/environs/tags" 17 "github.com/juju/juju/provider/gce/google" 18 "github.com/juju/juju/storage" 19 ) 20 21 const ( 22 storageProviderType = storage.ProviderType("gce") 23 ) 24 25 // StorageProviderTypes implements storage.ProviderRegistry. 26 func (env *environ) StorageProviderTypes() ([]storage.ProviderType, error) { 27 return []storage.ProviderType{storageProviderType}, nil 28 } 29 30 // StorageProvider implements storage.ProviderRegistry. 31 func (env *environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) { 32 if t == storageProviderType { 33 return &storageProvider{env}, nil 34 } 35 return nil, errors.NotFoundf("storage provider %q", t) 36 } 37 38 type storageProvider struct { 39 env *environ 40 } 41 42 var _ storage.Provider = (*storageProvider)(nil) 43 44 func (g *storageProvider) ValidateConfig(cfg *storage.Config) error { 45 return nil 46 } 47 48 func (g *storageProvider) Supports(k storage.StorageKind) bool { 49 return k == storage.StorageKindBlock 50 } 51 52 func (g *storageProvider) Scope() storage.Scope { 53 return storage.ScopeEnviron 54 } 55 56 func (g *storageProvider) Dynamic() bool { 57 return true 58 } 59 60 func (e *storageProvider) Releasable() bool { 61 return true 62 } 63 64 func (g *storageProvider) DefaultPools() []*storage.Config { 65 // TODO(perrito666) Add explicit pools. 66 return nil 67 } 68 69 func (g *storageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) { 70 return nil, errors.NotSupportedf("filesystems") 71 } 72 73 type volumeSource struct { 74 gce gceConnection 75 envName string // non-unique, informational only 76 modelUUID string 77 } 78 79 func (g *storageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) { 80 environConfig := g.env.Config() 81 source := &volumeSource{ 82 gce: g.env.gce, 83 envName: environConfig.Name(), 84 modelUUID: environConfig.UUID(), 85 } 86 return source, nil 87 } 88 89 type instanceCache map[string]google.Instance 90 91 func (c instanceCache) update(gceClient gceConnection, ctx context.ProviderCallContext, ids ...string) error { 92 if len(ids) == 1 { 93 if _, ok := c[ids[0]]; ok { 94 return nil 95 } 96 } 97 idMap := make(map[string]int, len(ids)) 98 for _, id := range ids { 99 idMap[id] = 0 100 } 101 instances, err := gceClient.Instances("", google.StatusRunning) 102 if err != nil { 103 return google.HandleCredentialError(errors.Annotate(err, "querying instance details"), ctx) 104 } 105 for _, instance := range instances { 106 if _, ok := idMap[instance.ID]; !ok { 107 continue 108 } 109 c[instance.ID] = instance 110 } 111 return nil 112 } 113 114 func (c instanceCache) get(id string) (google.Instance, error) { 115 inst, ok := c[id] 116 if !ok { 117 return google.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id) 118 } 119 return inst, nil 120 } 121 122 func (v *volumeSource) CreateVolumes(ctx context.ProviderCallContext, params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) { 123 results := make([]storage.CreateVolumesResult, len(params)) 124 instanceIds := set.NewStrings() 125 for i, p := range params { 126 if err := v.ValidateVolumeParams(p); err != nil { 127 results[i].Error = err 128 continue 129 } 130 instanceIds.Add(string(p.Attachment.InstanceId)) 131 } 132 133 instances := make(instanceCache) 134 if instanceIds.Size() > 1 { 135 if err := instances.update(v.gce, ctx, instanceIds.Values()...); err != nil { 136 logger.Debugf("querying running instances: %v", err) 137 // We ignore the error, because we don't want an invalid 138 // InstanceId reference from one VolumeParams to prevent 139 // the creation of another volume. 140 // ... Unless the error is due to an invalid credential, in which case, continuing with this call 141 // is pointless and creates an unnecessary churn: we know all calls will fail with the same error. 142 if google.HasDenialStatusCode(err) { 143 return results, err 144 } 145 } 146 } 147 148 for i, p := range params { 149 if results[i].Error != nil { 150 continue 151 } 152 volume, attachment, err := v.createOneVolume(ctx, p, instances) 153 if err != nil { 154 results[i].Error = err 155 logger.Errorf("could not create one volume (or attach it): %v", err) 156 // ... Unless the error is due to an invalid credential, in which case, continuing with this call 157 // is pointless and creates an unnecessary churn: we know all calls will fail with the same error. 158 if google.HasDenialStatusCode(err) { 159 return results, err 160 } 161 continue 162 } 163 results[i].Volume = volume 164 results[i].VolumeAttachment = attachment 165 } 166 return results, nil 167 } 168 169 // mibToGib converts mebibytes to gibibytes. 170 // GCE expects GiB, we work in MiB; round up 171 // to nearest GiB. 172 func mibToGib(m uint64) uint64 { 173 return (m + 1023) / 1024 174 } 175 176 func nameVolume(zone string) (string, error) { 177 volumeUUID, err := utils.NewUUID() 178 if err != nil { 179 return "", errors.Annotate(err, "cannot generate uuid to name the volume") 180 } 181 // type-zone-uuid 182 volumeName := fmt.Sprintf("%s--%s", zone, volumeUUID.String()) 183 return volumeName, nil 184 } 185 186 func (v *volumeSource) createOneVolume(ctx context.ProviderCallContext, p storage.VolumeParams, instances instanceCache) (volume *storage.Volume, volumeAttachment *storage.VolumeAttachment, err error) { 187 var volumeName, zone string 188 defer func() { 189 if err == nil || volumeName == "" { 190 return 191 } 192 if err := v.gce.RemoveDisk(zone, volumeName); err != nil { 193 logger.Errorf("error cleaning up volume %v: %v", volumeName, google.HandleCredentialError(err, ctx)) 194 } 195 }() 196 197 instId := string(p.Attachment.InstanceId) 198 if err := instances.update(v.gce, ctx, instId); err != nil { 199 return nil, nil, errors.Annotatef(err, "cannot add %q to instance cache", instId) 200 } 201 inst, err := instances.get(instId) 202 if err != nil { 203 // Can't create the volume without the instance, 204 // because we need to know what its AZ is. 205 return nil, nil, errors.Annotatef(err, "cannot obtain %q from instance cache", instId) 206 } 207 persistentType, ok := p.Attributes["type"].(google.DiskType) 208 if !ok { 209 persistentType = google.DiskPersistentStandard 210 } 211 212 zone = inst.ZoneName 213 volumeName, err = nameVolume(zone) 214 if err != nil { 215 return nil, nil, errors.Annotate(err, "cannot create a new volume name") 216 } 217 // TODO(perrito666) the volumeName is arbitrary and it was crafted this 218 // way to help solve the need to have zone all over the place. 219 disk := google.DiskSpec{ 220 SizeHintGB: mibToGib(p.Size), 221 Name: volumeName, 222 PersistentDiskType: persistentType, 223 Labels: resourceTagsToDiskLabels(p.ResourceTags), 224 } 225 226 gceDisks, err := v.gce.CreateDisks(zone, []google.DiskSpec{disk}) 227 if err != nil { 228 return nil, nil, google.HandleCredentialError(errors.Annotate(err, "cannot create disk"), ctx) 229 } 230 if len(gceDisks) != 1 { 231 return nil, nil, errors.New(fmt.Sprintf("unexpected number of disks created: %d", len(gceDisks))) 232 } 233 gceDisk := gceDisks[0] 234 235 attachedDisk, err := v.attachOneVolume(ctx, gceDisk.Name, google.ModeRW, inst.ID) 236 if err != nil { 237 return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId) 238 } 239 240 volume = &storage.Volume{ 241 p.Tag, 242 storage.VolumeInfo{ 243 VolumeId: gceDisk.Name, 244 Size: gceDisk.Size, 245 Persistent: true, 246 }, 247 } 248 249 volumeAttachment = &storage.VolumeAttachment{ 250 p.Tag, 251 p.Attachment.Machine, 252 storage.VolumeAttachmentInfo{ 253 DeviceLink: fmt.Sprintf( 254 "/dev/disk/by-id/google-%s", 255 attachedDisk.DeviceName, 256 ), 257 }, 258 } 259 260 return volume, volumeAttachment, nil 261 } 262 263 func (v *volumeSource) DestroyVolumes(ctx context.ProviderCallContext, volNames []string) ([]error, error) { 264 return v.foreachVolume(ctx, volNames, v.destroyOneVolume), nil 265 } 266 267 func (v *volumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volNames []string) ([]error, error) { 268 return v.foreachVolume(ctx, volNames, v.releaseOneVolume), nil 269 } 270 271 func (v *volumeSource) foreachVolume(ctx context.ProviderCallContext, volNames []string, f func(context.ProviderCallContext, string) error) []error { 272 var wg sync.WaitGroup 273 wg.Add(len(volNames)) 274 results := make([]error, len(volNames)) 275 for i, volumeName := range volNames { 276 go func(i int, volumeName string) { 277 defer wg.Done() 278 results[i] = f(ctx, volumeName) 279 }(i, volumeName) 280 } 281 wg.Wait() 282 return results 283 } 284 285 func parseVolumeId(volName string) (string, string, error) { 286 idRest := strings.SplitN(volName, "--", 2) 287 if len(idRest) != 2 { 288 return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName)) 289 } 290 zone := idRest[0] 291 volumeUUID := idRest[1] 292 return zone, volumeUUID, nil 293 } 294 295 func isValidVolume(volumeName string) bool { 296 _, _, err := parseVolumeId(volumeName) 297 return err == nil 298 } 299 300 func (v *volumeSource) destroyOneVolume(ctx context.ProviderCallContext, volName string) error { 301 zone, _, err := parseVolumeId(volName) 302 if err != nil { 303 return errors.Annotatef(err, "invalid volume id %q", volName) 304 } 305 if err := v.gce.RemoveDisk(zone, volName); err != nil { 306 return google.HandleCredentialError(errors.Annotatef(err, "cannot destroy volume %q", volName), ctx) 307 } 308 return nil 309 } 310 311 func (v *volumeSource) releaseOneVolume(ctx context.ProviderCallContext, volName string) error { 312 zone, _, err := parseVolumeId(volName) 313 if err != nil { 314 return errors.Annotatef(err, "invalid volume id %q", volName) 315 } 316 disk, err := v.gce.Disk(zone, volName) 317 if err != nil { 318 return google.HandleCredentialError(errors.Trace(err), ctx) 319 } 320 switch disk.Status { 321 case google.StatusReady, google.StatusFailed: 322 default: 323 return errors.Errorf( 324 "cannot release volume %q with status %q", 325 volName, disk.Status, 326 ) 327 } 328 if len(disk.AttachedInstances) > 0 { 329 return errors.Errorf( 330 "cannot release volume %q, attached to instances %q", 331 volName, disk.AttachedInstances, 332 ) 333 } 334 delete(disk.Labels, tags.JujuController) 335 delete(disk.Labels, tags.JujuModel) 336 if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil { 337 return google.HandleCredentialError(errors.Annotatef(err, "cannot remove labels from volume %q", volName), ctx) 338 } 339 return nil 340 } 341 342 func (v *volumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) { 343 var volumes []string 344 disks, err := v.gce.Disks() 345 if err != nil { 346 return nil, google.HandleCredentialError(errors.Trace(err), ctx) 347 } 348 for _, disk := range disks { 349 if !isValidVolume(disk.Name) { 350 continue 351 } 352 if disk.Labels[tags.JujuModel] != v.modelUUID { 353 continue 354 } 355 volumes = append(volumes, disk.Name) 356 } 357 return volumes, nil 358 } 359 360 // ImportVolume is specified on the storage.VolumeImporter interface. 361 func (v *volumeSource) ImportVolume(ctx context.ProviderCallContext, volName string, tags map[string]string) (storage.VolumeInfo, error) { 362 zone, _, err := parseVolumeId(volName) 363 if err != nil { 364 return storage.VolumeInfo{}, errors.Annotatef(err, "cannot get volume %q", volName) 365 } 366 disk, err := v.gce.Disk(zone, volName) 367 if err != nil { 368 return storage.VolumeInfo{}, google.HandleCredentialError(errors.Annotatef(err, "cannot get volume %q", volName), ctx) 369 } 370 if disk.Status != google.StatusReady { 371 return storage.VolumeInfo{}, errors.Errorf( 372 "cannot import volume %q with status %q", 373 volName, disk.Status, 374 ) 375 } 376 if disk.Labels == nil { 377 disk.Labels = make(map[string]string) 378 } 379 for k, v := range resourceTagsToDiskLabels(tags) { 380 disk.Labels[k] = v 381 } 382 if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil { 383 return storage.VolumeInfo{}, google.HandleCredentialError(errors.Annotatef(err, "cannot update labels on volume %q", volName), ctx) 384 } 385 return storage.VolumeInfo{ 386 VolumeId: disk.Name, 387 Size: disk.Size, 388 Persistent: true, 389 }, nil 390 } 391 392 func (v *volumeSource) DescribeVolumes(ctx context.ProviderCallContext, volNames []string) ([]storage.DescribeVolumesResult, error) { 393 results := make([]storage.DescribeVolumesResult, len(volNames)) 394 for i, vol := range volNames { 395 res, err := v.describeOneVolume(ctx, vol) 396 if err != nil { 397 return nil, errors.Annotate(err, "cannot describe volumes") 398 } 399 results[i] = res 400 } 401 return results, nil 402 } 403 404 func (v *volumeSource) describeOneVolume(ctx context.ProviderCallContext, volName string) (storage.DescribeVolumesResult, error) { 405 zone, _, err := parseVolumeId(volName) 406 if err != nil { 407 return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName) 408 } 409 disk, err := v.gce.Disk(zone, volName) 410 if err != nil { 411 return storage.DescribeVolumesResult{}, google.HandleCredentialError(errors.Annotatef(err, "cannot get volume %q", volName), ctx) 412 } 413 desc := storage.DescribeVolumesResult{ 414 &storage.VolumeInfo{ 415 Size: disk.Size, 416 VolumeId: disk.Name, 417 }, 418 nil, 419 } 420 return desc, nil 421 } 422 423 // TODO(perrito666) These rules are yet to be defined. 424 func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 425 return nil 426 } 427 428 func (v *volumeSource) AttachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 429 results := make([]storage.AttachVolumesResult, len(attachParams)) 430 for i, attachment := range attachParams { 431 volumeName := attachment.VolumeId 432 mode := google.ModeRW 433 if attachment.ReadOnly { 434 mode = google.ModeRW 435 } 436 instanceId := attachment.InstanceId 437 attached, err := v.attachOneVolume(ctx, volumeName, mode, string(instanceId)) 438 if err != nil { 439 logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err) 440 results[i].Error = err 441 // ... Unless the error is due to an invalid credential, in which case, continuing with this call 442 // is pointless and creates an unnecessary churn: we know all calls will fail with the same error. 443 if google.HasDenialStatusCode(err) { 444 return results, err 445 } 446 continue 447 } 448 results[i].VolumeAttachment = &storage.VolumeAttachment{ 449 attachment.Volume, 450 attachment.Machine, 451 storage.VolumeAttachmentInfo{ 452 DeviceLink: fmt.Sprintf( 453 "/dev/disk/by-id/google-%s", 454 attached.DeviceName, 455 ), 456 }, 457 } 458 } 459 return results, nil 460 } 461 462 func (v *volumeSource) attachOneVolume(ctx context.ProviderCallContext, volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) { 463 zone, _, err := parseVolumeId(volumeName) 464 if err != nil { 465 return nil, errors.Annotate(err, "invalid volume name") 466 } 467 instanceDisks, err := v.gce.InstanceDisks(zone, instanceId) 468 if err != nil { 469 return nil, google.HandleCredentialError(errors.Annotate(err, "cannot verify if the disk is already in the instance"), ctx) 470 } 471 // Is it already attached? 472 for _, disk := range instanceDisks { 473 if disk.VolumeName == volumeName { 474 return disk, nil 475 } 476 } 477 478 attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode) 479 if err != nil { 480 return nil, google.HandleCredentialError(errors.Annotate(err, "cannot attach volume"), ctx) 481 } 482 return attachment, nil 483 } 484 485 func (v *volumeSource) DetachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]error, error) { 486 result := make([]error, len(attachParams)) 487 for i, volumeAttachment := range attachParams { 488 err := v.detachOneVolume(ctx, volumeAttachment) 489 if google.HasDenialStatusCode(err) { 490 // no need to continue as we'll keep getting the same invalid credential error. 491 return result, err 492 } 493 result[i] = err 494 } 495 return result, nil 496 } 497 498 func (v *volumeSource) detachOneVolume(ctx context.ProviderCallContext, attachParam storage.VolumeAttachmentParams) error { 499 instId := attachParam.InstanceId 500 volumeName := attachParam.VolumeId 501 zone, _, err := parseVolumeId(volumeName) 502 if err != nil { 503 return errors.Annotatef(err, "%q is not a valid volume id", volumeName) 504 } 505 return google.HandleCredentialError(v.gce.DetachDisk(zone, string(instId), volumeName), ctx) 506 } 507 508 // resourceTagsToDiskLabels translates a set of 509 // resource tags, provided by Juju, to disk labels. 510 func resourceTagsToDiskLabels(in map[string]string) map[string]string { 511 out := make(map[string]string) 512 for k, v := range in { 513 // Only the controller and model UUID tags are carried 514 // over, as they're known not to conflict with GCE's 515 // rules regarding label values. 516 switch k { 517 case tags.JujuController, tags.JujuModel: 518 out[k] = v 519 } 520 } 521 return out 522 }