Commit 4a6c100b authored by Jose Borreguero's avatar Jose Borreguero
Browse files

switch from username to custodian/executor


Signed-off-by: default avatarJose Borreguero <borreguero@gmail.com>
parent c6c2fbdb
Pipeline #196793 failed with stages
in 6 minutes and 11 seconds
...@@ -25,7 +25,6 @@ import ast ...@@ -25,7 +25,6 @@ import ast
import uuid import uuid
from django.db import models from django.db import models
from django.contrib.auth import get_user_model
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError, ObjectDoesNotExist, MultipleObjectsReturned from django.core.exceptions import ValidationError, ObjectDoesNotExist, MultipleObjectsReturned
...@@ -36,6 +35,7 @@ from model_utils.models import TimeStampedModel ...@@ -36,6 +35,7 @@ from model_utils.models import TimeStampedModel
# standard imports # standard imports
from pathlib import Path from pathlib import Path
from typing import Optional
# Thanks http://stackoverflow.com/a/7394475 # Thanks http://stackoverflow.com/a/7394475
...@@ -466,33 +466,34 @@ class Result(TimeStampedModel): ...@@ -466,33 +466,34 @@ class Result(TimeStampedModel):
return '{self.remote_filename} <{self.job}>'.format(self=self) return '{self.remote_filename} <{self.job}>'.format(self=self)
class IdentityFileModelError(Exception):
pass
class IdentityFileModelManager(models.Manager): class IdentityFileModelManager(models.Manager):
class MultipleIdentityFileError(MultipleObjectsReturned): class MultipleIdentityFileError(MultipleObjectsReturned):
pass pass
def create_from_username(self, username: str) -> "IdentityFile": def create_from_custodian(self, custodian: str, executor: Optional[str] = None) -> "IdentityFile":
r"""Instantiate an identity file object and save in the database by passing only a user name. r"""Instantiate an identity file object and save in the database by passing only a user name.
Avoids creation when a record for `username` is found in the database. In such case, returns the found record. Avoids creation when a record for `username` is found in the database. In such case, returns the found record.
:param username: login user name :param custodian: login user name, or Django session. Will be associated to a private/public SSH key pair.
:param executor: user name in charge of opening an SSH tunnel between the app and worker servers.
:raise MultipleObjectsReturned: when more than one identity-file is found in the database :raise MultipleObjectsReturned: when more than one identity-file is found in the database
:return: instance of model IdentityFileModel :return: instance of model IdentityFileModel
""" """
recipient = get_user_model().objects.get(username=username) if not executor:
custodian = str(username) executor = custodian
try: try:
idf_record = self.get(recipient=custodian) idf_record = self.get(custodian=custodian)
except ObjectDoesNotExist: except ObjectDoesNotExist:
id_file = IdentityFile(persistent=True) id_file = IdentityFile(persistent=True)
idf_record = super().create( idf_record = super().create(
recipient=custodian, private=str(id_file.private), public=str(id_file.public), custodian=custodian, executor=executor
private=str(id_file.private),
public=str(id_file.public),
custodian=custodian,
executor=custodian,
) )
except MultipleObjectsReturned: except MultipleObjectsReturned:
# this should not happen # this should not happen
raise self.MultipleIdentityFileError(f"More than one identity file stored for {recipient}") raise self.MultipleIdentityFileError(f"More than one identity file stored for {custodian}")
return idf_record return idf_record
...@@ -517,19 +518,13 @@ class IdentityFileModel(TimeStampedModel): ...@@ -517,19 +518,13 @@ class IdentityFileModel(TimeStampedModel):
executor can store more than one public key, meaning one executor can execute jobs executor can store more than one public key, meaning one executor can execute jobs
for more than one custodian. for more than one custodian.
record = IdentityFile.objects.create_from_username(username) record = IdentityFile.objects.create_from_custodian(username)
""" """
recipient = models.OneToOneField(
settings.AUTH_USER_MODEL,
models.PROTECT,
related_name="idfile",
verbose_name=_("User"),
help_text=_("The user receiving the public file"),
)
private = models.CharField( private = models.CharField(
_("private key file"), help_text=_("The path to the private SSH key file"), max_length=250 _("private key file"), help_text=_("The path to the private SSH key file"), max_length=250
) )
public = models.CharField(_("public key file"), help_text=_("The path to the public SSH key file"), max_length=250) public = models.CharField(_("public key file"), help_text=_("The path to the public SSH key file"), max_length=250)
custodian = models.CharField( custodian = models.CharField(
...@@ -547,11 +542,12 @@ class IdentityFileModel(TimeStampedModel): ...@@ -547,11 +542,12 @@ class IdentityFileModel(TimeStampedModel):
def __str__(self): def __str__(self):
r"""Convert model to string, e.g. ``"zzz IDF"``""" r"""Convert model to string, e.g. ``"zzz IDF"``"""
return f"{self.recipient} IDF" return f"{self.custodian} IDF"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
r"""Override the save method to prevent updating the record, if extant""" if IdentityFileModel.objects.filter(custodian=self.custodian):
kwargs["force_insert"] = True raise IdentityFileModelError(f"An IdentityFile record for {self.custodian} already exists in the database")
kwargs["force_insert"] = True # prevent updating the record, if extante
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
......
...@@ -8,12 +8,19 @@ test_django-remote-submission ...@@ -8,12 +8,19 @@ test_django-remote-submission
Tests for `django-remote-submission` models module. Tests for `django-remote-submission` models module.
""" """
# package imports # package imports
from django_remote_submission.models import ListField, IdentityFileModel, Interpreter, Log, Job, Result, Server from django_remote_submission.models import (
ListField,
IdentityFileModel,
IdentityFileModelError,
Interpreter,
Log,
Job,
Result,
Server,
)
from django_remote_submission.wrapper.remote import IdentityFile from django_remote_submission.wrapper.remote import IdentityFile
# third party imports # third party imports
from django.core.exceptions import ObjectDoesNotExist
from django.db.utils import IntegrityError
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
import pytest import pytest
...@@ -125,70 +132,79 @@ class TestListField: ...@@ -125,70 +132,79 @@ class TestListField:
class TestIdentityFileModel: class TestIdentityFileModel:
r''' r"""
@pytest.mark.django_db @pytest.mark.django_db
def test_create(self, user): def test_create(self, user):
with pytest.raises(ObjectDoesNotExist) as e: with pytest.raises(ObjectDoesNotExist) as e:
record = IdentityFileModel.objects.create_by_username("non-existing-user") record = IdentityFileModel.objects.create_by_custodian("non-existing-user")
''' """
@pytest.mark.django_db @pytest.mark.django_db
def test_instantiate(self, user): def test_instantiate(self, user):
id_file = IdentityFile(sshdir="/tmp") id_file = IdentityFile(sshdir="/tmp")
record = IdentityFileModel(recipient=user, private=str(id_file.private), public=str(id_file.public)) username = user.username
assert record.recipient == user record = IdentityFileModel(
private=str(id_file.private), public=str(id_file.public), custodian=username, executor=username
)
assert os.path.exists(record.private) assert os.path.exists(record.private)
assert os.path.exists(record.public) assert os.path.exists(record.public)
assert record.custodian == username
assert record.executor == username
@pytest.mark.django_db @pytest.mark.django_db
def test_create(self, user): def test_create(self, user):
id_file = IdentityFile(sshdir="/tmp") id_file = IdentityFile(sshdir="/tmp")
record = IdentityFileModel.objects.create(recipient=user, username = user.username
private=str(id_file.private), record = IdentityFileModel.objects.create(
public=str(id_file.public)) private=str(id_file.private), public=str(id_file.public), custodian=username, executor=username
assert record.recipient == user )
assert os.path.exists(record.private) assert os.path.exists(record.private)
assert os.path.exists(record.public) assert os.path.exists(record.public)
assert record.custodian == username
assert record.executor == username
record.delete() record.delete()
assert os.path.exists(record.private) is False assert os.path.exists(record.private) is False
assert os.path.exists(record.public) is False assert os.path.exists(record.public) is False
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_create_from_username(self, user): def test_create_from_custodian(self, user):
# error if user does not exist username = user.username
with pytest.raises(ObjectDoesNotExist) as e:
IdentityFileModel.objects.create_from_username("non-existing-user")
assert str(e.value) == "User matching query does not exist."
# create an IdentityFileModel record for `user` # create an IdentityFileModel record for `user`
record = IdentityFileModel.objects.create_from_username(user.username) record_1 = IdentityFileModel.objects.create_from_custodian(username)
assert record.recipient == user assert record_1.custodian == username
assert Path(record.private).parent == Path.home() / ".ssh" assert Path(record_1.private).parent == Path.home() / ".ssh"
[os.remove(file) for file in [record_1.private, record_1.public]]
# return the newly created IdentityFileModel record, do not instantiate another one # return the newly created IdentityFileModel record, do not instantiate another one
record_2 = IdentityFileModel.objects.create_from_username(user.username) record_2 = IdentityFileModel.objects.create_from_custodian(username)
assert record_2.id == record.id assert record_2.id == record_1.id
# we screw up if we try to create & store two records for the same user # we screw up if we try to save in the database a record that already exists
id_file = IdentityFile(sshdir="/tmp") id_file = IdentityFile(sshdir="/tmp")
with pytest.raises(IntegrityError) as e: record = IdentityFileModel(
IdentityFileModel.objects.create(recipient=user, private=str(id_file.private), public=str(id_file.public)) private=str(id_file.private), public=str(id_file.public), custodian=username, executor=username
assert str(e.value) == "UNIQUE constraint failed: django_remote_submission_identityfilemodel.recipient_id" )
with pytest.raises(IdentityFileModelError) as e:
record.save()
assert str(e.value) == f"An IdentityFile record for {username} already exists in the database"
[os.remove(file) for file in [id_file.private, id_file.public]] [os.remove(file) for file in [id_file.private, id_file.public]]
# same as before, but using method IdentityFileModel.save() # same as before, but using Model.objects.create(), which calls Model.save()
id_file = IdentityFile(sshdir="/tmp") id_file = IdentityFile(sshdir="/tmp")
record = IdentityFileModel(recipient=user, private=str(id_file.private), public=str(id_file.public)) with pytest.raises(IdentityFileModelError) as e:
with pytest.raises(IntegrityError) as e: IdentityFileModel.objects.create(
record.save() private=str(id_file.private), public=str(id_file.public), custodian=username, executor=username
assert str(e.value) == "UNIQUE constraint failed: django_remote_submission_identityfilemodel.recipient_id" )
assert str(e.value) == f"An IdentityFile record for {username} already exists in the database"
[os.remove(file) for file in [id_file.private, id_file.public]] [os.remove(file) for file in [id_file.private, id_file.public]]
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_delete(self, user): def test_delete(self, user):
r"""delete the record, along with the SSH key files""" r"""delete the record, along with the SSH key files"""
username = user.username username = user.username
record = IdentityFileModel.objects.create_from_username(user.username) record = IdentityFileModel.objects.create_from_custodian(user.username)
record.delete() record.delete()
assert os.path.exists(record.private) is False assert os.path.exists(record.private) is False
assert os.path.exists(record.public) is False assert os.path.exists(record.public) is False
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment