349 lines
12 KiB
Python
349 lines
12 KiB
Python
#!/usr/bin/env python
|
|
from __future__ import absolute_import, print_function
|
|
|
|
r"""
|
|
Utility script that submits ComfyUI sync jobs as Deadline maintenance jobs.
|
|
|
|
Supports two types of sync:
|
|
1. ComfyUI portable installation sync (mirrors a shared ComfyUI portable install to local Workers)
|
|
2. ComfyUI models sync (syncs AI models from shared storage to local Worker drives, using modellist.txt)
|
|
|
|
Defaults are publication-safe examples:
|
|
- Pool none / group none / no region label
|
|
- Maintenance flag enabled
|
|
- Empty machine allow list by default
|
|
- DeadlineCommand plugin executes sync scripts via deadlinecommand -ExecuteScript
|
|
|
|
Usage examples:
|
|
# Submit both ComfyUI installation and models sync
|
|
python maintenance/submit_comfy_sync.py
|
|
|
|
# Submit only ComfyUI installation sync
|
|
python maintenance/submit_comfy_sync.py --type installation
|
|
|
|
# Submit only models sync
|
|
python maintenance/submit_comfy_sync.py --type models
|
|
|
|
# Custom settings
|
|
python maintenance/submit_comfy_sync.py --pool mypool --allowlist "GPU01,GPU02"
|
|
"""
|
|
|
|
import argparse
|
|
import io
|
|
import locale
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
# Default configuration
|
|
DEFAULT_POOL = "none"
|
|
DEFAULT_GROUP = "none"
|
|
DEFAULT_REGION = ""
|
|
DEFAULT_PRIORITY = 100
|
|
DEFAULT_WORKER_DEADLINE_COMMAND = "deadlinecommand"
|
|
DEFAULT_ALLOWLIST = ()
|
|
DEFAULT_ALLOWLIST_STRING = ",".join(DEFAULT_ALLOWLIST)
|
|
|
|
# Script paths
|
|
COMFYUI_SYNC_SCRIPT = r"\\YOUR-SERVER\share\scripts\maintenance\ComfyUISync.py"
|
|
COMFY_MODELS_SYNC_SCRIPT = r"\\YOUR-SERVER\share\scripts\maintenance\ComfyModelsSync.py"
|
|
|
|
|
|
def normalize_allowlist(raw_value):
|
|
"""Return a Deadline-ready comma-separated allowlist string and token list."""
|
|
if not raw_value:
|
|
return "", []
|
|
tokens = []
|
|
for chunk in raw_value.replace("\n", ",").split(","):
|
|
candidate = chunk.strip()
|
|
if candidate:
|
|
tokens.append(candidate)
|
|
return ",".join(tokens), tokens
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description="Submit ComfyUI sync maintenance jobs to Deadline."
|
|
)
|
|
parser.add_argument(
|
|
"--type",
|
|
choices=["both", "installation", "models"],
|
|
default="both",
|
|
help="Type of sync job to submit (default: both)"
|
|
)
|
|
parser.add_argument("--job-name", help="Base job name (will be suffixed with sync type)")
|
|
parser.add_argument(
|
|
"--comment",
|
|
help="Base job comment/description (will be suffixed with sync type)",
|
|
)
|
|
parser.add_argument("--pool", default=DEFAULT_POOL, help="Deadline pool.")
|
|
parser.add_argument("--group", default=DEFAULT_GROUP, help="Deadline group.")
|
|
parser.add_argument("--region", default=DEFAULT_REGION, help="Deadline region label.")
|
|
parser.add_argument("--priority", type=int, default=DEFAULT_PRIORITY, help="Job priority (0-100).")
|
|
parser.add_argument(
|
|
"--machine-limit",
|
|
type=int,
|
|
help="Limit each job to N Workers (default: auto = size of allow list).",
|
|
)
|
|
parser.add_argument(
|
|
"--allowlist",
|
|
default=DEFAULT_ALLOWLIST_STRING,
|
|
help="Comma or newline separated Worker names allowed to run the jobs.",
|
|
)
|
|
parser.add_argument(
|
|
"--comfyui-script",
|
|
default=COMFYUI_SYNC_SCRIPT,
|
|
help="UNC path to ComfyUISync.py accessible by Workers.",
|
|
)
|
|
parser.add_argument(
|
|
"--models-script",
|
|
default=COMFY_MODELS_SYNC_SCRIPT,
|
|
help="UNC path to ComfyModelsSync.py accessible by Workers.",
|
|
)
|
|
parser.add_argument(
|
|
"--additional-arguments",
|
|
default="",
|
|
help="Extra arguments passed to sync scripts (optional).",
|
|
)
|
|
parser.add_argument(
|
|
"--deadline-command",
|
|
default=None,
|
|
help="Path to deadlinecommand executable for submitting jobs. "
|
|
"Defaults to DEADLINE_PATH or PATH lookup.",
|
|
)
|
|
parser.add_argument(
|
|
"--worker-deadline-command",
|
|
default=DEFAULT_WORKER_DEADLINE_COMMAND,
|
|
help="Executable that Workers should run inside the DeadlineCommand plugin.",
|
|
)
|
|
parser.add_argument(
|
|
"--suspended",
|
|
action="store_true",
|
|
help="Submit jobs in suspended state (resume manually).",
|
|
)
|
|
parser.add_argument(
|
|
"--user-name",
|
|
default="",
|
|
help="Override the Deadline job owner (leave blank to use current user).",
|
|
)
|
|
parser.add_argument(
|
|
"--maintenance",
|
|
dest="maintenance",
|
|
action="store_true",
|
|
help="Mark as maintenance jobs (default).",
|
|
)
|
|
parser.add_argument(
|
|
"--no-maintenance",
|
|
dest="maintenance",
|
|
action="store_false",
|
|
help="Submit without the MaintenanceJob flag.",
|
|
)
|
|
parser.set_defaults(maintenance=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def find_deadline_command(explicit_path):
|
|
if explicit_path:
|
|
return explicit_path
|
|
|
|
env_path = os.environ.get("DEADLINE_PATH")
|
|
candidates = []
|
|
if env_path:
|
|
candidates.append(os.path.join(env_path, "deadlinecommand.exe"))
|
|
candidates.append(os.path.join(env_path, "deadlinecommand"))
|
|
|
|
candidates.append("deadlinecommand")
|
|
|
|
for candidate in candidates:
|
|
if candidate == "deadlinecommand":
|
|
return candidate
|
|
if os.path.isfile(candidate):
|
|
return candidate
|
|
|
|
return "deadlinecommand"
|
|
|
|
|
|
def create_temp_file(lines, suffix):
|
|
fd, path = tempfile.mkstemp(suffix=suffix)
|
|
try:
|
|
with io.open(fd, "w", encoding="utf-8") as handle:
|
|
handle.write(u"\n".join(lines))
|
|
handle.write(u"\n")
|
|
except Exception:
|
|
os.close(fd)
|
|
os.unlink(path)
|
|
raise
|
|
return path
|
|
|
|
|
|
def build_job_info(args, job_name, comment):
|
|
lines = [
|
|
"Plugin=DeadlineCommand",
|
|
"Name={0}".format(job_name),
|
|
"Comment={0}".format(comment),
|
|
"Pool={0}".format(args.pool),
|
|
"Group={0}".format(args.group),
|
|
"Region={0}".format(args.region),
|
|
"Priority={0}".format(args.priority),
|
|
"MachineLimit={0}".format(args.machine_limit),
|
|
"MaintenanceJob={0}".format("true" if args.maintenance else "false"),
|
|
"OnJobComplete=Nothing",
|
|
]
|
|
if args.allowlist:
|
|
lines.append("Whitelist={0}".format(args.allowlist))
|
|
if args.user_name:
|
|
lines.append("UserName={0}".format(args.user_name))
|
|
if args.suspended:
|
|
lines.append("SubmitSuspended=true")
|
|
return lines
|
|
|
|
|
|
def build_plugin_info(args, script_path):
|
|
argument_parts = ["-ExecuteScript", '"{0}"'.format(script_path)]
|
|
if args.additional_arguments:
|
|
argument_parts.append(args.additional_arguments)
|
|
|
|
arguments = " ".join(argument_parts)
|
|
lines = [
|
|
"Executable={0}".format(args.worker_deadline_command),
|
|
"Arguments={0}".format(arguments),
|
|
"ShellExecute=False",
|
|
"StartupDirectory=",
|
|
"SingleFramesOnly=False",
|
|
"HideWindow=False",
|
|
"IgnoreExitCode=False",
|
|
]
|
|
return lines
|
|
|
|
|
|
def submit_job(deadline_command, job_info_path, plugin_info_path, job_name):
|
|
cmd = [deadline_command, "SubmitJob", job_info_path, plugin_info_path]
|
|
print("[submit_comfy_sync] Submitting job '{0}'...".format(job_name))
|
|
print("[submit_comfy_sync] Running:", " ".join(cmd))
|
|
|
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
stdout_bytes, stderr_bytes = process.communicate()
|
|
encoding = locale.getpreferredencoding(False) or "utf-8"
|
|
|
|
stdout_text = stdout_bytes.decode(encoding, "ignore") if isinstance(stdout_bytes, bytes) else stdout_bytes
|
|
stderr_text = stderr_bytes.decode(encoding, "ignore") if isinstance(stderr_bytes, bytes) else stderr_bytes
|
|
|
|
if process.returncode != 0:
|
|
raise RuntimeError(
|
|
"deadlinecommand failed (code {0}):\nSTDOUT:\n{1}\nSTDERR:\n{2}".format(
|
|
process.returncode, stdout_text, stderr_text
|
|
)
|
|
)
|
|
|
|
print(stdout_text.strip())
|
|
return stdout_text
|
|
|
|
|
|
def submit_comfyui_sync(args, deadline_command):
|
|
"""Submit ComfyUI installation sync job."""
|
|
job_name = args.job_name or "ComfyUI Installation Sync"
|
|
comment = args.comment or "Mirrors ComfyUI portable installation from shared storage to local Workers"
|
|
|
|
print("\n[submit_comfy_sync] Preparing ComfyUI installation sync job...")
|
|
|
|
if not os.path.exists(args.comfyui_script):
|
|
print(
|
|
"[submit_comfy_sync] Warning: ComfyUI sync script not found right now ({0}). "
|
|
"Ensure Workers can reach this path.".format(args.comfyui_script)
|
|
)
|
|
|
|
job_info_lines = build_job_info(args, job_name, comment)
|
|
plugin_info_lines = build_plugin_info(args, args.comfyui_script)
|
|
|
|
job_info_path = create_temp_file(job_info_lines, ".job")
|
|
plugin_info_path = create_temp_file(plugin_info_lines, ".plugin")
|
|
|
|
try:
|
|
result = submit_job(deadline_command, job_info_path, plugin_info_path, job_name)
|
|
return result
|
|
finally:
|
|
for temp_path in (job_info_path, plugin_info_path):
|
|
try:
|
|
os.unlink(temp_path)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def submit_models_sync(args, deadline_command):
|
|
"""Submit ComfyUI models sync job."""
|
|
job_name = (args.job_name + " - Models" if args.job_name else "ComfyUI Models Sync")
|
|
comment = (args.comment + " - Models" if args.comment else "Syncs ComfyUI AI models to local Worker storage based on modellist.txt")
|
|
|
|
print("\n[submit_comfy_sync] Preparing ComfyUI models sync job...")
|
|
|
|
if not os.path.exists(args.models_script):
|
|
print(
|
|
"[submit_comfy_sync] Warning: Models sync script not found right now ({0}). "
|
|
"Ensure Workers can reach this path.".format(args.models_script)
|
|
)
|
|
|
|
job_info_lines = build_job_info(args, job_name, comment)
|
|
plugin_info_lines = build_plugin_info(args, args.models_script)
|
|
|
|
job_info_path = create_temp_file(job_info_lines, ".job")
|
|
plugin_info_path = create_temp_file(plugin_info_lines, ".plugin")
|
|
|
|
try:
|
|
result = submit_job(deadline_command, job_info_path, plugin_info_path, job_name)
|
|
return result
|
|
finally:
|
|
for temp_path in (job_info_path, plugin_info_path):
|
|
try:
|
|
os.unlink(temp_path)
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def main():
|
|
args = parse_args()
|
|
allowlist_str, allowlist_tokens = normalize_allowlist(args.allowlist)
|
|
args.allowlist = allowlist_str
|
|
allowlist_count = len(allowlist_tokens)
|
|
if args.machine_limit is None:
|
|
args.machine_limit = allowlist_count if allowlist_count else 0
|
|
|
|
print("[submit_comfy_sync] ComfyUI sync job submission starting...")
|
|
print("[submit_comfy_sync] Pool: {0}".format(args.pool))
|
|
print("[submit_comfy_sync] Group: {0}".format(args.group))
|
|
print("[submit_comfy_sync] Region: {0}".format(args.region))
|
|
print("[submit_comfy_sync] Allowlist: {0} workers".format(allowlist_count))
|
|
print("[submit_comfy_sync] Sync type: {0}".format(args.type))
|
|
|
|
deadline_command = find_deadline_command(args.deadline_command)
|
|
|
|
jobs_submitted = []
|
|
|
|
try:
|
|
if args.type in ["both", "installation"]:
|
|
result = submit_comfyui_sync(args, deadline_command)
|
|
jobs_submitted.append(("ComfyUI Installation", result))
|
|
|
|
if args.type in ["both", "models"]:
|
|
result = submit_models_sync(args, deadline_command)
|
|
jobs_submitted.append(("ComfyUI Models", result))
|
|
|
|
print("\n[submit_comfy_sync] Job submission completed successfully!")
|
|
print("[submit_comfy_sync] Jobs submitted: {0}".format(len(jobs_submitted)))
|
|
for job_type, result in jobs_submitted:
|
|
print("[submit_comfy_sync] - {0}: {1}".format(job_type, result.strip() if result else "Unknown"))
|
|
|
|
except Exception as e:
|
|
print("[submit_comfy_sync] ERROR: {0}".format(e))
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
sys.exit(130)
|
|
except Exception as exc:
|
|
print("[submit_comfy_sync] ERROR:", exc)
|
|
sys.exit(1)
|