github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/errors" 12 "github.com/juju/utils" 13 "github.com/juju/utils/set" 14 15 "github.com/juju/juju/provider/gce/google" 16 "github.com/juju/juju/storage" 17 ) 18 19 const ( 20 storageProviderType = storage.ProviderType("gce") 21 ) 22 23 // StorageProviderTypes implements storage.ProviderRegistry. 24 func (env *environ) StorageProviderTypes() ([]storage.ProviderType, error) { 25 return []storage.ProviderType{storageProviderType}, nil 26 } 27 28 // StorageProvider implements storage.ProviderRegistry. 29 func (env *environ) StorageProvider(t storage.ProviderType) (storage.Provider, error) { 30 if t == storageProviderType { 31 return &storageProvider{env}, nil 32 } 33 return nil, errors.NotFoundf("storage provider %q", t) 34 } 35 36 type storageProvider struct { 37 env *environ 38 } 39 40 var _ storage.Provider = (*storageProvider)(nil) 41 42 func (g *storageProvider) ValidateConfig(cfg *storage.Config) error { 43 return nil 44 } 45 46 func (g *storageProvider) Supports(k storage.StorageKind) bool { 47 return k == storage.StorageKindBlock 48 } 49 50 func (g *storageProvider) Scope() storage.Scope { 51 return storage.ScopeEnviron 52 } 53 54 func (g *storageProvider) Dynamic() bool { 55 return true 56 } 57 58 func (g *storageProvider) DefaultPools() []*storage.Config { 59 // TODO(perrito666) Add explicit pools. 60 return nil 61 } 62 63 func (g *storageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) { 64 return nil, errors.NotSupportedf("filesystems") 65 } 66 67 type volumeSource struct { 68 gce gceConnection 69 envName string // non-unique, informational only 70 modelUUID string 71 } 72 73 func (g *storageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) { 74 environConfig := g.env.Config() 75 source := &volumeSource{ 76 gce: g.env.gce, 77 envName: environConfig.Name(), 78 modelUUID: environConfig.UUID(), 79 } 80 return source, nil 81 } 82 83 type instanceCache map[string]google.Instance 84 85 func (c instanceCache) update(gceClient gceConnection, ids ...string) error { 86 if len(ids) == 1 { 87 if _, ok := c[ids[0]]; ok { 88 return nil 89 } 90 } 91 idMap := make(map[string]int, len(ids)) 92 for _, id := range ids { 93 idMap[id] = 0 94 } 95 instances, err := gceClient.Instances("", google.StatusRunning) 96 if err != nil { 97 return errors.Annotate(err, "querying instance details") 98 } 99 for _, instance := range instances { 100 if _, ok := idMap[instance.ID]; !ok { 101 continue 102 } 103 c[instance.ID] = instance 104 } 105 return nil 106 } 107 108 func (c instanceCache) get(id string) (google.Instance, error) { 109 inst, ok := c[id] 110 if !ok { 111 return google.Instance{}, errors.Errorf("cannot attach to non-running instance %v", id) 112 } 113 return inst, nil 114 } 115 116 func (v *volumeSource) CreateVolumes(params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) { 117 results := make([]storage.CreateVolumesResult, len(params)) 118 instanceIds := set.NewStrings() 119 for i, p := range params { 120 if err := v.ValidateVolumeParams(p); err != nil { 121 results[i].Error = err 122 continue 123 } 124 instanceIds.Add(string(p.Attachment.InstanceId)) 125 } 126 127 instances := make(instanceCache) 128 if instanceIds.Size() > 1 { 129 if err := instances.update(v.gce, instanceIds.Values()...); err != nil { 130 logger.Debugf("querying running instances: %v", err) 131 // We ignore the error, because we don't want an invalid 132 // InstanceId reference from one VolumeParams to prevent 133 // the creation of another volume. 134 } 135 } 136 137 for i, p := range params { 138 if results[i].Error != nil { 139 continue 140 } 141 volume, attachment, err := v.createOneVolume(p, instances) 142 if err != nil { 143 results[i].Error = err 144 logger.Errorf("could not create one volume (or attach it): %v", err) 145 continue 146 } 147 results[i].Volume = volume 148 results[i].VolumeAttachment = attachment 149 } 150 return results, nil 151 } 152 153 // mibToGib converts mebibytes to gibibytes. 154 // GCE expects GiB, we work in MiB; round up 155 // to nearest GiB. 156 func mibToGib(m uint64) uint64 { 157 return (m + 1023) / 1024 158 } 159 160 func nameVolume(zone string) (string, error) { 161 volumeUUID, err := utils.NewUUID() 162 if err != nil { 163 return "", errors.Annotate(err, "cannot generate uuid to name the volume") 164 } 165 // type-zone-uuid 166 volumeName := fmt.Sprintf("%s--%s", zone, volumeUUID.String()) 167 return volumeName, nil 168 } 169 170 func (v *volumeSource) createOneVolume(p storage.VolumeParams, instances instanceCache) (volume *storage.Volume, volumeAttachment *storage.VolumeAttachment, err error) { 171 var volumeName, zone string 172 defer func() { 173 if err == nil || volumeName == "" { 174 return 175 } 176 if err := v.gce.RemoveDisk(zone, volumeName); err != nil { 177 logger.Errorf("error cleaning up volume %v: %v", volumeName, err) 178 } 179 }() 180 181 instId := string(p.Attachment.InstanceId) 182 if err := instances.update(v.gce, instId); err != nil { 183 return nil, nil, errors.Annotatef(err, "cannot add %q to instance cache", instId) 184 } 185 inst, err := instances.get(instId) 186 if err != nil { 187 // Can't create the volume without the instance, 188 // because we need to know what its AZ is. 189 return nil, nil, errors.Annotatef(err, "cannot obtain %q from instance cache", instId) 190 } 191 persistentType, ok := p.Attributes["type"].(google.DiskType) 192 if !ok { 193 persistentType = google.DiskPersistentStandard 194 } 195 196 zone = inst.ZoneName 197 volumeName, err = nameVolume(zone) 198 if err != nil { 199 return nil, nil, errors.Annotate(err, "cannot create a new volume name") 200 } 201 // TODO(perrito666) the volumeName is arbitrary and it was crafted this 202 // way to help solve the need to have zone all over the place. 203 disk := google.DiskSpec{ 204 SizeHintGB: mibToGib(p.Size), 205 Name: volumeName, 206 PersistentDiskType: persistentType, 207 Description: v.modelUUID, 208 } 209 210 gceDisks, err := v.gce.CreateDisks(zone, []google.DiskSpec{disk}) 211 if err != nil { 212 return nil, nil, errors.Annotate(err, "cannot create disk") 213 } 214 if len(gceDisks) != 1 { 215 return nil, nil, errors.New(fmt.Sprintf("unexpected number of disks created: %d", len(gceDisks))) 216 } 217 gceDisk := gceDisks[0] 218 // TODO(perrito666) Tag, there are no tags in gce, how do we fix it? 219 220 attachedDisk, err := v.attachOneVolume(gceDisk.Name, google.ModeRW, inst.ID) 221 if err != nil { 222 return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId) 223 } 224 225 volume = &storage.Volume{ 226 p.Tag, 227 storage.VolumeInfo{ 228 VolumeId: gceDisk.Name, 229 Size: gceDisk.Size, 230 Persistent: true, 231 }, 232 } 233 234 volumeAttachment = &storage.VolumeAttachment{ 235 p.Tag, 236 p.Attachment.Machine, 237 storage.VolumeAttachmentInfo{ 238 DeviceLink: fmt.Sprintf( 239 "/dev/disk/by-id/google-%s", 240 attachedDisk.DeviceName, 241 ), 242 }, 243 } 244 245 return volume, volumeAttachment, nil 246 } 247 248 func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) { 249 var wg sync.WaitGroup 250 wg.Add(len(volNames)) 251 results := make([]error, len(volNames)) 252 for i, volumeName := range volNames { 253 go func(i int, volumeName string) { 254 defer wg.Done() 255 results[i] = v.destroyOneVolume(volumeName) 256 }(i, volumeName) 257 } 258 wg.Wait() 259 return results, nil 260 } 261 262 func parseVolumeId(volName string) (string, string, error) { 263 idRest := strings.SplitN(volName, "--", 2) 264 if len(idRest) != 2 { 265 return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName)) 266 } 267 zone := idRest[0] 268 volumeUUID := idRest[1] 269 return zone, volumeUUID, nil 270 271 } 272 273 func isValidVolume(volumeName string) bool { 274 _, _, err := parseVolumeId(volumeName) 275 return err == nil 276 } 277 278 func (v *volumeSource) destroyOneVolume(volName string) error { 279 zone, _, err := parseVolumeId(volName) 280 if err != nil { 281 return errors.Annotatef(err, "invalid volume id %q", volName) 282 } 283 if err := v.gce.RemoveDisk(zone, volName); err != nil { 284 return errors.Annotatef(err, "cannot destroy volume %q", volName) 285 } 286 return nil 287 } 288 289 func (v *volumeSource) ListVolumes() ([]string, error) { 290 azs, err := v.gce.AvailabilityZones("") 291 if err != nil { 292 return nil, errors.Annotate(err, "cannot determine availability zones") 293 } 294 var volumes []string 295 for _, zone := range azs { 296 disks, err := v.gce.Disks(zone.Name()) 297 if err != nil { 298 // maybe use available and status also. 299 logger.Errorf("cannot get disks for %q zone", zone.Name()) 300 continue 301 } 302 for _, disk := range disks { 303 // Blank disk description means an older disk or a disk 304 // not created by storage, we should not touch it. 305 if disk.Description != v.modelUUID && disk.Description != "" { 306 continue 307 } 308 // We don't want to lay hands on disks we did not create. 309 if isValidVolume(disk.Name) { 310 volumes = append(volumes, disk.Name) 311 } 312 } 313 } 314 return volumes, nil 315 } 316 func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) { 317 results := make([]storage.DescribeVolumesResult, len(volNames)) 318 for i, vol := range volNames { 319 res, err := v.describeOneVolume(vol) 320 if err != nil { 321 return nil, errors.Annotate(err, "cannot describe volumes") 322 } 323 results[i] = res 324 } 325 return results, nil 326 } 327 328 func (v *volumeSource) describeOneVolume(volName string) (storage.DescribeVolumesResult, error) { 329 zone, _, err := parseVolumeId(volName) 330 if err != nil { 331 return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName) 332 } 333 disk, err := v.gce.Disk(zone, volName) 334 if err != nil { 335 return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot get volume %q", volName) 336 } 337 desc := storage.DescribeVolumesResult{ 338 &storage.VolumeInfo{ 339 Size: disk.Size, 340 VolumeId: disk.Name, 341 }, 342 nil, 343 } 344 return desc, nil 345 } 346 347 // TODO(perrito666) These rules are yet to be defined. 348 func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 349 return nil 350 } 351 352 func (v *volumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 353 results := make([]storage.AttachVolumesResult, len(attachParams)) 354 for i, attachment := range attachParams { 355 volumeName := attachment.VolumeId 356 mode := google.ModeRW 357 if attachment.ReadOnly { 358 mode = google.ModeRW 359 } 360 instanceId := attachment.InstanceId 361 attached, err := v.attachOneVolume(volumeName, mode, string(instanceId)) 362 if err != nil { 363 logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err) 364 results[i].Error = err 365 continue 366 } 367 results[i].VolumeAttachment = &storage.VolumeAttachment{ 368 attachment.Volume, 369 attachment.Machine, 370 storage.VolumeAttachmentInfo{ 371 DeviceName: attached.DeviceName, 372 }, 373 } 374 } 375 return results, nil 376 } 377 378 func (v *volumeSource) attachOneVolume(volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) { 379 zone, _, err := parseVolumeId(volumeName) 380 if err != nil { 381 return nil, errors.Annotate(err, "invalid volume name") 382 } 383 instanceDisks, err := v.gce.InstanceDisks(zone, instanceId) 384 if err != nil { 385 return nil, errors.Annotate(err, "cannot verify if the disk is already in the instance") 386 } 387 // Is it already attached? 388 for _, disk := range instanceDisks { 389 if disk.VolumeName == volumeName { 390 return disk, nil 391 } 392 } 393 394 attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode) 395 if err != nil { 396 return nil, errors.Annotate(err, "cannot attach volume") 397 } 398 return attachment, nil 399 } 400 401 func (v *volumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) { 402 result := make([]error, len(attachParams)) 403 for i, volumeAttachment := range attachParams { 404 result[i] = v.detachOneVolume(volumeAttachment) 405 } 406 return result, nil 407 } 408 409 func (v *volumeSource) detachOneVolume(attachParam storage.VolumeAttachmentParams) error { 410 instId := attachParam.InstanceId 411 volumeName := attachParam.VolumeId 412 zone, _, err := parseVolumeId(volumeName) 413 if err != nil { 414 return errors.Annotatef(err, "%q is not a valid volume id", volumeName) 415 } 416 return v.gce.DetachDisk(zone, string(instId), volumeName) 417 }