github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 GCEProviderType = 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 // volume is a named return 215 volume = &storage.Volume{ 216 p.Tag, 217 storage.VolumeInfo{ 218 VolumeId: gceDisk.Name, 219 Size: gceDisk.Size, 220 Persistent: true, 221 }, 222 } 223 224 // there is no attachment information 225 if p.Attachment == nil { 226 return volume, nil, nil 227 } 228 229 attachedDisk, err := v.attachOneVolume(gceDisk.Name, google.ModeRW, inst.ID) 230 if err != nil { 231 return nil, nil, errors.Annotatef(err, "attaching %q to %q", gceDisk.Name, instId) 232 } 233 234 // volumeAttachment is a named return 235 volumeAttachment = &storage.VolumeAttachment{ 236 p.Tag, 237 p.Attachment.Machine, 238 storage.VolumeAttachmentInfo{ 239 DeviceName: attachedDisk.DeviceName, 240 }, 241 } 242 243 return volume, volumeAttachment, nil 244 } 245 246 func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) { 247 var wg sync.WaitGroup 248 wg.Add(len(volNames)) 249 results := make([]error, len(volNames)) 250 for i, volumeName := range volNames { 251 go func(i int, volumeName string) { 252 defer wg.Done() 253 results[i] = v.destroyOneVolume(volumeName) 254 }(i, volumeName) 255 } 256 wg.Wait() 257 return results, nil 258 } 259 260 func parseVolumeId(volName string) (string, string, error) { 261 idRest := strings.SplitN(volName, "--", 2) 262 if len(idRest) != 2 { 263 return "", "", errors.New(fmt.Sprintf("malformed volume id %q", volName)) 264 } 265 zone := idRest[0] 266 volumeUUID := idRest[1] 267 return zone, volumeUUID, nil 268 269 } 270 func (v *volumeSource) destroyOneVolume(volName string) error { 271 zone, _, err := parseVolumeId(volName) 272 if err != nil { 273 return errors.Annotatef(err, "invalid volume id %q", volName) 274 } 275 if err := v.gce.RemoveDisk(zone, volName); err != nil { 276 return errors.Annotatef(err, "cannot destroy volume %q", volName) 277 } 278 return nil 279 280 } 281 282 func (v *volumeSource) ListVolumes() ([]string, error) { 283 azs, err := v.gce.AvailabilityZones("") 284 if err != nil { 285 return nil, errors.Annotate(err, "cannot determine availability zones") 286 } 287 var volumes []string 288 for _, zone := range azs { 289 disks, err := v.gce.Disks(zone.Name()) 290 if err != nil { 291 // maybe use available and status also. 292 logger.Errorf("cannot get disks for %q zone", zone.Name()) 293 continue 294 } 295 for _, disk := range disks { 296 volumes = append(volumes, disk.Name) 297 } 298 } 299 return volumes, nil 300 } 301 func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) { 302 results := make([]storage.DescribeVolumesResult, len(volNames)) 303 for i, vol := range volNames { 304 res, err := v.describeOneVolume(vol) 305 if err != nil { 306 return nil, errors.Annotate(err, "cannot describe volumes") 307 } 308 results[i] = res 309 } 310 return results, nil 311 } 312 313 func (v *volumeSource) describeOneVolume(volName string) (storage.DescribeVolumesResult, error) { 314 zone, _, err := parseVolumeId(volName) 315 if err != nil { 316 return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot describe %q", volName) 317 } 318 disk, err := v.gce.Disk(zone, volName) 319 if err != nil { 320 return storage.DescribeVolumesResult{}, errors.Annotatef(err, "cannot get volume %q", volName) 321 } 322 desc := storage.DescribeVolumesResult{ 323 &storage.VolumeInfo{ 324 Size: disk.Size, 325 VolumeId: disk.Name, 326 }, 327 nil, 328 } 329 return desc, nil 330 } 331 332 // TODO(perrito666) These rules are yet to be defined. 333 func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error { 334 return nil 335 } 336 337 func (v *volumeSource) AttachVolumes(attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) { 338 results := make([]storage.AttachVolumesResult, len(attachParams)) 339 for i, attachment := range attachParams { 340 volumeName := attachment.VolumeId 341 mode := google.ModeRW 342 if attachment.ReadOnly { 343 mode = google.ModeRW 344 } 345 instanceId := attachment.InstanceId 346 attached, err := v.attachOneVolume(volumeName, mode, string(instanceId)) 347 if err != nil { 348 logger.Errorf("could not attach %q to %q: %v", volumeName, instanceId, err) 349 results[i].Error = err 350 continue 351 } 352 results[i].VolumeAttachment = &storage.VolumeAttachment{ 353 attachment.Volume, 354 attachment.Machine, 355 storage.VolumeAttachmentInfo{ 356 DeviceName: attached.DeviceName, 357 }, 358 } 359 } 360 return results, nil 361 } 362 363 func (v *volumeSource) attachOneVolume(volumeName string, mode google.DiskMode, instanceId string) (*google.AttachedDisk, error) { 364 zone, _, err := parseVolumeId(volumeName) 365 if err != nil { 366 return nil, errors.Annotate(err, "invalid volume name") 367 } 368 instanceDisks, err := v.gce.InstanceDisks(zone, instanceId) 369 if err != nil { 370 return nil, errors.Annotate(err, "cannot verify if the disk is already in the instance") 371 } 372 // Is it already attached? 373 for _, disk := range instanceDisks { 374 if disk.VolumeName == volumeName { 375 return disk, nil 376 } 377 } 378 379 attachment, err := v.gce.AttachDisk(zone, volumeName, instanceId, mode) 380 if err != nil { 381 return nil, errors.Annotate(err, "cannot attach volume") 382 } 383 return attachment, nil 384 } 385 386 func (v *volumeSource) DetachVolumes(attachParams []storage.VolumeAttachmentParams) ([]error, error) { 387 result := make([]error, len(attachParams)) 388 for i, volumeAttachment := range attachParams { 389 result[i] = v.detachOneVolume(volumeAttachment) 390 } 391 return result, nil 392 } 393 394 func (v *volumeSource) detachOneVolume(attachParam storage.VolumeAttachmentParams) error { 395 instId := attachParam.InstanceId 396 volumeName := attachParam.VolumeId 397 zone, _, err := parseVolumeId(volumeName) 398 if err != nil { 399 return errors.Annotatef(err, "%q is not a valid volume id", volumeName) 400 } 401 return v.gce.DetachDisk(zone, string(instId), volumeName) 402 }