CORD-270 CORD-444 added REST API to get list of switches and addded switches to MAAS
Change-Id: I0f1778b835fed947e19ace4ecff4900d72b405b6
diff --git a/switchq/switchq.go b/switchq/switchq.go
index d637b74..deddf74 100644
--- a/switchq/switchq.go
+++ b/switchq/switchq.go
@@ -18,8 +18,11 @@
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
+ "github.com/gorilla/mux"
+ maas "github.com/juju/gomaasapi"
"github.com/kelseyhightower/envconfig"
"net/http"
+ "sync"
"time"
)
@@ -34,6 +37,10 @@
Script string `default:"do-ansible"`
LogLevel string `default:"warning" envconfig:"LOG_LEVEL"`
LogFormat string `default:"text" envconfig:"LOG_FORMAT"`
+ Listen string `default:""`
+ Port int `default:"4244"`
+ MaasURL string `default:"http://localhost/MAAS" envconfig:"MAAS_URL"`
+ MaasKey string `default:"" envconfig:"MAAS_API_KEY"`
vendors Vendors
addressSource AddressSource
@@ -74,16 +81,31 @@
Timestamp int64 `json:"timestamp"`
}
+type AppContext struct {
+ config Config
+
+ maasClient *maas.MAASObject
+ pushChan chan []AddressRec
+ mutex sync.RWMutex
+ nextList []AddressRec
+ publishList []AddressRec
+}
+
func checkError(err error, msg string, args ...interface{}) {
if err != nil {
log.Fatalf(msg, args...)
}
}
-func (c *Config) getProvisionedState(rec AddressRec) (*StatusMsg, error) {
+func (c *AppContext) getProvisionedState(rec AddressRec) (*StatusMsg, error) {
+ if len(c.config.ProvisionURL) == 0 {
+ log.Warnf("Unable to fetch provisioning state of device '%s' (%s, %s) as no URL for the provisioner was specified",
+ rec.Name, rec.IP, rec.MAC)
+ return nil, fmt.Errorf("No URL for provisioner specified")
+ }
log.Debugf("Fetching provisioned state of device '%s' (%s, %s)",
rec.Name, rec.IP, rec.MAC)
- resp, err := http.Get(c.ProvisionURL + rec.MAC)
+ resp, err := http.Get(c.config.ProvisionURL + rec.MAC)
if err != nil {
log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
rec.Name, rec.IP, rec.MAC, err)
@@ -112,22 +134,27 @@
return nil, nil
}
-func (c *Config) provision(rec AddressRec) error {
- log.Infof("POSTing to '%s' for provisioning of '%s (%s)'", c.ProvisionURL, rec.Name, rec.MAC)
+func (c *AppContext) provision(rec AddressRec) error {
+ if len(c.config.ProvisionURL) == 0 {
+ log.Warnf("Unable to POST to provisioner for device '%s' (%s, %s) as no URL for the provisioner was specified",
+ rec.Name, rec.IP, rec.MAC)
+ return fmt.Errorf("No URL for provisioner specified")
+ }
+ log.Infof("POSTing to '%s' for provisioning of '%s (%s)'", c.config.ProvisionURL, rec.Name, rec.MAC)
data := map[string]string{
"id": rec.MAC,
"name": rec.Name,
"ip": rec.IP,
"mac": rec.MAC,
}
- if c.RoleSelectorURL != "" {
- data["role_selector"] = c.RoleSelectorURL
+ if c.config.RoleSelectorURL != "" {
+ data["role_selector"] = c.config.RoleSelectorURL
}
- if c.DefaultRole != "" {
- data["role"] = c.DefaultRole
+ if c.config.DefaultRole != "" {
+ data["role"] = c.config.DefaultRole
}
- if c.Script != "" {
- data["script"] = c.Script
+ if c.config.Script != "" {
+ data["script"] = c.config.Script
}
hc := http.Client{}
@@ -137,7 +164,7 @@
log.Errorf("Unable to marshal provisioning data : %s", err)
return err
}
- req, err := http.NewRequest("POST", c.ProvisionURL, bytes.NewReader(b))
+ req, err := http.NewRequest("POST", c.config.ProvisionURL, bytes.NewReader(b))
if err != nil {
log.Errorf("Unable to construct POST request to provisioner : %s", err)
return err
@@ -159,8 +186,8 @@
return nil
}
-func (c *Config) processRecord(rec AddressRec) error {
- ok, err := c.vendors.Switchq(rec.MAC)
+func (c *AppContext) processRecord(rec AddressRec) error {
+ ok, err := c.config.vendors.Switchq(rec.MAC)
if err != nil {
return fmt.Errorf("unable to determine ventor of MAC '%s' (%s)", rec.MAC, err)
}
@@ -172,6 +199,9 @@
return nil
}
+ // Add this IP information to our list of known switches
+ c.nextList = append(c.nextList, rec)
+
// Verify if the provision status of the node is complete, if in an error state then TTL means
// nothing
state, err := c.getProvisionedState(rec)
@@ -199,34 +229,64 @@
}
// If TTL is 0 then we will only provision a switch once.
- if state == nil || (c.ttl > 0 && time.Since(time.Unix(state.Timestamp, 0)) > c.ttl) {
+ if state == nil || (c.config.ttl > 0 && time.Since(time.Unix(state.Timestamp, 0)) > c.config.ttl) {
if state != nil {
log.Debugf("device '%s' (%s, %s) TTL expired, reprovisioning",
rec.Name, rec.IP, rec.MAC)
}
c.provision(rec)
- } else if c.ttl == 0 {
+ } else if c.config.ttl == 0 {
log.Debugf("device '%s' (%s, %s) has completed its one time provisioning, with a TTL set to %s",
- rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
+ rec.Name, rec.IP, rec.MAC, c.config.ProvisionTTL)
} else {
log.Debugf("device '%s' (%s, %s) has completed provisioning within the specified TTL of %s",
- rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
+ rec.Name, rec.IP, rec.MAC, c.config.ProvisionTTL)
}
return nil
}
+func (c *AppContext) processLoop() {
+ // We use two methods to attempt to find the MAC (hardware) address associated with an IP. The first
+ // is to look in the table. The second is to send an ARP packet.
+ for {
+ log.Infof("Checking for switches @ %s", time.Now())
+ addresses, err := c.config.addressSource.GetAddresses()
+
+ if err != nil {
+ log.Errorf("unable to read addresses from address source : %s", err)
+ } else {
+ log.Infof("Queried %d addresses from address source", len(addresses))
+
+ c.nextList = make([]AddressRec, 0, len(addresses))
+ for _, rec := range addresses {
+ log.Debugf("Processing %s(%s, %s)", rec.Name, rec.IP, rec.MAC)
+ if err := c.processRecord(rec); err != nil {
+ log.Errorf("Error when processing IP '%s' : %s", rec.IP, err)
+ }
+ }
+ c.mutex.Lock()
+ c.publishList = c.nextList
+ c.nextList = nil
+ c.mutex.Unlock()
+ c.pushChan <- c.publishList
+ }
+
+ time.Sleep(c.config.interval)
+ }
+}
+
var log = logrus.New()
func main() {
var err error
- config := Config{}
- err = envconfig.Process("SWITCHQ", &config)
+ context := &AppContext{}
+ err = envconfig.Process("SWITCHQ", &context.config)
if err != nil {
log.Fatalf("Unable to parse configuration options : %s", err)
}
- switch config.LogFormat {
+ switch context.config.LogFormat {
case "json":
log.Formatter = &logrus.JSONFormatter{}
default:
@@ -236,24 +296,12 @@
}
}
- level, err := logrus.ParseLevel(config.LogLevel)
+ level, err := logrus.ParseLevel(context.config.LogLevel)
if err != nil {
level = logrus.WarnLevel
}
log.Level = level
- config.vendors, err = NewVendors(config.VendorsURL)
- checkError(err, "Unable to create known vendors list from specified URL '%s' : %s", config.VendorsURL, err)
-
- config.addressSource, err = NewAddressSource(config.AddressURL)
- checkError(err, "Unable to create required address source for specified URL '%s' : %s", config.AddressURL, err)
-
- config.interval, err = time.ParseDuration(config.PollInterval)
- checkError(err, "Unable to parse specified poll interface '%s' : %s", config.PollInterval, err)
-
- config.ttl, err = time.ParseDuration(config.ProvisionTTL)
- checkError(err, "Unable to parse specified provision TTL value of '%s' : %s", config.ProvisionTTL, err)
-
log.Infof(`Configuration:
Vendors URL: %s
Poll Interval: %s
@@ -263,31 +311,50 @@
Role Selector URL: %s
Default Role: %s
Script: %s
+ API Listen IP: %s
+ API Listen Port: %d
+ MAAS URL: %s
+ MAAS APIKEY: %s
Log Level: %s
Log Format: %s`,
- config.VendorsURL, config.PollInterval, config.AddressURL, config.ProvisionTTL,
- config.ProvisionURL, config.RoleSelectorURL, config.DefaultRole, config.Script,
- config.LogLevel, config.LogFormat)
+ context.config.VendorsURL, context.config.PollInterval, context.config.AddressURL, context.config.ProvisionTTL,
+ context.config.ProvisionURL, context.config.RoleSelectorURL, context.config.DefaultRole, context.config.Script,
+ context.config.Listen, context.config.Port, context.config.MaasURL, context.config.MaasKey,
+ context.config.LogLevel, context.config.LogFormat)
- // We use two methods to attempt to find the MAC (hardware) address associated with an IP. The first
- // is to look in the table. The second is to send an ARP packet.
- for {
- log.Infof("Checking for switches @ %s", time.Now())
- addresses, err := config.addressSource.GetAddresses()
+ context.config.vendors, err = NewVendors(context.config.VendorsURL)
+ checkError(err, "Unable to create known vendors list from specified URL '%s' : %s", context.config.VendorsURL, err)
- if err != nil {
- log.Errorf("unable to read addresses from address source : %s", err)
- } else {
- log.Infof("Queried %d addresses from address source", len(addresses))
+ context.config.addressSource, err = NewAddressSource(context.config.AddressURL)
+ checkError(err, "Unable to create required address source for specified URL '%s' : %s", context.config.AddressURL, err)
- for _, rec := range addresses {
- log.Debugf("Processing %s(%s, %s)", rec.Name, rec.IP, rec.MAC)
- if err := config.processRecord(rec); err != nil {
- log.Errorf("Error when processing IP '%s' : %s", rec.IP, err)
- }
- }
- }
+ context.config.interval, err = time.ParseDuration(context.config.PollInterval)
+ checkError(err, "Unable to parse specified poll interface '%s' : %s", context.config.PollInterval, err)
- time.Sleep(config.interval)
+ context.config.ttl, err = time.ParseDuration(context.config.ProvisionTTL)
+ checkError(err, "Unable to parse specified provision TTL value of '%s' : %s", context.config.ProvisionTTL, err)
+
+ if len(context.config.MaasURL) > 0 {
+
+ // Attempt to connect to MAAS
+ authClient, err := maas.NewAuthenticatedClient(context.config.MaasURL, context.config.MaasKey, "1.0")
+ checkError(err, "Unable to connect to MAAS at '%s' : %s", context.config.MaasURL, err)
+
+ context.maasClient = maas.NewMAAS(*authClient)
+ }
+
+ context.pushChan = make(chan []AddressRec, 1)
+
+ go context.processLoop()
+ go context.syncToMaas(context.pushChan)
+
+ router := mux.NewRouter()
+ router.HandleFunc("/switch/", context.ListSwitchesHandler).Methods("GET")
+ http.Handle("/", router)
+ log.Infof("Listening for HTTP request on '%s:%d'", context.config.Listen, context.config.Port)
+ err = http.ListenAndServe(fmt.Sprintf("%s:%d", context.config.Listen, context.config.Port), nil)
+ if err != nil {
+ checkError(err, "Error while attempting to listen to REST requests on '%s:%d' : %s",
+ context.config.Listen, context.config.Port, err)
}
}