CORD-313 refactor configuration generator
Change-Id: I4428ff0b67ee8d6ebb9b7009cd82413416c25a84
diff --git a/config-generator/configGen.go b/config-generator/configGen.go
index b0460f0..c23ce53 100644
--- a/config-generator/configGen.go
+++ b/config-generator/configGen.go
@@ -14,83 +14,29 @@
package main
import (
- "encoding/json"
- "errors"
"fmt"
+ "net/http"
+
+ "github.com/Sirupsen/logrus"
+
"github.com/gorilla/mux"
"github.com/kelseyhightower/envconfig"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "strings"
- "text/template"
)
-var unmarshalError = errors.New("Error Unmarshaling the JSON Data\n")
-
type Config struct {
- Port string `default:"8181"`
- IP string `default:"127.0.0.1"`
- SwitchCount int `default:"0"`
- HostCount int `default:"0"`
- Username string `default:"karaf"`
- Password string `default:"karaf"`
- LogLevel string `default:"warning" envconfig:"LOG_LEVEL"`
- LogFormat string `default:"text" envconfig:"LOG_FORMAT"`
- ConfigServerPort string `default:"1337"`
- ConfigServerIP string `default:"127.0.0.1"`
-}
+ Port int `default:"1337"`
+ Listen string `default:"0.0.0.0"`
+ Controller string `default:"http://%s:%s@127.0.0.1:8181"`
+ Username string `default:"karaf"`
+ Password string `default:"karaf"`
+ LogLevel string `default:"warning" envconfig:"LOG_LEVEL"`
+ LogFormat string `default:"text" envconfig:"LOG_FORMAT"`
-type hosts struct {
- Host []struct {
- Mac string `json:"mac"`
- IpAddresses []string `json:"ipAddresses"`
- Location struct {
- ElementID string `json:"elementId`
- Port string `json:"port"`
- } `json:"location"`
- Comma string
- Gateway string
- } `json:"hosts"`
-}
-
-type devices struct {
- Device []struct {
- Id string `json:"id"`
- ChassisId string `json:"chassisId"`
- Annotations struct {
- ManagementAddress string `json:"managementAddress"`
- } `json:"annotations"`
- Comma string `default:","`
- } `json:"devices"`
-}
-
-type onosLinks struct {
- Links []struct {
- Src struct {
- Port string `json:"port"`
- Device string `json:"device"`
- } `json:"src"`
- Dst struct {
- Port string `json:"port"`
- Device string `json:"device"`
- } `json:"dst"`
- } `json:"links"`
-}
-
-type linkStructJSON struct {
- Val string
- Comma string
-}
-
-type ConfigParam struct {
- SwitchCount int `json:"switchcount"`
- HostCount int `json:"hostcount"`
- ONOSIP string `json:"onosip"`
+ connect string
}
var c Config
+var log = logrus.New()
func main() {
@@ -99,207 +45,39 @@
log.Fatalf("[ERROR] Unable to parse configuration options : %s", err)
}
+ switch c.LogFormat {
+ case "json":
+ log.Formatter = &logrus.JSONFormatter{}
+ default:
+ log.Formatter = &logrus.TextFormatter{
+ FullTimestamp: true,
+ ForceColors: true,
+ }
+ }
+
+ level, err := logrus.ParseLevel(c.LogLevel)
+ if err != nil {
+ level = logrus.WarnLevel
+ }
+ log.Level = level
+
+ log.Infof(`Configuration:
+ LISTEN: %s
+ PORT: %d
+ CONTROLLER: %s
+ USERNAME: %s
+ PASSWORD: %s
+ LOG_LEVEL: %s
+ LOG_FORMAT: %s`,
+ c.Listen, c.Port, c.Controller,
+ c.Username, c.Password,
+ c.LogLevel, c.LogFormat)
+
router := mux.NewRouter()
- router.HandleFunc("/config/", ConfigGenHandler).Methods("POST")
+ router.HandleFunc("/config/", c.configGenHandler).Methods("POST")
http.Handle("/", router)
- fmt.Println("Config Generator server listening at: " + c.ConfigServerIP + ":" + c.ConfigServerPort)
+ c.connect = fmt.Sprintf(c.Controller, c.Username, c.Password)
- http.ListenAndServe(c.ConfigServerIP+":"+c.ConfigServerPort, nil)
-
-}
-
-func ConfigGenHandler(w http.ResponseWriter, r *http.Request) {
- var para ConfigParam
-
- decoder := json.NewDecoder(r.Body)
- defer r.Body.Close()
- if err := decoder.Decode(¶); err != nil {
- fmt.Errorf("Unable to decode request to provision : %s", err)
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- c.HostCount = para.HostCount
- c.SwitchCount = para.SwitchCount
- c.IP = para.ONOSIP
-
- onos := "http://" + c.Username + ":" + c.Password + "@" + c.IP + ":" + c.Port
-
- err := os.Remove("network-cfg.json")
- if err != nil {
- log.Println("Warning: no file called network-cfg.json (ignore if this is the first run)")
- }
- err = generateDevicesJSON(onos)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, err.Error())
- return
- }
- err = generateLinkJSON(onos)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, err.Error())
- return
- }
- err = generateHostJSON(onos)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- fmt.Fprintf(w, err.Error())
- return
- }
-
- fmt.Println("Config file generated: network-cfg.json")
-
- data, err := ioutil.ReadFile("network-cfg.json")
- check(err)
-
- w.WriteHeader(http.StatusAccepted)
- fmt.Fprintf(w, string(data))
-
-}
-
-func writeToFile(object interface{}, t string) {
- f, err := os.OpenFile("network-cfg.json", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
- if err != nil {
- panic(err)
- }
-
- defer f.Close()
-
- tpl, err := template.ParseFiles(t)
- check(err)
- err = tpl.Execute(f, object)
- check(err)
-}
-
-func generateDevicesJSON(onos string) error {
- ds, err := getData(onos + "/onos/v1/devices")
- if err != nil {
- return err
- }
-
- var d devices
- err = json.Unmarshal(ds, &d)
- if err != nil {
- return unmarshalError
- }
-
- if len(d.Device) != c.SwitchCount {
- _ = os.Remove("network-cfg.json")
- log.Println("[INFO] Cleaning up unfinished config file")
- e := fmt.Sprintf("[ERROR] Number of switches configured don't match actual switches connected to the controller. Configured: %d, connected: %d", c.SwitchCount, len(d.Device))
- log.Println(e)
- return errors.New(e)
- }
-
- for k, _ := range d.Device {
- d.Device[k].Comma = ","
- if k >= len(d.Device)-1 {
- d.Device[k].Comma = ""
- }
- }
-
- writeToFile(d.Device, "devices.tpl")
- return nil
-
-}
-
-func generateHostJSON(onos string) error {
- hs, err := getData(onos + "/onos/v1/hosts")
- if err != nil {
- return err
- }
- var h hosts
- err = json.Unmarshal(hs, &h)
- if err != nil {
- return unmarshalError
- }
-
- if len(h.Host) != c.HostCount {
- _ = os.Remove("network-cfg.json")
- log.Println("[INFO] Cleaning up unfinished config file")
- e := fmt.Sprintf("[ERROR] Number of hosts configured don't match actual hosts visible to the controller. Configured: %d, connected: %d", c.HostCount, len(h.Host))
- log.Println(e)
- return errors.New(e)
- }
-
- for k, _ := range h.Host {
-
- h.Host[k].Comma = ","
- if k >= len(h.Host)-1 {
- h.Host[k].Comma = ""
- }
-
- parts := strings.Split(h.Host[k].IpAddresses[0], ".")
- ip := ""
- for _, v := range parts[:len(parts)-1] {
- ip = ip + v + "."
- }
- h.Host[k].Gateway = ip
- }
-
- writeToFile(h.Host, "ports.tpl")
-
- writeToFile(h.Host, "hosts.tpl")
- return nil
-
-}
-
-func generateLinkJSON(onos string) error {
-
- links, err := getData(onos + "/onos/v1/links")
- if err != nil {
- return err
- }
-
- var l onosLinks
- err = json.Unmarshal(links, &l)
- if err != nil {
- return unmarshalError
- }
-
- var in []linkStructJSON
-
- for k, v := range l.Links {
-
- comma := ","
- val := fmt.Sprint(v.Src.Device + "/" + v.Src.Port + "-" + v.Dst.Device + "/" + v.Dst.Port)
- if k >= len(l.Links)-1 {
- comma = ""
- }
-
- tmp := linkStructJSON{val, comma}
- in = append(in, tmp)
-
- }
-
- writeToFile(in, "links.tpl")
-
- return nil
-
-}
-
-func getData(url string) ([]byte, error) {
-
- resp, err := http.Get(url)
- if err != nil {
- return nil, errors.New("Error getting data from the URL\n")
- }
-
- defer resp.Body.Close()
-
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, errors.New("Error reading data from response body\n")
- }
-
- return body, nil
-
-}
-
-func check(e error) {
- if e != nil {
- panic(e)
- }
+ panic(http.ListenAndServe(fmt.Sprintf(":%d", c.Port), nil))
}
diff --git a/config-generator/devices.tpl b/config-generator/devices.tpl
deleted file mode 100644
index 66ef4d9..0000000
--- a/config-generator/devices.tpl
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "devices" : {
-{{ range $index, $element := . }}
- "{{ .Id}}" : {
- "segmentrouting" : {
- "name" : "device-{{ .ChassisId }}",
- "nodeSid" : 10{{ $index }},
- "routerIp" : "{{ .Annotations.ManagementAddress }}",
- "routerMac" : "cc:37:ab:00:00:0{{ $index }}",
- "isEdgeRouter" : true,
- "adjacencySids" : []
- }
- }{{ .Comma }}{{ end }}
- },
-
diff --git a/config-generator/handlers.go b/config-generator/handlers.go
new file mode 100644
index 0000000..eabdbe3
--- /dev/null
+++ b/config-generator/handlers.go
@@ -0,0 +1,168 @@
+// 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 (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "strings"
+ "text/template"
+)
+
+type GenerationOptions struct {
+ SwitchCount int `json:"switchcount"`
+ HostCount int `json:"hostcount"`
+}
+
+func (c *Config) configGenHandler(w http.ResponseWriter, r *http.Request) {
+ var options GenerationOptions
+
+ deviceMap := make(map[string]*onosDevice)
+
+ decoder := json.NewDecoder(r.Body)
+ defer r.Body.Close()
+ if err := decoder.Decode(&options); err != nil {
+ log.Errorf("Unable to decode provisioning request options: %s", err)
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ var devices onosDevices
+ err := c.fetch("/onos/v1/devices", &devices)
+ if err != nil {
+ log.Errorf("Unable to retrieve device information from controller: %s", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // If the request specified the number of switches, validate we have that
+ // exact number
+ if options.SwitchCount > 0 && len(devices.Devices) != options.SwitchCount {
+ log.Errorf("Expecting %d switch(es), found %d, no configuration generated",
+ options.SwitchCount, len(devices.Devices))
+ http.Error(w, "Expected switch count mismatch",
+ http.StatusInternalServerError)
+ return
+ }
+
+ for _, device := range devices.Devices {
+ deviceMap[device.Id] = device
+ device.Mac = splitString(device.ChassisId, 2, ":")
+ }
+
+ var hosts onosHosts
+ err = c.fetch("/onos/v1/hosts", &hosts)
+ if err != nil {
+ log.Errorf("Unable to retrieve host information from controller: %s", err)
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+
+ }
+
+ // If the request specified the number of hosts, validate we have that
+ // exact number
+ if options.HostCount > 0 && len(hosts.Hosts) != options.HostCount {
+ log.Errorf("Expecting %d host(s), found %d, no configuration generaged",
+ options.HostCount, len(hosts.Hosts))
+ http.Error(w, "Expected host count mismatch",
+ http.StatusInternalServerError)
+ return
+ }
+
+ // Use a simple heuristic to determine which switches are edge routers
+ // and which are not
+ markEdgeRouters(deviceMap, hosts)
+
+ // Generate the configuration file
+ cfg := onosConfig{
+ Devices: devices.Devices,
+ Hosts: hosts.Hosts,
+ }
+
+ funcMap := template.FuncMap{
+ // The name "inc" is what the function will be called in the template text.
+ "add": func(a, b int) int {
+ return a + b
+ },
+ "gateway": func(ips []string) string {
+ if len(ips) > 0 {
+ parts := strings.Split(ips[0], ".")
+ ip := ""
+ for _, v := range parts[:len(parts)-1] {
+ ip = ip + v + "."
+ }
+ return ip + "254/24"
+ } else {
+ return "0.0.0.254/24"
+ }
+ },
+ }
+
+ tpl, err := template.New("netconfig.tpl").Funcs(funcMap).ParseFiles("netconfig.tpl")
+ if err != nil {
+ log.Errorf("Unable to parse template: %s", err)
+ http.Error(w, "Template parse error", http.StatusInternalServerError)
+ return
+ }
+
+ // Write template to buffer, so if there is an error we can return an
+ // http error
+ buf := new(bytes.Buffer)
+ err = tpl.Execute(buf, cfg)
+ if err != nil {
+ log.Errorf("Unexpected error while processing template: %s", err)
+ http.Error(w, "Template processing error", http.StatusInternalServerError)
+ }
+
+ w.Write(buf.Bytes())
+}
+
+// markEdgeRouters use hueristic to determine and mark switches that act
+// as edge routers
+func markEdgeRouters(dm map[string]*onosDevice, hosts onosHosts) {
+ // Walk the list of know compute nodes (hosts) and if the compute node
+ // is connected to a switch, then that switch is an edge router
+ for _, host := range hosts.Hosts {
+ if device, ok := dm[host.Location.ElementID]; ok {
+ (*device).IsEdgeRouter = true
+ }
+ }
+}
+
+// splitString used to convert a string to a psudeo MAC address by
+// splitting and separating it with a colon
+func splitString(src string, n int, sep string) string {
+ r := ""
+ for i, c := range src {
+ if i > 0 && i%n == 0 {
+ r += sep
+ }
+ r += string(c)
+ }
+ return r
+}
+
+// fetch fetch the specified data from ONOS
+func (c *Config) fetch(path string, data interface{}) error {
+ resp, err := http.Get(c.connect + path)
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+ decoder := json.NewDecoder(resp.Body)
+ err = decoder.Decode(data)
+ return err
+}
diff --git a/config-generator/hosts.tpl b/config-generator/hosts.tpl
deleted file mode 100644
index 3fb102c..0000000
--- a/config-generator/hosts.tpl
+++ /dev/null
@@ -1,17 +0,0 @@
- "hosts" : {
-{{ range . }}
- "{{ .Mac }}/-1" : {
- "basic" : {
- "ips" : ["{{ range $element := .IpAddresses }}{{ $element }}{{ end}}"],
- "location" : "{{ .Location.ElementID }}/{{ .Location.Port }}"
- }
- }{{ .Comma }}{{ end }}
- },
- "apps" : {
- "org.onosproject.core" : {
- "core" : {
- "linkDiscoveryMode" : "STRICT"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/config-generator/links.tpl b/config-generator/links.tpl
deleted file mode 100644
index 4f39412..0000000
--- a/config-generator/links.tpl
+++ /dev/null
@@ -1,7 +0,0 @@
- "links": {
-{{ range . }}
- "{{ .Val }}": {
- "basic": {}
- }{{ .Comma }}{{ end }}
- },
-
diff --git a/config-generator/netconfig.tpl b/config-generator/netconfig.tpl
new file mode 100644
index 0000000..62d175f
--- /dev/null
+++ b/config-generator/netconfig.tpl
@@ -0,0 +1,34 @@
+{
+ "devices": {
+ {{ range $index, $element := .Devices }}{{ if $index }},
+ {{ end }}"{{ .Id }}": {
+ "segmentrouting": {
+ "name": "device-{{ .ChassisId }}",
+ "nodeSid": {{ add 100 $index }},
+ "routerIp": "{{ .Annotations.ManagementAddress }}",
+ "routerMac": "{{ .Mac }}",
+ "isEdgeRouter": {{ .IsEdgeRouter }},
+ "adjacencySids": []
+ }
+ }{{ end }}
+ },
+ "hosts": {
+ {{ range $index, $element := .Hosts }}{{ if $index }},
+ {{ end }}"{{ .Mac }}": {
+ "ips": ["{{ range $ip := .IpAddresses }}{{ $ip }}{{ end }}"],
+ "location": "{{ .Location.ElementID }}/{{ .Location.Port }}"
+ }{{ end }}
+ },
+ "ports": {
+ {{ range $index, $element := .Hosts }}{{ if $index }},
+ {{ end }}"{{ .Location.ElementID }}/{{ .Location.Port }}": {
+ "interfaces": [
+ {
+ "ips": [ "{{ gateway .IpAddresses }}" ]
+ }
+ ]
+ }{{ end }}
+ },
+ "links": {},
+ "apps": {}
+}
diff --git a/config-generator/onos_types.go b/config-generator/onos_types.go
new file mode 100644
index 0000000..b5d3c59
--- /dev/null
+++ b/config-generator/onos_types.go
@@ -0,0 +1,63 @@
+// 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
+
+type onosHost struct {
+ Id string `json:"id"`
+ Mac string `json:"mac"`
+ IpAddresses []string `json:"ipAddresses"`
+ Location struct {
+ ElementID string `json:"elementId`
+ Port string `json:"port"`
+ } `json:"location"`
+}
+
+type onosHosts struct {
+ Hosts []*onosHost `json:"hosts"`
+}
+
+type onosDevice struct {
+ Id string `json:"id"`
+ ChassisId string `json:"chassisId"`
+ IsEdgeRouter bool `json:"isEdgeRouter"`
+ Annotations struct {
+ ManagementAddress string `json:"managementAddress"`
+ } `json:"annotations"`
+ Mac string `json:"-"`
+}
+
+type onosDevices struct {
+ Devices []*onosDevice `json:"devices"`
+}
+
+type onosLink struct {
+ Src struct {
+ Port string `json:"port"`
+ Device string `json:"device"`
+ } `json:"src"`
+ Dst struct {
+ Port string `json:"port"`
+ Device string `json:"device"`
+ } `json:"dst"`
+}
+
+type onosLinks struct {
+ Links []*onosLink `json:"links"`
+}
+
+type onosConfig struct {
+ Devices []*onosDevice
+ Hosts []*onosHost
+ Links []*onosLink
+}
diff --git a/config-generator/ports.tpl b/config-generator/ports.tpl
deleted file mode 100644
index 1252024..0000000
--- a/config-generator/ports.tpl
+++ /dev/null
@@ -1,11 +0,0 @@
-"ports": {
-{{ range . }}
- "{{ .Location.ElementID }}/{{ .Location.Port }}" : {
- "interfaces": [
- {
- "ips" : [ "{{ .Gateway }}254/24" ]
- }
- ]
- }{{ .Comma }}{{ end }}
- },
-