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