From 17be11d2b70c33dd03bbdfd807b67f73445b33de Mon Sep 17 00:00:00 2001 From: Michael Pawliszyn Date: Thu, 5 Apr 2018 11:59:19 -0400 Subject: [PATCH 1/4] Adds the possibility to vary the template of tablet debug urls. Signed-off-by: Michael Pawliszyn --- go/vt/discovery/healthcheck.go | 61 +++++++++++++++++++++++++++-- go/vt/discovery/healthcheck_test.go | 36 +++++++++++++++++ 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go index 440f1d3de06..7fe2edebe2a 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" @@ -67,6 +68,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 +120,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 +204,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 +792,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 +809,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..dac000635b3 100644 --- a/go/vt/discovery/healthcheck_test.go +++ b/go/vt/discovery/healthcheck_test.go @@ -573,6 +573,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 } From 0d373f4e930bb66ac93633eebd3a9bab19c0ec7c Mon Sep 17 00:00:00 2001 From: Michael Berlin Date: Wed, 25 Apr 2018 10:07:32 -0700 Subject: [PATCH 2/4] vt/discovery: Fix import groups in healthcheck files. Signed-off-by: Michael Berlin --- go/vt/discovery/healthcheck.go | 3 +-- go/vt/discovery/healthcheck_test.go | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go index 7fe2edebe2a..e7cac724ebf 100644 --- a/go/vt/discovery/healthcheck.go +++ b/go/vt/discovery/healthcheck.go @@ -48,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" diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go index dac000635b3..bcff21f7dae 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" ) From a990225feec67779efe40deb990a17949642bb10 Mon Sep 17 00:00:00 2001 From: Michael Berlin Date: Wed, 25 Apr 2018 10:08:29 -0700 Subject: [PATCH 3/4] vt/discovery: Do not export LoadTabletURLTemplate. It is only used for tests in the same package. Signed-off-by: Michael Berlin --- go/vt/discovery/healthcheck.go | 6 +++--- go/vt/discovery/healthcheck_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go/vt/discovery/healthcheck.go b/go/vt/discovery/healthcheck.go index e7cac724ebf..27612dd5675 100644 --- a/go/vt/discovery/healthcheck.go +++ b/go/vt/discovery/healthcheck.go @@ -120,12 +120,12 @@ const ( ) func init() { - LoadTabletURLTemplate() + loadTabletURLTemplate() } -// LoadTabletURLTemplate loads or reloads the URL template. +// loadTabletURLTemplate loads or reloads the URL template. // Should only be used independently for testing. -func LoadTabletURLTemplate() { +func loadTabletURLTemplate() { tabletURLTemplate = template.New("") _, err := tabletURLTemplate.Parse(*tabletURLTemplateString) if err != nil { diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go index bcff21f7dae..cddc3cdc135 100644 --- a/go/vt/discovery/healthcheck_test.go +++ b/go/vt/discovery/healthcheck_test.go @@ -574,7 +574,7 @@ func TestTemplate(t *testing.T) { func TestDebugURLFormatting(t *testing.T) { flag.Set("tablet_url_template", "https://{{.GetHostNameLevel 0}}.bastion.{{.Tablet.Alias.Cell}}.corp") - LoadTabletURLTemplate() + loadTabletURLTemplate() tablet := topo.NewTablet(0, "cell", "host.dc.domain") ts := []*TabletStats{ From 9dd560d3ecd82648b3fafa7b1ca15e46acc03bdb Mon Sep 17 00:00:00 2001 From: Michael Berlin Date: Wed, 25 Apr 2018 10:10:02 -0700 Subject: [PATCH 4/4] vt/discovery: Check for quotes in template output as well. This way we avoid that a partial match would be a success as well. Signed-off-by: Michael Berlin --- go/vt/discovery/healthcheck_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/vt/discovery/healthcheck_test.go b/go/vt/discovery/healthcheck_test.go index cddc3cdc135..1e6086da33f 100644 --- a/go/vt/discovery/healthcheck_test.go +++ b/go/vt/discovery/healthcheck_test.go @@ -602,7 +602,7 @@ func TestDebugURLFormatting(t *testing.T) { if err := templ.Execute(wr, []*TabletsCacheStatus{tcs}); err != nil { t.Fatalf("error executing template: %v", err) } - expectedURL := "https://host.bastion.cell.corp" + 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()) }