github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/google/disk.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package google
     5  
     6  import (
     7  	"path"
     8  
     9  	"github.com/juju/errors"
    10  	jujuos "github.com/juju/os"
    11  	"github.com/juju/os/series"
    12  	"google.golang.org/api/compute/v1"
    13  )
    14  
    15  // The different types of disk persistence supported by GCE.
    16  const (
    17  	diskPersistenceTypeScratch    = "SCRATCH"
    18  	diskPersistenceTypePersistent = "PERSISTENT"
    19  )
    20  
    21  type DiskType string
    22  
    23  // The types of disk supported by GCE
    24  const (
    25  	// persistent
    26  	DiskPersistentStandard DiskType = "pd-standard"
    27  	DiskPersistentSSD      DiskType = "pd-ssd"
    28  	// scratch
    29  	DiskLocalSSD DiskType = "local-ssd"
    30  )
    31  
    32  type DiskMode string
    33  
    34  // The different disk modes supported by GCE.
    35  const (
    36  	ModeRW DiskMode = "READ_WRITE"
    37  	ModeRO DiskMode = "READ_ONLY"
    38  )
    39  
    40  type DiskStatus string
    41  
    42  const (
    43  	StatusCreating  DiskStatus = "CREATING"
    44  	StatusFailed    DiskStatus = "FAILED"
    45  	StatusReady     DiskStatus = "READY"
    46  	StatusRestoring DiskStatus = "RESTORING"
    47  )
    48  
    49  // MinDiskSizeGB is the minimum/default size (in megabytes) for
    50  // GCE disks.
    51  //
    52  // Note: GCE does not currently have an official minimum disk size.
    53  // However, in testing we found the minimum size to be 10 GB for ubuntu
    54  // and 50 GB for windows due to the image size. See gceapi message.
    55  //
    56  // gceapi: Requested disk size cannot be smaller than the image size (10 GB)
    57  func MinDiskSizeGB(ser string) uint64 {
    58  	// See comment below that explains why we're ignoring the error
    59  	os, _ := series.GetOSFromSeries(ser)
    60  	switch os {
    61  	case jujuos.Ubuntu:
    62  		return 10
    63  	case jujuos.Windows:
    64  		return 50
    65  	// On default we just return a "sane" default since the error
    66  	// will be propagated through the api and appear in juju status anyway
    67  	default:
    68  		return 10
    69  	}
    70  }
    71  
    72  // gibToMib converts gibibytes to mebibytes.
    73  func gibToMib(g int64) uint64 {
    74  	return uint64(g) * 1024
    75  }
    76  
    77  // DiskSpec holds all the data needed to request a new disk on GCE.
    78  // Some fields are used only for attached disks (i.e. in association
    79  // with instances).
    80  type DiskSpec struct {
    81  	// Series is the OS series on which the disk size depends
    82  	Series string
    83  	// SizeHintGB is the requested disk size in Gigabytes. It must be
    84  	// greater than 0.
    85  	SizeHintGB uint64
    86  	// ImageURL is the location of the image to which the disk should
    87  	// be initialized.
    88  	ImageURL string
    89  	// Boot indicates that this is a boot disk. An instance may only
    90  	// have one boot disk. (attached only)
    91  	Boot bool
    92  	// Scratch indicates that the disk should be a "scratch" disk
    93  	// instead of a "persistent" disk (the default).
    94  	Scratch bool
    95  	// Readonly indicates that the disk should not support writes.
    96  	Readonly bool
    97  	// AutoDelete indicates that the attached disk should be removed
    98  	// when the instance to which it is attached is removed.
    99  	AutoDelete bool
   100  	// PersistenDiskType is exclusive to persistent disks and indicates which of the
   101  	// persistent types available this disk should be.
   102  	PersistentDiskType DiskType
   103  	// Name: Name of the resource; provided by the client when the resource
   104  	// is created. The name must be 1-63 characters long, and comply with
   105  	// RFC1035. Specifically, the name must be 1-63 characters long and
   106  	// match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means
   107  	// the first character must be a lowercase letter, and all following
   108  	// characters must be a dash, lowercase letter, or digit, except the
   109  	// last character, which cannot be a dash.
   110  	Name string
   111  	// Labels holds labels/metadata for the disk. Labels are used for
   112  	// storing volume resource tags.
   113  	Labels map[string]string
   114  }
   115  
   116  // TooSmall checks the spec's size hint and indicates whether or not
   117  // it is smaller than the minimum disk size.
   118  func (ds *DiskSpec) TooSmall() bool {
   119  	return ds.SizeHintGB < MinDiskSizeGB(ds.Series)
   120  }
   121  
   122  // SizeGB returns the disk size to use for a new disk. The size hint
   123  // is returned if it isn't too small (otherwise the min size is
   124  // returned).
   125  func (ds *DiskSpec) SizeGB() uint64 {
   126  	size := ds.SizeHintGB
   127  	if ds.TooSmall() {
   128  		size = MinDiskSizeGB(ds.Series)
   129  	}
   130  	return size
   131  }
   132  
   133  // newAttached builds a compute.AttachedDisk using the information in
   134  // the disk spec and returns it.
   135  //
   136  // Note: Not all AttachedDisk fields are set.
   137  func (ds *DiskSpec) newAttached() *compute.AttachedDisk {
   138  	// TODO(ericsnow) Fail if SizeHintGB is 0?
   139  	diskType := diskPersistenceTypePersistent
   140  	if ds.Scratch {
   141  		diskType = diskPersistenceTypeScratch
   142  	}
   143  	mode := ModeRW
   144  	if ds.Readonly {
   145  		mode = ModeRO
   146  	}
   147  
   148  	disk := compute.AttachedDisk{
   149  		Type:       diskType,
   150  		Boot:       ds.Boot,
   151  		Mode:       string(mode),
   152  		AutoDelete: ds.AutoDelete,
   153  		InitializeParams: &compute.AttachedDiskInitializeParams{
   154  			// DiskName (defaults to instance name)
   155  			DiskSizeGb: int64(ds.SizeGB()),
   156  			// DiskType (defaults to pd-standard, pd-ssd, local-ssd)
   157  			SourceImage: ds.ImageURL,
   158  		},
   159  		// Interface (defaults to SCSI)
   160  		// DeviceName (GCE sets this, persistent disk only)
   161  	}
   162  	return &disk
   163  }
   164  
   165  // newDetached creates a new detached persistent disk representation,
   166  // this DOES NOT create a disk in gce, just creates the spec.
   167  // reference in https://cloud.google.com/compute/docs/reference/latest/disks#resource
   168  func (ds *DiskSpec) newDetached() (*compute.Disk, error) {
   169  	if ds.Scratch {
   170  		return nil, errors.New("cannot create scratch volumes detached")
   171  	}
   172  	if ds.PersistentDiskType == DiskLocalSSD {
   173  		return nil, errors.New("cannot create local ssd disks detached")
   174  	}
   175  	return &compute.Disk{
   176  		Name:        ds.Name,
   177  		SizeGb:      int64(ds.SizeGB()),
   178  		SourceImage: ds.ImageURL,
   179  		Type:        string(ds.PersistentDiskType),
   180  		Labels:      ds.Labels,
   181  	}, nil
   182  }
   183  
   184  // AttachedDisk represents a disk that is attached to an instance.
   185  type AttachedDisk struct {
   186  	// VolumeName is the name of the volume that is attached, this is unique
   187  	// and used by gce as an identifier.
   188  	VolumeName string
   189  	// DeviceName is the name of the device in the instance, typycally
   190  	// is reflected into the /dev/disk/by-id/google-*
   191  	DeviceName string
   192  	// Mode is the read/write mode of the disk.
   193  	Mode DiskMode
   194  }
   195  
   196  // Disk represents a gce disk.
   197  type Disk struct {
   198  	// Id is an unique identifier google adds to the disk, it usually
   199  	// is not used in the API.
   200  	Id uint64
   201  
   202  	// Name is a unique identifier string for each disk.
   203  	Name string
   204  
   205  	// Description holds the description field for a disk, we used to
   206  	// store the model UUID here.
   207  	Description string
   208  
   209  	// Size is the size in mbit.
   210  	Size uint64
   211  
   212  	// Type is one of the available disk types supported by
   213  	// gce (persistent or ephemeral).
   214  	Type DiskType
   215  
   216  	// Zone holds the name of the zone in which the disk lives.
   217  	Zone string
   218  
   219  	// DiskStatus holds the status of he aforementioned disk.
   220  	Status DiskStatus
   221  
   222  	// AttachedInstances holds the IDs of instances that have the disk
   223  	// attached.
   224  	AttachedInstances []string
   225  
   226  	// Labels holds labels/metadata for the disk. Labels are used for
   227  	// storing volume resource tags.
   228  	Labels map[string]string
   229  
   230  	// LabelFingerprint holds a hash of the labels, to be used to prevent
   231  	// conflicting changes to labels.
   232  	LabelFingerprint string
   233  }
   234  
   235  func NewDisk(cd *compute.Disk) *Disk {
   236  	// cd.Users contains the users of the disk (attached instances),
   237  	// in form: project/zones/zone/instances/instance. Disks and
   238  	// instances must be in the same zone, so we just take the
   239  	// final part of the path.
   240  	attachedInstances := make([]string, len(cd.Users))
   241  	for i, u := range cd.Users {
   242  		attachedInstances[i] = path.Base(u)
   243  	}
   244  	d := &Disk{
   245  		Id:                cd.Id,
   246  		Name:              cd.Name,
   247  		Description:       cd.Description,
   248  		Size:              gibToMib(cd.SizeGb),
   249  		Type:              DiskType(cd.Type),
   250  		Zone:              path.Base(cd.Zone),
   251  		Status:            DiskStatus(cd.Status),
   252  		Labels:            cd.Labels,
   253  		LabelFingerprint:  cd.LabelFingerprint,
   254  		AttachedInstances: attachedInstances,
   255  	}
   256  	return d
   257  }