github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/core/description/relation.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 "github.com/juju/errors" 8 "github.com/juju/schema" 9 "github.com/juju/utils/set" 10 ) 11 12 type relations struct { 13 Version int `yaml:"version"` 14 Relations_ []*relation `yaml:"relations"` 15 } 16 17 type relation struct { 18 Id_ int `yaml:"id"` 19 Key_ string `yaml:"key"` 20 Endpoints_ *endpoints `yaml:"endpoints"` 21 } 22 23 // RelationArgs is an argument struct used to specify a relation. 24 type RelationArgs struct { 25 Id int 26 Key string 27 } 28 29 func newRelation(args RelationArgs) *relation { 30 relation := &relation{ 31 Id_: args.Id, 32 Key_: args.Key, 33 } 34 relation.setEndpoints(nil) 35 return relation 36 } 37 38 // Id implements Relation. 39 func (r *relation) Id() int { 40 return r.Id_ 41 } 42 43 // Key implements Relation. 44 func (r *relation) Key() string { 45 return r.Key_ 46 } 47 48 // Endpoints implements Relation. 49 func (r *relation) Endpoints() []Endpoint { 50 result := make([]Endpoint, len(r.Endpoints_.Endpoints_)) 51 for i, ep := range r.Endpoints_.Endpoints_ { 52 result[i] = ep 53 } 54 return result 55 } 56 57 // AddEndpoint implements Relation. 58 func (r *relation) AddEndpoint(args EndpointArgs) Endpoint { 59 ep := newEndpoint(args) 60 r.Endpoints_.Endpoints_ = append(r.Endpoints_.Endpoints_, ep) 61 return ep 62 } 63 64 func (r *relation) setEndpoints(endpointList []*endpoint) { 65 r.Endpoints_ = &endpoints{ 66 Version: 1, 67 Endpoints_: endpointList, 68 } 69 } 70 71 func importRelations(source map[string]interface{}) ([]*relation, error) { 72 checker := versionedChecker("relations") 73 coerced, err := checker.Coerce(source, nil) 74 if err != nil { 75 return nil, errors.Annotatef(err, "relations version schema check failed") 76 } 77 valid := coerced.(map[string]interface{}) 78 79 version := int(valid["version"].(int64)) 80 importFunc, ok := relationDeserializationFuncs[version] 81 if !ok { 82 return nil, errors.NotValidf("version %d", version) 83 } 84 relationList := valid["relations"].([]interface{}) 85 return importRelationList(relationList, importFunc) 86 } 87 88 func importRelationList(sourceList []interface{}, importFunc relationDeserializationFunc) ([]*relation, error) { 89 result := make([]*relation, 0, len(sourceList)) 90 for i, value := range sourceList { 91 source, ok := value.(map[string]interface{}) 92 if !ok { 93 return nil, errors.Errorf("unexpected value for relation %d, %T", i, value) 94 } 95 relation, err := importFunc(source) 96 if err != nil { 97 return nil, errors.Annotatef(err, "relation %d", i) 98 } 99 result = append(result, relation) 100 } 101 return result, nil 102 } 103 104 type relationDeserializationFunc func(map[string]interface{}) (*relation, error) 105 106 var relationDeserializationFuncs = map[int]relationDeserializationFunc{ 107 1: importRelationV1, 108 } 109 110 func importRelationV1(source map[string]interface{}) (*relation, error) { 111 fields := schema.Fields{ 112 "id": schema.Int(), 113 "key": schema.String(), 114 "endpoints": schema.StringMap(schema.Any()), 115 } 116 117 checker := schema.FieldMap(fields, nil) // no defaults 118 119 coerced, err := checker.Coerce(source, nil) 120 if err != nil { 121 return nil, errors.Annotatef(err, "relation v1 schema check failed") 122 } 123 valid := coerced.(map[string]interface{}) 124 // From here we know that the map returned from the schema coercion 125 // contains fields of the right type. 126 result := &relation{ 127 Id_: int(valid["id"].(int64)), 128 Key_: valid["key"].(string), 129 } 130 131 endpoints, err := importEndpoints(valid["endpoints"].(map[string]interface{})) 132 if err != nil { 133 return nil, errors.Trace(err) 134 } 135 result.setEndpoints(endpoints) 136 137 return result, nil 138 } 139 140 type endpoints struct { 141 Version int `yaml:"version"` 142 Endpoints_ []*endpoint `yaml:"endpoints"` 143 } 144 145 type endpoint struct { 146 ApplicationName_ string `yaml:"application-name"` 147 Name_ string `yaml:"name"` 148 Role_ string `yaml:"role"` 149 Interface_ string `yaml:"interface"` 150 Optional_ bool `yaml:"optional"` 151 Limit_ int `yaml:"limit"` 152 Scope_ string `yaml:"scope"` 153 154 UnitSettings_ map[string]map[string]interface{} `yaml:"unit-settings"` 155 } 156 157 // EndpointArgs is an argument struct used to specify a relation. 158 type EndpointArgs struct { 159 ApplicationName string 160 Name string 161 Role string 162 Interface string 163 Optional bool 164 Limit int 165 Scope string 166 } 167 168 func newEndpoint(args EndpointArgs) *endpoint { 169 return &endpoint{ 170 ApplicationName_: args.ApplicationName, 171 Name_: args.Name, 172 Role_: args.Role, 173 Interface_: args.Interface, 174 Optional_: args.Optional, 175 Limit_: args.Limit, 176 Scope_: args.Scope, 177 UnitSettings_: make(map[string]map[string]interface{}), 178 } 179 } 180 181 func (e *endpoint) unitNames() set.Strings { 182 result := set.NewStrings() 183 for key := range e.UnitSettings_ { 184 result.Add(key) 185 } 186 return result 187 } 188 189 // ApplicationName implements Endpoint. 190 func (e *endpoint) ApplicationName() string { 191 return e.ApplicationName_ 192 } 193 194 // Name implements Endpoint. 195 func (e *endpoint) Name() string { 196 return e.Name_ 197 } 198 199 // Role implements Endpoint. 200 func (e *endpoint) Role() string { 201 return e.Role_ 202 } 203 204 // Interface implements Endpoint. 205 func (e *endpoint) Interface() string { 206 return e.Interface_ 207 } 208 209 // Optional implements Endpoint. 210 func (e *endpoint) Optional() bool { 211 return e.Optional_ 212 } 213 214 // Limit implements Endpoint. 215 func (e *endpoint) Limit() int { 216 return e.Limit_ 217 } 218 219 // Scope implements Endpoint. 220 func (e *endpoint) Scope() string { 221 return e.Scope_ 222 } 223 224 // UnitCount implements Endpoint. 225 func (e *endpoint) UnitCount() int { 226 return len(e.UnitSettings_) 227 } 228 229 // Settings implements Endpoint. 230 func (e *endpoint) Settings(unitName string) map[string]interface{} { 231 return e.UnitSettings_[unitName] 232 } 233 234 // SetUnitSettings implements Endpoint. 235 func (e *endpoint) SetUnitSettings(unitName string, settings map[string]interface{}) { 236 e.UnitSettings_[unitName] = settings 237 } 238 239 func importEndpoints(source map[string]interface{}) ([]*endpoint, error) { 240 checker := versionedChecker("endpoints") 241 coerced, err := checker.Coerce(source, nil) 242 if err != nil { 243 return nil, errors.Annotatef(err, "endpoints version schema check failed") 244 } 245 valid := coerced.(map[string]interface{}) 246 247 version := int(valid["version"].(int64)) 248 importFunc, ok := endpointDeserializationFuncs[version] 249 if !ok { 250 return nil, errors.NotValidf("version %d", version) 251 } 252 endpointList := valid["endpoints"].([]interface{}) 253 return importEndpointList(endpointList, importFunc) 254 } 255 256 func importEndpointList(sourceList []interface{}, importFunc endpointDeserializationFunc) ([]*endpoint, error) { 257 result := make([]*endpoint, 0, len(sourceList)) 258 for i, value := range sourceList { 259 source, ok := value.(map[string]interface{}) 260 if !ok { 261 return nil, errors.Errorf("unexpected value for endpoint %d, %T", i, value) 262 } 263 application, err := importFunc(source) 264 if err != nil { 265 return nil, errors.Annotatef(err, "endpoint %d", i) 266 } 267 result = append(result, application) 268 } 269 return result, nil 270 } 271 272 type endpointDeserializationFunc func(map[string]interface{}) (*endpoint, error) 273 274 var endpointDeserializationFuncs = map[int]endpointDeserializationFunc{ 275 1: importEndpointV1, 276 } 277 278 func importEndpointV1(source map[string]interface{}) (*endpoint, error) { 279 fields := schema.Fields{ 280 "application-name": schema.String(), 281 "name": schema.String(), 282 "role": schema.String(), 283 "interface": schema.String(), 284 "optional": schema.Bool(), 285 "limit": schema.Int(), 286 "scope": schema.String(), 287 "unit-settings": schema.StringMap(schema.StringMap(schema.Any())), 288 } 289 290 checker := schema.FieldMap(fields, nil) // No defaults. 291 292 coerced, err := checker.Coerce(source, nil) 293 if err != nil { 294 return nil, errors.Annotatef(err, "endpoint v1 schema check failed") 295 } 296 valid := coerced.(map[string]interface{}) 297 // From here we know that the map returned from the schema coercion 298 // contains fields of the right type. 299 300 result := &endpoint{ 301 ApplicationName_: valid["application-name"].(string), 302 Name_: valid["name"].(string), 303 Role_: valid["role"].(string), 304 Interface_: valid["interface"].(string), 305 Optional_: valid["optional"].(bool), 306 Limit_: int(valid["limit"].(int64)), 307 Scope_: valid["scope"].(string), 308 UnitSettings_: make(map[string]map[string]interface{}), 309 } 310 311 for unitname, settings := range valid["unit-settings"].(map[string]interface{}) { 312 result.UnitSettings_[unitname] = settings.(map[string]interface{}) 313 } 314 315 return result, nil 316 }