Flamenco/internal/manager/persistence/workers_test.go
Eveline Anderson 830c3fe794 Rename worker 'clusters' to 'tags'
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;
```
2023-07-10 11:11:03 +02:00

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)
}