1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
7
"github.com/juju/errors"
8
"github.com/juju/schema"
9
"github.com/juju/version"
10
"gopkg.in/juju/names.v2"
13
type machines struct {
14
Version int `yaml:"version"`
15
Machines_ []*machine `yaml:"machines"`
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"`
27
Status_ *status `yaml:"status"`
28
StatusHistory_ `yaml:"status-history"`
30
ProviderAddresses_ []*address `yaml:"provider-addresses,omitempty"`
31
MachineAddresses_ []*address `yaml:"machine-addresses,omitempty"`
33
PreferredPublicAddress_ *address `yaml:"preferred-public-address,omitempty"`
34
PreferredPrivateAddress_ *address `yaml:"preferred-private-address,omitempty"`
36
Tools_ *agentTools `yaml:"tools"`
37
Jobs_ []string `yaml:"jobs"`
39
SupportedContainers_ *[]string `yaml:"supported-containers,omitempty"`
41
Containers_ []*machine `yaml:"containers"`
43
OpenedPorts_ *versionedOpenedPorts `yaml:"opened-ports,omitempty"`
45
Annotations_ `yaml:"annotations,omitempty"`
47
Constraints_ *constraints `yaml:"constraints,omitempty"`
49
BlockDevices_ blockdevices `yaml:"block-devices,omitempty"`
52
// MachineArgs is an argument struct used to add a machine to the Model.
53
type MachineArgs struct {
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
66
func newMachine(args MachineArgs) *machine {
68
if count := len(args.Jobs); count > 0 {
69
jobs = make([]string, count)
75
PasswordHash_: args.PasswordHash,
76
Placement_: args.Placement,
78
ContainerType_: args.ContainerType,
80
StatusHistory_: newStatusHistory(),
82
if args.SupportedContainers != nil {
83
supported := make([]string, len(*args.SupportedContainers))
84
copy(supported, *args.SupportedContainers)
85
m.SupportedContainers_ = &supported
87
m.setBlockDevices(nil)
91
// Id implements Machine.
92
func (m *machine) Id() string {
96
// Tag implements Machine.
97
func (m *machine) Tag() names.MachineTag {
98
return names.NewMachineTag(m.Id_)
101
// Nonce implements Machine.
102
func (m *machine) Nonce() string {
106
// PasswordHash implements Machine.
107
func (m *machine) PasswordHash() string {
108
return m.PasswordHash_
111
// Placement implements Machine.
112
func (m *machine) Placement() string {
116
// Instance implements Machine.
117
func (m *machine) Instance() CloudInstance {
118
// To avoid typed nils check nil here.
119
if m.Instance_ == nil {
125
// SetInstance implements Machine.
126
func (m *machine) SetInstance(args CloudInstanceArgs) {
127
m.Instance_ = newCloudInstance(args)
130
// Series implements Machine.
131
func (m *machine) Series() string {
135
// ContainerType implements Machine.
136
func (m *machine) ContainerType() string {
137
return m.ContainerType_
140
// Status implements Machine.
141
func (m *machine) Status() Status {
142
// To avoid typed nils check nil here.
143
if m.Status_ == nil {
149
// SetStatus implements Machine.
150
func (m *machine) SetStatus(args StatusArgs) {
151
m.Status_ = newStatus(args)
154
// ProviderAddresses implements Machine.
155
func (m *machine) ProviderAddresses() []Address {
157
for _, addr := range m.ProviderAddresses_ {
158
result = append(result, addr)
163
// MachineAddresses implements Machine.
164
func (m *machine) MachineAddresses() []Address {
166
for _, addr := range m.MachineAddresses_ {
167
result = append(result, addr)
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))
181
for _, args := range pargs {
182
if args.Value != "" {
183
m.ProviderAddresses_ = append(m.ProviderAddresses_, newAddress(args))
188
// PreferredPublicAddress implements Machine.
189
func (m *machine) PreferredPublicAddress() Address {
190
// To avoid typed nils check nil here.
191
if m.PreferredPublicAddress_ == nil {
194
return m.PreferredPublicAddress_
197
// PreferredPrivateAddress implements Machine.
198
func (m *machine) PreferredPrivateAddress() Address {
199
// To avoid typed nils check nil here.
200
if m.PreferredPrivateAddress_ == nil {
203
return m.PreferredPrivateAddress_
206
// SetPreferredAddresses implements Machine.
207
func (m *machine) SetPreferredAddresses(public AddressArgs, private AddressArgs) {
208
if public.Value != "" {
209
m.PreferredPublicAddress_ = newAddress(public)
211
if private.Value != "" {
212
m.PreferredPrivateAddress_ = newAddress(private)
216
// Tools implements Machine.
217
func (m *machine) Tools() AgentTools {
218
// To avoid a typed nil, check before returning.
225
// SetTools implements Machine.
226
func (m *machine) SetTools(args AgentToolsArgs) {
227
m.Tools_ = newAgentTools(args)
230
// Jobs implements Machine.
231
func (m *machine) Jobs() []string {
235
// SupportedContainers implements Machine.
236
func (m *machine) SupportedContainers() ([]string, bool) {
237
if m.SupportedContainers_ == nil {
240
return *m.SupportedContainers_, true
243
// Containers implements Machine.
244
func (m *machine) Containers() []Machine {
246
for _, container := range m.Containers_ {
247
result = append(result, container)
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)
261
// AddBlockDevice implements Machine.
262
func (m *machine) AddBlockDevice(args BlockDeviceArgs) BlockDevice {
263
return m.BlockDevices_.add(args)
266
func (m *machine) setBlockDevices(devices []*blockdevice) {
267
m.BlockDevices_ = blockdevices{
269
BlockDevices_: devices,
273
// AddContainer implements Machine.
274
func (m *machine) AddContainer(args MachineArgs) Machine {
275
container := newMachine(args)
276
m.Containers_ = append(m.Containers_, container)
280
// OpenedPorts implements Machine.
281
func (m *machine) OpenedPorts() []OpenedPorts {
282
if m.OpenedPorts_ == nil {
285
var result []OpenedPorts
286
for _, ports := range m.OpenedPorts_.OpenedPorts_ {
287
result = append(result, ports)
292
// AddOpenedPorts implements Machine.
293
func (m *machine) AddOpenedPorts(args OpenedPortsArgs) OpenedPorts {
294
if m.OpenedPorts_ == nil {
295
m.OpenedPorts_ = &versionedOpenedPorts{Version: 1}
297
ports := newOpenedPorts(args)
298
m.OpenedPorts_.OpenedPorts_ = append(m.OpenedPorts_.OpenedPorts_, ports)
302
func (m *machine) setOpenedPorts(portsList []*openedPorts) {
303
m.OpenedPorts_ = &versionedOpenedPorts{
305
OpenedPorts_: portsList,
309
// Constraints implements HasConstraints.
310
func (m *machine) Constraints() Constraints {
311
if m.Constraints_ == nil {
314
return m.Constraints_
317
// SetConstraints implements HasConstraints.
318
func (m *machine) SetConstraints(args ConstraintsArgs) {
319
m.Constraints_ = newConstraints(args)
322
// Validate implements Machine.
323
func (m *machine) Validate() error {
325
return errors.NotValidf("machine missing id")
327
if m.Status_ == nil {
328
return errors.NotValidf("machine %q missing status", m.Id_)
330
// Since all exports should be done when machines are stable,
331
// there should always be tools and cloud instance.
333
return errors.NotValidf("machine %q missing tools", m.Id_)
335
if m.Instance_ == nil {
336
return errors.NotValidf("machine %q missing instance", m.Id_)
338
for _, container := range m.Containers_ {
339
if err := container.Validate(); err != nil {
340
return errors.Trace(err)
347
func importMachines(source map[string]interface{}) ([]*machine, error) {
348
checker := versionedChecker("machines")
349
coerced, err := checker.Coerce(source, nil)
351
return nil, errors.Annotatef(err, "machines version schema check failed")
353
valid := coerced.(map[string]interface{})
355
version := int(valid["version"].(int64))
356
importFunc, ok := machineDeserializationFuncs[version]
358
return nil, errors.NotValidf("version %d", version)
360
sourceList := valid["machines"].([]interface{})
361
return importMachineList(sourceList, importFunc)
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{})
369
return nil, errors.Errorf("unexpected value for machine %d, %T", i, value)
371
machine, err := importFunc(source)
373
return nil, errors.Annotatef(err, "machine %d", i)
375
result = append(result, machine)
380
type machineDeserializationFunc func(map[string]interface{}) (*machine, error)
382
var machineDeserializationFuncs = map[int]machineDeserializationFunc{
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()),
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()),
407
"block-devices": schema.StringMap(schema.Any()),
410
defaults := schema.Defaults{
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,
424
addAnnotationSchema(fields, defaults)
425
addConstraintsSchema(fields, defaults)
426
addStatusHistorySchema(fields)
427
checker := schema.FieldMap(fields, defaults)
429
coerced, err := checker.Coerce(source, nil)
431
return nil, errors.Annotatef(err, "machine v1 schema check failed")
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.
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"]),
446
result.importAnnotations(valid)
447
if err := result.importStatusHistory(valid); err != nil {
448
return nil, errors.Trace(err)
451
if constraintsMap, ok := valid["constraints"]; ok {
452
constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
454
return nil, errors.Trace(err)
456
result.Constraints_ = constraints
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)
465
result.SupportedContainers_ = &s
468
if instanceMap, ok := valid["instance"]; ok {
469
instance, err := importCloudInstance(instanceMap.(map[string]interface{}))
471
return nil, errors.Trace(err)
473
result.Instance_ = instance
476
if blockDeviceMap, ok := valid["block-devices"]; ok {
477
devices, err := importBlockDevices(blockDeviceMap.(map[string]interface{}))
479
return nil, errors.Trace(err)
481
result.setBlockDevices(devices)
483
result.setBlockDevices(nil)
486
// Tools and status are required, so we expect them to be there.
487
tools, err := importAgentTools(valid["tools"].(map[string]interface{}))
489
return nil, errors.Trace(err)
491
result.Tools_ = tools
493
status, err := importStatus(valid["status"].(map[string]interface{}))
495
return nil, errors.Trace(err)
497
result.Status_ = status
499
if addresses, ok := valid["provider-addresses"]; ok {
500
providerAddresses, err := importAddresses(addresses.([]interface{}))
502
return nil, errors.Trace(err)
504
result.ProviderAddresses_ = providerAddresses
507
if addresses, ok := valid["machine-addresses"]; ok {
508
machineAddresses, err := importAddresses(addresses.([]interface{}))
510
return nil, errors.Trace(err)
512
result.MachineAddresses_ = machineAddresses
515
if address, ok := valid["preferred-public-address"]; ok {
516
publicAddress, err := importAddress(address.(map[string]interface{}))
518
return nil, errors.Trace(err)
520
result.PreferredPublicAddress_ = publicAddress
523
if address, ok := valid["preferred-private-address"]; ok {
524
privateAddress, err := importAddress(address.(map[string]interface{}))
526
return nil, errors.Trace(err)
528
result.PreferredPrivateAddress_ = privateAddress
531
machineList := valid["containers"].([]interface{})
532
machines, err := importMachineList(machineList, importMachineV1)
534
return nil, errors.Annotatef(err, "containers")
536
result.Containers_ = machines
538
if portsMap, ok := valid["opened-ports"]; ok {
539
portsList, err := importOpenedPorts(portsMap.(map[string]interface{}))
541
return nil, errors.Trace(err)
543
result.setOpenedPorts(portsList)
550
// CloudInstanceArgs is an argument struct used to add information about the
551
// cloud instance to a Machine.
552
type CloudInstanceArgs struct {
561
AvailabilityZone string
564
func newCloudInstance(args CloudInstanceArgs) *cloudInstance {
565
tags := make([]string, len(args.Tags))
566
copy(tags, args.Tags)
567
return &cloudInstance{
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,
577
AvailabilityZone_: args.AvailabilityZone,
581
type cloudInstance struct {
582
Version int `yaml:"version"`
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
589
Architecture_ string `yaml:"architecture,omitempty"`
590
Memory_ uint64 `yaml:"memory,omitempty"`
591
RootDisk_ uint64 `yaml:"root-disk,omitempty"`
592
CpuCores_ uint64 `yaml:"cpu-cores,omitempty"`
593
CpuPower_ uint64 `yaml:"cpu-power,omitempty"`
594
Tags_ []string `yaml:"tags,omitempty"`
595
AvailabilityZone_ string `yaml:"availability-zone,omitempty"`
598
// InstanceId implements CloudInstance.
599
func (c *cloudInstance) InstanceId() string {
603
// Status implements CloudInstance.
604
func (c *cloudInstance) Status() string {
608
// Architecture implements CloudInstance.
609
func (c *cloudInstance) Architecture() string {
610
return c.Architecture_
613
// Memory implements CloudInstance.
614
func (c *cloudInstance) Memory() uint64 {
618
// RootDisk implements CloudInstance.
619
func (c *cloudInstance) RootDisk() uint64 {
623
// CpuCores implements CloudInstance.
624
func (c *cloudInstance) CpuCores() uint64 {
628
// CpuPower implements CloudInstance.
629
func (c *cloudInstance) CpuPower() uint64 {
633
// Tags implements CloudInstance.
634
func (c *cloudInstance) Tags() []string {
635
tags := make([]string, len(c.Tags_))
640
// AvailabilityZone implements CloudInstance.
641
func (c *cloudInstance) AvailabilityZone() string {
642
return c.AvailabilityZone_
645
func importCloudInstance(source map[string]interface{}) (*cloudInstance, error) {
646
version, err := getVersion(source)
648
return nil, errors.Annotate(err, "cloudInstance version schema check failed")
651
importFunc, ok := cloudInstanceDeserializationFuncs[version]
653
return nil, errors.NotValidf("version %d", version)
656
return importFunc(source)
659
type cloudInstanceDeserializationFunc func(map[string]interface{}) (*cloudInstance, error)
661
var cloudInstanceDeserializationFuncs = map[int]cloudInstanceDeserializationFunc{
662
1: importCloudInstanceV1,
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.Uint(),
671
"root-disk": schema.Uint(),
672
"cpu-cores": schema.Uint(),
673
"cpu-power": schema.Uint(),
674
"tags": schema.List(schema.String()),
675
"availability-zone": schema.String(),
677
// Some values don't have to be there.
678
defaults := schema.Defaults{
681
"root-disk": uint64(0),
682
"cpu-cores": uint64(0),
683
"cpu-power": uint64(0),
685
"availability-zone": "",
687
checker := schema.FieldMap(fields, defaults)
689
coerced, err := checker.Coerce(source, nil)
691
return nil, errors.Annotatef(err, "cloudInstance v1 schema check failed")
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.
697
return &cloudInstance{
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["cpu-cores"].(uint64),
705
CpuPower_: valid["cpu-power"].(uint64),
706
Tags_: convertToStringSlice(valid["tags"]),
707
AvailabilityZone_: valid["availability-zone"].(string),
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
720
func newAgentTools(args AgentToolsArgs) *agentTools {
723
ToolsVersion_: args.Version,
725
SHA256_: args.SHA256,
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
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"`
741
// Version implements AgentTools.
742
func (a *agentTools) Version() version.Binary {
743
return a.ToolsVersion_
746
// URL implements AgentTools.
747
func (a *agentTools) URL() string {
751
// SHA256 implements AgentTools.
752
func (a *agentTools) SHA256() string {
756
// Size implements AgentTools.
757
func (a *agentTools) Size() int64 {
761
func importAgentTools(source map[string]interface{}) (*agentTools, error) {
762
version, err := getVersion(source)
764
return nil, errors.Annotate(err, "agentTools version schema check failed")
767
importFunc, ok := agentToolsDeserializationFuncs[version]
769
return nil, errors.NotValidf("version %d", version)
772
return importFunc(source)
775
type agentToolsDeserializationFunc func(map[string]interface{}) (*agentTools, error)
777
var agentToolsDeserializationFuncs = map[int]agentToolsDeserializationFunc{
778
1: importAgentToolsV1,
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(),
788
checker := schema.FieldMap(fields, nil) // no defaults
790
coerced, err := checker.Coerce(source, nil)
792
return nil, errors.Annotatef(err, "agentTools v1 schema check failed")
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.
798
verString := valid["tools-version"].(string)
799
toolsVersion, err := version.ParseBinary(verString)
801
return nil, errors.Annotatef(err, "agentTools tools-version")
806
ToolsVersion_: toolsVersion,
807
URL_: valid["url"].(string),
808
SHA256_: valid["sha256"].(string),
809
Size_: valid["size"].(int64),