WIP: convert GORM to sqlc, for jobs/tasks #104304

Closed
Sybren A. Stüvel wants to merge 27 commits from sqlc-task into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
43 changed files with 671 additions and 308 deletions
Showing only changes of commit 7d354bcb36 - Show all commits

View File

@ -4,14 +4,23 @@ This file contains the history of changes to Flamenco. Only changes that might
be interesting for users are listed here, such as new features and fixes for
bugs in actually-released versions.
## 3.5 - in development
## 3.6 - in development
- Add MQTT support. Flamenco Manager can now send internal events to an MQTT broker.
## 3.5 - released 2024-04-16
- Add MQTT support ([docs](https://flamenco.blender.org/usage/manager-configuration/mqtt/)). Flamenco Manager can now send internal events to an MQTT broker.
- Simplify the preview video filename when a complex set of frames rendered ([#104285](https://projects.blender.org/studio/flamenco/issues/104285)). Instead of `video-1, 4, 10.mp4` it is now simply `video-1-10.mp4`.
- Make the `blendfile` parameter of a `blender-render` command optional. This makes it possible to pass, for example, a Python file that loads/constructs the blend file, instead of loading one straight from disk.
- Show the farm status in the web frontend. This shows whether the farm is actively working on a job, idle, asleep (all workers are sleeping and no work is queued), waiting (all workers are sleeping, and work is queued), or inoperable (no workers, or all workers are offline). This status is also broadcast as event via the event bus, and thus available via SocketIO and MQTT.
- Fix an issue where the columns in the web interface wouldn't correctly resize when the shown information changed.
- Add-on: replace the different 'refresh' buttons (for Manager info & storage location, job types, and worker tags) with a single button that just refreshes everything in one go. The information obtained from Flamenco Manager is now stored in a JSON file on disk, making it independent from Blender auto-saving the user preferences.
- Ensure the web frontend connects to the backend correctly when served over HTTPS ([#104296](https://projects.blender.org/studio/flamenco/pulls/104296)).
- For Workers running on Linux, it is now possible to configure the "OOM score adjustment" for sub-processes. This makes it possible for the out-of-memory killer to target Blender, and not Flamenco Worker itself.
- Security updates of some dependencies:
- [Incorrect forwarding of sensitive headers and cookies on HTTP redirect in net/http](https://pkg.go.dev/vuln/GO-2024-2600)
- [Memory exhaustion in multipart form parsing in net/textproto and net/http](https://pkg.go.dev/vuln/GO-2024-2599)
- [Verify panics on certificates with an unknown public key algorithm in crypto/x509](https://pkg.go.dev/vuln/GO-2024-2600)
- [HTTP/2 CONTINUATION flood in net/http](https://pkg.go.dev/vuln/GO-2024-2687)
## 3.4 - released 2024-01-12

View File

@ -4,7 +4,7 @@ PKG := projects.blender.org/studio/flamenco
# To update the version number in all the relevant places, update the VERSION
# variable below and run `make update-version`.
VERSION := 3.5-alpha1
VERSION := 3.6-alpha0
# "alpha", "beta", or "release".
RELEASE_CYCLE := alpha

View File

@ -5,14 +5,14 @@
bl_info = {
"name": "Flamenco 3",
"author": "Sybren A. Stüvel",
"version": (3, 5),
"version": (3, 6),
"blender": (3, 1, 0),
"description": "Flamenco client for Blender.",
"location": "Output Properties > Flamenco",
"doc_url": "https://flamenco.blender.org/",
"category": "System",
"support": "COMMUNITY",
"warning": "This is version 3.5-alpha1 of the add-on, which is not a stable release",
"warning": "This is version 3.6-alpha0 of the add-on, which is not a stable release",
}
from pathlib import Path

View File

@ -286,12 +286,12 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore
return None
self.log.debug(" %s: %s", file_spec.status, file_spec.path)
match file_spec.status.value:
case "unknown":
status = file_spec.status.value
if status == "unknown":
to_upload.appendleft(file_spec)
case "uploading":
elif status == "uploading":
to_upload.append(file_spec)
case _:
else:
msg = "Unknown status in response from Shaman: %r" % file_spec
self.log.error(msg)
self.error_set(msg)
@ -375,21 +375,22 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore
x_shaman_original_filename=file_spec.path,
)
except ApiException as ex:
match ex.status:
case 425: # Too Early, i.e. defer uploading this file.
if ex.status == 425:
# Too Early, i.e. defer uploading this file.
self.log.info(
" %s: someone else is uploading this file, deferring",
file_spec.path,
)
defer(file_spec)
continue
case 417: # Expectation Failed; mismatch of checksum or file size.
elif ex.status == 417:
# Expectation Failed; mismatch of checksum or file size.
msg = "Error from Shaman uploading %s, code %d: %s" % (
file_spec.path,
ex.status,
ex.body,
)
case _: # Unknown error
else: # Unknown error
msg = "API exception\nHeaders: %s\nBody: %s\n" % (
ex.headers,
ex.body,
@ -453,15 +454,11 @@ class Transferrer(submodules.transfer.FileTransferer): # type: ignore
checkoutRequest
)
except ApiException as ex:
match ex.status:
case 424: # Files were missing
if ex.status == 424: # Files were missing
msg = "We did not upload some files, checkout aborted"
case 409: # Checkout already exists
msg = (
"There is already an existing checkout at %s"
% self.checkout_path
)
case _: # Unknown error
elif ex.status == 409: # Checkout already exists
msg = "There is already an existing checkout at %s" % self.checkout_path
else: # Unknown error
msg = "API exception\nHeaders: %s\nBody: %s\n" % (
ex.headers,
ex.body,

View File

@ -116,7 +116,8 @@ def _store_available_job_types(available_job_types: _AvailableJobTypes) -> None:
else:
# Convert from API response type to list suitable for an EnumProperty.
_job_type_enum_items = [
(job_type.name, job_type.label, "") for job_type in job_types
(job_type.name, job_type.label, getattr(job_type, "description", ""))
for job_type in job_types
]
_job_type_enum_items.insert(0, _JOB_TYPE_NOT_SELECTED_ENUM_ITEM)

View File

@ -10,7 +10,7 @@
"""
__version__ = "3.5-alpha1"
__version__ = "3.6-alpha0"
# import ApiClient
from flamenco.manager.api_client import ApiClient

View File

@ -76,7 +76,7 @@ class ApiClient(object):
self.default_headers[header_name] = header_value
self.cookie = cookie
# Set default User-Agent.
self.user_agent = 'Flamenco/3.5-alpha1 (Blender add-on)'
self.user_agent = 'Flamenco/3.6-alpha0 (Blender add-on)'
def __enter__(self):
return self

View File

@ -404,7 +404,7 @@ conf = flamenco.manager.Configuration(
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 1.0.0\n"\
"SDK Package Version: 3.5-alpha1".\
"SDK Package Version: 3.6-alpha0".\
format(env=sys.platform, pyversion=sys.version)
def get_host_settings(self):

View File

@ -9,6 +9,7 @@ Name | Type | Description | Notes
**label** | **str** | |
**settings** | [**[AvailableJobSetting]**](AvailableJobSetting.md) | |
**etag** | **str** | Hash of the job type. If the job settings or the label change, this etag will change. This is used on job submission to ensure that the submitted job settings are up to date. |
**description** | **str** | The description/tooltip shown in the user interface. | [optional]
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@ -91,6 +91,7 @@ class AvailableJobType(ModelNormal):
'label': (str,), # noqa: E501
'settings': ([AvailableJobSetting],), # noqa: E501
'etag': (str,), # noqa: E501
'description': (str,), # noqa: E501
}
@cached_property
@ -103,6 +104,7 @@ class AvailableJobType(ModelNormal):
'label': 'label', # noqa: E501
'settings': 'settings', # noqa: E501
'etag': 'etag', # noqa: E501
'description': 'description', # noqa: E501
}
read_only_vars = {
@ -152,6 +154,7 @@ class AvailableJobType(ModelNormal):
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
description (str): The description/tooltip shown in the user interface.. [optional] # noqa: E501
"""
_check_type = kwargs.pop('_check_type', True)
@ -243,6 +246,7 @@ class AvailableJobType(ModelNormal):
Animal class but this time we won't travel
through its discriminator because we passed in
_visited_composed_classes = (Animal,)
description (str): The description/tooltip shown in the user interface.. [optional] # noqa: E501
"""
_check_type = kwargs.pop('_check_type', True)

View File

@ -4,7 +4,7 @@ Render Farm manager API
The `flamenco.manager` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.0.0
- Package version: 3.5-alpha1
- Package version: 3.6-alpha0
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
For more information, please visit [https://flamenco.io/](https://flamenco.io/)

View File

@ -5,7 +5,7 @@ import dataclasses
import json
import platform
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Union
from urllib3.exceptions import HTTPError, MaxRetryError
@ -133,7 +133,7 @@ def _to_json(info: ManagerInfo) -> str:
return json.dumps(info, indent=" ", cls=Encoder)
def _from_json(contents: str | bytes) -> ManagerInfo:
def _from_json(contents: Union[str, bytes]) -> ManagerInfo:
# Do a late import, so that the API is only imported when actually used.
from flamenco.manager.configuration import Configuration
from flamenco.manager.model_utils import validate_and_convert_types

View File

@ -2,7 +2,7 @@
# <pep8 compliant>
from pathlib import Path
from typing import Callable, TypeAlias
from typing import Callable
import dataclasses
from .bat.submodules import bpathlib
@ -64,7 +64,7 @@ def _search_path_marker(blendfile: Path, marker_path: str) -> Path:
return blendfile_dir
Finder: TypeAlias = Callable[[Path], Path]
Finder = Callable[[Path], Path]
@dataclasses.dataclass

View File

@ -23,6 +23,7 @@ import (
"projects.blender.org/studio/flamenco/internal/appinfo"
"projects.blender.org/studio/flamenco/internal/worker"
"projects.blender.org/studio/flamenco/internal/worker/cli_runner"
"projects.blender.org/studio/flamenco/pkg/oomscore"
"projects.blender.org/studio/flamenco/pkg/sysinfo"
"projects.blender.org/studio/flamenco/pkg/website"
)
@ -114,6 +115,10 @@ func main() {
findBlender()
findFFmpeg()
// Create the CLI runner before the auto-discovery, to make any configuration
// problems clear before waiting for the Manager to respond.
cliRunner := createCLIRunner(&configWrangler)
// Give the auto-discovery some time to find a Manager.
discoverTimeout := 10 * time.Minute
discoverCtx, discoverCancel := context.WithTimeout(context.Background(), discoverTimeout)
@ -149,7 +154,6 @@ func main() {
return
}
cliRunner := cli_runner.NewCLIRunner()
listener = worker.NewListener(client, buffer)
cmdRunner := worker.NewCommandExecutor(cliRunner, listener, timeService)
taskRunner := worker.NewTaskExecutor(cmdRunner, listener)
@ -304,3 +308,27 @@ func logFatalManagerDiscoveryError(err error, discoverTimeout time.Duration) {
Msgf("auto-discovery error, see %s", website.CannotFindManagerHelpURL)
}
}
func createCLIRunner(configWrangler *worker.FileConfigWrangler) *cli_runner.CLIRunner {
config, err := configWrangler.WorkerConfig()
if err != nil {
log.Fatal().Err(err).Msg("error loading worker configuration")
}
if config.LinuxOOMScoreAdjust == nil {
log.Debug().Msg("executables will be run without OOM score adjustment")
return cli_runner.NewCLIRunner()
}
if !oomscore.Available() {
log.Warn().
Msgf("config: oom_score_adjust configured, but that is only available on Linux, not this platform. See %s for more information.",
website.OOMScoreAdjURL)
return cli_runner.NewCLIRunner()
}
adjustment := *config.LinuxOOMScoreAdjust
log.Info().Int("oom_score_adjust", adjustment).Msg("executables will be run with OOM score adjustment")
return cli_runner.NewCLIRunnerWithOOMScoreAdjuster(adjustment)
}

8
go.mod
View File

@ -1,6 +1,6 @@
module projects.blender.org/studio/flamenco
go 1.22
go 1.22.2
require (
github.com/adrg/xdg v0.4.0
@ -28,10 +28,10 @@ require (
github.com/stretchr/testify v1.8.4
github.com/zcalusic/sysinfo v1.0.1
github.com/ziflex/lecho/v3 v3.1.0
golang.org/x/crypto v0.16.0
golang.org/x/crypto v0.21.0
golang.org/x/image v0.10.0
golang.org/x/net v0.19.0
golang.org/x/sys v0.15.0
golang.org/x/net v0.23.0
golang.org/x/sys v0.18.0
gopkg.in/yaml.v2 v2.4.0
gorm.io/gorm v1.25.5
modernc.org/sqlite v1.28.0

6
go.sum
View File

@ -201,6 +201,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
@ -223,6 +225,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -262,6 +266,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@ -442,6 +442,32 @@ func TestGetJobTypeHappy(t *testing.T) {
assertResponseJSON(t, echoCtx, http.StatusOK, jt)
}
func TestGetJobTypeWithDescriptionHappy(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mf := newMockedFlamenco(mockCtrl)
// Get an existing job type with a description.
description := "This is a test job type"
jt := api.AvailableJobType{
Description: &description,
Etag: "some etag",
Name: "test-job-type",
Label: "Test Job Type",
Settings: []api.AvailableJobSetting{
{Key: "setting", Type: api.AvailableJobSettingTypeString},
},
}
mf.jobCompiler.EXPECT().GetJobType("test-job-type").
Return(jt, nil)
echoCtx := mf.prepareMockedRequest(nil)
err := mf.flamenco.GetJobType(echoCtx, "test-job-type")
require.NoError(t, err)
assertResponseJSON(t, echoCtx, http.StatusOK, jt)
}
func TestGetJobTypeUnknown(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

View File

@ -2,6 +2,7 @@
const JOB_TYPE = {
label: "Simple Blender Render",
description: "Render a sequence of frames, and create a preview video file",
settings: [
// Settings for artists to determine:
{ key: "frames", type: "string", required: true, eval: "f'{C.scene.frame_start}-{C.scene.frame_end}'",

View File

@ -2,6 +2,7 @@
const JOB_TYPE = {
label: "Simple Blender Render",
description: "Render a sequence of frames, and create a preview video file",
settings: [
// Settings for artists to determine:
{ key: "frames", type: "string", required: true,

View File

@ -62,6 +62,15 @@ func (db *DB) SaveWorkerTag(ctx context.Context, tag *WorkerTag) error {
// DeleteWorkerTag deletes the given tag, after unassigning all workers from it.
func (db *DB) DeleteWorkerTag(ctx context.Context, uuid string) error {
// As a safety measure, refuse to delete unless foreign key constraints are active.
fkEnabled, err := db.areForeignKeysEnabled()
if err != nil {
return fmt.Errorf("checking whether foreign keys are enabled: %w", err)
}
if !fkEnabled {
return ErrDeletingWithoutFK
}
tx := db.gormDB.WithContext(ctx).
Where("uuid = ?", uuid).
Delete(&WorkerTag{})

View File

@ -3,6 +3,7 @@ package persistence
// SPDX-License-Identifier: GPL-3.0-or-later
import (
"slices"
"testing"
"time"
@ -50,17 +51,7 @@ func TestFetchDeleteTags(t *testing.T) {
}
require.NoError(t, f.db.CreateWorkerTag(f.ctx, &secondTag))
allTags, err := f.db.FetchWorkerTags(f.ctx)
require.NoError(t, err)
require.Len(t, allTags, 2)
var allTagIDs [2]string
for idx := range allTags {
allTagIDs[idx] = allTags[idx].UUID
}
assert.Contains(t, allTagIDs, f.tag.UUID)
assert.Contains(t, allTagIDs, secondTag.UUID)
assertTagsMatch(t, f, f.tag.UUID, secondTag.UUID)
has, err = f.db.HasWorkerTags(f.ctx)
require.NoError(t, err)
@ -68,11 +59,7 @@ func TestFetchDeleteTags(t *testing.T) {
// Test deleting the 2nd tag.
require.NoError(t, f.db.DeleteWorkerTag(f.ctx, secondTag.UUID))
allTags, err = f.db.FetchWorkerTags(f.ctx)
require.NoError(t, err)
require.Len(t, allTags, 1)
assert.Equal(t, f.tag.UUID, allTags[0].UUID)
assertTagsMatch(t, f, f.tag.UUID)
// Test deleting the 1st tag.
require.NoError(t, f.db.DeleteWorkerTag(f.ctx, f.tag.UUID))
@ -81,6 +68,31 @@ func TestFetchDeleteTags(t *testing.T) {
assert.False(t, has, "expecting HasWorkerTags to return false")
}
func TestDeleteTagsWithoutFK(t *testing.T) {
f := workerTestFixtures(t, 1*time.Second)
defer f.done()
// Single tag was created by fixture.
has, err := f.db.HasWorkerTags(f.ctx)
require.NoError(t, err)
assert.True(t, has, "expecting HasWorkerTags to return true")
secondTag := WorkerTag{
UUID: uuid.New(),
Name: "arbeiderskaartje",
Description: "Worker tag in Dutch",
}
require.NoError(t, f.db.CreateWorkerTag(f.ctx, &secondTag))
// Try deleting with foreign key constraints disabled.
require.NoError(t, f.db.pragmaForeignKeys(false))
err = f.db.DeleteWorkerTag(f.ctx, f.tag.UUID)
require.ErrorIs(t, err, ErrDeletingWithoutFK)
// Test the deletion did not happen.
assertTagsMatch(t, f, f.tag.UUID, secondTag.UUID)
}
func TestAssignUnassignWorkerTags(t *testing.T) {
f := workerTestFixtures(t, 1*time.Second)
defer f.done()
@ -163,3 +175,19 @@ func TestDeleteWorkerTagWithWorkersAssigned(t *testing.T) {
require.NoError(t, err)
assert.Empty(t, w.Tags)
}
func assertTagsMatch(t *testing.T, f WorkerTestFixture, expectUUIDs ...string) {
allTags, err := f.db.FetchWorkerTags(f.ctx)
require.NoError(t, err)
require.Len(t, allTags, len(expectUUIDs))
var actualUUIDs []string
for idx := range allTags {
actualUUIDs = append(actualUUIDs, allTags[idx].UUID)
}
slices.Sort(expectUUIDs)
slices.Sort(actualUUIDs)
assert.Equal(t, actualUUIDs, expectUUIDs)
}

View File

@ -101,6 +101,15 @@ func (db *DB) FetchWorker(ctx context.Context, uuid string) (*Worker, error) {
}
func (db *DB) DeleteWorker(ctx context.Context, uuid string) error {
// As a safety measure, refuse to delete unless foreign key constraints are active.
fkEnabled, err := db.areForeignKeysEnabled()
if err != nil {
return fmt.Errorf("checking whether foreign keys are enabled: %w", err)
}
if !fkEnabled {
return ErrDeletingWithoutFK
}
tx := db.gormDB.WithContext(ctx).
Where("uuid = ?", uuid).
Delete(&Worker{})

View File

@ -314,6 +314,30 @@ func TestDeleteWorker(t *testing.T) {
}
}
func TestDeleteWorkerNoForeignKeys(t *testing.T) {
ctx, cancel, db := persistenceTestFixtures(t, 1*time.Second)
defer cancel()
// Create a Worker to delete.
w1 := Worker{
UUID: "fd97a35b-a5bd-44b4-ac2b-64c193ca877d",
Name: "Worker 1",
Status: api.WorkerStatusAwake,
}
require.NoError(t, db.CreateWorker(ctx, &w1))
// Try deleting with foreign key constraints disabled.
require.NoError(t, db.pragmaForeignKeys(false))
require.ErrorIs(t, ErrDeletingWithoutFK, db.DeleteWorker(ctx, w1.UUID))
// The worker should still exist.
{
fetchedWorker, err := db.FetchWorker(ctx, w1.UUID)
require.NoError(t, err)
assert.Equal(t, w1.UUID, fetchedWorker.UUID)
}
}
func TestDeleteWorkerWithTagAssigned(t *testing.T) {
f := workerTestFixtures(t, 1*time.Second)
defer f.done()

View File

@ -11,6 +11,7 @@ import (
"github.com/alessio/shellescape"
"github.com/rs/zerolog"
"projects.blender.org/studio/flamenco/pkg/oomscore"
)
// The buffer size used to read stdout/stderr output from subprocesses, in
@ -20,11 +21,19 @@ const StdoutBufferSize = 40 * 1024
// CLIRunner is a wrapper around exec.CommandContext() to allow mocking.
type CLIRunner struct {
oomScoreAdjust int
useOOMScoreAdjust bool
}
func NewCLIRunner() *CLIRunner {
return &CLIRunner{}
}
func NewCLIRunnerWithOOMScoreAdjuster(oomScoreAdjust int) *CLIRunner {
return &CLIRunner{
oomScoreAdjust: oomScoreAdjust,
useOOMScoreAdjust: true,
}
}
func (cli *CLIRunner) CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
return exec.CommandContext(ctx, name, arg...)
@ -55,7 +64,7 @@ func (cli *CLIRunner) RunWithTextOutput(
return err
}
if err := execCmd.Start(); err != nil {
if err := cli.startWithOOMAdjust(execCmd); err != nil {
logger.Error().Err(err).Msg("error starting CLI execution")
return err
}
@ -171,3 +180,13 @@ func (cli *CLIRunner) logCmd(
}
return nil
}
// startWithOOMAdjust runs the command with its OOM score adjusted.
func (cli *CLIRunner) startWithOOMAdjust(execCmd *exec.Cmd) error {
if cli.useOOMScoreAdjust {
oomScoreRestore := oomscore.Adjust(cli.oomScoreAdjust)
defer oomScoreRestore()
}
return execCmd.Start()
}

View File

@ -58,6 +58,18 @@ type WorkerConfig struct {
TaskTypes []string `yaml:"task_types"`
RestartExitCode int `yaml:"restart_exit_code"`
// LinuxOOMScoreAdjust controls the Linux out-of-memory killer. Is used when
// spawning a sub-process, to adjust the likelyness that that subprocess is
// killed rather than Flamenco Worker itself. That way Flamenco Worker can
// report the failure to the Manager.
//
// If the Worker itself would be OOM-killed, it would just be restarted and
// get the task it was already working on, causing an infinite OOM-loop.
//
// If this value is not specified in the configuration file, Flamenco Worker
// will not attempt to adjust its OOM score.
LinuxOOMScoreAdjust *int `yaml:"oom_score_adjust"`
}
type WorkerCredentials struct {

View File

@ -52,8 +52,8 @@ func (ou *OutputUploader) OutputProduced(taskID, filename string) {
}
func (ou *OutputUploader) Run(ctx context.Context) {
log.Info().Msg("output uploader: running")
defer log.Info().Msg("output uploader: shutting down")
log.Debug().Msg("output uploader: running")
defer log.Debug().Msg("output uploader: shutting down")
wg := sync.WaitGroup{}
wg.Add(1)

View File

@ -133,7 +133,7 @@ func (ub *UpstreamBufferDB) Close() error {
ub.wg.Wait()
// Attempt one final flush, if it's fast enough:
log.Info().Msg("upstream buffer shutting down, doing one final flush")
log.Debug().Msg("upstream buffer shutting down, doing one final flush")
flushCtx, ctxCancel := context.WithTimeout(context.Background(), flushOnShutdownTimeout)
defer ctxCancel()
if err := ub.Flush(flushCtx); err != nil {

View File

@ -1719,6 +1719,9 @@ components:
properties:
"name": { type: string }
"label": { type: string }
"description":
type: string
description: The description/tooltip shown in the user interface.
"settings":
type: array
items: { $ref: "#/components/schemas/AvailableJobSetting" }

View File

@ -44,208 +44,208 @@ var swaggerSpec = []string{
"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",
"AQA=",
"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",
"Qwu3RhEBAA==",
}
// GetSwagger returns the content of the embedded swagger specification file

View File

@ -242,6 +242,9 @@ type AvailableJobSettingVisibility string
// Job type supported by this Manager, and its parameters.
type AvailableJobType struct {
// The description/tooltip shown in the user interface.
Description *string `json:"description,omitempty"`
// Hash of the job type. If the job settings or the label change, this etag will change. This is used on job submission to ensure that the submitted job settings are up to date.
Etag string `json:"etag"`
Label string `json:"label"`

86
pkg/oomscore/oomscore.go Normal file
View File

@ -0,0 +1,86 @@
// package oomscore provides some functions to adjust the Linux
// out-of-memory (OOM) score, i.e. the number that determines how likely it is
// that a process is killed in an out-of-memory situation.
//
// It is available only on Linux. On other platforms ErrNotImplemented will be returned.
package oomscore
import (
"errors"
"github.com/rs/zerolog/log"
)
var ErrNotImplemented = errors.New("OOM score functionality not implemented on this platform")
// Available returns whether the functionality in this package is available for
// the current platform.
func Available() bool {
return available
}
// GetOOMScore returns the current process' OOM score.
func GetOOMScore() (int, error) {
return getOOMScore()
}
// GetOOMScoreAdj returns the current process' OOM score adjustment.
func GetOOMScoreAdj() (int, error) {
return getOOMScoreAdj()
}
// SetOOMScoreAdj sets the current process' OOM score adjustment.
func SetOOMScoreAdj(score int) error {
return setOOMScoreAdj(score)
}
type ScoreRestoreFunc func()
var emptyRestoreFunc ScoreRestoreFunc = func() {}
// Adjust temporarily sets the OOM score adjustment.
// The returned function MUST be called to restore the original value.
// Any problems changing the score are logged, but not otherwise returned.
func Adjust(score int) (restoreFunc ScoreRestoreFunc) {
restoreFunc = emptyRestoreFunc
if !Available() {
return
}
origScore, err := getOOMScoreAdj()
if err != nil {
log.Error().
AnErr("cause", err).
Msg("could not get the current process' oom_score_adj value")
return
}
log.Trace().
Int("oom_score_adj", score).
Msg("setting oom_score_adj")
err = setOOMScoreAdj(score)
if err != nil {
log.Error().
Int("oom_score_adj", score).
AnErr("cause", err).
Msg("could not set the current process' oom_score_adj value")
return
}
return func() {
log.Trace().
Int("oom_score_adj", origScore).
Msg("restoring oom_score_adj")
err = setOOMScoreAdj(origScore)
if err != nil {
log.Error().
Int("oom_score_adj", origScore).
AnErr("cause", err).
Msg("could not restore the current process' oom_score_adj value")
return
}
}
}

View File

@ -0,0 +1,66 @@
//go:build linux
package oomscore
import (
"fmt"
"os"
"path/filepath"
)
const (
available = true
)
// getOOMScore returns the current process' OOM score.
func getOOMScore() (int, error) {
return readInt("oom_score")
}
// getOOMScoreAdj returns the current process' OOM score adjustment.
func getOOMScoreAdj() (int, error) {
return readInt("oom_score_adj")
}
// setOOMScoreAdj sets the current process' OOM score adjustment.
func setOOMScoreAdj(newScore int) error {
return writeInt(newScore, "oom_score_adj")
}
// readInt reads an integer from /proc/{pid}/{filename}
func readInt(filename string) (int, error) {
fullPath := procPidPath(filename)
file, err := os.Open(fullPath)
if err != nil {
return 0, fmt.Errorf("opening %s: %w", fullPath, err)
}
var valueInFile int
n, err := fmt.Fscan(file, &valueInFile)
if err != nil {
return 0, fmt.Errorf("reading %s: %w", fullPath, err)
}
if n < 1 {
return 0, fmt.Errorf("reading %s: did not find a number", fullPath)
}
return valueInFile, nil
}
// writeInt writes an integer to /proc/{pid}/{filename}
func writeInt(value int, filename string) error {
fullPath := procPidPath(filename)
contents := fmt.Sprint(value)
err := os.WriteFile(fullPath, []byte(contents), os.ModePerm)
if err != nil {
return fmt.Errorf("writing %s: %w", fullPath, err)
}
return nil
}
// procPidPath returns "/proc/{pid}/{filename}".
func procPidPath(filename string) string {
pid := os.Getpid()
return filepath.Join("/proc", fmt.Sprint(pid), filename)
}

View File

@ -0,0 +1,19 @@
//go:build !linux
package oomscore
const (
available = false
)
func getOOMScore() (int, error) {
return 0, ErrNotImplemented
}
func getOOMScoreAdj() (int, error) {
return 0, ErrNotImplemented
}
func setOOMScoreAdj(int) error {
return ErrNotImplemented
}

View File

@ -9,4 +9,5 @@ const (
BugReportURL = "https://flamenco.blender.org/get-involved"
ShamanRequirementsURL = "https://flamenco.blender.org/usage/shared-storage/shaman/#requirements"
WorkerConfigURL = "https://flamenco.blender.org/usage/worker-configuration/"
OOMScoreAdjURL = WorkerConfigURL
)

View File

@ -12,7 +12,7 @@
}
],
"scripts": {
"dev": "vite --port 8081 --base /app/",
"dev": "vite --port 8081 --base /app/ --mode development",
"build": "vite build",
"preview": "vite preview --port 5050",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"

View File

@ -55,7 +55,7 @@ class ApiClient {
* @default {}
*/
this.defaultHeaders = {
'User-Agent': 'Flamenco/3.5-alpha1 / webbrowser'
'User-Agent': 'Flamenco/3.6-alpha0 / webbrowser'
};
/**

View File

@ -63,6 +63,9 @@ class AvailableJobType {
if (data.hasOwnProperty('label')) {
obj['label'] = ApiClient.convertToType(data['label'], 'String');
}
if (data.hasOwnProperty('description')) {
obj['description'] = ApiClient.convertToType(data['description'], 'String');
}
if (data.hasOwnProperty('settings')) {
obj['settings'] = ApiClient.convertToType(data['settings'], [AvailableJobSetting]);
}
@ -86,6 +89,12 @@ AvailableJobType.prototype['name'] = undefined;
*/
AvailableJobType.prototype['label'] = undefined;
/**
* The description/tooltip shown in the user interface.
* @member {String} description
*/
AvailableJobType.prototype['description'] = undefined;
/**
* @member {Array.<module:model/AvailableJobSetting>} settings
*/

View File

@ -1,18 +1,15 @@
let url = new URL(window.location.href);
// Uncomment this when the web interface is running on a different port than the
// API, for example when using the Vite devserver. Set the API port here.
if (url.port == '8081') {
// When the web interface is running on a different port than the API, for
// example when using the Vite devserver, setting the Vite --mode flag to
// "development" will force port 8080 for the API.
if (import.meta.env.MODE == 'development') {
url.port = '8080';
}
url.pathname = '/';
const flamencoAPIURL = url.href;
url.protocol = 'ws:';
const websocketURL = url.href;
const URLs = {
api: flamencoAPIURL,
ws: websocketURL,
api: `${url.protocol}//${url.hostname}:${url.port}/`,
ws: `${url.protocol.replace('http', 'ws')}//${url.hostname}:${url.port}/`,
};
// console.log("Flamenco API:", URLs.api);

View File

@ -74,11 +74,3 @@ Flamenco v3 is in active development at Blender Studio. Join
[the chat](https://blender.chat/channel/flamenco) to see what's happening!
{{< /columns >}}
-------------------
Looking for the old [Flamenco v2 documentation][F2]?
[F2]: /v2/

View File

@ -68,8 +68,8 @@ start afresh with the following steps:
The macOS "Silicon" build does not ship with FFmpeg, because a trusted build for
this architecture is not provided by the FFmpeg project. This is why Flamenco v3
did not ship macOS/ARM64 builds. As of v3.3 this architecture will be included
in the official Flamenco builds, but still without FFmpeg binary.
did not ship macOS/ARM64 builds. As of v3.3 this architecture is included in the
official Flamenco builds, but still without FFmpeg binary.
You can install FFmpeg using [the ffmpeg Homebrew formula][brew] or any other
method. Once installed Flamenco Worker should find it automatically. If not,

View File

@ -3,9 +3,10 @@ title: Compositor Nodes
weight: 10
---
*Job type documented and maintained by: [Dylan Blanqué][author].*
*Job type documented and maintained by: [Dylan Blanqué][author]. Please report any issues at [the script's Github project][github].*
[author]: https://projects.blender.org/Dylan-Blanque
[github]: https://github.com/dblanque/flamenco-compositor-script/issues
{{< hint >}}

View File

@ -19,6 +19,9 @@ This is an example of such a configuration file:
manager_url: http://flamenco.local:8080/
task_types: [blender, ffmpeg, file-management, misc]
restart_exit_code: 47
# Optional advanced option, available on Linux only:
oom_score_adjust: 500
```
- `manager_url`: The URL of the Manager to connect to. If the setting is blank
@ -31,10 +34,18 @@ restart_exit_code: 47
- `restart_exit_code`: Having this set to a non-zero value will mark this Worker
as 'restartable'. See [Shut Down & Restart Actions][restarting] for more
information.
- `oom_score_adjust`: an optional value between 0 and 1000, only available on
Linux. It configures the Out Of Memory behaviour of the Linux kernel. This is
the `oom_score_adj` value for all sub-processes started by the Worker. Set
this to a high value, so that when the machine runs out of memory when
rendering, it is Blender that gets killed, and not Flamenco Worker itself. For
more information, see [Linux Kernel: Per-Process Parameters][per-process-proc].
*This option was introduced in Flamenco v3.5.*
[scripts]: {{< ref "usage/job-types" >}}
[task-types]: {{< ref "usage/job-types" >}}#task-types
[restarting]: {{< ref "usage/worker-actions" >}}#shut-down--restart-actions
[per-process-proc]: https://docs.kernel.org/filesystems/proc.html#chapter-3-per-process-parameters
## Worker-Specific Files

View File

@ -1,2 +1,2 @@
latestVersion: "3.4"
latestExperimentalVersion: "3.5-alpha0"
latestVersion: "3.5"
latestExperimentalVersion: "3.6-alpha0"