github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/maas/volumes.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"strconv"
     8  	"strings"
     9  	"unicode"
    10  
    11  	"github.com/dustin/go-humanize"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/schema"
    14  	"github.com/juju/utils/set"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/constraints"
    18  	"github.com/juju/juju/provider/common"
    19  	"github.com/juju/juju/storage"
    20  )
    21  
    22  const (
    23  	// maasStorageProviderType is the name of the storage provider
    24  	// used to specify storage when acquiring MAAS nodes.
    25  	maasStorageProviderType = storage.ProviderType("maas")
    26  
    27  	// rootDiskLabel is the label recognised by MAAS as being for
    28  	// the root disk.
    29  	rootDiskLabel = "root"
    30  
    31  	// tagsAttribute is the name of the pool attribute used
    32  	// to specify tag values for requested volumes.
    33  	tagsAttribute = "tags"
    34  )
    35  
    36  // StorageProviderTypes implements storage.ProviderRegistry.
    37  func (*maasEnviron) StorageProviderTypes() ([]storage.ProviderType, error) {
    38  	return []storage.ProviderType{maasStorageProviderType}, nil
    39  }
    40  
    41  // StorageProvider implements storage.ProviderRegistry.
    42  func (*maasEnviron) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
    43  	if t == maasStorageProviderType {
    44  		return maasStorageProvider{}, nil
    45  	}
    46  	return nil, errors.NotFoundf("storage provider %q", t)
    47  }
    48  
    49  // maasStorageProvider allows volumes to be specified when a node is acquired.
    50  type maasStorageProvider struct{}
    51  
    52  var storageConfigFields = schema.Fields{
    53  	tagsAttribute: schema.OneOf(
    54  		schema.List(schema.String()),
    55  		schema.String(),
    56  	),
    57  }
    58  
    59  var storageConfigChecker = schema.FieldMap(
    60  	storageConfigFields,
    61  	schema.Defaults{
    62  		tagsAttribute: schema.Omit,
    63  	},
    64  )
    65  
    66  type storageConfig struct {
    67  	tags []string
    68  }
    69  
    70  func newStorageConfig(attrs map[string]interface{}) (*storageConfig, error) {
    71  	out, err := storageConfigChecker.Coerce(attrs, nil)
    72  	if err != nil {
    73  		return nil, errors.Annotate(err, "validating MAAS storage config")
    74  	}
    75  	coerced := out.(map[string]interface{})
    76  	var tags []string
    77  	switch v := coerced[tagsAttribute].(type) {
    78  	case []string:
    79  		tags = v
    80  	case string:
    81  		fields := strings.Split(v, ",")
    82  		for _, f := range fields {
    83  			f = strings.TrimSpace(f)
    84  			if len(f) == 0 {
    85  				continue
    86  			}
    87  			if i := strings.IndexFunc(f, unicode.IsSpace); i >= 0 {
    88  				return nil, errors.Errorf("tags may not contain whitespace: %q", f)
    89  			}
    90  			tags = append(tags, f)
    91  		}
    92  	}
    93  	return &storageConfig{tags: tags}, nil
    94  }
    95  
    96  // ValidateConfig is defined on the Provider interface.
    97  func (maasStorageProvider) ValidateConfig(cfg *storage.Config) error {
    98  	_, err := newStorageConfig(cfg.Attrs())
    99  	return errors.Trace(err)
   100  }
   101  
   102  // Supports is defined on the Provider interface.
   103  func (maasStorageProvider) Supports(k storage.StorageKind) bool {
   104  	return k == storage.StorageKindBlock
   105  }
   106  
   107  // Scope is defined on the Provider interface.
   108  func (maasStorageProvider) Scope() storage.Scope {
   109  	return storage.ScopeEnviron
   110  }
   111  
   112  // Dynamic is defined on the Provider interface.
   113  func (maasStorageProvider) Dynamic() bool {
   114  	return false
   115  }
   116  
   117  // DefaultPools is defined on the Provider interface.
   118  func (maasStorageProvider) DefaultPools() []*storage.Config {
   119  	return nil
   120  }
   121  
   122  // VolumeSource is defined on the Provider interface.
   123  func (maasStorageProvider) VolumeSource(providerConfig *storage.Config) (storage.VolumeSource, error) {
   124  	// Dynamic volumes not supported.
   125  	return nil, errors.NotSupportedf("volumes")
   126  }
   127  
   128  // FilesystemSource is defined on the Provider interface.
   129  func (maasStorageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
   130  	return nil, errors.NotSupportedf("filesystems")
   131  }
   132  
   133  type volumeInfo struct {
   134  	name     string
   135  	sizeInGB uint64
   136  	tags     []string
   137  }
   138  
   139  // mibToGB converts the value in MiB to GB.
   140  // Juju works in MiB, MAAS expects GB.
   141  func mibToGb(m uint64) uint64 {
   142  	return common.MiBToGiB(m) * (humanize.GiByte / humanize.GByte)
   143  }
   144  
   145  // buildMAASVolumeParameters creates the MAAS volume information to include
   146  // in a request to acquire a MAAS node, based on the supplied storage parameters.
   147  func buildMAASVolumeParameters(args []storage.VolumeParams, cons constraints.Value) ([]volumeInfo, error) {
   148  	if len(args) == 0 && cons.RootDisk == nil {
   149  		return nil, nil
   150  	}
   151  	volumes := make([]volumeInfo, len(args)+1)
   152  	rootVolume := volumeInfo{name: rootDiskLabel}
   153  	if cons.RootDisk != nil {
   154  		rootVolume.sizeInGB = mibToGb(*cons.RootDisk)
   155  	}
   156  	volumes[0] = rootVolume
   157  	for i, v := range args {
   158  		cfg, err := newStorageConfig(v.Attributes)
   159  		if err != nil {
   160  			return nil, errors.Trace(err)
   161  		}
   162  		info := volumeInfo{
   163  			name:     v.Tag.Id(),
   164  			sizeInGB: mibToGb(v.Size),
   165  			tags:     cfg.tags,
   166  		}
   167  		volumes[i+1] = info
   168  	}
   169  	return volumes, nil
   170  }
   171  
   172  // volumes creates the storage volumes and attachments
   173  // corresponding to the volume info associated with a MAAS node.
   174  func (mi *maas1Instance) volumes(
   175  	mTag names.MachineTag, requestedVolumes []names.VolumeTag,
   176  ) (
   177  	[]storage.Volume, []storage.VolumeAttachment, error,
   178  ) {
   179  	var volumes []storage.Volume
   180  	var attachments []storage.VolumeAttachment
   181  
   182  	deviceInfo, ok := mi.maasObject.GetMap()["physicalblockdevice_set"]
   183  	// Older MAAS servers don't support storage.
   184  	if !ok || deviceInfo.IsNil() {
   185  		return volumes, attachments, nil
   186  	}
   187  
   188  	labelsMap, ok := mi.maasObject.GetMap()["constraint_map"]
   189  	if !ok || labelsMap.IsNil() {
   190  		return nil, nil, errors.NotFoundf("constraint map field")
   191  	}
   192  
   193  	devices, err := deviceInfo.GetArray()
   194  	if err != nil {
   195  		return nil, nil, errors.Trace(err)
   196  	}
   197  	// deviceLabel is the volume label passed
   198  	// into the acquire node call as part
   199  	// of the storage constraints parameter.
   200  	deviceLabels, err := labelsMap.GetMap()
   201  	if err != nil {
   202  		return nil, nil, errors.Annotate(err, "invalid constraint map value")
   203  	}
   204  
   205  	// Set up a collection of volumes tags which
   206  	// we specifically asked for when the node was acquired.
   207  	validVolumes := set.NewStrings()
   208  	for _, v := range requestedVolumes {
   209  		validVolumes.Add(v.Id())
   210  	}
   211  
   212  	for _, d := range devices {
   213  		deviceAttrs, err := d.GetMap()
   214  		if err != nil {
   215  			return nil, nil, errors.Trace(err)
   216  		}
   217  		// id in devices list is numeric
   218  		id, err := deviceAttrs["id"].GetFloat64()
   219  		if err != nil {
   220  			return nil, nil, errors.Annotate(err, "invalid device id")
   221  		}
   222  		// id in constraint_map field is a string
   223  		idKey := strconv.Itoa(int(id))
   224  
   225  		// Device Label.
   226  		deviceLabelValue, ok := deviceLabels[idKey]
   227  		if !ok {
   228  			logger.Debugf("acquire maas node: missing volume label for id %q", idKey)
   229  			continue
   230  		}
   231  		deviceLabel, err := deviceLabelValue.GetString()
   232  		if err != nil {
   233  			return nil, nil, errors.Annotate(err, "invalid device label")
   234  		}
   235  		// We don't explicitly allow the root volume to be specified yet.
   236  		if deviceLabel == rootDiskLabel {
   237  			continue
   238  		}
   239  		// We only care about the volumes we specifically asked for.
   240  		if !validVolumes.Contains(deviceLabel) {
   241  			continue
   242  		}
   243  
   244  		// HardwareId and DeviceName.
   245  		// First try for id_path.
   246  		idPathPrefix := "/dev/disk/by-id/"
   247  		hardwareId, err := deviceAttrs["id_path"].GetString()
   248  		var deviceName string
   249  		if err == nil {
   250  			if !strings.HasPrefix(hardwareId, idPathPrefix) {
   251  				return nil, nil, errors.Errorf("invalid device id %q", hardwareId)
   252  			}
   253  			hardwareId = hardwareId[len(idPathPrefix):]
   254  		} else {
   255  			// On VMAAS, id_path not available so try for path instead.
   256  			deviceName, err = deviceAttrs["name"].GetString()
   257  			if err != nil {
   258  				return nil, nil, errors.Annotate(err, "invalid device name")
   259  			}
   260  		}
   261  
   262  		// Size.
   263  		sizeinBytes, err := deviceAttrs["size"].GetFloat64()
   264  		if err != nil {
   265  			return nil, nil, errors.Annotate(err, "invalid device size")
   266  		}
   267  
   268  		volumeTag := names.NewVolumeTag(deviceLabel)
   269  		vol := storage.Volume{
   270  			volumeTag,
   271  			storage.VolumeInfo{
   272  				VolumeId:   volumeTag.String(),
   273  				HardwareId: hardwareId,
   274  				Size:       uint64(sizeinBytes / humanize.MiByte),
   275  				Persistent: false,
   276  			},
   277  		}
   278  		volumes = append(volumes, vol)
   279  
   280  		attachment := storage.VolumeAttachment{
   281  			volumeTag,
   282  			mTag,
   283  			storage.VolumeAttachmentInfo{
   284  				DeviceName: deviceName,
   285  				ReadOnly:   false,
   286  			},
   287  		}
   288  		attachments = append(attachments, attachment)
   289  	}
   290  	return volumes, attachments, nil
   291  }
   292  
   293  func (mi *maas2Instance) volumes(
   294  	mTag names.MachineTag, requestedVolumes []names.VolumeTag,
   295  ) (
   296  	[]storage.Volume, []storage.VolumeAttachment, error,
   297  ) {
   298  	if mi.constraintMatches.Storage == nil {
   299  		return nil, nil, errors.NotFoundf("constraint storage mapping")
   300  	}
   301  
   302  	var volumes []storage.Volume
   303  	var attachments []storage.VolumeAttachment
   304  
   305  	// Set up a collection of volumes tags which
   306  	// we specifically asked for when the node was acquired.
   307  	validVolumes := set.NewStrings()
   308  	for _, v := range requestedVolumes {
   309  		validVolumes.Add(v.Id())
   310  	}
   311  
   312  	for label, devices := range mi.constraintMatches.Storage {
   313  		// We don't explicitly allow the root volume to be specified yet.
   314  		if label == rootDiskLabel {
   315  			continue
   316  		}
   317  		// We only care about the volumes we specifically asked for.
   318  		if !validVolumes.Contains(label) {
   319  			continue
   320  		}
   321  
   322  		for _, device := range devices {
   323  			volumeTag := names.NewVolumeTag(label)
   324  			vol := storage.Volume{
   325  				volumeTag,
   326  				storage.VolumeInfo{
   327  					VolumeId:   volumeTag.String(),
   328  					Size:       uint64(device.Size() / humanize.MiByte),
   329  					Persistent: false,
   330  				},
   331  			}
   332  			volumes = append(volumes, vol)
   333  
   334  			attachment := storage.VolumeAttachment{
   335  				volumeTag,
   336  				mTag,
   337  				storage.VolumeAttachmentInfo{
   338  					DeviceLink: device.Path(),
   339  					ReadOnly:   false,
   340  				},
   341  			}
   342  			attachments = append(attachments, attachment)
   343  		}
   344  	}
   345  	return volumes, attachments, nil
   346  }