diff --git a/internal/adapters/storage/postgres/scheduler/db_scheduler.go b/internal/adapters/storage/postgres/scheduler/db_scheduler.go index 54c06522..acfadd44 100644 --- a/internal/adapters/storage/postgres/scheduler/db_scheduler.go +++ b/internal/adapters/storage/postgres/scheduler/db_scheduler.go @@ -63,7 +63,7 @@ type schedulerInfo struct { Annotations map[string]string Labels map[string]string LastDownscaleAt time.Time - MatchAllocation allocation.MatchAllocation + MatchAllocation *allocation.MatchAllocation } func NewDBScheduler(scheduler *entities.Scheduler) *Scheduler { @@ -99,6 +99,12 @@ func (s *Scheduler) ToScheduler() (*entities.Scheduler, error) { if err != nil { return nil, err } + + // Backward compatibility with schedulers that don't have MatchAllocation + if info.MatchAllocation == nil { + info.MatchAllocation = &allocation.MatchAllocation{MaxMatches: 1} + } + return &entities.Scheduler{ Name: s.Name, Game: s.Game, diff --git a/internal/adapters/storage/postgres/scheduler/db_scheduler_test.go b/internal/adapters/storage/postgres/scheduler/db_scheduler_test.go index 64395176..90e834c9 100644 --- a/internal/adapters/storage/postgres/scheduler/db_scheduler_test.go +++ b/internal/adapters/storage/postgres/scheduler/db_scheduler_test.go @@ -65,7 +65,7 @@ func TestScheduler_ToScheduler(t *testing.T) { Affinity: "affinity", }, RoomsReplicas: 0, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 1, }, }, @@ -85,7 +85,7 @@ func TestScheduler_ToScheduler(t *testing.T) { End: 60000, }, RoomsReplicas: 1, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 2, }, }, @@ -133,7 +133,7 @@ func TestScheduler_ToScheduler(t *testing.T) { TerminationGracePeriod: 60, }, RoomsReplicas: 2, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 3, }, }, @@ -185,7 +185,7 @@ func TestScheduler_ToScheduler(t *testing.T) { End: 60000, }, RoomsReplicas: 3, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 4, }, }, @@ -216,7 +216,7 @@ func TestScheduler_ToScheduler(t *testing.T) { }, Annotations: map[string]string{"imageregistry": "https://hub.docker.com/"}, Labels: map[string]string{"scheduler": "scheduler-name"}, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 5, }, }, @@ -247,7 +247,7 @@ func TestScheduler_ToScheduler(t *testing.T) { }, }, Annotations: map[string]string{"imageregistry": "https://hub.docker.com/"}, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 6, }, }, @@ -279,7 +279,7 @@ func TestScheduler_ToScheduler(t *testing.T) { }, }, Annotations: map[string]string{"imageregistry": "https://hub.docker.com/"}, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 7, }, }, @@ -310,7 +310,7 @@ func TestScheduler_ToScheduler(t *testing.T) { }, }, Annotations: map[string]string{"imageregistry": "https://hub.docker.com/"}, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 8, }, }, diff --git a/internal/adapters/storage/postgres/scheduler/scheduler_storage_pg_test.go b/internal/adapters/storage/postgres/scheduler/scheduler_storage_pg_test.go index 12c80bd2..37053fbc 100644 --- a/internal/adapters/storage/postgres/scheduler/scheduler_storage_pg_test.go +++ b/internal/adapters/storage/postgres/scheduler/scheduler_storage_pg_test.go @@ -28,6 +28,7 @@ package scheduler import ( "context" "fmt" + "github.com/stretchr/testify/assert" "sync/atomic" "testing" "time" @@ -91,6 +92,18 @@ func TestSchedulerStorage_GetScheduler(t *testing.T) { assertSchedulers(t, []*entities.Scheduler{expectedScheduler}, []*entities.Scheduler{actualScheduler}) }) + t.Run("scheduler exists and was created before multiple matches", func(t *testing.T) { + db := getPostgresDB(t) + storage := NewSchedulerStorage(db.Options()) + + err := storage.CreateScheduler(context.Background(), expectedScheduler) + require.NoError(t, err) + + actualScheduler, err := storage.GetScheduler(context.Background(), "scheduler") + require.NoError(t, err) + assert.NotNil(t, actualScheduler.MatchAllocation) + }) + t.Run("scheduler does not exists", func(t *testing.T) { db := getPostgresDB(t) storage := NewSchedulerStorage(db.Options()) diff --git a/internal/api/handlers/requestadapters/schedulers.go b/internal/api/handlers/requestadapters/schedulers.go index f693bd81..6be15d01 100644 --- a/internal/api/handlers/requestadapters/schedulers.go +++ b/internal/api/handlers/requestadapters/schedulers.go @@ -78,6 +78,10 @@ func FromApiPatchSchedulerRequestToChangeMap(request *api.PatchSchedulerRequest) patchMap[patch.LabelLabels] = request.GetLabels() } + if request.MatchAllocation != nil { + patchMap[patch.LabelMatchAllocation] = fromApiMatchAllocation(request.GetMatchAllocation()) + } + return patchMap } @@ -126,7 +130,7 @@ func FromApiCreateSchedulerRequestToEntity(request *api.CreateSchedulerRequest) fromApiForwarders(request.GetForwarders()), request.GetAnnotations(), request.GetLabels(), - *fromApiMatchAllocation(request.GetMatchAllocation()), + fromApiMatchAllocation(request.GetMatchAllocation()), ) } @@ -140,7 +144,7 @@ func FromEntitySchedulerToListResponse(entity *entities.Scheduler) *api.Schedule CreatedAt: timestamppb.New(entity.CreatedAt), MaxSurge: entity.MaxSurge, RoomsReplicas: int32(entity.RoomsReplicas), - MatchAllocation: fromEntityMatchAllocationToResponse(&entity.MatchAllocation), + MatchAllocation: fromEntityMatchAllocationToResponse(entity.MatchAllocation), } } @@ -165,7 +169,7 @@ func FromApiNewSchedulerVersionRequestToEntity(request *api.NewSchedulerVersionR fromApiForwarders(request.GetForwarders()), request.GetAnnotations(), request.GetLabels(), - *fromApiMatchAllocation(request.GetMatchAllocation()), + fromApiMatchAllocation(request.GetMatchAllocation()), ) } @@ -189,7 +193,7 @@ func FromEntitySchedulerToResponse(entity *entities.Scheduler) (*api.Scheduler, Forwarders: forwarders, Annotations: entity.Annotations, Labels: entity.Labels, - MatchAllocation: fromEntityMatchAllocationToResponse(&entity.MatchAllocation), + MatchAllocation: fromEntityMatchAllocationToResponse(entity.MatchAllocation), }, nil } @@ -337,7 +341,11 @@ func fromEntityForwardOptions(entity *forwarder.ForwardOptions) (*api.ForwarderO } func fromEntityMatchAllocationToResponse(entity *allocation.MatchAllocation) *api.MatchAllocation { - return &api.MatchAllocation{MaxMatches: int32(entity.MaxMatches)} + if entity != nil { + return &api.MatchAllocation{MaxMatches: int32(entity.MaxMatches)} + } + + return nil } func fromApiSpec(apiSpec *api.Spec) *game_room.Spec { @@ -503,12 +511,13 @@ func fromApiForwarders(apiForwarders []*api.Forwarder) []*forwarder.Forwarder { } func fromApiMatchAllocation(matchAllocation *api.MatchAllocation) *allocation.MatchAllocation { - maxMatches := int(matchAllocation.GetMaxMatches()) - if maxMatches == 0 { - maxMatches = 1 + if matchAllocation != nil { + return &allocation.MatchAllocation{ + MaxMatches: int(matchAllocation.GetMaxMatches()), + } } - return &allocation.MatchAllocation{MaxMatches: maxMatches} + return nil } func getPortRange(portRange *port.PortRange) *api.PortRange { diff --git a/internal/api/handlers/requestadapters/schedulers_test.go b/internal/api/handlers/requestadapters/schedulers_test.go index ee6e3749..5719c4c7 100644 --- a/internal/api/handlers/requestadapters/schedulers_test.go +++ b/internal/api/handlers/requestadapters/schedulers_test.go @@ -573,6 +573,9 @@ func TestFromApiCreateSchedulerRequestToEntity(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, + MatchAllocation: &api.MatchAllocation{ + MaxMatches: 2, + }, }, }, Output: Output{ @@ -674,8 +677,8 @@ func TestFromApiCreateSchedulerRequestToEntity(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, - MatchAllocation: allocation.MatchAllocation{ - MaxMatches: 1, + MatchAllocation: &allocation.MatchAllocation{ + MaxMatches: 2, }, }, }, @@ -764,6 +767,9 @@ func TestFromApiCreateSchedulerRequestToEntity(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, + MatchAllocation: &api.MatchAllocation{ + MaxMatches: 0, + }, }, }, Output: Output{ @@ -851,8 +857,8 @@ func TestFromApiCreateSchedulerRequestToEntity(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, - MatchAllocation: allocation.MatchAllocation{ - MaxMatches: 1, + MatchAllocation: &allocation.MatchAllocation{ + MaxMatches: 0, }, }, }, @@ -980,6 +986,9 @@ func TestFromEntitySchedulerToListResponse(t *testing.T) { }, }, }, + MatchAllocation: &allocation.MatchAllocation{ + MaxMatches: 0, + }, }, }, Output: Output{ @@ -1084,6 +1093,9 @@ func TestFromEntitySchedulerToListResponse(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, + MatchAllocation: &allocation.MatchAllocation{ + MaxMatches: 0, + }, }, }, Output: Output{ @@ -1239,6 +1251,9 @@ func TestFromApiNewSchedulerVersionRequestToEntity(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, + MatchAllocation: &api.MatchAllocation{ + MaxMatches: 1, + }, }, }, Output: Output{ @@ -1340,7 +1355,7 @@ func TestFromApiNewSchedulerVersionRequestToEntity(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 1, }, }, @@ -1477,6 +1492,9 @@ func TestFromEntitySchedulerToResponse(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, + MatchAllocation: &allocation.MatchAllocation{ + MaxMatches: 0, + }, }, }, Output: Output{ @@ -1661,6 +1679,9 @@ func TestFromEntitySchedulerToResponse(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, + MatchAllocation: &allocation.MatchAllocation{ + MaxMatches: 0, + }, }, }, Output: Output{ diff --git a/internal/api/handlers/schedulers_handler_test.go b/internal/api/handlers/schedulers_handler_test.go index bc7fc015..cae4cd8c 100644 --- a/internal/api/handlers/schedulers_handler_test.go +++ b/internal/api/handlers/schedulers_handler_test.go @@ -271,7 +271,7 @@ func TestGetScheduler(t *testing.T) { }, Annotations: map[string]string{}, Labels: map[string]string{}, - MatchAllocation: allocation.MatchAllocation{MaxMatches: 1}, + MatchAllocation: &allocation.MatchAllocation{MaxMatches: 1}, } schedulerCache.EXPECT().GetScheduler(gomock.Any(), gomock.Any()).Return(scheduler, nil) @@ -542,7 +542,7 @@ func TestCreateScheduler(t *testing.T) { }, Annotations: map[string]string{"imageregistry": "https://docker.hub.com/"}, Labels: map[string]string{"scheduler": "scheduler-name"}, - MatchAllocation: allocation.MatchAllocation{MaxMatches: 1}, + MatchAllocation: &allocation.MatchAllocation{MaxMatches: 1}, } schedulerStorage.EXPECT().CreateScheduler(gomock.Any(), gomock.Any()).Do( @@ -562,6 +562,7 @@ func TestCreateScheduler(t *testing.T) { } assert.Equal(t, scheduler.Annotations, arg.Annotations) assert.Equal(t, scheduler.Labels, arg.Labels) + assert.Equal(t, scheduler.MatchAllocation, arg.MatchAllocation) }, ).Return(nil) operationManager.EXPECT().CreateOperation(gomock.Any(), scheduler.Name, gomock.Any()).Return(&operation.Operation{ID: "id-1"}, nil) @@ -1077,6 +1078,27 @@ func TestPatchScheduler(t *testing.T) { Status: http.StatusInternalServerError, }, }, + { + Title: "When the scheduler was created before the MatchAllocation return 200", + Input: Input{ + Request: &api.PatchSchedulerRequest{ + RoomsReplicas: &roomsReplicas, + }, + }, + Mocks: Mocks{ + RequestFile: "scheduler-patch.json", + GetSchedulerReturn: newValidScheduler(), + GetSchedulerError: nil, + CreateOperationReturn: &operation.Operation{ + ID: "some-id", + }, + CreateOperationError: nil, + }, + Output: Output{ + Response: nil, + Status: http.StatusOK, + }, + }, } err := validations.RegisterValidations() @@ -1274,7 +1296,7 @@ func newValidScheduler() *entities.Scheduler { }, }, }, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 1, }, PortRange: &port.PortRange{ diff --git a/internal/core/entities/allocation/allocation.go b/internal/core/entities/allocation/allocation.go index 9a019a80..5793cd23 100644 --- a/internal/core/entities/allocation/allocation.go +++ b/internal/core/entities/allocation/allocation.go @@ -28,7 +28,3 @@ type MatchAllocation struct { // MaxMatches defined the maximum number of matches that a room can host. MaxMatches int `validate:"required,min=1,max=30"` } - -func NewDefaultMatchAllocation() *MatchAllocation { - return &MatchAllocation{MaxMatches: 1} -} diff --git a/internal/core/entities/scheduler.go b/internal/core/entities/scheduler.go index 40e6ed64..907f6c6f 100644 --- a/internal/core/entities/scheduler.go +++ b/internal/core/entities/scheduler.go @@ -74,7 +74,7 @@ type Scheduler struct { Forwarders []*forwarder.Forwarder `validate:"dive"` Annotations map[string]string Labels map[string]string - MatchAllocation allocation.MatchAllocation + MatchAllocation *allocation.MatchAllocation `validate:"required"` } // NewScheduler instantiate a new scheduler struct. @@ -90,7 +90,7 @@ func NewScheduler( forwarders []*forwarder.Forwarder, annotations map[string]string, labels map[string]string, - matchAllocation allocation.MatchAllocation, + matchAllocation *allocation.MatchAllocation, ) (*Scheduler, error) { scheduler := &Scheduler{ Name: name, diff --git a/internal/core/entities/scheduler_test.go b/internal/core/entities/scheduler_test.go index 799c6ef1..56a505b1 100644 --- a/internal/core/entities/scheduler_test.go +++ b/internal/core/entities/scheduler_test.go @@ -92,7 +92,7 @@ func TestNewScheduler(t *testing.T) { forwarders := []*forwarder.Forwarder{fwd} annotations := map[string]string{"imageregistry": "https://hub.docker.com/"} labels := map[string]string{"scheduler": "scheduler-name"} - matchAllocation := allocation.MatchAllocation{MaxMatches: 1} + matchAllocation := &allocation.MatchAllocation{MaxMatches: 1} t.Run("with success when create valid scheduler", func(t *testing.T) { scheduler, err := entities.NewScheduler( @@ -204,7 +204,7 @@ func TestNewScheduler(t *testing.T) { forwarders, annotations, labels, - allocation.MatchAllocation{}) + nil) require.Error(t, err) }) diff --git a/internal/core/services/autoscaler/autoscaler_test.go b/internal/core/services/autoscaler/autoscaler_test.go index 53067496..b13d355b 100644 --- a/internal/core/services/autoscaler/autoscaler_test.go +++ b/internal/core/services/autoscaler/autoscaler_test.go @@ -212,7 +212,7 @@ func TestCanDownscale(t *testing.T) { }, }, }, - MatchAllocation: allocation.MatchAllocation{MaxMatches: 1}, + MatchAllocation: &allocation.MatchAllocation{MaxMatches: 1}, } t.Run("Success cases", func(t *testing.T) { diff --git a/internal/core/services/autoscaler/policies/roomoccupancy/policy_test.go b/internal/core/services/autoscaler/policies/roomoccupancy/policy_test.go index 278dd991..560fa8e8 100644 --- a/internal/core/services/autoscaler/policies/roomoccupancy/policy_test.go +++ b/internal/core/services/autoscaler/policies/roomoccupancy/policy_test.go @@ -50,7 +50,7 @@ func TestCurrentStateBuilder(t *testing.T) { scheduler := &entities.Scheduler{ Name: "some-name", - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 1, }, } diff --git a/internal/core/services/rooms/status.go b/internal/core/services/rooms/status.go index eb682e65..63c63dd9 100644 --- a/internal/core/services/rooms/status.go +++ b/internal/core/services/rooms/status.go @@ -35,7 +35,7 @@ type StatusCalculator interface { } func NewStatusCalculator(scheduler entities.Scheduler, logger *zap.Logger) StatusCalculator { - if scheduler.MatchAllocation.MaxMatches > 1 { + if scheduler.MatchAllocation != nil && scheduler.MatchAllocation.MaxMatches > 1 { return &MultipleMatchStatusCalculator{ scheduler: scheduler, logger: logger, diff --git a/internal/core/services/rooms/status_test.go b/internal/core/services/rooms/status_test.go index b157ee90..4ab2a695 100644 --- a/internal/core/services/rooms/status_test.go +++ b/internal/core/services/rooms/status_test.go @@ -38,7 +38,7 @@ import ( func TestMultipleMatchStatusCalculator_CalculateRoomStatus(t *testing.T) { logger := zap.NewNop() scheduler := entities.Scheduler{ - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 3, }, } diff --git a/internal/core/services/schedulers/patch/patch_scheduler.go b/internal/core/services/schedulers/patch/patch_scheduler.go index 4824b888..0cb57630 100644 --- a/internal/core/services/schedulers/patch/patch_scheduler.go +++ b/internal/core/services/schedulers/patch/patch_scheduler.go @@ -24,6 +24,7 @@ package patch import ( "fmt" + "github.com/topfreegames/maestro/internal/core/entities/allocation" "time" "github.com/topfreegames/maestro/internal/core/entities/autoscaling" @@ -90,6 +91,8 @@ const ( LabelAnnotations = "annotations" // LabelLabels is the labels key in the patch map LabelLabels = "labels" + // LabelMatchAllocation is the match allocation key in the patch map. + LabelMatchAllocation = "match_allocation" ) // PatchScheduler function applies the patchMap in the scheduler, returning the patched Scheduler. @@ -151,6 +154,12 @@ func PatchScheduler(scheduler entities.Scheduler, patchMap map[string]interface{ } } + if _, ok := patchMap[LabelMatchAllocation]; ok { + if scheduler.MatchAllocation, ok = patchMap[LabelMatchAllocation].(*allocation.MatchAllocation); !ok { + return nil, fmt.Errorf("error parsing scheduler: match allocation malformed") + } + } + return &scheduler, nil } diff --git a/internal/core/services/schedulers/patch/patch_scheduler_test.go b/internal/core/services/schedulers/patch/patch_scheduler_test.go index b0db53c1..96db83a5 100644 --- a/internal/core/services/schedulers/patch/patch_scheduler_test.go +++ b/internal/core/services/schedulers/patch/patch_scheduler_test.go @@ -1033,7 +1033,7 @@ func basicSchedulerToPatchSchedulerTests() *entities.Scheduler { Start: 40000, End: 60000, }, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 1, }, } diff --git a/internal/core/services/schedulers/scheduler_manager_test.go b/internal/core/services/schedulers/scheduler_manager_test.go index 3447c69c..f8820192 100644 --- a/internal/core/services/schedulers/scheduler_manager_test.go +++ b/internal/core/services/schedulers/scheduler_manager_test.go @@ -1073,7 +1073,7 @@ func newValidScheduler() *entities.Scheduler { Start: 40000, End: 60000, }, - MatchAllocation: allocation.MatchAllocation{ + MatchAllocation: &allocation.MatchAllocation{ MaxMatches: 1, }, } diff --git a/test/data/handlers/fixtures/request/scheduler-config.json b/test/data/handlers/fixtures/request/scheduler-config.json index d1f922c7..12981899 100644 --- a/test/data/handlers/fixtures/request/scheduler-config.json +++ b/test/data/handlers/fixtures/request/scheduler-config.json @@ -70,5 +70,8 @@ }, "labels": { "scheduler": "scheduler-name" + }, + "matchAllocation": { + "maxMatches": 1 } }