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)
+ })
+ }
+}