1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
9
"github.com/juju/utils/set"
11
"github.com/juju/errors"
12
"github.com/juju/names"
13
"github.com/juju/schema"
16
type services struct {
17
Version int `yaml:"version"`
18
Services_ []*service `yaml:"services"`
22
Name_ string `yaml:"name"`
23
Series_ string `yaml:"series"`
24
Subordinate_ bool `yaml:"subordinate,omitempty"`
25
CharmURL_ string `yaml:"charm-url"`
26
CharmModifiedVersion_ int `yaml:"charm-mod-version"`
28
// ForceCharm is true if an upgrade charm is forced.
29
// It means upgrade even if the charm is in an error state.
30
ForceCharm_ bool `yaml:"force-charm,omitempty"`
31
Exposed_ bool `yaml:"exposed,omitempty"`
32
MinUnits_ int `yaml:"min-units,omitempty"`
34
Status_ *status `yaml:"status"`
35
StatusHistory_ `yaml:"status-history"`
37
Settings_ map[string]interface{} `yaml:"settings"`
38
SettingsRefCount_ int `yaml:"settings-refcount"`
40
Leader_ string `yaml:"leader,omitempty"`
41
LeadershipSettings_ map[string]interface{} `yaml:"leadership-settings"`
43
MetricsCredentials_ string `yaml:"metrics-creds,omitempty"`
45
// unit count will be assumed by the number of units associated.
46
Units_ units `yaml:"units"`
48
Annotations_ `yaml:"annotations,omitempty"`
50
Constraints_ *constraints `yaml:"constraints,omitempty"`
53
// Storage Constraints
56
// ServiceArgs is an argument struct used to add a service to the Model.
57
type ServiceArgs struct {
62
CharmModifiedVersion int
66
Settings map[string]interface{}
69
LeadershipSettings map[string]interface{}
70
MetricsCredentials []byte
73
func newService(args ServiceArgs) *service {
74
creds := base64.StdEncoding.EncodeToString(args.MetricsCredentials)
78
Subordinate_: args.Subordinate,
79
CharmURL_: args.CharmURL,
80
CharmModifiedVersion_: args.CharmModifiedVersion,
81
ForceCharm_: args.ForceCharm,
82
Exposed_: args.Exposed,
83
MinUnits_: args.MinUnits,
84
Settings_: args.Settings,
85
SettingsRefCount_: args.SettingsRefCount,
87
LeadershipSettings_: args.LeadershipSettings,
88
MetricsCredentials_: creds,
89
StatusHistory_: newStatusHistory(),
95
// Tag implements Service.
96
func (s *service) Tag() names.ServiceTag {
97
return names.NewServiceTag(s.Name_)
100
// Name implements Service.
101
func (s *service) Name() string {
105
// Series implements Service.
106
func (s *service) Series() string {
110
// Subordinate implements Service.
111
func (s *service) Subordinate() bool {
112
return s.Subordinate_
115
// CharmURL implements Service.
116
func (s *service) CharmURL() string {
120
// CharmModifiedVersion implements Service.
121
func (s *service) CharmModifiedVersion() int {
122
return s.CharmModifiedVersion_
125
// ForceCharm implements Service.
126
func (s *service) ForceCharm() bool {
130
// Exposed implements Service.
131
func (s *service) Exposed() bool {
135
// MinUnits implements Service.
136
func (s *service) MinUnits() int {
140
// Settings implements Service.
141
func (s *service) Settings() map[string]interface{} {
145
// SettingsRefCount implements Service.
146
func (s *service) SettingsRefCount() int {
147
return s.SettingsRefCount_
150
// Leader implements Service.
151
func (s *service) Leader() string {
155
// LeadershipSettings implements Service.
156
func (s *service) LeadershipSettings() map[string]interface{} {
157
return s.LeadershipSettings_
160
// MetricsCredentials implements Service.
161
func (s *service) MetricsCredentials() []byte {
162
// Here we are explicitly throwing away any decode error. We check that
163
// the creds can be decoded when we parse the incoming data, or we encode
164
// an incoming byte array, so in both cases, we know that the stored creds
166
creds, _ := base64.StdEncoding.DecodeString(s.MetricsCredentials_)
170
// Status implements Service.
171
func (s *service) Status() Status {
172
// To avoid typed nils check nil here.
173
if s.Status_ == nil {
179
// SetStatus implements Service.
180
func (s *service) SetStatus(args StatusArgs) {
181
s.Status_ = newStatus(args)
184
// Units implements Service.
185
func (s *service) Units() []Unit {
186
result := make([]Unit, len(s.Units_.Units_))
187
for i, u := range s.Units_.Units_ {
193
func (s *service) unitNames() set.Strings {
194
result := set.NewStrings()
195
for _, u := range s.Units_.Units_ {
201
// AddUnit implements Service.
202
func (s *service) AddUnit(args UnitArgs) Unit {
204
s.Units_.Units_ = append(s.Units_.Units_, u)
208
func (s *service) setUnits(unitList []*unit) {
215
// Constraints implements HasConstraints.
216
func (s *service) Constraints() Constraints {
217
if s.Constraints_ == nil {
220
return s.Constraints_
223
// SetConstraints implements HasConstraints.
224
func (s *service) SetConstraints(args ConstraintsArgs) {
225
s.Constraints_ = newConstraints(args)
228
// Validate implements Service.
229
func (s *service) Validate() error {
231
return errors.NotValidf("service missing name")
233
if s.Status_ == nil {
234
return errors.NotValidf("service %q missing status", s.Name_)
236
// If leader is set, it must match one of the units.
238
// All of the services units should also be valid.
239
for _, u := range s.Units() {
240
if err := u.Validate(); err != nil {
241
return errors.Trace(err)
243
// We know that the unit has a name, because it validated correctly.
244
if u.Name() == s.Leader_ {
248
if s.Leader_ != "" && !leaderFound {
249
return errors.NotValidf("missing unit for leader %q", s.Leader_)
254
func importServices(source map[string]interface{}) ([]*service, error) {
255
checker := versionedChecker("services")
256
coerced, err := checker.Coerce(source, nil)
258
return nil, errors.Annotatef(err, "services version schema check failed")
260
valid := coerced.(map[string]interface{})
262
version := int(valid["version"].(int64))
263
importFunc, ok := serviceDeserializationFuncs[version]
265
return nil, errors.NotValidf("version %d", version)
267
sourceList := valid["services"].([]interface{})
268
return importServiceList(sourceList, importFunc)
271
func importServiceList(sourceList []interface{}, importFunc serviceDeserializationFunc) ([]*service, error) {
272
result := make([]*service, 0, len(sourceList))
273
for i, value := range sourceList {
274
source, ok := value.(map[string]interface{})
276
return nil, errors.Errorf("unexpected value for service %d, %T", i, value)
278
service, err := importFunc(source)
280
return nil, errors.Annotatef(err, "service %d", i)
282
result = append(result, service)
287
type serviceDeserializationFunc func(map[string]interface{}) (*service, error)
289
var serviceDeserializationFuncs = map[int]serviceDeserializationFunc{
293
func importServiceV1(source map[string]interface{}) (*service, error) {
294
fields := schema.Fields{
295
"name": schema.String(),
296
"series": schema.String(),
297
"subordinate": schema.Bool(),
298
"charm-url": schema.String(),
299
"charm-mod-version": schema.Int(),
300
"force-charm": schema.Bool(),
301
"exposed": schema.Bool(),
302
"min-units": schema.Int(),
303
"status": schema.StringMap(schema.Any()),
304
"settings": schema.StringMap(schema.Any()),
305
"settings-refcount": schema.Int(),
306
"leader": schema.String(),
307
"leadership-settings": schema.StringMap(schema.Any()),
308
"metrics-creds": schema.String(),
309
"units": schema.StringMap(schema.Any()),
312
defaults := schema.Defaults{
313
"subordinate": false,
314
"force-charm": false,
316
"min-units": int64(0),
320
addAnnotationSchema(fields, defaults)
321
addConstraintsSchema(fields, defaults)
322
addStatusHistorySchema(fields)
323
checker := schema.FieldMap(fields, defaults)
325
coerced, err := checker.Coerce(source, nil)
327
return nil, errors.Annotatef(err, "service v1 schema check failed")
329
valid := coerced.(map[string]interface{})
330
// From here we know that the map returned from the schema coercion
331
// contains fields of the right type.
333
Name_: valid["name"].(string),
334
Series_: valid["series"].(string),
335
Subordinate_: valid["subordinate"].(bool),
336
CharmURL_: valid["charm-url"].(string),
337
CharmModifiedVersion_: int(valid["charm-mod-version"].(int64)),
338
ForceCharm_: valid["force-charm"].(bool),
339
Exposed_: valid["exposed"].(bool),
340
MinUnits_: int(valid["min-units"].(int64)),
341
Settings_: valid["settings"].(map[string]interface{}),
342
SettingsRefCount_: int(valid["settings-refcount"].(int64)),
343
Leader_: valid["leader"].(string),
344
LeadershipSettings_: valid["leadership-settings"].(map[string]interface{}),
345
StatusHistory_: newStatusHistory(),
347
result.importAnnotations(valid)
348
if err := result.importStatusHistory(valid); err != nil {
349
return nil, errors.Trace(err)
352
if constraintsMap, ok := valid["constraints"]; ok {
353
constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
355
return nil, errors.Trace(err)
357
result.Constraints_ = constraints
360
encodedCreds := valid["metrics-creds"].(string)
361
// The model stores the creds encoded, but we want to make sure that
362
// we are storing something that can be decoded.
363
if _, err := base64.StdEncoding.DecodeString(encodedCreds); err != nil {
364
return nil, errors.Annotate(err, "metrics credentials not valid")
366
result.MetricsCredentials_ = encodedCreds
368
status, err := importStatus(valid["status"].(map[string]interface{}))
370
return nil, errors.Trace(err)
372
result.Status_ = status
374
units, err := importUnits(valid["units"].(map[string]interface{}))
376
return nil, errors.Trace(err)
378
result.setUnits(units)