diff options
Diffstat (limited to 'internal/handlers/polling.go')
-rw-r--r-- | internal/handlers/polling.go | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/internal/handlers/polling.go b/internal/handlers/polling.go new file mode 100644 index 0000000..12f36e2 --- /dev/null +++ b/internal/handlers/polling.go @@ -0,0 +1,161 @@ +// Copyright 2018 ThousandEyes Inc. +// +// 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 handlers + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + "os" + + "github.com/gorilla/mux" + "github.com/thousandeyes/shoelaces/internal/log" + "github.com/thousandeyes/shoelaces/internal/polling" + "github.com/thousandeyes/shoelaces/internal/server" + "github.com/thousandeyes/shoelaces/internal/utils" +) + +// PollHandler is called by iPXE boot agents. It returns the boot script +// specified on the configuration or, if the host is unknown, it makes it +// retry for a while until the user specifies alternative IPXE boot script. +func PollHandler(w http.ResponseWriter, r *http.Request) { + env := envFromRequest(r) + + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + vars := mux.Vars(r) + // iPXE MAC addresses come with dashes instead of colons + mac := utils.MacDashToColon(vars["mac"]) + host := r.FormValue("host") + + err = validateMACAndIP(env.Logger, mac, ip) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if host == "" { + host = resolveHostname(env.Logger, ip) + } + + server := server.New(mac, ip, host) + script, err := polling.Poll( + env.Logger, env.ServerStates, env.HostnameMaps, env.NetworkMaps, + env.EventLog, env.Templates, env.BaseURL, server) + + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write([]byte(script)) +} + +// ServerListHandler provides a list of the servers that tried to boot +// but did not match the hostname regex or network mappings. +func ServerListHandler(w http.ResponseWriter, r *http.Request) { + env := envFromRequest(r) + + servers, err := json.Marshal(polling.ListServers(env.ServerStates)) + if err != nil { + env.Logger.Error("component", "handler", "err", err) + os.Exit(1) + } + + w.Header().Set("Content-Type", "application/json") + w.Write(servers) +} + +// UpdateTargetHandler is a POST endpoint that receives parameters for +// booting manually. +func UpdateTargetHandler(w http.ResponseWriter, r *http.Request) { + env := envFromRequest(r) + + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + mac, scriptName, environment, params := parsePostForm(r.PostForm) + if mac == "" || scriptName == "" { + http.Error(w, "MAC address and target must not be empty", http.StatusBadRequest) + return + } + + server := server.New(mac, ip, "") + inputErr, err := polling.UpdateTarget( + env.Logger, env.ServerStates, env.Templates, env.EventLog, env.BaseURL, server, + scriptName, environment, params) + + if err != nil { + if inputErr { + http.Error(w, err.Error(), http.StatusBadRequest) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + http.Redirect(w, r, "/", http.StatusFound) +} + +func parsePostForm(form map[string][]string) (mac, scriptName, environment string, params map[string]interface{}) { + params = make(map[string]interface{}) + for k, v := range form { + if k == "mac" { + mac = utils.MacDashToColon(v[0]) + } else if k == "target" { + scriptName = v[0] + } else if k == "environment" { + environment = v[0] + } else { + params[k] = v[0] + } + } + return +} + +func validateMACAndIP(logger log.Logger, mac string, ip string) (err error) { + if !utils.IsValidMAC(mac) { + logger.Error("component", "polling", "msg", "Invalid MAC", "mac", mac) + return fmt.Errorf("%s", "Invalid MAC") + } + + if !utils.IsValidIP(ip) { + logger.Error("component", "polling", "msg", "Invalid IP", "ip", ip) + return fmt.Errorf("%s", "Invalid IP") + } + + logger.Debug("component", "polling", "msg", "MAC and IP validated", "mac", mac, "ip", ip) + + return nil +} + +func resolveHostname(logger log.Logger, ip string) string { + host := utils.ResolveHostname(ip) + if host == "" { + logger.Info("component", "polling", "msg", "Can't resolve IP", "ip", ip) + } + + return host +} |