blob: d637b74d37000c7cf4c63effc8f3fc82fbd9df75 [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"`
David K. Bainbridge97ee8052016-06-14 00:52:07 -070028 AddressURL string `default:"file:///switchq/dhcp_harvest.inc" envconfig:"address_url"`
29 PollInterval string `default:"1m" envconfig:"poll_interval"`
30 ProvisionTTL string `default:"1h" envconfig:"provision_ttl"`
31 ProvisionURL string `default:"" envconfig:"provision_url"`
32 RoleSelectorURL string `default:"" envconfig:"role_selector_url"`
33 DefaultRole string `default:"fabric-switch" envconfig:"default_role"`
34 Script string `default:"do-ansible"`
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070035 LogLevel string `default:"warning" envconfig:"LOG_LEVEL"`
36 LogFormat string `default:"text" envconfig:"LOG_FORMAT"`
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070037
David K. Bainbridge97ee8052016-06-14 00:52:07 -070038 vendors Vendors
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070039 addressSource AddressSource
40 interval time.Duration
41 ttl time.Duration
42}
43
David K. Bainbridge52f29542016-07-27 22:28:15 -070044const (
45 Pending TaskStatus = iota
46 Running
47 Complete
48 Failed
49)
50
51type RequestInfo struct {
52 Id string `json:"id"`
53 Name string `json:"name"`
54 Ip string `json:"ip"`
55 Mac string `json:"mac"`
56 RoleSelector string `json:"role_selector"`
57 Role string `json:"role"`
58 Script string `json:"script"`
59}
60
61type TaskStatus uint8
62
63type WorkRequest struct {
64 Info *RequestInfo
65 Script string
66 Role string
67}
68
69type StatusMsg struct {
70 Request *WorkRequest `json:"request"`
71 Worker int `json:"worker"`
72 Status TaskStatus `json:"status"`
73 Message string `json:"message"`
74 Timestamp int64 `json:"timestamp"`
75}
76
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070077func checkError(err error, msg string, args ...interface{}) {
78 if err != nil {
79 log.Fatalf(msg, args...)
80 }
81}
82
David K. Bainbridge52f29542016-07-27 22:28:15 -070083func (c *Config) getProvisionedState(rec AddressRec) (*StatusMsg, error) {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070084 log.Debugf("Fetching provisioned state of device '%s' (%s, %s)",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070085 rec.Name, rec.IP, rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070086 resp, err := http.Get(c.ProvisionURL + rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070087 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070088 log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070089 rec.Name, rec.IP, rec.MAC, err)
David K. Bainbridge52f29542016-07-27 22:28:15 -070090 return nil, err
David K. Bainbridge97ee8052016-06-14 00:52:07 -070091 }
92 if resp.StatusCode != 404 && int(resp.StatusCode/100) != 2 {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070093 log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -070094 rec.Name, rec.IP, rec.MAC, resp.Status)
David K. Bainbridge52f29542016-07-27 22:28:15 -070095 return nil, fmt.Errorf(resp.Status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -070096 }
97 defer resp.Body.Close()
98 if resp.StatusCode != 404 {
99 decoder := json.NewDecoder(resp.Body)
David K. Bainbridge52f29542016-07-27 22:28:15 -0700100 var status StatusMsg
101 err = decoder.Decode(&status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700102 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700103 log.Errorf("Unmarshal provisioning service response for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700104 rec.Name, rec.IP, rec.MAC, err)
David K. Bainbridge52f29542016-07-27 22:28:15 -0700105 return nil, err
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700106 }
David K. Bainbridge52f29542016-07-27 22:28:15 -0700107 return &status, nil
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700108 }
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700109
110 // If we end up here that means that no record was found in the provisioning, so return
111 // a status of -1, w/o an error
David K. Bainbridge52f29542016-07-27 22:28:15 -0700112 return nil, nil
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700113}
114
115func (c *Config) provision(rec AddressRec) error {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700116 log.Infof("POSTing to '%s' for provisioning of '%s (%s)'", c.ProvisionURL, rec.Name, rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700117 data := map[string]string{
118 "id": rec.MAC,
119 "name": rec.Name,
120 "ip": rec.IP,
121 "mac": rec.MAC,
122 }
123 if c.RoleSelectorURL != "" {
124 data["role_selector"] = c.RoleSelectorURL
125 }
126 if c.DefaultRole != "" {
127 data["role"] = c.DefaultRole
128 }
129 if c.Script != "" {
130 data["script"] = c.Script
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700131 }
132
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700133 hc := http.Client{}
134 var b []byte
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700135 b, err := json.Marshal(data)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700136 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700137 log.Errorf("Unable to marshal provisioning data : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700138 return err
139 }
140 req, err := http.NewRequest("POST", c.ProvisionURL, bytes.NewReader(b))
141 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700142 log.Errorf("Unable to construct POST request to provisioner : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700143 return err
144 }
145
146 req.Header.Add("Content-Type", "application/json")
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700147 resp, err := hc.Do(req)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700148 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700149 log.Errorf("Unable to POST request to provisioner : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700150 return err
151 }
152
153 defer resp.Body.Close()
154 if resp.StatusCode != http.StatusAccepted {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700155 log.Errorf("Provisioning request not accepted by provisioner : %s", resp.Status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700156 return err
157 }
158
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700159 return nil
160}
161
162func (c *Config) processRecord(rec AddressRec) error {
163 ok, err := c.vendors.Switchq(rec.MAC)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700164 if err != nil {
165 return fmt.Errorf("unable to determine ventor of MAC '%s' (%s)", rec.MAC, err)
166 }
167
168 if !ok {
169 // Not something we care about
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700170 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 -0700171 rec.IP, rec.MAC, rec.Name)
172 return nil
173 }
174
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700175 // Verify if the provision status of the node is complete, if in an error state then TTL means
176 // nothing
David K. Bainbridge52f29542016-07-27 22:28:15 -0700177 state, err := c.getProvisionedState(rec)
178 if state != nil {
179 switch state.Status {
180 case Pending, Running: // Pending or Running
181 log.Debugf("device '%s' (%s, %s) is being provisioned",
182 rec.Name, rec.IP, rec.MAC)
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700183 return nil
David K. Bainbridge52f29542016-07-27 22:28:15 -0700184 case Complete: // Complete
185 log.Debugf("device '%s' (%s, %s) has completed provisioning",
186 rec.Name, rec.IP, rec.MAC)
187 case Failed: // Failed
188 log.Debugf("device '%s' (%s, %s) failed last provisioning with message '%s', reattempt",
189 rec.Name, rec.IP, rec.MAC, state.Message)
David K. Bainbridge98bbc042016-08-22 17:35:28 -0700190 state = nil
David K. Bainbridge52f29542016-07-27 22:28:15 -0700191 default: // Unknown state
192 log.Debugf("device '%s' (%s, %s) has unknown provisioning state '%d', will provision",
193 rec.Name, rec.IP, rec.MAC, state.Status)
David K. Bainbridge98bbc042016-08-22 17:35:28 -0700194 state = nil
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700195 }
David K. Bainbridge52f29542016-07-27 22:28:15 -0700196 } else {
197 log.Debugf("device '%s' (%s, %s) has no provisioning record",
198 rec.Name, rec.IP, rec.MAC)
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700199 }
200
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700201 // If TTL is 0 then we will only provision a switch once.
David K. Bainbridge52f29542016-07-27 22:28:15 -0700202 if state == nil || (c.ttl > 0 && time.Since(time.Unix(state.Timestamp, 0)) > c.ttl) {
203 if state != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700204 log.Debugf("device '%s' (%s, %s) TTL expired, reprovisioning",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700205 rec.Name, rec.IP, rec.MAC)
206 }
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700207 c.provision(rec)
208 } else if c.ttl == 0 {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700209 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 -0700210 rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
211 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700212 log.Debugf("device '%s' (%s, %s) has completed provisioning within the specified TTL of %s",
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700213 rec.Name, rec.IP, rec.MAC, c.ProvisionTTL)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700214 }
215 return nil
216}
217
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700218var log = logrus.New()
219
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700220func main() {
221
222 var err error
223 config := Config{}
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700224 err = envconfig.Process("SWITCHQ", &config)
225 if err != nil {
226 log.Fatalf("Unable to parse configuration options : %s", err)
227 }
228
229 switch config.LogFormat {
230 case "json":
231 log.Formatter = &logrus.JSONFormatter{}
232 default:
233 log.Formatter = &logrus.TextFormatter{
234 FullTimestamp: true,
235 ForceColors: true,
236 }
237 }
238
239 level, err := logrus.ParseLevel(config.LogLevel)
240 if err != nil {
241 level = logrus.WarnLevel
242 }
243 log.Level = level
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700244
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700245 config.vendors, err = NewVendors(config.VendorsURL)
246 checkError(err, "Unable to create known vendors list from specified URL '%s' : %s", config.VendorsURL, err)
247
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700248 config.addressSource, err = NewAddressSource(config.AddressURL)
249 checkError(err, "Unable to create required address source for specified URL '%s' : %s", config.AddressURL, err)
250
251 config.interval, err = time.ParseDuration(config.PollInterval)
252 checkError(err, "Unable to parse specified poll interface '%s' : %s", config.PollInterval, err)
253
254 config.ttl, err = time.ParseDuration(config.ProvisionTTL)
255 checkError(err, "Unable to parse specified provision TTL value of '%s' : %s", config.ProvisionTTL, err)
256
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700257 log.Infof(`Configuration:
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700258 Vendors URL: %s
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700259 Poll Interval: %s
260 Address Source: %s
261 Provision TTL: %s
262 Provision URL: %s
263 Role Selector URL: %s
264 Default Role: %s
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700265 Script: %s
266 Log Level: %s
267 Log Format: %s`,
David K. Bainbridge52f29542016-07-27 22:28:15 -0700268 config.VendorsURL, config.PollInterval, config.AddressURL, config.ProvisionTTL,
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700269 config.ProvisionURL, config.RoleSelectorURL, config.DefaultRole, config.Script,
270 config.LogLevel, config.LogFormat)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700271
272 // We use two methods to attempt to find the MAC (hardware) address associated with an IP. The first
273 // is to look in the table. The second is to send an ARP packet.
274 for {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700275 log.Infof("Checking for switches @ %s", time.Now())
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700276 addresses, err := config.addressSource.GetAddresses()
277
278 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700279 log.Errorf("unable to read addresses from address source : %s", err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700280 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700281 log.Infof("Queried %d addresses from address source", len(addresses))
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700282
283 for _, rec := range addresses {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700284 log.Debugf("Processing %s(%s, %s)", rec.Name, rec.IP, rec.MAC)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700285 if err := config.processRecord(rec); err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700286 log.Errorf("Error when processing IP '%s' : %s", rec.IP, err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700287 }
288 }
289 }
290
291 time.Sleep(config.interval)
292 }
293}