blob: f30a9a8f7e3007627395e733cd51dc24e1cf5116 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2016 Canonical Ltd.
2// Licensed under the LGPLv3, see LICENCE file for details.
3
4package gomaasapi
5
6import (
7 "fmt"
8 "net/http"
9
10 "github.com/juju/errors"
11 "github.com/juju/schema"
12 "github.com/juju/version"
13)
14
15// Can't use interface as a type, so add an underscore. Yay.
16type interface_ struct {
17 controller *controller
18
19 resourceURI string
20
21 id int
22 name string
23 type_ string
24 enabled bool
25 tags []string
26
27 vlan *vlan
28 links []*link
29
30 macAddress string
31 effectiveMTU int
32
33 parents []string
34 children []string
35}
36
37func (i *interface_) updateFrom(other *interface_) {
38 i.resourceURI = other.resourceURI
39 i.id = other.id
40 i.name = other.name
41 i.type_ = other.type_
42 i.enabled = other.enabled
43 i.tags = other.tags
44 i.vlan = other.vlan
45 i.links = other.links
46 i.macAddress = other.macAddress
47 i.effectiveMTU = other.effectiveMTU
48 i.parents = other.parents
49 i.children = other.children
50}
51
52// ID implements Interface.
53func (i *interface_) ID() int {
54 return i.id
55}
56
57// Name implements Interface.
58func (i *interface_) Name() string {
59 return i.name
60}
61
62// Parents implements Interface.
63func (i *interface_) Parents() []string {
64 return i.parents
65}
66
67// Children implements Interface.
68func (i *interface_) Children() []string {
69 return i.children
70}
71
72// Type implements Interface.
73func (i *interface_) Type() string {
74 return i.type_
75}
76
77// Enabled implements Interface.
78func (i *interface_) Enabled() bool {
79 return i.enabled
80}
81
82// Tags implements Interface.
83func (i *interface_) Tags() []string {
84 return i.tags
85}
86
87// VLAN implements Interface.
88func (i *interface_) VLAN() VLAN {
89 if i.vlan == nil {
90 return nil
91 }
92 return i.vlan
93}
94
95// Links implements Interface.
96func (i *interface_) Links() []Link {
97 result := make([]Link, len(i.links))
98 for i, link := range i.links {
99 result[i] = link
100 }
101 return result
102}
103
104// MACAddress implements Interface.
105func (i *interface_) MACAddress() string {
106 return i.macAddress
107}
108
109// EffectiveMTU implements Interface.
110func (i *interface_) EffectiveMTU() int {
111 return i.effectiveMTU
112}
113
114// UpdateInterfaceArgs is an argument struct for calling Interface.Update.
115type UpdateInterfaceArgs struct {
116 Name string
117 MACAddress string
118 VLAN VLAN
119}
120
121func (a *UpdateInterfaceArgs) vlanID() int {
122 if a.VLAN == nil {
123 return 0
124 }
125 return a.VLAN.ID()
126}
127
128// Update implements Interface.
129func (i *interface_) Update(args UpdateInterfaceArgs) error {
130 var empty UpdateInterfaceArgs
131 if args == empty {
132 return nil
133 }
134 params := NewURLParams()
135 params.MaybeAdd("name", args.Name)
136 params.MaybeAdd("mac_address", args.MACAddress)
137 params.MaybeAddInt("vlan", args.vlanID())
138 source, err := i.controller.put(i.resourceURI, params.Values)
139 if err != nil {
140 if svrErr, ok := errors.Cause(err).(ServerError); ok {
141 switch svrErr.StatusCode {
142 case http.StatusNotFound:
143 return errors.Wrap(err, NewNoMatchError(svrErr.BodyMessage))
144 case http.StatusForbidden:
145 return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
146 }
147 }
148 return NewUnexpectedError(err)
149 }
150
151 response, err := readInterface(i.controller.apiVersion, source)
152 if err != nil {
153 return errors.Trace(err)
154 }
155 i.updateFrom(response)
156 return nil
157}
158
159// Delete implements Interface.
160func (i *interface_) Delete() error {
161 err := i.controller.delete(i.resourceURI)
162 if err != nil {
163 if svrErr, ok := errors.Cause(err).(ServerError); ok {
164 switch svrErr.StatusCode {
165 case http.StatusNotFound:
166 return errors.Wrap(err, NewNoMatchError(svrErr.BodyMessage))
167 case http.StatusForbidden:
168 return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
169 }
170 }
171 return NewUnexpectedError(err)
172 }
173 return nil
174}
175
176// InterfaceLinkMode is the type of the various link mode constants used for
177// LinkSubnetArgs.
178type InterfaceLinkMode string
179
180const (
181 // LinkModeDHCP - Bring the interface up with DHCP on the given subnet. Only
182 // one subnet can be set to DHCP. If the subnet is managed this interface
183 // will pull from the dynamic IP range.
184 LinkModeDHCP InterfaceLinkMode = "DHCP"
185
186 // LinkModeStatic - Bring the interface up with a STATIC IP address on the
187 // given subnet. Any number of STATIC links can exist on an interface.
188 LinkModeStatic InterfaceLinkMode = "STATIC"
189
190 // LinkModeLinkUp - Bring the interface up only on the given subnet. No IP
191 // address will be assigned to this interface. The interface cannot have any
192 // current DHCP or STATIC links.
193 LinkModeLinkUp InterfaceLinkMode = "LINK_UP"
194)
195
196// LinkSubnetArgs is an argument struct for passing parameters to
197// the Interface.LinkSubnet method.
198type LinkSubnetArgs struct {
199 // Mode is used to describe how the address is provided for the Link.
200 // Required field.
201 Mode InterfaceLinkMode
202 // Subnet is the subnet to link to. Required field.
203 Subnet Subnet
204 // IPAddress is only valid when the Mode is set to LinkModeStatic. If
205 // not specified with a Mode of LinkModeStatic, an IP address from the
206 // subnet will be auto selected.
207 IPAddress string
208 // DefaultGateway will set the gateway IP address for the Subnet as the
209 // default gateway for the machine or device the interface belongs to.
210 // Option can only be used with mode LinkModeStatic.
211 DefaultGateway bool
212}
213
214// Validate ensures that the Mode and Subnet are set, and that the other options
215// are consistent with the Mode.
216func (a *LinkSubnetArgs) Validate() error {
217 switch a.Mode {
218 case LinkModeDHCP, LinkModeLinkUp, LinkModeStatic:
219 case "":
220 return errors.NotValidf("missing Mode")
221 default:
222 return errors.NotValidf("unknown Mode value (%q)", a.Mode)
223 }
224 if a.Subnet == nil {
225 return errors.NotValidf("missing Subnet")
226 }
227 if a.IPAddress != "" && a.Mode != LinkModeStatic {
228 return errors.NotValidf("setting IP Address when Mode is not LinkModeStatic")
229 }
230 if a.DefaultGateway && a.Mode != LinkModeStatic {
231 return errors.NotValidf("specifying DefaultGateway for Mode %q", a.Mode)
232 }
233 return nil
234}
235
236// LinkSubnet implements Interface.
237func (i *interface_) LinkSubnet(args LinkSubnetArgs) error {
238 if err := args.Validate(); err != nil {
239 return errors.Trace(err)
240 }
241 params := NewURLParams()
242 params.Values.Add("mode", string(args.Mode))
243 params.Values.Add("subnet", fmt.Sprint(args.Subnet.ID()))
244 params.MaybeAdd("ip_address", args.IPAddress)
245 params.MaybeAddBool("default_gateway", args.DefaultGateway)
246 source, err := i.controller.post(i.resourceURI, "link_subnet", params.Values)
247 if err != nil {
248 if svrErr, ok := errors.Cause(err).(ServerError); ok {
249 switch svrErr.StatusCode {
250 case http.StatusNotFound, http.StatusBadRequest:
251 return errors.Wrap(err, NewBadRequestError(svrErr.BodyMessage))
252 case http.StatusForbidden:
253 return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
254 case http.StatusServiceUnavailable:
255 return errors.Wrap(err, NewCannotCompleteError(svrErr.BodyMessage))
256 }
257 }
258 return NewUnexpectedError(err)
259 }
260
261 response, err := readInterface(i.controller.apiVersion, source)
262 if err != nil {
263 return errors.Trace(err)
264 }
265 i.updateFrom(response)
266 return nil
267}
268
269func (i *interface_) linkForSubnet(subnet Subnet) *link {
270 for _, link := range i.links {
271 if s := link.Subnet(); s != nil && s.ID() == subnet.ID() {
272 return link
273 }
274 }
275 return nil
276}
277
278// LinkSubnet implements Interface.
279func (i *interface_) UnlinkSubnet(subnet Subnet) error {
280 if subnet == nil {
281 return errors.NotValidf("missing Subnet")
282 }
283 link := i.linkForSubnet(subnet)
284 if link == nil {
285 return errors.NotValidf("unlinked Subnet")
286 }
287 params := NewURLParams()
288 params.Values.Add("id", fmt.Sprint(link.ID()))
289 source, err := i.controller.post(i.resourceURI, "unlink_subnet", params.Values)
290 if err != nil {
291 if svrErr, ok := errors.Cause(err).(ServerError); ok {
292 switch svrErr.StatusCode {
293 case http.StatusNotFound, http.StatusBadRequest:
294 return errors.Wrap(err, NewBadRequestError(svrErr.BodyMessage))
295 case http.StatusForbidden:
296 return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
297 }
298 }
299 return NewUnexpectedError(err)
300 }
301
302 response, err := readInterface(i.controller.apiVersion, source)
303 if err != nil {
304 return errors.Trace(err)
305 }
306 i.updateFrom(response)
307
308 return nil
309}
310
311func readInterface(controllerVersion version.Number, source interface{}) (*interface_, error) {
312 readFunc, err := getInterfaceDeserializationFunc(controllerVersion)
313 if err != nil {
314 return nil, errors.Trace(err)
315 }
316
317 checker := schema.StringMap(schema.Any())
318 coerced, err := checker.Coerce(source, nil)
319 if err != nil {
320 return nil, WrapWithDeserializationError(err, "interface base schema check failed")
321 }
322 valid := coerced.(map[string]interface{})
323 return readFunc(valid)
324}
325
326func readInterfaces(controllerVersion version.Number, source interface{}) ([]*interface_, error) {
327 readFunc, err := getInterfaceDeserializationFunc(controllerVersion)
328 if err != nil {
329 return nil, errors.Trace(err)
330 }
331
332 checker := schema.List(schema.StringMap(schema.Any()))
333 coerced, err := checker.Coerce(source, nil)
334 if err != nil {
335 return nil, WrapWithDeserializationError(err, "interface base schema check failed")
336 }
337 valid := coerced.([]interface{})
338 return readInterfaceList(valid, readFunc)
339}
340
341func getInterfaceDeserializationFunc(controllerVersion version.Number) (interfaceDeserializationFunc, error) {
342 var deserialisationVersion version.Number
343 for v := range interfaceDeserializationFuncs {
344 if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 {
345 deserialisationVersion = v
346 }
347 }
348 if deserialisationVersion == version.Zero {
349 return nil, NewUnsupportedVersionError("no interface read func for version %s", controllerVersion)
350 }
351 return interfaceDeserializationFuncs[deserialisationVersion], nil
352}
353
354func readInterfaceList(sourceList []interface{}, readFunc interfaceDeserializationFunc) ([]*interface_, error) {
355 result := make([]*interface_, 0, len(sourceList))
356 for i, value := range sourceList {
357 source, ok := value.(map[string]interface{})
358 if !ok {
359 return nil, NewDeserializationError("unexpected value for interface %d, %T", i, value)
360 }
361 read, err := readFunc(source)
362 if err != nil {
363 return nil, errors.Annotatef(err, "interface %d", i)
364 }
365 result = append(result, read)
366 }
367 return result, nil
368}
369
370type interfaceDeserializationFunc func(map[string]interface{}) (*interface_, error)
371
372var interfaceDeserializationFuncs = map[version.Number]interfaceDeserializationFunc{
373 twoDotOh: interface_2_0,
374}
375
376func interface_2_0(source map[string]interface{}) (*interface_, error) {
377 fields := schema.Fields{
378 "resource_uri": schema.String(),
379
380 "id": schema.ForceInt(),
381 "name": schema.String(),
382 "type": schema.String(),
383 "enabled": schema.Bool(),
384 "tags": schema.OneOf(schema.Nil(""), schema.List(schema.String())),
385
386 "vlan": schema.OneOf(schema.Nil(""), schema.StringMap(schema.Any())),
387 "links": schema.List(schema.StringMap(schema.Any())),
388
389 "mac_address": schema.OneOf(schema.Nil(""), schema.String()),
390 "effective_mtu": schema.ForceInt(),
391
392 "parents": schema.List(schema.String()),
393 "children": schema.List(schema.String()),
394 }
395 defaults := schema.Defaults{
396 "mac_address": "",
397 }
398 checker := schema.FieldMap(fields, defaults)
399 coerced, err := checker.Coerce(source, nil)
400 if err != nil {
401 return nil, WrapWithDeserializationError(err, "interface 2.0 schema check failed")
402 }
403 valid := coerced.(map[string]interface{})
404 // From here we know that the map returned from the schema coercion
405 // contains fields of the right type.
406
407 var vlan *vlan
408 // If it's not an attribute map then we know it's nil from the schema check.
409 if vlanMap, ok := valid["vlan"].(map[string]interface{}); ok {
410 vlan, err = vlan_2_0(vlanMap)
411 if err != nil {
412 return nil, errors.Trace(err)
413 }
414 }
415
416 links, err := readLinkList(valid["links"].([]interface{}), link_2_0)
417 if err != nil {
418 return nil, errors.Trace(err)
419 }
420 macAddress, _ := valid["mac_address"].(string)
421 result := &interface_{
422 resourceURI: valid["resource_uri"].(string),
423
424 id: valid["id"].(int),
425 name: valid["name"].(string),
426 type_: valid["type"].(string),
427 enabled: valid["enabled"].(bool),
428 tags: convertToStringSlice(valid["tags"]),
429
430 vlan: vlan,
431 links: links,
432
433 macAddress: macAddress,
434 effectiveMTU: valid["effective_mtu"].(int),
435
436 parents: convertToStringSlice(valid["parents"]),
437 children: convertToStringSlice(valid["children"]),
438 }
439 return result, nil
440}