Skip to content

Commit 94302e1

Browse files
authored
chore: remove admin jobs (#795)
Impacts: validator
1 parent 2afcc60 commit 94302e1

File tree

9 files changed

+139
-341
lines changed

9 files changed

+139
-341
lines changed

validator/app/src/compute_horde_validator/validator/admin.py

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
from compute_horde.base.admin import AddOnlyAdminMixin, ReadOnlyAdminMixin
2-
from compute_horde.executor_class import EXECUTOR_CLASS
3-
from django import forms
1+
from compute_horde.base.admin import ReadOnlyAdminMixin
42
from django.contrib import (
53
admin, # noqa
64
messages, # noqa
@@ -11,7 +9,6 @@
119
from rangefilter.filters import DateTimeRangeFilter, NumericRangeFilter
1210

1311
from compute_horde_validator.validator.models import (
14-
AdminJobRequest,
1512
Miner,
1613
MinerBlacklist,
1714
OrganicJob,
@@ -26,7 +23,6 @@
2623
from compute_horde_validator.validator.models.scoring.internal import Weights
2724

2825
# noqa
29-
from compute_horde_validator.validator.tasks import trigger_run_admin_job_request # noqa
3026

3127
admin.site.site_header = "ComputeHorde Validator Administration"
3228
admin.site.site_title = "compute_horde_validator"
@@ -35,52 +31,6 @@
3531
admin.site.index_template = "admin/validator_index.html"
3632

3733

38-
class AdminJobRequestForm(forms.ModelForm):
39-
executor_class = forms.ChoiceField()
40-
41-
class Meta:
42-
model = AdminJobRequest
43-
fields = [
44-
"uuid",
45-
"miner",
46-
"executor_class",
47-
"docker_image",
48-
"timeout",
49-
"args",
50-
"use_gpu",
51-
"input_url",
52-
"output_url",
53-
"status_message",
54-
]
55-
56-
def __init__(self, *args, **kwargs):
57-
super().__init__(*args, **kwargs)
58-
if self.fields:
59-
# exclude blacklisted miners from valid results
60-
self.fields["miner"].queryset = Miner.objects.exclude(minerblacklist__isnull=False)
61-
self.fields["executor_class"].choices = [(name, name) for name in EXECUTOR_CLASS]
62-
63-
64-
class AdminJobRequestAddOnlyAdmin(admin.ModelAdmin, AddOnlyAdminMixin):
65-
form = AdminJobRequestForm
66-
exclude = ["env"] # not used ?
67-
list_display = ["uuid", "executor_class", "docker_image", "use_gpu", "miner", "created_at"]
68-
readonly_fields = ["uuid", "status_message"]
69-
ordering = ["-created_at"]
70-
autocomplete_fields = ["miner"]
71-
72-
def save_model(self, request, obj, form, change):
73-
super().save_model(request, obj, form, change)
74-
trigger_run_admin_job_request.delay(obj.id)
75-
organic_job = OrganicJob.objects.filter(job_uuid=obj.uuid).first()
76-
msg = (
77-
f"Please see <a href='/admin/validator/organicjob/{organic_job.pk}/change/'>ORGANIC JOB</a> for further details"
78-
if organic_job
79-
else f"Job {obj.uuid} failed to initialize"
80-
)
81-
messages.add_message(request, messages.INFO, mark_safe(msg))
82-
83-
8434
class JobReadOnlyAdmin(admin.ModelAdmin, ReadOnlyAdminMixin):
8535
list_display = ["job_uuid", "miner", "executor_class", "status", "updated_at"]
8636
search_fields = ["job_uuid", "miner__hotkey"]
@@ -317,7 +267,6 @@ def total_allowance(self, obj):
317267
admin.site.register(Miner, admin_class=MinerReadOnlyAdmin)
318268
admin.site.register(OrganicJob, admin_class=JobReadOnlyAdmin)
319269
admin.site.register(MinerBlacklist, admin_class=MinerBlacklistAdmin)
320-
admin.site.register(AdminJobRequest, admin_class=AdminJobRequestAddOnlyAdmin)
321270
admin.site.register(SystemEvent, admin_class=SystemEventAdmin)
322271
admin.site.register(Weights, admin_class=WeightsReadOnlyAdmin)
323272
admin.site.register(ValidatorWhitelist, admin_class=ValidatorWhitelistAdmin)
Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
1+
import shlex
12
import sys
3+
import uuid
24

35
from asgiref.sync import async_to_sync
46
from compute_horde.executor_class import DEFAULT_EXECUTOR_CLASS
7+
from compute_horde.fv_protocol.facilitator_requests import V2JobRequest
58
from compute_horde.fv_protocol.validator_requests import JobStatusUpdate
9+
from compute_horde_core.executor_class import ExecutorClass
10+
from django.conf import settings
611
from django.core.management.base import BaseCommand
7-
from django.utils import timezone
812

13+
from compute_horde_validator.validator.allowance.default import allowance
914
from compute_horde_validator.validator.models import (
10-
AdminJobRequest,
1115
Miner,
1216
MinerBlacklist,
1317
OrganicJob,
1418
)
15-
from compute_horde_validator.validator.tasks import run_admin_job_request
19+
from compute_horde_validator.validator.organic_jobs.miner_client import MinerClient
20+
from compute_horde_validator.validator.organic_jobs.miner_driver import (
21+
drive_organic_job,
22+
)
23+
24+
25+
def get_keypair():
26+
return settings.BITTENSOR_WALLET().get_hotkey()
1627

1728

1829
async def notify_job_status_update(msg: JobStatusUpdate):
@@ -40,44 +51,36 @@ class Command(BaseCommand):
4051
"""
4152

4253
def add_arguments(self, parser):
43-
parser.add_argument("--miner_hotkey", default=None, type=str, help="Miner Hotkey")
44-
# TODO: mock miner with address, port, ip_type
45-
# parser.add_argument("--miner_address", default=None, type=str, help="Miner IPv4 address")
46-
# parser.add_argument("--miner_port", default=None, type=int, help="Miner port")
47-
parser.add_argument(
48-
"--executor_class", type=str, help="Executor class", default=DEFAULT_EXECUTOR_CLASS
49-
)
50-
parser.add_argument("--timeout", type=int, help="Timeout value", required=True)
54+
parser.add_argument("--miner_hotkey", default=None, help="Miner Hotkey")
55+
parser.add_argument("--miner_address", default=None, help="Miner IP address")
56+
parser.add_argument("--miner_ip_version", default=4, help="Miner IP version")
57+
parser.add_argument("--miner_port", default=None, type=int, help="Miner port")
5158
parser.add_argument(
52-
"--docker_image", type=str, help="docker image for job execution", required=True
59+
"--executor_class",
60+
type=ExecutorClass,
61+
help="Executor class",
62+
default=DEFAULT_EXECUTOR_CLASS,
5363
)
64+
parser.add_argument("--docker_image", help="docker image for job execution", required=True)
5465
parser.add_argument(
55-
"--cmd_args",
56-
type=str,
57-
default="",
58-
help="arguments passed to the script or docker image",
66+
"--cmd_args", default="", help="arguments passed to the script or docker image"
5967
)
68+
parser.add_argument("--use_gpu", action="store_true", help="use gpu for job execution")
69+
6070
parser.add_argument(
61-
"--use_gpu",
62-
action="store_true",
63-
help="use gpu for job execution",
71+
"--download_time_limit", default=10, type=int, help="download time limit in seconds"
6472
)
6573
parser.add_argument(
66-
"--input_url",
67-
type=str,
68-
default="",
69-
help="input url for job execution",
74+
"--execution_time_limit", default=100, type=int, help="execution time limit in seconds"
7075
)
7176
parser.add_argument(
72-
"--output_url",
73-
type=str,
74-
default="",
75-
help="output url for job execution",
77+
"--upload_time_limit", default=10, type=int, help="upload time limit in seconds"
7678
)
7779
parser.add_argument(
78-
"--nonzero_if_not_complete",
79-
action="store_true",
80-
help="if job completes with PENDING or FAILED state, exit with non-zero status code",
80+
"--streaming_start_time_limit",
81+
default=10,
82+
type=int,
83+
help="streaming start time limit in seconds",
8184
)
8285

8386
def handle(self, *args, **options):
@@ -102,32 +105,64 @@ def handle(self, *args, **options):
102105

103106
print(f"\nPicked miner: {miner} to run the job")
104107

105-
job_request = AdminJobRequest.objects.create(
106-
miner=miner,
107-
timeout=options["timeout"],
108+
miner_address = miner.address
109+
miner_ip_version = miner.ip_version
110+
miner_port = miner.port
111+
if options["miner_address"]:
112+
miner_address = options["miner_address"]
113+
miner_ip_version = options["miner_ip_version"]
114+
if options["miner_port"]:
115+
miner_port = options["miner_port"]
116+
117+
job_request = V2JobRequest(
118+
uuid=str(uuid.uuid4()),
108119
executor_class=options["executor_class"],
109120
docker_image=options["docker_image"],
110-
args=options["cmd_args"],
121+
args=shlex.split(options["cmd_args"]),
122+
env={},
111123
use_gpu=options["use_gpu"],
112-
input_url=options["input_url"],
113-
output_url=options["output_url"],
114-
created_at=timezone.now(),
124+
download_time_limit=options["download_time_limit"],
125+
execution_time_limit=options["execution_time_limit"],
126+
upload_time_limit=options["upload_time_limit"],
127+
streaming_start_time_limit=options["streaming_start_time_limit"],
128+
)
129+
130+
job = OrganicJob.objects.create(
131+
job_uuid=str(job_request.uuid),
132+
miner=miner,
133+
miner_address=miner_address,
134+
miner_address_ip_version=miner_ip_version,
135+
miner_port=miner_port,
136+
namespace=job_request.job_namespace or job_request.docker_image or None,
137+
executor_class=job_request.executor_class,
138+
job_description="User job from facilitator",
139+
block=allowance().get_current_block(),
115140
)
116141

142+
async def _run_job():
143+
keypair = get_keypair()
144+
miner_client = MinerClient(
145+
miner_hotkey=miner.hotkey,
146+
miner_address=miner_address,
147+
miner_port=miner_port,
148+
job_uuid=str(job.job_uuid),
149+
my_keypair=keypair,
150+
)
151+
await drive_organic_job(
152+
miner_client,
153+
job,
154+
job_request,
155+
notify_callback=notify_job_status_update,
156+
)
157+
117158
try:
118-
async_to_sync(run_admin_job_request)(job_request.pk, callback=notify_job_status_update)
159+
async_to_sync(_run_job)()
160+
except Exception as e:
161+
print(f"Failed to run job {job.job_uuid}: {e}")
162+
sys.exit(1)
119163
except KeyboardInterrupt:
120164
print("Interrupted by user")
121165
sys.exit(1)
122166

123-
try:
124-
job_request.refresh_from_db()
125-
job = OrganicJob.objects.get(job_uuid=job_request.uuid)
126-
print(f"\nJob {job.job_uuid} done processing")
127-
except OrganicJob.DoesNotExist:
128-
print(f"\nJob {job_request.uuid} not found")
129-
sys.exit(1)
130-
131-
if options["nonzero_if_not_complete"] and job.status != OrganicJob.Status.COMPLETED:
132-
print(f"\nJob {job_request.uuid} was unsuccessful, status = {job.status}")
133-
sys.exit(1)
167+
job.refresh_from_db()
168+
print(f"\nJob {job.job_uuid} done processing with status: {job.status}")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Generated by Django 4.2.19 on 2025-11-05 14:29
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("validator", "0092_remove_prompt_sample_remove_promptsample_series_and_more"),
9+
]
10+
11+
operations = [
12+
migrations.DeleteModel(
13+
name="AdminJobRequest",
14+
),
15+
]

validator/app/src/compute_horde_validator/validator/models/spaghetti.py

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import logging
2-
import shlex
32
import uuid
43
from datetime import datetime
54
from decimal import Decimal
65
from typing import Self
76

87
from compute_horde.executor_class import DEFAULT_EXECUTOR_CLASS
9-
from compute_horde_core.output_upload import OutputUpload, ZipAndHttpPutUpload
10-
from compute_horde_core.volume import Volume, ZipUrlVolume
118
from django.conf import settings
129
from django.contrib.postgres.fields import ArrayField
1310
from django.db import models
@@ -297,48 +294,6 @@ def get_absolute_url(self):
297294
return f"/admin/validator/organicjob/{self.pk}/change/"
298295

299296

300-
class AdminJobRequest(models.Model):
301-
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
302-
miner = models.ForeignKey(Miner, on_delete=models.PROTECT)
303-
executor_class = models.CharField(
304-
max_length=255, default=DEFAULT_EXECUTOR_CLASS, help_text="executor hardware class"
305-
)
306-
timeout = models.PositiveIntegerField(default=300, help_text="timeout in seconds")
307-
308-
docker_image = models.CharField(max_length=255, help_text="docker image for job execution")
309-
310-
args = models.TextField(blank=True, help_text="arguments passed to the script or docker image")
311-
env = models.JSONField(blank=True, default=dict, help_text="environment variables for the job")
312-
313-
use_gpu = models.BooleanField(default=False, help_text="Whether to use GPU for the job")
314-
input_url = models.URLField(
315-
blank=True, help_text="URL to the input data source", max_length=1000
316-
)
317-
output_url = models.TextField(blank=True, help_text="URL for uploading output")
318-
319-
created_at = models.DateTimeField(auto_now_add=True)
320-
321-
status_message = models.TextField(blank=True, default="")
322-
323-
def get_args(self):
324-
return shlex.split(self.args)
325-
326-
def __str__(self):
327-
return f"uuid: {self.uuid} - miner hotkey: {self.miner.hotkey}"
328-
329-
@property
330-
def volume(self) -> Volume | None:
331-
if self.input_url:
332-
return ZipUrlVolume(contents=self.input_url)
333-
return None
334-
335-
@property
336-
def output_upload(self) -> OutputUpload | None:
337-
if self.output_url:
338-
return ZipAndHttpPutUpload(url=self.output_url)
339-
return None
340-
341-
342297
class MinerPreliminaryReservationQueryset(models.QuerySet["MinerPreliminaryReservation"]):
343298
def active(self, at: datetime | None = None) -> Self:
344299
at = at or now()

0 commit comments

Comments
 (0)