830c3fe794
As it was decided that the name "tags" would be better for the clarity of the feature, all files and code named "cluster" or "worker cluster" have been removed and replaced with "tag" and "worker tag". This is only a name change, no other features were touched. This addresses part of #104204. Reviewed-on: https://projects.blender.org/studio/flamenco/pulls/104223 As a note to anyone who already ran a pre-release version of Flamenco and configured some worker clusters, with the help of an SQLite client you can migrate the clusters to tags. First build Flamenco Manager and start it, to create the new database schema. Then run these SQL queries via an sqlite commandline client: ```sql insert into worker_tags (id, created_at, updated_at, uuid, name, description) select id, created_at, updated_at, uuid, name, description from worker_clusters; insert into worker_tag_membership (worker_tag_id, worker_id) select worker_cluster_id, worker_id from worker_cluster_membership; ```
337 lines
10 KiB
Go
337 lines
10 KiB
Go
// Package persistence provides the database interface for Flamenco Manager.
|
|
package persistence
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"git.blender.org/flamenco/internal/uuid"
|
|
"git.blender.org/flamenco/pkg/api"
|
|
)
|
|
|
|
func TestCreateFetchWorker(t *testing.T) {
|
|
ctx, cancel, db := persistenceTestFixtures(t, 1*time.Second)
|
|
defer cancel()
|
|
|
|
// Test fetching non-existent worker
|
|
fetchedWorker, err := db.FetchWorker(ctx, "dabf67a1-b591-4232-bf73-0b8de2a9488e")
|
|
assert.ErrorIs(t, err, ErrWorkerNotFound)
|
|
assert.Nil(t, fetchedWorker)
|
|
|
|
// Test existing worker
|
|
w := Worker{
|
|
UUID: uuid.New(),
|
|
Name: "дрон",
|
|
Address: "fe80::5054:ff:fede:2ad7",
|
|
Platform: "linux",
|
|
Software: "3.0",
|
|
Status: api.WorkerStatusAwake,
|
|
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
|
}
|
|
|
|
err = db.CreateWorker(ctx, &w)
|
|
assert.NoError(t, err)
|
|
|
|
fetchedWorker, err = db.FetchWorker(ctx, w.UUID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, fetchedWorker)
|
|
|
|
// Test contents of fetched job
|
|
assert.Equal(t, w.UUID, fetchedWorker.UUID)
|
|
assert.Equal(t, w.Name, fetchedWorker.Name)
|
|
assert.Equal(t, w.Address, fetchedWorker.Address)
|
|
assert.Equal(t, w.Platform, fetchedWorker.Platform)
|
|
assert.Equal(t, w.Software, fetchedWorker.Software)
|
|
assert.Equal(t, w.Status, fetchedWorker.Status)
|
|
|
|
assert.EqualValues(t, w.SupportedTaskTypes, fetchedWorker.SupportedTaskTypes)
|
|
}
|
|
|
|
func TestFetchWorkerTask(t *testing.T) {
|
|
ctx, cancel, db := persistenceTestFixtures(t, 10*time.Second)
|
|
defer cancel()
|
|
|
|
// Worker without task.
|
|
w := Worker{
|
|
UUID: uuid.New(),
|
|
Name: "дрон",
|
|
Address: "fe80::5054:ff:fede:2ad7",
|
|
Platform: "linux",
|
|
Software: "3.0",
|
|
Status: api.WorkerStatusAwake,
|
|
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
|
}
|
|
|
|
err := db.CreateWorker(ctx, &w)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
{ // Test without any task assigned.
|
|
task, err := db.FetchWorkerTask(ctx, &w)
|
|
if assert.NoError(t, err) {
|
|
assert.Nil(t, task)
|
|
}
|
|
}
|
|
|
|
// Create a job with tasks.
|
|
authTask1 := authorTestTask("the task", "blender")
|
|
authTask2 := authorTestTask("the other task", "blender")
|
|
jobUUID := "b6a1d859-122f-4791-8b78-b943329a9989"
|
|
atj := authorTestJob(jobUUID, "simple-blender-render", authTask1, authTask2)
|
|
constructTestJob(ctx, t, db, atj)
|
|
|
|
assignedTask, err := db.ScheduleTask(ctx, &w)
|
|
assert.NoError(t, err)
|
|
|
|
{ // Assigned task should be returned.
|
|
foundTask, err := db.FetchWorkerTask(ctx, &w)
|
|
if assert.NoError(t, err) && assert.NotNil(t, foundTask) {
|
|
assert.Equal(t, assignedTask.UUID, foundTask.UUID)
|
|
assert.Equal(t, jobUUID, foundTask.Job.UUID, "the job UUID should be returned as well")
|
|
}
|
|
}
|
|
|
|
// Set the task to 'completed'.
|
|
assignedTask.Status = api.TaskStatusCompleted
|
|
assert.NoError(t, db.SaveTaskStatus(ctx, assignedTask))
|
|
|
|
{ // Completed-but-last-assigned task should be returned.
|
|
foundTask, err := db.FetchWorkerTask(ctx, &w)
|
|
if assert.NoError(t, err) && assert.NotNil(t, foundTask) {
|
|
assert.Equal(t, assignedTask.UUID, foundTask.UUID)
|
|
assert.Equal(t, jobUUID, foundTask.Job.UUID, "the job UUID should be returned as well")
|
|
}
|
|
}
|
|
|
|
// Assign another task.
|
|
newlyAssignedTask, err := db.ScheduleTask(ctx, &w)
|
|
if !assert.NoError(t, err) || !assert.NotNil(t, newlyAssignedTask) {
|
|
t.FailNow()
|
|
}
|
|
|
|
{ // Newly assigned task should be returned.
|
|
foundTask, err := db.FetchWorkerTask(ctx, &w)
|
|
if assert.NoError(t, err) && assert.NotNil(t, foundTask) {
|
|
assert.Equal(t, newlyAssignedTask.UUID, foundTask.UUID)
|
|
assert.Equal(t, jobUUID, foundTask.Job.UUID, "the job UUID should be returned as well")
|
|
}
|
|
}
|
|
|
|
// Set the new task to 'completed'.
|
|
newlyAssignedTask.Status = api.TaskStatusCompleted
|
|
assert.NoError(t, db.SaveTaskStatus(ctx, newlyAssignedTask))
|
|
|
|
{ // Completed-but-last-assigned task should be returned.
|
|
foundTask, err := db.FetchWorkerTask(ctx, &w)
|
|
if assert.NoError(t, err) && assert.NotNil(t, foundTask) {
|
|
assert.Equal(t, newlyAssignedTask.UUID, foundTask.UUID)
|
|
assert.Equal(t, jobUUID, foundTask.Job.UUID, "the job UUID should be returned as well")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestSaveWorker(t *testing.T) {
|
|
ctx, cancel, db := persistenceTestFixtures(t, 1*time.Second)
|
|
defer cancel()
|
|
|
|
w := Worker{
|
|
UUID: uuid.New(),
|
|
Name: "дрон",
|
|
Address: "fe80::5054:ff:fede:2ad7",
|
|
Platform: "linux",
|
|
Software: "3.0",
|
|
Status: api.WorkerStatusAwake,
|
|
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
|
}
|
|
|
|
err := db.CreateWorker(ctx, &w)
|
|
assert.NoError(t, err)
|
|
|
|
fetchedWorker, err := db.FetchWorker(ctx, w.UUID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, fetchedWorker)
|
|
|
|
// Update all updatable fields of the Worker
|
|
updatedWorker := *fetchedWorker
|
|
updatedWorker.Name = "7 မှ 9"
|
|
updatedWorker.Address = "fe80::cafe:f00d"
|
|
updatedWorker.Platform = "windows"
|
|
updatedWorker.Software = "3.1"
|
|
updatedWorker.Status = api.WorkerStatusAsleep
|
|
updatedWorker.SupportedTaskTypes = "blender,ffmpeg,file-management,misc"
|
|
|
|
// Saving only the status should just do that.
|
|
err = db.SaveWorkerStatus(ctx, &updatedWorker)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "7 မှ 9", updatedWorker.Name, "Saving status should not touch the name")
|
|
|
|
// Check saved worker
|
|
fetchedWorker, err = db.FetchWorker(ctx, w.UUID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, fetchedWorker)
|
|
assert.Equal(t, updatedWorker.Status, fetchedWorker.Status, "new status should have been saved")
|
|
assert.NotEqual(t, updatedWorker.Name, fetchedWorker.Name, "non-status fields should not have been updated")
|
|
|
|
// Saving the entire worker should save everything.
|
|
err = db.SaveWorker(ctx, &updatedWorker)
|
|
assert.NoError(t, err)
|
|
|
|
// Check saved worker
|
|
fetchedWorker, err = db.FetchWorker(ctx, w.UUID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, fetchedWorker)
|
|
assert.Equal(t, updatedWorker.Status, fetchedWorker.Status, "new status should have been saved")
|
|
assert.Equal(t, updatedWorker.Name, fetchedWorker.Name, "non-status fields should also have been updated")
|
|
assert.Equal(t, updatedWorker.Software, fetchedWorker.Software, "non-status fields should also have been updated")
|
|
}
|
|
|
|
func TestFetchWorkers(t *testing.T) {
|
|
ctx, cancel, db := persistenceTestFixtures(t, 1*time.Second)
|
|
defer cancel()
|
|
|
|
// No workers
|
|
workers, err := db.FetchWorkers(ctx)
|
|
if !assert.NoError(t, err) {
|
|
t.Fatal("error fetching empty list of workers, no use in continuing the test")
|
|
}
|
|
assert.Empty(t, workers)
|
|
|
|
linuxWorker := Worker{
|
|
UUID: uuid.New(),
|
|
Name: "дрон",
|
|
Address: "fe80::5054:ff:fede:2ad7",
|
|
Platform: "linux",
|
|
Software: "3.0",
|
|
Status: api.WorkerStatusAwake,
|
|
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
|
}
|
|
|
|
// One worker:
|
|
err = db.CreateWorker(ctx, &linuxWorker)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, time.Now().UTC().Location(), linuxWorker.CreatedAt.Location(),
|
|
"Timestamps should be using UTC timezone")
|
|
|
|
workers, err = db.FetchWorkers(ctx)
|
|
assert.NoError(t, err)
|
|
if assert.Len(t, workers, 1) {
|
|
// FIXME: this fails, because the fetched timestamps have nil location instead of UTC.
|
|
// assert.Equal(t, time.Now().UTC().Location(), workers[0].CreatedAt.Location(),
|
|
// "Timestamps should be using UTC timezone")
|
|
|
|
assert.Equal(t, linuxWorker.UUID, workers[0].UUID)
|
|
assert.Equal(t, linuxWorker.Name, workers[0].Name)
|
|
assert.Equal(t, linuxWorker.Address, workers[0].Address)
|
|
assert.Equal(t, linuxWorker.Status, workers[0].Status)
|
|
assert.Equal(t, linuxWorker.SupportedTaskTypes, workers[0].SupportedTaskTypes)
|
|
}
|
|
|
|
// Two workers:
|
|
windowsWorker := Worker{
|
|
UUID: uuid.New(),
|
|
Name: "очиститель окон",
|
|
Address: "fe80::c000:d000:::3",
|
|
Platform: "windows",
|
|
Software: "3.0",
|
|
Status: api.WorkerStatusOffline,
|
|
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
|
}
|
|
err = db.CreateWorker(ctx, &windowsWorker)
|
|
assert.NoError(t, err)
|
|
|
|
workers, err = db.FetchWorkers(ctx)
|
|
assert.NoError(t, err)
|
|
if assert.Len(t, workers, 2) {
|
|
assert.Equal(t, linuxWorker.UUID, workers[0].UUID)
|
|
assert.Equal(t, windowsWorker.UUID, workers[1].UUID)
|
|
}
|
|
}
|
|
|
|
func TestDeleteWorker(t *testing.T) {
|
|
ctx, cancel, db := persistenceTestFixtures(t, 1*time.Second)
|
|
defer cancel()
|
|
|
|
// Test deleting non-existent worker
|
|
err := db.DeleteWorker(ctx, "dabf67a1-b591-4232-bf73-0b8de2a9488e")
|
|
assert.ErrorIs(t, err, ErrWorkerNotFound)
|
|
|
|
// Test deleting existing worker
|
|
w1 := Worker{
|
|
UUID: "fd97a35b-a5bd-44b4-ac2b-64c193ca877d",
|
|
Name: "Worker 1",
|
|
Status: api.WorkerStatusAwake,
|
|
}
|
|
w2 := Worker{
|
|
UUID: "82b2d176-cb8c-4bfa-8300-41c216d766df",
|
|
Name: "Worker 2",
|
|
Status: api.WorkerStatusOffline,
|
|
}
|
|
|
|
assert.NoError(t, db.CreateWorker(ctx, &w1))
|
|
assert.NoError(t, db.CreateWorker(ctx, &w2))
|
|
|
|
// Delete the 2nd worker, just to have a test with ID != 1.
|
|
assert.NoError(t, db.DeleteWorker(ctx, w2.UUID))
|
|
|
|
// The deleted worker should now no longer be found.
|
|
{
|
|
fetchedWorker, err := db.FetchWorker(ctx, w2.UUID)
|
|
assert.ErrorIs(t, err, ErrWorkerNotFound)
|
|
assert.Nil(t, fetchedWorker)
|
|
}
|
|
|
|
// The other worker should still exist.
|
|
{
|
|
fetchedWorker, err := db.FetchWorker(ctx, w1.UUID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, w1.UUID, fetchedWorker.UUID)
|
|
}
|
|
|
|
// Assign a task to the other worker, and then delete that worker.
|
|
authJob := createTestAuthoredJobWithTasks()
|
|
persistAuthoredJob(t, ctx, db, authJob)
|
|
taskUUID := authJob.Tasks[0].UUID
|
|
{
|
|
task, err := db.FetchTask(ctx, taskUUID)
|
|
assert.NoError(t, err)
|
|
task.Worker = &w1
|
|
assert.NoError(t, db.SaveTask(ctx, task))
|
|
}
|
|
|
|
// Delete the worker.
|
|
assert.NoError(t, db.DeleteWorker(ctx, w1.UUID))
|
|
|
|
// Check the task after deletion of the Worker.
|
|
{
|
|
fetchedTask, err := db.FetchTask(ctx, taskUUID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, taskUUID, fetchedTask.UUID)
|
|
assert.Equal(t, w1.UUID, fetchedTask.Worker.UUID)
|
|
assert.NotZero(t, fetchedTask.Worker.DeletedAt.Time)
|
|
assert.True(t, fetchedTask.Worker.DeletedAt.Valid)
|
|
}
|
|
}
|
|
|
|
func TestDeleteWorkerWithTagAssigned(t *testing.T) {
|
|
f := workerTestFixtures(t, 1*time.Second)
|
|
defer f.done()
|
|
|
|
// Assign the worker.
|
|
require.NoError(t, f.db.WorkerSetTags(f.ctx, f.worker, []string{f.tag.UUID}))
|
|
|
|
// Delete the Worker.
|
|
require.NoError(t, f.db.DeleteWorker(f.ctx, f.worker.UUID))
|
|
|
|
// Check the Worker has been unassigned from the tag.
|
|
tag, err := f.db.FetchWorkerTag(f.ctx, f.tag.UUID)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, tag.Workers)
|
|
}
|