async def test_list_jobs_return_ordered_by_name(self, dev_with_infra_fixture, dev_another_job_fixture): await _load_jobs_into_chronos(dev_another_job_fixture, dev_with_infra_fixture) account = Account(**ACCOUNT_DEV_DICT) resp = await self.client.get( "/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) self.assertEqual(HTTPStatus.OK, resp.status) expected_asgard_jobs = [ ChronosScheduledJobConverter.to_asgard_model( ChronosJob( **dev_another_job_fixture)).remove_namespace(account), ChronosScheduledJobConverter.to_asgard_model( ChronosJob( **dev_with_infra_fixture)).remove_namespace(account), ] resp_data = await resp.json() self.assertEqual(expected_asgard_jobs[0], resp_data["jobs"][0]) self.assertEqual(expected_asgard_jobs[1], resp_data["jobs"][1])
async def test_to_asgard_model_required_fields(self, chronos_job_fixture): del chronos_job_fixture["environmentVariables"] del chronos_job_fixture["constraints"] del chronos_job_fixture["fetch"] asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**chronos_job_fixture)) asgard_job_converted = ChronosScheduledJobConverter.to_asgard_model( ChronosScheduledJobConverter.to_client_model(asgard_job)) self.assertEqual(asgard_job_converted.dict(), asgard_job.dict())
async def test_to_client_model_required_fields(self, chronos_job_fixture): asgard_job_dict = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**chronos_job_fixture)).dict() del asgard_job_dict["env"] del asgard_job_dict["fetch"] del asgard_job_dict["constraints"] chronos_job = ChronosScheduledJobConverter.to_client_model( ScheduledJob(**asgard_job_dict)) chronos_converted = ChronosScheduledJobConverter.to_client_model( ChronosScheduledJobConverter.to_asgard_model(chronos_job)) self.assertEqual(chronos_converted.dict(), chronos_job.dict())
async def test_delete_job_job_exist(self, dev_job_fixture): await _load_jobs_into_chronos(dev_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_job_fixture)).remove_namespace(self.account) resp = await self.client.delete( f"/jobs/{asgard_job.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) self.assertEqual(HTTPStatus.OK, resp.status) resp_data = await resp.json() self.assertEqual( ScheduledJobResource(job=asgard_job).dict(), resp_data) resp = await self.client.get( f"/jobs/{asgard_job.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) self.assertEqual(HTTPStatus.NOT_FOUND, resp.status)
async def test_update_job_job_exist(self, dev_job_fixture): """ Conferimos que um job é atualizado corretamente """ await _load_jobs_into_chronos(dev_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_job_fixture)) asgard_job.remove_namespace(self.account) self.assertEqual(asgard_job.cpus, dev_job_fixture["cpus"]) self.assertEqual(asgard_job.mem, dev_job_fixture["mem"]) asgard_job.cpus = 2 asgard_job.mem = 2048 resp = await self.client.put( f"/jobs/{asgard_job.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, json=asgard_job.dict(), ) self.assertEqual(HTTPStatus.ACCEPTED, resp.status) updated_job_response = await self.client.get( f"/jobs/{asgard_job.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) updated_job_data = await updated_job_response.json() updated_job_resource = CreateScheduledJobResource(**updated_job_data) self.assertEqual(asgard_job.cpus, updated_job_resource.job.cpus) self.assertEqual(asgard_job.mem, updated_job_resource.job.mem)
async def test_create_job_validation_error(self, infra_job_fixture): """ Validamos que retornamos HTTPStatus.UNPROCESSABLE_ENTITY caso a entrada esteja incompleta """ account = Account(**ACCOUNT_DEV_DICT) asgard_job_no_namespace = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**infra_job_fixture)).remove_namespace(account) incomplete_asgard_job = asgard_job_no_namespace.dict() del incomplete_asgard_job["container"] resp = await self.client.post( "/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, json=incomplete_asgard_job, ) self.assertEqual(HTTPStatus.UNPROCESSABLE_ENTITY, resp.status) resp_data = await resp.json() expected_error_msg = """1 validation error for ScheduledJob\ncontainer\n field required (type=value_error.missing)""" self.assertEqual( ErrorResource(errors=[ErrorDetail(msg=expected_error_msg)]).dict(), resp_data, )
async def test_create_job_name_has_namespace_from_another_account( self, infra_job_fixture): await _cleanup_chronos() account = Account(**ACCOUNT_DEV_DICT) asgard_job_no_namespace = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**infra_job_fixture)).remove_namespace(account) resp = await self.client.post( "/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, json=asgard_job_no_namespace.dict(), ) self.assertEqual(HTTPStatus.CREATED, resp.status) resp_data = await resp.json() self.assertEqual( f"{asgard_job_no_namespace.id}", CreateScheduledJobResource(**resp_data).job.id, ) await asyncio.sleep(0.3) resp_created_job = await self.client.get( f"/jobs/{asgard_job_no_namespace.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) self.assertEqual(HTTPStatus.OK, resp_created_job.status)
async def test_create_job_on_alternate_account(self, dev_job_fixture): """ Confirmar que podemos fazer POST /jobs?account_id=<id> o o job será criado com o namespace da account de id = <id> """ await _cleanup_chronos() resp = await self.client.post( "/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, params={"account_id": ACCOUNT_INFRA_ID}, json=ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_job_fixture)).dict(), ) self.assertEqual(HTTPStatus.CREATED, resp.status) resp_data = await resp.json() self.assertEqual( f"{dev_job_fixture['name']}", CreateScheduledJobResource(**resp_data).job.id, ) resp_created_job = await self.client.get( f"/jobs/{dev_job_fixture['name']}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, params={"account_id": ACCOUNT_INFRA_ID}, ) self.assertEqual(HTTPStatus.OK, resp_created_job.status)
async def test_list_jobs_do_not_include_jobs_from_alternate_account( self, dev_job_fixture, infra_job_fixture): """ Valida o parametro ?account_id= """ await _load_jobs_into_chronos(dev_job_fixture, infra_job_fixture) account = Account(**ACCOUNT_INFRA_DICT) resp = await self.client.get( "/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, params={"account_id": ACCOUNT_INFRA_ID}, ) self.assertEqual(HTTPStatus.OK, resp.status) expected_asgard_jobs = [ ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**infra_job_fixture)).remove_namespace(account) ] resp_data = await resp.json() self.assertEqual( ScheduledJobsListResource(jobs=expected_asgard_jobs).dict(), resp_data, )
async def test_jobs_get_by_id_job_exist(self, chronos_job_fixture): chronos_job_fixture["name"] = f"{self.account.namespace}-my-job" async with http_client as client: await client.post( f"{settings.SCHEDULED_JOBS_SERVICE_ADDRESS}/v1/scheduler/iso8601", json=chronos_job_fixture, ) # Para dar tempo do chronos registra e responder no request log abaixo await asyncio.sleep(1) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**chronos_job_fixture)) # A busca deve ser feita sempre *sem* o namespace asgard_job.remove_namespace(self.account) resp = await self.client.get( f"/jobs/{asgard_job.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) self.assertEqual(HTTPStatus.OK, resp.status) resp_data = await resp.json() self.assertEqual( ScheduledJobResource(job=asgard_job).dict(), resp_data)
async def test_convert_to_asgard_model_enabled_field( self, chronos_job_fixture): """ O Campo orignal no chronos é "disabled". Como nosso campo é "enabled", os valores devem ser invertidos no momento da conversão dos modelos """ chronos_job = ChronosJob(**chronos_job_fixture) asgard_scheduled_job = ChronosScheduledJobConverter.to_asgard_model( chronos_job) self.assertTrue(asgard_scheduled_job.enabled) chronos_job.disabled = True asgard_scheduled_job = ChronosScheduledJobConverter.to_asgard_model( chronos_job) self.assertFalse(asgard_scheduled_job.enabled)
async def test_update_job_add_default_fetch_uri(self): del self.chronos_dev_job_fixture["fetch"] new_fetch_uri = FetchURLSpec( uri="https://static.server.com/assets/main.css") expected_fetch_list = [ new_fetch_uri, FetchURLSpec( uri=settings.SCHEDULED_JOBS_DEFAULT_FETCH_URIS[0].uri), FetchURLSpec( uri=settings.SCHEDULED_JOBS_DEFAULT_FETCH_URIS[1].uri), ] await _load_jobs_into_chronos(self.chronos_dev_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**self.chronos_dev_job_fixture)) asgard_job.add_fetch_uri(new_fetch_uri) asgard_job.remove_namespace(self.account) await self.backend.update_job(asgard_job, self.user, self.account) stored_job = await self.backend.get_job_by_id(asgard_job.id, self.user, self.account) self.assertEqual(expected_fetch_list, stored_job.fetch)
async def test_delete_job_job_does_not_exist(self): await _cleanup_chronos() job_not_found = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**self.chronos_dev_job_fixture)) job_not_found.id = "this-job-does-not-exist" with self.assertRaises(NotFoundEntity): await self.backend.delete_job(job_not_found, self.user, self.account)
async def test_convert_to_asgard_model_constraints_field( self, chronos_job_fixture): chronos_job = ChronosJob(**chronos_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model(chronos_job) self.assertEqual( ["hostname:LIKE:10.0.0.1", "workload:LIKE:general"], asgard_job.constraints, )
async def test_to_client_model_disabled_field(self, chronos_job_fixture): asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**chronos_job_fixture)) self.assertFalse( ChronosScheduledJobConverter.to_client_model(asgard_job).disabled) asgard_job.enabled = False self.assertTrue( ChronosScheduledJobConverter.to_client_model(asgard_job).disabled)
async def test_to_client_model_retries_field(self, chronos_job_fixture): chronos_job = ChronosJob(**chronos_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model(chronos_job) self.assertEqual(chronos_job_fixture["retries"], asgard_job.retries) asgard_job.retries = 4 chronos_job_converted = ChronosScheduledJobConverter.to_client_model( asgard_job) self.assertEqual(asgard_job.retries, chronos_job_converted.retries)
async def test_list_jobs_no_not_include_jobs_from_other_namespaces( self, infra_job_fixture, dev_job_fixture): await _load_jobs_into_chronos(infra_job_fixture, dev_job_fixture) user = User(**USER_WITH_MULTIPLE_ACCOUNTS_DICT) account = Account(**ACCOUNT_DEV_DICT) jobs = await self.backend.list_jobs(user, account) expected_asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_job_fixture)).remove_namespace(account) self.assertCountEqual([expected_asgard_job], jobs)
async def setUp(self): self.backend = ChronosScheduledJobsBackend() self.chronos_dev_job_fixture = get_fixture( "scheduled-jobs/chronos/dev-with-infra-in-name.json") self.asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**self.chronos_dev_job_fixture)) self.user = User(**USER_WITH_MULTIPLE_ACCOUNTS_DICT) self.account = Account(**ACCOUNT_DEV_DICT)
async def list_jobs(self, user: User, account: Account) -> List[ScheduledJob]: filter_prefix = f"{account.namespace}-" chronos_jobs = await self.client.search(name=filter_prefix) all_jobs = [ ChronosScheduledJobConverter.to_asgard_model(job).remove_namespace( account) for job in chronos_jobs if job.name.startswith(filter_prefix) ] all_jobs.sort(key=lambda job: job.id) return all_jobs
async def test_convert_to_asgard_model_schedule_field( self, chronos_job_fixture): chronos_job = ChronosJob(**chronos_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model(chronos_job) self.assertEqual( { "value": chronos_job_fixture["schedule"], "tz": chronos_job_fixture["scheduleTimeZone"], }, asgard_job.schedule.dict(), )
async def test_convert_to_asgard_model_env_field(self, chronos_job_fixture): chronos_job = ChronosJob(**chronos_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model(chronos_job) self.assertEqual( { "ENV_1": "VALUE_1", "ENV_2": "VALUE_2", "ENV_3": "VALUE_3" }, asgard_job.dict()["env"], )
async def test_create_job_add_internal_field_values(self, dev_job_fixture): """ Conferimos que quando um job é criado adicionamos os valores obrigatórios de alguns campos """ await _cleanup_chronos() account = Account(**ACCOUNT_DEV_DICT) asgard_job_no_namespace = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_job_fixture)).remove_namespace(account) resp = await self.client.post( "/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, json=asgard_job_no_namespace.dict(), ) self.assertEqual(HTTPStatus.CREATED, resp.status) resp_data = await resp.json() self.assertEqual( f"{asgard_job_no_namespace.id}", CreateScheduledJobResource(**resp_data).job.id, ) await asyncio.sleep(0.5) resp_created_job = await self.client.get( f"/jobs/{asgard_job_no_namespace.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) created_job_resource = ScheduledJobResource( **await resp_created_job.json()) self.assertEqual(HTTPStatus.OK, resp_created_job.status) expected_constraints = asgard_job_no_namespace.constraints + [ f"owner:LIKE:{account.owner}" ] self.assertEqual(created_job_resource.job.constraints, expected_constraints) expected_fetch_uris = asgard_job_no_namespace.fetch + [ FetchURLSpec( uri=settings.SCHEDULED_JOBS_DEFAULT_FETCH_URIS[0].uri), FetchURLSpec( uri=settings.SCHEDULED_JOBS_DEFAULT_FETCH_URIS[1].uri), ] self.assertEqual(created_job_resource.job.fetch, expected_fetch_uris)
async def get_job_by_id(self, job_id: str, user: User, account: Account) -> Optional[ScheduledJob]: namespaced_job_id = f"{account.namespace}-{job_id}" try: chronos_job = await self.client.get_job_by_id(namespaced_job_id) if chronos_job: scheduled_job = ChronosScheduledJobConverter.to_asgard_model( chronos_job) scheduled_job.remove_namespace(account) return scheduled_job except HTTPNotFound: return None return None
async def test_convert_to_client_full_model(self, chronos_job_fixture): """ Confirma que os campos que são, na verdade, sub-modelos também são incluídos na conversão. """ chronos_job_original = ChronosJob(**chronos_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model( chronos_job_original) chronos_job_converted = ChronosScheduledJobConverter.to_client_model( asgard_job) self.assertEqual(chronos_job_original.dict(), chronos_job_converted.dict())
async def _save_job(self, job: ScheduledJob, user: User, account: Account) -> ScheduledJob: job.add_constraint(f"owner:LIKE:{account.owner}") [ job.add_fetch_uri(fetch) for fetch in settings.SCHEDULED_JOBS_DEFAULT_FETCH_URIS ] namespaced_job_id = f"{account.namespace}-{job.id}" chronos_job = ChronosScheduledJobConverter.to_client_model(job) chronos_job.name = namespaced_job_id cretaed_chronos_job = await self.client.create_job(chronos_job) asgard_job = ChronosScheduledJobConverter.to_asgard_model( cretaed_chronos_job) asgard_job.remove_namespace(account) return asgard_job
async def test_add_multiple_constraints_builder_style( self, dev_chronos_job_fixture): """ Confirmamos que podemos fazer: job.add_constraint(...) .add_constraint(...) """ del dev_chronos_job_fixture["constraints"] asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_chronos_job_fixture)) self.assertIsNone(asgard_job.constraints) asgard_job.add_constraint("dc:LIKE:aws").add_constraint( "workload:LIKE:general") self.assertCountEqual(["dc:LIKE:aws", "workload:LIKE:general"], asgard_job.constraints)
async def test_jobs_get_by_id_job_exist(self, chronos_job_fixture): await _load_jobs_into_chronos(chronos_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**chronos_job_fixture)) # A busca deve ser feita sempre *sem* o namespace asgard_job.remove_namespace(self.account) resp = await self.client.get( f"/jobs/{asgard_job.id}", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, ) self.assertEqual(HTTPStatus.OK, resp.status) resp_data = await resp.json() self.assertEqual( ScheduledJobResource(job=asgard_job).dict(), resp_data)
async def test_update_job_body_without_job_id(self, dev_job_fixture): """ Validamos que o id é obrigatório para atualizar um job """ asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**dev_job_fixture)) asgard_job_dict = asgard_job.dict() del asgard_job_dict["id"] resp = await self.client.put( f"/jobs", headers={ "Authorization": f"Token {USER_WITH_MULTIPLE_ACCOUNTS_AUTH_KEY}" }, json=asgard_job_dict, ) self.assertEqual(HTTPStatus.UNPROCESSABLE_ENTITY, resp.status)
async def test_create_job_name_has_namespace_from_another_account( self, dev_job_fixture, infra_job_fixture): """ Mesmo que o nome do job começe com o namespace de outra conta, o registro do novo job deve ser feito na conta correta """ user = User(**USER_WITH_MULTIPLE_ACCOUNTS_DICT) account = Account(**ACCOUNT_DEV_DICT) await _load_jobs_into_chronos(dev_job_fixture) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**infra_job_fixture)) returned_job = await self.backend.create_job(asgard_job, user, account) await asyncio.sleep(1) stored_job = await self.backend.get_job_by_id(returned_job.id, user, account) self.assertEqual(returned_job, stored_job)
async def test_create_job_duplicate_entity(self, dev_job_fixture, infra_job_fixture): """ Se tentarmos criar um job com o mesmo nome de um que já existe, lançamos DuplicateEntity exception. Para atualizar um job temos um método separado """ await _load_jobs_into_chronos(dev_job_fixture) infra_job_fixture["name"] = dev_job_fixture["name"] user = User(**USER_WITH_MULTIPLE_ACCOUNTS_DICT) account = Account(**ACCOUNT_DEV_DICT) asgard_job = ChronosScheduledJobConverter.to_asgard_model( ChronosJob(**infra_job_fixture)) asgard_job.remove_namespace(account) with self.assertRaises(DuplicateEntity): await self.backend.create_job(asgard_job, user, account)