Brian O'Connor | 6a37ea9 | 2017-08-03 22:45:59 -0700 | [diff] [blame] | 1 | // Copyright 2016 Open Networking Foundation |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package main |
| 15 | |
| 16 | import ( |
| 17 | "bytes" |
| 18 | "encoding/json" |
| 19 | "net/http" |
| 20 | "strings" |
| 21 | "text/template" |
| 22 | ) |
| 23 | |
| 24 | type GenerationOptions struct { |
| 25 | SwitchCount int `json:"switchcount"` |
| 26 | HostCount int `json:"hostcount"` |
| 27 | } |
| 28 | |
| 29 | func (c *Config) configGenHandler(w http.ResponseWriter, r *http.Request) { |
| 30 | var options GenerationOptions |
| 31 | |
| 32 | deviceMap := make(map[string]*onosDevice) |
| 33 | |
| 34 | decoder := json.NewDecoder(r.Body) |
| 35 | defer r.Body.Close() |
| 36 | if err := decoder.Decode(&options); err != nil { |
| 37 | log.Errorf("Unable to decode provisioning request options: %s", err) |
| 38 | http.Error(w, err.Error(), http.StatusBadRequest) |
| 39 | return |
| 40 | } |
| 41 | |
| 42 | var devices onosDevices |
| 43 | err := c.fetch("/onos/v1/devices", &devices) |
| 44 | if err != nil { |
| 45 | log.Errorf("Unable to retrieve device information from controller: %s", err) |
| 46 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 47 | return |
| 48 | } |
| 49 | |
| 50 | // If the request specified the number of switches, validate we have that |
| 51 | // exact number |
| 52 | if options.SwitchCount > 0 && len(devices.Devices) != options.SwitchCount { |
| 53 | log.Errorf("Expecting %d switch(es), found %d, no configuration generated", |
| 54 | options.SwitchCount, len(devices.Devices)) |
| 55 | http.Error(w, "Expected switch count mismatch", |
| 56 | http.StatusInternalServerError) |
| 57 | return |
| 58 | } |
| 59 | |
| 60 | for _, device := range devices.Devices { |
| 61 | deviceMap[device.Id] = device |
| 62 | device.Mac = splitString(device.ChassisId, 2, ":") |
| 63 | } |
| 64 | |
| 65 | var hosts onosHosts |
| 66 | err = c.fetch("/onos/v1/hosts", &hosts) |
| 67 | if err != nil { |
| 68 | log.Errorf("Unable to retrieve host information from controller: %s", err) |
| 69 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 70 | return |
| 71 | |
| 72 | } |
| 73 | |
| 74 | // If the request specified the number of hosts, validate we have that |
| 75 | // exact number |
| 76 | if options.HostCount > 0 && len(hosts.Hosts) != options.HostCount { |
Jonathan Hart | e8f66d1 | 2017-08-21 16:05:42 -0700 | [diff] [blame] | 77 | log.Errorf("Expecting %d host(s), found %d, no configuration generated", |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 78 | options.HostCount, len(hosts.Hosts)) |
| 79 | http.Error(w, "Expected host count mismatch", |
| 80 | http.StatusInternalServerError) |
| 81 | return |
| 82 | } |
| 83 | |
| 84 | // Use a simple heuristic to determine which switches are edge routers |
| 85 | // and which are not |
| 86 | markEdgeRouters(deviceMap, hosts) |
| 87 | |
| 88 | // Generate the configuration file |
| 89 | cfg := onosConfig{ |
| 90 | Devices: devices.Devices, |
| 91 | Hosts: hosts.Hosts, |
| 92 | } |
| 93 | |
| 94 | funcMap := template.FuncMap{ |
| 95 | // The name "inc" is what the function will be called in the template text. |
| 96 | "add": func(a, b int) int { |
| 97 | return a + b |
| 98 | }, |
| 99 | "gateway": func(ips []string) string { |
David K. Bainbridge | 566d957 | 2017-09-21 10:51:53 -0700 | [diff] [blame] | 100 | // Find the first v4 address, as determined by |
| 101 | // not having a ':' in the IP |
| 102 | for _, ip := range ips { |
| 103 | if strings.Index(ip, ":") == -1 { |
| 104 | parts := strings.Split(ip, ".") |
| 105 | targetIp := "" |
| 106 | for _, v := range parts[:len(parts)-1] { |
| 107 | targetIp = targetIp + v + "." |
| 108 | } |
| 109 | return targetIp + "254/24" |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 110 | } |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 111 | } |
David K. Bainbridge | 566d957 | 2017-09-21 10:51:53 -0700 | [diff] [blame] | 112 | return "0.0.0.254/24" |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 113 | }, |
Jonathan Hart | e8f66d1 | 2017-08-21 16:05:42 -0700 | [diff] [blame] | 114 | "vlan": func(ips []string) string { |
David K. Bainbridge | 566d957 | 2017-09-21 10:51:53 -0700 | [diff] [blame] | 115 | // Find the first v4 address, as determined by |
| 116 | // not having a ':' in the IP |
| 117 | for _, ip := range ips { |
| 118 | if strings.Index(ip, ":") == -1 { |
| 119 | return strings.Split(ip, ".")[2] |
| 120 | } |
Jonathan Hart | e8f66d1 | 2017-08-21 16:05:42 -0700 | [diff] [blame] | 121 | } |
David K. Bainbridge | 566d957 | 2017-09-21 10:51:53 -0700 | [diff] [blame] | 122 | return "0" |
Jonathan Hart | e8f66d1 | 2017-08-21 16:05:42 -0700 | [diff] [blame] | 123 | }, |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 124 | } |
| 125 | |
| 126 | tpl, err := template.New("netconfig.tpl").Funcs(funcMap).ParseFiles("netconfig.tpl") |
| 127 | if err != nil { |
| 128 | log.Errorf("Unable to parse template: %s", err) |
| 129 | http.Error(w, "Template parse error", http.StatusInternalServerError) |
| 130 | return |
| 131 | } |
| 132 | |
| 133 | // Write template to buffer, so if there is an error we can return an |
| 134 | // http error |
| 135 | buf := new(bytes.Buffer) |
| 136 | err = tpl.Execute(buf, cfg) |
| 137 | if err != nil { |
| 138 | log.Errorf("Unexpected error while processing template: %s", err) |
| 139 | http.Error(w, "Template processing error", http.StatusInternalServerError) |
| 140 | } |
| 141 | |
| 142 | w.Write(buf.Bytes()) |
| 143 | } |
| 144 | |
| 145 | // markEdgeRouters use hueristic to determine and mark switches that act |
| 146 | // as edge routers |
| 147 | func markEdgeRouters(dm map[string]*onosDevice, hosts onosHosts) { |
| 148 | // Walk the list of know compute nodes (hosts) and if the compute node |
| 149 | // is connected to a switch, then that switch is an edge router |
| 150 | for _, host := range hosts.Hosts { |
Jonathan Hart | f5ce31a | 2018-01-31 15:48:21 -0800 | [diff] [blame] | 151 | // Assume that each host has only one location for now |
| 152 | if device, ok := dm[host.Locations[0].ElementID]; ok { |
David K. Bainbridge | c04fd55 | 2016-11-08 18:39:29 -0800 | [diff] [blame] | 153 | (*device).IsEdgeRouter = true |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | // splitString used to convert a string to a psudeo MAC address by |
| 159 | // splitting and separating it with a colon |
| 160 | func splitString(src string, n int, sep string) string { |
| 161 | r := "" |
| 162 | for i, c := range src { |
| 163 | if i > 0 && i%n == 0 { |
| 164 | r += sep |
| 165 | } |
| 166 | r += string(c) |
| 167 | } |
| 168 | return r |
| 169 | } |
| 170 | |
| 171 | // fetch fetch the specified data from ONOS |
| 172 | func (c *Config) fetch(path string, data interface{}) error { |
| 173 | resp, err := http.Get(c.connect + path) |
| 174 | if err != nil { |
| 175 | return err |
| 176 | } |
| 177 | |
| 178 | defer resp.Body.Close() |
| 179 | decoder := json.NewDecoder(resp.Body) |
| 180 | err = decoder.Decode(data) |
| 181 | return err |
| 182 | } |