check point of code to identify and provision switches
Change-Id: I692d83127cbb718f03d359911689d0f688fa4a3d
diff --git a/switchq/Dockerfile b/switchq/Dockerfile
new file mode 100644
index 0000000..a0966e8
--- /dev/null
+++ b/switchq/Dockerfile
@@ -0,0 +1,50 @@
+FROM ubuntu:14.04
+
+# Base image information borrowed by official golang wheezy Dockerfile
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ g++ \
+ gcc \
+ libc6-dev \
+ make \
+ curl \
+ && rm -rf /var/lib/apt/lists/*
+
+ENV GOLANG_VERSION 1.6.2
+ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz
+ENV GOLANG_DOWNLOAD_SHA256 e40c36ae71756198478624ed1bb4ce17597b3c19d243f3f0899bb5740d56212a
+
+RUN curl -kfsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \
+ && echo "$GOLANG_DOWNLOAD_SHA256 golang.tar.gz" | sha256sum -c - \
+ && tar -C /usr/local -xzf golang.tar.gz \
+ && rm golang.tar.gz
+
+ENV GOPATH /go
+ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
+
+RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
+
+# CORD Provisioner Dockerfile
+WORKDIR $GOPATH
+
+RUN apt-get update && \
+ apt-get install -y software-properties-common && \
+ apt-add-repository ppa:ansible/ansible && \
+ apt-get update -y -m && \
+ apt-get install -y git ansible
+
+RUN mkdir -p /root/.ssh
+COPY ssh-config /root/.ssh/config
+
+RUN mkdir -p /switchq
+COPY vendors.json /switchq/vendors.json
+
+RUN go get github.com/tools/godep
+ADD . $GOPATH/src/gerrit.opencord.com/maas/switchq
+
+WORKDIR $GOPATH/src/gerrit.opencord.com/maas/switchq
+RUN $GOPATH/bin/godep restore
+
+WORKDIR $GOPATH
+RUN go install gerrit.opencord.com/maas/switchq
+
+ENTRYPOINT ["/go/bin/switchq"]
diff --git a/switchq/Godeps/Godeps.json b/switchq/Godeps/Godeps.json
new file mode 100644
index 0000000..00c42a5
--- /dev/null
+++ b/switchq/Godeps/Godeps.json
@@ -0,0 +1,12 @@
+{
+ "ImportPath": "gerrit.opencord.org/maas/swtichq",
+ "GoVersion": "go1.6",
+ "GodepVersion": "v72",
+ "Deps": [
+ {
+ "ImportPath": "github.com/kelseyhightower/envconfig",
+ "Comment": "1.1.0-17-g91921eb",
+ "Rev": "91921eb4cf999321cdbeebdba5a03555800d493b"
+ }
+ ]
+}
diff --git a/switchq/address.go b/switchq/address.go
new file mode 100644
index 0000000..26c662c
--- /dev/null
+++ b/switchq/address.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "net/url"
+ "os"
+ "strings"
+)
+
+func NewAddressSource(spec string) (AddressSource, error) {
+ u, err := url.Parse(spec)
+ if err != nil {
+ return nil, err
+ }
+
+ switch u.Scheme {
+ case "file":
+ return NewFileAddressSource(u)
+ default:
+ }
+ return nil, fmt.Errorf("Unknown address source scheme specified '%s'", spec)
+}
+
+type AddressRec struct {
+ Name string
+ IP string
+ MAC string
+}
+
+type AddressSource interface {
+ GetAddresses() ([]AddressRec, error)
+}
+
+type FileAddressSource struct {
+ Path string
+}
+
+func NewFileAddressSource(connect *url.URL) (AddressSource, error) {
+ // Validate file exists before returning a source
+ if _, err := os.Stat(connect.Path); os.IsNotExist(err) {
+ return nil, err
+ }
+ source := FileAddressSource{}
+ source.Path = connect.Path
+ return &source, nil
+}
+
+func (s *FileAddressSource) GetAddresses() ([]AddressRec, error) {
+ // Read the file
+ file, err := os.Open(s.Path)
+ defer file.Close()
+ if err != nil {
+ return nil, err
+ }
+
+ capacity := 20
+ result := make([]AddressRec, capacity)
+ idx := 0
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ parts := strings.Fields(scanner.Text())
+
+ // Only process lines with the correct number of parts
+ if len(parts) == 6 {
+ result[idx].Name = parts[0]
+ result[idx].IP = parts[3]
+ result[idx].MAC = parts[5]
+ idx += 1
+ if idx >= capacity {
+ capacity += 20
+ tmp, result := result, make([]AddressRec, capacity)
+ copy(result, tmp)
+ }
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, err
+ }
+
+ return result[:idx], nil
+}
diff --git a/switchq/data.txt b/switchq/data.txt
new file mode 100644
index 0000000..a42563f
--- /dev/null
+++ b/switchq/data.txt
@@ -0,0 +1,9 @@
+xos IN A 172.18.0.100 ; 52:54:00:47:42:21
+leaf-1 IN A 10.6.0.7 ; cc:37:ab:7c:bd:e6
+rabbitmq-server IN A 172.18.0.91 ; 52:54:00:73:da:98
+leaf-2 IN A 10.6.0.10 ; cc:37:ab:7c:ba:58
+sample IN A 172.42.42.4 ; 52:54:00:1f:10:1f
+3938-128PC IN A 10.6.0.14 ; 00:25:90:5c:f7:48
+onos-fabric IN A 172.18.0.102 ; 52:54:00:1f:10:1f
+spine-1 IN A 10.6.0.6 ; cc:37:ab:7c:b7:4c
+spine-2 IN A 10.6.0.8 ; cc:37:ab:7c:bf:6c
diff --git a/switchq/ssh-config b/switchq/ssh-config
new file mode 100644
index 0000000..990a43d
--- /dev/null
+++ b/switchq/ssh-config
@@ -0,0 +1,3 @@
+Host *
+ StrictHostKeyChecking no
+ UserKnownHostsFile=/dev/null
diff --git a/switchq/storage.go b/switchq/storage.go
new file mode 100644
index 0000000..d4ec36d
--- /dev/null
+++ b/switchq/storage.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+)
+
+func NewStorage(spec string) (Storage, error) {
+ u, err := url.Parse(spec)
+ if err != nil {
+ return nil, err
+ }
+ switch u.Scheme {
+ case "memory":
+ return NewMemoryStorage(u)
+ default:
+ }
+ return nil, fmt.Errorf("Unknown storage scheme specified, '%s'", u.Scheme)
+}
+
+type Storage interface {
+ Switchq(mac string) (bool, error)
+ LastMACCheck(mac string) (*time.Time, error)
+ MarkMACCheck(mac string, when *time.Time) error
+ LastProvisioned(mac string) (*time.Time, error)
+ MarkProvisioned(mac string, when *time.Time) error
+}
+
+type VendorRec struct {
+ Prefix string `json:"prefix"`
+ Vendor string `json:"vendor"`
+ Provision bool `json:"provision"`
+}
+
+type MemoryStorage struct {
+ Vendors map[string]VendorRec
+ Checks map[string]time.Time
+ Times map[string]time.Time
+}
+
+func NewMemoryStorage(u *url.URL) (Storage, error) {
+
+ s := MemoryStorage{}
+ s.Vendors = make(map[string]VendorRec)
+
+ if u.Path != "" {
+ file, err := os.Open(u.Path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ data := make([]VendorRec, 0)
+ decoder := json.NewDecoder(file)
+ err = decoder.Decode(&data)
+ if err != nil {
+ return nil, err
+ }
+ for _, rec := range data {
+ s.Vendors[rec.Prefix] = rec
+ }
+ log.Printf("[debug] %v", s.Vendors)
+
+ } else {
+ log.Printf("[warn] no vendors have been set, no switches will be provisioned")
+ }
+ return &s, nil
+}
+
+func (s *MemoryStorage) Switchq(mac string) (bool, error) {
+ if len(mac) < 8 {
+ return false, nil
+ }
+ rec, ok := s.Vendors[strings.ToUpper(mac[0:8])]
+ if !ok || !rec.Provision {
+ return false, nil
+ }
+
+ return true, nil
+}
+
+func (s *MemoryStorage) LastMACCheck(mac string) (*time.Time, error) {
+ when, ok := s.Checks[mac]
+ if !ok {
+ return nil, nil
+ }
+ result := when
+ return &result, nil
+}
+
+func (s *MemoryStorage) MarkMACCheck(mac string, when *time.Time) error {
+ s.Checks[mac] = *when
+ return nil
+}
+
+func (s *MemoryStorage) LastProvisioned(mac string) (*time.Time, error) {
+ when, ok := s.Times[mac]
+ if !ok {
+ return nil, nil
+ }
+ result := when
+ return &result, nil
+}
+
+func (s *MemoryStorage) MarkProvisioned(mac string, when *time.Time) error {
+ s.Times[mac] = *when
+ return nil
+}
diff --git a/switchq/switchq.go b/switchq/switchq.go
new file mode 100644
index 0000000..922f1ba
--- /dev/null
+++ b/switchq/switchq.go
@@ -0,0 +1,102 @@
+package main
+
+import (
+ "fmt"
+ "github.com/kelseyhightower/envconfig"
+ "log"
+ "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"`
+
+ storage Storage
+ addressSource AddressSource
+ interval time.Duration
+ ttl time.Duration
+}
+
+func checkError(err error, msg string, args ...interface{}) {
+ if err != nil {
+ log.Fatalf(msg, args...)
+ }
+}
+
+func (c *Config) processRecord(rec AddressRec) error {
+ if c.ttl == 0 {
+ // One provisioning only please
+ return nil
+ }
+
+ ok, err := c.storage.Switchq(rec.MAC)
+ if err != nil {
+ return fmt.Errorf("unable to determine ventor of MAC '%s' (%s)", rec.MAC, err)
+ }
+
+ if !ok {
+ // Not something we care about
+ log.Printf("[debug] host with IP '%s' and MAC '%s' and named '%s' not a known switch type",
+ rec.IP, rec.MAC, rec.Name)
+ return nil
+ }
+
+ last, err := c.storage.LastProvisioned(rec.MAC)
+ if err != nil {
+ return err
+ }
+ if last == nil || time.Since(*last) > c.ttl {
+ log.Printf("[debug] time to provision %s", rec.MAC)
+ }
+ return nil
+}
+
+func main() {
+
+ var err error
+ config := Config{}
+ envconfig.Process("SWITCHQ", &config)
+
+ config.storage, err = NewStorage(config.StorageURL)
+ checkError(err, "Unable to create require storage for specified URL '%s' : %s", config.StorageURL, 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.Printf(`Configuration:
+ Storage URL: %s
+ Poll Interval: %s
+ Address Source: %s
+ Provision TTL: %s`,
+ config.StorageURL, config.PollInterval, config.AddressURL, config.ProvisionTTL)
+
+ // 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.Printf("[info] Checking for switches @ %s", time.Now())
+ addresses, err := config.addressSource.GetAddresses()
+
+ if err != nil {
+ log.Printf("[error] unable to read addresses from address source : %s", err)
+ } else {
+ log.Printf("[info] Queried %d addresses from address source", len(addresses))
+
+ for _, rec := range addresses {
+ log.Printf("[debug] Processing %s(%s, %s)", rec.Name, rec.IP, rec.MAC)
+ if err := config.processRecord(rec); err != nil {
+ log.Printf("[error] Error when processing IP '%s' : %s", rec.IP, err)
+ }
+ }
+ }
+
+ time.Sleep(config.interval)
+ }
+}
diff --git a/switchq/vendors.json b/switchq/vendors.json
new file mode 100644
index 0000000..03d888f
--- /dev/null
+++ b/switchq/vendors.json
@@ -0,0 +1,17 @@
+[
+ {
+ "prefix" : "CC:37:AB",
+ "vendor" : "Edgecore Networks Corportation",
+ "provision" : true
+ },
+ {
+ "prefix" : "70:72:CF",
+ "vendor" : "Edgecore Networks Corportation",
+ "provision" : true
+ },
+ {
+ "prefix" : "60:5B:B4",
+ "vendor" : "Fake",
+ "provision" : false
+ }
+]