320 lines
10 KiB
Go
320 lines
10 KiB
Go
package api_impl
|
||
|
||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"testing"
|
||
|
||
"github.com/golang/mock/gomock"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
|
||
"git.blender.org/flamenco/internal/manager/persistence"
|
||
"git.blender.org/flamenco/pkg/api"
|
||
)
|
||
|
||
func TestFetchWorkers(t *testing.T) {
|
||
mockCtrl := gomock.NewController(t)
|
||
defer mockCtrl.Finish()
|
||
|
||
mf := newMockedFlamenco(mockCtrl)
|
||
worker1 := testWorker()
|
||
worker2 := worker1
|
||
worker2.ID = 4
|
||
worker2.UUID = "f07b6d53-16ec-40a8-a7b4-a9cc8547f790"
|
||
worker2.Status = api.WorkerStatusAwake
|
||
worker2.StatusChangeRequest(api.WorkerStatusAsleep, false)
|
||
|
||
mf.persistence.EXPECT().FetchWorkers(gomock.Any()).
|
||
Return([]*persistence.Worker{&worker1, &worker2}, nil)
|
||
|
||
echo := mf.prepareMockedRequest(nil)
|
||
err := mf.flamenco.FetchWorkers(echo)
|
||
assert.NoError(t, err)
|
||
|
||
// Check the response
|
||
workers := api.WorkerList{
|
||
Workers: []api.WorkerSummary{
|
||
{
|
||
Id: worker1.UUID,
|
||
Name: worker1.Name,
|
||
Status: worker1.Status,
|
||
Version: worker1.Software,
|
||
},
|
||
{
|
||
Id: worker2.UUID,
|
||
Name: worker2.Name,
|
||
Status: worker2.Status,
|
||
Version: worker2.Software,
|
||
StatusChange: &api.WorkerStatusChangeRequest{
|
||
Status: worker2.StatusRequested,
|
||
IsLazy: false,
|
||
},
|
||
},
|
||
},
|
||
}
|
||
assertResponseJSON(t, echo, http.StatusOK, workers)
|
||
resp := getRecordedResponse(echo)
|
||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||
}
|
||
|
||
func TestFetchWorker(t *testing.T) {
|
||
mockCtrl := gomock.NewController(t)
|
||
defer mockCtrl.Finish()
|
||
|
||
mf := newMockedFlamenco(mockCtrl)
|
||
worker := testWorker()
|
||
workerUUID := worker.UUID
|
||
|
||
// Test without worker in the database.
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).
|
||
Return(nil, fmt.Errorf("wrapped: %w", persistence.ErrWorkerNotFound))
|
||
echo := mf.prepareMockedRequest(nil)
|
||
err := mf.flamenco.FetchWorker(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseAPIError(t, echo, http.StatusNotFound, fmt.Sprintf("worker %q not found", workerUUID))
|
||
|
||
// Test database error fetching worker.
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).
|
||
Return(nil, errors.New("some unknown error"))
|
||
echo = mf.prepareMockedRequest(nil)
|
||
err = mf.flamenco.FetchWorker(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseAPIError(t, echo, http.StatusInternalServerError, "error fetching worker: some unknown error")
|
||
|
||
// Test with worker that does NOT have a status change requested, and DOES have an assigned task.
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).Return(&worker, nil)
|
||
assignedTask := persistence.Task{
|
||
UUID: "806057d5-759a-4e75-86a4-356d43f28cff",
|
||
Name: "test task",
|
||
Job: &persistence.Job{UUID: "f0e25ee4-0d13-4291-afc3-e9446b555aaf"},
|
||
Status: api.TaskStatusActive,
|
||
}
|
||
mf.persistence.EXPECT().FetchWorkerTask(gomock.Any(), &worker).Return(&assignedTask, nil)
|
||
|
||
echo = mf.prepareMockedRequest(nil)
|
||
err = mf.flamenco.FetchWorker(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseJSON(t, echo, http.StatusOK, api.Worker{
|
||
WorkerSummary: api.WorkerSummary{
|
||
Id: workerUUID,
|
||
Name: "дрон",
|
||
Version: "3.0",
|
||
Status: api.WorkerStatusAwake,
|
||
},
|
||
IpAddress: "fe80::5054:ff:fede:2ad7",
|
||
Platform: "linux",
|
||
SupportedTaskTypes: []string{"blender", "ffmpeg", "file-management", "misc"},
|
||
Task: &api.WorkerTask{
|
||
TaskSummary: api.TaskSummary{
|
||
Id: assignedTask.UUID,
|
||
Name: assignedTask.Name,
|
||
Status: assignedTask.Status,
|
||
},
|
||
JobId: assignedTask.Job.UUID,
|
||
},
|
||
})
|
||
|
||
// Test with worker that does have a status change requested, but does NOT Have an assigned task.
|
||
requestedStatus := api.WorkerStatusAsleep
|
||
worker.StatusChangeRequest(requestedStatus, false)
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).Return(&worker, nil)
|
||
mf.persistence.EXPECT().FetchWorkerTask(gomock.Any(), &worker).Return(nil, nil)
|
||
|
||
echo = mf.prepareMockedRequest(nil)
|
||
err = mf.flamenco.FetchWorker(echo, worker.UUID)
|
||
assert.NoError(t, err)
|
||
assertResponseJSON(t, echo, http.StatusOK, api.Worker{
|
||
WorkerSummary: api.WorkerSummary{
|
||
Id: workerUUID,
|
||
Name: "дрон",
|
||
Version: "3.0",
|
||
Status: api.WorkerStatusAwake,
|
||
StatusChange: &api.WorkerStatusChangeRequest{Status: requestedStatus},
|
||
},
|
||
IpAddress: "fe80::5054:ff:fede:2ad7",
|
||
Platform: "linux",
|
||
SupportedTaskTypes: []string{"blender", "ffmpeg", "file-management", "misc"},
|
||
Task: nil,
|
||
})
|
||
}
|
||
|
||
func TestDeleteWorker(t *testing.T) {
|
||
mockCtrl := gomock.NewController(t)
|
||
defer mockCtrl.Finish()
|
||
|
||
mf := newMockedFlamenco(mockCtrl)
|
||
worker := testWorker()
|
||
workerUUID := worker.UUID
|
||
|
||
// Test on non-existent worker.
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).
|
||
Return(nil, fmt.Errorf("wrapped: %w", persistence.ErrWorkerNotFound))
|
||
echo := mf.prepareMockedRequest(nil)
|
||
err := mf.flamenco.DeleteWorker(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseAPIError(t, echo, http.StatusNotFound, fmt.Sprintf("worker %q not found", workerUUID))
|
||
|
||
// Test with existing worker.
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).Return(&worker, nil)
|
||
mf.stateMachine.EXPECT().RequeueActiveTasksOfWorker(
|
||
gomock.Any(), &worker, "worker is being deleted")
|
||
mf.persistence.EXPECT().DeleteWorker(gomock.Any(), workerUUID).Return(nil)
|
||
|
||
mockedNow := mf.clock.Now()
|
||
mf.broadcaster.EXPECT().BroadcastWorkerUpdate(api.SocketIOWorkerUpdate{
|
||
DeletedAt: &mockedNow,
|
||
Id: worker.UUID,
|
||
Name: worker.Name,
|
||
Status: worker.Status,
|
||
Updated: worker.UpdatedAt,
|
||
Version: worker.Software,
|
||
})
|
||
|
||
echo = mf.prepareMockedRequest(nil)
|
||
err = mf.flamenco.DeleteWorker(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseNoContent(t, echo)
|
||
}
|
||
|
||
func TestRequestWorkerStatusChange(t *testing.T) {
|
||
mockCtrl := gomock.NewController(t)
|
||
defer mockCtrl.Finish()
|
||
|
||
mf := newMockedFlamenco(mockCtrl)
|
||
worker := testWorker()
|
||
workerUUID := worker.UUID
|
||
prevStatus := worker.Status
|
||
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).Return(&worker, nil)
|
||
|
||
requestStatus := api.WorkerStatusAsleep
|
||
savedWorker := worker
|
||
savedWorker.StatusChangeRequest(requestStatus, true)
|
||
mf.persistence.EXPECT().SaveWorker(gomock.Any(), &savedWorker).Return(nil)
|
||
|
||
// Expect a broadcast of the change
|
||
mf.broadcaster.EXPECT().BroadcastWorkerUpdate(api.SocketIOWorkerUpdate{
|
||
Id: worker.UUID,
|
||
Name: worker.Name,
|
||
Status: prevStatus,
|
||
Updated: worker.UpdatedAt,
|
||
Version: worker.Software,
|
||
StatusChange: &api.WorkerStatusChangeRequest{
|
||
Status: requestStatus,
|
||
IsLazy: true,
|
||
},
|
||
})
|
||
|
||
echo := mf.prepareMockedJSONRequest(api.WorkerStatusChangeRequest{
|
||
Status: requestStatus,
|
||
IsLazy: true,
|
||
})
|
||
err := mf.flamenco.RequestWorkerStatusChange(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseNoContent(t, echo)
|
||
}
|
||
|
||
func TestRequestWorkerStatusChangeRevert(t *testing.T) {
|
||
mockCtrl := gomock.NewController(t)
|
||
defer mockCtrl.Finish()
|
||
|
||
mf := newMockedFlamenco(mockCtrl)
|
||
worker := testWorker()
|
||
|
||
// Mimick that a status change request to 'asleep' was already performed.
|
||
worker.StatusChangeRequest(api.WorkerStatusAsleep, true)
|
||
|
||
workerUUID := worker.UUID
|
||
currentStatus := worker.Status
|
||
|
||
mf.persistence.EXPECT().FetchWorker(gomock.Any(), workerUUID).Return(&worker, nil)
|
||
|
||
// Perform a request to go to the current worker status. This should cancel
|
||
// the previous status change request.
|
||
requestStatus := currentStatus
|
||
savedWorker := worker
|
||
savedWorker.StatusChangeClear()
|
||
mf.persistence.EXPECT().SaveWorker(gomock.Any(), &savedWorker).Return(nil)
|
||
|
||
// Expect a broadcast of the change
|
||
mf.broadcaster.EXPECT().BroadcastWorkerUpdate(api.SocketIOWorkerUpdate{
|
||
Id: worker.UUID,
|
||
Name: worker.Name,
|
||
Status: currentStatus,
|
||
Updated: worker.UpdatedAt,
|
||
Version: worker.Software,
|
||
StatusChange: nil,
|
||
})
|
||
|
||
echo := mf.prepareMockedJSONRequest(api.WorkerStatusChangeRequest{
|
||
Status: requestStatus,
|
||
|
||
// This shouldn't matter; requesting the current status should simply erase
|
||
// the previous status change request.
|
||
IsLazy: true,
|
||
})
|
||
err := mf.flamenco.RequestWorkerStatusChange(echo, workerUUID)
|
||
assert.NoError(t, err)
|
||
assertResponseNoContent(t, echo)
|
||
}
|
||
|
||
func TestWorkerClusterCRUDHappyFlow(t *testing.T) {
|
||
mockCtrl := gomock.NewController(t)
|
||
defer mockCtrl.Finish()
|
||
|
||
mf := newMockedFlamenco(mockCtrl)
|
||
|
||
// Create a cluster.
|
||
UUID := "18d9234e-5135-458f-a1ba-a350c3d4e837"
|
||
apiCluster := api.WorkerCluster{
|
||
Id: UUID,
|
||
Name: "ʻO nā manu ʻino",
|
||
Description: ptr("Ke aloha"),
|
||
}
|
||
expectDBCluster := persistence.WorkerCluster{
|
||
UUID: UUID,
|
||
Name: apiCluster.Name,
|
||
Description: *apiCluster.Description,
|
||
}
|
||
mf.persistence.EXPECT().CreateWorkerCluster(gomock.Any(), &expectDBCluster)
|
||
// TODO: expect SocketIO broadcast of the cluster creation.
|
||
echo := mf.prepareMockedJSONRequest(apiCluster)
|
||
require.NoError(t, mf.flamenco.CreateWorkerCluster(echo))
|
||
assertResponseNoContent(t, echo)
|
||
|
||
// Fetch the cluster
|
||
mf.persistence.EXPECT().FetchWorkerCluster(gomock.Any(), UUID).Return(&expectDBCluster, nil)
|
||
echo = mf.prepareMockedRequest(nil)
|
||
require.NoError(t, mf.flamenco.FetchWorkerCluster(echo, UUID))
|
||
assertResponseJSON(t, echo, http.StatusOK, &apiCluster)
|
||
|
||
// Update & save.
|
||
newUUID := "60442762-83d3-4fc3-bf75-6ab5799cdbaa"
|
||
newAPICluster := api.WorkerCluster{
|
||
Id: newUUID, // Intentionally change the UUID. This should just be ignored.
|
||
Name: "updated name",
|
||
}
|
||
expectNewDBCluster := persistence.WorkerCluster{
|
||
UUID: UUID,
|
||
Name: newAPICluster.Name,
|
||
Description: "",
|
||
}
|
||
// TODO: expect SocketIO broadcast of the cluster update.
|
||
mf.persistence.EXPECT().FetchWorkerCluster(gomock.Any(), UUID).Return(&expectDBCluster, nil)
|
||
mf.persistence.EXPECT().SaveWorkerCluster(gomock.Any(), &expectNewDBCluster)
|
||
echo = mf.prepareMockedJSONRequest(newAPICluster)
|
||
require.NoError(t, mf.flamenco.UpdateWorkerCluster(echo, UUID))
|
||
assertResponseNoContent(t, echo)
|
||
|
||
// Delete.
|
||
mf.persistence.EXPECT().DeleteWorkerCluster(gomock.Any(), UUID)
|
||
// TODO: expect SocketIO broadcast of the cluster deletion.
|
||
echo = mf.prepareMockedJSONRequest(newAPICluster)
|
||
require.NoError(t, mf.flamenco.DeleteWorkerCluster(echo, UUID))
|
||
assertResponseNoContent(t, echo)
|
||
}
|