70cf45ead4
Deploy / deploy (push) Successful in 7m2s
Blender work is not a shot submission: no Export, no version, no validation, QC or delivery. It is "Ctrl+F12, but on the farm", so it is modelled as what it is — a queue entry and nothing more. That turned out to need very little. Claim, progress, fail and complete-render already tolerated a job with no Export behind them, so a Blender job reuses the whole lifecycle; RenderJob gains projectId, label and submittedBy so a job with no shot code still has something to show. Capability gating is new and was already missing: the claim now filters job types by the machine's reported tools. Without it a workstation without Blender would claim Blender work and fail it repeatedly, burning the attempts while a machine that could have rendered it sat idle. The worker asks for Blender jobs unconditionally and the server refuses them — trusting the worker to self-select would have left the same hole. The runner treats a clean exit with no saved frames as a failure: Blender exits 0 when a frame range is empty or an output path is unwritable, and a job that silently rendered nothing is the worst outcome available. Standalone jobs auto-requeue on a transient failure like any other work; delivery builds are deliberately excluded, since a half-written package wants a human first. Adds the Blender add-on (BlenderPanel/) and its docs. The add-on refuses to submit an unsaved file — the farm opens the .blend from disk, so unsaved changes would render the previous state without warning. Its payload is pinned by a contract test, since add-on and server only ever meet over JSON. Worker 1.5.0. Adds migration 20260815000000_blender_render_jobs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
122 lines
4.8 KiB
C#
122 lines
4.8 KiB
C#
using System.Net.Http.Headers;
|
|
|
|
namespace VFXReviewWorker;
|
|
|
|
/// <summary>
|
|
/// `VFXReviewWorker.exe --check` — install verification for a new machine.
|
|
/// Resolves every path and dependency the worker needs, reports what is
|
|
/// missing, and exits non-zero if anything would stop it running. Cheaper than
|
|
/// installing the service and reading logs to find a typo'd path.
|
|
/// </summary>
|
|
public static class Preflight
|
|
{
|
|
public static async Task<int> RunAsync(string? configPath)
|
|
{
|
|
var ok = true;
|
|
Console.WriteLine("VFXReview RenderWorker preflight");
|
|
Console.WriteLine();
|
|
|
|
var resolvedConfigPath = configPath ?? WorkerOptions.DefaultConfigPath;
|
|
WorkerOptions? options = null;
|
|
try
|
|
{
|
|
options = WorkerOptions.Load(configPath);
|
|
ok &= Report("config file", resolvedConfigPath, true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Report("config file", resolvedConfigPath, false, ex.Message);
|
|
Console.WriteLine();
|
|
Console.WriteLine("Cannot continue without a config file. See RenderWorker/README.md.");
|
|
return 1;
|
|
}
|
|
|
|
Console.WriteLine($" worker version : {options.WorkerVersion}");
|
|
Console.WriteLine($" machine name : {options.MachineName}");
|
|
Console.WriteLine($" server : {options.ServerUrl}");
|
|
Console.WriteLine($" api key : {(string.IsNullOrWhiteSpace(options.ApiKey) ? "(missing)" : "set")}");
|
|
Console.WriteLine();
|
|
|
|
ok &= Report("aerender.exe", options.AerenderPath, File.Exists(options.AerenderPath));
|
|
// Not required: a machine without Blender simply never claims Blender
|
|
// jobs, so this reports rather than fails.
|
|
Report("blender.exe", options.BlenderPath ?? "(not configured)",
|
|
!string.IsNullOrWhiteSpace(options.BlenderPath) && File.Exists(options.BlenderPath),
|
|
"needed only for Blender render jobs");
|
|
var afterFx = options.ResolveAfterFxPath();
|
|
ok &= Report("AfterFX.com", afterFx, File.Exists(afterFx),
|
|
"needed for preview builds only");
|
|
var script = options.ResolvePreviewScriptPath();
|
|
ok &= Report("preview script", script, File.Exists(script),
|
|
"needed for preview builds only");
|
|
|
|
if (!string.IsNullOrWhiteSpace(options.FfmpegPath))
|
|
{
|
|
Report("ffmpeg", options.FfmpegPath!, File.Exists(options.FfmpegPath!),
|
|
"optional — thumbnails are skipped without it");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" [ - ] ffmpeg not configured (thumbnails will be skipped)");
|
|
}
|
|
|
|
// Path mappings: prove the mapped roots are actually reachable here.
|
|
Console.WriteLine();
|
|
if (options.PathMappings.Count == 0)
|
|
{
|
|
Console.WriteLine(" [ ! ] no pathMappings configured — manifest UNC paths will be used as-is");
|
|
}
|
|
foreach (var m in options.PathMappings)
|
|
{
|
|
var reachable = Directory.Exists(m.To);
|
|
Report($"path map {m.From}", m.To, reachable, "target must be reachable from this machine");
|
|
ok &= reachable;
|
|
}
|
|
|
|
// Server reachability + API key validity in one call.
|
|
Console.WriteLine();
|
|
try
|
|
{
|
|
using var http = new HttpClient { BaseAddress = new Uri(options.ServerUrl.TrimEnd('/') + "/"), Timeout = TimeSpan.FromSeconds(15) };
|
|
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey);
|
|
var res = await http.GetAsync("api/ext/projects");
|
|
if (res.IsSuccessStatusCode)
|
|
{
|
|
Console.WriteLine(" [ok ] server reachable and API key accepted");
|
|
}
|
|
else if ((int)res.StatusCode == 401)
|
|
{
|
|
Console.WriteLine(" [FAIL] server reachable but API key was rejected (401)");
|
|
ok = false;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($" [FAIL] server responded {(int)res.StatusCode}");
|
|
ok = false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($" [FAIL] cannot reach {options.ServerUrl}: {ex.Message}");
|
|
ok = false;
|
|
}
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine(ok
|
|
? "All checks passed — safe to install the service."
|
|
: "One or more checks FAILED — fix the above before installing.");
|
|
return ok ? 0 : 1;
|
|
}
|
|
|
|
private static bool Report(string label, string path, bool exists, string? note = null)
|
|
{
|
|
var status = exists ? "ok " : "FAIL";
|
|
Console.WriteLine($" [{status}] {label,-16} {path}");
|
|
if (!exists && note is not null)
|
|
{
|
|
Console.WriteLine($" ({note})");
|
|
}
|
|
return exists;
|
|
}
|
|
}
|