blob: 4be1c3153dd2995e62b17366db146963cda297fc [file] [log] [blame]
Brian O'Connor6a37ea92017-08-03 22:45:59 -07001// Copyright 2016 Open Networking Foundation
David K. Bainbridgedf9df632016-07-07 18:47:46 -07002//
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. Bainbridge528b3182017-01-23 08:51:59 -080019 "flag"
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070020 "fmt"
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -070021 "github.com/Sirupsen/logrus"
David K. Bainbridge3569d622016-09-16 08:40:54 -070022 "github.com/gorilla/mux"
23 maas "github.com/juju/gomaasapi"
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070024 "github.com/kelseyhightower/envconfig"
David K. Bainbridge11850cb2016-10-28 14:05:59 -070025 "io/ioutil"
David K. Bainbridge97ee8052016-06-14 00:52:07 -070026 "net/http"
David K. Bainbridge528b3182017-01-23 08:51:59 -080027 "os"
David K. Bainbridge11850cb2016-10-28 14:05:59 -070028 "regexp"
David K. Bainbridge3569d622016-09-16 08:40:54 -070029 "sync"
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070030 "time"
31)
32
Cem Turkerf203f932018-02-21 23:42:32 +000033const(
34 appName = "SWITCHQ"
35 massApiVersion = "2.0"
36)
David K. Bainbridge528b3182017-01-23 08:51:59 -080037
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070038type Config struct {
David K. Bainbridge528b3182017-01-23 08:51:59 -080039 VendorsURL string `default:"file:///switchq/vendors.json" envconfig:"VENDORS_URL" desc:"URL that specifies supported vendor OUI information"`
40 AddressURL string `default:"file:///switchq/dhcp_harvest.inc" envconfig:"ADDRESS_URL" desc:"URL of service or file from which to query IP information"`
41 PollInterval string `default:"1m" envconfig:"POLL_INTERVAL" desc:"how often IP information should be queried and processed"`
Jonathan Hart1728fc82017-08-22 12:47:10 -070042 ProvisionTTL string `default:"1h" envconfig:"PROVISION_TTL" desc:"duration to wait for a provisioning request before considering it failed"`
David K. Bainbridge528b3182017-01-23 08:51:59 -080043 ProvisionURL string `default:"" envconfig:"PROVISION_URL" desc:"URL of provisioning service"`
44 RoleSelectorURL string `default:"" envconfig:"ROLE_SELECTOR_URL" desc:"URL of service to query for switch role"`
45 DefaultRole string `default:"fabric-switch" envconfig:"DEFAULT_ROLE" desc:"default switch role"`
Jonathan Hart1728fc82017-08-22 12:47:10 -070046 Script string `default:"do-ansible" desc:"script to run for provisioner"`
David K. Bainbridge528b3182017-01-23 08:51:59 -080047 LogLevel string `default:"warning" envconfig:"LOG_LEVEL" desc:"detail level for logging"`
48 LogFormat string `default:"text" envconfig:"LOG_FORMAT" desc:"output format for logging, text or json"`
49 Listen string `default:"" desc:"IP on which to listen for requests"`
50 Port int `default:"4244" desc:"port on which to listen for requests"`
51 MaasURL string `default:"http://localhost/MAAS" envconfig:"MAAS_URL" desc:"connection string for MAAS"`
52 MaasKey string `default:"" envconfig:"MAAS_API_KEY" desc:"API key for MAAS"`
53 ShowApiKey bool `default:"false" envconfig:"MAAS_SHOW_API_KEY" desc:"display API key in log"`
54 ApiKeyFile string `default:"/secrets/maas_api_key" envconfig:"MAAS_API_KEY_FILE" desc:"file from which to read API key"`
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070055
David K. Bainbridge97ee8052016-06-14 00:52:07 -070056 vendors Vendors
David K. Bainbridgef694f5a2016-06-10 16:21:27 -070057 addressSource AddressSource
58 interval time.Duration
59 ttl time.Duration
60}
61
David K. Bainbridge52f29542016-07-27 22:28:15 -070062const (
63 Pending TaskStatus = iota
64 Running
65 Complete
66 Failed
67)
68
69type RequestInfo struct {
70 Id string `json:"id"`
71 Name string `json:"name"`
72 Ip string `json:"ip"`
73 Mac string `json:"mac"`
74 RoleSelector string `json:"role_selector"`
75 Role string `json:"role"`
76 Script string `json:"script"`
77}
78
79type TaskStatus uint8
80
81type WorkRequest struct {
82 Info *RequestInfo
83 Script string
84 Role string
85}
86
87type StatusMsg struct {
88 Request *WorkRequest `json:"request"`
89 Worker int `json:"worker"`
90 Status TaskStatus `json:"status"`
91 Message string `json:"message"`
92 Timestamp int64 `json:"timestamp"`
93}
94
David K. Bainbridge3569d622016-09-16 08:40:54 -070095type AppContext struct {
96 config Config
97
98 maasClient *maas.MAASObject
99 pushChan chan []AddressRec
100 mutex sync.RWMutex
101 nextList []AddressRec
102 publishList []AddressRec
103}
104
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700105func checkError(err error, msg string, args ...interface{}) {
106 if err != nil {
107 log.Fatalf(msg, args...)
108 }
109}
110
David K. Bainbridge3569d622016-09-16 08:40:54 -0700111func (c *AppContext) getProvisionedState(rec AddressRec) (*StatusMsg, error) {
112 if len(c.config.ProvisionURL) == 0 {
113 log.Warnf("Unable to fetch provisioning state of device '%s' (%s, %s) as no URL for the provisioner was specified",
114 rec.Name, rec.IP, rec.MAC)
115 return nil, fmt.Errorf("No URL for provisioner specified")
116 }
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700117 log.Debugf("Fetching provisioned state of device '%s' (%s, %s)",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700118 rec.Name, rec.IP, rec.MAC)
David K. Bainbridge3569d622016-09-16 08:40:54 -0700119 resp, err := http.Get(c.config.ProvisionURL + rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700120 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700121 log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700122 rec.Name, rec.IP, rec.MAC, err)
David K. Bainbridge52f29542016-07-27 22:28:15 -0700123 return nil, err
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700124 }
125 if resp.StatusCode != 404 && int(resp.StatusCode/100) != 2 {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700126 log.Errorf("Error while retrieving provisioning state for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700127 rec.Name, rec.IP, rec.MAC, resp.Status)
David K. Bainbridge52f29542016-07-27 22:28:15 -0700128 return nil, fmt.Errorf(resp.Status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700129 }
130 defer resp.Body.Close()
131 if resp.StatusCode != 404 {
132 decoder := json.NewDecoder(resp.Body)
David K. Bainbridge52f29542016-07-27 22:28:15 -0700133 var status StatusMsg
134 err = decoder.Decode(&status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700135 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700136 log.Errorf("Unmarshal provisioning service response for device '%s (%s, %s)' : %s",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700137 rec.Name, rec.IP, rec.MAC, err)
David K. Bainbridge52f29542016-07-27 22:28:15 -0700138 return nil, err
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700139 }
David K. Bainbridge52f29542016-07-27 22:28:15 -0700140 return &status, nil
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700141 }
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700142
143 // If we end up here that means that no record was found in the provisioning, so return
144 // a status of -1, w/o an error
David K. Bainbridge52f29542016-07-27 22:28:15 -0700145 return nil, nil
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700146}
147
David K. Bainbridge3569d622016-09-16 08:40:54 -0700148func (c *AppContext) provision(rec AddressRec) error {
149 if len(c.config.ProvisionURL) == 0 {
150 log.Warnf("Unable to POST to provisioner for device '%s' (%s, %s) as no URL for the provisioner was specified",
151 rec.Name, rec.IP, rec.MAC)
152 return fmt.Errorf("No URL for provisioner specified")
153 }
154 log.Infof("POSTing to '%s' for provisioning of '%s (%s)'", c.config.ProvisionURL, rec.Name, rec.MAC)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700155 data := map[string]string{
156 "id": rec.MAC,
157 "name": rec.Name,
158 "ip": rec.IP,
159 "mac": rec.MAC,
160 }
David K. Bainbridge3569d622016-09-16 08:40:54 -0700161 if c.config.RoleSelectorURL != "" {
162 data["role_selector"] = c.config.RoleSelectorURL
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700163 }
David K. Bainbridge3569d622016-09-16 08:40:54 -0700164 if c.config.DefaultRole != "" {
165 data["role"] = c.config.DefaultRole
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700166 }
David K. Bainbridge3569d622016-09-16 08:40:54 -0700167 if c.config.Script != "" {
168 data["script"] = c.config.Script
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700169 }
170
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700171 hc := http.Client{}
172 var b []byte
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700173 b, err := json.Marshal(data)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700174 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700175 log.Errorf("Unable to marshal provisioning data : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700176 return err
177 }
David K. Bainbridge3569d622016-09-16 08:40:54 -0700178 req, err := http.NewRequest("POST", c.config.ProvisionURL, bytes.NewReader(b))
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700179 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700180 log.Errorf("Unable to construct POST request to provisioner : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700181 return err
182 }
183
184 req.Header.Add("Content-Type", "application/json")
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700185 resp, err := hc.Do(req)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700186 if err != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700187 log.Errorf("Unable to POST request to provisioner : %s", err)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700188 return err
189 }
190
191 defer resp.Body.Close()
192 if resp.StatusCode != http.StatusAccepted {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700193 log.Errorf("Provisioning request not accepted by provisioner : %s", resp.Status)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700194 return err
195 }
196
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700197 return nil
198}
199
David K. Bainbridge3569d622016-09-16 08:40:54 -0700200func (c *AppContext) processRecord(rec AddressRec) error {
201 ok, err := c.config.vendors.Switchq(rec.MAC)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700202 if err != nil {
203 return fmt.Errorf("unable to determine ventor of MAC '%s' (%s)", rec.MAC, err)
204 }
205
206 if !ok {
207 // Not something we care about
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700208 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 -0700209 rec.IP, rec.MAC, rec.Name)
210 return nil
211 }
212
David K. Bainbridge3569d622016-09-16 08:40:54 -0700213 // Add this IP information to our list of known switches
214 c.nextList = append(c.nextList, rec)
215
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700216 // Verify if the provision status of the node is complete, if in an error state then TTL means
217 // nothing
David K. Bainbridge52f29542016-07-27 22:28:15 -0700218 state, err := c.getProvisionedState(rec)
219 if state != nil {
220 switch state.Status {
221 case Pending, Running: // Pending or Running
222 log.Debugf("device '%s' (%s, %s) is being provisioned",
223 rec.Name, rec.IP, rec.MAC)
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700224 return nil
David K. Bainbridge52f29542016-07-27 22:28:15 -0700225 case Complete: // Complete
226 log.Debugf("device '%s' (%s, %s) has completed provisioning",
227 rec.Name, rec.IP, rec.MAC)
228 case Failed: // Failed
229 log.Debugf("device '%s' (%s, %s) failed last provisioning with message '%s', reattempt",
230 rec.Name, rec.IP, rec.MAC, state.Message)
David K. Bainbridge98bbc042016-08-22 17:35:28 -0700231 state = nil
David K. Bainbridge52f29542016-07-27 22:28:15 -0700232 default: // Unknown state
233 log.Debugf("device '%s' (%s, %s) has unknown provisioning state '%d', will provision",
234 rec.Name, rec.IP, rec.MAC, state.Status)
David K. Bainbridge98bbc042016-08-22 17:35:28 -0700235 state = nil
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700236 }
David K. Bainbridge52f29542016-07-27 22:28:15 -0700237 } else {
238 log.Debugf("device '%s' (%s, %s) has no provisioning record",
239 rec.Name, rec.IP, rec.MAC)
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700240 }
241
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700242 // If TTL is 0 then we will only provision a switch once.
David K. Bainbridge3569d622016-09-16 08:40:54 -0700243 if state == nil || (c.config.ttl > 0 && time.Since(time.Unix(state.Timestamp, 0)) > c.config.ttl) {
David K. Bainbridge52f29542016-07-27 22:28:15 -0700244 if state != nil {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700245 log.Debugf("device '%s' (%s, %s) TTL expired, reprovisioning",
David K. Bainbridgec809ef72016-06-22 21:18:07 -0700246 rec.Name, rec.IP, rec.MAC)
247 }
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700248 c.provision(rec)
David K. Bainbridge3569d622016-09-16 08:40:54 -0700249 } else if c.config.ttl == 0 {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700250 log.Debugf("device '%s' (%s, %s) has completed its one time provisioning, with a TTL set to %s",
David K. Bainbridge3569d622016-09-16 08:40:54 -0700251 rec.Name, rec.IP, rec.MAC, c.config.ProvisionTTL)
David K. Bainbridge97ee8052016-06-14 00:52:07 -0700252 } else {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700253 log.Debugf("device '%s' (%s, %s) has completed provisioning within the specified TTL of %s",
David K. Bainbridge3569d622016-09-16 08:40:54 -0700254 rec.Name, rec.IP, rec.MAC, c.config.ProvisionTTL)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700255 }
256 return nil
257}
258
David K. Bainbridge3569d622016-09-16 08:40:54 -0700259func (c *AppContext) processLoop() {
260 // We use two methods to attempt to find the MAC (hardware) address associated with an IP. The first
261 // is to look in the table. The second is to send an ARP packet.
262 for {
263 log.Infof("Checking for switches @ %s", time.Now())
264 addresses, err := c.config.addressSource.GetAddresses()
265
266 if err != nil {
267 log.Errorf("unable to read addresses from address source : %s", err)
268 } else {
269 log.Infof("Queried %d addresses from address source", len(addresses))
270
271 c.nextList = make([]AddressRec, 0, len(addresses))
272 for _, rec := range addresses {
273 log.Debugf("Processing %s(%s, %s)", rec.Name, rec.IP, rec.MAC)
274 if err := c.processRecord(rec); err != nil {
275 log.Errorf("Error when processing IP '%s' : %s", rec.IP, err)
276 }
277 }
278 c.mutex.Lock()
279 c.publishList = c.nextList
280 c.nextList = nil
281 c.mutex.Unlock()
282 c.pushChan <- c.publishList
283 }
284
285 time.Sleep(c.config.interval)
286 }
287}
288
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700289var log = logrus.New()
David K. Bainbridge528b3182017-01-23 08:51:59 -0800290var appFlags = flag.NewFlagSet("", flag.ContinueOnError)
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700291
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700292func main() {
293
294 var err error
David K. Bainbridge3569d622016-09-16 08:40:54 -0700295 context := &AppContext{}
David K. Bainbridge528b3182017-01-23 08:51:59 -0800296
297 appFlags.Usage = func() {
298 envconfig.Usage(appName, &(context.config))
299 }
300 if err := appFlags.Parse(os.Args[1:]); err != nil {
301 if err != flag.ErrHelp {
302 os.Exit(1)
303 } else {
304 return
305 }
306 }
307 err = envconfig.Process(appName, &context.config)
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700308 if err != nil {
309 log.Fatalf("Unable to parse configuration options : %s", err)
310 }
311
David K. Bainbridge3569d622016-09-16 08:40:54 -0700312 switch context.config.LogFormat {
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700313 case "json":
314 log.Formatter = &logrus.JSONFormatter{}
315 default:
316 log.Formatter = &logrus.TextFormatter{
317 FullTimestamp: true,
318 ForceColors: true,
319 }
320 }
321
David K. Bainbridge3569d622016-09-16 08:40:54 -0700322 level, err := logrus.ParseLevel(context.config.LogLevel)
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700323 if err != nil {
324 level = logrus.WarnLevel
325 }
326 log.Level = level
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700327
David K. Bainbridge11850cb2016-10-28 14:05:59 -0700328 re := regexp.MustCompile("[^:]")
329 pubKey := context.config.MaasKey
330 if !context.config.ShowApiKey {
331 pubKey = re.ReplaceAllString(context.config.MaasKey, "X")
332 }
333
David K. Bainbridgea9c2e0a2016-07-01 18:33:50 -0700334 log.Infof(`Configuration:
David K. Bainbridge11850cb2016-10-28 14:05:59 -0700335 VENDORS_URL: %s
336 POLL_INTERVAL: %s
337 ADDRESS_URL: %s
338 PROVISION_TTL: %s
339 PROVISION_URL: %s
340 ROLE_SELECTOR_URL: %s
341 DEFAULT_ROLE: %s
342 SCRIPT: %s
343 LISTEN: %s
344 PORT: %d
345 MAAS_URL: %s
346 MAAS_SHOW_API_KEY %t
347 MAAS_API_KEY: %s
348 MAAS_API_KEY_FILE: %s
349 LOG_LEVEL: %s
350 LOG_FORMAT: %s`,
David K. Bainbridge3569d622016-09-16 08:40:54 -0700351 context.config.VendorsURL, context.config.PollInterval, context.config.AddressURL, context.config.ProvisionTTL,
352 context.config.ProvisionURL, context.config.RoleSelectorURL, context.config.DefaultRole, context.config.Script,
David K. Bainbridge11850cb2016-10-28 14:05:59 -0700353 context.config.Listen, context.config.Port, context.config.MaasURL, context.config.ShowApiKey, pubKey,
354 context.config.ApiKeyFile, context.config.LogLevel, context.config.LogFormat)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700355
David K. Bainbridge3569d622016-09-16 08:40:54 -0700356 context.config.vendors, err = NewVendors(context.config.VendorsURL)
357 checkError(err, "Unable to create known vendors list from specified URL '%s' : %s", context.config.VendorsURL, err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700358
David K. Bainbridge3569d622016-09-16 08:40:54 -0700359 context.config.addressSource, err = NewAddressSource(context.config.AddressURL)
360 checkError(err, "Unable to create required address source for specified URL '%s' : %s", context.config.AddressURL, err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700361
David K. Bainbridge3569d622016-09-16 08:40:54 -0700362 context.config.interval, err = time.ParseDuration(context.config.PollInterval)
363 checkError(err, "Unable to parse specified poll interface '%s' : %s", context.config.PollInterval, err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700364
David K. Bainbridge3569d622016-09-16 08:40:54 -0700365 context.config.ttl, err = time.ParseDuration(context.config.ProvisionTTL)
366 checkError(err, "Unable to parse specified provision TTL value of '%s' : %s", context.config.ProvisionTTL, err)
367
David K. Bainbridge11850cb2016-10-28 14:05:59 -0700368 // Attempt to load the API key from a file if it was not set via the environment
369 // and if the file exists
370 if context.config.MaasKey == "" {
371 log.Debugf("Attempting to read MAAS API key from file '%s', because it was not set via environment", context.config.ApiKeyFile)
372 keyBytes, err := ioutil.ReadFile(context.config.ApiKeyFile)
373 if err != nil {
374 log.Warnf("Failed to read MAAS API key from file '%s', was the file mounted as a volume? : %s ",
375 context.config.ApiKeyFile, err)
376 } else {
377 context.config.MaasKey = string(keyBytes)
378 if context.config.ShowApiKey {
379 pubKey = context.config.MaasKey
380 } else {
381 pubKey = re.ReplaceAllString(context.config.MaasKey, "X")
382 }
383 }
384 }
385
David K. Bainbridge3569d622016-09-16 08:40:54 -0700386 if len(context.config.MaasURL) > 0 {
387
388 // Attempt to connect to MAAS
Cem Turkerf203f932018-02-21 23:42:32 +0000389 authClient, err := maas.NewAuthenticatedClient(context.config.MaasURL, context.config.MaasKey, massApiVersion)
David K. Bainbridge3569d622016-09-16 08:40:54 -0700390 checkError(err, "Unable to connect to MAAS at '%s' : %s", context.config.MaasURL, err)
391
392 context.maasClient = maas.NewMAAS(*authClient)
393 }
394
395 context.pushChan = make(chan []AddressRec, 1)
396
397 go context.processLoop()
398 go context.syncToMaas(context.pushChan)
399
400 router := mux.NewRouter()
401 router.HandleFunc("/switch/", context.ListSwitchesHandler).Methods("GET")
402 http.Handle("/", router)
403 log.Infof("Listening for HTTP request on '%s:%d'", context.config.Listen, context.config.Port)
404 err = http.ListenAndServe(fmt.Sprintf("%s:%d", context.config.Listen, context.config.Port), nil)
405 if err != nil {
406 checkError(err, "Error while attempting to listen to REST requests on '%s:%d' : %s",
407 context.config.Listen, context.config.Port, err)
David K. Bainbridgef694f5a2016-06-10 16:21:27 -0700408 }
409}