blob: 7c9bc701c3f8a4b960cecfd1058cb56c02695697 [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 "strings"
10
11 "github.com/juju/errors"
12 "github.com/juju/schema"
13 "github.com/juju/version"
14)
15
16type device struct {
17 controller *controller
18
19 resourceURI string
20
21 systemID string
22 hostname string
23 fqdn string
24
25 parent string
26 owner string
27
28 ipAddresses []string
29 interfaceSet []*interface_
30 zone *zone
31}
32
33// SystemID implements Device.
34func (d *device) SystemID() string {
35 return d.systemID
36}
37
38// Hostname implements Device.
39func (d *device) Hostname() string {
40 return d.hostname
41}
42
43// FQDN implements Device.
44func (d *device) FQDN() string {
45 return d.fqdn
46}
47
48// Parent implements Device.
49func (d *device) Parent() string {
50 return d.parent
51}
52
53// Owner implements Device.
54func (d *device) Owner() string {
55 return d.owner
56}
57
58// IPAddresses implements Device.
59func (d *device) IPAddresses() []string {
60 return d.ipAddresses
61}
62
63// Zone implements Device.
64func (d *device) Zone() Zone {
65 if d.zone == nil {
66 return nil
67 }
68 return d.zone
69}
70
71// InterfaceSet implements Device.
72func (d *device) InterfaceSet() []Interface {
73 result := make([]Interface, len(d.interfaceSet))
74 for i, v := range d.interfaceSet {
75 v.controller = d.controller
76 result[i] = v
77 }
78 return result
79}
80
81// CreateInterfaceArgs is an argument struct for passing parameters to
82// the Machine.CreateInterface method.
83type CreateInterfaceArgs struct {
84 // Name of the interface (required).
85 Name string
86 // MACAddress is the MAC address of the interface (required).
87 MACAddress string
88 // VLAN is the untagged VLAN the interface is connected to (required).
89 VLAN VLAN
90 // Tags to attach to the interface (optional).
91 Tags []string
92 // MTU - Maximum transmission unit. (optional)
93 MTU int
94 // AcceptRA - Accept router advertisements. (IPv6 only)
95 AcceptRA bool
96 // Autoconf - Perform stateless autoconfiguration. (IPv6 only)
97 Autoconf bool
98}
99
100// Validate checks the required fields are set for the arg structure.
101func (a *CreateInterfaceArgs) Validate() error {
102 if a.Name == "" {
103 return errors.NotValidf("missing Name")
104 }
105 if a.MACAddress == "" {
106 return errors.NotValidf("missing MACAddress")
107 }
108 if a.VLAN == nil {
109 return errors.NotValidf("missing VLAN")
110 }
111 return nil
112}
113
114// interfacesURI used to add interfaces for this device. The operations
115// are on the nodes endpoint, not devices.
116func (d *device) interfacesURI() string {
117 return strings.Replace(d.resourceURI, "devices", "nodes", 1) + "interfaces/"
118}
119
120// CreateInterface implements Device.
121func (d *device) CreateInterface(args CreateInterfaceArgs) (Interface, error) {
122 if err := args.Validate(); err != nil {
123 return nil, errors.Trace(err)
124 }
125 params := NewURLParams()
126 params.Values.Add("name", args.Name)
127 params.Values.Add("mac_address", args.MACAddress)
128 params.Values.Add("vlan", fmt.Sprint(args.VLAN.ID()))
129 params.MaybeAdd("tags", strings.Join(args.Tags, ","))
130 params.MaybeAddInt("mtu", args.MTU)
131 params.MaybeAddBool("accept_ra", args.AcceptRA)
132 params.MaybeAddBool("autoconf", args.Autoconf)
133 result, err := d.controller.post(d.interfacesURI(), "create_physical", params.Values)
134 if err != nil {
135 if svrErr, ok := errors.Cause(err).(ServerError); ok {
136 switch svrErr.StatusCode {
137 case http.StatusNotFound, http.StatusConflict:
138 return nil, errors.Wrap(err, NewBadRequestError(svrErr.BodyMessage))
139 case http.StatusForbidden:
140 return nil, errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
141 case http.StatusServiceUnavailable:
142 return nil, errors.Wrap(err, NewCannotCompleteError(svrErr.BodyMessage))
143 }
144 }
145 return nil, NewUnexpectedError(err)
146 }
147
148 iface, err := readInterface(d.controller.apiVersion, result)
149 if err != nil {
150 return nil, errors.Trace(err)
151 }
152 iface.controller = d.controller
153
154 // TODO: add to the interfaces for the device when the interfaces are returned.
155 // lp:bug 1567213.
156 return iface, nil
157}
158
159// Delete implements Device.
160func (d *device) Delete() error {
161 err := d.controller.delete(d.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
176func readDevice(controllerVersion version.Number, source interface{}) (*device, error) {
177 readFunc, err := getDeviceDeserializationFunc(controllerVersion)
178 if err != nil {
179 return nil, errors.Trace(err)
180 }
181
182 checker := schema.StringMap(schema.Any())
183 coerced, err := checker.Coerce(source, nil)
184 if err != nil {
185 return nil, WrapWithDeserializationError(err, "device base schema check failed")
186 }
187 valid := coerced.(map[string]interface{})
188 return readFunc(valid)
189}
190
191func readDevices(controllerVersion version.Number, source interface{}) ([]*device, error) {
192 readFunc, err := getDeviceDeserializationFunc(controllerVersion)
193 if err != nil {
194 return nil, errors.Trace(err)
195 }
196
197 checker := schema.List(schema.StringMap(schema.Any()))
198 coerced, err := checker.Coerce(source, nil)
199 if err != nil {
200 return nil, WrapWithDeserializationError(err, "device base schema check failed")
201 }
202 valid := coerced.([]interface{})
203 return readDeviceList(valid, readFunc)
204}
205
206func getDeviceDeserializationFunc(controllerVersion version.Number) (deviceDeserializationFunc, error) {
207 var deserialisationVersion version.Number
208 for v := range deviceDeserializationFuncs {
209 if v.Compare(deserialisationVersion) > 0 && v.Compare(controllerVersion) <= 0 {
210 deserialisationVersion = v
211 }
212 }
213 if deserialisationVersion == version.Zero {
214 return nil, NewUnsupportedVersionError("no device read func for version %s", controllerVersion)
215 }
216 return deviceDeserializationFuncs[deserialisationVersion], nil
217}
218
219// readDeviceList expects the values of the sourceList to be string maps.
220func readDeviceList(sourceList []interface{}, readFunc deviceDeserializationFunc) ([]*device, error) {
221 result := make([]*device, 0, len(sourceList))
222 for i, value := range sourceList {
223 source, ok := value.(map[string]interface{})
224 if !ok {
225 return nil, NewDeserializationError("unexpected value for device %d, %T", i, value)
226 }
227 device, err := readFunc(source)
228 if err != nil {
229 return nil, errors.Annotatef(err, "device %d", i)
230 }
231 result = append(result, device)
232 }
233 return result, nil
234}
235
236type deviceDeserializationFunc func(map[string]interface{}) (*device, error)
237
238var deviceDeserializationFuncs = map[version.Number]deviceDeserializationFunc{
239 twoDotOh: device_2_0,
240}
241
242func device_2_0(source map[string]interface{}) (*device, error) {
243 fields := schema.Fields{
244 "resource_uri": schema.String(),
245
246 "system_id": schema.String(),
247 "hostname": schema.String(),
248 "fqdn": schema.String(),
249 "parent": schema.OneOf(schema.Nil(""), schema.String()),
250 "owner": schema.OneOf(schema.Nil(""), schema.String()),
251
252 "ip_addresses": schema.List(schema.String()),
253 "interface_set": schema.List(schema.StringMap(schema.Any())),
254 "zone": schema.StringMap(schema.Any()),
255 }
256 defaults := schema.Defaults{
257 "owner": "",
258 "parent": "",
259 }
260 checker := schema.FieldMap(fields, defaults)
261 coerced, err := checker.Coerce(source, nil)
262 if err != nil {
263 return nil, WrapWithDeserializationError(err, "device 2.0 schema check failed")
264 }
265 valid := coerced.(map[string]interface{})
266 // From here we know that the map returned from the schema coercion
267 // contains fields of the right type.
268
269 interfaceSet, err := readInterfaceList(valid["interface_set"].([]interface{}), interface_2_0)
270 if err != nil {
271 return nil, errors.Trace(err)
272 }
273 zone, err := zone_2_0(valid["zone"].(map[string]interface{}))
274 if err != nil {
275 return nil, errors.Trace(err)
276 }
277 owner, _ := valid["owner"].(string)
278 parent, _ := valid["parent"].(string)
279 result := &device{
280 resourceURI: valid["resource_uri"].(string),
281
282 systemID: valid["system_id"].(string),
283 hostname: valid["hostname"].(string),
284 fqdn: valid["fqdn"].(string),
285 parent: parent,
286 owner: owner,
287
288 ipAddresses: convertToStringSlice(valid["ip_addresses"]),
289 interfaceSet: interfaceSet,
290 zone: zone,
291 }
292 return result, nil
293}