[rotang] Adding in the legacy Sheriff rotation js/json handlers.
Files are presented at https://rota-ng.appspot.com/legacy/*
Eg. https://rota-ng.appspot.com/legacy/sheriff.js
Bug: 889221
Change-Id: I0e753d9a0f93d895f16c2ff1a8165a42cd48a292
Reviewed-on: https://chromium-review.googlesource.com/c/1272740
Commit-Queue: Ola Karlsson <olakar@chromium.org>
Reviewed-by: Tiffany Zhang <zhangtiff@chromium.org>
Cr-Commit-Position: refs/heads/master@{#18305}diff --git a/cmd/app/templates/pages/index.html b/cmd/app/templates/pages/index.html
index 42c42d2..f0329d1 100644
--- a/cmd/app/templates/pages/index.html
+++ b/cmd/app/templates/pages/index.html
@@ -14,8 +14,9 @@
</p>
<p> Information about how to
<a href="https://chromium.googlesource.com/infra/infra/+/master/go/src/infra/appengine/rotang/SWITCH.md">
-Switch to RotaNG
+ Switch to RotaNG
</a>
+</p>
<a href="upload">Upload Legacy JSON rota configuraton</a><br>
<a href="list">List configurations in backend store</a><br>
<a href="managerota">Manage rotations</a><br>
diff --git a/cmd/handlers/handle_deleterota.go b/cmd/handlers/handle_deleterota.go
index a344a99..35ec6d4 100644
--- a/cmd/handlers/handle_deleterota.go
+++ b/cmd/handlers/handle_deleterota.go
@@ -3,7 +3,6 @@
import (
"net/http"
- "go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/router"
)
@@ -41,21 +40,7 @@
}
rota := rotas[0]
- usr := auth.CurrentUser(ctx.Context)
- if usr == nil {
- http.Error(ctx.Writer, "login required", http.StatusForbidden)
- return
- }
-
- isOwner := false
- for _, o := range rota.Config.Owners {
- if o == usr.Email {
- isOwner = true
- break
- }
- }
-
- if !isOwner {
+ if !adminOrOwner(ctx, rota) {
http.Error(ctx.Writer, "not in the rotation owners", http.StatusForbidden)
return
}
diff --git a/cmd/handlers/handle_legacy.go b/cmd/handlers/handle_legacy.go
index 139e10c..930c926 100644
--- a/cmd/handlers/handle_legacy.go
+++ b/cmd/handlers/handle_legacy.go
@@ -4,8 +4,10 @@
"bytes"
"encoding/json"
"fmt"
+ "infra/appengine/rotang"
"net/http"
"strings"
+ "time"
"go.chromium.org/gae/service/memcache"
"go.chromium.org/luci/common/clock"
@@ -70,9 +72,8 @@
str += ", secondary: " + strings.Join(oc[1:], ", ")
}
}
- return "document.Write('" + str + "');", nil
+ return "document.write('" + str + "');", nil
case "current_trooper.json":
- var buf bytes.Buffer
primary := "None"
var secondary []string
if len(oc) > 0 {
@@ -81,6 +82,8 @@
secondary = oc[1:]
}
}
+
+ var buf bytes.Buffer
enc := json.NewEncoder(&buf)
if err := enc.Encode(&trooperJSON{
Primary: primary,
@@ -99,3 +102,112 @@
return "", status.Errorf(codes.InvalidArgument, "legacyTrooper only handles `trooper.js` and `current_trooper.txt`")
}
}
+
+var fileToRota = map[string][2]string{
+ "sheriff.js": {"Build Sheriff", ""},
+ // "sheriff_webkit.js": "",
+ // "sheriff_memory.js": "",
+ "sheriff_cros_mtv.js": {"Chrome OS Build Sheriff", ""},
+ "sheriff_cros_nonmtv.js": {"Chrome OS Build Sheriff - Other", "Chrome OS Build Sheriff"},
+ "sheriff_perf.js": {"Chromium Perf Regression Sheriff Rotation", ""},
+ "sheriff_cr_cros_gardeners.js": {"Chrome on ChromeOS Gardening", ""},
+ "sheriff_gpu.js": {"Chrome GPU Pixel Wrangling", ""},
+ "sheriff_angle.js": {"The ANGLE Wrangle", ""},
+ "sheriff_android.js": {"Chrome on Android Build Sheriff", ""},
+ "sheriff_ios.js": {"Chrome iOS Build Sheriff", ""},
+ "sheriff_v8.js": {"V8 Sheriff", ""},
+ "sheriff_perfbot.js": {"Chromium Perf Bot Sheriff Rotation", ""},
+
+ "sheriff.json": {"Build Sheriff", ""},
+ // "sheriff_webkit.json": "",
+ // "sheriff_memory.json": "",
+ "sheriff_cros_mtv.json": {"Chrome OS Build Sheriff", ""},
+ "sheriff_cros_nonmtv.json": {"Chrome OS Build Sheriff - Other", "Chrome OS Build Sheriff"},
+ "sheriff_perf.json": {"Chromium Perf Regression Sheriff Rotation", ""},
+ "sheriff_cr_cros_gardeners.json": {"Chrome on ChromeOS Gardening", ""},
+ "sheriff_gpu.json": {"Chrome GPU Pixel Wrangling", ""},
+ "sheriff_angle.json": {"The ANGLE Wrangle", ""},
+ "sheriff_android.json": {"Chrome on Android Build Sheriff", ""},
+ "sheriff_ios.json": {"Chrome iOS Build Sheriff", ""},
+ "sheriff_v8.json": {"V8 Sheriff", ""},
+ "sheriff_perfbot.json": {"Chromium Perf Bot Sheriff Rotation", ""},
+ //"all_rotations.js": "",
+ //"all_rotations.js": "",
+}
+
+const week = 7 * 24 * time.Hour
+
+type sheriffJSON struct {
+ UnixTS int64 `json:"updated_unix_timestamp"`
+ Emails []string `json:"emails"`
+}
+
+// legacySheriff produces the legacy cron created sherriff oncall files.
+func (h *State) legacySheriff(ctx *router.Context, file string) (string, error) {
+ rota, ok := fileToRota[file]
+ if !ok {
+ return "", status.Errorf(codes.InvalidArgument, "file: %q not handled by legacySheriff", file)
+ }
+ r, err := h.configStore(ctx.Context).RotaConfig(ctx.Context, rota[0])
+ if err != nil {
+ return "", err
+ }
+ if len(r) != 1 {
+ return "", status.Errorf(codes.Internal, "RotaConfig did not return 1 configuration")
+ }
+ cfg := r[0]
+ // As a workaround to handle split shifts some users create multiple configurations with different
+ // calendars but the same Event Name. The new service use the rota name as a key in the datastore.
+ if rota[1] != "" {
+ cfg.Config.Name = rota[1]
+ }
+
+ updated := clock.Now(ctx.Context)
+ events, err := h.legacyCalendar.Events(ctx, cfg, updated.Add(-week), updated.Add(week))
+ if err != nil {
+ return "", err
+ }
+
+ var entry rotang.ShiftEntry
+ for _, e := range events {
+ if (updated.After(e.StartTime) || updated.Equal(e.StartTime)) &&
+ updated.Before(e.EndTime) {
+ entry = e
+ }
+ }
+
+ sp := strings.Split(file, ".")
+ if len(sp) != 2 {
+ return "", status.Errorf(codes.InvalidArgument, "filename in wrong format")
+ }
+
+ switch sp[1] {
+ case "js":
+ var oc []string
+ for _, o := range entry.OnCall {
+ oc = append(oc, strings.Split(o.Email, "@")[0])
+ }
+ str := "None"
+ if len(oc) > 0 {
+ str = strings.Join(oc, ", ")
+ }
+ return "document.write('" + str + "');", nil
+ case "json":
+ oc := make([]string, 0)
+ for _, o := range entry.OnCall {
+ oc = append(oc, o.Email)
+ }
+ var buf bytes.Buffer
+ enc := json.NewEncoder(&buf)
+ if err := enc.Encode(&sheriffJSON{
+ UnixTS: updated.Unix(),
+ Emails: oc,
+ }); err != nil {
+ return "", err
+ }
+ return buf.String(), nil
+
+ default:
+ return "", status.Errorf(codes.InvalidArgument, "filename in wrong format")
+ }
+}
diff --git a/cmd/handlers/handle_legacy_test.go b/cmd/handlers/handle_legacy_test.go
index 2114d2c..43924d1 100644
--- a/cmd/handlers/handle_legacy_test.go
+++ b/cmd/handlers/handle_legacy_test.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "infra/appengine/rotang"
"net/http"
"net/http/httptest"
"testing"
@@ -104,6 +105,173 @@
}
+func TestLegacySheriff(t *testing.T) {
+ ctx := newTestContext()
+
+ tests := []struct {
+ name string
+ fail bool
+ calFail bool
+ time time.Time
+ calShifts []rotang.ShiftEntry
+ ctx *router.Context
+ file string
+ memberPool []rotang.Member
+ cfgs []*rotang.Configuration
+ want string
+ }{{
+ name: "Success JS",
+ ctx: &router.Context{
+ Context: ctx,
+ Writer: httptest.NewRecorder(),
+ },
+ file: "sheriff.js",
+ time: midnight,
+ cfgs: []*rotang.Configuration{
+ {
+ Config: rotang.Config{
+ Name: "Build Sheriff",
+ },
+ },
+ },
+ calShifts: []rotang.ShiftEntry{
+ {
+ StartTime: midnight,
+ EndTime: midnight.Add(5 * fullDay),
+ OnCall: []rotang.ShiftMember{
+ {
+ Email: "test1@oncall.com",
+ }, {
+ Email: "test2@oncall.com",
+ },
+ },
+ }, {
+ StartTime: midnight.Add(5 * fullDay),
+ EndTime: midnight.Add(10 * fullDay),
+ OnCall: []rotang.ShiftMember{
+ {
+ Email: "test3@oncall.com",
+ }, {
+ Email: "test4@oncall.com",
+ },
+ },
+ },
+ },
+ want: "document.write('test1, test2');",
+ }, {
+ name: "Success JSON",
+ ctx: &router.Context{
+ Context: ctx,
+ Writer: httptest.NewRecorder(),
+ },
+ file: "sheriff.json",
+ time: midnight.Add(6 * fullDay),
+ cfgs: []*rotang.Configuration{
+ {
+ Config: rotang.Config{
+ Name: "Build Sheriff",
+ },
+ },
+ },
+ calShifts: []rotang.ShiftEntry{
+ {
+ StartTime: midnight,
+ EndTime: midnight.Add(5 * fullDay),
+ OnCall: []rotang.ShiftMember{
+ {
+ Email: "test1@oncall.com",
+ }, {
+ Email: "test2@oncall.com",
+ },
+ },
+ }, {
+ StartTime: midnight.Add(5 * fullDay),
+ EndTime: midnight.Add(10 * fullDay),
+ OnCall: []rotang.ShiftMember{
+ {
+ Email: "test3@oncall.com",
+ }, {
+ Email: "test4@oncall.com",
+ },
+ },
+ },
+ },
+ want: `{"updated_unix_timestamp":1144454400,"emails":["test3@oncall.com","test4@oncall.com"]}
+`,
+ }, {
+ name: "File not supported",
+ fail: true,
+ ctx: &router.Context{
+ Context: ctx,
+ Writer: httptest.NewRecorder(),
+ },
+ file: "sheriff_not_supported.js",
+ time: midnight,
+ }, {
+ name: "Config not found",
+ fail: true,
+ ctx: &router.Context{
+ Context: ctx,
+ Writer: httptest.NewRecorder(),
+ },
+ file: "sheriff.js",
+ time: midnight,
+ }, {
+ name: "Calendar fail",
+ fail: true,
+ calFail: true,
+ ctx: &router.Context{
+ Context: ctx,
+ Writer: httptest.NewRecorder(),
+ },
+ file: "sheriff.json",
+ time: midnight.Add(6 * fullDay),
+ cfgs: []*rotang.Configuration{
+ {
+ Config: rotang.Config{
+ Name: "Build Sheriff",
+ },
+ },
+ },
+ },
+ }
+
+ h := testSetup(t)
+
+ for _, tst := range tests {
+ t.Run(tst.name, func(t *testing.T) {
+ for _, m := range tst.memberPool {
+ if err := h.memberStore(ctx).CreateMember(ctx, &m); err != nil {
+ t.Fatalf("%s: AddMember(ctx, _) failed: %v", tst.name, err)
+ }
+ defer h.memberStore(ctx).DeleteMember(ctx, m.Email)
+ }
+ for _, cfg := range tst.cfgs {
+ if err := h.configStore(ctx).CreateRotaConfig(ctx, cfg); err != nil {
+ t.Fatalf("%s: CreateRotaConfig(ctx, _) failed: %v", tst.name, err)
+ }
+ defer h.configStore(ctx).DeleteRotaConfig(ctx, cfg.Config.Name)
+ }
+
+ h.legacyCalendar.(*fakeCal).Set(tst.calShifts, tst.calFail, false, 0)
+
+ tst.ctx.Context = clock.Set(tst.ctx.Context, testclock.New(tst.time))
+
+ res, err := h.legacySheriff(tst.ctx, tst.file)
+ if got, want := (err != nil), tst.fail; got != want {
+ t.Fatalf("%s: h.legacySheriff(ctx, %q) = %t want: %t, err: %v", tst.name, tst.file, got, want, err)
+ }
+ if err != nil {
+ return
+ }
+
+ if diff := pretty.Compare(tst.want, res); diff != "" {
+ t.Fatalf("%s: h.legacySheriff(ctx, %q) differ -want +got, \n%s", tst.name, tst.file, diff)
+ }
+ })
+ }
+}
+
func TestLegacyTroopers(t *testing.T) {
ctx := newTestContext()
@@ -126,7 +294,7 @@
file: "trooper.js",
oncallers: []string{"primary1", "secondary1", "secondary2"},
updateTime: midnight,
- want: "document.Write('primary1, secondary: secondary1, secondary2');",
+ want: "document.write('primary1, secondary: secondary1, secondary2');",
}, {
name: "Success JSON",
ctx: &router.Context{
diff --git a/cmd/handlers/handlers.go b/cmd/handlers/handlers.go
index f091a6d..70f976e 100644
--- a/cmd/handlers/handlers.go
+++ b/cmd/handlers/handlers.go
@@ -14,9 +14,10 @@
"golang.org/x/net/context"
"golang.org/x/oauth2"
"google.golang.org/appengine"
- aeuser "google.golang.org/appengine/user"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
+
+ aeuser "google.golang.org/appengine/user"
)
var mtvTime = func() *time.Location {
@@ -45,9 +46,33 @@
func buildLegacyMap(h *State) map[string]func(ctx *router.Context, file string) (string, error) {
return map[string]func(ctx *router.Context, file string) (string, error){
+ // Trooper files.
"trooper.js": h.legacyTrooper,
"current_trooper.json": h.legacyTrooper,
"current_trooper.txt": h.legacyTrooper,
+ // Sheriff files.
+ "sheriff.js": h.legacySheriff,
+ "sheriff_cros_mtv.js": h.legacySheriff,
+ "sheriff_cros_nonmtv.js": h.legacySheriff,
+ "sheriff_perf.js": h.legacySheriff,
+ "sheriff_cr_cros_gardeners.js": h.legacySheriff,
+ "sheriff_gpu.js": h.legacySheriff,
+ "sheriff_angle.js": h.legacySheriff,
+ "sheriff_android.js": h.legacySheriff,
+ "sheriff_ios.js": h.legacySheriff,
+ "sheriff_v8.js": h.legacySheriff,
+ "sheriff_perfbot.js": h.legacySheriff,
+ "sheriff.json": h.legacySheriff,
+ "sheriff_cros_mtv.json": h.legacySheriff,
+ "sheriff_cros_nonmtv.json": h.legacySheriff,
+ "sheriff_perf.json": h.legacySheriff,
+ "sheriff_cr_cros_gardeners.json": h.legacySheriff,
+ "sheriff_gpu.json": h.legacySheriff,
+ "sheriff_angle.json": h.legacySheriff,
+ "sheriff_android.json": h.legacySheriff,
+ "sheriff_ios.json": h.legacySheriff,
+ "sheriff_v8.json": h.legacySheriff,
+ "sheriff_perfbot.json": h.legacySheriff,
}
}
diff --git a/cmd/handlers/handlers_test.go b/cmd/handlers/handlers_test.go
index 0a28b46..7548d52 100644
--- a/cmd/handlers/handlers_test.go
+++ b/cmd/handlers/handlers_test.go
@@ -124,6 +124,13 @@
return f.oncallers, nil
}
+func (f *fakeCal) TrooperShifts(_ *router.Context, _, _ string, _ time.Time) ([]rotang.ShiftEntry, error) {
+ if f.fail {
+ return nil, status.Errorf(codes.Internal, "fake is failing as requested")
+ }
+ return f.ret, nil
+}
+
func TestNew(t *testing.T) {
tests := []struct {