gddo-server: redirect based on rollout Logic is added to redirect paths based on a rollout percentage. Change-Id: Ic79f1064ca6af1301d2e12d398dd8fd0e7ece73b Reviewed-on: https://go-review.googlesource.com/c/gddo/+/285876 Trust: Julie Qiu <julie@golang.org> Run-TryBot: Julie Qiu <julie@golang.org> Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/gddo-server/dynconfig/dynconfig.go b/gddo-server/dynconfig/dynconfig.go index 5207164..4464a60 100644 --- a/gddo-server/dynconfig/dynconfig.go +++ b/gddo-server/dynconfig/dynconfig.go
@@ -29,7 +29,9 @@ RedirectBadges bool RedirectHomepage bool RedirectSearch bool - RedirectPaths []string + + RedirectPaths []string + RedirectRollout uint } // Read reads dynamic configuration from the given location.
diff --git a/gddo-server/pkgsite.go b/gddo-server/pkgsite.go index 162d6e1..033e867 100644 --- a/gddo-server/pkgsite.go +++ b/gddo-server/pkgsite.go
@@ -9,6 +9,7 @@ import ( "context" "fmt" + "hash/fnv" "io/ioutil" "log" "net/http" @@ -217,9 +218,21 @@ } } +var vcsHostsWithThreeElementRepoName = map[string]bool{ + "bitbucket.org": true, + "gitea.com": true, + "gitee.com": true, + "github.com": true, + "gitlab.com": true, + "golang.org": true, +} + // shouldRedirectURL reports whether a request to the given URL should be // redirected to pkg.go.dev. func shouldRedirectURL(r *http.Request, poller *poller.Poller) bool { + if poller == nil { + return false + } cfg := poller.Current().(*dynconfig.DynamicConfig) return shouldRedirectURLForSnapshot(r, cfg) } @@ -256,8 +269,20 @@ return false } - // TODO: redirect based on rollout percentage. - return false + if cfg.RedirectRollout >= 100 { + return true + } + if cfg.RedirectRollout == 0 { + return false + } + parts := strings.Split(r.URL.Path, "/") + prefix := parts[1] + if _, ok := vcsHostsWithThreeElementRepoName[prefix]; ok { + prefix = strings.Join(parts[1:3], "/") + } + h := fnv.New32a() + fmt.Fprintf(h, "%s", prefix) + return uint(h.Sum32()%100) < cfg.RedirectRollout } const goGithubRepoURLPath = "/github.com/golang/go"
diff --git a/gddo-server/pkgsite_test.go b/gddo-server/pkgsite_test.go index 34ad51b..8c0dddf 100644 --- a/gddo-server/pkgsite_test.go +++ b/gddo-server/pkgsite_test.go
@@ -8,9 +8,11 @@ import ( "bufio" + "fmt" "net/http" "net/http/httptest" "net/url" + "strconv" "strings" "testing" @@ -564,3 +566,54 @@ }) } } + +func TestShouldRedirectURLForSnapshot_RolloutPercentage(t *testing.T) { + checkRollout := func(t *testing.T, paths []string, rollout uint, want uint) { + t.Helper() + var inExperiment int + for _, p := range paths { + req, err := http.NewRequest("GET", "http://godoc.org/"+p, nil) + if err != nil { + t.Fatal(err) + } + snapshot := &dynconfig.DynamicConfig{RedirectRollout: rollout} + if shouldRedirectURLForSnapshot(req, snapshot) { + inExperiment++ + } + } + if rollout == 0 { + if inExperiment != 0 { + t.Fatalf("rollout is 0 and inExperiment = %d; want = 0", inExperiment) + } + return + } + got := uint(100 * inExperiment / len(paths)) + if got != want { + t.Errorf("rollout = %d; want = %d", got, want) + } + } + + var paths []string + for host := range vcsHostsWithThreeElementRepoName { + for i := 0; i < 1000; i++ { + p := host + "/" + strconv.Itoa(i) + "/foo" + paths = append(paths, p) + } + } + pathsWithCustomHost := paths + for i := 0; i < 1000; i++ { + pathsWithCustomHost = append(pathsWithCustomHost, "mymodule.com/"+strconv.Itoa(i)) + } + for _, rollout := range []uint{0, 33, 47, 50, 53, 75, 100} { + t.Run(fmt.Sprintf("%d", rollout), func(t *testing.T) { + checkRollout(t, paths, rollout, rollout) + }) + t.Run(fmt.Sprintf("customhost %d", rollout), func(t *testing.T) { + // Map of rollout set to expected rollout percentage. Numbers are + // skewed because all my.module.com/<i> paths are added, and they + // will not be opted in. + want := map[uint]uint{0: 0, 33: 28, 47: 40, 50: 42, 53: 45, 75: 64, 100: 100}[rollout] + checkRollout(t, pathsWithCustomHost, rollout, want) + }) + } +}