diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go
index 440f1d3de06..27612dd5675 100644
--- a/go/vt/discovery/healthcheck.go
+++ b/go/vt/discovery/healthcheck.go
@@ -39,6 +39,7 @@ package discovery
import (
"bytes"
"encoding/json"
+ "flag"
"fmt"
"html/template"
"net/http"
@@ -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"
@@ -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.
@@ -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:
@@ -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.
+// 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
@@ -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 {
@@ -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(`%v%v`, addr, color, name, extra))
+ tLinks = append(tLinks, fmt.Sprintf(`%v%v`, ts.getTabletDebugURL(), color, name, extra))
}
return template.HTML(strings.Join(tLinks, "
"))
}
diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go
index 637d8445a81..1e6086da33f 100644
--- a/go/vt/discovery/healthcheck_test.go
+++ b/go/vt/discovery/healthcheck_test.go
@@ -23,6 +23,8 @@ import (
"html/template"
"io"
"reflect"
+ "strings"
+ "sync"
"testing"
"time"
@@ -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"
)
@@ -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
}