~juju-qa/ubuntu/xenial/juju/2.0-rc2

« back to all changes in this revision

Viewing changes to src/github.com/juju/gomaasapi/machine.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
package gomaasapi
5
5
 
6
6
import (
 
7
        "fmt"
7
8
        "net/http"
 
9
        "net/url"
8
10
 
9
11
        "github.com/juju/errors"
10
12
        "github.com/juju/schema"
16
18
 
17
19
        resourceURI string
18
20
 
19
 
        systemID string
20
 
        hostname string
21
 
        fqdn     string
22
 
        tags     []string
 
21
        systemID  string
 
22
        hostname  string
 
23
        fqdn      string
 
24
        tags      []string
 
25
        ownerData map[string]string
23
26
 
24
27
        operatingSystem string
25
28
        distroSeries    string
57
60
        m.statusName = other.statusName
58
61
        m.statusMessage = other.statusMessage
59
62
        m.zone = other.zone
 
63
        m.tags = other.tags
 
64
        m.ownerData = other.ownerData
60
65
}
61
66
 
62
67
// SystemID implements Machine.
248
253
}
249
254
 
250
255
// CreateMachineDeviceArgs is an argument structure for Machine.CreateDevice.
251
 
// All fields except Hostname are required.
 
256
// Only InterfaceName and MACAddress fields are required, the others are only
 
257
// used if set. If Subnet and VLAN are both set, Subnet.VLAN() must match the
 
258
// given VLAN. On failure, returns an error satisfying errors.IsNotValid().
252
259
type CreateMachineDeviceArgs struct {
253
260
        Hostname      string
254
261
        InterfaceName string
255
262
        MACAddress    string
256
263
        Subnet        Subnet
 
264
        VLAN          VLAN
257
265
}
258
266
 
259
267
// Validate ensures that all required values are non-emtpy.
261
269
        if a.InterfaceName == "" {
262
270
                return errors.NotValidf("missing InterfaceName")
263
271
        }
 
272
 
264
273
        if a.MACAddress == "" {
265
274
                return errors.NotValidf("missing MACAddress")
266
275
        }
267
 
        if a.Subnet == nil {
268
 
                return errors.NotValidf("missing Subnet")
 
276
 
 
277
        if a.Subnet != nil && a.VLAN != nil && a.Subnet.VLAN() != a.VLAN {
 
278
                msg := fmt.Sprintf(
 
279
                        "given subnet %q on VLAN %d does not match given VLAN %d",
 
280
                        a.Subnet.CIDR(), a.Subnet.VLAN().ID(), a.VLAN.ID(),
 
281
                )
 
282
                return errors.NewNotValid(nil, msg)
269
283
        }
 
284
 
270
285
        return nil
271
286
}
272
287
 
284
299
                return nil, errors.Trace(err)
285
300
        }
286
301
 
287
 
        defer func() {
 
302
        defer func(err *error) {
288
303
                // If there is an error return, at least try to delete the device we just created.
289
 
                if err != nil {
 
304
                if *err != nil {
290
305
                        if innerErr := device.Delete(); innerErr != nil {
291
306
                                logger.Warningf("could not delete device %q", device.SystemID())
292
307
                        }
293
308
                }
294
 
        }()
295
 
 
296
 
        // There should be one interface created for each MAC Address, and since
297
 
        // we only specified one, there should just be one response.
 
309
        }(&err)
 
310
 
 
311
        // Update the VLAN to use for the interface, if given.
 
312
        vlanToUse := args.VLAN
 
313
        if vlanToUse == nil && args.Subnet != nil {
 
314
                vlanToUse = args.Subnet.VLAN()
 
315
        }
 
316
 
 
317
        // There should be one interface created for each MAC Address, and since we
 
318
        // only specified one, there should just be one response.
298
319
        interfaces := device.InterfaceSet()
299
320
        if count := len(interfaces); count != 1 {
300
321
                err := errors.Errorf("unexpected interface count for device: %d", count)
301
322
                return nil, NewUnexpectedError(err)
302
323
        }
303
324
        iface := interfaces[0]
304
 
 
305
 
        // Now update the name and vlan of interface that was created…
 
325
        nameToUse := args.InterfaceName
 
326
 
 
327
        if err := m.updateDeviceInterface(iface, nameToUse, vlanToUse); err != nil {
 
328
                return nil, errors.Trace(err)
 
329
        }
 
330
 
 
331
        if args.Subnet == nil {
 
332
                // Nothing further to update.
 
333
                return device, nil
 
334
        }
 
335
 
 
336
        if err := m.linkDeviceInterfaceToSubnet(iface, args.Subnet); err != nil {
 
337
                return nil, errors.Trace(err)
 
338
        }
 
339
 
 
340
        return device, nil
 
341
}
 
342
 
 
343
func (m *machine) updateDeviceInterface(iface Interface, nameToUse string, vlanToUse VLAN) error {
306
344
        updateArgs := UpdateInterfaceArgs{}
307
 
        if iface.Name() != args.InterfaceName {
308
 
                updateArgs.Name = args.InterfaceName
309
 
        }
310
 
        if iface.VLAN().ID() != args.Subnet.VLAN().ID() {
311
 
                updateArgs.VLAN = args.Subnet.VLAN()
312
 
        }
313
 
        err = iface.Update(updateArgs)
314
 
        if err != nil {
315
 
                return nil, errors.Trace(err)
316
 
        }
317
 
 
318
 
        err = iface.LinkSubnet(LinkSubnetArgs{
 
345
        updateArgs.Name = nameToUse
 
346
 
 
347
        if vlanToUse != nil {
 
348
                updateArgs.VLAN = vlanToUse
 
349
        }
 
350
 
 
351
        if err := iface.Update(updateArgs); err != nil {
 
352
                return errors.Annotatef(err, "updating device interface %q failed", iface.Name())
 
353
        }
 
354
 
 
355
        return nil
 
356
}
 
357
 
 
358
func (m *machine) linkDeviceInterfaceToSubnet(iface Interface, subnetToUse Subnet) error {
 
359
        err := iface.LinkSubnet(LinkSubnetArgs{
319
360
                Mode:   LinkModeStatic,
320
 
                Subnet: args.Subnet,
 
361
                Subnet: subnetToUse,
321
362
        })
322
363
        if err != nil {
323
 
                return nil, errors.Trace(err)
324
 
        }
325
 
 
326
 
        return device, nil
 
364
                return errors.Annotatef(
 
365
                        err, "linking device interface %q to subnet %q failed",
 
366
                        iface.Name(), subnetToUse.CIDR())
 
367
        }
 
368
 
 
369
        return nil
 
370
}
 
371
 
 
372
// OwnerData implements OwnerDataHolder.
 
373
func (m *machine) OwnerData() map[string]string {
 
374
        result := make(map[string]string)
 
375
        for key, value := range m.ownerData {
 
376
                result[key] = value
 
377
        }
 
378
        return result
 
379
}
 
380
 
 
381
// SetOwnerData implements OwnerDataHolder.
 
382
func (m *machine) SetOwnerData(ownerData map[string]string) error {
 
383
        params := make(url.Values)
 
384
        for key, value := range ownerData {
 
385
                params.Add(key, value)
 
386
        }
 
387
        result, err := m.controller.post(m.resourceURI, "set_owner_data", params)
 
388
        if err != nil {
 
389
                return errors.Trace(err)
 
390
        }
 
391
        machine, err := readMachine(m.controller.apiVersion, result)
 
392
        if err != nil {
 
393
                return errors.Trace(err)
 
394
        }
 
395
        m.updateFrom(machine)
 
396
        return nil
327
397
}
328
398
 
329
399
func readMachine(controllerVersion version.Number, source interface{}) (*machine, error) {
395
465
        fields := schema.Fields{
396
466
                "resource_uri": schema.String(),
397
467
 
398
 
                "system_id": schema.String(),
399
 
                "hostname":  schema.String(),
400
 
                "fqdn":      schema.String(),
401
 
                "tag_names": schema.List(schema.String()),
 
468
                "system_id":  schema.String(),
 
469
                "hostname":   schema.String(),
 
470
                "fqdn":       schema.String(),
 
471
                "tag_names":  schema.List(schema.String()),
 
472
                "owner_data": schema.StringMap(schema.String()),
402
473
 
403
474
                "osystem":       schema.String(),
404
475
                "distro_series": schema.String(),
459
530
        result := &machine{
460
531
                resourceURI: valid["resource_uri"].(string),
461
532
 
462
 
                systemID: valid["system_id"].(string),
463
 
                hostname: valid["hostname"].(string),
464
 
                fqdn:     valid["fqdn"].(string),
465
 
                tags:     convertToStringSlice(valid["tag_names"]),
 
533
                systemID:  valid["system_id"].(string),
 
534
                hostname:  valid["hostname"].(string),
 
535
                fqdn:      valid["fqdn"].(string),
 
536
                tags:      convertToStringSlice(valid["tag_names"]),
 
537
                ownerData: convertToStringMap(valid["owner_data"]),
466
538
 
467
539
                operatingSystem: valid["osystem"].(string),
468
540
                distroSeries:    valid["distro_series"].(string),
496
568
        }
497
569
        return result
498
570
}
 
571
 
 
572
func convertToStringMap(field interface{}) map[string]string {
 
573
        if field == nil {
 
574
                return nil
 
575
        }
 
576
        // This function is only called after a schema Coerce, so it's
 
577
        // safe.
 
578
        fieldMap := field.(map[string]interface{})
 
579
        result := make(map[string]string)
 
580
        for key, value := range fieldMap {
 
581
                result[key] = value.(string)
 
582
        }
 
583
        return result
 
584
}