Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlessio Caiazza <acaiazza@gitlab.com>2020-01-28 19:55:06 +0300
committerAlessio Caiazza <acaiazza@gitlab.com>2020-01-28 19:55:06 +0300
commitf316fbf094402db2cf36c44639eeb3ae2f32c94b (patch)
tree70fcda9c68bea39e2a2d04b71e59ee4422bc3733
parentf08c7a13b8115b99393ce1e58f79d06ceae9b8a5 (diff)
parent12a986b1eb48a54b965861b2d1571e1cbd7c9fec (diff)
Merge branch 'incremental-rollout' into 'master'
Implement support for incremental rollout of the new API based config source See merge request gitlab-org/gitlab-pages!224
-rw-r--r--internal/rollout/rollout.go53
-rw-r--r--internal/source/domains.go15
-rw-r--r--internal/source/domains/gitlabsourceconfig/gitlabsourceconfig.go13
-rw-r--r--internal/source/domains_test.go73
-rw-r--r--main.go3
5 files changed, 155 insertions, 2 deletions
diff --git a/internal/rollout/rollout.go b/internal/rollout/rollout.go
new file mode 100644
index 00000000..ec592c38
--- /dev/null
+++ b/internal/rollout/rollout.go
@@ -0,0 +1,53 @@
+package rollout
+
+import (
+ "errors"
+ "hash/fnv"
+ "math/rand"
+)
+
+// Rollout returns true and no error when during this run something should
+// happen for given actor according to the stickiness and likelyhood passed
+// as a percentage value to this function. It returns false rollout and an
+// error if the percentage value is negative or higher than 100.
+func Rollout(actor string, percentage int, stickiness string) (bool, error) {
+ if percentage < 0 || percentage > 100 {
+ return false, errors.New("Rollout value should be between 0 and 100 inclusive")
+ }
+
+ if percentage == 0 {
+ return false, nil
+ }
+
+ if percentage == 100 {
+ return true, nil
+ }
+
+ switch stickiness {
+ case "random":
+ return random(percentage), nil
+ default:
+ return forActor(actor, percentage), nil
+ }
+}
+
+// random guarantees no stickiness. For every call it will yield a random
+// true/false based on the provided rollout percentage.
+func random(percentage int) bool {
+ return rand.Intn(100) < percentage
+}
+
+// forActor provides "stickines", i.e. guarantees that the same actor
+// gets the same result every time. It also assures that an actor which is
+// among the first 10% will also be among the first 20%.
+func forActor(actor string, percentage int) bool {
+ h := fnv.New32a()
+ h.Write([]byte(actor))
+ sum32 := h.Sum32()
+
+ if sum32 == 0 {
+ return false
+ }
+
+ return (sum32 % uint32(100)) < uint32(percentage)
+}
diff --git a/internal/source/domains.go b/internal/source/domains.go
index 22187855..79357766 100644
--- a/internal/source/domains.go
+++ b/internal/source/domains.go
@@ -4,7 +4,10 @@ import (
"errors"
"time"
+ log "github.com/sirupsen/logrus"
+
"gitlab.com/gitlab-org/gitlab-pages/internal/domain"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/rollout"
"gitlab.com/gitlab-org/gitlab-pages/internal/source/disk"
"gitlab.com/gitlab-org/gitlab-pages/internal/source/domains/gitlabsourceconfig"
"gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab"
@@ -81,5 +84,17 @@ func (d *Domains) source(domain string) Source {
}
}
+ r := gitlabSourceConfig.Domains.Rollout
+
+ enabled, err := rollout.Rollout(domain, r.Percentage, r.Stickiness)
+ if err != nil {
+ log.WithError(err).Error("Rollout error")
+ return d.disk
+ }
+
+ if enabled {
+ return d.gitlab
+ }
+
return d.disk
}
diff --git a/internal/source/domains/gitlabsourceconfig/gitlabsourceconfig.go b/internal/source/domains/gitlabsourceconfig/gitlabsourceconfig.go
index 4eda31aa..ebc8b485 100644
--- a/internal/source/domains/gitlabsourceconfig/gitlabsourceconfig.go
+++ b/internal/source/domains/gitlabsourceconfig/gitlabsourceconfig.go
@@ -14,6 +14,13 @@ import (
type GitlabSourceDomains struct {
Enabled []string
Broken string
+ Rollout GitlabSourceRollout
+}
+
+// GitlabSourceRollout holds the rollout strategy and percentage
+type GitlabSourceRollout struct {
+ Stickiness string
+ Percentage int
}
// GitlabSourceConfig holds the configuration for the gitlab source
@@ -36,8 +43,10 @@ func (config *GitlabSourceConfig) UpdateFromYaml(content []byte) error {
*config = updated
log.WithFields(log.Fields{
- "Enabled domains": config.Domains.Enabled,
- "Broken domain": config.Domains.Broken,
+ "Enabled domains": config.Domains.Enabled,
+ "Broken domain": config.Domains.Broken,
+ "Rollout %": config.Domains.Rollout.Percentage,
+ "Rollout stickiness": config.Domains.Rollout.Stickiness,
}).Info("gitlab source config updated")
return nil
diff --git a/internal/source/domains_test.go b/internal/source/domains_test.go
index bbdb8c21..d639b02d 100644
--- a/internal/source/domains_test.go
+++ b/internal/source/domains_test.go
@@ -1,6 +1,7 @@
package source
import (
+ "math/rand"
"testing"
"time"
@@ -109,3 +110,75 @@ func TestGetDomain(t *testing.T) {
require.NoError(t, err)
})
}
+
+func TestGetDomainWithIncrementalrolloutOfGitLabSource(t *testing.T) {
+ // This will produce the following pseudo-random sequence: 5, 87, 68
+ rand.Seed(42)
+
+ // Generates FNV hash 4091421005, 4091421005 % 100 = 5
+ domain05 := "test-domain-a.com"
+ // Generates FNV 2643293380, 2643293380 % 100 = 80
+ domain80 := "test-domain-b.com"
+
+ diskSource := disk.New()
+
+ gitlabSourceConfig.Domains.Rollout.Percentage = 80
+
+ type testDomain struct {
+ name string
+ source string
+ times int
+ }
+
+ tests := map[string]struct {
+ stickiness string
+ domains []testDomain
+ }{
+ // domain05 should always use gitlab source,
+ // domain80 should use disk source
+ "default stickiness": {
+ stickiness: "",
+ domains: []testDomain{
+ {name: domain05, source: "gitlab"},
+ {name: domain80, source: "disk"},
+ {name: domain05, source: "gitlab"},
+ },
+ },
+ // Given that randSeed(42) will produce the following pseudo-random sequence:
+ // {5, 87, 68} the first and third call for domain05 should use gitlab source,
+ // while the second one should use disk source
+ "no stickiness": {
+ stickiness: "random",
+ domains: []testDomain{
+ {name: domain05, source: "gitlab"},
+ {name: domain05, source: "disk"},
+ {name: domain05, source: "gitlab"},
+ }},
+ }
+
+ for name, tc := range tests {
+ t.Run(name, func(t *testing.T) {
+ gitlabSource := NewMockSource()
+ for _, d := range tc.domains {
+ if d.source == "gitlab" {
+ gitlabSource.On("GetDomain", d.name).
+ Return(&domain.Domain{Name: d.name}, nil).
+ Once()
+ }
+ }
+ defer gitlabSource.AssertExpectations(t)
+
+ domains := &Domains{
+ disk: diskSource,
+ gitlab: gitlabSource,
+ }
+
+ gitlabSourceConfig.Domains.Rollout.Stickiness = tc.stickiness
+
+ for _, domain := range tc.domains {
+ _, err := domains.GetDomain(domain.name)
+ require.NoError(t, err)
+ }
+ })
+ }
+}
diff --git a/main.go b/main.go
index 8459314d..ea96a096 100644
--- a/main.go
+++ b/main.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
+ "math/rand"
"net/url"
"os"
"strings"
@@ -394,6 +395,8 @@ func printVersion(showVersion bool, version string) {
func main() {
log.SetOutput(os.Stderr)
+ rand.Seed(time.Now().UnixNano())
+
daemonMain()
appMain()
}