#4 change some model fields to proper uuid type; add api to claim and release tasks
This commit is contained in:
		
							parent
							
								
									ca65c4eaa7
								
							
						
					
					
						commit
						5feca3ff62
					
				| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
from django.contrib import admin
 | 
			
		||||
from .models import Identity, Task, Claim
 | 
			
		||||
from .models import Identity, Task
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IdentityAdmin(admin.ModelAdmin):
 | 
			
		||||
| 
						 | 
				
			
			@ -7,13 +7,8 @@ class IdentityAdmin(admin.ModelAdmin):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class TaskAdmin(admin.ModelAdmin):
 | 
			
		||||
    fields = ['stream', 'type', 'configuration']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClaimAdmin(admin.ModelAdmin):
 | 
			
		||||
    fields = ['owner', 'task']
 | 
			
		||||
    fields = ['stream', 'type', 'configuration', 'claimed_by']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
admin.site.register(Identity, IdentityAdmin)
 | 
			
		||||
admin.site.register(Task, TaskAdmin)
 | 
			
		||||
admin.site.register(Claim, ClaimAdmin)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-26 18:34
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('concierge', '0001_initial'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='claim',
 | 
			
		||||
            name='id',
 | 
			
		||||
            field=models.CharField(default=uuid.uuid4, max_length=36, primary_key=True, serialize=False, unique=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-26 18:35
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('concierge', '0002_auto_20200426_1834'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='identity',
 | 
			
		||||
            name='heartbeat',
 | 
			
		||||
            field=models.DateTimeField(blank=True, null=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='identity',
 | 
			
		||||
            name='notes',
 | 
			
		||||
            field=models.TextField(blank=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-26 19:12
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('concierge', '0003_auto_20200426_1835'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='task',
 | 
			
		||||
            name='claimed_by',
 | 
			
		||||
            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='concierge.Identity'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name='task',
 | 
			
		||||
            name='uuid',
 | 
			
		||||
            field=models.UUIDField(default=uuid.uuid4, serialize=False, unique=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.DeleteModel(
 | 
			
		||||
            name='Claim',
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-26 20:07
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('concierge', '0004_auto_20200426_1912'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='identity',
 | 
			
		||||
            name='identity',
 | 
			
		||||
            field=models.UUIDField(default=uuid.uuid4, unique=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='task',
 | 
			
		||||
            name='claimed_by',
 | 
			
		||||
            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='concierge.Identity'),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='task',
 | 
			
		||||
            name='uuid',
 | 
			
		||||
            field=models.UUIDField(default=uuid.uuid4, unique=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -6,23 +6,20 @@ from rtmp.models import Stream
 | 
			
		|||
class Identity(models.Model):
 | 
			
		||||
    # models a concierge identity. every running concierge needs to have a
 | 
			
		||||
    # unique identity that is being used for task claims, etc.
 | 
			
		||||
    identity = models.CharField(max_length=36, unique=True, default=uuid.uuid4)
 | 
			
		||||
    identity = models.UUIDField(unique=True, default=uuid.uuid4)
 | 
			
		||||
    name = models.CharField(max_length=100)
 | 
			
		||||
    notes = models.TextField()
 | 
			
		||||
    notes = models.TextField(blank=True)
 | 
			
		||||
 | 
			
		||||
    # heartbeat indicates last point in time that this identity was seen.
 | 
			
		||||
    # some cronjob should scan the heartbeats and release all claims by
 | 
			
		||||
    # identities that have not been seen in a while. this interval should
 | 
			
		||||
    # be quite short so that the tasks can be claimed by other identities asap.
 | 
			
		||||
    heartbeat = models.DateTimeField(blank=True)
 | 
			
		||||
    heartbeat = models.DateTimeField(blank=True, null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Task(models.Model):
 | 
			
		||||
    uuid = models.UUIDField(unique=True, default=uuid.uuid4)
 | 
			
		||||
    stream = models.ForeignKey(Stream, on_delete=models.CASCADE)
 | 
			
		||||
    type = models.CharField(max_length=100)
 | 
			
		||||
    configuration = models.TextField()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Claim(models.Model):
 | 
			
		||||
    owner = models.ForeignKey(Identity, on_delete=models.CASCADE)
 | 
			
		||||
    task = models.ForeignKey(Task, on_delete=models.CASCADE)
 | 
			
		||||
    claimed_by = models.ForeignKey(Identity, null=True, blank=True, on_delete=models.CASCADE)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
from django.urls import path
 | 
			
		||||
from . import views
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('api/<uuid:identity>/heartbeat', views.heartbeat, name='heartbeat'),
 | 
			
		||||
    path('api/<uuid:identity>/claim/<uuid:task_uuid>', views.claim, name='claim'),
 | 
			
		||||
    path('api/<uuid:identity>/release/<uuid:task_uuid>', views.release, name='release'),
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,95 @@
 | 
			
		|||
from django.shortcuts import render  # noqa
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
# Create your views here.
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.http import JsonResponse
 | 
			
		||||
from django.views.decorators.csrf import csrf_exempt
 | 
			
		||||
from django.views.decorators.http import require_POST
 | 
			
		||||
from django.core.exceptions import ObjectDoesNotExist
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
 | 
			
		||||
from .models import Identity, Task
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@csrf_exempt
 | 
			
		||||
@require_POST
 | 
			
		||||
def heartbeat(request, identity):
 | 
			
		||||
    try:
 | 
			
		||||
        id = Identity.objects.get(identity=identity)
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        return JsonResponse({'error': 'identity unknown'}, status=403)
 | 
			
		||||
 | 
			
		||||
    # update heartbeat
 | 
			
		||||
    id.heartbeat = now()
 | 
			
		||||
    id.save()
 | 
			
		||||
 | 
			
		||||
    # get current claims and available tasks
 | 
			
		||||
    claims = Task.objects.filter(claimed_by=id).all()
 | 
			
		||||
    available = Task.objects.filter(claimed_by=None).all()
 | 
			
		||||
 | 
			
		||||
    data = {
 | 
			
		||||
        'success': True,
 | 
			
		||||
        'claims': [{'uuid': str(o.uuid)} for o in list(claims)],
 | 
			
		||||
        'available': [{'uuid': str(o.uuid), 'type': o.type} for o in list(available)],
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return JsonResponse(data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@csrf_exempt
 | 
			
		||||
@require_POST
 | 
			
		||||
def claim(request, identity, task_uuid):
 | 
			
		||||
    try:
 | 
			
		||||
        id = Identity.objects.get(identity=identity)
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        return JsonResponse({'error': 'identity unknown'}, status=403)
 | 
			
		||||
 | 
			
		||||
    with transaction.atomic():
 | 
			
		||||
        try:
 | 
			
		||||
            task = Task.objects.get(uuid=task_uuid)
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            return JsonResponse({'error': 'task unknown'}, status=404)
 | 
			
		||||
 | 
			
		||||
        if task.claimed_by:
 | 
			
		||||
            return JsonResponse({'error': 'task already claimed'}, status=423)
 | 
			
		||||
 | 
			
		||||
        task.claimed_by = id
 | 
			
		||||
        task.save()
 | 
			
		||||
 | 
			
		||||
        data = {
 | 
			
		||||
            'success': True,
 | 
			
		||||
            'uuid': task.uuid,
 | 
			
		||||
            'type': task.type,
 | 
			
		||||
            'configuration': json.loads(task.configuration)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return JsonResponse(data)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@csrf_exempt
 | 
			
		||||
@require_POST
 | 
			
		||||
def release(request, identity, task_uuid):
 | 
			
		||||
    try:
 | 
			
		||||
        id = Identity.objects.get(identity=identity)
 | 
			
		||||
    except ObjectDoesNotExist:
 | 
			
		||||
        return JsonResponse({'error': 'identity unknown'}, status=403)
 | 
			
		||||
 | 
			
		||||
    with transaction.atomic():
 | 
			
		||||
        try:
 | 
			
		||||
            task = Task.objects.get(uuid=task_uuid)
 | 
			
		||||
        except ObjectDoesNotExist:
 | 
			
		||||
            return JsonResponse({'error': 'task unknown'}, status=404)
 | 
			
		||||
 | 
			
		||||
        if task.claimed_by != id:
 | 
			
		||||
            return JsonResponse({'error': 'task claimed by other identity'}, status=403)
 | 
			
		||||
 | 
			
		||||
        task.claimed_by = None
 | 
			
		||||
        task.save()
 | 
			
		||||
 | 
			
		||||
        data = {
 | 
			
		||||
            'success': True,
 | 
			
		||||
            'uuid': task.uuid,
 | 
			
		||||
            'type': task.type,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return JsonResponse(data)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,5 +19,6 @@ from django.urls import include, path
 | 
			
		|||
urlpatterns = [
 | 
			
		||||
    path('admin/', admin.site.urls),
 | 
			
		||||
    path('rtmp/', include('rtmp.urls')),
 | 
			
		||||
    path('concierge/', include('concierge.urls')),
 | 
			
		||||
    path('', include('portal.urls')),
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,12 +3,21 @@ from rtmp.signals import stream_active
 | 
			
		|||
from .models import RestreamConfig
 | 
			
		||||
from rtmp.models import Stream
 | 
			
		||||
from concierge.models import Task
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@receiver(stream_active)
 | 
			
		||||
def create_tasks(sender, **kwargs):
 | 
			
		||||
    stream = Stream.objects.get(stream=kwargs['stream'])
 | 
			
		||||
    configs = RestreamConfig.objects.filter(stream=stream)
 | 
			
		||||
    for config in configs:
 | 
			
		||||
        task = Task(stream=stream, type='restream', configuration='{}')
 | 
			
		||||
    instances = RestreamConfig.objects.filter(active=True, stream=stream)
 | 
			
		||||
    for inst in instances:
 | 
			
		||||
        config = {
 | 
			
		||||
            'name': inst.name,
 | 
			
		||||
            'app': inst.stream.application.name,
 | 
			
		||||
            'stream': str(inst.stream.stream),
 | 
			
		||||
            'target': inst.target
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        json_config = json.dumps(config)
 | 
			
		||||
        task = Task(stream=stream, type='restream', configuration=json_config)
 | 
			
		||||
        task.save()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
# Generated by Django 3.0.5 on 2020-04-26 18:34
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('rtmp', '0002_stream_publish_counter'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='stream',
 | 
			
		||||
            name='stream',
 | 
			
		||||
            field=models.UUIDField(default=uuid.uuid4, unique=True),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ class Application(models.Model):
 | 
			
		|||
 | 
			
		||||
class Stream(models.Model):
 | 
			
		||||
    application = models.ForeignKey(Application, on_delete=models.CASCADE)
 | 
			
		||||
    stream = models.CharField(max_length=64, unique=True, default=uuid.uuid4)
 | 
			
		||||
    stream = models.UUIDField(unique=True, default=uuid.uuid4)
 | 
			
		||||
    name = models.CharField(max_length=100)
 | 
			
		||||
 | 
			
		||||
    # the same stream uuid can be published multiple times to different origin
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ class Stream(models.Model):
 | 
			
		|||
        # is now being considered active
 | 
			
		||||
        if self.publish_counter < 1:
 | 
			
		||||
            signals.stream_active.send(sender=self.__class__,
 | 
			
		||||
                                       stream=self.stream,
 | 
			
		||||
                                       stream=str(self.stream),
 | 
			
		||||
                                       param=param
 | 
			
		||||
                                       )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +45,7 @@ class Stream(models.Model):
 | 
			
		|||
        # considered inactive
 | 
			
		||||
        if self.publish_counter < 1:
 | 
			
		||||
            signals.stream_inactive.send(sender=self.__class__,
 | 
			
		||||
                                         stream=self.stream,
 | 
			
		||||
                                         stream=str(self.stream),
 | 
			
		||||
                                         param=param
 | 
			
		||||
                                         )
 | 
			
		||||
        self.save()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue