github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/core/description/machine.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  	"github.com/juju/version"
    10  	"gopkg.in/juju/names.v2"
    11  )
    12  
    13  type machines struct {
    14  	Version   int        `yaml:"version"`
    15  	Machines_ []*machine `yaml:"machines"`
    16  }
    17  
    18  type machine struct {
    19  	Id_            string         `yaml:"id"`
    20  	Nonce_         string         `yaml:"nonce"`
    21  	PasswordHash_  string         `yaml:"password-hash"`
    22  	Placement_     string         `yaml:"placement,omitempty"`
    23  	Instance_      *cloudInstance `yaml:"instance,omitempty"`
    24  	Series_        string         `yaml:"series"`
    25  	ContainerType_ string         `yaml:"container-type,omitempty"`
    26  
    27  	Status_        *status `yaml:"status"`
    28  	StatusHistory_ `yaml:"status-history"`
    29  
    30  	ProviderAddresses_ []*address `yaml:"provider-addresses,omitempty"`
    31  	MachineAddresses_  []*address `yaml:"machine-addresses,omitempty"`
    32  
    33  	PreferredPublicAddress_  *address `yaml:"preferred-public-address,omitempty"`
    34  	PreferredPrivateAddress_ *address `yaml:"preferred-private-address,omitempty"`
    35  
    36  	Tools_ *agentTools `yaml:"tools"`
    37  	Jobs_  []string    `yaml:"jobs"`
    38  
    39  	SupportedContainers_ *[]string `yaml:"supported-containers,omitempty"`
    40  
    41  	Containers_ []*machine `yaml:"containers"`
    42  
    43  	OpenedPorts_ *versionedOpenedPorts `yaml:"opened-ports,omitempty"`
    44  
    45  	Annotations_ `yaml:"annotations,omitempty"`
    46  
    47  	Constraints_ *constraints `yaml:"constraints,omitempty"`
    48  
    49  	BlockDevices_ blockdevices `yaml:"block-devices,omitempty"`
    50  }
    51  
    52  // MachineArgs is an argument struct used to add a machine to the Model.
    53  type MachineArgs struct {
    54  	Id            names.MachineTag
    55  	Nonce         string
    56  	PasswordHash  string
    57  	Placement     string
    58  	Series        string
    59  	ContainerType string
    60  	Jobs          []string
    61  	// A null value means that we don't yet know which containers
    62  	// are supported. An empty slice means 'no containers are supported'.
    63  	SupportedContainers *[]string
    64  }
    65  
    66  func newMachine(args MachineArgs) *machine {
    67  	var jobs []string
    68  	if count := len(args.Jobs); count > 0 {
    69  		jobs = make([]string, count)
    70  		copy(jobs, args.Jobs)
    71  	}
    72  	m := &machine{
    73  		Id_:            args.Id.Id(),
    74  		Nonce_:         args.Nonce,
    75  		PasswordHash_:  args.PasswordHash,
    76  		Placement_:     args.Placement,
    77  		Series_:        args.Series,
    78  		ContainerType_: args.ContainerType,
    79  		Jobs_:          jobs,
    80  		StatusHistory_: newStatusHistory(),
    81  	}
    82  	if args.SupportedContainers != nil {
    83  		supported := make([]string, len(*args.SupportedContainers))
    84  		copy(supported, *args.SupportedContainers)
    85  		m.SupportedContainers_ = &supported
    86  	}
    87  	m.setBlockDevices(nil)
    88  	return m
    89  }
    90  
    91  // Id implements Machine.
    92  func (m *machine) Id() string {
    93  	return m.Id_
    94  }
    95  
    96  // Tag implements Machine.
    97  func (m *machine) Tag() names.MachineTag {
    98  	return names.NewMachineTag(m.Id_)
    99  }
   100  
   101  // Nonce implements Machine.
   102  func (m *machine) Nonce() string {
   103  	return m.Nonce_
   104  }
   105  
   106  // PasswordHash implements Machine.
   107  func (m *machine) PasswordHash() string {
   108  	return m.PasswordHash_
   109  }
   110  
   111  // Placement implements Machine.
   112  func (m *machine) Placement() string {
   113  	return m.Placement_
   114  }
   115  
   116  // Instance implements Machine.
   117  func (m *machine) Instance() CloudInstance {
   118  	// To avoid typed nils check nil here.
   119  	if m.Instance_ == nil {
   120  		return nil
   121  	}
   122  	return m.Instance_
   123  }
   124  
   125  // SetInstance implements Machine.
   126  func (m *machine) SetInstance(args CloudInstanceArgs) {
   127  	m.Instance_ = newCloudInstance(args)
   128  }
   129  
   130  // Series implements Machine.
   131  func (m *machine) Series() string {
   132  	return m.Series_
   133  }
   134  
   135  // ContainerType implements Machine.
   136  func (m *machine) ContainerType() string {
   137  	return m.ContainerType_
   138  }
   139  
   140  // Status implements Machine.
   141  func (m *machine) Status() Status {
   142  	// To avoid typed nils check nil here.
   143  	if m.Status_ == nil {
   144  		return nil
   145  	}
   146  	return m.Status_
   147  }
   148  
   149  // SetStatus implements Machine.
   150  func (m *machine) SetStatus(args StatusArgs) {
   151  	m.Status_ = newStatus(args)
   152  }
   153  
   154  // ProviderAddresses implements Machine.
   155  func (m *machine) ProviderAddresses() []Address {
   156  	var result []Address
   157  	for _, addr := range m.ProviderAddresses_ {
   158  		result = append(result, addr)
   159  	}
   160  	return result
   161  }
   162  
   163  // MachineAddresses implements Machine.
   164  func (m *machine) MachineAddresses() []Address {
   165  	var result []Address
   166  	for _, addr := range m.MachineAddresses_ {
   167  		result = append(result, addr)
   168  	}
   169  	return result
   170  }
   171  
   172  // SetAddresses implements Machine.
   173  func (m *machine) SetAddresses(margs []AddressArgs, pargs []AddressArgs) {
   174  	m.MachineAddresses_ = nil
   175  	m.ProviderAddresses_ = nil
   176  	for _, args := range margs {
   177  		if args.Value != "" {
   178  			m.MachineAddresses_ = append(m.MachineAddresses_, newAddress(args))
   179  		}
   180  	}
   181  	for _, args := range pargs {
   182  		if args.Value != "" {
   183  			m.ProviderAddresses_ = append(m.ProviderAddresses_, newAddress(args))
   184  		}
   185  	}
   186  }
   187  
   188  // PreferredPublicAddress implements Machine.
   189  func (m *machine) PreferredPublicAddress() Address {
   190  	// To avoid typed nils check nil here.
   191  	if m.PreferredPublicAddress_ == nil {
   192  		return nil
   193  	}
   194  	return m.PreferredPublicAddress_
   195  }
   196  
   197  // PreferredPrivateAddress implements Machine.
   198  func (m *machine) PreferredPrivateAddress() Address {
   199  	// To avoid typed nils check nil here.
   200  	if m.PreferredPrivateAddress_ == nil {
   201  		return nil
   202  	}
   203  	return m.PreferredPrivateAddress_
   204  }
   205  
   206  // SetPreferredAddresses implements Machine.
   207  func (m *machine) SetPreferredAddresses(public AddressArgs, private AddressArgs) {
   208  	if public.Value != "" {
   209  		m.PreferredPublicAddress_ = newAddress(public)
   210  	}
   211  	if private.Value != "" {
   212  		m.PreferredPrivateAddress_ = newAddress(private)
   213  	}
   214  }
   215  
   216  // Tools implements Machine.
   217  func (m *machine) Tools() AgentTools {
   218  	// To avoid a typed nil, check before returning.
   219  	if m.Tools_ == nil {
   220  		return nil
   221  	}
   222  	return m.Tools_
   223  }
   224  
   225  // SetTools implements Machine.
   226  func (m *machine) SetTools(args AgentToolsArgs) {
   227  	m.Tools_ = newAgentTools(args)
   228  }
   229  
   230  // Jobs implements Machine.
   231  func (m *machine) Jobs() []string {
   232  	return m.Jobs_
   233  }
   234  
   235  // SupportedContainers implements Machine.
   236  func (m *machine) SupportedContainers() ([]string, bool) {
   237  	if m.SupportedContainers_ == nil {
   238  		return nil, false
   239  	}
   240  	return *m.SupportedContainers_, true
   241  }
   242  
   243  // Containers implements Machine.
   244  func (m *machine) Containers() []Machine {
   245  	var result []Machine
   246  	for _, container := range m.Containers_ {
   247  		result = append(result, container)
   248  	}
   249  	return result
   250  }
   251  
   252  // BlockDevices implements Machine.
   253  func (m *machine) BlockDevices() []BlockDevice {
   254  	var result []BlockDevice
   255  	for _, device := range m.BlockDevices_.BlockDevices_ {
   256  		result = append(result, device)
   257  	}
   258  	return result
   259  }
   260  
   261  // AddBlockDevice implements Machine.
   262  func (m *machine) AddBlockDevice(args BlockDeviceArgs) BlockDevice {
   263  	return m.BlockDevices_.add(args)
   264  }
   265  
   266  func (m *machine) setBlockDevices(devices []*blockdevice) {
   267  	m.BlockDevices_ = blockdevices{
   268  		Version:       1,
   269  		BlockDevices_: devices,
   270  	}
   271  }
   272  
   273  // AddContainer implements Machine.
   274  func (m *machine) AddContainer(args MachineArgs) Machine {
   275  	container := newMachine(args)
   276  	m.Containers_ = append(m.Containers_, container)
   277  	return container
   278  }
   279  
   280  // OpenedPorts implements Machine.
   281  func (m *machine) OpenedPorts() []OpenedPorts {
   282  	if m.OpenedPorts_ == nil {
   283  		return nil
   284  	}
   285  	var result []OpenedPorts
   286  	for _, ports := range m.OpenedPorts_.OpenedPorts_ {
   287  		result = append(result, ports)
   288  	}
   289  	return result
   290  }
   291  
   292  // AddOpenedPorts implements Machine.
   293  func (m *machine) AddOpenedPorts(args OpenedPortsArgs) OpenedPorts {
   294  	if m.OpenedPorts_ == nil {
   295  		m.OpenedPorts_ = &versionedOpenedPorts{Version: 1}
   296  	}
   297  	ports := newOpenedPorts(args)
   298  	m.OpenedPorts_.OpenedPorts_ = append(m.OpenedPorts_.OpenedPorts_, ports)
   299  	return ports
   300  }
   301  
   302  func (m *machine) setOpenedPorts(portsList []*openedPorts) {
   303  	m.OpenedPorts_ = &versionedOpenedPorts{
   304  		Version:      1,
   305  		OpenedPorts_: portsList,
   306  	}
   307  }
   308  
   309  // Constraints implements HasConstraints.
   310  func (m *machine) Constraints() Constraints {
   311  	if m.Constraints_ == nil {
   312  		return nil
   313  	}
   314  	return m.Constraints_
   315  }
   316  
   317  // SetConstraints implements HasConstraints.
   318  func (m *machine) SetConstraints(args ConstraintsArgs) {
   319  	m.Constraints_ = newConstraints(args)
   320  }
   321  
   322  // Validate implements Machine.
   323  func (m *machine) Validate() error {
   324  	if m.Id_ == "" {
   325  		return errors.NotValidf("machine missing id")
   326  	}
   327  	if m.Status_ == nil {
   328  		return errors.NotValidf("machine %q missing status", m.Id_)
   329  	}
   330  	// Since all exports should be done when machines are stable,
   331  	// there should always be tools and cloud instance.
   332  	if m.Tools_ == nil {
   333  		return errors.NotValidf("machine %q missing tools", m.Id_)
   334  	}
   335  	if m.Instance_ == nil {
   336  		return errors.NotValidf("machine %q missing instance", m.Id_)
   337  	}
   338  	for _, container := range m.Containers_ {
   339  		if err := container.Validate(); err != nil {
   340  			return errors.Trace(err)
   341  		}
   342  	}
   343  
   344  	return nil
   345  }
   346  
   347  func importMachines(source map[string]interface{}) ([]*machine, error) {
   348  	checker := versionedChecker("machines")
   349  	coerced, err := checker.Coerce(source, nil)
   350  	if err != nil {
   351  		return nil, errors.Annotatef(err, "machines version schema check failed")
   352  	}
   353  	valid := coerced.(map[string]interface{})
   354  
   355  	version := int(valid["version"].(int64))
   356  	importFunc, ok := machineDeserializationFuncs[version]
   357  	if !ok {
   358  		return nil, errors.NotValidf("version %d", version)
   359  	}
   360  	sourceList := valid["machines"].([]interface{})
   361  	return importMachineList(sourceList, importFunc)
   362  }
   363  
   364  func importMachineList(sourceList []interface{}, importFunc machineDeserializationFunc) ([]*machine, error) {
   365  	result := make([]*machine, 0, len(sourceList))
   366  	for i, value := range sourceList {
   367  		source, ok := value.(map[string]interface{})
   368  		if !ok {
   369  			return nil, errors.Errorf("unexpected value for machine %d, %T", i, value)
   370  		}
   371  		machine, err := importFunc(source)
   372  		if err != nil {
   373  			return nil, errors.Annotatef(err, "machine %d", i)
   374  		}
   375  		result = append(result, machine)
   376  	}
   377  	return result, nil
   378  }
   379  
   380  type machineDeserializationFunc func(map[string]interface{}) (*machine, error)
   381  
   382  var machineDeserializationFuncs = map[int]machineDeserializationFunc{
   383  	1: importMachineV1,
   384  }
   385  
   386  func importMachineV1(source map[string]interface{}) (*machine, error) {
   387  	fields := schema.Fields{
   388  		"id":                   schema.String(),
   389  		"nonce":                schema.String(),
   390  		"password-hash":        schema.String(),
   391  		"placement":            schema.String(),
   392  		"instance":             schema.StringMap(schema.Any()),
   393  		"series":               schema.String(),
   394  		"container-type":       schema.String(),
   395  		"jobs":                 schema.List(schema.String()),
   396  		"status":               schema.StringMap(schema.Any()),
   397  		"supported-containers": schema.List(schema.String()),
   398  		"tools":                schema.StringMap(schema.Any()),
   399  		"containers":           schema.List(schema.StringMap(schema.Any())),
   400  		"opened-ports":         schema.StringMap(schema.Any()),
   401  
   402  		"provider-addresses":        schema.List(schema.StringMap(schema.Any())),
   403  		"machine-addresses":         schema.List(schema.StringMap(schema.Any())),
   404  		"preferred-public-address":  schema.StringMap(schema.Any()),
   405  		"preferred-private-address": schema.StringMap(schema.Any()),
   406  
   407  		"block-devices": schema.StringMap(schema.Any()),
   408  	}
   409  
   410  	defaults := schema.Defaults{
   411  		"placement":      "",
   412  		"container-type": "",
   413  		// Even though we are expecting instance data for every machine,
   414  		// it isn't strictly necessary, so we allow it to not exist here.
   415  		"instance":                  schema.Omit,
   416  		"supported-containers":      schema.Omit,
   417  		"opened-ports":              schema.Omit,
   418  		"block-devices":             schema.Omit,
   419  		"provider-addresses":        schema.Omit,
   420  		"machine-addresses":         schema.Omit,
   421  		"preferred-public-address":  schema.Omit,
   422  		"preferred-private-address": schema.Omit,
   423  	}
   424  	addAnnotationSchema(fields, defaults)
   425  	addConstraintsSchema(fields, defaults)
   426  	addStatusHistorySchema(fields)
   427  	checker := schema.FieldMap(fields, defaults)
   428  
   429  	coerced, err := checker.Coerce(source, nil)
   430  	if err != nil {
   431  		return nil, errors.Annotatef(err, "machine v1 schema check failed")
   432  	}
   433  	valid := coerced.(map[string]interface{})
   434  	// From here we know that the map returned from the schema coercion
   435  	// contains fields of the right type.
   436  	result := &machine{
   437  		Id_:            valid["id"].(string),
   438  		Nonce_:         valid["nonce"].(string),
   439  		PasswordHash_:  valid["password-hash"].(string),
   440  		Placement_:     valid["placement"].(string),
   441  		Series_:        valid["series"].(string),
   442  		ContainerType_: valid["container-type"].(string),
   443  		StatusHistory_: newStatusHistory(),
   444  		Jobs_:          convertToStringSlice(valid["jobs"]),
   445  	}
   446  	result.importAnnotations(valid)
   447  	if err := result.importStatusHistory(valid); err != nil {
   448  		return nil, errors.Trace(err)
   449  	}
   450  
   451  	if constraintsMap, ok := valid["constraints"]; ok {
   452  		constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
   453  		if err != nil {
   454  			return nil, errors.Trace(err)
   455  		}
   456  		result.Constraints_ = constraints
   457  	}
   458  
   459  	if supported, ok := valid["supported-containers"]; ok {
   460  		supportedList := supported.([]interface{})
   461  		s := make([]string, len(supportedList))
   462  		for i, containerType := range supportedList {
   463  			s[i] = containerType.(string)
   464  		}
   465  		result.SupportedContainers_ = &s
   466  	}
   467  
   468  	if instanceMap, ok := valid["instance"]; ok {
   469  		instance, err := importCloudInstance(instanceMap.(map[string]interface{}))
   470  		if err != nil {
   471  			return nil, errors.Trace(err)
   472  		}
   473  		result.Instance_ = instance
   474  	}
   475  
   476  	if blockDeviceMap, ok := valid["block-devices"]; ok {
   477  		devices, err := importBlockDevices(blockDeviceMap.(map[string]interface{}))
   478  		if err != nil {
   479  			return nil, errors.Trace(err)
   480  		}
   481  		result.setBlockDevices(devices)
   482  	} else {
   483  		result.setBlockDevices(nil)
   484  	}
   485  
   486  	// Tools and status are required, so we expect them to be there.
   487  	tools, err := importAgentTools(valid["tools"].(map[string]interface{}))
   488  	if err != nil {
   489  		return nil, errors.Trace(err)
   490  	}
   491  	result.Tools_ = tools
   492  
   493  	status, err := importStatus(valid["status"].(map[string]interface{}))
   494  	if err != nil {
   495  		return nil, errors.Trace(err)
   496  	}
   497  	result.Status_ = status
   498  
   499  	if addresses, ok := valid["provider-addresses"]; ok {
   500  		providerAddresses, err := importAddresses(addresses.([]interface{}))
   501  		if err != nil {
   502  			return nil, errors.Trace(err)
   503  		}
   504  		result.ProviderAddresses_ = providerAddresses
   505  	}
   506  
   507  	if addresses, ok := valid["machine-addresses"]; ok {
   508  		machineAddresses, err := importAddresses(addresses.([]interface{}))
   509  		if err != nil {
   510  			return nil, errors.Trace(err)
   511  		}
   512  		result.MachineAddresses_ = machineAddresses
   513  	}
   514  
   515  	if address, ok := valid["preferred-public-address"]; ok {
   516  		publicAddress, err := importAddress(address.(map[string]interface{}))
   517  		if err != nil {
   518  			return nil, errors.Trace(err)
   519  		}
   520  		result.PreferredPublicAddress_ = publicAddress
   521  	}
   522  
   523  	if address, ok := valid["preferred-private-address"]; ok {
   524  		privateAddress, err := importAddress(address.(map[string]interface{}))
   525  		if err != nil {
   526  			return nil, errors.Trace(err)
   527  		}
   528  		result.PreferredPrivateAddress_ = privateAddress
   529  	}
   530  
   531  	machineList := valid["containers"].([]interface{})
   532  	machines, err := importMachineList(machineList, importMachineV1)
   533  	if err != nil {
   534  		return nil, errors.Annotatef(err, "containers")
   535  	}
   536  	result.Containers_ = machines
   537  
   538  	if portsMap, ok := valid["opened-ports"]; ok {
   539  		portsList, err := importOpenedPorts(portsMap.(map[string]interface{}))
   540  		if err != nil {
   541  			return nil, errors.Trace(err)
   542  		}
   543  		result.setOpenedPorts(portsList)
   544  	}
   545  
   546  	return result, nil
   547  
   548  }
   549  
   550  // CloudInstanceArgs is an argument struct used to add information about the
   551  // cloud instance to a Machine.
   552  type CloudInstanceArgs struct {
   553  	InstanceId       string
   554  	Status           string
   555  	Architecture     string
   556  	Memory           uint64
   557  	RootDisk         uint64
   558  	CpuCores         uint64
   559  	CpuPower         uint64
   560  	Tags             []string
   561  	AvailabilityZone string
   562  }
   563  
   564  func newCloudInstance(args CloudInstanceArgs) *cloudInstance {
   565  	tags := make([]string, len(args.Tags))
   566  	copy(tags, args.Tags)
   567  	return &cloudInstance{
   568  		Version:           1,
   569  		InstanceId_:       args.InstanceId,
   570  		Status_:           args.Status,
   571  		Architecture_:     args.Architecture,
   572  		Memory_:           args.Memory,
   573  		RootDisk_:         args.RootDisk,
   574  		CpuCores_:         args.CpuCores,
   575  		CpuPower_:         args.CpuPower,
   576  		Tags_:             tags,
   577  		AvailabilityZone_: args.AvailabilityZone,
   578  	}
   579  }
   580  
   581  type cloudInstance struct {
   582  	Version int `yaml:"version"`
   583  
   584  	InstanceId_ string `yaml:"instance-id"`
   585  	Status_     string `yaml:"status"`
   586  	// For all the optional values, empty values make no sense, and
   587  	// it would be better to have them not set rather than set with
   588  	// a nonsense value.
   589  	Architecture_     string   `yaml:"architecture,omitempty"`
   590  	Memory_           uint64   `yaml:"memory,omitempty"`
   591  	RootDisk_         uint64   `yaml:"root-disk,omitempty"`
   592  	CpuCores_         uint64   `yaml:"cores,omitempty"`
   593  	CpuPower_         uint64   `yaml:"cpu-power,omitempty"`
   594  	Tags_             []string `yaml:"tags,omitempty"`
   595  	AvailabilityZone_ string   `yaml:"availability-zone,omitempty"`
   596  }
   597  
   598  // InstanceId implements CloudInstance.
   599  func (c *cloudInstance) InstanceId() string {
   600  	return c.InstanceId_
   601  }
   602  
   603  // Status implements CloudInstance.
   604  func (c *cloudInstance) Status() string {
   605  	return c.Status_
   606  }
   607  
   608  // Architecture implements CloudInstance.
   609  func (c *cloudInstance) Architecture() string {
   610  	return c.Architecture_
   611  }
   612  
   613  // Memory implements CloudInstance.
   614  func (c *cloudInstance) Memory() uint64 {
   615  	return c.Memory_
   616  }
   617  
   618  // RootDisk implements CloudInstance.
   619  func (c *cloudInstance) RootDisk() uint64 {
   620  	return c.RootDisk_
   621  }
   622  
   623  // CpuCores implements CloudInstance.
   624  func (c *cloudInstance) CpuCores() uint64 {
   625  	return c.CpuCores_
   626  }
   627  
   628  // CpuPower implements CloudInstance.
   629  func (c *cloudInstance) CpuPower() uint64 {
   630  	return c.CpuPower_
   631  }
   632  
   633  // Tags implements CloudInstance.
   634  func (c *cloudInstance) Tags() []string {
   635  	tags := make([]string, len(c.Tags_))
   636  	copy(tags, c.Tags_)
   637  	return tags
   638  }
   639  
   640  // AvailabilityZone implements CloudInstance.
   641  func (c *cloudInstance) AvailabilityZone() string {
   642  	return c.AvailabilityZone_
   643  }
   644  
   645  func importCloudInstance(source map[string]interface{}) (*cloudInstance, error) {
   646  	version, err := getVersion(source)
   647  	if err != nil {
   648  		return nil, errors.Annotate(err, "cloudInstance version schema check failed")
   649  	}
   650  
   651  	importFunc, ok := cloudInstanceDeserializationFuncs[version]
   652  	if !ok {
   653  		return nil, errors.NotValidf("version %d", version)
   654  	}
   655  
   656  	return importFunc(source)
   657  }
   658  
   659  type cloudInstanceDeserializationFunc func(map[string]interface{}) (*cloudInstance, error)
   660  
   661  var cloudInstanceDeserializationFuncs = map[int]cloudInstanceDeserializationFunc{
   662  	1: importCloudInstanceV1,
   663  }
   664  
   665  func importCloudInstanceV1(source map[string]interface{}) (*cloudInstance, error) {
   666  	fields := schema.Fields{
   667  		"instance-id":       schema.String(),
   668  		"status":            schema.String(),
   669  		"architecture":      schema.String(),
   670  		"memory":            schema.ForceUint(),
   671  		"root-disk":         schema.ForceUint(),
   672  		"cores":             schema.ForceUint(),
   673  		"cpu-power":         schema.ForceUint(),
   674  		"tags":              schema.List(schema.String()),
   675  		"availability-zone": schema.String(),
   676  	}
   677  	// Some values don't have to be there.
   678  	defaults := schema.Defaults{
   679  		"architecture":      "",
   680  		"memory":            uint64(0),
   681  		"root-disk":         uint64(0),
   682  		"cores":             uint64(0),
   683  		"cpu-power":         uint64(0),
   684  		"tags":              schema.Omit,
   685  		"availability-zone": "",
   686  	}
   687  	checker := schema.FieldMap(fields, defaults)
   688  
   689  	coerced, err := checker.Coerce(source, nil)
   690  	if err != nil {
   691  		return nil, errors.Annotatef(err, "cloudInstance v1 schema check failed")
   692  	}
   693  	valid := coerced.(map[string]interface{})
   694  	// From here we know that the map returned from the schema coercion
   695  	// contains fields of the right type.
   696  
   697  	return &cloudInstance{
   698  		Version:           1,
   699  		InstanceId_:       valid["instance-id"].(string),
   700  		Status_:           valid["status"].(string),
   701  		Architecture_:     valid["architecture"].(string),
   702  		Memory_:           valid["memory"].(uint64),
   703  		RootDisk_:         valid["root-disk"].(uint64),
   704  		CpuCores_:         valid["cores"].(uint64),
   705  		CpuPower_:         valid["cpu-power"].(uint64),
   706  		Tags_:             convertToStringSlice(valid["tags"]),
   707  		AvailabilityZone_: valid["availability-zone"].(string),
   708  	}, nil
   709  }
   710  
   711  // AgentToolsArgs is an argument struct used to add information about the
   712  // tools the agent is using to a Machine.
   713  type AgentToolsArgs struct {
   714  	Version version.Binary
   715  	URL     string
   716  	SHA256  string
   717  	Size    int64
   718  }
   719  
   720  func newAgentTools(args AgentToolsArgs) *agentTools {
   721  	return &agentTools{
   722  		Version_:      1,
   723  		ToolsVersion_: args.Version,
   724  		URL_:          args.URL,
   725  		SHA256_:       args.SHA256,
   726  		Size_:         args.Size,
   727  	}
   728  }
   729  
   730  // Keeping the agentTools with the machine code, because we hope
   731  // that one day we will succeed in merging the unit agents with the
   732  // machine agents.
   733  type agentTools struct {
   734  	Version_      int            `yaml:"version"`
   735  	ToolsVersion_ version.Binary `yaml:"tools-version"`
   736  	URL_          string         `yaml:"url"`
   737  	SHA256_       string         `yaml:"sha256"`
   738  	Size_         int64          `yaml:"size"`
   739  }
   740  
   741  // Version implements AgentTools.
   742  func (a *agentTools) Version() version.Binary {
   743  	return a.ToolsVersion_
   744  }
   745  
   746  // URL implements AgentTools.
   747  func (a *agentTools) URL() string {
   748  	return a.URL_
   749  }
   750  
   751  // SHA256 implements AgentTools.
   752  func (a *agentTools) SHA256() string {
   753  	return a.SHA256_
   754  }
   755  
   756  // Size implements AgentTools.
   757  func (a *agentTools) Size() int64 {
   758  	return a.Size_
   759  }
   760  
   761  func importAgentTools(source map[string]interface{}) (*agentTools, error) {
   762  	version, err := getVersion(source)
   763  	if err != nil {
   764  		return nil, errors.Annotate(err, "agentTools version schema check failed")
   765  	}
   766  
   767  	importFunc, ok := agentToolsDeserializationFuncs[version]
   768  	if !ok {
   769  		return nil, errors.NotValidf("version %d", version)
   770  	}
   771  
   772  	return importFunc(source)
   773  }
   774  
   775  type agentToolsDeserializationFunc func(map[string]interface{}) (*agentTools, error)
   776  
   777  var agentToolsDeserializationFuncs = map[int]agentToolsDeserializationFunc{
   778  	1: importAgentToolsV1,
   779  }
   780  
   781  func importAgentToolsV1(source map[string]interface{}) (*agentTools, error) {
   782  	fields := schema.Fields{
   783  		"tools-version": schema.String(),
   784  		"url":           schema.String(),
   785  		"sha256":        schema.String(),
   786  		"size":          schema.Int(),
   787  	}
   788  	checker := schema.FieldMap(fields, nil) // no defaults
   789  
   790  	coerced, err := checker.Coerce(source, nil)
   791  	if err != nil {
   792  		return nil, errors.Annotatef(err, "agentTools v1 schema check failed")
   793  	}
   794  	valid := coerced.(map[string]interface{})
   795  	// From here we know that the map returned from the schema coercion
   796  	// contains fields of the right type.
   797  
   798  	verString := valid["tools-version"].(string)
   799  	toolsVersion, err := version.ParseBinary(verString)
   800  	if err != nil {
   801  		return nil, errors.Annotatef(err, "agentTools tools-version")
   802  	}
   803  
   804  	return &agentTools{
   805  		Version_:      1,
   806  		ToolsVersion_: toolsVersion,
   807  		URL_:          valid["url"].(string),
   808  		SHA256_:       valid["sha256"].(string),
   809  		Size_:         valid["size"].(int64),
   810  	}, nil
   811  }