blob: a4bec2ed4b9f21c3e3a5abc77d984a4deb7bc05a [file] [log] [blame]
David K. Bainbridgedf9df632016-07-07 18:47:46 -07001// Copyright 2016 Open Networking Laboratory
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.
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070014package main
15
16import (
David K. Bainbridge97ee8052016-06-14 00:52:07 -070017 "bytes"
18 "encoding/json"
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070019 "fmt"
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070020 "github.com/Sirupsen/logrus"
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070021 "github.com/kelseyhightower/envconfig"
David K. Bainbridge97ee8052016-06-14 00:52:07 -070022 "net/http"
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070023 "time"
24)
25
26type Config struct {
David K. Bainbridge97ee8052016-06-14 00:52:07 -070027 VendorsURL string `default:"file:///switchq/vendors.json" envconfig:"vendors_url"`
28 StorageURL string `default:"memory:" envconfig:"storage_url"`
29 AddressURL string `default:"file:///switchq/dhcp_harvest.inc" envconfig:"address_url"`
30 PollInterval string `default:"1m" envconfig:"poll_interval"`
31 ProvisionTTL string `default:"1h" envconfig:"provision_ttl"`
32 ProvisionURL string `default:"" envconfig:"provision_url"`
33 RoleSelectorURL string `default:"" envconfig:"role_selector_url"`
34 DefaultRole string `default:"fabric-switch" envconfig:"default_role"`
35 Script string `default:"do-ansible"`
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070036 LogLevel string `default:"warning" envconfig:"LOG_LEVEL"`
37 LogFormat string `default:"text" envconfig:"LOG_FORMAT"`
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070038
David K. Bainbridge97ee8052016-06-14 00:52:07 -070039 vendors Vendors
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070040 storage Storage
41 addressSource AddressSource
42 interval time.Duration
43 ttl time.Duration
44}
45
46func checkError(err error, msg string, args ...interface{}) {
47 if err != nil {
48 log.Fatalf(msg, args...)
49 }
50}
51
David K. Bainbridgec809ef72016-06-22 21:18:07 -070052func (c *Config) getProvisionedState(rec AddressRec) (int, string, error) {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070053 log.Debugf("Fetching provisioned state of device '%s' (%s, %s)",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070054 rec.Name, rec.IP, rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070055 resp, err := http.Get(c.ProvisionURL + rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070056 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070057 log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070058 rec.Name, rec.IP, rec.MAC, err)
59 return -1, "", err
David K. Bainbridge97ee8052016-06-14 00:52:07 -070060 }
61 if resp.StatusCode != 404 && int(resp.StatusCode/100) != 2 {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070062 log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070063 rec.Name, rec.IP, rec.MAC, resp.Status)
64 return -1, "", fmt.Errorf(resp.Status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070065 }
66 defer resp.Body.Close()
67 if resp.StatusCode != 404 {
68 decoder := json.NewDecoder(resp.Body)
69 var raw interface{}
70 err = decoder.Decode(&raw)
71 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070072 log.Errorf("Unmarshal provisioning service response for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070073 rec.Name, rec.IP, rec.MAC, err)
74 return -1, "", err
David K. Bainbridge97ee8052016-06-14 00:52:07 -070075 }
76 status := raw.(map[string]interface{})
77 switch int(status["status"].(float64)) {
78 case 0, 1: // "PENDING", "RUNNING"
David K. Bainbridgec809ef72016-06-22 21:18:07 -070079 return int(status["status"].(float64)), "", nil
David K. Bainbridge97ee8052016-06-14 00:52:07 -070080 case 2: // "COMPLETE"
David K. Bainbridgec809ef72016-06-22 21:18:07 -070081 return 2, "", nil
David K. Bainbridge97ee8052016-06-14 00:52:07 -070082 case 3: // "FAILED"
David K. Bainbridgec809ef72016-06-22 21:18:07 -070083 return 3, status["message"].(string), nil
David K. Bainbridge97ee8052016-06-14 00:52:07 -070084 default:
85 err = fmt.Errorf("unknown provisioning status : %d", status["status"])
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070086 log.Errorf("received unknown provisioning status for device '%s (%s)' : %s",
David K. Bainbridge97ee8052016-06-14 00:52:07 -070087 rec.Name, rec.MAC, err)
David K. Bainbridgec809ef72016-06-22 21:18:07 -070088 return -1, "", err
David K. Bainbridge97ee8052016-06-14 00:52:07 -070089 }
90 }
David K. Bainbridgec809ef72016-06-22 21:18:07 -070091
92 // If we end up here that means that no record was found in the provisioning, so return
93 // a status of -1, w/o an error
94 return -1, "", nil
95}
96
97func (c *Config) provision(rec AddressRec) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070098 log.Infof("POSTing to '%s' for provisioning of '%s (%s)'", c.ProvisionURL, rec.Name, rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070099 data := map[string]string{
100 "id": rec.MAC,
101 "name": rec.Name,
102 "ip": rec.IP,
103 "mac": rec.MAC,
104 }
105 if c.RoleSelectorURL != "" {
106 data["role_selector"] = c.RoleSelectorURL
107 }
108 if c.DefaultRole != "" {
109 data["role"] = c.DefaultRole
110 }
111 if c.Script != "" {
112 data["script"] = c.Script
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700113 }
114
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700115 hc := http.Client{}
116 var b []byte
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700117 b, err := json.Marshal(data)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700118 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700119 log.Errorf("Unable to marshal provisioning data : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700120 return err
121 }
122 req, err := http.NewRequest("POST", c.ProvisionURL, bytes.NewReader(b))
123 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700124 log.Errorf("Unable to construct POST request to provisioner : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700125 return err
126 }
127
128 req.Header.Add("Content-Type", "application/json")
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700129 resp, err := hc.Do(req)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700130 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700131 log.Errorf("Unable to POST request to provisioner : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700132 return err
133 }
134
135 defer resp.Body.Close()
136 if resp.StatusCode != http.StatusAccepted {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700137 log.Errorf("Provisioning request not accepted by provisioner : %s", resp.Status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700138 return err
139 }
140
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700141 return nil
142}
143
144func (c *Config) processRecord(rec AddressRec) error {
145 ok, err := c.vendors.Switchq(rec.MAC)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700146 if err != nil {
147 return fmt.Errorf("unable to determine ventor of MAC '%s' (%s)", rec.MAC, err)
148 }
149
150 if !ok {
151 // Not something we care about
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700152 log.Debugf("host with IP '%s' and MAC '%s' and named '%s' not a known switch type",
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700153 rec.IP, rec.MAC, rec.Name)
154 return nil
155 }
156
157 last, err := c.storage.LastProvisioned(rec.MAC)
158 if err != nil {
159 return err
160 }
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700161
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700162 if last == nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700163 log.Debugf("no TTL for device '%s' (%s, %s)",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700164 rec.Name, rec.IP, rec.MAC)
165 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700166 log.Debugf("TTL for device '%s' (%s, %s) is %v",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700167 rec.Name, rec.IP, rec.MAC, *last)
168 }
169
170 // Verify if the provision status of the node is complete, if in an error state then TTL means
171 // nothing
172 state, message, err := c.getProvisionedState(rec)
173 switch state {
174 case 0, 1: // Pending or Running
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700175 log.Debugf("device '%s' (%s, %s) is being provisioned",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700176 rec.Name, rec.IP, rec.MAC)
177 return nil
178 case 2: // Complete
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700179 log.Debugf("device '%s' (%s, %s) has completed provisioning",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700180 rec.Name, rec.IP, rec.MAC)
181 // If no last record then set the TTL
182 if last == nil {
183 now := time.Now()
184 last = &now
185 c.storage.MarkProvisioned(rec.MAC, last)
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700186 log.Debugf("Storing TTL for device '%s' (%s, %s) as %v",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700187 rec.Name, rec.IP, rec.MAC, now)
188 return nil
189 }
190 case 3: // Failed
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700191 log.Debugf("device '%s' (%s, %s) failed last provisioning with message '%s', reattempt",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700192 rec.Name, rec.IP, rec.MAC, message)
193 c.storage.ClearProvisioned(rec.MAC)
194 last = nil
195 default: // No record
196 }
197
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700198 // If TTL is 0 then we will only provision a switch once.
199 if last == nil || (c.ttl > 0 && time.Since(*last) > c.ttl) {
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700200 if last != nil {
201 c.storage.ClearProvisioned(rec.MAC)
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700202 log.Debugf("device '%s' (%s, %s) TTL expired, reprovisioning",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700203 rec.Name, rec.IP, rec.MAC)
204 }
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700205 c.provision(rec)
206 } else if c.ttl == 0 {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700207 log.Debugf("device '%s' (%s, %s) has completed its one time provisioning, with a TTL set to %s",
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700208 rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
209 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700210 log.Debugf("device '%s' (%s, %s) has completed provisioning within the specified TTL of %s",
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700211 rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700212 }
213 return nil
214}
215
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700216var log = logrus.New()
217
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700218func main() {
219
220 var err error
221 config := Config{}
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700222 err = envconfig.Process("SWITCHQ", &config)
223 if err != nil {
224 log.Fatalf("Unable to parse configuration options : %s", err)
225 }
226
227 switch config.LogFormat {
228 case "json":
229 log.Formatter = &logrus.JSONFormatter{}
230 default:
231 log.Formatter = &logrus.TextFormatter{
232 FullTimestamp: true,
233 ForceColors: true,
234 }
235 }
236
237 level, err := logrus.ParseLevel(config.LogLevel)
238 if err != nil {
239 level = logrus.WarnLevel
240 }
241 log.Level = level
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700242
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700243 config.vendors, err = NewVendors(config.VendorsURL)
244 checkError(err, "Unable to create known vendors list from specified URL '%s' : %s", config.VendorsURL, err)
245
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700246 config.storage, err = NewStorage(config.StorageURL)
247 checkError(err, "Unable to create require storage for specified URL '%s' : %s", config.StorageURL, err)
248
249 config.addressSource, err = NewAddressSource(config.AddressURL)
250 checkError(err, "Unable to create required address source for specified URL '%s' : %s", config.AddressURL, err)
251
252 config.interval, err = time.ParseDuration(config.PollInterval)
253 checkError(err, "Unable to parse specified poll interface '%s' : %s", config.PollInterval, err)
254
255 config.ttl, err = time.ParseDuration(config.ProvisionTTL)
256 checkError(err, "Unable to parse specified provision TTL value of '%s' : %s", config.ProvisionTTL, err)
257
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700258 log.Infof(`Configuration:
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700259 Vendors URL: %s
260 Storage URL: %s
261 Poll Interval: %s
262 Address Source: %s
263 Provision TTL: %s
264 Provision URL: %s
265 Role Selector URL: %s
266 Default Role: %s
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700267 Script: %s
268 Log Level: %s
269 Log Format: %s`,
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700270 config.VendorsURL, config.StorageURL, config.PollInterval, config.AddressURL, config.ProvisionTTL,
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700271 config.ProvisionURL, config.RoleSelectorURL, config.DefaultRole, config.Script,
272 config.LogLevel, config.LogFormat)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700273
274 // We use two methods to attempt to find the MAC (hardware) address associated with an IP. The first
275 // is to look in the table. The second is to send an ARP packet.
276 for {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700277 log.Infof("Checking for switches @ %s", time.Now())
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700278 addresses, err := config.addressSource.GetAddresses()
279
280 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700281 log.Errorf("unable to read addresses from address source : %s", err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700282 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700283 log.Infof("Queried %d addresses from address source", len(addresses))
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700284
285 for _, rec := range addresses {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700286 log.Debugf("Processing %s(%s, %s)", rec.Name, rec.IP, rec.MAC)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700287 if err := config.processRecord(rec); err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700288 log.Errorf("Error when processing IP '%s' : %s", rec.IP, err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700289 }
290 }
291 }
292
293 time.Sleep(config.interval)
294 }
295}