From 2de2105d8b9dbb6cfdbe3c05d1cd57db0d49e711 Mon Sep 17 00:00:00 2001 From: Sugu Sougoumarane Date: Mon, 13 Apr 2020 13:05:02 -0700 Subject: [PATCH 1/3] vtctld: proxy must update target URLs VTTablet URLs can change. If so, the reverse proxy should update its redirects. Signed-off-by: Sugu Sougoumarane --- go/vt/vtctld/vttablet_proxy.go | 69 ++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/go/vt/vtctld/vttablet_proxy.go b/go/vt/vtctld/vttablet_proxy.go index b9f4a6e8f1d..1a252b0dc91 100644 --- a/go/vt/vtctld/vttablet_proxy.go +++ b/go/vt/vtctld/vttablet_proxy.go @@ -33,49 +33,78 @@ import ( var ( proxyTablets = flag.Bool("proxy_tablets", false, "Setting this true will make vtctld proxy the tablet status instead of redirecting to them") - proxyMu sync.Mutex - remotes = make(map[string]*httputil.ReverseProxy) + remotesMu sync.Mutex + remotes = make(map[string]*redirection) ) -func addRemote(tabletID, path string) { - u, err := url.Parse(path) - if err != nil { - log.Errorf("Error parsing URL %v: %v", path, err) - return - } +type redirection struct { + mu sync.Mutex + url *url.URL + prefixPath string +} - proxyMu.Lock() - defer proxyMu.Unlock() - if _, ok := remotes[tabletID]; ok { - return +func newRedirection(u *url.URL, prefixPath string) *redirection { + rd := &redirection{ + url: u, + prefixPath: prefixPath, } + // rp is created once per prefixPath. But the data it uses + // to redirect is controlled by redirection. rp := &httputil.ReverseProxy{} rp.Director = func(req *http.Request) { + rd.mu.Lock() + defer rd.mu.Unlock() + splits := strings.SplitN(req.URL.Path, "/", 4) if len(splits) < 4 { return } - req.URL.Scheme = u.Scheme - req.URL.Host = u.Host + req.URL.Scheme = rd.url.Scheme + req.URL.Host = rd.url.Host req.URL.Path = "/" + splits[3] } - prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) - rp.ModifyResponse = func(r *http.Response) error { + rd.mu.Lock() + defer rd.mu.Unlock() + b, _ := ioutil.ReadAll(r.Body) - b = bytes.ReplaceAll(b, []byte(`href="/`), []byte(fmt.Sprintf(`href="%s`, prefixPath))) - b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, prefixPath))) + b = bytes.ReplaceAll(b, []byte(`href="/`), []byte(fmt.Sprintf(`href="%s`, rd.prefixPath))) + b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, rd.prefixPath))) r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) r.Header["Content-Length"] = []string{fmt.Sprint(len(b))} // Don't forget redirects loc := r.Header["Location"] for i, v := range loc { - loc[i] = strings.Replace(v, "/", prefixPath, 1) + loc[i] = strings.Replace(v, "/", rd.prefixPath, 1) } return nil } http.Handle(prefixPath, rp) - remotes[tabletID] = rp + return rd +} + +func (rd *redirection) set(u *url.URL, prefixPath string) { + rd.mu.Lock() + defer rd.mu.Unlock() + rd.url = u + rd.prefixPath = prefixPath +} + +func addRemote(tabletID, path string) { + u, err := url.Parse(path) + if err != nil { + log.Errorf("Error parsing URL %v: %v", path, err) + return + } + prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) + + remotesMu.Lock() + defer remotesMu.Unlock() + if rd, ok := remotes[tabletID]; ok { + rd.set(u, prefixPath) + return + } + remotes[tabletID] = newRedirection(u, prefixPath) } From 451fb64076e37927958d0d7730c70f99d4095daf Mon Sep 17 00:00:00 2001 From: Sugu Sougoumarane Date: Mon, 13 Apr 2020 13:56:36 -0700 Subject: [PATCH 2/3] vtctld: single handler approach for vttablet proxy Signed-off-by: Sugu Sougoumarane --- go/vt/vtctld/api.go | 6 +- go/vt/vtctld/redirection.go | 79 +++++++++++++++++++++++ go/vt/vtctld/vtctld.go | 3 + go/vt/vtctld/vttablet_proxy.go | 110 --------------------------------- 4 files changed, 84 insertions(+), 114 deletions(-) create mode 100644 go/vt/vtctld/redirection.go delete mode 100644 go/vt/vtctld/vttablet_proxy.go diff --git a/go/vt/vtctld/api.go b/go/vt/vtctld/api.go index 3bb7912a60f..939d8cab67d 100644 --- a/go/vt/vtctld/api.go +++ b/go/vt/vtctld/api.go @@ -48,6 +48,7 @@ import ( var ( localCell = flag.String("cell", "", "cell to use") showTopologyCRUD = flag.Bool("vtctld_show_topology_crud", true, "Controls the display of the CRUD topology actions in the vtctld UI.") + proxyTablets = flag.Bool("proxy_tablets", false, "Setting this true will make vtctld proxy the tablet status instead of redirecting to them") ) // This file implements a REST-style API for the vtctld web interface. @@ -406,13 +407,10 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re MysqlPort: t.MysqlPort, MasterTermStartTime: t.MasterTermStartTime, } - tabletPath = fmt.Sprintf("http://%s:%d", t.Tablet.Hostname, t.PortMap["vt"]) if *proxyTablets { - tabletID := fmt.Sprintf("%s-%d", t.Tablet.Alias.Cell, t.Tablet.Alias.Uid) - addRemote(tabletID, tabletPath) tab.URL = fmt.Sprintf("/vttablet/%s-%d", t.Tablet.Alias.Cell, t.Tablet.Alias.Uid) } else { - tab.URL = tabletPath + tab.URL = fmt.Sprintf("http://%s:%d", t.Tablet.Hostname, t.PortMap["vt"]) } return tab, nil }) diff --git a/go/vt/vtctld/redirection.go b/go/vt/vtctld/redirection.go new file mode 100644 index 00000000000..f55cf275f57 --- /dev/null +++ b/go/vt/vtctld/redirection.go @@ -0,0 +1,79 @@ +/* +Copyright 2020 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vtctld + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "vitess.io/vitess/go/vt/log" + "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/topo/topoproto" +) + +func initVTTabletRedirection(ts *topo.Server) { + http.HandleFunc("/vttablet/", func(w http.ResponseWriter, r *http.Request) { + splits := strings.SplitN(r.URL.Path, "/", 4) + if len(splits) < 4 { + log.Errorf("Invalid URL: %v", r.URL) + http.NotFound(w, r) + return + } + tabletID := splits[2] + tabletAlias, err := topoproto.ParseTabletAlias(tabletID) + if err != nil { + log.Errorf("Error parsting tablet alias %v: %v", tabletID, err) + http.NotFound(w, r) + return + } + tablet, err := ts.GetTablet(r.Context(), tabletAlias) + if err != nil { + log.Errorf("Error fetching tablet %v: %v", splits[2], err) + http.NotFound(w, r) + return + } + redirect := fmt.Sprintf("http://%s:%d", tablet.Hostname, tablet.PortMap["vt"]) + u, err := url.Parse(redirect) + if err != nil { + log.Errorf("Error parsing url %s: %v", redirect, err) + http.NotFound(w, r) + return + } + rp := httputil.NewSingleHostReverseProxy(u) + prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) + rp.ModifyResponse = func(r *http.Response) error { + b, _ := ioutil.ReadAll(r.Body) + b = bytes.ReplaceAll(b, []byte(`href="/`), []byte(fmt.Sprintf(`href="%s`, prefixPath))) + b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, prefixPath))) + r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) + r.Header["Content-Length"] = []string{fmt.Sprint(len(b))} + // Don't forget redirects + loc := r.Header["Location"] + for i, v := range loc { + loc[i] = strings.Replace(v, "/", prefixPath, 1) + } + return nil + } + r.URL.Path = "/" + splits[3] + rp.ServeHTTP(w, r) + }) +} diff --git a/go/vt/vtctld/vtctld.go b/go/vt/vtctld/vtctld.go index 4913ed5faa6..fe6c2347372 100644 --- a/go/vt/vtctld/vtctld.go +++ b/go/vt/vtctld/vtctld.go @@ -178,4 +178,7 @@ func InitVtctld(ts *topo.Server) { // Init workflow manager. initWorkflowManager(ts) + + // Setup reverse proxy for all vttablets through /vttablet/. + initVTTabletRedirection(ts) } diff --git a/go/vt/vtctld/vttablet_proxy.go b/go/vt/vtctld/vttablet_proxy.go deleted file mode 100644 index 1a252b0dc91..00000000000 --- a/go/vt/vtctld/vttablet_proxy.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 2020 The Vitess Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package vtctld - -import ( - "bytes" - "flag" - "fmt" - "io/ioutil" - "net/http" - "net/http/httputil" - "net/url" - "strings" - "sync" - - "vitess.io/vitess/go/vt/log" -) - -var ( - proxyTablets = flag.Bool("proxy_tablets", false, "Setting this true will make vtctld proxy the tablet status instead of redirecting to them") - - remotesMu sync.Mutex - remotes = make(map[string]*redirection) -) - -type redirection struct { - mu sync.Mutex - url *url.URL - prefixPath string -} - -func newRedirection(u *url.URL, prefixPath string) *redirection { - rd := &redirection{ - url: u, - prefixPath: prefixPath, - } - - // rp is created once per prefixPath. But the data it uses - // to redirect is controlled by redirection. - rp := &httputil.ReverseProxy{} - rp.Director = func(req *http.Request) { - rd.mu.Lock() - defer rd.mu.Unlock() - - splits := strings.SplitN(req.URL.Path, "/", 4) - if len(splits) < 4 { - return - } - req.URL.Scheme = rd.url.Scheme - req.URL.Host = rd.url.Host - req.URL.Path = "/" + splits[3] - } - - rp.ModifyResponse = func(r *http.Response) error { - rd.mu.Lock() - defer rd.mu.Unlock() - - b, _ := ioutil.ReadAll(r.Body) - b = bytes.ReplaceAll(b, []byte(`href="/`), []byte(fmt.Sprintf(`href="%s`, rd.prefixPath))) - b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, rd.prefixPath))) - r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) - r.Header["Content-Length"] = []string{fmt.Sprint(len(b))} - // Don't forget redirects - loc := r.Header["Location"] - for i, v := range loc { - loc[i] = strings.Replace(v, "/", rd.prefixPath, 1) - } - return nil - } - http.Handle(prefixPath, rp) - return rd -} - -func (rd *redirection) set(u *url.URL, prefixPath string) { - rd.mu.Lock() - defer rd.mu.Unlock() - rd.url = u - rd.prefixPath = prefixPath -} - -func addRemote(tabletID, path string) { - u, err := url.Parse(path) - if err != nil { - log.Errorf("Error parsing URL %v: %v", path, err) - return - } - prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) - - remotesMu.Lock() - defer remotesMu.Unlock() - if rd, ok := remotes[tabletID]; ok { - rd.set(u, prefixPath) - return - } - remotes[tabletID] = newRedirection(u, prefixPath) -} From 3a1494eb3a91a63afe294275d0e8838185355dc9 Mon Sep 17 00:00:00 2001 From: Sugu Sougoumarane Date: Tue, 14 Apr 2020 13:25:27 -0700 Subject: [PATCH 3/3] vtctld: tablet proxy review comments Signed-off-by: Sugu Sougoumarane --- go/vt/vtctld/api.go | 5 +++-- go/vt/vtctld/redirection.go | 24 ++++++++++++++++-------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/go/vt/vtctld/api.go b/go/vt/vtctld/api.go index 939d8cab67d..ede03e98815 100644 --- a/go/vt/vtctld/api.go +++ b/go/vt/vtctld/api.go @@ -29,6 +29,7 @@ import ( "golang.org/x/net/context" "vitess.io/vitess/go/acl" + "vitess.io/vitess/go/netutil" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/logutil" "vitess.io/vitess/go/vt/schemamanager" @@ -408,9 +409,9 @@ func initAPI(ctx context.Context, ts *topo.Server, actions *ActionRepository, re MasterTermStartTime: t.MasterTermStartTime, } if *proxyTablets { - tab.URL = fmt.Sprintf("/vttablet/%s-%d", t.Tablet.Alias.Cell, t.Tablet.Alias.Uid) + tab.URL = fmt.Sprintf("/vttablet/%s-%d", t.Alias.Cell, t.Alias.Uid) } else { - tab.URL = fmt.Sprintf("http://%s:%d", t.Tablet.Hostname, t.PortMap["vt"]) + tab.URL = "http://" + netutil.JoinHostPort(t.Hostname, t.PortMap["vt"]) } return tab, nil }) diff --git a/go/vt/vtctld/redirection.go b/go/vt/vtctld/redirection.go index f55cf275f57..02701e91f07 100644 --- a/go/vt/vtctld/redirection.go +++ b/go/vt/vtctld/redirection.go @@ -22,9 +22,9 @@ import ( "io/ioutil" "net/http" "net/http/httputil" - "net/url" "strings" + "vitess.io/vitess/go/netutil" "vitess.io/vitess/go/vt/log" "vitess.io/vitess/go/vt/topo" "vitess.io/vitess/go/vt/topo/topoproto" @@ -51,14 +51,19 @@ func initVTTabletRedirection(ts *topo.Server) { http.NotFound(w, r) return } - redirect := fmt.Sprintf("http://%s:%d", tablet.Hostname, tablet.PortMap["vt"]) - u, err := url.Parse(redirect) - if err != nil { - log.Errorf("Error parsing url %s: %v", redirect, err) + if tablet.Hostname == "" || tablet.PortMap["vt"] == 0 { + log.Errorf("Invalid host/port: %s %d", tablet.Hostname, tablet.PortMap["vt"]) http.NotFound(w, r) return } - rp := httputil.NewSingleHostReverseProxy(u) + + rp := &httputil.ReverseProxy{} + rp.Director = func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = netutil.JoinHostPort(tablet.Hostname, tablet.PortMap["vt"]) + req.URL.Path = "/" + splits[3] + } + prefixPath := fmt.Sprintf("/vttablet/%s/", tabletID) rp.ModifyResponse = func(r *http.Response) error { b, _ := ioutil.ReadAll(r.Body) @@ -66,14 +71,17 @@ func initVTTabletRedirection(ts *topo.Server) { b = bytes.ReplaceAll(b, []byte(`href=/`), []byte(fmt.Sprintf(`href=%s`, prefixPath))) r.Body = ioutil.NopCloser(bytes.NewBuffer(b)) r.Header["Content-Length"] = []string{fmt.Sprint(len(b))} + // Don't forget redirects loc := r.Header["Location"] for i, v := range loc { - loc[i] = strings.Replace(v, "/", prefixPath, 1) + if strings.HasPrefix(v, "/") { + loc[i] = strings.Replace(v, "/", prefixPath, 1) + } } return nil } - r.URL.Path = "/" + splits[3] + rp.ServeHTTP(w, r) }) }