Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
95bf70c
organization oidc login added
ihsaan-ullah Feb 7, 2024
93a8af6
unused test file removed
ihsaan-ullah Feb 7, 2024
4004cec
http client
ihsaan-ullah Feb 8, 2024
0c2e12a
some changes
ihsaan-ullah Feb 20, 2024
f6e5a8b
oidc login and signup added
ihsaan-ullah Feb 20, 2024
9935f2c
oidc flow completed
ihsaan-ullah Mar 4, 2024
df0ceab
Fix revoking tasks on custom queues
cjh1 Mar 5, 2024
48cb7f8
Prevent LimitOverrunError with large output lines
cjh1 Mar 5, 2024
0fe72d5
terms and condition check added
ihsaan-ullah Mar 16, 2024
5966fef
Merge branch 'develop' into oidc_2
ihsaan-ullah Mar 16, 2024
f017559
one terms checkbox for all organization login buttons
ihsaan-ullah Mar 31, 2024
1f8bdcd
Merge branch 'oidc_2' of https://github.com/codalab/codabench into oi…
ihsaan-ullah Mar 31, 2024
7d33156
Merge branch 'develop' into oidc_2
ihsaan-ullah Apr 4, 2024
1b4dde2
removed sandbox property from iframe to allow links in the iframe
ihsaan-ullah Apr 11, 2024
df76fd9
Merge pull request #1351 from cjh1/limit-overrun
ihsaan-ullah Apr 11, 2024
df26ec2
Merge pull request #1309 from codalab/oidc_2
Didayolo Apr 11, 2024
362f443
Detailed results title removed
ihsaan-ullah Apr 11, 2024
e2bab19
Merge pull request #1404 from codalab/limit_over_run_outpu
Didayolo Apr 12, 2024
dcd9f03
Merge pull request #1403 from codalab/iframe
Didayolo Apr 12, 2024
2423eb8
Detailed results configuration (#1374)
ihsaan-ullah Apr 12, 2024
403fc2b
Option to hide make submission public button from participants (#1372)
ihsaan-ullah Apr 12, 2024
c3ac800
Submission ID in leaderboard (#1370)
ihsaan-ullah Apr 12, 2024
34bbf0b
CPU limit on default queue (#1154)
bbearce Apr 16, 2024
bade1eb
Auto-migrate leaderboard submissions (#1341)
ihsaan-ullah Apr 16, 2024
80b634f
Merge pull request #1352 from cjh1/revoke
Didayolo Apr 19, 2024
b0bbb71
Clean code in celery_config.py (#1420)
Didayolo Apr 19, 2024
992bd86
Update README.md
Didayolo Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env_sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ DB_PORT=5432
DJANGO_SETTINGS_MODULE=settings.develop
ALLOWED_HOSTS=localhost,example.com
SUBMISSIONS_API_URL=http://django:8000/api
MAX_EXECUTION_TIME_LIMIT=600 # time limit for the default queue (in seconds)

# Local domain definition
DOMAIN_NAME=localhost:80
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ $ docker-compose exec django ./manage.py generate_data
$ docker-compose exec django ./manage.py collectstatic --noinput
```

You can now login as username "admin" with password "admin" at http://localhost:8000
You can now login as username "admin" with password "admin" at http://localhost/

If you ever need to reset the database, use the script `./reset_db.sh`

For more information about installation, checkout [Codabench Basic Installation Guide](https://github.com/codalab/codabench/wiki/Codabench-Installation) and [How to Deploy Server](https://github.com/codalab/codabench/wiki/How-to-deploy-Codabench-on-your-server).


## License

Expand Down
15 changes: 14 additions & 1 deletion compute_worker/compute_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,12 +507,25 @@ async def _run_container_engine_cmd(self, engine_cmd, kind):
websocket = await websockets.connect(self.websocket_url)
websocket_errors = (socket.gaierror, websockets.WebSocketException, websockets.ConnectionClosedError, ConnectionRefusedError)

# Function to read a line, if the line is larger than the buffer size we will
# return the buffer so we can continue reading until we get a newline, rather
# than getting a LimitOverrunError
async def _readline_or_chunk(stream):
try:
return await stream.readuntil(b"\n")
except asyncio.exceptions.IncompleteReadError as e:
# Just return what has been read so far
return e.partial
except asyncio.exceptions.LimitOverrunError as e:
# If we get a LimitOverrunError, we will return the buffer so we can continue reading
return await stream.read(e.consumed)

while any(v["continue"] for k, v in self.logs[kind].items() if k in ['stdout', 'stderr']):
try:
logs = [self.logs[kind][key] for key in ('stdout', 'stderr')]
for value in logs:
try:
out = await asyncio.wait_for(value["stream"].readline(), timeout=.1)
out = await asyncio.wait_for(_readline_or_chunk(value["stream"]), timeout=.1)
if out:
value["data"] += out
print("WS: " + str(out))
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,4 @@ services:
logging:
options:
max-size: "20k"
max-file: "10"
max-file: "10"
6 changes: 6 additions & 0 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ class Meta:
'registration_auto_approve',
'queue',
'enable_detailed_results',
'show_detailed_results_in_submission_panel',
'show_detailed_results_in_leaderboard',
'auto_run_submissions',
'can_participants_make_submissions_public',
'make_programs_available',
'make_input_data_available',
'docker_image',
Expand Down Expand Up @@ -373,7 +376,10 @@ class Meta:
'submission_count',
'queue',
'enable_detailed_results',
'show_detailed_results_in_submission_panel',
'show_detailed_results_in_leaderboard',
'auto_run_submissions',
'can_participants_make_submissions_public',
'make_programs_available',
'make_input_data_available',
'docker_image',
Expand Down
8 changes: 7 additions & 1 deletion src/apps/api/serializers/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class SubmissionSerializer(serializers.ModelSerializer):
task = TaskSerializer()
created_when = serializers.DateTimeField(format="%Y-%m-%d %H:%M")
auto_run = serializers.SerializerMethodField(read_only=True)
can_make_submissions_public = serializers.SerializerMethodField(read_only=True)

class Meta:
model = Submission
Expand All @@ -67,7 +68,8 @@ class Meta:
'leaderboard',
'on_leaderboard',
'task',
'auto_run'
'auto_run',
'can_make_submissions_public',
)
read_only_fields = (
'pk',
Expand All @@ -85,6 +87,10 @@ def get_auto_run(self, instance):
# returns this submission's competition auto_run_submissions Flag
return instance.phase.competition.auto_run_submissions

def get_can_make_submissions_public(self, instance):
# returns this submission's competition can_participants_make_submissions_public Flag
return instance.phase.competition.can_participants_make_submissions_public


class SubmissionLeaderBoardSerializer(serializers.ModelSerializer):
scores = SubmissionScoreSerializer(many=True)
Expand Down
4 changes: 2 additions & 2 deletions src/apps/api/tests/test_competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def test_manual_migration_makes_submissions_from_one_phase_in_another(self):

# make 5 submissions in phase 1
for _ in range(5):
SubmissionFactory(owner=self.creator, phase=self.phase_1, status=Submission.FINISHED)
SubmissionFactory(owner=self.creator, phase=self.phase_1, status=Submission.FINISHED, leaderboard=self.leaderboard)
assert self.phase_1.submissions.count() == 5
assert self.phase_2.submissions.count() == 0

Expand All @@ -130,7 +130,7 @@ def test_manual_migration_makes_submissions_out_of_only_parents_not_children(sel
self.client.login(username='creator', password='creator')

# make 1 submission with 4 children
parent = SubmissionFactory(owner=self.creator, phase=self.phase_1, has_children=True, status=Submission.FINISHED)
parent = SubmissionFactory(owner=self.creator, phase=self.phase_1, has_children=True, status=Submission.FINISHED, leaderboard=self.leaderboard)
for _ in range(4):
# Make a submission _and_ new Task for phase 2
self.phase_2.tasks.add(TaskFactory())
Expand Down
6 changes: 3 additions & 3 deletions src/apps/api/views/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,15 +352,15 @@ def get_detail_result(self, request, pk):
)
else:
return Response({
"error_msg": "Visualizations are disabled"},
"error_msg": "Detailed results are disable for this competition!"},
status=status.HTTP_404_NOT_FOUND
)

@action(detail=True, methods=('GET',))
def toggle_public(self, request, pk):
submission = super().get_object()
if not self.has_admin_permission(request.user, submission):
raise PermissionDenied(f'You do not have permission to publish this submissions')
if not submission.phase.competition.can_participants_make_submissions_public:
raise PermissionDenied("You do not have permission to make this submissions public/private")
is_public = not submission.is_public
submission.data.is_public = is_public
submission.data.save(send=False)
Expand Down
1 change: 1 addition & 0 deletions src/apps/chahub/tests/test_chahub_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def setUp(self):
participant=self.participant,
status='Finished',
is_public=True,
leaderboard=None
)

def test_submission_save_sends_to_chahub(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.17 on 2024-03-28 13:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('competitions', '0046_merge_20240222_1916'),
]

operations = [
migrations.AddField(
model_name='competition',
name='can_participants_make_submissions_public',
field=models.BooleanField(default=True),
),
]
23 changes: 23 additions & 0 deletions src/apps/competitions/migrations/0048_auto_20240401_1646.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 2.2.17 on 2024-04-01 16:46

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('competitions', '0047_competition_can_participants_make_submissions_public'),
]

operations = [
migrations.AddField(
model_name='competition',
name='show_detailed_results_in_leaderboard',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='competition',
name='show_detailed_results_in_submission_panel',
field=models.BooleanField(default=True),
),
]
18 changes: 16 additions & 2 deletions src/apps/competitions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.urls import reverse
from django.utils.timezone import now

from celery_config import app
from celery_config import app, app_for_vhost
from chahub.models import ChaHubSaveMixin
from leaderboards.models import SubmissionScore
from profiles.models import User, Organization
Expand Down Expand Up @@ -49,6 +49,10 @@ class Competition(ChaHubSaveMixin, models.Model):
description = models.TextField(null=True, blank=True)
docker_image = models.CharField(max_length=128, default="codalab/codalab-legacy:py37")
enable_detailed_results = models.BooleanField(default=False)
# If true, show detailed results in submission panel
show_detailed_results_in_submission_panel = models.BooleanField(default=True)
# If true, show detailed results in leaderboard
show_detailed_results_in_leaderboard = models.BooleanField(default=True)
make_programs_available = models.BooleanField(default=False)
make_input_data_available = models.BooleanField(default=False)

Expand All @@ -69,6 +73,9 @@ class Competition(ChaHubSaveMixin, models.Model):
# if false, submissions run will be intiiated by organizer
auto_run_submissions = models.BooleanField(default=True)

# If true, participants see the make their submissions public
can_participants_make_submissions_public = models.BooleanField(default=True)

def __str__(self):
return f"competition-{self.title}-{self.pk}-{self.competition_type}"

Expand Down Expand Up @@ -121,10 +128,12 @@ def apply_phase_migration(self, current_phase, next_phase, force_migration=False
self.is_migrating = True
self.save()

# Get submissions of current phase with finished status and which are on leaderboard
submissions = Submission.objects.filter(
phase=current_phase,
is_migrated=False,
parent__isnull=True,
leaderboard__isnull=False,
status=Submission.FINISHED
)

Expand Down Expand Up @@ -644,7 +653,12 @@ def cancel(self, status=CANCELLED):
if self.has_children:
for sub in self.children.all():
sub.cancel(status=status)
app.control.revoke(self.celery_task_id, terminate=True)
celery_app = app
# If a custom queue is set, we need to fetch the appropriate celery app
if self.phase.competition.queue:
celery_app = app_for_vhost(str(self.phase.competition.queue.vhost))

celery_app.control.revoke(self.celery_task_id, terminate=True)
self.status = status
self.save()
return True
Expand Down
6 changes: 4 additions & 2 deletions src/apps/competitions/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
'computation_indexes',
'hidden',
]
MAX_EXECUTION_TIME_LIMIT = int(os.environ.get('MAX_EXECUTION_TIME_LIMIT', 600)) # time limit of the default queue


def _send_to_compute_worker(submission, is_scoring):
Expand All @@ -107,7 +108,7 @@ def _send_to_compute_worker(submission, is_scoring):
"submissions_api_url": settings.SUBMISSIONS_API_URL,
"secret": submission.secret,
"docker_image": submission.phase.competition.docker_image,
"execution_time_limit": submission.phase.execution_time_limit,
"execution_time_limit": min(MAX_EXECUTION_TIME_LIMIT, submission.phase.execution_time_limit),
"id": submission.pk,
"is_scoring": is_scoring,
}
Expand Down Expand Up @@ -185,8 +186,9 @@ def _send_to_compute_worker(submission, is_scoring):
time_padding = 60 * 20 # 20 minutes
time_limit = submission.phase.execution_time_limit + time_padding

if submission.phase.competition.queue:
if submission.phase.competition.queue: # if the competition is running on a custom queue, not the default queue
submission.queue_name = submission.phase.competition.queue.name or ''
run_args['execution_time_limit'] = submission.phase.execution_time_limit # use the competition time limit
submission.save()

# Send to special queue? Using `celery_app` var name here since we'd be overriding the imported `app`
Expand Down
4 changes: 3 additions & 1 deletion src/apps/competitions/tests/test_phase_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from competitions.models import Submission, Competition, Phase
from competitions.tasks import do_phase_migrations
from factories import UserFactory, CompetitionFactory, PhaseFactory, SubmissionFactory, CompetitionParticipantFactory, \
TaskFactory
TaskFactory, LeaderboardFactory

twenty_minutes_ago = now() - timedelta(hours=0, minutes=20)
twenty_five_minutes_ago = now() - timedelta(hours=0, minutes=25)
Expand All @@ -22,6 +22,7 @@ def setUp(self):
self.competition = CompetitionFactory(created_by=self.owner, title="Competition One")
self.competition_participant = CompetitionParticipantFactory(user=self.normal_user,
competition=self.competition)
self.leaderboard = LeaderboardFactory()
self.phase1 = PhaseFactory(
competition=self.competition,
auto_migrate_to_this_phase=False,
Expand Down Expand Up @@ -59,6 +60,7 @@ def make_submission(self, **kwargs):
kwargs.setdefault('participant', self.competition_participant)
kwargs.setdefault('phase', self.phase1)
kwargs.setdefault('status', Submission.FINISHED)
kwargs.setdefault('leaderboard', self.leaderboard)
sub = SubmissionFactory(**kwargs)
return sub

Expand Down
2 changes: 2 additions & 0 deletions src/apps/competitions/unpackers/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def __init__(self, *args, **kwargs):
"description": self.competition_yaml.get("description", ""),
"docker_image": docker_image,
"enable_detailed_results": self.competition_yaml.get('enable_detailed_results', False),
"show_detailed_results_in_submission_panel": self.competition_yaml.get('show_detailed_results_in_submission_panel', True),
"show_detailed_results_in_leaderboard": self.competition_yaml.get('show_detailed_results_in_leaderboard', True),
"auto_run_submissions": self.competition_yaml.get('auto_run_submissions', True),
"make_programs_available": self.competition_yaml.get('make_programs_available', False),
"make_input_data_available": self.competition_yaml.get('make_input_data_available', False),
Expand Down
3 changes: 3 additions & 0 deletions src/apps/competitions/unpackers/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ def __init__(self, *args, **kwargs):
"registration_auto_approve": self.competition_yaml.get('registration_auto_approve', False),
"docker_image": self.competition_yaml.get('docker_image', 'codalab/codalab-legacy:py37'),
"enable_detailed_results": self.competition_yaml.get('enable_detailed_results', False),
"show_detailed_results_in_submission_panel": self.competition_yaml.get('show_detailed_results_in_submission_panel', True),
"show_detailed_results_in_leaderboard": self.competition_yaml.get('show_detailed_results_in_leaderboard', True),
"auto_run_submissions": self.competition_yaml.get('auto_run_submissions', True),
"can_participants_make_submissions_public": self.competition_yaml.get('can_participants_make_submissions_public', True),
"make_programs_available": self.competition_yaml.get('make_programs_available', False),
"make_input_data_available": self.competition_yaml.get('make_input_data_available', False),
"description": self.competition_yaml.get("description", ""),
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions src/apps/oidc_configurations/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin
from .models import Auth_Organization

admin.site.register(Auth_Organization)

# Register your models here.
5 changes: 5 additions & 0 deletions src/apps/oidc_configurations/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class OidcConfigurationsConfig(AppConfig):
name = 'oidc_configurations'
29 changes: 29 additions & 0 deletions src/apps/oidc_configurations/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 2.2.17 on 2024-03-04 06:16

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Auth_Organization',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('client_id', models.CharField(max_length=255)),
('client_secret', models.CharField(max_length=255)),
('authorization_url', models.CharField(max_length=255)),
('token_url', models.CharField(max_length=255)),
('user_info_url', models.CharField(max_length=255)),
('redirect_url', models.CharField(max_length=255)),
('button_bg_color', models.CharField(default='#2C3E4C', max_length=20)),
('button_text_color', models.CharField(default='#FFFFFF', max_length=20)),
],
),
]
Empty file.
14 changes: 14 additions & 0 deletions src/apps/oidc_configurations/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# oidc_configurations/models.py
from django.db import models


class Auth_Organization(models.Model):
name = models.CharField(max_length=255)
client_id = models.CharField(max_length=255)
client_secret = models.CharField(max_length=255)
authorization_url = models.CharField(max_length=255)
token_url = models.CharField(max_length=255)
user_info_url = models.CharField(max_length=255)
redirect_url = models.CharField(max_length=255)
button_bg_color = models.CharField(max_length=20, default='#2C3E4C')
button_text_color = models.CharField(max_length=20, default='#FFFFFF')
10 changes: 10 additions & 0 deletions src/apps/oidc_configurations/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# oidc_configurations/urls.py
from django.urls import path
from .views import organization_oidc_login, oidc_complete

app_name = 'oidc_configurations'

urlpatterns = [
path('organization_oidc_login/', organization_oidc_login, name='organization_oidc_login'),
path('complete/<int:auth_organization_id>/', oidc_complete, name='oidc_complete'),
]
Loading