github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/core/description/volume.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package description
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/schema"
     9  	"gopkg.in/juju/names.v2"
    10  )
    11  
    12  type volumes struct {
    13  	Version  int       `yaml:"version"`
    14  	Volumes_ []*volume `yaml:"volumes"`
    15  }
    16  
    17  type volume struct {
    18  	ID_          string `yaml:"id"`
    19  	Binding_     string `yaml:"binding,omitempty"`
    20  	StorageID_   string `yaml:"storage-id,omitempty"`
    21  	Provisioned_ bool   `yaml:"provisioned"`
    22  	Size_        uint64 `yaml:"size"`
    23  	Pool_        string `yaml:"pool,omitempty"`
    24  	HardwareID_  string `yaml:"hardware-id,omitempty"`
    25  	VolumeID_    string `yaml:"volume-id,omitempty"`
    26  	Persistent_  bool   `yaml:"persistent"`
    27  
    28  	Status_        *status `yaml:"status"`
    29  	StatusHistory_ `yaml:"status-history"`
    30  
    31  	Attachments_ volumeAttachments `yaml:"attachments"`
    32  }
    33  
    34  type volumeAttachments struct {
    35  	Version      int                 `yaml:"version"`
    36  	Attachments_ []*volumeAttachment `yaml:"attachments"`
    37  }
    38  
    39  type volumeAttachment struct {
    40  	MachineID_   string `yaml:"machine-id"`
    41  	Provisioned_ bool   `yaml:"provisioned"`
    42  	ReadOnly_    bool   `yaml:"read-only"`
    43  	DeviceName_  string `yaml:"device-name"`
    44  	DeviceLink_  string `yaml:"device-link"`
    45  	BusAddress_  string `yaml:"bus-address"`
    46  }
    47  
    48  // VolumeArgs is an argument struct used to add a volume to the Model.
    49  type VolumeArgs struct {
    50  	Tag         names.VolumeTag
    51  	Storage     names.StorageTag
    52  	Binding     names.Tag
    53  	Provisioned bool
    54  	Size        uint64
    55  	Pool        string
    56  	HardwareID  string
    57  	VolumeID    string
    58  	Persistent  bool
    59  }
    60  
    61  func newVolume(args VolumeArgs) *volume {
    62  	v := &volume{
    63  		ID_:            args.Tag.Id(),
    64  		StorageID_:     args.Storage.Id(),
    65  		Provisioned_:   args.Provisioned,
    66  		Size_:          args.Size,
    67  		Pool_:          args.Pool,
    68  		HardwareID_:    args.HardwareID,
    69  		VolumeID_:      args.VolumeID,
    70  		Persistent_:    args.Persistent,
    71  		StatusHistory_: newStatusHistory(),
    72  	}
    73  	if args.Binding != nil {
    74  		v.Binding_ = args.Binding.String()
    75  	}
    76  	v.setAttachments(nil)
    77  	return v
    78  }
    79  
    80  // Tag implements Volume.
    81  func (v *volume) Tag() names.VolumeTag {
    82  	return names.NewVolumeTag(v.ID_)
    83  }
    84  
    85  // Storage implements Volume.
    86  func (v *volume) Storage() names.StorageTag {
    87  	if v.StorageID_ == "" {
    88  		return names.StorageTag{}
    89  	}
    90  	return names.NewStorageTag(v.StorageID_)
    91  }
    92  
    93  // Binding implements Volume.
    94  func (v *volume) Binding() (names.Tag, error) {
    95  	if v.Binding_ == "" {
    96  		return nil, nil
    97  	}
    98  	tag, err := names.ParseTag(v.Binding_)
    99  	if err != nil {
   100  		return nil, errors.Trace(err)
   101  	}
   102  	return tag, nil
   103  }
   104  
   105  // Provisioned implements Volume.
   106  func (v *volume) Provisioned() bool {
   107  	return v.Provisioned_
   108  }
   109  
   110  // Size implements Volume.
   111  func (v *volume) Size() uint64 {
   112  	return v.Size_
   113  }
   114  
   115  // Pool implements Volume.
   116  func (v *volume) Pool() string {
   117  	return v.Pool_
   118  }
   119  
   120  // HardwareID implements Volume.
   121  func (v *volume) HardwareID() string {
   122  	return v.HardwareID_
   123  }
   124  
   125  // VolumeID implements Volume.
   126  func (v *volume) VolumeID() string {
   127  	return v.VolumeID_
   128  }
   129  
   130  // Persistent implements Volume.
   131  func (v *volume) Persistent() bool {
   132  	return v.Persistent_
   133  }
   134  
   135  // Status implements Volume.
   136  func (v *volume) Status() Status {
   137  	// To avoid typed nils check nil here.
   138  	if v.Status_ == nil {
   139  		return nil
   140  	}
   141  	return v.Status_
   142  }
   143  
   144  // SetStatus implements Volume.
   145  func (v *volume) SetStatus(args StatusArgs) {
   146  	v.Status_ = newStatus(args)
   147  }
   148  
   149  func (v *volume) setAttachments(attachments []*volumeAttachment) {
   150  	v.Attachments_ = volumeAttachments{
   151  		Version:      1,
   152  		Attachments_: attachments,
   153  	}
   154  }
   155  
   156  // Attachments implements Volume.
   157  func (v *volume) Attachments() []VolumeAttachment {
   158  	var result []VolumeAttachment
   159  	for _, attachment := range v.Attachments_.Attachments_ {
   160  		result = append(result, attachment)
   161  	}
   162  	return result
   163  }
   164  
   165  // AddAttachment implements Volume.
   166  func (v *volume) AddAttachment(args VolumeAttachmentArgs) VolumeAttachment {
   167  	a := newVolumeAttachment(args)
   168  	v.Attachments_.Attachments_ = append(v.Attachments_.Attachments_, a)
   169  	return a
   170  }
   171  
   172  // Validate implements Volume.
   173  func (v *volume) Validate() error {
   174  	if v.ID_ == "" {
   175  		return errors.NotValidf("volume missing id")
   176  	}
   177  	if v.Size_ == 0 {
   178  		return errors.NotValidf("volume %q missing size", v.ID_)
   179  	}
   180  	if v.Status_ == nil {
   181  		return errors.NotValidf("volume %q missing status", v.ID_)
   182  	}
   183  	if _, err := v.Binding(); err != nil {
   184  		return errors.Wrap(err, errors.NotValidf("volume %q binding", v.ID_))
   185  	}
   186  	return nil
   187  }
   188  
   189  func importVolumes(source map[string]interface{}) ([]*volume, error) {
   190  	checker := versionedChecker("volumes")
   191  	coerced, err := checker.Coerce(source, nil)
   192  	if err != nil {
   193  		return nil, errors.Annotatef(err, "volumes version schema check failed")
   194  	}
   195  	valid := coerced.(map[string]interface{})
   196  
   197  	version := int(valid["version"].(int64))
   198  	importFunc, ok := volumeDeserializationFuncs[version]
   199  	if !ok {
   200  		return nil, errors.NotValidf("version %d", version)
   201  	}
   202  	sourceList := valid["volumes"].([]interface{})
   203  	return importVolumeList(sourceList, importFunc)
   204  }
   205  
   206  func importVolumeList(sourceList []interface{}, importFunc volumeDeserializationFunc) ([]*volume, error) {
   207  	result := make([]*volume, 0, len(sourceList))
   208  	for i, value := range sourceList {
   209  		source, ok := value.(map[string]interface{})
   210  		if !ok {
   211  			return nil, errors.Errorf("unexpected value for volume %d, %T", i, value)
   212  		}
   213  		volume, err := importFunc(source)
   214  		if err != nil {
   215  			return nil, errors.Annotatef(err, "volume %d", i)
   216  		}
   217  		result = append(result, volume)
   218  	}
   219  	return result, nil
   220  }
   221  
   222  type volumeDeserializationFunc func(map[string]interface{}) (*volume, error)
   223  
   224  var volumeDeserializationFuncs = map[int]volumeDeserializationFunc{
   225  	1: importVolumeV1,
   226  }
   227  
   228  func importVolumeV1(source map[string]interface{}) (*volume, error) {
   229  	fields := schema.Fields{
   230  		"id":          schema.String(),
   231  		"storage-id":  schema.String(),
   232  		"binding":     schema.String(),
   233  		"provisioned": schema.Bool(),
   234  		"size":        schema.ForceUint(),
   235  		"pool":        schema.String(),
   236  		"hardware-id": schema.String(),
   237  		"volume-id":   schema.String(),
   238  		"persistent":  schema.Bool(),
   239  		"status":      schema.StringMap(schema.Any()),
   240  		"attachments": schema.StringMap(schema.Any()),
   241  	}
   242  
   243  	defaults := schema.Defaults{
   244  		"storage-id":  "",
   245  		"binding":     "",
   246  		"pool":        "",
   247  		"hardware-id": "",
   248  		"volume-id":   "",
   249  		"attachments": schema.Omit,
   250  	}
   251  	addStatusHistorySchema(fields)
   252  	checker := schema.FieldMap(fields, defaults)
   253  
   254  	coerced, err := checker.Coerce(source, nil)
   255  	if err != nil {
   256  		return nil, errors.Annotatef(err, "volume v1 schema check failed")
   257  	}
   258  	valid := coerced.(map[string]interface{})
   259  	// From here we know that the map returned from the schema coercion
   260  	// contains fields of the right type.
   261  	result := &volume{
   262  		ID_:            valid["id"].(string),
   263  		StorageID_:     valid["storage-id"].(string),
   264  		Binding_:       valid["binding"].(string),
   265  		Provisioned_:   valid["provisioned"].(bool),
   266  		Size_:          valid["size"].(uint64),
   267  		Pool_:          valid["pool"].(string),
   268  		HardwareID_:    valid["hardware-id"].(string),
   269  		VolumeID_:      valid["volume-id"].(string),
   270  		Persistent_:    valid["persistent"].(bool),
   271  		StatusHistory_: newStatusHistory(),
   272  	}
   273  	if err := result.importStatusHistory(valid); err != nil {
   274  		return nil, errors.Trace(err)
   275  	}
   276  
   277  	status, err := importStatus(valid["status"].(map[string]interface{}))
   278  	if err != nil {
   279  		return nil, errors.Trace(err)
   280  	}
   281  	result.Status_ = status
   282  
   283  	attachments, err := importVolumeAttachments(valid["attachments"].(map[string]interface{}))
   284  	if err != nil {
   285  		return nil, errors.Trace(err)
   286  	}
   287  	result.setAttachments(attachments)
   288  
   289  	return result, nil
   290  }
   291  
   292  // VolumeAttachmentArgs is an argument struct used to add information about the
   293  // cloud instance to a Volume.
   294  type VolumeAttachmentArgs struct {
   295  	Machine     names.MachineTag
   296  	Provisioned bool
   297  	ReadOnly    bool
   298  	DeviceName  string
   299  	DeviceLink  string
   300  	BusAddress  string
   301  }
   302  
   303  func newVolumeAttachment(args VolumeAttachmentArgs) *volumeAttachment {
   304  	return &volumeAttachment{
   305  		MachineID_:   args.Machine.Id(),
   306  		Provisioned_: args.Provisioned,
   307  		ReadOnly_:    args.ReadOnly,
   308  		DeviceName_:  args.DeviceName,
   309  		DeviceLink_:  args.DeviceLink,
   310  		BusAddress_:  args.BusAddress,
   311  	}
   312  }
   313  
   314  // Machine implements VolumeAttachment
   315  func (a *volumeAttachment) Machine() names.MachineTag {
   316  	return names.NewMachineTag(a.MachineID_)
   317  }
   318  
   319  // Provisioned implements VolumeAttachment
   320  func (a *volumeAttachment) Provisioned() bool {
   321  	return a.Provisioned_
   322  }
   323  
   324  // ReadOnly implements VolumeAttachment
   325  func (a *volumeAttachment) ReadOnly() bool {
   326  	return a.ReadOnly_
   327  }
   328  
   329  // DeviceName implements VolumeAttachment
   330  func (a *volumeAttachment) DeviceName() string {
   331  	return a.DeviceName_
   332  }
   333  
   334  // DeviceLink implements VolumeAttachment
   335  func (a *volumeAttachment) DeviceLink() string {
   336  	return a.DeviceLink_
   337  }
   338  
   339  // BusAddress implements VolumeAttachment
   340  func (a *volumeAttachment) BusAddress() string {
   341  	return a.BusAddress_
   342  }
   343  
   344  func importVolumeAttachments(source map[string]interface{}) ([]*volumeAttachment, error) {
   345  	checker := versionedChecker("attachments")
   346  	coerced, err := checker.Coerce(source, nil)
   347  	if err != nil {
   348  		return nil, errors.Annotatef(err, "volume attachments version schema check failed")
   349  	}
   350  	valid := coerced.(map[string]interface{})
   351  
   352  	version := int(valid["version"].(int64))
   353  	importFunc, ok := volumeAttachmentDeserializationFuncs[version]
   354  	if !ok {
   355  		return nil, errors.NotValidf("version %d", version)
   356  	}
   357  	sourceList := valid["attachments"].([]interface{})
   358  	return importVolumeAttachmentList(sourceList, importFunc)
   359  }
   360  
   361  func importVolumeAttachmentList(sourceList []interface{}, importFunc volumeAttachmentDeserializationFunc) ([]*volumeAttachment, error) {
   362  	result := make([]*volumeAttachment, 0, len(sourceList))
   363  	for i, value := range sourceList {
   364  		source, ok := value.(map[string]interface{})
   365  		if !ok {
   366  			return nil, errors.Errorf("unexpected value for volumeAttachment %d, %T", i, value)
   367  		}
   368  		volumeAttachment, err := importFunc(source)
   369  		if err != nil {
   370  			return nil, errors.Annotatef(err, "volumeAttachment %d", i)
   371  		}
   372  		result = append(result, volumeAttachment)
   373  	}
   374  	return result, nil
   375  }
   376  
   377  type volumeAttachmentDeserializationFunc func(map[string]interface{}) (*volumeAttachment, error)
   378  
   379  var volumeAttachmentDeserializationFuncs = map[int]volumeAttachmentDeserializationFunc{
   380  	1: importVolumeAttachmentV1,
   381  }
   382  
   383  func importVolumeAttachmentV1(source map[string]interface{}) (*volumeAttachment, error) {
   384  	fields := schema.Fields{
   385  		"machine-id":  schema.String(),
   386  		"provisioned": schema.Bool(),
   387  		"read-only":   schema.Bool(),
   388  		"device-name": schema.String(),
   389  		"device-link": schema.String(),
   390  		"bus-address": schema.String(),
   391  	}
   392  	checker := schema.FieldMap(fields, nil) // no defaults
   393  
   394  	coerced, err := checker.Coerce(source, nil)
   395  	if err != nil {
   396  		return nil, errors.Annotatef(err, "volumeAttachment v1 schema check failed")
   397  	}
   398  	valid := coerced.(map[string]interface{})
   399  	// From here we know that the map returned from the schema coercion
   400  	// contains fields of the right type.
   401  
   402  	result := &volumeAttachment{
   403  		MachineID_:   valid["machine-id"].(string),
   404  		Provisioned_: valid["provisioned"].(bool),
   405  		ReadOnly_:    valid["read-only"].(bool),
   406  		DeviceName_:  valid["device-name"].(string),
   407  		DeviceLink_:  valid["device-link"].(string),
   408  		BusAddress_:  valid["bus-address"].(string),
   409  	}
   410  	return result, nil
   411  }