1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
7
"github.com/juju/errors"
8
"github.com/juju/names"
9
"github.com/juju/schema"
10
"github.com/juju/version"
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
NetworkPorts_ *versionedNetworkPorts `yaml:"network-ports,omitempty"`
45
Annotations_ `yaml:"annotations,omitempty"`
47
Constraints_ *constraints `yaml:"constraints,omitempty"`
50
// MachineArgs is an argument struct used to add a machine to the Model.
51
type MachineArgs struct {
59
// A null value means that we don't yet know which containers
60
// are supported. An empty slice means 'no containers are supported'.
61
SupportedContainers *[]string
64
func newMachine(args MachineArgs) *machine {
66
if count := len(args.Jobs); count > 0 {
67
jobs = make([]string, count)
73
PasswordHash_: args.PasswordHash,
74
Placement_: args.Placement,
76
ContainerType_: args.ContainerType,
78
StatusHistory_: newStatusHistory(),
80
if args.SupportedContainers != nil {
81
supported := make([]string, len(*args.SupportedContainers))
82
copy(supported, *args.SupportedContainers)
83
m.SupportedContainers_ = &supported
88
// Id implements Machine.
89
func (m *machine) Id() string {
93
// Tag implements Machine.
94
func (m *machine) Tag() names.MachineTag {
95
return names.NewMachineTag(m.Id_)
98
// Nonce implements Machine.
99
func (m *machine) Nonce() string {
103
// PasswordHash implements Machine.
104
func (m *machine) PasswordHash() string {
105
return m.PasswordHash_
108
// Placement implements Machine.
109
func (m *machine) Placement() string {
113
// Instance implements Machine.
114
func (m *machine) Instance() CloudInstance {
115
// To avoid typed nils check nil here.
116
if m.Instance_ == nil {
122
// SetInstance implements Machine.
123
func (m *machine) SetInstance(args CloudInstanceArgs) {
124
m.Instance_ = newCloudInstance(args)
127
// Series implements Machine.
128
func (m *machine) Series() string {
132
// ContainerType implements Machine.
133
func (m *machine) ContainerType() string {
134
return m.ContainerType_
137
// Status implements Machine.
138
func (m *machine) Status() Status {
139
// To avoid typed nils check nil here.
140
if m.Status_ == nil {
146
// SetStatus implements Machine.
147
func (m *machine) SetStatus(args StatusArgs) {
148
m.Status_ = newStatus(args)
151
// ProviderAddresses implements Machine.
152
func (m *machine) ProviderAddresses() []Address {
154
for _, addr := range m.ProviderAddresses_ {
155
result = append(result, addr)
160
// MachineAddresses implements Machine.
161
func (m *machine) MachineAddresses() []Address {
163
for _, addr := range m.MachineAddresses_ {
164
result = append(result, addr)
169
// SetAddresses implements Machine.
170
func (m *machine) SetAddresses(margs []AddressArgs, pargs []AddressArgs) {
171
m.MachineAddresses_ = nil
172
m.ProviderAddresses_ = nil
173
for _, args := range margs {
174
if args.Value != "" {
175
m.MachineAddresses_ = append(m.MachineAddresses_, newAddress(args))
178
for _, args := range pargs {
179
if args.Value != "" {
180
m.ProviderAddresses_ = append(m.ProviderAddresses_, newAddress(args))
185
// PreferredPublicAddress implements Machine.
186
func (m *machine) PreferredPublicAddress() Address {
187
// To avoid typed nils check nil here.
188
if m.PreferredPublicAddress_ == nil {
191
return m.PreferredPublicAddress_
194
// PreferredPrivateAddress implements Machine.
195
func (m *machine) PreferredPrivateAddress() Address {
196
// To avoid typed nils check nil here.
197
if m.PreferredPrivateAddress_ == nil {
200
return m.PreferredPrivateAddress_
203
// SetPreferredAddresses implements Machine.
204
func (m *machine) SetPreferredAddresses(public AddressArgs, private AddressArgs) {
205
if public.Value != "" {
206
m.PreferredPublicAddress_ = newAddress(public)
208
if private.Value != "" {
209
m.PreferredPrivateAddress_ = newAddress(private)
213
// Tools implements Machine.
214
func (m *machine) Tools() AgentTools {
215
// To avoid a typed nil, check before returning.
222
// SetTools implements Machine.
223
func (m *machine) SetTools(args AgentToolsArgs) {
224
m.Tools_ = newAgentTools(args)
227
// Jobs implements Machine.
228
func (m *machine) Jobs() []string {
232
// SupportedContainers implements Machine.
233
func (m *machine) SupportedContainers() ([]string, bool) {
234
if m.SupportedContainers_ == nil {
237
return *m.SupportedContainers_, true
240
// Containers implements Machine.
241
func (m *machine) Containers() []Machine {
243
for _, container := range m.Containers_ {
244
result = append(result, container)
249
// AddContainer implements Machine.
250
func (m *machine) AddContainer(args MachineArgs) Machine {
251
container := newMachine(args)
252
m.Containers_ = append(m.Containers_, container)
256
// NetworkPorts implements Machine.
257
func (m *machine) NetworkPorts() []NetworkPorts {
258
if m.NetworkPorts_ == nil {
261
var result []NetworkPorts
262
for _, ports := range m.NetworkPorts_.NetworkPorts_ {
263
result = append(result, ports)
268
// AddNetworkPorts implements Machine.
269
func (m *machine) AddNetworkPorts(args NetworkPortsArgs) NetworkPorts {
270
if m.NetworkPorts_ == nil {
271
m.NetworkPorts_ = &versionedNetworkPorts{Version: 1}
273
ports := newNetworkPorts(args)
274
m.NetworkPorts_.NetworkPorts_ = append(m.NetworkPorts_.NetworkPorts_, ports)
278
func (m *machine) setNetworkPorts(networkPortsList []*networkPorts) {
279
m.NetworkPorts_ = &versionedNetworkPorts{
281
NetworkPorts_: networkPortsList,
285
// Constraints implements HasConstraints.
286
func (m *machine) Constraints() Constraints {
287
if m.Constraints_ == nil {
290
return m.Constraints_
293
// SetConstraints implements HasConstraints.
294
func (m *machine) SetConstraints(args ConstraintsArgs) {
295
m.Constraints_ = newConstraints(args)
298
// Validate implements Machine.
299
func (m *machine) Validate() error {
301
return errors.NotValidf("machine missing id")
303
if m.Status_ == nil {
304
return errors.NotValidf("machine %q missing status", m.Id_)
306
// Since all exports should be done when machines are stable,
307
// there should always be tools and cloud instance.
309
return errors.NotValidf("machine %q missing tools", m.Id_)
311
if m.Instance_ == nil {
312
return errors.NotValidf("machine %q missing instance", m.Id_)
314
for _, container := range m.Containers_ {
315
if err := container.Validate(); err != nil {
316
return errors.Trace(err)
323
func importMachines(source map[string]interface{}) ([]*machine, error) {
324
checker := versionedChecker("machines")
325
coerced, err := checker.Coerce(source, nil)
327
return nil, errors.Annotatef(err, "machines version schema check failed")
329
valid := coerced.(map[string]interface{})
331
version := int(valid["version"].(int64))
332
importFunc, ok := machineDeserializationFuncs[version]
334
return nil, errors.NotValidf("version %d", version)
336
sourceList := valid["machines"].([]interface{})
337
return importMachineList(sourceList, importFunc)
340
func importMachineList(sourceList []interface{}, importFunc machineDeserializationFunc) ([]*machine, error) {
341
result := make([]*machine, 0, len(sourceList))
342
for i, value := range sourceList {
343
source, ok := value.(map[string]interface{})
345
return nil, errors.Errorf("unexpected value for machine %d, %T", i, value)
347
machine, err := importFunc(source)
349
return nil, errors.Annotatef(err, "machine %d", i)
351
result = append(result, machine)
356
type machineDeserializationFunc func(map[string]interface{}) (*machine, error)
358
var machineDeserializationFuncs = map[int]machineDeserializationFunc{
362
func importMachineV1(source map[string]interface{}) (*machine, error) {
363
fields := schema.Fields{
364
"id": schema.String(),
365
"nonce": schema.String(),
366
"password-hash": schema.String(),
367
"placement": schema.String(),
368
"instance": schema.StringMap(schema.Any()),
369
"series": schema.String(),
370
"container-type": schema.String(),
371
"jobs": schema.List(schema.String()),
372
"status": schema.StringMap(schema.Any()),
373
"supported-containers": schema.List(schema.String()),
374
"tools": schema.StringMap(schema.Any()),
375
"containers": schema.List(schema.StringMap(schema.Any())),
376
"network-ports": schema.StringMap(schema.Any()),
378
"provider-addresses": schema.List(schema.StringMap(schema.Any())),
379
"machine-addresses": schema.List(schema.StringMap(schema.Any())),
380
"preferred-public-address": schema.StringMap(schema.Any()),
381
"preferred-private-address": schema.StringMap(schema.Any()),
384
defaults := schema.Defaults{
386
"container-type": "",
387
// Even though we are expecting instance data for every machine,
388
// it isn't strictly necessary, so we allow it to not exist here.
389
"instance": schema.Omit,
390
"supported-containers": schema.Omit,
391
"network-ports": schema.Omit,
392
"provider-addresses": schema.Omit,
393
"machine-addresses": schema.Omit,
394
"preferred-public-address": schema.Omit,
395
"preferred-private-address": schema.Omit,
397
addAnnotationSchema(fields, defaults)
398
addConstraintsSchema(fields, defaults)
399
addStatusHistorySchema(fields)
400
checker := schema.FieldMap(fields, defaults)
402
coerced, err := checker.Coerce(source, nil)
404
return nil, errors.Annotatef(err, "machine v1 schema check failed")
406
valid := coerced.(map[string]interface{})
407
// From here we know that the map returned from the schema coercion
408
// contains fields of the right type.
410
Id_: valid["id"].(string),
411
Nonce_: valid["nonce"].(string),
412
PasswordHash_: valid["password-hash"].(string),
413
Placement_: valid["placement"].(string),
414
Series_: valid["series"].(string),
415
ContainerType_: valid["container-type"].(string),
416
StatusHistory_: newStatusHistory(),
418
result.importAnnotations(valid)
419
if err := result.importStatusHistory(valid); err != nil {
420
return nil, errors.Trace(err)
423
if constraintsMap, ok := valid["constraints"]; ok {
424
constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
426
return nil, errors.Trace(err)
428
result.Constraints_ = constraints
431
if jobs := valid["jobs"].([]interface{}); len(jobs) > 0 {
432
for _, job := range jobs {
433
result.Jobs_ = append(result.Jobs_, job.(string))
436
if supported, ok := valid["supported-containers"]; ok {
437
supportedList := supported.([]interface{})
438
s := make([]string, len(supportedList))
439
for i, containerType := range supportedList {
440
s[i] = containerType.(string)
442
result.SupportedContainers_ = &s
445
if instanceMap, ok := valid["instance"]; ok {
446
instance, err := importCloudInstance(instanceMap.(map[string]interface{}))
448
return nil, errors.Trace(err)
450
result.Instance_ = instance
453
// Tools and status are required, so we expect them to be there.
454
tools, err := importAgentTools(valid["tools"].(map[string]interface{}))
456
return nil, errors.Trace(err)
458
result.Tools_ = tools
460
status, err := importStatus(valid["status"].(map[string]interface{}))
462
return nil, errors.Trace(err)
464
result.Status_ = status
466
if addresses, ok := valid["provider-addresses"]; ok {
467
providerAddresses, err := importAddresses(addresses.([]interface{}))
469
return nil, errors.Trace(err)
471
result.ProviderAddresses_ = providerAddresses
474
if addresses, ok := valid["machine-addresses"]; ok {
475
machineAddresses, err := importAddresses(addresses.([]interface{}))
477
return nil, errors.Trace(err)
479
result.MachineAddresses_ = machineAddresses
482
if address, ok := valid["preferred-public-address"]; ok {
483
publicAddress, err := importAddress(address.(map[string]interface{}))
485
return nil, errors.Trace(err)
487
result.PreferredPublicAddress_ = publicAddress
490
if address, ok := valid["preferred-private-address"]; ok {
491
privateAddress, err := importAddress(address.(map[string]interface{}))
493
return nil, errors.Trace(err)
495
result.PreferredPrivateAddress_ = privateAddress
498
machineList := valid["containers"].([]interface{})
499
machines, err := importMachineList(machineList, importMachineV1)
501
return nil, errors.Annotatef(err, "containers")
503
result.Containers_ = machines
505
if npMap, ok := valid["network-ports"]; ok {
506
networkPortsList, err := importNetworkPorts(npMap.(map[string]interface{}))
508
return nil, errors.Trace(err)
510
result.setNetworkPorts(networkPortsList)
517
// CloudInstanceArgs is an argument struct used to add information about the
518
// cloud instance to a Machine.
519
type CloudInstanceArgs struct {
528
AvailabilityZone string
531
func newCloudInstance(args CloudInstanceArgs) *cloudInstance {
532
tags := make([]string, len(args.Tags))
533
copy(tags, args.Tags)
534
return &cloudInstance{
536
InstanceId_: args.InstanceId,
537
Status_: args.Status,
538
Architecture_: args.Architecture,
539
Memory_: args.Memory,
540
RootDisk_: args.RootDisk,
541
CpuCores_: args.CpuCores,
542
CpuPower_: args.CpuPower,
544
AvailabilityZone_: args.AvailabilityZone,
548
type cloudInstance struct {
549
Version int `yaml:"version"`
551
InstanceId_ string `yaml:"instance-id"`
552
Status_ string `yaml:"status"`
553
// For all the optional values, empty values make no sense, and
554
// it would be better to have them not set rather than set with
556
Architecture_ string `yaml:"architecture,omitempty"`
557
Memory_ uint64 `yaml:"memory,omitempty"`
558
RootDisk_ uint64 `yaml:"root-disk,omitempty"`
559
CpuCores_ uint64 `yaml:"cpu-cores,omitempty"`
560
CpuPower_ uint64 `yaml:"cpu-power,omitempty"`
561
Tags_ []string `yaml:"tags,omitempty"`
562
AvailabilityZone_ string `yaml:"availability-zone,omitempty"`
565
// InstanceId implements CloudInstance.
566
func (c *cloudInstance) InstanceId() string {
570
// Status implements CloudInstance.
571
func (c *cloudInstance) Status() string {
575
// Architecture implements CloudInstance.
576
func (c *cloudInstance) Architecture() string {
577
return c.Architecture_
580
// Memory implements CloudInstance.
581
func (c *cloudInstance) Memory() uint64 {
585
// RootDisk implements CloudInstance.
586
func (c *cloudInstance) RootDisk() uint64 {
590
// CpuCores implements CloudInstance.
591
func (c *cloudInstance) CpuCores() uint64 {
595
// CpuPower implements CloudInstance.
596
func (c *cloudInstance) CpuPower() uint64 {
600
// Tags implements CloudInstance.
601
func (c *cloudInstance) Tags() []string {
602
tags := make([]string, len(c.Tags_))
607
// AvailabilityZone implements CloudInstance.
608
func (c *cloudInstance) AvailabilityZone() string {
609
return c.AvailabilityZone_
612
func importCloudInstance(source map[string]interface{}) (*cloudInstance, error) {
613
version, err := getVersion(source)
615
return nil, errors.Annotate(err, "cloudInstance version schema check failed")
618
importFunc, ok := cloudInstanceDeserializationFuncs[version]
620
return nil, errors.NotValidf("version %d", version)
623
return importFunc(source)
626
type cloudInstanceDeserializationFunc func(map[string]interface{}) (*cloudInstance, error)
628
var cloudInstanceDeserializationFuncs = map[int]cloudInstanceDeserializationFunc{
629
1: importCloudInstanceV1,
632
func importCloudInstanceV1(source map[string]interface{}) (*cloudInstance, error) {
633
fields := schema.Fields{
634
"instance-id": schema.String(),
635
"status": schema.String(),
636
"architecture": schema.String(),
637
"memory": schema.Uint(),
638
"root-disk": schema.Uint(),
639
"cpu-cores": schema.Uint(),
640
"cpu-power": schema.Uint(),
641
"tags": schema.List(schema.String()),
642
"availability-zone": schema.String(),
644
// Some values don't have to be there.
645
defaults := schema.Defaults{
648
"root-disk": uint64(0),
649
"cpu-cores": uint64(0),
650
"cpu-power": uint64(0),
652
"availability-zone": "",
654
checker := schema.FieldMap(fields, defaults)
656
coerced, err := checker.Coerce(source, nil)
658
return nil, errors.Annotatef(err, "cloudInstance v1 schema check failed")
660
valid := coerced.(map[string]interface{})
661
// From here we know that the map returned from the schema coercion
662
// contains fields of the right type.
664
return &cloudInstance{
666
InstanceId_: valid["instance-id"].(string),
667
Status_: valid["status"].(string),
668
Architecture_: valid["architecture"].(string),
669
Memory_: valid["memory"].(uint64),
670
RootDisk_: valid["root-disk"].(uint64),
671
CpuCores_: valid["cpu-cores"].(uint64),
672
CpuPower_: valid["cpu-power"].(uint64),
673
Tags_: convertToStringSlice(valid["tags"]),
674
AvailabilityZone_: valid["availability-zone"].(string),
678
// AgentToolsArgs is an argument struct used to add information about the
679
// tools the agent is using to a Machine.
680
type AgentToolsArgs struct {
681
Version version.Binary
687
func newAgentTools(args AgentToolsArgs) *agentTools {
690
ToolsVersion_: args.Version,
692
SHA256_: args.SHA256,
697
// Keeping the agentTools with the machine code, because we hope
698
// that one day we will succeed in merging the unit agents with the
700
type agentTools struct {
701
Version_ int `yaml:"version"`
702
ToolsVersion_ version.Binary `yaml:"tools-version"`
703
URL_ string `yaml:"url"`
704
SHA256_ string `yaml:"sha256"`
705
Size_ int64 `yaml:"size"`
708
// Version implements AgentTools.
709
func (a *agentTools) Version() version.Binary {
710
return a.ToolsVersion_
713
// URL implements AgentTools.
714
func (a *agentTools) URL() string {
718
// SHA256 implements AgentTools.
719
func (a *agentTools) SHA256() string {
723
// Size implements AgentTools.
724
func (a *agentTools) Size() int64 {
728
func importAgentTools(source map[string]interface{}) (*agentTools, error) {
729
version, err := getVersion(source)
731
return nil, errors.Annotate(err, "agentTools version schema check failed")
734
importFunc, ok := agentToolsDeserializationFuncs[version]
736
return nil, errors.NotValidf("version %d", version)
739
return importFunc(source)
742
type agentToolsDeserializationFunc func(map[string]interface{}) (*agentTools, error)
744
var agentToolsDeserializationFuncs = map[int]agentToolsDeserializationFunc{
745
1: importAgentToolsV1,
748
func importAgentToolsV1(source map[string]interface{}) (*agentTools, error) {
749
fields := schema.Fields{
750
"tools-version": schema.String(),
751
"url": schema.String(),
752
"sha256": schema.String(),
753
"size": schema.Int(),
755
checker := schema.FieldMap(fields, nil) // no defaults
757
coerced, err := checker.Coerce(source, nil)
759
return nil, errors.Annotatef(err, "agentTools v1 schema check failed")
761
valid := coerced.(map[string]interface{})
762
// From here we know that the map returned from the schema coercion
763
// contains fields of the right type.
765
verString := valid["tools-version"].(string)
766
toolsVersion, err := version.ParseBinary(verString)
768
return nil, errors.Annotatef(err, "agentTools tools-version")
773
ToolsVersion_: toolsVersion,
774
URL_: valid["url"].(string),
775
SHA256_: valid["sha256"].(string),
776
Size_: valid["size"].(int64),