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