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 }