From bc0bb2e0d46f3e1feb2727cabb3a437c02e0f71d Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:00:16 -0400 Subject: [PATCH 01/20] Refactorize program service to program serializer --- gateway/api/serializers.py | 39 ++++++++++++ gateway/api/services.py | 87 +-------------------------- gateway/api/v1/services.py | 6 -- gateway/api/v1/views.py | 4 -- gateway/api/views.py | 49 +++++++++------ gateway/tests/api/test_v1_services.py | 38 +----------- 6 files changed, 71 insertions(+), 152 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 5bc1a9d3f..5d134dbc9 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -189,3 +189,42 @@ class RuntimeJobSerializer(serializers.ModelSerializer): class Meta: model = RuntimeJob + + +class RunProgramSerializer(serializers.ModelSerializer): + """ + Program serializer for the /run end-point + """ + + config = serializers.CharField() + + class Meta: + model = Program + + def retrieve_one_by_title(self, title, author): + """ + This method returns a Program entry if it finds an entry searching by the title, if not None + """ + return ( + Program.objects.filter(title=title, author=author) + .order_by("-created") + .first() + ) + + def create(self, validated_data): + title = validated_data.get("title") + logger.info("Creating program [%s] with UploadProgramSerializer", title) + return Program.objects.create(**validated_data) + + def update(self, instance, validated_data): + logger.info( + "Updating program [%s] with UploadProgramSerializer", instance.title + ) + instance.arguments = validated_data.get("arguments", "{}") + instance.entrypoint = validated_data.get("entrypoint") + instance.dependencies = validated_data.get("dependencies", "[]") + instance.env_vars = validated_data.get("env_vars", "{}") + instance.artifact = validated_data.get("artifact") + instance.author = validated_data.get("author") + instance.save() + return instance diff --git a/gateway/api/services.py b/gateway/api/services.py index 989f66c51..98bcf03da 100644 --- a/gateway/api/services.py +++ b/gateway/api/services.py @@ -14,97 +14,12 @@ import json from .models import Program, JobConfig, Job -from .exceptions import InternalServerErrorException, ResourceNotFoundException +from .exceptions import InternalServerErrorException from .utils import encrypt_env_vars, build_env_variables logger = logging.getLogger("gateway.services") -class ProgramService: - """ - Program service allocate the logic related with programs - """ - - @staticmethod - def save(serializer, author, artifact) -> Program: - """ - Save method gets a program serializer and creates or updates a program - - Args: - serializer: django program model serializer - author: user tipically got it from request - artifact: file that is going to be run - - Returns: - program: new Program instance - """ - - title = serializer.data.get("title") - existing_program = ( - Program.objects.filter(title=title, author=author) - .order_by("-created") - .first() - ) - - if existing_program is not None: - program = existing_program - program.arguments = serializer.data.get("arguments") - program.entrypoint = serializer.data.get("entrypoint") - program.dependencies = serializer.data.get("dependencies", "[]") - program.env_vars = serializer.data.get("env_vars", "{}") - logger.debug("Program [%s] will be updated by [%s]", title, author) - else: - program = Program(**serializer.data) - logger.debug("Program [%s] will be created by [%s]", title, author) - program.artifact = artifact - program.author = author - - # It would be nice if we could unify all the saves logic in one unique entry-point - try: - program.save() - except (Exception) as save_program_exception: - logger.error( - "Exception was caught saving the program [%s] by [%s] \n" - "Error trace: %s", - title, - author, - save_program_exception, - ) - raise InternalServerErrorException( - "Unexpected error saving the program" - ) from save_program_exception - - logger.debug("Program [%s] saved", title) - - return program - - @staticmethod - def find_one_by_title(title, author) -> Program: - """ - It returns the last created Program by title from an author - - Args: - title: program title - author: user tipically got it from request - - Returns: - program: Program instance found - """ - - logger.debug("Filtering Program by title[%s] and author [%s]", title, author) - program = ( - Program.objects.filter(title=title, author=author) - .order_by("-created") - .first() - ) - - if program is None: - logger.error("Program [%s] by author [%s] not found", title, author) - raise ResourceNotFoundException("Program [{title}] was not found") - - return program - - class JobConfigService: """ JobConfig service allocate the logic related with job configuration diff --git a/gateway/api/v1/services.py b/gateway/api/v1/services.py index 4b357e17c..e63b46f1c 100644 --- a/gateway/api/v1/services.py +++ b/gateway/api/v1/services.py @@ -7,12 +7,6 @@ from api import services -class ProgramService(services.ProgramService): - """ - Program service first version. - """ - - class JobConfigService(services.JobConfigService): """ JobConfig service first version. diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index 278c41a93..494787d6b 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -23,10 +23,6 @@ class ProgramViewSet(views.ProgramViewSet): # pylint: disable=too-many-ancestor serializer_class = v1_serializers.ProgramSerializer permission_classes = [permissions.IsAuthenticated] - @staticmethod - def get_service_program_class(): - return v1_services.ProgramService - @staticmethod def get_service_job_config_class(): return v1_services.JobConfigService diff --git a/gateway/api/views.py b/gateway/api/views.py index 2b0346181..9f8684d72 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -37,9 +37,10 @@ JobConfigSerializer, RunExistingJobSerializer, RunExistingProgramSerializer, + RunProgramSerializer, UploadProgramSerializer, ) -from .services import JobService, ProgramService, JobConfigService +from .services import JobService, JobConfigService logger = logging.getLogger("gateway") resource = Resource(attributes={SERVICE_NAME: "QuantumServerless-Gateway"}) @@ -64,14 +65,6 @@ class ProgramViewSet(viewsets.ModelViewSet): # pylint: disable=too-many-ancesto BASE_NAME = "programs" - @staticmethod - def get_service_program_class(): - """ - This method returns Program service to be used in Program ViewSet. - """ - - return ProgramService - @staticmethod def get_service_job_config_class(): """ @@ -128,6 +121,14 @@ def get_serializer_run_existing_job(*args, **kwargs): return RunExistingJobSerializer(*args, **kwargs) + @staticmethod + def get_serializer_run_program(*args, **kwargs): + """ + This method returns the program serializer for the run end-point + """ + + return RunProgramSerializer(*args, **kwargs) + def get_serializer_class(self): return self.serializer_class @@ -250,21 +251,29 @@ def run(self, request): tracer = trace.get_tracer("gateway.tracer") ctx = TraceContextTextMapPropagator().extract(carrier=request.headers) with tracer.start_as_current_span("gateway.program.run", context=ctx): - serializer = self.get_serializer(data=request.data) + serializer = self.get_serializer_run_program(data=request.data) if not serializer.is_valid(): + logger.error( + "RunProgramSerializer validation failed:\n %s", + serializer.errors, + ) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + title = serializer.validated_data.get("title") author = request.user - program = None - program_service = self.get_service_program_class() - try: - program = program_service.save( - serializer=serializer, - author=author, - artifact=request.FILES.get("artifact"), - ) - except InternalServerErrorException as exception: - return Response(exception, exception.http_code) + program = serializer.retrieve_one_by_title(title=title, author=author) + if program is not None: + logger.info("Program found. [%s] is going to be updated", title) + serializer = self.get_serializer_run_program(program, data=request.data) + if not serializer.is_valid(): + logger.error( + "RunProgramSerializer validation failed with program instance:\n %s", + serializer.errors, + ) + return Response( + serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + serializer.save(author=author) jobconfig = None config_data = request.data.get("config") diff --git a/gateway/tests/api/test_v1_services.py b/gateway/tests/api/test_v1_services.py index bd8544a8c..36cdf7f3e 100644 --- a/gateway/tests/api/test_v1_services.py +++ b/gateway/tests/api/test_v1_services.py @@ -3,8 +3,8 @@ from rest_framework.test import APITestCase from api.exceptions import ResourceNotFoundException -from api.v1.services import ProgramService, JobConfigService, JobService -from api.v1.serializers import ProgramSerializer, JobConfigSerializer +from api.v1.services import JobConfigService, JobService +from api.v1.serializers import JobConfigSerializer from api.models import Job, Program, JobConfig from django.contrib.auth.models import User @@ -14,40 +14,6 @@ class ServicesTest(APITestCase): fixtures = ["tests/fixtures/fixtures.json"] - def test_save_program(self): - """Test to verify that the service creates correctly an entry with its serializer.""" - - user = User.objects.get(id=1) - data = '{"title": "My Qiskit Pattern", "entrypoint": "pattern.py"}' - program_serializer = ProgramSerializer(data=json.loads(data)) - program_serializer.is_valid() - - program = ProgramService.save(program_serializer, user, "path") - entry = Program.objects.get(id=program.id) - - self.assertIsNotNone(entry) - self.assertEqual(program.title, entry.title) - - def test_find_one_program_by_title(self): - """The test must return one Program filtered by specific title.""" - - user = User.objects.get(id=1) - title = "Program" - - program = ProgramService.find_one_by_title(title, user) - - self.assertIsNotNone(program) - self.assertEqual(program.title, title) - - def test_fail_to_find_program_by_title(self): - """The test must raise a 404 exception when we don't find a Program with a specific title.""" - - user = User.objects.get(id=1) - title = "This Program doesn't exist" - - with self.assertRaises(ResourceNotFoundException): - ProgramService.find_one_by_title(title, user) - def test_create_job_config(self): """The test will create a job config with a basic configuration.""" From 2ebbb19f9fd6f55108305c08ef8104a58b52be65 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:51:06 -0400 Subject: [PATCH 02/20] Migrate job config service to the serializer --- gateway/api/services.py | 34 ------------------------ gateway/api/v1/services.py | 6 ----- gateway/api/v1/views.py | 4 --- gateway/api/views.py | 37 ++++++++++----------------- gateway/tests/api/test_v1_services.py | 17 +----------- 5 files changed, 14 insertions(+), 84 deletions(-) diff --git a/gateway/api/services.py b/gateway/api/services.py index 98bcf03da..1e93e43c5 100644 --- a/gateway/api/services.py +++ b/gateway/api/services.py @@ -20,40 +20,6 @@ logger = logging.getLogger("gateway.services") -class JobConfigService: - """ - JobConfig service allocate the logic related with job configuration - """ - - @staticmethod - def save_with_serializer(serializer) -> JobConfig: - """ - It returns a new JobConfig from its serializer - - Args: - serializer: JobConfig serializer from the model - - Returns: - JobConfig: new JobConfig instance - """ - - # It would be nice if we could unify all the saves logic in one unique entry-point - try: - jobconfig = serializer.save() - except (Exception) as save_job_config_exception: - logger.error( - "Exception was caught saving a JobConfig. \n Error trace: %s", - save_job_config_exception, - ) - raise InternalServerErrorException( - "Unexpected error saving the configuration of the job" - ) from save_job_config_exception - - logger.debug("JobConfig [%s] saved", jobconfig.id) - - return jobconfig - - class JobService: """ Job service allocate the logic related with a job diff --git a/gateway/api/v1/services.py b/gateway/api/v1/services.py index e63b46f1c..1876f8822 100644 --- a/gateway/api/v1/services.py +++ b/gateway/api/v1/services.py @@ -7,12 +7,6 @@ from api import services -class JobConfigService(services.JobConfigService): - """ - JobConfig service first version. - """ - - class JobService(services.JobService): """ Job service first version. diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index 494787d6b..2b3b522ee 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -23,10 +23,6 @@ class ProgramViewSet(views.ProgramViewSet): # pylint: disable=too-many-ancestor serializer_class = v1_serializers.ProgramSerializer permission_classes = [permissions.IsAuthenticated] - @staticmethod - def get_service_job_config_class(): - return v1_services.JobConfigService - @staticmethod def get_service_job_class(): return v1_services.JobService diff --git a/gateway/api/views.py b/gateway/api/views.py index 9f8684d72..7e11fe96e 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -40,7 +40,7 @@ RunProgramSerializer, UploadProgramSerializer, ) -from .services import JobService, JobConfigService +from .services import JobService logger = logging.getLogger("gateway") resource = Resource(attributes={SERVICE_NAME: "QuantumServerless-Gateway"}) @@ -65,14 +65,6 @@ class ProgramViewSet(viewsets.ModelViewSet): # pylint: disable=too-many-ancesto BASE_NAME = "programs" - @staticmethod - def get_service_job_config_class(): - """ - This method return JobConfig service to be used in Program ViewSet. - """ - - return JobConfigService - @staticmethod def get_service_job_class(): """ @@ -276,23 +268,20 @@ def run(self, request): serializer.save(author=author) jobconfig = None - config_data = request.data.get("config") - if config_data: - config_serializer = self.get_serializer_job_config( - data=json.loads(config_data) - ) - if not config_serializer.is_valid(): - return Response( - config_serializer.errors, status=status.HTTP_400_BAD_REQUEST + config_json = serializer.data.get("config") + if config_json: + logger.info("Configuration for [%s] was found.", title) + job_config_serializer = self.get_serializer_job_config(data=config_json) + if not job_config_serializer.is_valid(): + logger.error( + "JobConfigSerializer validation failed:\n %s", + serializer.errors, ) - try: - jobconfig = ( - self.get_service_job_config_class().save_with_serializer( - config_serializer - ) + return Response( + job_config_serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - except InternalServerErrorException as exception: - return Response(exception, exception.http_code) + jobconfig = job_config_serializer.save() + logger.info("JobConfig [%s] created.", jobconfig.id) job = None carrier = {} diff --git a/gateway/tests/api/test_v1_services.py b/gateway/tests/api/test_v1_services.py index 36cdf7f3e..38b50bb3f 100644 --- a/gateway/tests/api/test_v1_services.py +++ b/gateway/tests/api/test_v1_services.py @@ -1,9 +1,7 @@ import json -from unittest.mock import MagicMock from rest_framework.test import APITestCase -from api.exceptions import ResourceNotFoundException -from api.v1.services import JobConfigService, JobService +from api.v1.services import JobService from api.v1.serializers import JobConfigSerializer from api.models import Job, Program, JobConfig from django.contrib.auth.models import User @@ -14,19 +12,6 @@ class ServicesTest(APITestCase): fixtures = ["tests/fixtures/fixtures.json"] - def test_create_job_config(self): - """The test will create a job config with a basic configuration.""" - - data = "{}" - job_config_serializer = JobConfigSerializer(data=json.loads(data)) - job_config_serializer.is_valid() - - job_config = JobConfigService.save_with_serializer(job_config_serializer) - entry = JobConfig.objects.get(id=job_config.id) - - self.assertIsNotNone(job_config) - self.assertEqual(entry.id, job_config.id) - def test_create_job(self): """Creating a job with basic consfiguration.""" From fdf5b0156f971be5921c1103d1bd742bd174442c Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:27:48 -0400 Subject: [PATCH 03/20] Removed services and exceptions --- gateway/api/exceptions.py | 42 ------------------ gateway/api/serializers.py | 6 +-- gateway/api/services.py | 80 ----------------------------------- gateway/api/v1/serializers.py | 20 ++++++++- gateway/api/v1/services.py | 13 ------ gateway/api/v1/views.py | 11 +---- gateway/api/views.py | 60 +++++++++++--------------- gateway/main/settings.py | 5 --- 8 files changed, 46 insertions(+), 191 deletions(-) delete mode 100644 gateway/api/exceptions.py delete mode 100644 gateway/api/services.py delete mode 100644 gateway/api/v1/services.py diff --git a/gateway/api/exceptions.py b/gateway/api/exceptions.py deleted file mode 100644 index 706d1c8c6..000000000 --- a/gateway/api/exceptions.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Custom exceptions for the gateway application -""" - -from rest_framework import status - - -class GatewayException(Exception): - """ - Generic custom exception for our application - """ - - def __init__(self, message): - super().__init__(message) - - -class GatewayHttpException(GatewayException): - """ - Generic http custom exception for our application - """ - - def __init__(self, message, http_code): - super().__init__(message) - self.http_code = http_code - - -class InternalServerErrorException(GatewayHttpException): - """ - A wrapper for when we want to raise an internal server error - """ - - def __init__(self, message, http_code=status.HTTP_500_INTERNAL_SERVER_ERROR): - super().__init__(message, http_code) - - -class ResourceNotFoundException(GatewayHttpException): - """ - A wrapper for when we want to raise a 404 error - """ - - def __init__(self, message, http_code=status.HTTP_404_NOT_FOUND): - super().__init__(message, http_code) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 5d134dbc9..6db0bf8a5 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -143,9 +143,9 @@ def create(self, validated_data): pass -class RunExistingJobSerializer(serializers.ModelSerializer): +class RunAndRunExistingJobSerializer(serializers.ModelSerializer): """ - Job serializer for the /run_existing end-point + Job serializer for the /run and /run_existing end-point """ class Meta: @@ -196,7 +196,7 @@ class RunProgramSerializer(serializers.ModelSerializer): Program serializer for the /run end-point """ - config = serializers.CharField() + config = serializers.CharField(read_only=True) class Meta: model = Program diff --git a/gateway/api/services.py b/gateway/api/services.py deleted file mode 100644 index 1e93e43c5..000000000 --- a/gateway/api/services.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Services for api application: - - Program Service - - Job Service - -Version services inherit from the different services. -""" - -# pylint: disable=too-few-public-methods -# pylint: disable=duplicate-code -# Disable duplicate code due to refactorization. This file will be delited. - -import logging -import json - -from .models import Program, JobConfig, Job -from .exceptions import InternalServerErrorException -from .utils import encrypt_env_vars, build_env_variables - -logger = logging.getLogger("gateway.services") - - -class JobService: - """ - Job service allocate the logic related with a job - """ - - @staticmethod - def save( - program: Program, - arguments: str, - author, - jobconfig: JobConfig, - token: str, - carrier, - status=Job.QUEUED, - ) -> Job: - """ - Creates or updates a job - - Args: - program: instance of a Program - arguments: arguments from the serializer - author: author from the request - jobconfig: instance of a JobConfig - token: token from the request after being decoded - carrier: object injected from TraceContextTextMapPropagator - status: status of the job, QUEUED by default - - Returns: - Job instance - """ - - job = Job( - program=program, - arguments=arguments, - author=author, - status=status, - config=jobconfig, - ) - env = encrypt_env_vars(build_env_variables(token, job, arguments)) - try: - env["traceparent"] = carrier["traceparent"] - except KeyError: - pass - - try: - job.env_vars = json.dumps(env) - job.save() - except (Exception) as save_job_exception: - logger.error( - "Exception was caught saving the Job[%s]. \n Error trace: %s", - job.id, - save_job_exception, - ) - raise InternalServerErrorException( - "Unexpected error saving the environment variables of the job" - ) from save_job_exception - - return job diff --git a/gateway/api/v1/serializers.py b/gateway/api/v1/serializers.py index cb2f8b6ec..34fc8a37d 100644 --- a/gateway/api/v1/serializers.py +++ b/gateway/api/v1/serializers.py @@ -56,12 +56,12 @@ class Meta(serializers.JobConfigSerializer.Meta): ] -class RunExistingJobSerializer(serializers.RunExistingJobSerializer): +class RunExistingJobSerializer(serializers.RunAndRunExistingJobSerializer): """ RunExistingJobSerializer is used by the /run_existing end-point """ - class Meta(serializers.RunExistingJobSerializer.Meta): + class Meta(serializers.RunAndRunExistingJobSerializer.Meta): fields = ["id", "result", "status", "program", "created", "arguments"] @@ -85,3 +85,19 @@ class RuntimeJobSerializer(serializers.RuntimeJobSerializer): class Meta(serializers.RuntimeJobSerializer.Meta): fields = ["job", "runtime_job"] + + +class RunProgramSerializer(serializers.RunProgramSerializer): + """ + Run Programs serliazer is used in /run end-point + """ + + class Meta(serializers.RuntimeJobSerializer.Meta): + fields = [ + "title", + "entrypoint", + "artifact", + "dependencies", + "arguments", + "config", + ] diff --git a/gateway/api/v1/services.py b/gateway/api/v1/services.py deleted file mode 100644 index 1876f8822..000000000 --- a/gateway/api/v1/services.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Services api for V1. -""" - -# pylint: disable=too-few-public-methods - -from api import services - - -class JobService(services.JobService): - """ - Job service first version. - """ diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index 2b3b522ee..a5f81ab4e 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -11,7 +11,6 @@ from api.models import Program, Job, RuntimeJob from api.permissions import IsOwner from . import serializers as v1_serializers -from . import services as v1_services class ProgramViewSet(views.ProgramViewSet): # pylint: disable=too-many-ancestors @@ -23,14 +22,6 @@ class ProgramViewSet(views.ProgramViewSet): # pylint: disable=too-many-ancestor serializer_class = v1_serializers.ProgramSerializer permission_classes = [permissions.IsAuthenticated] - @staticmethod - def get_service_job_class(): - return v1_services.JobService - - @staticmethod - def get_serializer_job(*args, **kwargs): - return v1_serializers.JobSerializer(*args, **kwargs) - @staticmethod def get_serializer_job_config(*args, **kwargs): return v1_serializers.JobConfigSerializer(*args, **kwargs) @@ -44,7 +35,7 @@ def get_serializer_run_existing_program(*args, **kwargs): return v1_serializers.RunExistingProgramSerializer(*args, **kwargs) @staticmethod - def get_serializer_run_existing_job(*args, **kwargs): + def get_serializer_run_and_run_existing_job(*args, **kwargs): return v1_serializers.RunExistingJobSerializer(*args, **kwargs) def get_serializer_class(self): diff --git a/gateway/api/views.py b/gateway/api/views.py index 7e11fe96e..758b24972 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -29,18 +29,15 @@ from rest_framework.response import Response from utils import sanitize_file_path -from .exceptions import InternalServerErrorException from .models import Program, Job, RuntimeJob from .ray import get_job_handler from .serializers import ( - JobSerializer, JobConfigSerializer, - RunExistingJobSerializer, + RunAndRunExistingJobSerializer, RunExistingProgramSerializer, RunProgramSerializer, UploadProgramSerializer, ) -from .services import JobService logger = logging.getLogger("gateway") resource = Resource(attributes={SERVICE_NAME: "QuantumServerless-Gateway"}) @@ -65,22 +62,6 @@ class ProgramViewSet(viewsets.ModelViewSet): # pylint: disable=too-many-ancesto BASE_NAME = "programs" - @staticmethod - def get_service_job_class(): - """ - This method return Job service to be used in Program ViewSet. - """ - - return JobService - - @staticmethod - def get_serializer_job(*args, **kwargs): - """ - This method returns Job serializer to be used in Program ViewSet. - """ - - return JobSerializer(*args, **kwargs) - @staticmethod def get_serializer_job_config(*args, **kwargs): """ @@ -106,12 +87,12 @@ def get_serializer_run_existing_program(*args, **kwargs): return RunExistingProgramSerializer(*args, **kwargs) @staticmethod - def get_serializer_run_existing_job(*args, **kwargs): + def get_serializer_run_and_run_existing_job(*args, **kwargs): """ This method returns the job serializer for the run_existing end-point """ - return RunExistingJobSerializer(*args, **kwargs) + return RunAndRunExistingJobSerializer(*args, **kwargs) @staticmethod def get_serializer_run_program(*args, **kwargs): @@ -216,7 +197,7 @@ def run_existing(self, request): token = "" if request.auth: token = request.auth.token.decode() - job_serializer = self.get_serializer_run_existing_job(data={}) + job_serializer = self.get_serializer_run_and_run_existing_job(data={}) if not job_serializer.is_valid(): logger.error( "RunExistingJobSerializer validation failed:\n %s", @@ -283,24 +264,31 @@ def run(self, request): jobconfig = job_config_serializer.save() logger.info("JobConfig [%s] created.", jobconfig.id) - job = None carrier = {} TraceContextTextMapPropagator().inject(carrier) arguments = serializer.data.get("arguments") - token = request.auth.token.decode() - try: - job = self.get_service_job_class().save( - program=program, - arguments=arguments, - author=author, - jobconfig=jobconfig, - token=token, - carrier=carrier, + token = "" + if request.auth: + token = request.auth.token.decode() + job_serializer = self.get_serializer_run_and_run_existing_job(data={}) + if not job_serializer.is_valid(): + logger.error( + "JobSerializer validation failed:\n %s", + serializer.errors, ) - except InternalServerErrorException as exception: - return Response(exception, exception.http_code) + return Response( + job_serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + job = job_serializer.save( + arguments=arguments, + author=author, + carrier=carrier, + token=token, + program=program, + config=jobconfig, + ) + logger.info("Returning Job [%s] created.", job.id) - job_serializer = self.get_serializer_job(job) return Response(job_serializer.data) diff --git a/gateway/main/settings.py b/gateway/main/settings.py index dd18f8751..9c3b8a55c 100644 --- a/gateway/main/settings.py +++ b/gateway/main/settings.py @@ -126,11 +126,6 @@ "level": LOG_LEVEL, "propagate": False, }, - "gateway.services": { - "handlers": ["console"], - "level": LOG_LEVEL, - "propagate": False, - }, "gateway.serializers": { "handlers": ["console"], "level": LOG_LEVEL, From 657cd49352d555a8ce99c44c90442db6c44188f9 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:43:13 -0400 Subject: [PATCH 04/20] Updated tests with last changes --- gateway/tests/api/test_v1_serializers.py | 4 +-- gateway/tests/api/test_v1_services.py | 36 ------------------------ 2 files changed, 2 insertions(+), 38 deletions(-) delete mode 100644 gateway/tests/api/test_v1_services.py diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index a1c29a23b..452d8e651 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -156,13 +156,13 @@ def test_run_existing_program_serializer_config_json(self): self.assertEqual(type(assert_json), type(config)) self.assertDictEqual(assert_json, config) - def test_run_existing_job_serializer_check_empty_data(self): + def test_run_and_run_existing_job_serializer_check_empty_data(self): data = {} serializer = RunExistingJobSerializer(data=data) self.assertTrue(serializer.is_valid()) - def test_run_existing_job_serializer_creates_job(self): + def test_run_and_run_existing_job_serializer_creates_job(self): user = models.User.objects.get(username="test_user") program_instance = Program.objects.get( id="1a7947f9-6ae8-4e3d-ac1e-e7d608deec82" diff --git a/gateway/tests/api/test_v1_services.py b/gateway/tests/api/test_v1_services.py deleted file mode 100644 index 38b50bb3f..000000000 --- a/gateway/tests/api/test_v1_services.py +++ /dev/null @@ -1,36 +0,0 @@ -import json -from rest_framework.test import APITestCase - -from api.v1.services import JobService -from api.v1.serializers import JobConfigSerializer -from api.models import Job, Program, JobConfig -from django.contrib.auth.models import User - - -class ServicesTest(APITestCase): - """Tests for V1 services.""" - - fixtures = ["tests/fixtures/fixtures.json"] - - def test_create_job(self): - """Creating a job with basic consfiguration.""" - - user = User.objects.get(id=1) - program = Program.objects.get(pk="1a7947f9-6ae8-4e3d-ac1e-e7d608deec82") - arguments = "{}" - token = "42" - carrier = {} - jobconfig = None - - job = JobService.save( - program=program, - arguments=arguments, - author=user, - jobconfig=jobconfig, - token=token, - carrier=carrier, - ) - - self.assertIsNotNone(job) - self.assertEqual(Job.objects.count(), 4) - self.assertEqual(job.status, Job.QUEUED) From a0aaf5fa5fdd75047db0055a8263ebe767f33b76 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:56:37 -0400 Subject: [PATCH 05/20] Include v1 run program serializer --- gateway/api/v1/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index a5f81ab4e..8c9291ae9 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -38,6 +38,10 @@ def get_serializer_run_existing_program(*args, **kwargs): def get_serializer_run_and_run_existing_job(*args, **kwargs): return v1_serializers.RunExistingJobSerializer(*args, **kwargs) + @staticmethod + def get_serializer_run_program(*args, **kwargs): + return v1_serializers.RunProgramSerializer(*args, **kwargs) + def get_serializer_class(self): return v1_serializers.ProgramSerializer From 7a88db1ac6657602e4cb3bb58513d7922105a9e7 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:06:19 -0400 Subject: [PATCH 06/20] Fix a typo --- gateway/api/v1/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/api/v1/serializers.py b/gateway/api/v1/serializers.py index 34fc8a37d..2e7c9c007 100644 --- a/gateway/api/v1/serializers.py +++ b/gateway/api/v1/serializers.py @@ -92,7 +92,7 @@ class RunProgramSerializer(serializers.RunProgramSerializer): Run Programs serliazer is used in /run end-point """ - class Meta(serializers.RuntimeJobSerializer.Meta): + class Meta(serializers.RunProgramSerializer.Meta): fields = [ "title", "entrypoint", From 010cb46834ba30821e7383f2dc39c0099f320f12 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:42:00 -0400 Subject: [PATCH 07/20] Fix bug when the program was new --- gateway/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/api/views.py b/gateway/api/views.py index 758b24972..4c3b1bafb 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -246,7 +246,7 @@ def run(self, request): return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - serializer.save(author=author) + program = serializer.save(author=author) jobconfig = None config_json = serializer.data.get("config") From d8d8b7e12bbb0216d0b80d167fb842a7be9d51e9 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:43:07 -0400 Subject: [PATCH 08/20] Fix logs references --- gateway/api/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 6db0bf8a5..a616e38e8 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -213,12 +213,12 @@ def retrieve_one_by_title(self, title, author): def create(self, validated_data): title = validated_data.get("title") - logger.info("Creating program [%s] with UploadProgramSerializer", title) + logger.info("Creating program [%s] with RunProgramSerializer", title) return Program.objects.create(**validated_data) def update(self, instance, validated_data): logger.info( - "Updating program [%s] with UploadProgramSerializer", instance.title + "Updating program [%s] with RunProgramSerializer", instance.title ) instance.arguments = validated_data.get("arguments", "{}") instance.entrypoint = validated_data.get("entrypoint") From 165a7e0aa8bc6150f71ed488c6f65551905ebffd Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Mon, 15 Apr 2024 12:43:41 -0400 Subject: [PATCH 09/20] Added swagger to the views --- gateway/api/serializers.py | 4 +--- gateway/api/v1/serializers.py | 6 +++--- gateway/api/v1/views.py | 13 +++++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index a616e38e8..99cf2aaf1 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -217,9 +217,7 @@ def create(self, validated_data): return Program.objects.create(**validated_data) def update(self, instance, validated_data): - logger.info( - "Updating program [%s] with RunProgramSerializer", instance.title - ) + logger.info("Updating program [%s] with RunProgramSerializer", instance.title) instance.arguments = validated_data.get("arguments", "{}") instance.entrypoint = validated_data.get("entrypoint") instance.dependencies = validated_data.get("dependencies", "[]") diff --git a/gateway/api/v1/serializers.py b/gateway/api/v1/serializers.py index 2e7c9c007..5ee598bf6 100644 --- a/gateway/api/v1/serializers.py +++ b/gateway/api/v1/serializers.py @@ -56,9 +56,9 @@ class Meta(serializers.JobConfigSerializer.Meta): ] -class RunExistingJobSerializer(serializers.RunAndRunExistingJobSerializer): +class RunAndRunExistingJobSerializer(serializers.RunAndRunExistingJobSerializer): """ - RunExistingJobSerializer is used by the /run_existing end-point + RunAndRunExistingJobSerializer is used by the /run and /run_existing end-points """ class Meta(serializers.RunAndRunExistingJobSerializer.Meta): @@ -89,7 +89,7 @@ class Meta(serializers.RuntimeJobSerializer.Meta): class RunProgramSerializer(serializers.RunProgramSerializer): """ - Run Programs serliazer is used in /run end-point + RunProgram serializer is used in /run end-point """ class Meta(serializers.RunProgramSerializer.Meta): diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index 8c9291ae9..de2ba67da 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -36,7 +36,7 @@ def get_serializer_run_existing_program(*args, **kwargs): @staticmethod def get_serializer_run_and_run_existing_job(*args, **kwargs): - return v1_serializers.RunExistingJobSerializer(*args, **kwargs) + return v1_serializers.RunAndRunExistingJobSerializer(*args, **kwargs) @staticmethod def get_serializer_run_program(*args, **kwargs): @@ -57,12 +57,21 @@ def upload(self, request): @swagger_auto_schema( operation_description="Run an existing Qiskit Pattern", request_body=v1_serializers.RunExistingProgramSerializer, - responses={status.HTTP_200_OK: v1_serializers.RunExistingJobSerializer}, + responses={status.HTTP_200_OK: v1_serializers.RunAndRunExistingJobSerializer}, ) @action(methods=["POST"], detail=False) def run_existing(self, request): return super().run_existing(request) + @swagger_auto_schema( + operation_description="Run and upload a Qiskit Pattern", + request_body=v1_serializers.RunProgramSerializer, + responses={status.HTTP_200_OK: v1_serializers.RunAndRunExistingJobSerializer}, + ) + @action(methods=["POST"], detail=False) + def run(self, request): + return super().run(request) + class JobViewSet(views.JobViewSet): # pylint: disable=too-many-ancestors """ From 3fcc9bcf49bc6a2a578f330db9c204871fad9a68 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:58:51 -0400 Subject: [PATCH 10/20] Divide the request serializer in two serializers --- gateway/api/serializers.py | 35 +++++- gateway/api/v1/serializers.py | 8 +- gateway/api/v1/views.py | 4 + gateway/api/views.py | 40 ++++-- gateway/tests/api/test_v1_serializers.py | 154 ++++++++++++++++++++++- 5 files changed, 222 insertions(+), 19 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 99cf2aaf1..d03a606f5 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -191,15 +191,25 @@ class Meta: model = RuntimeJob -class RunProgramSerializer(serializers.ModelSerializer): +class RunProgramSerializer(serializers.Serializer): """ Program serializer for the /run end-point """ - config = serializers.CharField(read_only=True) + title = serializers.CharField(max_length=255, allow_blank=False) + entrypoint = serializers.CharField(max_length=255) + artifact = serializers.FileField(allow_empty_file=False, use_url=False) + dependencies = serializers.CharField(allow_blank=False) + arguments = serializers.CharField(allow_blank=False) + config = serializers.CharField(allow_blank=False) - class Meta: - model = Program + def to_representation(self, instance): + """ + Transforms string `config` to JSON + """ + representation = super().to_representation(instance) + representation["config"] = json.loads(representation["config"]) + return representation def retrieve_one_by_title(self, title, author): """ @@ -211,6 +221,23 @@ def retrieve_one_by_title(self, title, author): .first() ) + def create(self, validated_data): + pass + + def update(self, instance, validated_data): + pass + + +class RunProgramModelSerializer(serializers.ModelSerializer): + """ + Program model serializer for the /run end-point + """ + + config = serializers.CharField(read_only=True) + + class Meta: + model = Program + def create(self, validated_data): title = validated_data.get("title") logger.info("Creating program [%s] with RunProgramSerializer", title) diff --git a/gateway/api/v1/serializers.py b/gateway/api/v1/serializers.py index 5ee598bf6..7345d0456 100644 --- a/gateway/api/v1/serializers.py +++ b/gateway/api/v1/serializers.py @@ -92,7 +92,13 @@ class RunProgramSerializer(serializers.RunProgramSerializer): RunProgram serializer is used in /run end-point """ - class Meta(serializers.RunProgramSerializer.Meta): + +class RunProgramModelSerializer(serializers.RunProgramModelSerializer): + """ + RunProgram model serializer is used in /run end-point + """ + + class Meta(serializers.RunProgramModelSerializer.Meta): fields = [ "title", "entrypoint", diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index de2ba67da..ccf827d50 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -42,6 +42,10 @@ def get_serializer_run_and_run_existing_job(*args, **kwargs): def get_serializer_run_program(*args, **kwargs): return v1_serializers.RunProgramSerializer(*args, **kwargs) + @staticmethod + def get_model_serializer_run_program(*args, **kwargs): + return v1_serializers.RunProgramModelSerializer(*args, **kwargs) + def get_serializer_class(self): return v1_serializers.ProgramSerializer diff --git a/gateway/api/views.py b/gateway/api/views.py index 4c3b1bafb..9908e7cc3 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -35,6 +35,7 @@ JobConfigSerializer, RunAndRunExistingJobSerializer, RunExistingProgramSerializer, + RunProgramModelSerializer, RunProgramSerializer, UploadProgramSerializer, ) @@ -102,6 +103,14 @@ def get_serializer_run_program(*args, **kwargs): return RunProgramSerializer(*args, **kwargs) + @staticmethod + def get_model_serializer_run_program(*args, **kwargs): + """ + This method returns the program model serializer for the run end-point + """ + + return RunProgramModelSerializer(*args, **kwargs) + def get_serializer_class(self): return self.serializer_class @@ -235,18 +244,27 @@ def run(self, request): title = serializer.validated_data.get("title") author = request.user program = serializer.retrieve_one_by_title(title=title, author=author) - if program is not None: + program_data = serializer.data + program_data["artifact"] = request.data.get("artifact") + if program is None: + logger.info("Program not found. [%s] is going to be created", title) + program_serializer = self.get_model_serializer_run_program( + data=program_data + ) + else: logger.info("Program found. [%s] is going to be updated", title) - serializer = self.get_serializer_run_program(program, data=request.data) - if not serializer.is_valid(): - logger.error( - "RunProgramSerializer validation failed with program instance:\n %s", - serializer.errors, - ) - return Response( - serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) - program = serializer.save(author=author) + program_serializer = self.get_model_serializer_run_program( + program, data=program_data + ) + if not program_serializer.is_valid(): + logger.error( + "RunProgramModelSerializer validation failed with program instance:\n %s", + program_serializer.errors, + ) + return Response( + program_serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + program = program_serializer.save(author=author) jobconfig = None config_json = serializer.data.get("config") diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index 452d8e651..2033710fc 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -11,7 +11,9 @@ JobConfigSerializer, UploadProgramSerializer, RunExistingProgramSerializer, - RunExistingJobSerializer, + RunAndRunExistingJobSerializer, + RunProgramSerializer, + RunProgramModelSerializer, ) from api.models import JobConfig, Program @@ -159,7 +161,7 @@ def test_run_existing_program_serializer_config_json(self): def test_run_and_run_existing_job_serializer_check_empty_data(self): data = {} - serializer = RunExistingJobSerializer(data=data) + serializer = RunAndRunExistingJobSerializer(data=data) self.assertTrue(serializer.is_valid()) def test_run_and_run_existing_job_serializer_creates_job(self): @@ -169,7 +171,7 @@ def test_run_and_run_existing_job_serializer_creates_job(self): ) arguments = "{}" - job_serializer = RunExistingJobSerializer(data={}) + job_serializer = RunAndRunExistingJobSerializer(data={}) job_serializer.is_valid() job = job_serializer.save( arguments=arguments, @@ -180,3 +182,149 @@ def test_run_and_run_existing_job_serializer_creates_job(self): config=None, ) self.assertIsNotNone(job) + + def test_run_program_serializer_check_emtpy_data(self): + data = {} + + serializer = RunProgramSerializer(data=data) + self.assertFalse(serializer.is_valid()) + errors = serializer.errors + self.assertListEqual( + ["title", "entrypoint", "artifact", "dependencies", "arguments", "config"], + list(errors.keys()), + ) + + def test_run_program_serializer_fails_at_validation(self): + path_to_resource_artifact = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "resources", + "artifact.tar", + ) + file_data = File(open(path_to_resource_artifact, "rb")) + upload_file = SimpleUploadedFile( + "artifact.tar", file_data.read(), content_type="multipart/form-data" + ) + + data = { + "title": "Program", + "entrypoint": "pattern.py", + "dependencies": [], + "arguments": {}, + "config": {}, + } + data["artifact"] = upload_file + + serializer = RunProgramSerializer(data=data) + self.assertFalse(serializer.is_valid()) + errors = serializer.errors + self.assertListEqual( + ["dependencies", "arguments", "config"], list(errors.keys()) + ) + + def test_run_program_serializer_config_json(self): + path_to_resource_artifact = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "resources", + "artifact.tar", + ) + file_data = File(open(path_to_resource_artifact, "rb")) + upload_file = SimpleUploadedFile( + "artifact.tar", file_data.read(), content_type="multipart/form-data" + ) + + assert_json = { + "workers": None, + "min_workers": 1, + "max_workers": 5, + "auto_scaling": True, + } + + data = { + "title": "Program", + "entrypoint": "pattern.py", + "dependencies": "[]", + "arguments": "{}", + "config": json.dumps(assert_json), + } + data["artifact"] = upload_file + + serializer = RunProgramSerializer(data=data) + self.assertTrue(serializer.is_valid()) + + config = serializer.data.get("config") + self.assertEqual(type(assert_json), type(config)) + self.assertDictEqual(assert_json, config) + + def test_run_program_model_serializer_creates_program(self): + path_to_resource_artifact = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "resources", + "artifact.tar", + ) + file_data = File(open(path_to_resource_artifact, "rb")) + upload_file = SimpleUploadedFile( + "artifact.tar", file_data.read(), content_type="multipart/form-data" + ) + + user = models.User.objects.get(username="test_user") + + title = "Hello world" + entrypoint = "pattern.py" + arguments = "{}" + dependencies = "[]" + + data = {} + data["title"] = title + data["entrypoint"] = entrypoint + data["arguments"] = arguments + data["dependencies"] = dependencies + data["artifact"] = upload_file + + serializer = RunProgramModelSerializer(data=data) + self.assertTrue(serializer.is_valid()) + + program: Program = serializer.save(author=user) + self.assertEqual(title, program.title) + self.assertEqual(entrypoint, program.entrypoint) + self.assertEqual(arguments, program.arguments) + self.assertEqual(dependencies, program.dependencies) + + def test_run_program_model_serializer_check_empty_data(self): + data = {} + + serializer = RunProgramModelSerializer(data=data) + self.assertFalse(serializer.is_valid()) + errors = serializer.errors + self.assertListEqual(["title", "entrypoint", "artifact"], list(errors.keys())) + + def test_run_program_model_serializer_fails_at_validation(self): + path_to_resource_artifact = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "..", + "resources", + "artifact.tar", + ) + file_data = File(open(path_to_resource_artifact, "rb")) + upload_file = SimpleUploadedFile( + "artifact.tar", file_data.read(), content_type="multipart/form-data" + ) + + title = "Hello world" + entrypoint = "pattern.py" + arguments = {} + dependencies = [] + + data = {} + data["title"] = title + data["entrypoint"] = entrypoint + data["artifact"] = upload_file + data["arguments"] = arguments + data["dependencies"] = dependencies + + serializer = RunProgramModelSerializer(data=data) + self.assertFalse(serializer.is_valid()) + errors = serializer.errors + self.assertListEqual(["dependencies", "arguments"], list(errors.keys())) From 7489207058501fd999062f05ae893d48b988f707 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:04:18 -0400 Subject: [PATCH 11/20] Add comment --- gateway/api/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gateway/api/views.py b/gateway/api/views.py index 9908e7cc3..6262b5f0c 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -244,6 +244,7 @@ def run(self, request): title = serializer.validated_data.get("title") author = request.user program = serializer.retrieve_one_by_title(title=title, author=author) + # We need to add request artifact to maintain the reference that the serializer lost program_data = serializer.data program_data["artifact"] = request.data.get("artifact") if program is None: From 3935478850ab9300a705279dfaa84d0d53050c67 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:10:21 -0400 Subject: [PATCH 12/20] Removed arguments from program serializers --- gateway/api/serializers.py | 2 +- gateway/tests/api/test_v1_serializers.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index e257dfe21..4e2803919 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -232,6 +232,7 @@ class RunProgramModelSerializer(serializers.ModelSerializer): Program model serializer for the /run end-point """ + arguments = serializers.CharField(read_only=True) config = serializers.CharField(read_only=True) class Meta: @@ -244,7 +245,6 @@ def create(self, validated_data): def update(self, instance, validated_data): logger.info("Updating program [%s] with RunProgramSerializer", instance.title) - instance.arguments = validated_data.get("arguments", "{}") instance.entrypoint = validated_data.get("entrypoint") instance.dependencies = validated_data.get("dependencies", "[]") instance.env_vars = validated_data.get("env_vars", "{}") diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index b155deeb6..8125f0b0a 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -272,13 +272,11 @@ def test_run_program_model_serializer_creates_program(self): title = "Hello world" entrypoint = "pattern.py" - arguments = "{}" dependencies = "[]" data = {} data["title"] = title data["entrypoint"] = entrypoint - data["arguments"] = arguments data["dependencies"] = dependencies data["artifact"] = upload_file @@ -288,7 +286,6 @@ def test_run_program_model_serializer_creates_program(self): program: Program = serializer.save(author=user) self.assertEqual(title, program.title) self.assertEqual(entrypoint, program.entrypoint) - self.assertEqual(arguments, program.arguments) self.assertEqual(dependencies, program.dependencies) def test_run_program_model_serializer_check_empty_data(self): @@ -313,17 +310,15 @@ def test_run_program_model_serializer_fails_at_validation(self): title = "Hello world" entrypoint = "pattern.py" - arguments = {} dependencies = [] data = {} data["title"] = title data["entrypoint"] = entrypoint data["artifact"] = upload_file - data["arguments"] = arguments data["dependencies"] = dependencies serializer = RunProgramModelSerializer(data=data) self.assertFalse(serializer.is_valid()) errors = serializer.errors - self.assertListEqual(["dependencies", "arguments"], list(errors.keys())) + self.assertListEqual(["dependencies"], list(errors.keys())) From 81843781389b9940b9a4ef73838bbb83f61aaa63 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:21:44 -0400 Subject: [PATCH 13/20] Improve job serialization --- gateway/api/views.py | 24 ++++++------------ gateway/tests/api/test_v1_serializers.py | 31 ++++++++++++++---------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/gateway/api/views.py b/gateway/api/views.py index 6262b5f0c..df774990c 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -206,22 +206,18 @@ def run_existing(self, request): token = "" if request.auth: token = request.auth.token.decode() - job_serializer = self.get_serializer_run_and_run_existing_job(data={}) + job_data = {"arguments": arguments, "program": program.id} + job_serializer = self.get_serializer_run_and_run_existing_job(data=job_data) if not job_serializer.is_valid(): logger.error( - "RunExistingJobSerializer validation failed:\n %s", + "RunAndRunExistingJobSerializer validation failed:\n %s", serializer.errors, ) return Response( job_serializer.errors, status=status.HTTP_400_BAD_REQUEST ) job = job_serializer.save( - arguments=arguments, - author=author, - carrier=carrier, - token=token, - program=program, - config=jobconfig, + author=author, carrier=carrier, token=token, config=jobconfig ) logger.info("Returning Job [%s] created.", job.id) @@ -289,22 +285,18 @@ def run(self, request): token = "" if request.auth: token = request.auth.token.decode() - job_serializer = self.get_serializer_run_and_run_existing_job(data={}) + job_data = {"arguments": arguments, "program": program.id} + job_serializer = self.get_serializer_run_and_run_existing_job(data=job_data) if not job_serializer.is_valid(): logger.error( - "JobSerializer validation failed:\n %s", + "RunAndRunExistingJobSerializer validation failed:\n %s", serializer.errors, ) return Response( job_serializer.errors, status=status.HTTP_400_BAD_REQUEST ) job = job_serializer.save( - arguments=arguments, - author=author, - carrier=carrier, - token=token, - program=program, - config=jobconfig, + author=author, carrier=carrier, token=token, config=jobconfig ) logger.info("Returning Job [%s] created.", job.id) diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index 8125f0b0a..51268f664 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -157,12 +157,6 @@ def test_run_existing_program_serializer_config_json(self): self.assertEqual(type(assert_json), type(config)) self.assertDictEqual(assert_json, config) - def test_run_and_run_existing_job_serializer_check_empty_data(self): - data = {} - - serializer = RunAndRunExistingJobSerializer(data=data) - self.assertTrue(serializer.is_valid()) - def test_run_and_run_existing_job_serializer_creates_job(self): user = models.User.objects.get(username="test_user") program_instance = Program.objects.get( @@ -170,17 +164,28 @@ def test_run_and_run_existing_job_serializer_creates_job(self): ) arguments = "{}" - job_serializer = RunAndRunExistingJobSerializer(data={}) + config_data = { + "workers": None, + "min_workers": 1, + "max_workers": 5, + "auto_scaling": True, + } + config_serializer = JobConfigSerializer(data=config_data) + config_serializer.is_valid() + jobconfig = config_serializer.save() + + job_data = {"arguments": arguments, "program": program_instance.id} + job_serializer = RunAndRunExistingJobSerializer(data=job_data) job_serializer.is_valid() job = job_serializer.save( - arguments=arguments, - author=user, - carrier={}, - token="my_token", - program=program_instance, - config=None, + author=user, carrier={}, token="my_token", config=jobconfig ) + self.assertIsNotNone(job) + self.assertIsNotNone(job.program) + self.assertIsNotNone(job.arguments) + self.assertIsNotNone(job.config) + self.assertIsNotNone(job.author) def test_run_program_serializer_check_emtpy_data(self): data = {} From 0b141ce54aca3c1df8c71c4672adb910d68a3c55 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:20:54 -0400 Subject: [PATCH 14/20] Fix lint --- gateway/tests/api/test_v1_serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index 914795965..c755cb201 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -181,7 +181,7 @@ def test_run_and_run_existing_job_serializer_creates_job(self): author=user, carrier={}, token="my_token", config=jobconfig ) env_vars = json.loads(job.env_vars) - + self.assertIsNotNone(job) self.assertIsNotNone(job.program) self.assertIsNotNone(job.arguments) From d2cc05f5168a8acdcfa59190d1378a8287709141 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:30:51 -0400 Subject: [PATCH 15/20] Fix new config implementation --- gateway/api/serializers.py | 2 +- gateway/tests/api/test_v1_serializers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 6c03a25dc..75eb5e64b 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -199,7 +199,7 @@ class RunProgramSerializer(serializers.Serializer): artifact = serializers.FileField(allow_empty_file=False, use_url=False) dependencies = serializers.CharField(allow_blank=False) arguments = serializers.CharField(allow_blank=False) - config = serializers.CharField(allow_blank=False) + config = serializers.JSONField() def retrieve_one_by_title(self, title, author): """ diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index c755cb201..9519e052a 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -226,7 +226,7 @@ def test_run_program_serializer_fails_at_validation(self): self.assertFalse(serializer.is_valid()) errors = serializer.errors self.assertListEqual( - ["dependencies", "arguments", "config"], list(errors.keys()) + ["dependencies", "arguments"], list(errors.keys()) ) def test_run_program_serializer_config_json(self): @@ -253,7 +253,7 @@ def test_run_program_serializer_config_json(self): "entrypoint": "pattern.py", "dependencies": "[]", "arguments": "{}", - "config": json.dumps(assert_json), + "config": assert_json, } data["artifact"] = upload_file From 1ce5eb6b5170430a040e86bf295c24065421840b Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:32:17 -0400 Subject: [PATCH 16/20] Fix lint in tests --- gateway/tests/api/test_v1_serializers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index 9519e052a..8299ffdb0 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -225,9 +225,7 @@ def test_run_program_serializer_fails_at_validation(self): serializer = RunProgramSerializer(data=data) self.assertFalse(serializer.is_valid()) errors = serializer.errors - self.assertListEqual( - ["dependencies", "arguments"], list(errors.keys()) - ) + self.assertListEqual(["dependencies", "arguments"], list(errors.keys())) def test_run_program_serializer_config_json(self): path_to_resource_artifact = os.path.join( From 20c52d99f2a8cac3f95a5d578a22c940bbadfbda Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Wed, 17 Apr 2024 15:44:46 -0400 Subject: [PATCH 17/20] Fix JSON problem in QueryDict --- client/quantum_serverless/core/job.py | 6 +++--- gateway/api/serializers.py | 10 +++++++++- gateway/tests/api/test_v1_serializers.py | 6 ++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/client/quantum_serverless/core/job.py b/client/quantum_serverless/core/job.py index 9592a66bd..56f78d7cf 100644 --- a/client/quantum_serverless/core/job.py +++ b/client/quantum_serverless/core/job.py @@ -427,11 +427,11 @@ def run( # pylint: disable=too-many-locals "arguments": json.dumps(arguments or {}, cls=QiskitObjectsEncoder), "dependencies": json.dumps(program.dependencies or []), "env_var": json.dumps(program.env_vars or {}), - } # type: Dict[str, Any] + } if config: - data["config"] = asdict(config) + data["config"] = json.dumps(asdict(config)) else: - data["config"] = {} + data["config"] = "{}" response_data = safe_json_request( request=lambda: requests.post( diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 75eb5e64b..5ff781845 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -199,7 +199,15 @@ class RunProgramSerializer(serializers.Serializer): artifact = serializers.FileField(allow_empty_file=False, use_url=False) dependencies = serializers.CharField(allow_blank=False) arguments = serializers.CharField(allow_blank=False) - config = serializers.JSONField() + config = serializers.CharField(allow_blank=False) + + def to_representation(self, instance): + """ + Transforms string `config` to JSON + """ + representation = super().to_representation(instance) + representation["config"] = json.loads(representation["config"]) + return representation def retrieve_one_by_title(self, title, author): """ diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index 8299ffdb0..c755cb201 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -225,7 +225,9 @@ def test_run_program_serializer_fails_at_validation(self): serializer = RunProgramSerializer(data=data) self.assertFalse(serializer.is_valid()) errors = serializer.errors - self.assertListEqual(["dependencies", "arguments"], list(errors.keys())) + self.assertListEqual( + ["dependencies", "arguments", "config"], list(errors.keys()) + ) def test_run_program_serializer_config_json(self): path_to_resource_artifact = os.path.join( @@ -251,7 +253,7 @@ def test_run_program_serializer_config_json(self): "entrypoint": "pattern.py", "dependencies": "[]", "arguments": "{}", - "config": assert_json, + "config": json.dumps(assert_json), } data["artifact"] = upload_file From add2adca959483540838e43ab40c462361f30c9c Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:16:21 -0400 Subject: [PATCH 18/20] Fix typo in env_vars client --- client/quantum_serverless/core/job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/quantum_serverless/core/job.py b/client/quantum_serverless/core/job.py index 56f78d7cf..5b6509376 100644 --- a/client/quantum_serverless/core/job.py +++ b/client/quantum_serverless/core/job.py @@ -426,7 +426,7 @@ def run( # pylint: disable=too-many-locals "entrypoint": program.entrypoint, "arguments": json.dumps(arguments or {}, cls=QiskitObjectsEncoder), "dependencies": json.dumps(program.dependencies or []), - "env_var": json.dumps(program.env_vars or {}), + "env_vars": json.dumps(program.env_vars or {}), } if config: data["config"] = json.dumps(asdict(config)) From 28684c2a23ccfc4d145ee0d11bc39cc4a184a0fa Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:16:48 -0400 Subject: [PATCH 19/20] Add env_vars to run and improved run tests --- gateway/api/serializers.py | 1 + gateway/tests/api/test_v1_program.py | 59 ++++++++++++++++++++++-- gateway/tests/api/test_v1_serializers.py | 14 +++++- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 5ff781845..395312b28 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -199,6 +199,7 @@ class RunProgramSerializer(serializers.Serializer): artifact = serializers.FileField(allow_empty_file=False, use_url=False) dependencies = serializers.CharField(allow_blank=False) arguments = serializers.CharField(allow_blank=False) + env_vars = serializers.CharField(allow_blank=False) config = serializers.CharField(allow_blank=False) def to_representation(self, instance): diff --git a/gateway/tests/api/test_v1_program.py b/gateway/tests/api/test_v1_program.py index e8cf6f44a..0bd29f385 100644 --- a/gateway/tests/api/test_v1_program.py +++ b/gateway/tests/api/test_v1_program.py @@ -1,10 +1,15 @@ """Tests program APIs.""" + +import json +import os + +from django.contrib.auth import models +from django.core.files.base import ContentFile from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase -from api.models import Job, JobConfig -import json -from django.contrib.auth import models + +from api.models import Job class TestProgramApi(APITestCase): @@ -54,12 +59,14 @@ def test_run_existing(self): user = models.User.objects.get(username="test_user") self.client.force_authenticate(user=user) + + arguments = json.dumps({"MY_ARGUMENT_KEY": "MY_ARGUMENT_VALUE"}) programs_response = self.client.post( "/api/v1/programs/run_existing/", data={ "title": "Program", "entrypoint": "program.py", - "arguments": "{}", + "arguments": arguments, "dependencies": "[]", "config": { "workers": None, @@ -72,6 +79,50 @@ def test_run_existing(self): ) job_id = programs_response.data.get("id") job = Job.objects.get(id=job_id) + self.assertEqual(job.status, Job.QUEUED) + self.assertEqual(job.arguments, arguments) + self.assertEqual(job.program.dependencies, "[]") + self.assertEqual(job.config.min_workers, 1) + self.assertEqual(job.config.max_workers, 5) + self.assertEqual(job.config.workers, None) + self.assertEqual(job.config.auto_scaling, True) + + def test_run(self): + """Tests run authorized.""" + + fake_file = ContentFile(b"print('Hello World')") + fake_file.name = "test_run.tar" + + user = models.User.objects.get(username="test_user") + self.client.force_authenticate(user=user) + + arguments = json.dumps({"MY_ARGUMENT_KEY": "MY_ARGUMENT_VALUE"}) + env_vars = json.dumps({"MY_ENV_VAR_KEY": "MY_ENV_VAR_VALUE"}) + programs_response = self.client.post( + "/api/v1/programs/run/", + data={ + "title": "Program", + "entrypoint": "program.py", + "arguments": arguments, + "dependencies": "[]", + "env_vars": env_vars, + "config": json.dumps( + { + "workers": None, + "min_workers": 1, + "max_workers": 5, + "auto_scaling": True, + } + ), + "artifact": fake_file, + }, + ) + job_id = programs_response.data.get("id") + job = Job.objects.get(id=job_id) + self.assertEqual(job.status, Job.QUEUED) + self.assertEqual(job.arguments, arguments) + self.assertEqual(job.program.dependencies, "[]") + self.assertEqual(job.program.env_vars, env_vars) self.assertEqual(job.config.min_workers, 1) self.assertEqual(job.config.max_workers, 5) self.assertEqual(job.config.workers, None) diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index c755cb201..7c354e612 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -197,7 +197,15 @@ def test_run_program_serializer_check_emtpy_data(self): self.assertFalse(serializer.is_valid()) errors = serializer.errors self.assertListEqual( - ["title", "entrypoint", "artifact", "dependencies", "arguments", "config"], + [ + "title", + "entrypoint", + "artifact", + "dependencies", + "arguments", + "env_vars", + "config", + ], list(errors.keys()), ) @@ -218,6 +226,7 @@ def test_run_program_serializer_fails_at_validation(self): "entrypoint": "pattern.py", "dependencies": [], "arguments": {}, + "env_vars": {}, "config": {}, } data["artifact"] = upload_file @@ -226,7 +235,7 @@ def test_run_program_serializer_fails_at_validation(self): self.assertFalse(serializer.is_valid()) errors = serializer.errors self.assertListEqual( - ["dependencies", "arguments", "config"], list(errors.keys()) + ["dependencies", "arguments", "env_vars", "config"], list(errors.keys()) ) def test_run_program_serializer_config_json(self): @@ -253,6 +262,7 @@ def test_run_program_serializer_config_json(self): "entrypoint": "pattern.py", "dependencies": "[]", "arguments": "{}", + "env_vars": "{}", "config": json.dumps(assert_json), } data["artifact"] = upload_file From f824b4ca34fa4d379f6220fc7e8922f6b4c40026 Mon Sep 17 00:00:00 2001 From: David <9059044+Tansito@users.noreply.github.com> Date: Thu, 18 Apr 2024 11:56:54 -0400 Subject: [PATCH 20/20] Run job serializer name simplified --- gateway/api/serializers.py | 2 +- gateway/api/v1/serializers.py | 6 +++--- gateway/api/v1/views.py | 8 ++++---- gateway/api/views.py | 14 +++++++------- gateway/tests/api/test_v1_serializers.py | 6 +++--- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/gateway/api/serializers.py b/gateway/api/serializers.py index 395312b28..083d1e66f 100644 --- a/gateway/api/serializers.py +++ b/gateway/api/serializers.py @@ -138,7 +138,7 @@ def create(self, validated_data): pass -class RunAndRunExistingJobSerializer(serializers.ModelSerializer): +class RunJobSerializer(serializers.ModelSerializer): """ Job serializer for the /run and /run_existing end-point """ diff --git a/gateway/api/v1/serializers.py b/gateway/api/v1/serializers.py index 8d81ad570..8c0761237 100644 --- a/gateway/api/v1/serializers.py +++ b/gateway/api/v1/serializers.py @@ -55,12 +55,12 @@ class Meta(serializers.JobConfigSerializer.Meta): ] -class RunAndRunExistingJobSerializer(serializers.RunAndRunExistingJobSerializer): +class RunJobSerializer(serializers.RunJobSerializer): """ - RunAndRunExistingJobSerializer is used by the /run and /run_existing end-points + RunJobSerializer is used by the /run and /run_existing end-points """ - class Meta(serializers.RunAndRunExistingJobSerializer.Meta): + class Meta(serializers.RunJobSerializer.Meta): fields = ["id", "result", "status", "program", "created", "arguments"] diff --git a/gateway/api/v1/views.py b/gateway/api/v1/views.py index ccf827d50..82c42cf86 100644 --- a/gateway/api/v1/views.py +++ b/gateway/api/v1/views.py @@ -35,8 +35,8 @@ def get_serializer_run_existing_program(*args, **kwargs): return v1_serializers.RunExistingProgramSerializer(*args, **kwargs) @staticmethod - def get_serializer_run_and_run_existing_job(*args, **kwargs): - return v1_serializers.RunAndRunExistingJobSerializer(*args, **kwargs) + def get_serializer_run_job(*args, **kwargs): + return v1_serializers.RunJobSerializer(*args, **kwargs) @staticmethod def get_serializer_run_program(*args, **kwargs): @@ -61,7 +61,7 @@ def upload(self, request): @swagger_auto_schema( operation_description="Run an existing Qiskit Pattern", request_body=v1_serializers.RunExistingProgramSerializer, - responses={status.HTTP_200_OK: v1_serializers.RunAndRunExistingJobSerializer}, + responses={status.HTTP_200_OK: v1_serializers.RunJobSerializer}, ) @action(methods=["POST"], detail=False) def run_existing(self, request): @@ -70,7 +70,7 @@ def run_existing(self, request): @swagger_auto_schema( operation_description="Run and upload a Qiskit Pattern", request_body=v1_serializers.RunProgramSerializer, - responses={status.HTTP_200_OK: v1_serializers.RunAndRunExistingJobSerializer}, + responses={status.HTTP_200_OK: v1_serializers.RunJobSerializer}, ) @action(methods=["POST"], detail=False) def run(self, request): diff --git a/gateway/api/views.py b/gateway/api/views.py index df774990c..b6decb1ec 100644 --- a/gateway/api/views.py +++ b/gateway/api/views.py @@ -33,7 +33,7 @@ from .ray import get_job_handler from .serializers import ( JobConfigSerializer, - RunAndRunExistingJobSerializer, + RunJobSerializer, RunExistingProgramSerializer, RunProgramModelSerializer, RunProgramSerializer, @@ -88,12 +88,12 @@ def get_serializer_run_existing_program(*args, **kwargs): return RunExistingProgramSerializer(*args, **kwargs) @staticmethod - def get_serializer_run_and_run_existing_job(*args, **kwargs): + def get_serializer_run_job(*args, **kwargs): """ This method returns the job serializer for the run_existing end-point """ - return RunAndRunExistingJobSerializer(*args, **kwargs) + return RunJobSerializer(*args, **kwargs) @staticmethod def get_serializer_run_program(*args, **kwargs): @@ -207,10 +207,10 @@ def run_existing(self, request): if request.auth: token = request.auth.token.decode() job_data = {"arguments": arguments, "program": program.id} - job_serializer = self.get_serializer_run_and_run_existing_job(data=job_data) + job_serializer = self.get_serializer_run_job(data=job_data) if not job_serializer.is_valid(): logger.error( - "RunAndRunExistingJobSerializer validation failed:\n %s", + "RunJobSerializer validation failed:\n %s", serializer.errors, ) return Response( @@ -286,10 +286,10 @@ def run(self, request): if request.auth: token = request.auth.token.decode() job_data = {"arguments": arguments, "program": program.id} - job_serializer = self.get_serializer_run_and_run_existing_job(data=job_data) + job_serializer = self.get_serializer_run_job(data=job_data) if not job_serializer.is_valid(): logger.error( - "RunAndRunExistingJobSerializer validation failed:\n %s", + "RunJobSerializer validation failed:\n %s", serializer.errors, ) return Response( diff --git a/gateway/tests/api/test_v1_serializers.py b/gateway/tests/api/test_v1_serializers.py index 7c354e612..370e8244e 100644 --- a/gateway/tests/api/test_v1_serializers.py +++ b/gateway/tests/api/test_v1_serializers.py @@ -11,7 +11,7 @@ JobConfigSerializer, UploadProgramSerializer, RunExistingProgramSerializer, - RunAndRunExistingJobSerializer, + RunJobSerializer, RunProgramSerializer, RunProgramModelSerializer, ) @@ -157,7 +157,7 @@ def test_run_existing_program_serializer_config_json(self): self.assertEqual(type(assert_json), type(config)) self.assertDictEqual(assert_json, config) - def test_run_and_run_existing_job_serializer_creates_job(self): + def test_run_job_serializer_creates_job(self): user = models.User.objects.get(username="test_user") program_instance = Program.objects.get( id="1a7947f9-6ae8-4e3d-ac1e-e7d608deec82" @@ -175,7 +175,7 @@ def test_run_and_run_existing_job_serializer_creates_job(self): jobconfig = config_serializer.save() job_data = {"arguments": arguments, "program": program_instance.id} - job_serializer = RunAndRunExistingJobSerializer(data=job_data) + job_serializer = RunJobSerializer(data=job_data) job_serializer.is_valid() job = job_serializer.save( author=user, carrier={}, token="my_token", config=jobconfig