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