github.com/Cloud-Foundations/Dominator@v0.3.4/dom/herd/showSubs.go (about) 1 package herd 2 3 import ( 4 "bufio" 5 "encoding/csv" 6 "fmt" 7 "io" 8 "net/http" 9 "strings" 10 "time" 11 12 "github.com/Cloud-Foundations/Dominator/lib/constants" 13 "github.com/Cloud-Foundations/Dominator/lib/format" 14 "github.com/Cloud-Foundations/Dominator/lib/html" 15 "github.com/Cloud-Foundations/Dominator/lib/json" 16 "github.com/Cloud-Foundations/Dominator/lib/srpc" 17 "github.com/Cloud-Foundations/Dominator/lib/stringutil" 18 "github.com/Cloud-Foundations/Dominator/lib/tags/tagmatcher" 19 "github.com/Cloud-Foundations/Dominator/lib/url" 20 proto "github.com/Cloud-Foundations/Dominator/proto/dominator" 21 ) 22 23 func makeSelector(locationsToMatch []string, statusesToMatch []string, 24 tagsToMatch *tagmatcher.TagMatcher) func(sub *Sub) bool { 25 if len(locationsToMatch) < 1 && 26 len(statusesToMatch) < 1 && 27 tagsToMatch == nil { 28 return selectAll 29 } 30 locationsToMatchMap := stringutil.ConvertListToMap(locationsToMatch, false) 31 statusesToMatchMap := stringutil.ConvertListToMap(statusesToMatch, false) 32 return func(sub *Sub) bool { 33 if len(locationsToMatch) > 0 { 34 subLocationLength := len(sub.mdb.Location) 35 if subLocationLength < 1 { 36 return false 37 } 38 _, matched := locationsToMatchMap[sub.mdb.Location] 39 if !matched { 40 for _, locationToMatch := range locationsToMatch { 41 index := len(locationToMatch) 42 if index < subLocationLength && 43 sub.mdb.Location[index] == '/' && 44 strings.HasPrefix(sub.mdb.Location, locationToMatch) { 45 matched = true 46 break 47 } 48 } 49 } 50 if !matched { 51 return false 52 } 53 } 54 if len(statusesToMatch) > 0 { 55 if _, ok := statusesToMatchMap[sub.status.String()]; !ok { 56 return false 57 } 58 } 59 if !tagsToMatch.MatchEach(sub.mdb.Tags) { 60 return false 61 } 62 return true 63 } 64 } 65 66 func makeUrlQuerySelector(queryValues map[string][]string) func(sub *Sub) bool { 67 if len(queryValues) < 1 { 68 return selectAll 69 } 70 tagsToMatch := make(map[string][]string) 71 for _, queryTag := range queryValues["tag"] { 72 split := strings.Split(queryTag, "=") 73 if len(split) != 2 { 74 continue 75 } 76 key := split[0] 77 value := split[1] 78 tagsToMatch[key] = append(tagsToMatch[key], value) 79 } 80 return makeSelector(queryValues["location"], queryValues["status"], 81 tagmatcher.New(tagsToMatch, false)) 82 } 83 84 func selectAll(sub *Sub) bool { 85 return true 86 } 87 88 func (herd *Herd) makeShowSubsHandler(selectFunc func(*Sub) bool, 89 subType string) func(http.ResponseWriter, *http.Request) { 90 return func(writer http.ResponseWriter, req *http.Request) { 91 herd.showSubsHandler(writer, req, selectFunc, subType) 92 } 93 } 94 95 func (herd *Herd) showReachableSubsHandler(writer http.ResponseWriter, 96 req *http.Request) { 97 selectFunc, _ := herd.getReachableSelector(url.ParseQuery(req.URL)) 98 herd.showSubsHandler(writer, req, selectFunc, "reachable ") 99 } 100 101 func (herd *Herd) showUnreachableSubsHandler(writer http.ResponseWriter, 102 req *http.Request) { 103 selectFunc, _ := herd.getUnreachableSelector(url.ParseQuery(req.URL)) 104 herd.showSubsHandler(writer, req, selectFunc, "unreachable ") 105 } 106 107 func (herd *Herd) showSubsCSV(writer io.Writer, 108 selectFunc func(*Sub) bool) { 109 subs := herd.getSelectedSubs(selectFunc) 110 w := csv.NewWriter(writer) 111 defer w.Flush() 112 w.Write([]string{ 113 "Hostname", 114 "Required Image", 115 "Planned Image", 116 "Status", 117 "Last Image Update", 118 "Last Note", 119 }) 120 for _, sub := range subs { 121 w.Write([]string{ 122 sub.mdb.Hostname, 123 sub.mdb.RequiredImage, 124 sub.mdb.PlannedImage, 125 sub.publishedStatus.String(), 126 sub.lastSuccessfulImageName, 127 sub.lastNote, 128 }) 129 } 130 } 131 132 func (herd *Herd) showSubsHandler(rWriter http.ResponseWriter, 133 req *http.Request, _selectFunc func(*Sub) bool, 134 subType string) { 135 querySelectFunc := makeUrlQuerySelector(req.URL.Query()) 136 selectFunc := func(sub *Sub) bool { 137 return _selectFunc(sub) && querySelectFunc(sub) 138 } 139 writer := bufio.NewWriter(rWriter) 140 defer writer.Flush() 141 parsedQuery := url.ParseQuery(req.URL) 142 switch parsedQuery.OutputType() { 143 case url.OutputTypeCsv: 144 herd.showSubsCSV(writer, selectFunc) 145 case url.OutputTypeHtml: 146 herd.showSubsHTML(writer, selectFunc, subType) 147 case url.OutputTypeJson: 148 herd.showSubsJSON(writer, selectFunc) 149 case url.OutputTypeText: 150 fmt.Fprintln(writer, "Text output not supported") 151 default: 152 fmt.Fprintln(writer, "Unknown output type") 153 } 154 } 155 156 func (herd *Herd) showSubsHTML(writer *bufio.Writer, selectFunc func(*Sub) bool, 157 subType string) { 158 bd, _ := html.CreateBenchmarkData() 159 defer fmt.Fprintln(writer, "</body>") 160 fmt.Fprintf(writer, "<title>Dominator %s subs</title>", subType) 161 fmt.Fprintln(writer, `<style> 162 table, th, td { 163 border-collapse: collapse; 164 } 165 </style>`) 166 if srpc.CheckTlsRequired() { 167 fmt.Fprintln(writer, "<body>") 168 } else { 169 fmt.Fprintln(writer, "<body bgcolor=\"#ffb0b0\">") 170 fmt.Fprintln(writer, 171 `<h1><center><font color="red">Running in insecure mode. You can get pwned!!!</center></font></h1>`) 172 } 173 if herd.updatesDisabledReason != "" { 174 fmt.Fprintf(writer, "<center>") 175 herd.writeDisableStatus(writer) 176 fmt.Fprintln(writer, "</center>") 177 } 178 fmt.Fprintln(writer, `<table border="1" style="width:100%">`) 179 tw, _ := html.NewTableWriter(writer, true, "Name", "Required Image", 180 "Planned Image", "Busy", "Status", "Uptime", "Last Scan Duration", 181 "Staleness", "Last Update", "Last Sync", "Connect", "Short Poll", 182 "Full Poll", "Update Compute") 183 subs := herd.getSelectedSubs(selectFunc) 184 for _, sub := range subs { 185 showSub(tw, sub) 186 } 187 tw.Close() 188 bd.Write(writer) 189 } 190 191 func (herd *Herd) showSubsJSON(writer io.Writer, 192 selectFunc func(*Sub) bool) { 193 subs := herd.getSelectedSubs(selectFunc) 194 output := make([]proto.SubInfo, 0, len(subs)) 195 for _, sub := range subs { 196 output = append(output, sub.makeInfo()) 197 } 198 json.WriteWithIndent(writer, " ", output) 199 } 200 201 func (sub *Sub) makeInfo() proto.SubInfo { 202 return proto.SubInfo{ 203 Machine: sub.mdb, 204 LastDisruptionState: sub.lastDisruptionState, 205 LastNote: sub.lastNote, 206 LastScanDuration: sub.lastScanDuration, 207 LastSuccessfulImage: sub.lastSuccessfulImageName, 208 LastSyncTime: sub.lastSyncTime, 209 LastUpdateTime: sub.lastUpdateTime, 210 StartTime: sub.startTime, 211 Status: sub.publishedStatus.String(), 212 SystemUptime: sub.systemUptime, 213 } 214 } 215 216 func showSub(tw *html.TableWriter, sub *Sub) { 217 var background string 218 if sub.isInsecure { 219 background = "yellow" 220 } 221 tw.OpenRow("", background) 222 defer tw.CloseRow() 223 subURL := fmt.Sprintf("http://%s:%d/", 224 strings.SplitN(sub.String(), "*", 2)[0], constants.SubPortNumber) 225 timeNow := time.Now() 226 tw.WriteData("", fmt.Sprintf("<a href=\"%s\">%s</a>", subURL, sub)) 227 sub.herd.showImage(tw, sub.mdb.RequiredImage, true) 228 sub.herd.showImage(tw, sub.mdb.PlannedImage, false) 229 sub.showBusy(tw) 230 tw.WriteData("", 231 fmt.Sprintf("<a href=\"showSub?%s\">%s</a>", 232 sub.mdb.Hostname, sub.publishedStatus.html())) 233 showSince(tw, sub.pollTime, sub.startTime) 234 showDuration(tw, sub.lastScanDuration, false) 235 showSince(tw, timeNow, sub.lastPollSucceededTime) 236 showSince(tw, timeNow, sub.lastUpdateTime) 237 showSince(tw, timeNow, sub.lastSyncTime) 238 showDuration(tw, sub.lastConnectDuration, false) 239 showDuration(tw, sub.lastShortPollDuration, !sub.lastPollWasFull) 240 showDuration(tw, sub.lastFullPollDuration, sub.lastPollWasFull) 241 showDuration(tw, sub.lastComputeUpdateCpuDuration, false) 242 } 243 244 func (herd *Herd) showImage(tw *html.TableWriter, name string, 245 showDefault bool) error { 246 if name == "" { 247 if showDefault && herd.defaultImageName != "" { 248 return tw.WriteData("", fmt.Sprintf( 249 "<a style=\"color: #CCCC00\" href=\"http://%s/showImage?%s\">%s</a>", 250 herd.imageManager, herd.defaultImageName, 251 herd.defaultImageName)) 252 } else { 253 return tw.WriteData("", "") 254 } 255 } else if image, err := herd.imageManager.Get(name, false); err != nil { 256 return tw.WriteData("red", err.Error()) 257 } else if image != nil { 258 return tw.WriteData("", 259 fmt.Sprintf("<a href=\"http://%s/showImage?%s\">%s</a>", 260 herd.imageManager, name, name)) 261 } else { 262 return tw.WriteData("grey", name) 263 } 264 } 265 266 func (herd *Herd) showSubHandler(writer http.ResponseWriter, 267 req *http.Request) { 268 w := bufio.NewWriter(writer) 269 defer w.Flush() 270 subName := strings.Split(req.URL.RawQuery, "&")[0] 271 parsedQuery := url.ParseQuery(req.URL) 272 sub := herd.getSub(subName) 273 if sub == nil { 274 http.NotFound(writer, req) 275 return 276 } 277 if parsedQuery.OutputType() == url.OutputTypeJson { 278 json.WriteWithIndent(w, " ", sub.makeInfo()) 279 return 280 } 281 fmt.Fprintf(w, "<title>sub %s</title>", subName) 282 if srpc.CheckTlsRequired() { 283 fmt.Fprintln(w, "<body>") 284 } else { 285 fmt.Fprintln(w, "<body bgcolor=\"#ffb0b0\">") 286 fmt.Fprintln(w, 287 `<h1><center><font color="red">Running in insecure mode. You can get pwned!!!</center></font></h1>`) 288 } 289 if herd.updatesDisabledReason != "" { 290 fmt.Fprintf(w, "<center>") 291 herd.writeDisableStatus(w) 292 fmt.Fprintln(w, "</center>") 293 } 294 fmt.Fprintln(w, "<h3>") 295 timeNow := time.Now() 296 subURL := fmt.Sprintf("http://%s:%d/", 297 strings.SplitN(sub.String(), "*", 2)[0], constants.SubPortNumber) 298 fmt.Fprintf(w, 299 "Information for sub: <a href=\"%s\">%s</a> (<a href=\"%s?%s&output=json\">JSON</a>)<br>\n", 300 subURL, subName, req.URL.Path, subName) 301 fmt.Fprintln(w, "</h3>") 302 fmt.Fprint(w, "<table border=\"0\">\n") 303 tw, _ := html.NewTableWriter(w, false) 304 newRow(w, "Required Image", true) 305 sub.herd.showImage(tw, sub.mdb.RequiredImage, true) 306 newRow(w, "Planned Image", false) 307 sub.herd.showImage(tw, sub.mdb.PlannedImage, false) 308 newRow(w, "Last successful image update", false) 309 sub.herd.showImage(tw, sub.lastSuccessfulImageName, false) 310 if sub.lastNote != "" { 311 newRow(w, "Last note", false) 312 tw.WriteData("", sub.lastNote) 313 } 314 newRow(w, "Busy time", false) 315 sub.showBusy(tw) 316 newRow(w, "Status", false) 317 tw.WriteData("", sub.publishedStatus.html()) 318 if sub.lastWriteError != "" { 319 newRow(w, "Last write error", false) 320 tw.WriteData("", sub.lastWriteError) 321 } 322 newRow(w, "Uptime", false) 323 showSince(tw, sub.pollTime, sub.startTime) 324 newRow(w, "Last scan duration", false) 325 showDuration(tw, sub.lastScanDuration, false) 326 if sub.mdb.Location != "" { 327 newRow(w, "Location", false) 328 tw.WriteData("", sub.mdb.Location) 329 } 330 newRow(w, "Time since last successful poll", false) 331 showSince(tw, timeNow, sub.lastPollSucceededTime) 332 newRow(w, "Time since last update", false) 333 showSince(tw, timeNow, sub.lastUpdateTime) 334 newRow(w, "Time since last sync", false) 335 showSince(tw, timeNow, sub.lastSyncTime) 336 newRow(w, "Last connection duration", false) 337 showDuration(tw, sub.lastConnectDuration, false) 338 newRow(w, "Last short poll duration", false) 339 showDuration(tw, sub.lastShortPollDuration, !sub.lastPollWasFull) 340 newRow(w, "Last full poll duration", false) 341 showDuration(tw, sub.lastFullPollDuration, sub.lastPollWasFull) 342 newRow(w, "Last compute duration", false) 343 showDuration(tw, sub.lastComputeUpdateCpuDuration, false) 344 newRow(w, "Last disruption state", false) 345 tw.WriteData("", sub.lastDisruptionState.String()) 346 if sub.systemUptime != nil { 347 newRow(w, "System uptime", false) 348 showDuration(tw, *sub.systemUptime, false) 349 } 350 fmt.Fprint(w, " </tr>\n") 351 tw.Close() 352 fmt.Fprintln(w, "<br>") 353 fmt.Fprintln(w, "MDB Data:") 354 fmt.Fprintln(w, "<pre>") 355 json.WriteWithIndent(w, " ", sub.mdb) 356 fmt.Fprintln(w, "</pre>") 357 } 358 359 func newRow(w io.Writer, row string, first bool) { 360 if !first { 361 fmt.Fprint(w, " </tr>\n") 362 } 363 fmt.Fprint(w, " <tr>\n") 364 fmt.Fprintf(w, " <td>%s:</td>\n", row) 365 } 366 367 func (sub *Sub) showBusy(tw *html.TableWriter) { 368 if sub.busy { 369 if sub.busyStartTime.IsZero() { 370 tw.WriteData("", "busy") 371 } else { 372 tw.WriteData("", format.Duration(time.Since(sub.busyStartTime))) 373 } 374 } else { 375 if sub.busyStartTime.IsZero() { 376 tw.WriteData("", "") 377 } else { 378 tw.WriteData("grey", 379 format.Duration(sub.busyStopTime.Sub(sub.busyStartTime))) 380 } 381 } 382 } 383 384 func showSince(tw *html.TableWriter, now time.Time, since time.Time) { 385 if now.IsZero() || since.IsZero() { 386 tw.WriteData("", "") 387 } else { 388 showDuration(tw, now.Sub(since), false) 389 } 390 } 391 392 func showDuration(tw *html.TableWriter, duration time.Duration, 393 highlight bool) { 394 if duration < 1 { 395 tw.WriteData("", "") 396 } else { 397 str := format.Duration(duration) 398 if highlight { 399 str = "<b>" + str + "</b>" 400 } 401 tw.WriteData("", str) 402 } 403 }