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  }