From 979af9e00bf7d3dff5e3973f3c964a8bb1446584 Mon Sep 17 00:00:00 2001 From: Pedro Soares Date: Wed, 21 Aug 2024 14:51:00 -0300 Subject: [PATCH] debug(remove): log rooms being removed after sorted --- .../core/operations/rooms/remove/executor.go | 32 +- .../operations/rooms/remove/executor_test.go | 141 +++++--- internal/core/ports/mock/rooms_ports_mock.go | 9 +- internal/core/ports/room_ports.go | 6 +- internal/core/services/rooms/room_manager.go | 69 ++-- .../core/services/rooms/room_manager_test.go | 318 +++++++++--------- proto/apidocs.swagger.json | 2 +- 7 files changed, 324 insertions(+), 253 deletions(-) diff --git a/internal/core/operations/rooms/remove/executor.go b/internal/core/operations/rooms/remove/executor.go index 586de62a1..69d486e0c 100644 --- a/internal/core/operations/rooms/remove/executor.go +++ b/internal/core/operations/rooms/remove/executor.go @@ -26,7 +26,6 @@ import ( "context" "errors" "fmt" - "sync" "github.com/topfreegames/maestro/internal/core/entities/game_room" "github.com/topfreegames/maestro/internal/core/entities/operation" @@ -35,6 +34,7 @@ import ( "github.com/topfreegames/maestro/internal/core/ports" porterrors "github.com/topfreegames/maestro/internal/core/ports/errors" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "golang.org/x/sync/errgroup" ) @@ -110,31 +110,25 @@ func (e *Executor) removeRoomsByIDs(ctx context.Context, schedulerName string, r } func (e *Executor) removeRoomsByAmount(ctx context.Context, logger *zap.Logger, schedulerName string, amount int, op *operation.Operation, reason string) error { - rooms, err := e.roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", amount, &sync.Map{}) + activeScheduler, err := e.schedulerManager.GetActiveScheduler(ctx, schedulerName) if err != nil { return err } - activeScheduler, err := e.schedulerManager.GetActiveScheduler(ctx, schedulerName) + rooms, err := e.roomManager.ListRoomsWithDeletionPriority(ctx, activeScheduler, amount) if err != nil { - logger.Warn("error getting active scheduler, not sorting by version", zap.Error(err)) - } else { - var activeVersionRooms []*game_room.GameRoom - var mostPrioRoomsToBeRemoved []*game_room.GameRoom - for _, room := range rooms { - if room.Status == game_room.GameStatusOccupied || room.Status == game_room.GameStatusReady || room.Status == game_room.GameStatusPending { - if room.Version == activeScheduler.Spec.Version { - activeVersionRooms = append(activeVersionRooms, room) - } else { - mostPrioRoomsToBeRemoved = append(mostPrioRoomsToBeRemoved, room) - } - } else { - mostPrioRoomsToBeRemoved = append(mostPrioRoomsToBeRemoved, room) - } - } - rooms = append(mostPrioRoomsToBeRemoved, activeVersionRooms...) + return err } + logger.Info("removing rooms by amount sorting by version", + zap.Array("originalRoomsOrder", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, room := range rooms { + enc.AppendString(fmt.Sprintf("%s-%s-%s", room.ID, room.Version, room.Status.String())) + } + return nil + })), + ) + err = e.deleteRooms(ctx, rooms, op, reason) if err != nil { return err diff --git a/internal/core/operations/rooms/remove/executor_test.go b/internal/core/operations/rooms/remove/executor_test.go index c7c785587..b159b3b72 100644 --- a/internal/core/operations/rooms/remove/executor_test.go +++ b/internal/core/operations/rooms/remove/executor_test.go @@ -41,26 +41,25 @@ import ( "github.com/topfreegames/maestro/internal/core/entities/game_room" "github.com/topfreegames/maestro/internal/core/entities/operation" + "github.com/topfreegames/maestro/internal/core/entities/port" ) func TestExecutor_Execute(t *testing.T) { t.Run("RemoveRoom by Amount", func(t *testing.T) { - t.Run("should succeed - no rooms to be removed => returns without error", func(t *testing.T) { - executor, _, roomsManager, _, schedulerManager := testSetup(t) + t.Run("should fail - if fails to get active scheduler => returns error", func(t *testing.T) { + executor, _, _, _, schedulerManager := testSetup(t) - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{Amount: 2} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} ctx := context.Background() - emptyGameRoomSlice := []*game_room.GameRoom{} - schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), schedulerName).Return(nil, errors.New("error getting active scheduler")) - roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(emptyGameRoomSlice, nil) + schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), scheduler.Name).Return(nil, errors.New("error getting active scheduler")) err := executor.Execute(ctx, op, definition) - require.Nil(t, err) + require.NotNil(t, err) }) t.Run("should succeed and sort rooms by active scheduler version", func(t *testing.T) { @@ -108,7 +107,7 @@ func TestExecutor_Execute(t *testing.T) { availableRooms[7], // P2v2 } schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), schedulerName).Return(schedulerV2, nil) - roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) + roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) roomsManager.EXPECT().DeleteRoom(gomock.Any(), expectedSortedRoomsOrder[0], gomock.Any()).Times(1) roomsManager.EXPECT().DeleteRoom(gomock.Any(), expectedSortedRoomsOrder[1], gomock.Any()).Times(1) roomsManager.EXPECT().DeleteRoom(gomock.Any(), expectedSortedRoomsOrder[2], gomock.Any()).Times(1) @@ -125,17 +124,17 @@ func TestExecutor_Execute(t *testing.T) { t.Run("when any room failed to delete with unexpected error it returns with error", func(t *testing.T) { executor, _, roomsManager, operationManager, schedulerManager := testSetup(t) - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{Amount: 2, Reason: "reason"} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, - {ID: "second-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, + {ID: "first-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, + {ID: "second-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, } - schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), schedulerName).Return(nil, errors.New("error getting active scheduler")) - roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) + schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), scheduler.Name).Return(scheduler, nil) + roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) roomsManager.EXPECT().DeleteRoom(gomock.Any(), availableRooms[0], definition.Reason).Return(nil) @@ -149,17 +148,17 @@ func TestExecutor_Execute(t *testing.T) { t.Run("when any room failed to delete with timeout error it returns with error", func(t *testing.T) { executor, _, roomsManager, operationManager, schedulerManager := testSetup(t) - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{Amount: 2, Reason: "reason"} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, - {ID: "second-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, + {ID: "first-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, + {ID: "second-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, Metadata: map[string]interface{}{}}, } - schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), schedulerName).Return(nil, errors.New("error getting active scheduler")) - roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) + schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), scheduler.Name).Return(scheduler, nil) + roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) roomsManager.EXPECT().DeleteRoom(gomock.Any(), availableRooms[0], definition.Reason).Return(nil) roomsManager.EXPECT().DeleteRoom(gomock.Any(), availableRooms[1], definition.Reason).Return(serviceerrors.NewErrGameRoomStatusWaitingTimeout("some error")) @@ -170,13 +169,14 @@ func TestExecutor_Execute(t *testing.T) { }) t.Run("when list rooms has error returns with error", func(t *testing.T) { - executor, _, roomsManager, _, _ := testSetup(t) + executor, _, roomsManager, _, schedulerManager := testSetup(t) - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{Amount: 2} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} - roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("error")) + schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), scheduler.Name).Return(scheduler, nil) + roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, errors.New("error")) err := executor.Execute(context.Background(), op, definition) require.NotNil(t, err) @@ -188,9 +188,9 @@ func TestExecutor_Execute(t *testing.T) { t.Run("should succeed - no rooms to be removed => returns without error", func(t *testing.T) { executor, _, _, _, _ := testSetup(t) - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{RoomsIDs: []string{}} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} err := executor.Execute(context.Background(), op, definition) require.Nil(t, err) @@ -202,17 +202,17 @@ func TestExecutor_Execute(t *testing.T) { firstRoomID := "first-room-id" secondRoomID := "second-room-id" - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{RoomsIDs: []string{firstRoomID, secondRoomID}, Reason: "reason"} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} room := &game_room.GameRoom{ ID: firstRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } secondRoom := &game_room.GameRoom{ ID: secondRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } roomsManager.EXPECT().DeleteRoom(gomock.Any(), room, definition.Reason).Return(nil) @@ -228,17 +228,17 @@ func TestExecutor_Execute(t *testing.T) { firstRoomID := "first-room-id" secondRoomID := "second-room-id" - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{RoomsIDs: []string{firstRoomID, secondRoomID}, Reason: "reason"} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} room := &game_room.GameRoom{ ID: firstRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } secondRoom := &game_room.GameRoom{ ID: secondRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } roomsManager.EXPECT().DeleteRoom(gomock.Any(), room, definition.Reason).Return(nil) @@ -256,17 +256,17 @@ func TestExecutor_Execute(t *testing.T) { firstRoomID := "first-room-id" secondRoomID := "second-room-id" - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{RoomsIDs: []string{firstRoomID, secondRoomID}} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} room := &game_room.GameRoom{ ID: firstRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } secondRoom := &game_room.GameRoom{ ID: secondRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } roomsManager.EXPECT().DeleteRoom(gomock.Any(), room, definition.Reason).Return(nil) @@ -283,17 +283,17 @@ func TestExecutor_Execute(t *testing.T) { firstRoomID := "first-room-id" secondRoomID := "second-room-id" - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{RoomsIDs: []string{firstRoomID, secondRoomID}, Reason: "reason"} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} room := &game_room.GameRoom{ ID: firstRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } secondRoom := &game_room.GameRoom{ ID: secondRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, } roomsManager.EXPECT().DeleteRoom(gomock.Any(), room, definition.Reason).Return(nil) @@ -309,9 +309,9 @@ func TestExecutor_Execute(t *testing.T) { t.Run("should succeed - no rooms to be removed => returns without error", func(t *testing.T) { executor, _, _, _, _ := testSetup(t) - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{} - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} err := executor.Execute(context.Background(), op, definition) require.Nil(t, err) @@ -325,30 +325,30 @@ func TestExecutor_Execute(t *testing.T) { thirdRoomID := "third-room-id" fourthRoomID := "fourth-room-id" - schedulerName := uuid.NewString() + scheduler := newValidScheduler() definition := &Definition{ RoomsIDs: []string{firstRoomID, secondRoomID}, Amount: 2, Reason: "reason", } - op := &operation.Operation{ID: "random-uuid", SchedulerName: schedulerName} + op := &operation.Operation{ID: "random-uuid", SchedulerName: scheduler.Name} thirdRoom := &game_room.GameRoom{ ID: thirdRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, } fourthRoom := &game_room.GameRoom{ ID: fourthRoomID, - SchedulerID: schedulerName, + SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, } roomsManager.EXPECT().DeleteRoom(gomock.Any(), gomock.Any(), definition.Reason).Return(nil).Times(2) availableRooms := []*game_room.GameRoom{thirdRoom, fourthRoom} - schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), schedulerName).Return(nil, errors.New("error getting active scheduler")) - roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) + schedulerManager.EXPECT().GetActiveScheduler(gomock.Any(), scheduler.Name).Return(scheduler, nil) + roomsManager.EXPECT().ListRoomsWithDeletionPriority(gomock.Any(), gomock.Any(), gomock.Any()).Return(availableRooms, nil) roomsManager.EXPECT().DeleteRoom(gomock.Any(), gomock.Any(), definition.Reason).Return(nil).Times(2) err := executor.Execute(context.Background(), op, definition) @@ -366,3 +366,42 @@ func testSetup(t *testing.T) (*Executor, *mockports.MockRoomStorage, *mockports. executor := NewExecutor(roomsManager, roomsStorage, operationManager, schedulerManager) return executor, roomsStorage, roomsManager, operationManager, schedulerManager } + +func newValidScheduler() *entities.Scheduler { + return &entities.Scheduler{ + Name: "scheduler", + Game: "game", + State: entities.StateCreating, + MaxSurge: "5", + RollbackVersion: "", + Spec: game_room.Spec{ + Version: "v1.0.0", + TerminationGracePeriod: 60, + Toleration: "toleration", + Affinity: "affinity", + Containers: []game_room.Container{ + { + Name: "default", + Image: "some-image:v1", + ImagePullPolicy: "IfNotPresent", + Command: []string{"hello"}, + Ports: []game_room.ContainerPort{ + {Name: "tcp", Protocol: "tcp", Port: 80}, + }, + Requests: game_room.ContainerResources{ + CPU: "10m", + Memory: "100Mi", + }, + Limits: game_room.ContainerResources{ + CPU: "10m", + Memory: "100Mi", + }, + }, + }, + }, + PortRange: &port.PortRange{ + Start: 40000, + End: 60000, + }, + } +} diff --git a/internal/core/ports/mock/rooms_ports_mock.go b/internal/core/ports/mock/rooms_ports_mock.go index e66d9af31..4d457e048 100644 --- a/internal/core/ports/mock/rooms_ports_mock.go +++ b/internal/core/ports/mock/rooms_ports_mock.go @@ -7,7 +7,6 @@ package mock import ( context "context" reflect "reflect" - sync "sync" time "time" gomock "github.com/golang/mock/gomock" @@ -99,18 +98,18 @@ func (mr *MockRoomManagerMockRecorder) GetRoomInstance(ctx, scheduler, roomID in } // ListRoomsWithDeletionPriority mocks base method. -func (m *MockRoomManager) ListRoomsWithDeletionPriority(ctx context.Context, schedulerName, ignoredVersion string, amount int, roomsBeingReplaced *sync.Map) ([]*game_room.GameRoom, error) { +func (m *MockRoomManager) ListRoomsWithDeletionPriority(ctx context.Context, activeScheduler *entities.Scheduler, amount int) ([]*game_room.GameRoom, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListRoomsWithDeletionPriority", ctx, schedulerName, ignoredVersion, amount, roomsBeingReplaced) + ret := m.ctrl.Call(m, "ListRoomsWithDeletionPriority", ctx, activeScheduler, amount) ret0, _ := ret[0].([]*game_room.GameRoom) ret1, _ := ret[1].(error) return ret0, ret1 } // ListRoomsWithDeletionPriority indicates an expected call of ListRoomsWithDeletionPriority. -func (mr *MockRoomManagerMockRecorder) ListRoomsWithDeletionPriority(ctx, schedulerName, ignoredVersion, amount, roomsBeingReplaced interface{}) *gomock.Call { +func (mr *MockRoomManagerMockRecorder) ListRoomsWithDeletionPriority(ctx, activeScheduler, amount interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRoomsWithDeletionPriority", reflect.TypeOf((*MockRoomManager)(nil).ListRoomsWithDeletionPriority), ctx, schedulerName, ignoredVersion, amount, roomsBeingReplaced) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRoomsWithDeletionPriority", reflect.TypeOf((*MockRoomManager)(nil).ListRoomsWithDeletionPriority), ctx, activeScheduler, amount) } // SchedulerMaxSurge mocks base method. diff --git a/internal/core/ports/room_ports.go b/internal/core/ports/room_ports.go index dc4ec762f..fd19dc873 100644 --- a/internal/core/ports/room_ports.go +++ b/internal/core/ports/room_ports.go @@ -24,7 +24,6 @@ package ports import ( "context" - "sync" "time" "github.com/topfreegames/maestro/internal/core/entities" @@ -42,8 +41,7 @@ type RoomManager interface { // the number of rooms the scheduler has. SchedulerMaxSurge(ctx context.Context, scheduler *entities.Scheduler) (int, error) // ListRoomsWithDeletionPriority returns a specified number of rooms, following - // the priority of it being deleted and filtering the ignored version, - // the function will return rooms discarding such filter option. + // the priority of it being deleted // // The priority is: // @@ -55,7 +53,7 @@ type RoomManager interface { // // This function can return less rooms than the `amount` since it might not have // enough rooms on the scheduler. - ListRoomsWithDeletionPriority(ctx context.Context, schedulerName, ignoredVersion string, amount int, roomsBeingReplaced *sync.Map) ([]*game_room.GameRoom, error) + ListRoomsWithDeletionPriority(ctx context.Context, activeScheduler *entities.Scheduler, amount int) ([]*game_room.GameRoom, error) // CleanRoomState cleans the remaining state of a room. This function is // intended to be used after a `DeleteRoom`, where the room instance is // signaled to terminate. diff --git a/internal/core/services/rooms/room_manager.go b/internal/core/services/rooms/room_manager.go index 28d038b29..eadd3c8e1 100644 --- a/internal/core/services/rooms/room_manager.go +++ b/internal/core/services/rooms/room_manager.go @@ -29,7 +29,6 @@ import ( "math" "strconv" "strings" - "sync" "time" serviceerrors "github.com/topfreegames/maestro/internal/core/services/errors" @@ -38,6 +37,7 @@ import ( "github.com/topfreegames/maestro/internal/core/logs" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/topfreegames/maestro/internal/core/entities" "github.com/topfreegames/maestro/internal/core/entities/game_room" @@ -201,30 +201,30 @@ func (m *RoomManager) CleanRoomState(ctx context.Context, schedulerName, roomId return nil } -func (m *RoomManager) ListRoomsWithDeletionPriority(ctx context.Context, schedulerName, ignoredVersion string, amount int, roomsBeingReplaced *sync.Map) ([]*game_room.GameRoom, error) { +func (m *RoomManager) ListRoomsWithDeletionPriority(ctx context.Context, activeScheduler *entities.Scheduler, amount int) ([]*game_room.GameRoom, error) { var schedulerRoomsIDs []string - onErrorRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError) + onErrorRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, activeScheduler.Name, game_room.GameStatusError) if err != nil { return nil, fmt.Errorf("failed to list scheduler rooms on error: %w", err) } - oldLastPingRoomIDs, err := m.RoomStorage.GetRoomIDsByLastPing(ctx, schedulerName, time.Now().Add(m.Config.RoomPingTimeout*-1)) + oldLastPingRoomIDs, err := m.RoomStorage.GetRoomIDsByLastPing(ctx, activeScheduler.Name, time.Now().Add(m.Config.RoomPingTimeout*-1)) if err != nil { return nil, fmt.Errorf("failed to list scheduler rooms with old last ping datetime: %w", err) } - pendingRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusPending) + pendingRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, activeScheduler.Name, game_room.GameStatusPending) if err != nil { return nil, fmt.Errorf("failed to list scheduler rooms on pending status: %w", err) } - readyRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusReady) + readyRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, activeScheduler.Name, game_room.GameStatusReady) if err != nil { return nil, fmt.Errorf("failed to list scheduler rooms on ready status: %w", err) } - occupiedRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusOccupied) + occupiedRoomIDs, err := m.RoomStorage.GetRoomIDsByStatus(ctx, activeScheduler.Name, game_room.GameStatusOccupied) if err != nil { return nil, fmt.Errorf("failed to list scheduler rooms on occupied status: %w", err) } @@ -234,24 +234,38 @@ func (m *RoomManager) ListRoomsWithDeletionPriority(ctx context.Context, schedul schedulerRoomsIDs = append(schedulerRoomsIDs, pendingRoomIDs...) schedulerRoomsIDs = append(schedulerRoomsIDs, readyRoomIDs...) schedulerRoomsIDs = append(schedulerRoomsIDs, occupiedRoomIDs...) + + m.Logger.Info("schedulerRoomsIDs", + zap.Array("schedulerRoomsIDs", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, room := range schedulerRoomsIDs { + enc.AppendString(room) + } + return nil + })), + ) + schedulerRoomsIDs = removeDuplicateValues(schedulerRoomsIDs) + m.Logger.Info("schedulerRoomsIDs after duplicates", + zap.Array("schedulerRoomsIDs", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, room := range schedulerRoomsIDs { + enc.AppendString(room) + } + return nil + })), + ) + + var activeVersionRoomPool []*game_room.GameRoom var toDeleteRooms []*game_room.GameRoom var terminatingRooms []*game_room.GameRoom for _, roomID := range schedulerRoomsIDs { - room, err := m.RoomStorage.GetRoom(ctx, schedulerName, roomID) + room, err := m.RoomStorage.GetRoom(ctx, activeScheduler.Name, roomID) if err != nil { if !errors.Is(err, porterrors.ErrNotFound) { return nil, fmt.Errorf("failed to fetch room information: %w", err) } - room = &game_room.GameRoom{ID: roomID, SchedulerID: schedulerName, Status: game_room.GameStatusError} - } - - _, roomIsBeingReplaced := roomsBeingReplaced.Load(room.ID) - - if roomIsBeingReplaced { - continue + room = &game_room.GameRoom{ID: roomID, SchedulerID: activeScheduler.Name, Status: game_room.GameStatusError} } // Select Terminating rooms to be re-deleted. This is useful for fixing any desync state. @@ -260,14 +274,27 @@ func (m *RoomManager) ListRoomsWithDeletionPriority(ctx context.Context, schedul continue } - if ignoredVersion != "" && ignoredVersion == room.Version { - continue + isRoomActive := room.Status == game_room.GameStatusOccupied || room.Status == game_room.GameStatusReady || room.Status == game_room.GameStatusPending + if isRoomActive && room.Version == activeScheduler.Spec.Version { + activeVersionRoomPool = append(activeVersionRoomPool, room) + } else { + toDeleteRooms = append(toDeleteRooms, room) } + } + toDeleteRooms = append(toDeleteRooms, activeVersionRoomPool...) - toDeleteRooms = append(toDeleteRooms, room) - if len(toDeleteRooms) == amount { - break - } + m.Logger.Info("toDeleteRooms", + zap.Array("toDeleteRooms uncapped", zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + for _, room := range toDeleteRooms { + enc.AppendString(fmt.Sprintf("%s-%s-%s", room.ID, room.Version, room.Status.String())) + } + return nil + })), + zap.Int("amount", amount), + ) + + if len(toDeleteRooms) > amount { + toDeleteRooms = toDeleteRooms[:amount] } result := append(toDeleteRooms, terminatingRooms...) diff --git a/internal/core/services/rooms/room_manager_test.go b/internal/core/services/rooms/room_manager_test.go index a2e6e6c06..e6feaa5fa 100644 --- a/internal/core/services/rooms/room_manager_test.go +++ b/internal/core/services/rooms/room_manager_test.go @@ -29,7 +29,6 @@ import ( "context" "errors" "fmt" - "sync" "testing" "time" @@ -462,117 +461,115 @@ func TestRoomManager_ListRoomsWithDeletionPriority(t *testing.T) { eventsService, RoomManagerConfig{RoomPingTimeout: time.Hour}, ) - roomsBeingReplaced := &sync.Map{} t.Run("when there are enough rooms it should return the specified number", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" - schedulerLastVersion := "v1.2.3" + scheduler := newValidScheduler() availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Version: schedulerLastVersion, Status: game_room.GameStatusError}, - {ID: "second-room", SchedulerID: schedulerName, Version: schedulerLastVersion, Status: game_room.GameStatusReady}, - {ID: "third-room", SchedulerID: schedulerName, Version: schedulerLastVersion, Status: game_room.GameStatusPending}, - {ID: "forth-room", SchedulerID: schedulerName, Version: schedulerLastVersion, Status: game_room.GameStatusReady}, - {ID: "fifth-room", SchedulerID: schedulerName, Version: schedulerLastVersion, Status: game_room.GameStatusOccupied}, + {ID: "first-room", SchedulerID: scheduler.Name, Version: scheduler.Spec.Version, Status: game_room.GameStatusError}, + {ID: "second-room", SchedulerID: scheduler.Name, Version: scheduler.Spec.Version, Status: game_room.GameStatusReady}, + {ID: "third-room", SchedulerID: scheduler.Name, Version: scheduler.Spec.Version, Status: game_room.GameStatusPending}, + {ID: "forth-room", SchedulerID: scheduler.Name, Version: scheduler.Spec.Version, Status: game_room.GameStatusReady}, + {ID: "fifth-room", SchedulerID: scheduler.Name, Version: scheduler.Spec.Version, Status: game_room.GameStatusOccupied}, } roomStorage.EXPECT(). - GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError). + GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusError). Return([]string{availableRooms[0].ID}, nil) roomStorage.EXPECT(). - GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()). + GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()). Return([]string{availableRooms[1].ID}, nil) roomStorage.EXPECT(). - GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusPending). + GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusPending). Return([]string{availableRooms[2].ID}, nil) roomStorage.EXPECT(). - GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusReady). + GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusReady). Return([]string{availableRooms[3].ID}, nil) roomStorage.EXPECT(). - GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusOccupied). + GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusOccupied). Return([]string{availableRooms[4].ID, availableRooms[1].ID}, nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[0].ID).Return(availableRooms[0], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[1].ID).Return(availableRooms[1], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[2].ID).Return(availableRooms[2], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[3].ID).Return(availableRooms[3], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[4].ID).Return(availableRooms[4], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[0].ID).Return(availableRooms[0], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[1].ID).Return(availableRooms[1], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[2].ID).Return(availableRooms[2], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[3].ID).Return(availableRooms[3], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[4].ID).Return(availableRooms[4], nil) - rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "v1.2.2", 5, roomsBeingReplaced) + rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 5) require.NoError(t, err) require.Len(t, rooms, 5) }) t.Run("when error happens while fetching on-error room ids it returns error", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() getRoomIDsErr := errors.New("failed to get rooms IDs") - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError).Return(nil, getRoomIDsErr) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusError).Return(nil, getRoomIDsErr) - _, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + _, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.Error(t, err) require.ErrorIs(t, err, getRoomIDsErr) }) t.Run("when error happens while fetching old ping room ids it returns error", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() getRoomIDsErr := errors.New("failed to get rooms IDs") - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return(nil, getRoomIDsErr) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusError).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return(nil, getRoomIDsErr) - _, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + _, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.Error(t, err) require.ErrorIs(t, err, getRoomIDsErr) }) t.Run("when error happens while fetching pending room ids it returns error", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() getRoomIDsErr := errors.New("failed to get rooms IDs") - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusPending).Return(nil, getRoomIDsErr) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusError).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusPending).Return(nil, getRoomIDsErr) - _, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + _, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.Error(t, err) require.ErrorIs(t, err, getRoomIDsErr) }) t.Run("when error happens while fetching ready room ids it returns error", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() getRoomIDsErr := errors.New("failed to get rooms IDs") - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusPending).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusReady).Return(nil, getRoomIDsErr) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusError).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusPending).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusReady).Return(nil, getRoomIDsErr) - _, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + _, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.Error(t, err) require.ErrorIs(t, err, getRoomIDsErr) }) t.Run("when error happens while fetching occupied room ids it returns error", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() getRoomIDsErr := errors.New("failed to get rooms IDs") - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusError).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusPending).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusReady).Return([]string{}, nil) - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, game_room.GameStatusOccupied).Return(nil, getRoomIDsErr) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusError).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusPending).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusReady).Return([]string{}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, game_room.GameStatusOccupied).Return(nil, getRoomIDsErr) - _, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + _, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.Error(t, err) require.ErrorIs(t, err, getRoomIDsErr) }) @@ -581,107 +578,85 @@ func TestRoomManager_ListRoomsWithDeletionPriority(t *testing.T) { // a room that could be only existent in one of them should be deleted as well. t.Run("when fetching a room that does not exists returns room with ID", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady}, + {ID: "first-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady}, } notFoundRoomID := "second-room" - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[0].ID}, nil).AnyTimes() - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{notFoundRoomID}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, gomock.Any()).Return([]string{availableRooms[0].ID}, nil).AnyTimes() + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{notFoundRoomID}, nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[0].ID).Return(availableRooms[0], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[0].ID).Return(availableRooms[0], nil) getRoomErr := porterrors.NewErrNotFound("failed to get") - roomStorage.EXPECT().GetRoom(ctx, schedulerName, notFoundRoomID).Return(nil, getRoomErr) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, notFoundRoomID).Return(nil, getRoomErr) - rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.NoError(t, err) - availableRooms = append(availableRooms, &game_room.GameRoom{ID: notFoundRoomID, SchedulerID: schedulerName, Status: game_room.GameStatusError}) + availableRooms = append(availableRooms, &game_room.GameRoom{ID: notFoundRoomID, SchedulerID: scheduler.Name, Status: game_room.GameStatusError}) require.Equal(t, rooms, availableRooms) }) t.Run("when error happens while fetch a room it returns error", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" + scheduler := newValidScheduler() availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady}, - {ID: "second-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady}, + {ID: "first-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady}, + {ID: "second-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady}, } - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[0].ID}, nil).AnyTimes() - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[1].ID}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, gomock.Any()).Return([]string{availableRooms[0].ID}, nil).AnyTimes() + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{availableRooms[1].ID}, nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[0].ID).Return(availableRooms[0], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[0].ID).Return(availableRooms[0], nil) getRoomErr := errors.New("failed to get") - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[1].ID).Return(nil, getRoomErr) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[1].ID).Return(nil, getRoomErr) - _, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, "", 2, roomsBeingReplaced) + _, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.Error(t, err) require.ErrorIs(t, err, getRoomErr) }) - t.Run("when no room matches version returns an empty list", func(t *testing.T) { - ctx := context.Background() - schedulerName := "test-scheduler" - ignoredVersion := "v1.2.3" - availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Version: ignoredVersion}, - {ID: "second-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Version: ignoredVersion}, - } - - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[0].ID}, nil).AnyTimes() - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[1].ID}, nil) - - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[0].ID).Return(availableRooms[0], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[1].ID).Return(availableRooms[1], nil) - - rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, ignoredVersion, 2, roomsBeingReplaced) - require.NoError(t, err) - require.Empty(t, rooms) - }) - t.Run("when retrieving rooms with terminating status it returns them", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" - ignoredVersion := "v1.2.3" + scheduler := newValidScheduler() availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusTerminating, Version: "v1.1.1"}, - {ID: "second-room", SchedulerID: schedulerName, Status: game_room.GameStatusTerminating, Version: "v1.1.1"}, + {ID: "first-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusTerminating}, + {ID: "second-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusTerminating}, } - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, gomock.Any()).Return([]string{}, nil).AnyTimes() - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[0].ID, availableRooms[1].ID}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, gomock.Any()).Return([]string{}, nil).AnyTimes() + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{availableRooms[0].ID, availableRooms[1].ID}, nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[0].ID).Return(availableRooms[0], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[1].ID).Return(availableRooms[1], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[0].ID).Return(availableRooms[0], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[1].ID).Return(availableRooms[1], nil) - rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, ignoredVersion, 2, roomsBeingReplaced) + rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.NoError(t, err) require.Equal(t, availableRooms, rooms) }) t.Run("when retrieving rooms with terminating status always includes them", func(t *testing.T) { ctx := context.Background() - schedulerName := "test-scheduler" - ignoredVersion := "v1.2.3" + scheduler := newValidScheduler() availableRooms := []*game_room.GameRoom{ - {ID: "first-room", SchedulerID: schedulerName, Status: game_room.GameStatusReady, Version: "v1.1.1"}, - {ID: "second-room", SchedulerID: schedulerName, Status: game_room.GameStatusTerminating, Version: "v1.1.1"}, - {ID: "third-room", SchedulerID: schedulerName, Status: game_room.GameStatusTerminating, Version: "v1.1.1"}, + {ID: "first-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusReady, Version: "v1.1.1"}, + {ID: "second-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusTerminating, Version: "v1.1.1"}, + {ID: "third-room", SchedulerID: scheduler.Name, Status: game_room.GameStatusTerminating, Version: "v1.1.1"}, } - roomStorage.EXPECT().GetRoomIDsByStatus(ctx, schedulerName, gomock.Any()).Return([]string{}, nil).AnyTimes() - roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, schedulerName, gomock.Any()).Return([]string{availableRooms[0].ID, availableRooms[1].ID, availableRooms[2].ID}, nil) + roomStorage.EXPECT().GetRoomIDsByStatus(ctx, scheduler.Name, gomock.Any()).Return([]string{}, nil).AnyTimes() + roomStorage.EXPECT().GetRoomIDsByLastPing(ctx, scheduler.Name, gomock.Any()).Return([]string{availableRooms[0].ID, availableRooms[1].ID, availableRooms[2].ID}, nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[0].ID).Return(availableRooms[0], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[1].ID).Return(availableRooms[1], nil) - roomStorage.EXPECT().GetRoom(ctx, schedulerName, availableRooms[2].ID).Return(availableRooms[2], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[0].ID).Return(availableRooms[0], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[1].ID).Return(availableRooms[1], nil) + roomStorage.EXPECT().GetRoom(ctx, scheduler.Name, availableRooms[2].ID).Return(availableRooms[2], nil) - rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, schedulerName, ignoredVersion, 2, roomsBeingReplaced) + rooms, err := roomManager.ListRoomsWithDeletionPriority(ctx, scheduler, 2) require.NoError(t, err) require.Equal(t, availableRooms, rooms) }) @@ -749,46 +724,46 @@ func TestRoomManager_CleanRoomState(t *testing.T) { eventsService, config, ) - schedulerName := "scheduler-name" + scheduler := newValidScheduler() roomId := "some-unique-room-id" t.Run("when room and instance deletions do not return error", func(t *testing.T) { - roomStorage.EXPECT().DeleteRoom(context.Background(), schedulerName, roomId).Return(nil) - instanceStorage.EXPECT().DeleteInstance(context.Background(), schedulerName, roomId).Return(nil) + roomStorage.EXPECT().DeleteRoom(context.Background(), scheduler.Name, roomId).Return(nil) + instanceStorage.EXPECT().DeleteInstance(context.Background(), scheduler.Name, roomId).Return(nil) eventsService.EXPECT().ProduceEvent(context.Background(), gomock.Any()).Return(nil) - err := roomManager.CleanRoomState(context.Background(), schedulerName, roomId) + err := roomManager.CleanRoomState(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) t.Run("when room is not found but instance is, returns no error", func(t *testing.T) { - roomStorage.EXPECT().DeleteRoom(context.Background(), schedulerName, roomId).Return(porterrors.ErrNotFound) - instanceStorage.EXPECT().DeleteInstance(context.Background(), schedulerName, roomId).Return(nil) + roomStorage.EXPECT().DeleteRoom(context.Background(), scheduler.Name, roomId).Return(porterrors.ErrNotFound) + instanceStorage.EXPECT().DeleteInstance(context.Background(), scheduler.Name, roomId).Return(nil) eventsService.EXPECT().ProduceEvent(context.Background(), gomock.Any()).Return(nil) - err := roomManager.CleanRoomState(context.Background(), schedulerName, roomId) + err := roomManager.CleanRoomState(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) t.Run("when room is present but instance isn't, returns no error", func(t *testing.T) { - roomStorage.EXPECT().DeleteRoom(context.Background(), schedulerName, roomId).Return(nil) - instanceStorage.EXPECT().DeleteInstance(context.Background(), schedulerName, roomId).Return(porterrors.ErrNotFound) + roomStorage.EXPECT().DeleteRoom(context.Background(), scheduler.Name, roomId).Return(nil) + instanceStorage.EXPECT().DeleteInstance(context.Background(), scheduler.Name, roomId).Return(porterrors.ErrNotFound) eventsService.EXPECT().ProduceEvent(context.Background(), gomock.Any()).Return(nil) - err := roomManager.CleanRoomState(context.Background(), schedulerName, roomId) + err := roomManager.CleanRoomState(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) t.Run("when deletions returns unexpected error, returns error", func(t *testing.T) { - roomStorage.EXPECT().DeleteRoom(context.Background(), schedulerName, roomId).Return(porterrors.ErrUnexpected) + roomStorage.EXPECT().DeleteRoom(context.Background(), scheduler.Name, roomId).Return(porterrors.ErrUnexpected) - err := roomManager.CleanRoomState(context.Background(), schedulerName, roomId) + err := roomManager.CleanRoomState(context.Background(), scheduler.Name, roomId) require.Error(t, err) - roomStorage.EXPECT().DeleteRoom(context.Background(), schedulerName, roomId).Return(nil) - instanceStorage.EXPECT().DeleteInstance(context.Background(), schedulerName, roomId).Return(porterrors.ErrUnexpected) + roomStorage.EXPECT().DeleteRoom(context.Background(), scheduler.Name, roomId).Return(nil) + instanceStorage.EXPECT().DeleteInstance(context.Background(), scheduler.Name, roomId).Return(porterrors.ErrUnexpected) - err = roomManager.CleanRoomState(context.Background(), schedulerName, roomId) + err = roomManager.CleanRoomState(context.Background(), scheduler.Name, roomId) require.Error(t, err) }) } @@ -1145,122 +1120,122 @@ func TestUpdateGameRoomStatus(t *testing.T) { t.Run("when game room exists and changes states, it should return no error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, instanceStorage, roomManager, _ := setup(mockCtrl) room := &game_room.GameRoom{PingStatus: game_room.GameRoomPingStatusReady, Status: game_room.GameStatusPending, Metadata: map[string]interface{}{}} - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(room, nil) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(room, nil) instance := &game_room.Instance{Status: game_room.InstanceStatus{Type: game_room.InstanceReady}} - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(instance, nil) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(instance, nil) - roomStorage.EXPECT().UpdateRoomStatus(context.Background(), schedulerName, roomId, game_room.GameStatusReady) + roomStorage.EXPECT().UpdateRoomStatus(context.Background(), scheduler.Name, roomId, game_room.GameStatusReady) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) t.Run("when game room exists and there is not state transition, it should not update the room status and return no error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, instanceStorage, roomManager, _ := setup(mockCtrl) room := &game_room.GameRoom{PingStatus: game_room.GameRoomPingStatusReady, Status: game_room.GameStatusReady} - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(room, nil) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(room, nil) instance := &game_room.Instance{Status: game_room.InstanceStatus{Type: game_room.InstanceReady}} - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(instance, nil) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(instance, nil) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) t.Run("when game room doesn't exists, it should return error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, _, roomManager, _ := setup(mockCtrl) - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(nil, porterrors.ErrNotFound) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(nil, porterrors.ErrNotFound) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.Error(t, err) }) t.Run("when game room instance doesn't exists, it should return error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, instanceStorage, roomManager, _ := setup(mockCtrl) room := &game_room.GameRoom{PingStatus: game_room.GameRoomPingStatusReady, Status: game_room.GameStatusPending} - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(room, nil) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(room, nil) - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(nil, porterrors.ErrNotFound) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(nil, porterrors.ErrNotFound) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.Error(t, err) }) t.Run("when game room exists and state transition is invalid, it should return error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, instanceStorage, roomManager, _ := setup(mockCtrl) room := &game_room.GameRoom{PingStatus: game_room.GameRoomPingStatusReady, Status: game_room.GameStatusTerminating} - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(room, nil) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(room, nil) instance := &game_room.Instance{Status: game_room.InstanceStatus{Type: game_room.InstancePending}} - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(instance, nil) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(instance, nil) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.Error(t, err) }) t.Run("When instance status is terminating, forward ping", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, instanceStorage, roomManager, eventsService := setup(mockCtrl) room := &game_room.GameRoom{PingStatus: game_room.GameRoomPingStatusTerminating, Status: game_room.GameStatusReady} - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(room, nil) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(room, nil) instance := &game_room.Instance{Status: game_room.InstanceStatus{Type: game_room.InstanceTerminating}} - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(instance, nil) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(instance, nil) - roomStorage.EXPECT().UpdateRoomStatus(context.Background(), schedulerName, roomId, game_room.GameStatusTerminating).Return(nil) + roomStorage.EXPECT().UpdateRoomStatus(context.Background(), scheduler.Name, roomId, game_room.GameStatusTerminating).Return(nil) eventsService.EXPECT().ProduceEvent(context.Background(), gomock.Any()).Return(nil) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) t.Run("When instance status is terminating, and game room is deleted from storage, forward ping", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" roomStorage, instanceStorage, roomManager, eventsService := setup(mockCtrl) room := &game_room.GameRoom{PingStatus: game_room.GameRoomPingStatusTerminating, Status: game_room.GameStatusReady} - roomStorage.EXPECT().GetRoom(context.Background(), schedulerName, roomId).Return(room, nil) + roomStorage.EXPECT().GetRoom(context.Background(), scheduler.Name, roomId).Return(room, nil) instance := &game_room.Instance{Status: game_room.InstanceStatus{Type: game_room.InstanceTerminating}} - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(instance, nil) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(instance, nil) - roomStorage.EXPECT().UpdateRoomStatus(context.Background(), schedulerName, roomId, game_room.GameStatusTerminating).Return(porterrors.ErrNotFound) + roomStorage.EXPECT().UpdateRoomStatus(context.Background(), scheduler.Name, roomId, game_room.GameStatusTerminating).Return(porterrors.ErrNotFound) eventsService.EXPECT().ProduceEvent(context.Background(), gomock.Any()).Return(nil) - err := roomManager.UpdateGameRoomStatus(context.Background(), schedulerName, roomId) + err := roomManager.UpdateGameRoomStatus(context.Background(), scheduler.Name, roomId) require.NoError(t, err) }) } @@ -1286,14 +1261,14 @@ func TestRoomManager_GetRoomInstance(t *testing.T) { t.Run("when no error occurs return game room instance and no error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" _, instanceStorage, roomManager, _ := setup(mockCtrl) instance := &game_room.Instance{Status: game_room.InstanceStatus{Type: game_room.InstanceReady}} - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(instance, nil) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(instance, nil) - address, err := roomManager.GetRoomInstance(context.Background(), schedulerName, roomId) + address, err := roomManager.GetRoomInstance(context.Background(), scheduler.Name, roomId) require.NoError(t, err) require.Equal(t, instance, address) }) @@ -1301,13 +1276,13 @@ func TestRoomManager_GetRoomInstance(t *testing.T) { t.Run("when some error occurs in instance storage it returns error", func(t *testing.T) { mockCtrl := gomock.NewController(t) - schedulerName := "schedulerName" + scheduler := newValidScheduler() roomId := "room-id" _, instanceStorage, roomManager, _ := setup(mockCtrl) - instanceStorage.EXPECT().GetInstance(context.Background(), schedulerName, roomId).Return(nil, errors.New("some error")) + instanceStorage.EXPECT().GetInstance(context.Background(), scheduler.Name, roomId).Return(nil, errors.New("some error")) - _, err := roomManager.GetRoomInstance(context.Background(), schedulerName, roomId) + _, err := roomManager.GetRoomInstance(context.Background(), scheduler.Name, roomId) require.EqualError(t, err, "error getting instance: some error") }) @@ -1343,3 +1318,42 @@ func testSetup(t *testing.T) ( return roomManager, config, roomStorage, instanceStorage, runtime, eventsService, roomStorageStatusWatcher } + +func newValidScheduler() *entities.Scheduler { + return &entities.Scheduler{ + Name: "scheduler", + Game: "game", + State: entities.StateCreating, + MaxSurge: "5", + RollbackVersion: "", + Spec: game_room.Spec{ + Version: "v1.0.0", + TerminationGracePeriod: 60, + Toleration: "toleration", + Affinity: "affinity", + Containers: []game_room.Container{ + { + Name: "default", + Image: "some-image:v1", + ImagePullPolicy: "IfNotPresent", + Command: []string{"hello"}, + Ports: []game_room.ContainerPort{ + {Name: "tcp", Protocol: "tcp", Port: 80}, + }, + Requests: game_room.ContainerResources{ + CPU: "10m", + Memory: "100Mi", + }, + Limits: game_room.ContainerResources{ + CPU: "10m", + Memory: "100Mi", + }, + }, + }, + }, + PortRange: &port.PortRange{ + Start: 40000, + End: 60000, + }, + } +} diff --git a/proto/apidocs.swagger.json b/proto/apidocs.swagger.json index f0dbf9375..a4c112706 100644 --- a/proto/apidocs.swagger.json +++ b/proto/apidocs.swagger.json @@ -426,7 +426,7 @@ "parameters": [ { "name": "game", - "description": "Game name to filter schedulers\n\nMapped to URL query parameter `game`.", + "description": "Game name to filter schedulers", "in": "query", "required": false, "type": "string"