Skip to content

Commit

Permalink
Use readiness from scoring watcher for status check
Browse files Browse the repository at this point in the history
  • Loading branch information
J12934 committed Dec 11, 2024
1 parent a43f10a commit a783a2d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 18 deletions.
2 changes: 1 addition & 1 deletion balancer/pkg/scoring/scoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (s *ScoringService) StartingScoringWorker(ctx context.Context) {
score := calculateScore(s.bundle, deployment, cachedChallengesMap)

if currentTeamScore, ok := s.currentScores[score.Name]; ok {
if reflect.DeepEqual(currentTeamScore.Score, score.Score) {
if reflect.DeepEqual(currentTeamScore, score) {
// No need to update, if the score hasn't changed
continue
}
Expand Down
19 changes: 6 additions & 13 deletions balancer/routes/teamStatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package routes

import (
"encoding/json"
"fmt"
"net/http"

"github.com/juice-shop/multi-juicer/balancer/pkg/bundle"
"github.com/juice-shop/multi-juicer/balancer/pkg/scoring"
"github.com/juice-shop/multi-juicer/balancer/pkg/teamcookie"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type TeamStatus struct {
Expand Down Expand Up @@ -47,21 +45,16 @@ func handleTeamStatus(bundle *bundle.Bundle, scoringSerivce *scoring.ScoringServ
return
}

deployment, err := bundle.ClientSet.AppsV1().Deployments(bundle.RuntimeEnvironment.Namespace).Get(req.Context(), fmt.Sprintf("juiceshop-%s", team), metav1.GetOptions{})
if err != nil {
http.Error(responseWriter, "team not found", http.StatusNotFound)
return
}

currentScores := scoringSerivce.GetScores()
teamScore, ok := currentScores[team]
teamCount := len(currentScores)
if !ok {
teamScore = &scoring.TeamScore{
Name: team,
Score: -1,
Position: -1,
Challenges: []scoring.ChallengeProgress{},
Name: team,
Score: -1,
Position: -1,
Challenges: []scoring.ChallengeProgress{},
InstanceReadiness: false,
}
// increment the total count by one as we know that this teams hasn't been counted yet
teamCount++
Expand All @@ -73,7 +66,7 @@ func handleTeamStatus(bundle *bundle.Bundle, scoringSerivce *scoring.ScoringServ
Position: teamScore.Position,
TotalTeams: teamCount,
SolvedChallenges: len(teamScore.Challenges),
Readiness: deployment.Status.ReadyReplicas == 1,
Readiness: teamScore.InstanceReadiness,
}

responseBytes, err := json.Marshal(response)
Expand Down
49 changes: 45 additions & 4 deletions balancer/routes/teamStatus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import (
"net/http/httptest"
"strings"
"testing"
"time"

"github.com/juice-shop/multi-juicer/balancer/pkg/scoring"
"github.com/juice-shop/multi-juicer/balancer/pkg/testutil"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing"
)

func TestTeamStatusHandler(t *testing.T) {
team := "foobar"

createTeam := func(team string, challenges string, solvedChallenges string) *appsv1.Deployment {
createTeamNumberOfReadyReplicas := func(team string, challenges string, solvedChallenges string, readyReplicas int32) *appsv1.Deployment {
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("juiceshop-%s", team),
Expand All @@ -35,10 +38,13 @@ func TestTeamStatusHandler(t *testing.T) {
},
},
Status: appsv1.DeploymentStatus{
ReadyReplicas: 1,
ReadyReplicas: readyReplicas,
},
}
}
createTeam := func(team string, challenges string, solvedChallenges string) *appsv1.Deployment {
return createTeamNumberOfReadyReplicas(team, challenges, solvedChallenges, 1)
}

t.Run("returns the instance status", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/balancer/api/teams/status", nil)
Expand Down Expand Up @@ -70,12 +76,47 @@ func TestTeamStatusHandler(t *testing.T) {
scoringService := scoring.NewScoringService(bundle)
scoringService.CalculateAndCacheScoreBoard(context.Background())
AddRoutes(server, bundle, scoringService)
clientset.AppsV1().Deployments(bundle.RuntimeEnvironment.Namespace).Create(context.Background(), createTeam("foobar", `[{"key":"scoreBoardChallenge","solvedAt":"2024-11-01T19:55:48.211Z"}]`, "1"), metav1.CreateOptions{})

server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)
assert.JSONEq(t, `{"name":"foobar","score":-1,"position":-1,"solvedChallenges":0,"totalTeams":2,"readiness":true}`, rr.Body.String())
assert.JSONEq(t, `{"name":"foobar","score":-1,"position":-1,"solvedChallenges":0,"totalTeams":2,"readiness":false}`, rr.Body.String())
})

t.Run("returns ready when instance gets update by the scoring watcher", func(t *testing.T) {
server := http.NewServeMux()
clientset := fake.NewSimpleClientset(createTeamNumberOfReadyReplicas(team, `[{"key":"scoreBoardChallenge","solvedAt":"2024-11-01T19:55:48.211Z"}]`, "1", 0))
watcher := watch.NewFake()
clientset.PrependWatchReactor("deployments", testcore.DefaultWatchReactor(watcher, nil))
bundle := testutil.NewTestBundleWithCustomFakeClient(clientset)
scoringService := scoring.NewScoringService(bundle)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
scoringService.CalculateAndCacheScoreBoard(ctx)
go scoringService.StartingScoringWorker(ctx)

AddRoutes(server, bundle, scoringService)

{
req, _ := http.NewRequest("GET", "/balancer/api/teams/status", nil)
rr := httptest.NewRecorder()
req.Header.Set("Cookie", fmt.Sprintf("team=%s", testutil.SignTestTeamname(team)))
server.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
assert.JSONEq(t, `{"name":"foobar","score":10,"position":1,"solvedChallenges":1,"totalTeams":1,"readiness":false}`, rr.Body.String())
}

watcher.Modify(createTeamNumberOfReadyReplicas(team, `[{"key":"scoreBoardChallenge","solvedAt":"2024-11-01T19:55:48.211Z"}]`, "1", 1))

// the watcher might not have updated the readiness yet
assert.Eventually(t, func() bool {
req, _ := http.NewRequest("GET", "/balancer/api/teams/status", nil)
rr := httptest.NewRecorder()
req.Header.Set("Cookie", fmt.Sprintf("team=%s", testutil.SignTestTeamname(team)))
server.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
return assert.JSONEq(t, `{"name":"foobar","score":10,"position":1,"solvedChallenges":1,"totalTeams":1,"readiness":true}`, rr.Body.String())
}, 1*time.Second, 10*time.Millisecond)
})

t.Run("returns a 404 if the team doesn't have a deployment", func(t *testing.T) {
Expand Down

0 comments on commit a783a2d

Please sign in to comment.