CORD-270 CORD-444 added REST API to get list of switches and addded switches to MAAS
Change-Id: I0f1778b835fed947e19ace4ecff4900d72b405b6
diff --git a/switchq/sync.go b/switchq/sync.go
new file mode 100644
index 0000000..64b0d43
--- /dev/null
+++ b/switchq/sync.go
@@ -0,0 +1,292 @@
+// Copyright 2016 Open Networking Laboratory
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package main
+
+import (
+ "fmt"
+ maas "github.com/juju/gomaasapi"
+ "net"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+type MatchRec struct {
+ AddressRec
+ ResourceUri string
+}
+
+type subnetRec struct {
+ name string
+ cidr *net.IPNet
+ vlanID string
+}
+
+func getSubnetForAddr(subnets []subnetRec, ip string) *subnetRec {
+ asIp := net.ParseIP(ip)
+ for _, rec := range subnets {
+ if rec.cidr.Contains(asIp) {
+ return &rec
+ }
+ }
+ return nil
+}
+
+// process converts the device list from MAAS into a maps to quickly lookup a record by name
+func process(deviceList []maas.JSONObject) (byName map[string]*MatchRec, byMac map[string]*MatchRec, err error) {
+ byName = make(map[string]*MatchRec)
+ byMac = make(map[string]*MatchRec)
+ all := make([]MatchRec, len(deviceList))
+
+ for i, deviceObj := range deviceList {
+ device, err := deviceObj.GetMap()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ uri, err := device["resource_uri"].GetString()
+ if err != nil {
+ return nil, nil, err
+ }
+ all[i].ResourceUri = uri
+
+ name, err := device["hostname"].GetString()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Strip the domain from the hostname
+ idx := strings.Index(name, ".")
+ if idx != -1 {
+ name = name[:idx]
+ }
+ all[i].Name = name
+
+ mac_set_arr, err := device["macaddress_set"].GetArray()
+ if len(mac_set_arr) != 1 {
+ return nil, nil, fmt.Errorf("Expecting a single MAC address, recived %d", len(mac_set_arr))
+ }
+
+ mac_obj, err := mac_set_arr[0].GetMap()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ mac, err := mac_obj["mac_address"].GetString()
+ if err != nil {
+ return nil, nil, err
+ }
+ mac = strings.ToUpper(mac)
+ all[i].MAC = mac
+
+ byName[name] = &all[i]
+ byMac[mac] = &all[i]
+ }
+
+ return
+}
+
+// synctoMaas checks to see if the devices is already in MAAS and if not adds it containment is determined by a matching
+// hostname and MAC address. if there is not match then a new devie is POSTed to MAAS
+func (c *AppContext) syncToMaas(request chan []AddressRec) {
+ log.Info("Starting MAAS Switch Synchronizer")
+
+ // Wait for request
+ for list := range request {
+ // Get current device list and convert it to some maps for quick indexing
+ devices := c.maasClient.GetSubObject("devices")
+ deviceObjects, err := devices.CallGet("list", url.Values{})
+ if err != nil {
+ log.Errorf("Unable to synchronize switches to MAAS, unable to get current devices : %s",
+ err)
+ break
+ }
+ deviceList, err := deviceObjects.GetArray()
+ if err != nil {
+ log.Errorf("Unable to synchronize switches to MAAS, unable to deserialize devices : %s",
+ err)
+ break
+ }
+ byName, byMac, err := process(deviceList)
+ if err != nil {
+ log.Errorf("Unable to process current device list : %s", err)
+ return
+ }
+
+ // Get all the subnets from MAAS and store them in a local array for quick access. The subnets
+ // are used to attempt to map the switch into a subnet based on its IP address
+ subnets := c.maasClient.GetSubObject("subnets")
+ subnetObjects, err := subnets.CallGet("", url.Values{})
+ if err != nil {
+ log.Errorf("Unable to retrieve subnets from MAAS : %s", err)
+ return
+ }
+
+ subnetArr, err := subnetObjects.GetArray()
+ if err != nil {
+ log.Errorf("Unable to get subnet array from MAAS : %s", err)
+ return
+ }
+
+ subnetRecs := make([]subnetRec, len(subnetArr))
+ for i, subnetObj := range subnetArr {
+ subnet, err := subnetObj.GetMap()
+ if err != nil {
+ log.Errorf("Unable to process subnet from MAAS : %s", err)
+ return
+ }
+
+ name, err := subnet["name"].GetString()
+ if err != nil {
+ log.Errorf("Unable to get Name from MAAS subnet : %s", err)
+ return
+ }
+
+ s_cidr, err := subnet["cidr"].GetString()
+ if err != nil {
+ log.Errorf("Unable to get CIDR from MAAS subnet : %s", err)
+ return
+ }
+ _, cidr, err := net.ParseCIDR(s_cidr)
+ if err != nil {
+ log.Errorf("Unable to parse CIDR '%s' from MAAS : %s", s_cidr, err)
+ return
+ }
+
+ vlanMap, err := subnet["vlan"].GetMap()
+ if err != nil {
+ log.Errorf("Unable to get vlan for MAAS subnet '%s' : %s", s_cidr, err)
+ return
+ }
+
+ id, err := vlanMap["id"].GetFloat64()
+ if err != nil {
+ log.Errorf("Unable to get VLAN ID for MAAS subnet '%s' : %s", s_cidr, err)
+ return
+ }
+ subnetRecs[i].name = name
+ subnetRecs[i].cidr = cidr
+ subnetRecs[i].vlanID = strconv.Itoa(int(id))
+ }
+
+ // Iterage over the list of devices to sync to MAAS
+ for _, rec := range list {
+ // First check for matching hostname
+ found, ok := byName[rec.Name]
+ if ok {
+ // Found an existing record with a matching hostname. If the MAC matches then
+ // this means this device is already in MAAS and we are good.
+ if strings.ToUpper(rec.MAC) == found.MAC {
+ // All is good
+ log.Infof("Device '%s (%s)' already in MAAS", rec.Name, rec.MAC)
+ continue
+ } else {
+ // Have a matching hostname, but a different MAC address. Can't
+ // push a duplicate hostname to MAAS. As the MAC is considered the
+ // unique identifier we will append the MAC address to the given
+ // hostname and add the device under that host name
+ log.Warnf("Device '%s (%s)' exists in MAAS with a different MAC, augmenting hostname with MAC to form unique hostname",
+ rec.Name, rec.MAC)
+ namePlus := rec.Name + "-" + strings.Replace(strings.ToLower(rec.MAC), ":", "", -1)
+ _, ok = byName[namePlus]
+ if ok {
+ // A record with the name + mac already exists, assume this is the
+ // same record then and all is well
+ log.Infof("Device '%s (%s)' already in MAAS", namePlus, rec.MAC)
+ continue
+ }
+
+ // Modify the record so that it will be created with the new name
+ rec.Name = namePlus
+ }
+ }
+ found, ok = byMac[strings.ToUpper(rec.MAC)]
+ if ok {
+ // Found a record with this MAC address, but a different hostname. Update
+ // the hostname to the correct value
+ log.Infof("Device with matching MAC, but different name found, updating name to '%s (%s)'",
+ rec.Name, rec.MAC)
+ deviceObj := c.maasClient.GetSubObject(found.ResourceUri)
+ _, err := deviceObj.Update(url.Values{
+ "hostname": []string{rec.Name},
+ })
+ if err != nil {
+ log.Errorf("Unable to update hostname for device '%s (%s)' in MAAS : %s",
+ rec.Name, rec.MAC, err)
+ }
+ continue
+ }
+
+ // The device does not currently exist in MAAS, so add it
+ log.Infof("Adding device '%s (%s)' to MAAS", rec.Name, rec.MAC)
+ deviceObj, err := devices.CallPost("new", url.Values{
+ "hostname": []string{rec.Name},
+ "mac_addresses": []string{rec.MAC},
+ })
+ if err != nil {
+ log.Errorf("Unable to synchronize switch '%s' (%s, %s) to MAAS : %s",
+ rec.Name, rec.IP, rec.MAC, err)
+ continue
+ }
+
+ // Get the interface of the device so if can be assigned to a subnet
+ deviceMap, err := deviceObj.GetMap()
+ if err != nil {
+ log.Errorf("Can't get device object for '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ continue
+ }
+
+ interfaceSetArr, err := deviceMap["interface_set"].GetArray()
+ if err != nil {
+ log.Errorf("Can't get device interface set for '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ continue
+ }
+
+ ifaceMap, err := interfaceSetArr[0].GetMap()
+ if err != nil {
+ log.Errorf("Unable to get first interface for '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ continue
+ }
+
+ ifaceUri, err := ifaceMap["resource_uri"].GetString()
+ if err != nil {
+ log.Errorf("Unable to get interface URI for '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ continue
+ }
+
+ // Get the appropriate subnect for the switches IP. If one cannot be found then
+ // nothing can be done
+ subnetRec := getSubnetForAddr(subnetRecs, rec.IP)
+ if subnetRec == nil {
+ log.Errorf("Unable to find VLAN ID for '%s (%s)' using IP '%s'",
+ rec.Name, rec.MAC, rec.IP)
+ continue
+ }
+
+ // If we have a subnet for the device set it back to MAAS
+ _, err = c.maasClient.GetSubObject(ifaceUri).Update(url.Values{
+ "name": []string{"ma1"},
+ "vlan": []string{subnetRec.vlanID},
+ })
+ if err != nil {
+ log.Errorf("Unable to update interface name of '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ }
+ }
+ }
+}