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  }