1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
9
"github.com/juju/errors"
10
"github.com/juju/loggo"
11
"github.com/juju/names"
12
"github.com/juju/schema"
13
"github.com/juju/utils/set"
14
"github.com/juju/version"
18
var logger = loggo.GetLogger("juju.state.migration")
20
// ModelArgs represent the bare minimum information that is needed
21
// to represent a model.
22
type ModelArgs struct {
24
Config map[string]interface{}
25
LatestToolsVersion version.Number
26
Blocks map[string]string
29
// NewModel returns a Model based on the args specified.
30
func NewModel(args ModelArgs) Model {
33
Owner_: args.Owner.Id(),
35
LatestToolsVersion_: args.LatestToolsVersion,
36
Sequences_: make(map[string]int),
46
// Serialize mirrors the Deserialize method, and makes sure that
47
// the same serialization method is used.
48
func Serialize(model Model) ([]byte, error) {
49
return yaml.Marshal(model)
52
// Deserialize constructs a Model from a serialized YAML byte stream. The
53
// normal use for this is to construct the Model representation after getting
54
// the byte stream from an API connection or read from a file.
55
func Deserialize(bytes []byte) (Model, error) {
56
var source map[string]interface{}
57
err := yaml.Unmarshal(bytes, &source)
59
return nil, errors.Trace(err)
62
model, err := importModel(source)
64
return nil, errors.Trace(err)
70
Version int `yaml:"version"`
72
Owner_ string `yaml:"owner"`
73
Config_ map[string]interface{} `yaml:"config"`
74
Blocks_ map[string]string `yaml:"blocks,omitempty"`
76
LatestToolsVersion_ version.Number `yaml:"latest-tools,omitempty"`
78
Users_ users `yaml:"users"`
79
Machines_ machines `yaml:"machines"`
80
Services_ services `yaml:"services"`
81
Relations_ relations `yaml:"relations"`
83
Sequences_ map[string]int `yaml:"sequences"`
85
Annotations_ `yaml:"annotations,omitempty"`
87
Constraints_ *constraints `yaml:"constraints,omitempty"`
94
func (m *model) Tag() names.ModelTag {
95
// Here we make the assumption that the environment UUID is set
96
// correctly in the Config.
97
value := m.Config_["uuid"]
98
// Explicitly ignore the 'ok' aspect of the cast. If we don't have it
99
// and it is wrong, we panic. Here we fully expect it to exist, but
100
// paranoia says 'never panic', so worst case is we have an empty string.
101
uuid, _ := value.(string)
102
return names.NewModelTag(uuid)
105
// Owner implements Model.
106
func (m *model) Owner() names.UserTag {
107
return names.NewUserTag(m.Owner_)
110
// Config implements Model.
111
func (m *model) Config() map[string]interface{} {
112
// TODO: consider returning a deep copy.
116
// UpdateConfig implements Model.
117
func (m *model) UpdateConfig(config map[string]interface{}) {
118
for key, value := range config {
119
m.Config_[key] = value
123
// LatestToolsVersion implements Model.
124
func (m *model) LatestToolsVersion() version.Number {
125
return m.LatestToolsVersion_
128
// Blocks implements Model.
129
func (m *model) Blocks() map[string]string {
133
// Implement length-based sort with ByLen type.
136
func (a ByName) Len() int { return len(a) }
137
func (a ByName) Less(i, j int) bool { return a[i].Name().Canonical() < a[j].Name().Canonical() }
138
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
140
// Users implements Model.
141
func (m *model) Users() []User {
143
for _, user := range m.Users_.Users_ {
144
result = append(result, user)
146
sort.Sort(ByName(result))
150
// AddUser implements Model.
151
func (m *model) AddUser(args UserArgs) {
152
m.Users_.Users_ = append(m.Users_.Users_, newUser(args))
155
func (m *model) setUsers(userList []*user) {
162
// Machines implements Model.
163
func (m *model) Machines() []Machine {
165
for _, machine := range m.Machines_.Machines_ {
166
result = append(result, machine)
171
// AddMachine implements Model.
172
func (m *model) AddMachine(args MachineArgs) Machine {
173
machine := newMachine(args)
174
m.Machines_.Machines_ = append(m.Machines_.Machines_, machine)
178
func (m *model) setMachines(machineList []*machine) {
179
m.Machines_ = machines{
181
Machines_: machineList,
185
// Services implements Model.
186
func (m *model) Services() []Service {
188
for _, service := range m.Services_.Services_ {
189
result = append(result, service)
194
func (m *model) service(name string) *service {
195
for _, service := range m.Services_.Services_ {
196
if service.Name() == name {
203
// AddService implements Model.
204
func (m *model) AddService(args ServiceArgs) Service {
205
service := newService(args)
206
m.Services_.Services_ = append(m.Services_.Services_, service)
210
func (m *model) setServices(serviceList []*service) {
211
m.Services_ = services{
213
Services_: serviceList,
217
// Relations implements Model.
218
func (m *model) Relations() []Relation {
219
var result []Relation
220
for _, relation := range m.Relations_.Relations_ {
221
result = append(result, relation)
226
// AddRelation implements Model.
227
func (m *model) AddRelation(args RelationArgs) Relation {
228
relation := newRelation(args)
229
m.Relations_.Relations_ = append(m.Relations_.Relations_, relation)
233
func (m *model) setRelations(relationList []*relation) {
234
m.Relations_ = relations{
236
Relations_: relationList,
240
// Sequences implements Model.
241
func (m *model) Sequences() map[string]int {
245
// SetSequence implements Model.
246
func (m *model) SetSequence(name string, value int) {
247
m.Sequences_[name] = value
250
// Constraints implements HasConstraints.
251
func (m *model) Constraints() Constraints {
252
if m.Constraints_ == nil {
255
return m.Constraints_
258
// SetConstraints implements HasConstraints.
259
func (m *model) SetConstraints(args ConstraintsArgs) {
260
m.Constraints_ = newConstraints(args)
263
// Validate implements Model.
264
func (m *model) Validate() error {
265
// A model needs an owner.
267
return errors.NotValidf("missing model owner")
270
unitsWithOpenPorts := set.NewStrings()
271
for _, machine := range m.Machines_.Machines_ {
272
if err := machine.Validate(); err != nil {
273
return errors.Trace(err)
275
for _, np := range machine.NetworkPorts() {
276
for _, pr := range np.OpenPorts() {
277
unitsWithOpenPorts.Add(pr.UnitName())
281
allUnits := set.NewStrings()
282
for _, service := range m.Services_.Services_ {
283
if err := service.Validate(); err != nil {
284
return errors.Trace(err)
286
allUnits = allUnits.Union(service.unitNames())
288
// Make sure that all the unit names specified in machine opened ports
289
// exist as units of services.
290
unknownUnitsWithPorts := unitsWithOpenPorts.Difference(allUnits)
291
if len(unknownUnitsWithPorts) > 0 {
292
return errors.Errorf("unknown unit names in open ports: %s", unknownUnitsWithPorts.SortedValues())
295
return m.validateRelations()
298
// validateRelations makes sure that for each endpoint in each relation there
299
// are settings for all units of that service for that endpoint.
300
func (m *model) validateRelations() error {
301
for _, relation := range m.Relations_.Relations_ {
302
for _, ep := range relation.Endpoints_.Endpoints_ {
303
// Check service exists.
304
service := m.service(ep.ServiceName())
306
return errors.Errorf("unknown service %q for relation id %d", ep.ServiceName(), relation.Id())
308
// Check that all units have settings.
309
serviceUnits := service.unitNames()
310
epUnits := ep.unitNames()
311
if missingSettings := serviceUnits.Difference(epUnits); len(missingSettings) > 0 {
312
return errors.Errorf("missing relation settings for units %s in relation %d", missingSettings.SortedValues(), relation.Id())
314
if extraSettings := epUnits.Difference(serviceUnits); len(extraSettings) > 0 {
315
return errors.Errorf("settings for unknown units %s in relation %d", extraSettings.SortedValues(), relation.Id())
322
// importModel constructs a new Model from a map that in normal usage situations
323
// will be the result of interpreting a large YAML document.
325
// This method is a package internal serialisation method.
326
func importModel(source map[string]interface{}) (*model, error) {
327
version, err := getVersion(source)
329
return nil, errors.Trace(err)
332
importFunc, ok := modelDeserializationFuncs[version]
334
return nil, errors.NotValidf("version %d", version)
337
return importFunc(source)
340
type modelDeserializationFunc func(map[string]interface{}) (*model, error)
342
var modelDeserializationFuncs = map[int]modelDeserializationFunc{
346
func importModelV1(source map[string]interface{}) (*model, error) {
347
fields := schema.Fields{
348
"owner": schema.String(),
349
"config": schema.StringMap(schema.Any()),
350
"latest-tools": schema.String(),
351
"blocks": schema.StringMap(schema.String()),
352
"users": schema.StringMap(schema.Any()),
353
"machines": schema.StringMap(schema.Any()),
354
"services": schema.StringMap(schema.Any()),
355
"relations": schema.StringMap(schema.Any()),
356
"sequences": schema.StringMap(schema.Int()),
358
// Some values don't have to be there.
359
defaults := schema.Defaults{
360
"latest-tools": schema.Omit,
361
"blocks": schema.Omit,
363
addAnnotationSchema(fields, defaults)
364
addConstraintsSchema(fields, defaults)
365
checker := schema.FieldMap(fields, defaults)
367
coerced, err := checker.Coerce(source, nil)
369
return nil, errors.Annotatef(err, "model v1 schema check failed")
371
valid := coerced.(map[string]interface{})
372
// From here we know that the map returned from the schema coercion
373
// contains fields of the right type.
377
Owner_: valid["owner"].(string),
378
Config_: valid["config"].(map[string]interface{}),
379
Sequences_: make(map[string]int),
380
Blocks_: convertToStringMap(valid["blocks"]),
382
result.importAnnotations(valid)
383
sequences := valid["sequences"].(map[string]interface{})
384
for key, value := range sequences {
385
result.SetSequence(key, int(value.(int64)))
388
if constraintsMap, ok := valid["constraints"]; ok {
389
constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
391
return nil, errors.Trace(err)
393
result.Constraints_ = constraints
396
if availableTools, ok := valid["latest-tools"]; ok {
397
num, err := version.Parse(availableTools.(string))
399
return nil, errors.Trace(err)
401
result.LatestToolsVersion_ = num
404
userMap := valid["users"].(map[string]interface{})
405
users, err := importUsers(userMap)
407
return nil, errors.Annotate(err, "users")
409
result.setUsers(users)
411
machineMap := valid["machines"].(map[string]interface{})
412
machines, err := importMachines(machineMap)
414
return nil, errors.Annotate(err, "machines")
416
result.setMachines(machines)
418
serviceMap := valid["services"].(map[string]interface{})
419
services, err := importServices(serviceMap)
421
return nil, errors.Annotate(err, "services")
423
result.setServices(services)
425
relationMap := valid["relations"].(map[string]interface{})
426
relations, err := importRelations(relationMap)
428
return nil, errors.Annotate(err, "relations")
430
result.setRelations(relations)