Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 58 additions & 6 deletions go/vt/discovery/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ package discovery
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"html/template"
"net/http"
Expand All @@ -47,9 +48,8 @@ import (
"sync"
"time"

"golang.org/x/net/context"

"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"vitess.io/vitess/go/netutil"
"vitess.io/vitess/go/stats"
"vitess.io/vitess/go/vt/grpcclient"
Expand All @@ -67,6 +67,8 @@ var (
hcErrorCounters = stats.NewCountersWithMultiLabels("HealthcheckErrors", "Healthcheck Errors", []string{"Keyspace", "ShardName", "TabletType"})
hcMasterPromotedCounters = stats.NewCountersWithMultiLabels("HealthcheckMasterPromoted", "Master promoted in keyspace/shard name because of health check errors", []string{"Keyspace", "ShardName"})
healthcheckOnce sync.Once
tabletURLTemplateString = flag.String("tablet_url_template", "http://{{.GetTabletHostPort}}", "format string describing debug tablet url formatting. See the Go code for getTabletDebugURL() how to customize this.")
tabletURLTemplate *template.Template
)

// See the documentation for NewHealthCheck below for an explanation of these parameters.
Expand Down Expand Up @@ -117,6 +119,20 @@ const (
`
)

func init() {
loadTabletURLTemplate()
}

// loadTabletURLTemplate loads or reloads the URL template.
// Should only be used independently for testing.
func loadTabletURLTemplate() {
tabletURLTemplate = template.New("")
_, err := tabletURLTemplate.Parse(*tabletURLTemplateString)
if err != nil {
log.Exitf("error parsing template: %v", err)
}
}

// HealthCheckStatsListener is the listener to receive health check stats update.
type HealthCheckStatsListener interface {
// StatsUpdate is called when:
Expand Down Expand Up @@ -187,6 +203,44 @@ func (e *TabletStats) DeepEqual(f *TabletStats) bool {
(e.LastError != nil && f.LastError != nil && e.LastError.Error() == f.LastError.Error()))
}

// GetTabletHostPort formats a tablet host port address.
func (e TabletStats) GetTabletHostPort() string {
vtPort := e.Tablet.PortMap["vt"]
return netutil.JoinHostPort(e.Tablet.Hostname, vtPort)
}

// GetHostNameLevel returns the specified hostname level. If the level does not exist it will pick the closest level.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently unused. I assume you want to use it in your custom templates?

Please document how to use this and why it must be kept in the code.

Also, please add a test for this method. This will better demonstrate the intended use and also make sure that this functionality won't be broken.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok done.

// This seems unused but can be utilized by certain url formatting templates. See getTabletDebugURL for more details.
func (e TabletStats) GetHostNameLevel(level int) string {
chunkedHostname := strings.Split(e.Tablet.Hostname, ".")

if level < 0 {
return chunkedHostname[0]
} else if level >= len(chunkedHostname) {
return chunkedHostname[len(chunkedHostname)-1]
} else {
return chunkedHostname[level]
}
}

// getTabletDebugURL formats a debug url to the tablet.
// It uses a format string that can be passed into the app to format
// the debug URL to accommodate different network setups. It applies
// the html/template string defined to a TabletStats object. The
// format string can refer to members and functions of TabletStats
// like a regular html/template string.
//
// For instance given a tablet with hostname:port of host.dc.domain:22
// could be configured as follows:
// http://{{.GetTabletHostPort}} -> http://host.dc.domain:22
// https://{{.Tablet.Hostname}} -> https://host.dc.domain
// https://{{.GetHostNameLevel 0}}.bastion.corp -> https://host.bastion.corp
func (e TabletStats) getTabletDebugURL() string {
var buffer bytes.Buffer
tabletURLTemplate.Execute(&buffer, e)
return buffer.String()
}

// HealthCheck defines the interface of health checking module.
// The goal of this object is to maintain a StreamHealth RPC
// to a lot of tablets. Tablets are added / removed by calling the
Expand Down Expand Up @@ -737,7 +791,6 @@ func (tcs *TabletsCacheStatus) StatusAsHTML() template.HTML {
sort.Sort(tcs.TabletsStats)
}
for _, ts := range tcs.TabletsStats {
vtPort := ts.Tablet.PortMap["vt"]
color := "green"
extra := ""
if ts.LastError != nil {
Expand All @@ -755,11 +808,10 @@ func (tcs *TabletsCacheStatus) StatusAsHTML() template.HTML {
extra = fmt.Sprintf(" (RepLag: %v)", ts.Stats.SecondsBehindMaster)
}
name := ts.Name
addr := netutil.JoinHostPort(ts.Tablet.Hostname, vtPort)
if name == "" {
name = addr
name = ts.GetTabletHostPort()
}
tLinks = append(tLinks, fmt.Sprintf(`<a href="http://%s" style="color:%v">%v</a>%v`, addr, color, name, extra))
tLinks = append(tLinks, fmt.Sprintf(`<a href="%s" style="color:%v">%v</a>%v`, ts.getTabletDebugURL(), color, name, extra))
}
return template.HTML(strings.Join(tLinks, "<br>"))
}
Expand Down
41 changes: 38 additions & 3 deletions go/vt/discovery/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"html/template"
"io"
"reflect"
"strings"
"sync"
"testing"
"time"

Expand All @@ -35,9 +37,6 @@ import (
"vitess.io/vitess/go/vt/vttablet/queryservice/fakes"
"vitess.io/vitess/go/vt/vttablet/tabletconn"

"strings"
"sync"

querypb "vitess.io/vitess/go/vt/proto/query"
topodatapb "vitess.io/vitess/go/vt/proto/topodata"
)
Expand Down Expand Up @@ -573,6 +572,42 @@ func TestTemplate(t *testing.T) {
}
}

func TestDebugURLFormatting(t *testing.T) {
flag.Set("tablet_url_template", "https://{{.GetHostNameLevel 0}}.bastion.{{.Tablet.Alias.Cell}}.corp")
loadTabletURLTemplate()

tablet := topo.NewTablet(0, "cell", "host.dc.domain")
ts := []*TabletStats{
{
Key: "a",
Tablet: tablet,
Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_REPLICA},
Up: true,
Serving: false,
Stats: &querypb.RealtimeStats{SecondsBehindMaster: 1, CpuUsage: 0.3},
TabletExternallyReparentedTimestamp: 0,
},
}
tcs := &TabletsCacheStatus{
Cell: "cell",
Target: &querypb.Target{Keyspace: "k", Shard: "s", TabletType: topodatapb.TabletType_REPLICA},
TabletsStats: ts,
}
templ := template.New("").Funcs(status.StatusFuncs)
templ, err := templ.Parse(HealthCheckTemplate)
if err != nil {
t.Fatalf("error parsing template: %v", err)
}
wr := &bytes.Buffer{}
if err := templ.Execute(wr, []*TabletsCacheStatus{tcs}); err != nil {
t.Fatalf("error executing template: %v", err)
}
expectedURL := `"https://host.bastion.cell.corp"`
if !strings.Contains(wr.String(), expectedURL) {
t.Fatalf("output missing formatted URL, expectedURL: %s , output: %s", expectedURL, wr.String())
}
}

type listener struct {
output chan *TabletStats
}
Expand Down