github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/core/description/application.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  	"encoding/base64"
     8  
     9  	"github.com/juju/utils/set"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/schema"
    13  	"gopkg.in/juju/names.v2"
    14  )
    15  
    16  type applications struct {
    17  	Version       int            `yaml:"version"`
    18  	Applications_ []*application `yaml:"applications"`
    19  }
    20  
    21  type application struct {
    22  	Name_                 string `yaml:"name"`
    23  	Series_               string `yaml:"series"`
    24  	Subordinate_          bool   `yaml:"subordinate,omitempty"`
    25  	CharmURL_             string `yaml:"charm-url"`
    26  	Channel_              string `yaml:"cs-channel"`
    27  	CharmModifiedVersion_ int    `yaml:"charm-mod-version"`
    28  
    29  	// ForceCharm is true if an upgrade charm is forced.
    30  	// It means upgrade even if the charm is in an error state.
    31  	ForceCharm_ bool `yaml:"force-charm,omitempty"`
    32  	Exposed_    bool `yaml:"exposed,omitempty"`
    33  	MinUnits_   int  `yaml:"min-units,omitempty"`
    34  
    35  	Status_        *status `yaml:"status"`
    36  	StatusHistory_ `yaml:"status-history"`
    37  
    38  	Settings_ map[string]interface{} `yaml:"settings"`
    39  
    40  	Leader_             string                 `yaml:"leader,omitempty"`
    41  	LeadershipSettings_ map[string]interface{} `yaml:"leadership-settings"`
    42  
    43  	MetricsCredentials_ string `yaml:"metrics-creds,omitempty"`
    44  
    45  	// unit count will be assumed by the number of units associated.
    46  	Units_ units `yaml:"units"`
    47  
    48  	Annotations_ `yaml:"annotations,omitempty"`
    49  
    50  	Constraints_        *constraints                  `yaml:"constraints,omitempty"`
    51  	StorageConstraints_ map[string]*storageconstraint `yaml:"storage-constraints,omitempty"`
    52  }
    53  
    54  // ApplicationArgs is an argument struct used to add an application to the Model.
    55  type ApplicationArgs struct {
    56  	Tag                  names.ApplicationTag
    57  	Series               string
    58  	Subordinate          bool
    59  	CharmURL             string
    60  	Channel              string
    61  	CharmModifiedVersion int
    62  	ForceCharm           bool
    63  	Exposed              bool
    64  	MinUnits             int
    65  	Settings             map[string]interface{}
    66  	Leader               string
    67  	LeadershipSettings   map[string]interface{}
    68  	StorageConstraints   map[string]StorageConstraintArgs
    69  	MetricsCredentials   []byte
    70  }
    71  
    72  func newApplication(args ApplicationArgs) *application {
    73  	creds := base64.StdEncoding.EncodeToString(args.MetricsCredentials)
    74  	app := &application{
    75  		Name_:                 args.Tag.Id(),
    76  		Series_:               args.Series,
    77  		Subordinate_:          args.Subordinate,
    78  		CharmURL_:             args.CharmURL,
    79  		Channel_:              args.Channel,
    80  		CharmModifiedVersion_: args.CharmModifiedVersion,
    81  		ForceCharm_:           args.ForceCharm,
    82  		Exposed_:              args.Exposed,
    83  		MinUnits_:             args.MinUnits,
    84  		Settings_:             args.Settings,
    85  		Leader_:               args.Leader,
    86  		LeadershipSettings_:   args.LeadershipSettings,
    87  		MetricsCredentials_:   creds,
    88  		StatusHistory_:        newStatusHistory(),
    89  	}
    90  	app.setUnits(nil)
    91  	if len(args.StorageConstraints) > 0 {
    92  		app.StorageConstraints_ = make(map[string]*storageconstraint)
    93  		for key, value := range args.StorageConstraints {
    94  			app.StorageConstraints_[key] = newStorageConstraint(value)
    95  		}
    96  	}
    97  	return app
    98  }
    99  
   100  // Tag implements Application.
   101  func (s *application) Tag() names.ApplicationTag {
   102  	return names.NewApplicationTag(s.Name_)
   103  }
   104  
   105  // Name implements Application.
   106  func (s *application) Name() string {
   107  	return s.Name_
   108  }
   109  
   110  // Series implements Application.
   111  func (s *application) Series() string {
   112  	return s.Series_
   113  }
   114  
   115  // Subordinate implements Application.
   116  func (s *application) Subordinate() bool {
   117  	return s.Subordinate_
   118  }
   119  
   120  // CharmURL implements Application.
   121  func (s *application) CharmURL() string {
   122  	return s.CharmURL_
   123  }
   124  
   125  // Channel implements Application.
   126  func (s *application) Channel() string {
   127  	return s.Channel_
   128  }
   129  
   130  // CharmModifiedVersion implements Application.
   131  func (s *application) CharmModifiedVersion() int {
   132  	return s.CharmModifiedVersion_
   133  }
   134  
   135  // ForceCharm implements Application.
   136  func (s *application) ForceCharm() bool {
   137  	return s.ForceCharm_
   138  }
   139  
   140  // Exposed implements Application.
   141  func (s *application) Exposed() bool {
   142  	return s.Exposed_
   143  }
   144  
   145  // MinUnits implements Application.
   146  func (s *application) MinUnits() int {
   147  	return s.MinUnits_
   148  }
   149  
   150  // Settings implements Application.
   151  func (s *application) Settings() map[string]interface{} {
   152  	return s.Settings_
   153  }
   154  
   155  // Leader implements Application.
   156  func (s *application) Leader() string {
   157  	return s.Leader_
   158  }
   159  
   160  // LeadershipSettings implements Application.
   161  func (s *application) LeadershipSettings() map[string]interface{} {
   162  	return s.LeadershipSettings_
   163  }
   164  
   165  // StorageConstraints implements Application.
   166  func (a *application) StorageConstraints() map[string]StorageConstraint {
   167  	result := make(map[string]StorageConstraint)
   168  	for key, value := range a.StorageConstraints_ {
   169  		result[key] = value
   170  	}
   171  	return result
   172  }
   173  
   174  // MetricsCredentials implements Application.
   175  func (s *application) MetricsCredentials() []byte {
   176  	// Here we are explicitly throwing away any decode error. We check that
   177  	// the creds can be decoded when we parse the incoming data, or we encode
   178  	// an incoming byte array, so in both cases, we know that the stored creds
   179  	// can be decoded.
   180  	creds, _ := base64.StdEncoding.DecodeString(s.MetricsCredentials_)
   181  	return creds
   182  }
   183  
   184  // Status implements Application.
   185  func (s *application) Status() Status {
   186  	// To avoid typed nils check nil here.
   187  	if s.Status_ == nil {
   188  		return nil
   189  	}
   190  	return s.Status_
   191  }
   192  
   193  // SetStatus implements Application.
   194  func (s *application) SetStatus(args StatusArgs) {
   195  	s.Status_ = newStatus(args)
   196  }
   197  
   198  // Units implements Application.
   199  func (s *application) Units() []Unit {
   200  	result := make([]Unit, len(s.Units_.Units_))
   201  	for i, u := range s.Units_.Units_ {
   202  		result[i] = u
   203  	}
   204  	return result
   205  }
   206  
   207  func (s *application) unitNames() set.Strings {
   208  	result := set.NewStrings()
   209  	for _, u := range s.Units_.Units_ {
   210  		result.Add(u.Name())
   211  	}
   212  	return result
   213  }
   214  
   215  // AddUnit implements Application.
   216  func (s *application) AddUnit(args UnitArgs) Unit {
   217  	u := newUnit(args)
   218  	s.Units_.Units_ = append(s.Units_.Units_, u)
   219  	return u
   220  }
   221  
   222  func (s *application) setUnits(unitList []*unit) {
   223  	s.Units_ = units{
   224  		Version: 1,
   225  		Units_:  unitList,
   226  	}
   227  }
   228  
   229  // Constraints implements HasConstraints.
   230  func (s *application) Constraints() Constraints {
   231  	if s.Constraints_ == nil {
   232  		return nil
   233  	}
   234  	return s.Constraints_
   235  }
   236  
   237  // SetConstraints implements HasConstraints.
   238  func (s *application) SetConstraints(args ConstraintsArgs) {
   239  	s.Constraints_ = newConstraints(args)
   240  }
   241  
   242  // Validate implements Application.
   243  func (s *application) Validate() error {
   244  	if s.Name_ == "" {
   245  		return errors.NotValidf("application missing name")
   246  	}
   247  	if s.Status_ == nil {
   248  		return errors.NotValidf("application %q missing status", s.Name_)
   249  	}
   250  	// If leader is set, it must match one of the units.
   251  	var leaderFound bool
   252  	// All of the applications units should also be valid.
   253  	for _, u := range s.Units() {
   254  		if err := u.Validate(); err != nil {
   255  			return errors.Trace(err)
   256  		}
   257  		// We know that the unit has a name, because it validated correctly.
   258  		if u.Name() == s.Leader_ {
   259  			leaderFound = true
   260  		}
   261  	}
   262  	if s.Leader_ != "" && !leaderFound {
   263  		return errors.NotValidf("missing unit for leader %q", s.Leader_)
   264  	}
   265  	return nil
   266  }
   267  
   268  func importApplications(source map[string]interface{}) ([]*application, error) {
   269  	checker := versionedChecker("applications")
   270  	coerced, err := checker.Coerce(source, nil)
   271  	if err != nil {
   272  		return nil, errors.Annotatef(err, "applications version schema check failed")
   273  	}
   274  	valid := coerced.(map[string]interface{})
   275  
   276  	version := int(valid["version"].(int64))
   277  	importFunc, ok := applicationDeserializationFuncs[version]
   278  	if !ok {
   279  		return nil, errors.NotValidf("version %d", version)
   280  	}
   281  	sourceList := valid["applications"].([]interface{})
   282  	return importApplicationList(sourceList, importFunc)
   283  }
   284  
   285  func importApplicationList(sourceList []interface{}, importFunc applicationDeserializationFunc) ([]*application, error) {
   286  	result := make([]*application, 0, len(sourceList))
   287  	for i, value := range sourceList {
   288  		source, ok := value.(map[string]interface{})
   289  		if !ok {
   290  			return nil, errors.Errorf("unexpected value for application %d, %T", i, value)
   291  		}
   292  		application, err := importFunc(source)
   293  		if err != nil {
   294  			return nil, errors.Annotatef(err, "application %d", i)
   295  		}
   296  		result = append(result, application)
   297  	}
   298  	return result, nil
   299  }
   300  
   301  type applicationDeserializationFunc func(map[string]interface{}) (*application, error)
   302  
   303  var applicationDeserializationFuncs = map[int]applicationDeserializationFunc{
   304  	1: importApplicationV1,
   305  }
   306  
   307  func importApplicationV1(source map[string]interface{}) (*application, error) {
   308  	fields := schema.Fields{
   309  		"name":                schema.String(),
   310  		"series":              schema.String(),
   311  		"subordinate":         schema.Bool(),
   312  		"charm-url":           schema.String(),
   313  		"cs-channel":          schema.String(),
   314  		"charm-mod-version":   schema.Int(),
   315  		"force-charm":         schema.Bool(),
   316  		"exposed":             schema.Bool(),
   317  		"min-units":           schema.Int(),
   318  		"status":              schema.StringMap(schema.Any()),
   319  		"settings":            schema.StringMap(schema.Any()),
   320  		"leader":              schema.String(),
   321  		"leadership-settings": schema.StringMap(schema.Any()),
   322  		"storage-constraints": schema.StringMap(schema.StringMap(schema.Any())),
   323  		"metrics-creds":       schema.String(),
   324  		"units":               schema.StringMap(schema.Any()),
   325  	}
   326  
   327  	defaults := schema.Defaults{
   328  		"subordinate":         false,
   329  		"force-charm":         false,
   330  		"exposed":             false,
   331  		"min-units":           int64(0),
   332  		"leader":              "",
   333  		"metrics-creds":       "",
   334  		"storage-constraints": schema.Omit,
   335  	}
   336  	addAnnotationSchema(fields, defaults)
   337  	addConstraintsSchema(fields, defaults)
   338  	addStatusHistorySchema(fields)
   339  	checker := schema.FieldMap(fields, defaults)
   340  
   341  	coerced, err := checker.Coerce(source, nil)
   342  	if err != nil {
   343  		return nil, errors.Annotatef(err, "application v1 schema check failed")
   344  	}
   345  	valid := coerced.(map[string]interface{})
   346  	// From here we know that the map returned from the schema coercion
   347  	// contains fields of the right type.
   348  	result := &application{
   349  		Name_:                 valid["name"].(string),
   350  		Series_:               valid["series"].(string),
   351  		Subordinate_:          valid["subordinate"].(bool),
   352  		CharmURL_:             valid["charm-url"].(string),
   353  		Channel_:              valid["cs-channel"].(string),
   354  		CharmModifiedVersion_: int(valid["charm-mod-version"].(int64)),
   355  		ForceCharm_:           valid["force-charm"].(bool),
   356  		Exposed_:              valid["exposed"].(bool),
   357  		MinUnits_:             int(valid["min-units"].(int64)),
   358  		Settings_:             valid["settings"].(map[string]interface{}),
   359  		Leader_:               valid["leader"].(string),
   360  		LeadershipSettings_:   valid["leadership-settings"].(map[string]interface{}),
   361  		StatusHistory_:        newStatusHistory(),
   362  	}
   363  	result.importAnnotations(valid)
   364  	if err := result.importStatusHistory(valid); err != nil {
   365  		return nil, errors.Trace(err)
   366  	}
   367  
   368  	if constraintsMap, ok := valid["constraints"]; ok {
   369  		constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
   370  		if err != nil {
   371  			return nil, errors.Trace(err)
   372  		}
   373  		result.Constraints_ = constraints
   374  	}
   375  
   376  	if constraintsMap, ok := valid["storage-constraints"]; ok {
   377  		constraints, err := importStorageConstraints(constraintsMap.(map[string]interface{}))
   378  		if err != nil {
   379  			return nil, errors.Trace(err)
   380  		}
   381  		result.StorageConstraints_ = constraints
   382  	}
   383  
   384  	encodedCreds := valid["metrics-creds"].(string)
   385  	// The model stores the creds encoded, but we want to make sure that
   386  	// we are storing something that can be decoded.
   387  	if _, err := base64.StdEncoding.DecodeString(encodedCreds); err != nil {
   388  		return nil, errors.Annotate(err, "metrics credentials not valid")
   389  	}
   390  	result.MetricsCredentials_ = encodedCreds
   391  
   392  	status, err := importStatus(valid["status"].(map[string]interface{}))
   393  	if err != nil {
   394  		return nil, errors.Trace(err)
   395  	}
   396  	result.Status_ = status
   397  
   398  	units, err := importUnits(valid["units"].(map[string]interface{}))
   399  	if err != nil {
   400  		return nil, errors.Trace(err)
   401  	}
   402  	result.setUnits(units)
   403  
   404  	return result, nil
   405  }