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 }