github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/core/description/model.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 "sort" 8 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" 15 "gopkg.in/yaml.v2" 16 ) 17 18 var logger = loggo.GetLogger("juju.state.migration") 19 20 // ModelArgs represent the bare minimum information that is needed 21 // to represent a model. 22 type ModelArgs struct { 23 Owner names.UserTag 24 Config map[string]interface{} 25 LatestToolsVersion version.Number 26 Blocks map[string]string 27 } 28 29 // NewModel returns a Model based on the args specified. 30 func NewModel(args ModelArgs) Model { 31 m := &model{ 32 Version: 1, 33 Owner_: args.Owner.Id(), 34 Config_: args.Config, 35 LatestToolsVersion_: args.LatestToolsVersion, 36 Sequences_: make(map[string]int), 37 Blocks_: args.Blocks, 38 } 39 m.setUsers(nil) 40 m.setMachines(nil) 41 m.setServices(nil) 42 m.setRelations(nil) 43 return m 44 } 45 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) 50 } 51 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) 58 if err != nil { 59 return nil, errors.Trace(err) 60 } 61 62 model, err := importModel(source) 63 if err != nil { 64 return nil, errors.Trace(err) 65 } 66 return model, nil 67 } 68 69 type model struct { 70 Version int `yaml:"version"` 71 72 Owner_ string `yaml:"owner"` 73 Config_ map[string]interface{} `yaml:"config"` 74 Blocks_ map[string]string `yaml:"blocks,omitempty"` 75 76 LatestToolsVersion_ version.Number `yaml:"latest-tools,omitempty"` 77 78 Users_ users `yaml:"users"` 79 Machines_ machines `yaml:"machines"` 80 Services_ services `yaml:"services"` 81 Relations_ relations `yaml:"relations"` 82 83 Sequences_ map[string]int `yaml:"sequences"` 84 85 Annotations_ `yaml:"annotations,omitempty"` 86 87 Constraints_ *constraints `yaml:"constraints,omitempty"` 88 89 // TODO: 90 // Spaces 91 // Storage 92 } 93 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) 103 } 104 105 // Owner implements Model. 106 func (m *model) Owner() names.UserTag { 107 return names.NewUserTag(m.Owner_) 108 } 109 110 // Config implements Model. 111 func (m *model) Config() map[string]interface{} { 112 // TODO: consider returning a deep copy. 113 return m.Config_ 114 } 115 116 // UpdateConfig implements Model. 117 func (m *model) UpdateConfig(config map[string]interface{}) { 118 for key, value := range config { 119 m.Config_[key] = value 120 } 121 } 122 123 // LatestToolsVersion implements Model. 124 func (m *model) LatestToolsVersion() version.Number { 125 return m.LatestToolsVersion_ 126 } 127 128 // Blocks implements Model. 129 func (m *model) Blocks() map[string]string { 130 return m.Blocks_ 131 } 132 133 // Implement length-based sort with ByLen type. 134 type ByName []User 135 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] } 139 140 // Users implements Model. 141 func (m *model) Users() []User { 142 var result []User 143 for _, user := range m.Users_.Users_ { 144 result = append(result, user) 145 } 146 sort.Sort(ByName(result)) 147 return result 148 } 149 150 // AddUser implements Model. 151 func (m *model) AddUser(args UserArgs) { 152 m.Users_.Users_ = append(m.Users_.Users_, newUser(args)) 153 } 154 155 func (m *model) setUsers(userList []*user) { 156 m.Users_ = users{ 157 Version: 1, 158 Users_: userList, 159 } 160 } 161 162 // Machines implements Model. 163 func (m *model) Machines() []Machine { 164 var result []Machine 165 for _, machine := range m.Machines_.Machines_ { 166 result = append(result, machine) 167 } 168 return result 169 } 170 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) 175 return machine 176 } 177 178 func (m *model) setMachines(machineList []*machine) { 179 m.Machines_ = machines{ 180 Version: 1, 181 Machines_: machineList, 182 } 183 } 184 185 // Services implements Model. 186 func (m *model) Services() []Service { 187 var result []Service 188 for _, service := range m.Services_.Services_ { 189 result = append(result, service) 190 } 191 return result 192 } 193 194 func (m *model) service(name string) *service { 195 for _, service := range m.Services_.Services_ { 196 if service.Name() == name { 197 return service 198 } 199 } 200 return nil 201 } 202 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) 207 return service 208 } 209 210 func (m *model) setServices(serviceList []*service) { 211 m.Services_ = services{ 212 Version: 1, 213 Services_: serviceList, 214 } 215 } 216 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) 222 } 223 return result 224 } 225 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) 230 return relation 231 } 232 233 func (m *model) setRelations(relationList []*relation) { 234 m.Relations_ = relations{ 235 Version: 1, 236 Relations_: relationList, 237 } 238 } 239 240 // Sequences implements Model. 241 func (m *model) Sequences() map[string]int { 242 return m.Sequences_ 243 } 244 245 // SetSequence implements Model. 246 func (m *model) SetSequence(name string, value int) { 247 m.Sequences_[name] = value 248 } 249 250 // Constraints implements HasConstraints. 251 func (m *model) Constraints() Constraints { 252 if m.Constraints_ == nil { 253 return nil 254 } 255 return m.Constraints_ 256 } 257 258 // SetConstraints implements HasConstraints. 259 func (m *model) SetConstraints(args ConstraintsArgs) { 260 m.Constraints_ = newConstraints(args) 261 } 262 263 // Validate implements Model. 264 func (m *model) Validate() error { 265 // A model needs an owner. 266 if m.Owner_ == "" { 267 return errors.NotValidf("missing model owner") 268 } 269 270 unitsWithOpenPorts := set.NewStrings() 271 for _, machine := range m.Machines_.Machines_ { 272 if err := machine.Validate(); err != nil { 273 return errors.Trace(err) 274 } 275 for _, op := range machine.OpenedPorts() { 276 for _, pr := range op.OpenPorts() { 277 unitsWithOpenPorts.Add(pr.UnitName()) 278 } 279 } 280 } 281 allUnits := set.NewStrings() 282 for _, service := range m.Services_.Services_ { 283 if err := service.Validate(); err != nil { 284 return errors.Trace(err) 285 } 286 allUnits = allUnits.Union(service.unitNames()) 287 } 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()) 293 } 294 295 return m.validateRelations() 296 } 297 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()) 305 if service == nil { 306 return errors.Errorf("unknown service %q for relation id %d", ep.ServiceName(), relation.Id()) 307 } 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()) 313 } 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()) 316 } 317 } 318 } 319 return nil 320 } 321 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. 324 // 325 // This method is a package internal serialisation method. 326 func importModel(source map[string]interface{}) (*model, error) { 327 version, err := getVersion(source) 328 if err != nil { 329 return nil, errors.Trace(err) 330 } 331 332 importFunc, ok := modelDeserializationFuncs[version] 333 if !ok { 334 return nil, errors.NotValidf("version %d", version) 335 } 336 337 return importFunc(source) 338 } 339 340 type modelDeserializationFunc func(map[string]interface{}) (*model, error) 341 342 var modelDeserializationFuncs = map[int]modelDeserializationFunc{ 343 1: importModelV1, 344 } 345 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()), 357 } 358 // Some values don't have to be there. 359 defaults := schema.Defaults{ 360 "latest-tools": schema.Omit, 361 "blocks": schema.Omit, 362 } 363 addAnnotationSchema(fields, defaults) 364 addConstraintsSchema(fields, defaults) 365 checker := schema.FieldMap(fields, defaults) 366 367 coerced, err := checker.Coerce(source, nil) 368 if err != nil { 369 return nil, errors.Annotatef(err, "model v1 schema check failed") 370 } 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. 374 375 result := &model{ 376 Version: 1, 377 Owner_: valid["owner"].(string), 378 Config_: valid["config"].(map[string]interface{}), 379 Sequences_: make(map[string]int), 380 Blocks_: convertToStringMap(valid["blocks"]), 381 } 382 result.importAnnotations(valid) 383 sequences := valid["sequences"].(map[string]interface{}) 384 for key, value := range sequences { 385 result.SetSequence(key, int(value.(int64))) 386 } 387 388 if constraintsMap, ok := valid["constraints"]; ok { 389 constraints, err := importConstraints(constraintsMap.(map[string]interface{})) 390 if err != nil { 391 return nil, errors.Trace(err) 392 } 393 result.Constraints_ = constraints 394 } 395 396 if availableTools, ok := valid["latest-tools"]; ok { 397 num, err := version.Parse(availableTools.(string)) 398 if err != nil { 399 return nil, errors.Trace(err) 400 } 401 result.LatestToolsVersion_ = num 402 } 403 404 userMap := valid["users"].(map[string]interface{}) 405 users, err := importUsers(userMap) 406 if err != nil { 407 return nil, errors.Annotate(err, "users") 408 } 409 result.setUsers(users) 410 411 machineMap := valid["machines"].(map[string]interface{}) 412 machines, err := importMachines(machineMap) 413 if err != nil { 414 return nil, errors.Annotate(err, "machines") 415 } 416 result.setMachines(machines) 417 418 serviceMap := valid["services"].(map[string]interface{}) 419 services, err := importServices(serviceMap) 420 if err != nil { 421 return nil, errors.Annotate(err, "services") 422 } 423 result.setServices(services) 424 425 relationMap := valid["relations"].(map[string]interface{}) 426 relations, err := importRelations(relationMap) 427 if err != nil { 428 return nil, errors.Annotate(err, "relations") 429 } 430 result.setRelations(relations) 431 432 return result, nil 433 }