update switchq to calling provisioner, fixed a few bugs found while testing at on.labs
Change-Id: I2367669aa54f680b98ff0cbbc8d41a49fb7e7a79
diff --git a/switchq/switchq.go b/switchq/switchq.go
index 922f1ba..ca10f8f 100644
--- a/switchq/switchq.go
+++ b/switchq/switchq.go
@@ -1,18 +1,27 @@
package main
import (
+ "bytes"
+ "encoding/json"
"fmt"
"github.com/kelseyhightower/envconfig"
"log"
+ "net/http"
"time"
)
type Config struct {
- StorageURL string `default:"memory:///switchq/vendors.json" envconfig:"storage_url"`
- AddressURL string `default:"file:///switchq/dhcp_harvest.inc" envconfig:"address_url"`
- PollInterval string `default:"1m" envconfig:"poll_interval"`
- ProvisionTTL string `default:"1h" envconfig:"check_ttl"`
+ VendorsURL string `default:"file:///switchq/vendors.json" envconfig:"vendors_url"`
+ StorageURL string `default:"memory:" envconfig:"storage_url"`
+ AddressURL string `default:"file:///switchq/dhcp_harvest.inc" envconfig:"address_url"`
+ PollInterval string `default:"1m" envconfig:"poll_interval"`
+ ProvisionTTL string `default:"1h" envconfig:"provision_ttl"`
+ ProvisionURL string `default:"" envconfig:"provision_url"`
+ RoleSelectorURL string `default:"" envconfig:"role_selector_url"`
+ DefaultRole string `default:"fabric-switch" envconfig:"default_role"`
+ Script string `default:"do-ansible"`
+ vendors Vendors
storage Storage
addressSource AddressSource
interval time.Duration
@@ -25,13 +34,98 @@
}
}
-func (c *Config) processRecord(rec AddressRec) error {
- if c.ttl == 0 {
- // One provisioning only please
- return nil
+func (c *Config) provision(rec AddressRec) error {
+ log.Printf("[debug] Verifing that device '%s (%s)' isn't already in a provisioning state",
+ rec.Name, rec.MAC)
+ resp, err := http.Get(c.ProvisionURL + rec.MAC)
+ log.Printf("%s%s", c.ProvisionURL, rec.MAC)
+ if err != nil {
+ log.Printf("[error] Error while retrieving provisioning state for device '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ return err
+ }
+ if resp.StatusCode != 404 && int(resp.StatusCode/100) != 2 {
+ log.Printf("[error] Error while retrieving provisioning state for device '%s (%s)' : %s",
+ rec.Name, rec.MAC, resp.Status)
+ return fmt.Errorf(resp.Status)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 404 {
+ decoder := json.NewDecoder(resp.Body)
+ var raw interface{}
+ err = decoder.Decode(&raw)
+ if err != nil {
+ log.Printf("[error] Unable to unmarshal status response from provisioning service for device '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ return err
+ }
+ status := raw.(map[string]interface{})
+ switch int(status["status"].(float64)) {
+ case 0, 1: // "PENDING", "RUNNING"
+ log.Printf("[info] Device '%s (%s)' is already scheduled to be provisioned",
+ rec.Name, rec.MAC)
+ return nil
+ case 2: // "COMPLETE"
+ // noop
+ case 3: // "FAILED"
+ c.storage.ClearProvisioned(rec.MAC)
+ default:
+ err = fmt.Errorf("unknown provisioning status : %d", status["status"])
+ log.Printf("[error] received unknown provisioning status for device '%s (%s)' : %s",
+ rec.Name, rec.MAC, err)
+ return err
+ }
+ }
+ log.Printf("[info] POSTing to '%s' for provisioning of '%s (%s)'", c.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.DefaultRole != "" {
+ data["role"] = c.DefaultRole
+ }
+ if c.Script != "" {
+ data["script"] = c.Script
}
- ok, err := c.storage.Switchq(rec.MAC)
+ hc := http.Client{}
+ var b []byte
+ b, err = json.Marshal(data)
+ if err != nil {
+ log.Printf("[error] Unable to marshal provisioning data : %s", err)
+ return err
+ }
+ req, err := http.NewRequest("POST", c.ProvisionURL, bytes.NewReader(b))
+ if err != nil {
+ log.Printf("[error] Unable to construct POST request to provisioner : %s", err)
+ return err
+ }
+
+ req.Header.Add("Content-Type", "application/json")
+ resp, err = hc.Do(req)
+ if err != nil {
+ log.Printf("[error] Unable to POST request to provisioner : %s", err)
+ return err
+ }
+
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusAccepted {
+ log.Printf("[error] Provisioning request not accepted by provisioner : %s", resp.Status)
+ return err
+ }
+
+ now := time.Now()
+ c.storage.MarkProvisioned(rec.MAC, &now)
+ return nil
+}
+
+func (c *Config) processRecord(rec AddressRec) error {
+ ok, err := c.vendors.Switchq(rec.MAC)
if err != nil {
return fmt.Errorf("unable to determine ventor of MAC '%s' (%s)", rec.MAC, err)
}
@@ -47,8 +141,16 @@
if err != nil {
return err
}
- if last == nil || time.Since(*last) > c.ttl {
- log.Printf("[debug] time to provision %s", rec.MAC)
+
+ // If TTL is 0 then we will only provision a switch once.
+ if last == nil || (c.ttl > 0 && time.Since(*last) > c.ttl) {
+ c.provision(rec)
+ } else if c.ttl == 0 {
+ log.Printf("[debug] device '%s' (%s, %s) has completed its one time provisioning, with a TTL set to %s",
+ rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
+ } else {
+ log.Printf("[debug] device '%s' (%s, %s) has completed provisioning within the specified TTL of %s",
+ rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
}
return nil
}
@@ -59,6 +161,9 @@
config := Config{}
envconfig.Process("SWITCHQ", &config)
+ config.vendors, err = NewVendors(config.VendorsURL)
+ checkError(err, "Unable to create known vendors list from specified URL '%s' : %s", config.VendorsURL, err)
+
config.storage, err = NewStorage(config.StorageURL)
checkError(err, "Unable to create require storage for specified URL '%s' : %s", config.StorageURL, err)
@@ -72,11 +177,17 @@
checkError(err, "Unable to parse specified provision TTL value of '%s' : %s", config.ProvisionTTL, err)
log.Printf(`Configuration:
- Storage URL: %s
- Poll Interval: %s
- Address Source: %s
- Provision TTL: %s`,
- config.StorageURL, config.PollInterval, config.AddressURL, config.ProvisionTTL)
+ Vendors URL: %s
+ Storage URL: %s
+ Poll Interval: %s
+ Address Source: %s
+ Provision TTL: %s
+ Provision URL: %s
+ Role Selector URL: %s
+ Default Role: %s
+ Script: %s`,
+ config.VendorsURL, config.StorageURL, config.PollInterval, config.AddressURL, config.ProvisionTTL,
+ config.ProvisionURL, config.RoleSelectorURL, config.DefaultRole, config.Script)
// 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.