1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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
}
|