~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/core/description/service.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        CharmModifiedVersion_ int    `yaml:"charm-mod-version"`
 
27
 
 
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"`
 
33
 
 
34
        Status_        *status `yaml:"status"`
 
35
        StatusHistory_ `yaml:"status-history"`
 
36
 
 
37
        Settings_         map[string]interface{} `yaml:"settings"`
 
38
        SettingsRefCount_ int                    `yaml:"settings-refcount"`
 
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
 
 
52
        // Requested Networks
 
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
        CharmModifiedVersion int
 
63
        ForceCharm           bool
 
64
        Exposed              bool
 
65
        MinUnits             int
 
66
        Settings             map[string]interface{}
 
67
        SettingsRefCount     int
 
68
        Leader               string
 
69
        LeadershipSettings   map[string]interface{}
 
70
        MetricsCredentials   []byte
 
71
}
 
72
 
 
73
func newService(args ServiceArgs) *service {
 
74
        creds := base64.StdEncoding.EncodeToString(args.MetricsCredentials)
 
75
        svc := &service{
 
76
                Name_:                 args.Tag.Id(),
 
77
                Series_:               args.Series,
 
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,
 
86
                Leader_:               args.Leader,
 
87
                LeadershipSettings_:   args.LeadershipSettings,
 
88
                MetricsCredentials_:   creds,
 
89
                StatusHistory_:        newStatusHistory(),
 
90
        }
 
91
        svc.setUnits(nil)
 
92
        return svc
 
93
}
 
94
 
 
95
// Tag implements Service.
 
96
func (s *service) Tag() names.ServiceTag {
 
97
        return names.NewServiceTag(s.Name_)
 
98
}
 
99
 
 
100
// Name implements Service.
 
101
func (s *service) Name() string {
 
102
        return s.Name_
 
103
}
 
104
 
 
105
// Series implements Service.
 
106
func (s *service) Series() string {
 
107
        return s.Series_
 
108
}
 
109
 
 
110
// Subordinate implements Service.
 
111
func (s *service) Subordinate() bool {
 
112
        return s.Subordinate_
 
113
}
 
114
 
 
115
// CharmURL implements Service.
 
116
func (s *service) CharmURL() string {
 
117
        return s.CharmURL_
 
118
}
 
119
 
 
120
// CharmModifiedVersion implements Service.
 
121
func (s *service) CharmModifiedVersion() int {
 
122
        return s.CharmModifiedVersion_
 
123
}
 
124
 
 
125
// ForceCharm implements Service.
 
126
func (s *service) ForceCharm() bool {
 
127
        return s.ForceCharm_
 
128
}
 
129
 
 
130
// Exposed implements Service.
 
131
func (s *service) Exposed() bool {
 
132
        return s.Exposed_
 
133
}
 
134
 
 
135
// MinUnits implements Service.
 
136
func (s *service) MinUnits() int {
 
137
        return s.MinUnits_
 
138
}
 
139
 
 
140
// Settings implements Service.
 
141
func (s *service) Settings() map[string]interface{} {
 
142
        return s.Settings_
 
143
}
 
144
 
 
145
// SettingsRefCount implements Service.
 
146
func (s *service) SettingsRefCount() int {
 
147
        return s.SettingsRefCount_
 
148
}
 
149
 
 
150
// Leader implements Service.
 
151
func (s *service) Leader() string {
 
152
        return s.Leader_
 
153
}
 
154
 
 
155
// LeadershipSettings implements Service.
 
156
func (s *service) LeadershipSettings() map[string]interface{} {
 
157
        return s.LeadershipSettings_
 
158
}
 
159
 
 
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
 
165
        // can be decoded.
 
166
        creds, _ := base64.StdEncoding.DecodeString(s.MetricsCredentials_)
 
167
        return creds
 
168
}
 
169
 
 
170
// Status implements Service.
 
171
func (s *service) Status() Status {
 
172
        // To avoid typed nils check nil here.
 
173
        if s.Status_ == nil {
 
174
                return nil
 
175
        }
 
176
        return s.Status_
 
177
}
 
178
 
 
179
// SetStatus implements Service.
 
180
func (s *service) SetStatus(args StatusArgs) {
 
181
        s.Status_ = newStatus(args)
 
182
}
 
183
 
 
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_ {
 
188
                result[i] = u
 
189
        }
 
190
        return result
 
191
}
 
192
 
 
193
func (s *service) unitNames() set.Strings {
 
194
        result := set.NewStrings()
 
195
        for _, u := range s.Units_.Units_ {
 
196
                result.Add(u.Name())
 
197
        }
 
198
        return result
 
199
}
 
200
 
 
201
// AddUnit implements Service.
 
202
func (s *service) AddUnit(args UnitArgs) Unit {
 
203
        u := newUnit(args)
 
204
        s.Units_.Units_ = append(s.Units_.Units_, u)
 
205
        return u
 
206
}
 
207
 
 
208
func (s *service) setUnits(unitList []*unit) {
 
209
        s.Units_ = units{
 
210
                Version: 1,
 
211
                Units_:  unitList,
 
212
        }
 
213
}
 
214
 
 
215
// Constraints implements HasConstraints.
 
216
func (s *service) Constraints() Constraints {
 
217
        if s.Constraints_ == nil {
 
218
                return nil
 
219
        }
 
220
        return s.Constraints_
 
221
}
 
222
 
 
223
// SetConstraints implements HasConstraints.
 
224
func (s *service) SetConstraints(args ConstraintsArgs) {
 
225
        s.Constraints_ = newConstraints(args)
 
226
}
 
227
 
 
228
// Validate implements Service.
 
229
func (s *service) Validate() error {
 
230
        if s.Name_ == "" {
 
231
                return errors.NotValidf("service missing name")
 
232
        }
 
233
        if s.Status_ == nil {
 
234
                return errors.NotValidf("service %q missing status", s.Name_)
 
235
        }
 
236
        // If leader is set, it must match one of the units.
 
237
        var leaderFound bool
 
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)
 
242
                }
 
243
                // We know that the unit has a name, because it validated correctly.
 
244
                if u.Name() == s.Leader_ {
 
245
                        leaderFound = true
 
246
                }
 
247
        }
 
248
        if s.Leader_ != "" && !leaderFound {
 
249
                return errors.NotValidf("missing unit for leader %q", s.Leader_)
 
250
        }
 
251
        return nil
 
252
}
 
253
 
 
254
func importServices(source map[string]interface{}) ([]*service, error) {
 
255
        checker := versionedChecker("services")
 
256
        coerced, err := checker.Coerce(source, nil)
 
257
        if err != nil {
 
258
                return nil, errors.Annotatef(err, "services version schema check failed")
 
259
        }
 
260
        valid := coerced.(map[string]interface{})
 
261
 
 
262
        version := int(valid["version"].(int64))
 
263
        importFunc, ok := serviceDeserializationFuncs[version]
 
264
        if !ok {
 
265
                return nil, errors.NotValidf("version %d", version)
 
266
        }
 
267
        sourceList := valid["services"].([]interface{})
 
268
        return importServiceList(sourceList, importFunc)
 
269
}
 
270
 
 
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{})
 
275
                if !ok {
 
276
                        return nil, errors.Errorf("unexpected value for service %d, %T", i, value)
 
277
                }
 
278
                service, err := importFunc(source)
 
279
                if err != nil {
 
280
                        return nil, errors.Annotatef(err, "service %d", i)
 
281
                }
 
282
                result = append(result, service)
 
283
        }
 
284
        return result, nil
 
285
}
 
286
 
 
287
type serviceDeserializationFunc func(map[string]interface{}) (*service, error)
 
288
 
 
289
var serviceDeserializationFuncs = map[int]serviceDeserializationFunc{
 
290
        1: importServiceV1,
 
291
}
 
292
 
 
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()),
 
310
        }
 
311
 
 
312
        defaults := schema.Defaults{
 
313
                "subordinate":   false,
 
314
                "force-charm":   false,
 
315
                "exposed":       false,
 
316
                "min-units":     int64(0),
 
317
                "leader":        "",
 
318
                "metrics-creds": "",
 
319
        }
 
320
        addAnnotationSchema(fields, defaults)
 
321
        addConstraintsSchema(fields, defaults)
 
322
        addStatusHistorySchema(fields)
 
323
        checker := schema.FieldMap(fields, defaults)
 
324
 
 
325
        coerced, err := checker.Coerce(source, nil)
 
326
        if err != nil {
 
327
                return nil, errors.Annotatef(err, "service v1 schema check failed")
 
328
        }
 
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.
 
332
        result := &service{
 
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(),
 
346
        }
 
347
        result.importAnnotations(valid)
 
348
        if err := result.importStatusHistory(valid); err != nil {
 
349
                return nil, errors.Trace(err)
 
350
        }
 
351
 
 
352
        if constraintsMap, ok := valid["constraints"]; ok {
 
353
                constraints, err := importConstraints(constraintsMap.(map[string]interface{}))
 
354
                if err != nil {
 
355
                        return nil, errors.Trace(err)
 
356
                }
 
357
                result.Constraints_ = constraints
 
358
        }
 
359
 
 
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")
 
365
        }
 
366
        result.MetricsCredentials_ = encodedCreds
 
367
 
 
368
        status, err := importStatus(valid["status"].(map[string]interface{}))
 
369
        if err != nil {
 
370
                return nil, errors.Trace(err)
 
371
        }
 
372
        result.Status_ = status
 
373
 
 
374
        units, err := importUnits(valid["units"].(map[string]interface{}))
 
375
        if err != nil {
 
376
                return nil, errors.Trace(err)
 
377
        }
 
378
        result.setUnits(units)
 
379
 
 
380
        return result, nil
 
381
}