Glue: allow multiple job runs (#5483)
This commit is contained in:
parent
e230750a7c
commit
9fc64ad93b
@ -36,7 +36,7 @@ Sticking with the example above, you may want to test what happens if the cluste
|
|||||||
|
|
||||||
from moto.moto_api import state_manager
|
from moto.moto_api import state_manager
|
||||||
|
|
||||||
state_manager.set_transition(model_name="dax::cluster", transition={"progression": "time", "duration": 5})
|
state_manager.set_transition(model_name="dax::cluster", transition={"progression": "time", "seconds": 5})
|
||||||
|
|
||||||
create_and_wait_for_cluster("my_new_cluster")
|
create_and_wait_for_cluster("my_new_cluster")
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ In order to test what happens in the event of a timeout, we can order the cluste
|
|||||||
|
|
||||||
from moto.moto_api import state_manager
|
from moto.moto_api import state_manager
|
||||||
|
|
||||||
state_manager.set_transition(model_name="dax::cluster", transition={"progression": "time", "duration": 600})
|
state_manager.set_transition(model_name="dax::cluster", transition={"progression": "time", "seconds": 600})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
create_and_wait_for_cluster("my_new_cluster")
|
create_and_wait_for_cluster("my_new_cluster")
|
||||||
@ -124,7 +124,7 @@ This is an example request for `dax::cluster` to wait 5 seconds before the clust
|
|||||||
|
|
||||||
.. sourcecode:: python
|
.. sourcecode:: python
|
||||||
|
|
||||||
post_body = dict(model_name="dax::cluster", transition={"progression": "time", "duration": 5})
|
post_body = dict(model_name="dax::cluster", transition={"progression": "time", "seconds": 5})
|
||||||
resp = requests.post("http://localhost:5000/moto-api/state-manager/set-transition", data=json.dumps(post_body))
|
resp = requests.post("http://localhost:5000/moto-api/state-manager/set-transition", data=json.dumps(post_body))
|
||||||
|
|
||||||
An example request to see the currently configured transition for a specific model:
|
An example request to see the currently configured transition for a specific model:
|
||||||
|
@ -56,6 +56,19 @@ Advancement:
|
|||||||
|
|
||||||
Call `boto3.client("dax").describe_clusters(..)`.
|
Call `boto3.client("dax").describe_clusters(..)`.
|
||||||
|
|
||||||
|
Service: Glue
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
**Model**: `glue::job_run` :raw-html:`<br />`
|
||||||
|
Available States: :raw-html:`<br />`
|
||||||
|
|
||||||
|
"STARTING" --> "RUNNING" --> "SUCCEEDED"
|
||||||
|
|
||||||
|
Transition type: `immediate` :raw-html:`<br />`
|
||||||
|
Advancement:
|
||||||
|
|
||||||
|
Call `boto3.client("glue").get_job_run(..)`
|
||||||
|
|
||||||
Service: S3 (Glacier Restoration)
|
Service: S3 (Glacier Restoration)
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
|
@ -60,6 +60,11 @@ class JobNotFoundException(EntityNotFoundException):
|
|||||||
super().__init__("Job %s not found." % job)
|
super().__init__("Job %s not found." % job)
|
||||||
|
|
||||||
|
|
||||||
|
class JobRunNotFoundException(EntityNotFoundException):
|
||||||
|
def __init__(self, job_run):
|
||||||
|
super().__init__("Job run %s not found." % job_run)
|
||||||
|
|
||||||
|
|
||||||
class VersionNotFoundException(EntityNotFoundException):
|
class VersionNotFoundException(EntityNotFoundException):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__("Version not found.")
|
super().__init__("Version not found.")
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import time
|
import time
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.utils import BackendDict
|
from moto.core.utils import BackendDict
|
||||||
from moto.glue.exceptions import (
|
from moto.moto_api import state_manager
|
||||||
CrawlerRunningException,
|
from moto.moto_api._internal.managed_state_model import ManagedState
|
||||||
CrawlerNotRunningException,
|
|
||||||
SchemaVersionNotFoundFromSchemaVersionIdException,
|
|
||||||
SchemaVersionNotFoundFromSchemaIdException,
|
|
||||||
SchemaNotFoundException,
|
|
||||||
SchemaVersionMetadataAlreadyExistsException,
|
|
||||||
)
|
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
JsonRESTError,
|
JsonRESTError,
|
||||||
|
CrawlerRunningException,
|
||||||
|
CrawlerNotRunningException,
|
||||||
CrawlerAlreadyExistsException,
|
CrawlerAlreadyExistsException,
|
||||||
CrawlerNotFoundException,
|
CrawlerNotFoundException,
|
||||||
DatabaseAlreadyExistsException,
|
DatabaseAlreadyExistsException,
|
||||||
@ -25,7 +22,12 @@ from .exceptions import (
|
|||||||
PartitionNotFoundException,
|
PartitionNotFoundException,
|
||||||
VersionNotFoundException,
|
VersionNotFoundException,
|
||||||
JobNotFoundException,
|
JobNotFoundException,
|
||||||
|
JobRunNotFoundException,
|
||||||
ConcurrentRunsExceededException,
|
ConcurrentRunsExceededException,
|
||||||
|
SchemaVersionNotFoundFromSchemaVersionIdException,
|
||||||
|
SchemaVersionNotFoundFromSchemaIdException,
|
||||||
|
SchemaNotFoundException,
|
||||||
|
SchemaVersionMetadataAlreadyExistsException,
|
||||||
)
|
)
|
||||||
from .utils import PartitionFilter
|
from .utils import PartitionFilter
|
||||||
from .glue_schema_registry_utils import (
|
from .glue_schema_registry_utils import (
|
||||||
@ -78,6 +80,10 @@ class GlueBackend(BaseBackend):
|
|||||||
self.num_schemas = 0
|
self.num_schemas = 0
|
||||||
self.num_schema_versions = 0
|
self.num_schema_versions = 0
|
||||||
|
|
||||||
|
state_manager.register_default_transition(
|
||||||
|
model_name="glue::job_run", transition={"progression": "immediate"}
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_vpc_endpoint_service(service_region, zones):
|
def default_vpc_endpoint_service(service_region, zones):
|
||||||
"""Default VPC endpoint service."""
|
"""Default VPC endpoint service."""
|
||||||
@ -850,7 +856,7 @@ class FakeJob:
|
|||||||
self.description = description
|
self.description = description
|
||||||
self.log_uri = log_uri
|
self.log_uri = log_uri
|
||||||
self.role = role
|
self.role = role
|
||||||
self.execution_property = execution_property
|
self.execution_property = execution_property or {}
|
||||||
self.command = command
|
self.command = command
|
||||||
self.default_arguments = default_arguments
|
self.default_arguments = default_arguments
|
||||||
self.non_overridable_arguments = non_overridable_arguments
|
self.non_overridable_arguments = non_overridable_arguments
|
||||||
@ -858,7 +864,6 @@ class FakeJob:
|
|||||||
self.max_retries = max_retries
|
self.max_retries = max_retries
|
||||||
self.allocated_capacity = allocated_capacity
|
self.allocated_capacity = allocated_capacity
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.state = "READY"
|
|
||||||
self.max_capacity = max_capacity
|
self.max_capacity = max_capacity
|
||||||
self.security_configuration = security_configuration
|
self.security_configuration = security_configuration
|
||||||
self.notification_property = notification_property
|
self.notification_property = notification_property
|
||||||
@ -871,6 +876,8 @@ class FakeJob:
|
|||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.backend.tag_resource(self.arn, tags)
|
self.backend.tag_resource(self.arn, tags)
|
||||||
|
|
||||||
|
self.job_runs: List[FakeJobRun] = []
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@ -899,20 +906,26 @@ class FakeJob:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def start_job_run(self):
|
def start_job_run(self):
|
||||||
if self.state == "RUNNING":
|
running_jobs = len(
|
||||||
|
[jr for jr in self.job_runs if jr.status in ["STARTING", "RUNNING"]]
|
||||||
|
)
|
||||||
|
if running_jobs >= self.execution_property.get("MaxConcurrentRuns", 1):
|
||||||
raise ConcurrentRunsExceededException(
|
raise ConcurrentRunsExceededException(
|
||||||
f"Job with name {self.name} already running"
|
f"Job with name {self.name} already running"
|
||||||
)
|
)
|
||||||
fake_job_run = FakeJobRun(job_name=self.name)
|
fake_job_run = FakeJobRun(job_name=self.name)
|
||||||
self.state = "RUNNING"
|
self.job_runs.append(fake_job_run)
|
||||||
return fake_job_run.job_run_id
|
return fake_job_run.job_run_id
|
||||||
|
|
||||||
def get_job_run(self, run_id):
|
def get_job_run(self, run_id):
|
||||||
fake_job_run = FakeJobRun(job_name=self.name, job_run_id=run_id)
|
for job_run in self.job_runs:
|
||||||
return fake_job_run
|
if job_run.job_run_id == run_id:
|
||||||
|
job_run.advance()
|
||||||
|
return job_run
|
||||||
|
raise JobRunNotFoundException(run_id)
|
||||||
|
|
||||||
|
|
||||||
class FakeJobRun:
|
class FakeJobRun(ManagedState):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
job_name: int,
|
job_name: int,
|
||||||
@ -922,6 +935,11 @@ class FakeJobRun:
|
|||||||
timeout: int = None,
|
timeout: int = None,
|
||||||
worker_type: str = "Standard",
|
worker_type: str = "Standard",
|
||||||
):
|
):
|
||||||
|
ManagedState.__init__(
|
||||||
|
self,
|
||||||
|
model_name="glue::job_run",
|
||||||
|
transitions=[("STARTING", "RUNNING"), ("RUNNING", "SUCCEEDED")],
|
||||||
|
)
|
||||||
self.job_name = job_name
|
self.job_name = job_name
|
||||||
self.job_run_id = job_run_id
|
self.job_run_id = job_run_id
|
||||||
self.arguments = arguments
|
self.arguments = arguments
|
||||||
@ -945,7 +963,7 @@ class FakeJobRun:
|
|||||||
"StartedOn": self.started_on.isoformat(),
|
"StartedOn": self.started_on.isoformat(),
|
||||||
"LastModifiedOn": self.modified_on.isoformat(),
|
"LastModifiedOn": self.modified_on.isoformat(),
|
||||||
"CompletedOn": self.completed_on.isoformat(),
|
"CompletedOn": self.completed_on.isoformat(),
|
||||||
"JobRunState": "SUCCEEDED",
|
"JobRunState": self.status,
|
||||||
"Arguments": self.arguments or {"runSpark": "spark -f test_file.py"},
|
"Arguments": self.arguments or {"runSpark": "spark -f test_file.py"},
|
||||||
"ErrorMessage": "",
|
"ErrorMessage": "",
|
||||||
"PredecessorRuns": [
|
"PredecessorRuns": [
|
||||||
|
@ -27,7 +27,6 @@ from moto.core.utils import (
|
|||||||
BackendDict,
|
BackendDict,
|
||||||
)
|
)
|
||||||
from moto.cloudwatch.models import MetricDatum
|
from moto.cloudwatch.models import MetricDatum
|
||||||
from moto.iam.access_control import IAMPolicy, PermissionResult
|
|
||||||
from moto.moto_api import state_manager
|
from moto.moto_api import state_manager
|
||||||
from moto.moto_api._internal.managed_state_model import ManagedState
|
from moto.moto_api._internal.managed_state_model import ManagedState
|
||||||
from moto.utilities.tagging_service import TaggingService
|
from moto.utilities.tagging_service import TaggingService
|
||||||
@ -911,6 +910,8 @@ class FakeBucket(CloudFormationModel):
|
|||||||
def allow_action(self, action, resource):
|
def allow_action(self, action, resource):
|
||||||
if self.policy is None:
|
if self.policy is None:
|
||||||
return False
|
return False
|
||||||
|
from moto.iam.access_control import IAMPolicy, PermissionResult
|
||||||
|
|
||||||
iam_policy = IAMPolicy(self.policy.decode())
|
iam_policy = IAMPolicy(self.policy.decode())
|
||||||
result = iam_policy.is_action_permitted(action, resource)
|
result = iam_policy.is_action_permitted(action, resource)
|
||||||
return result == PermissionResult.PERMITTED
|
return result == PermissionResult.PERMITTED
|
||||||
|
@ -85,76 +85,28 @@ def test_get_job_exists():
|
|||||||
"GlueVersion": "string",
|
"GlueVersion": "string",
|
||||||
}
|
}
|
||||||
job_name = create_test_job_w_all_attributes(client, **job_attributes)
|
job_name = create_test_job_w_all_attributes(client, **job_attributes)
|
||||||
response = client.get_job(JobName=job_name)
|
job = client.get_job(JobName=job_name)["Job"]
|
||||||
assert response["Job"]["Name"] == job_name
|
job.should.have.key("Name").equals(job_name)
|
||||||
assert response["Job"]["Description"]
|
job.should.have.key("Description")
|
||||||
assert response["Job"]["LogUri"]
|
job.should.have.key("LogUri")
|
||||||
assert response["Job"]["Role"]
|
job.should.have.key("Role")
|
||||||
assert response["Job"]["CreatedOn"]
|
job.should.have.key("ExecutionProperty").equals({"MaxConcurrentRuns": 123})
|
||||||
assert response["Job"]["LastModifiedOn"]
|
job.should.have.key("CreatedOn")
|
||||||
assert response["Job"]["ExecutionProperty"]
|
job.should.have.key("LastModifiedOn")
|
||||||
assert response["Job"]["Command"]
|
job.should.have.key("ExecutionProperty")
|
||||||
assert response["Job"]["DefaultArguments"]
|
job.should.have.key("Command")
|
||||||
assert response["Job"]["NonOverridableArguments"]
|
job.should.have.key("DefaultArguments")
|
||||||
assert response["Job"]["Connections"]
|
job.should.have.key("NonOverridableArguments")
|
||||||
assert response["Job"]["MaxRetries"]
|
job.should.have.key("Connections")
|
||||||
assert response["Job"]["AllocatedCapacity"]
|
job.should.have.key("MaxRetries")
|
||||||
assert response["Job"]["Timeout"]
|
job.should.have.key("AllocatedCapacity")
|
||||||
assert response["Job"]["MaxCapacity"]
|
job.should.have.key("Timeout")
|
||||||
assert response["Job"]["WorkerType"]
|
job.should.have.key("MaxCapacity")
|
||||||
assert response["Job"]["NumberOfWorkers"]
|
job.should.have.key("WorkerType")
|
||||||
assert response["Job"]["SecurityConfiguration"]
|
job.should.have.key("NumberOfWorkers")
|
||||||
assert response["Job"]["NotificationProperty"]
|
job.should.have.key("SecurityConfiguration")
|
||||||
assert response["Job"]["GlueVersion"]
|
job.should.have.key("NotificationProperty")
|
||||||
|
job.should.have.key("GlueVersion")
|
||||||
|
|
||||||
@mock_glue
|
|
||||||
def test_start_job_run():
|
|
||||||
client = create_glue_client()
|
|
||||||
job_name = create_test_job(client)
|
|
||||||
response = client.start_job_run(JobName=job_name)
|
|
||||||
assert response["JobRunId"]
|
|
||||||
|
|
||||||
|
|
||||||
@mock_glue
|
|
||||||
def test_start_job_run_already_running():
|
|
||||||
client = create_glue_client()
|
|
||||||
job_name = create_test_job(client)
|
|
||||||
client.start_job_run(JobName=job_name)
|
|
||||||
with pytest.raises(ClientError) as exc:
|
|
||||||
client.start_job_run(JobName=job_name)
|
|
||||||
exc.value.response["Error"]["Code"].should.equal("ConcurrentRunsExceededException")
|
|
||||||
exc.value.response["Error"]["Message"].should.match(
|
|
||||||
f"Job with name {job_name} already running"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@mock_glue
|
|
||||||
def test_get_job_run():
|
|
||||||
client = create_glue_client()
|
|
||||||
job_name = create_test_job(client)
|
|
||||||
response = client.get_job_run(JobName=job_name, RunId="01")
|
|
||||||
assert response["JobRun"]["Id"]
|
|
||||||
assert response["JobRun"]["Attempt"]
|
|
||||||
assert response["JobRun"]["PreviousRunId"]
|
|
||||||
assert response["JobRun"]["TriggerName"]
|
|
||||||
assert response["JobRun"]["StartedOn"]
|
|
||||||
assert response["JobRun"]["LastModifiedOn"]
|
|
||||||
assert response["JobRun"]["CompletedOn"]
|
|
||||||
assert response["JobRun"]["JobRunState"]
|
|
||||||
assert response["JobRun"]["Arguments"]
|
|
||||||
assert response["JobRun"]["ErrorMessage"] == ""
|
|
||||||
assert response["JobRun"]["PredecessorRuns"]
|
|
||||||
assert response["JobRun"]["AllocatedCapacity"]
|
|
||||||
assert response["JobRun"]["ExecutionTime"]
|
|
||||||
assert response["JobRun"]["Timeout"]
|
|
||||||
assert response["JobRun"]["MaxCapacity"]
|
|
||||||
assert response["JobRun"]["WorkerType"]
|
|
||||||
assert response["JobRun"]["NumberOfWorkers"]
|
|
||||||
assert response["JobRun"]["SecurityConfiguration"]
|
|
||||||
assert response["JobRun"]["LogGroupName"]
|
|
||||||
assert response["JobRun"]["NotificationProperty"]
|
|
||||||
assert response["JobRun"]["GlueVersion"]
|
|
||||||
|
|
||||||
|
|
||||||
@mock_glue
|
@mock_glue
|
||||||
|
140
tests/test_glue/test_glue_job_runs.py
Normal file
140
tests/test_glue/test_glue_job_runs.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import pytest
|
||||||
|
import sure # noqa # pylint: disable=unused-import
|
||||||
|
from botocore.client import ClientError
|
||||||
|
from unittest import SkipTest
|
||||||
|
|
||||||
|
from moto import mock_glue, settings
|
||||||
|
from moto.moto_api import state_manager
|
||||||
|
from .test_glue import create_test_job, create_glue_client
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_start_job_run():
|
||||||
|
client = create_glue_client()
|
||||||
|
job_name = create_test_job(client)
|
||||||
|
response = client.start_job_run(JobName=job_name)
|
||||||
|
assert response["JobRunId"]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_start_job_run__multiple_runs_allowed():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Can't set transition directly in ServerMode")
|
||||||
|
|
||||||
|
state_manager.set_transition(
|
||||||
|
model_name="glue::job_run", transition={"progression": "manual", "times": 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
glue = create_glue_client()
|
||||||
|
glue.create_job(
|
||||||
|
Name="somejobname",
|
||||||
|
Role="some-role",
|
||||||
|
ExecutionProperty={"MaxConcurrentRuns": 5},
|
||||||
|
Command={
|
||||||
|
"Name": "some-name",
|
||||||
|
"ScriptLocation": "some-location",
|
||||||
|
"PythonVersion": "some-version",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
for _ in range(5):
|
||||||
|
glue.start_job_run(JobName="somejobname")
|
||||||
|
|
||||||
|
# The 6th should fail
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
glue.start_job_run(JobName="somejobname")
|
||||||
|
exc.value.response["Error"]["Code"].should.equal("ConcurrentRunsExceededException")
|
||||||
|
exc.value.response["Error"]["Message"].should.match(
|
||||||
|
"Job with name somejobname already running"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_start_job_run__single_run_allowed():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Can't set transition directly in ServerMode")
|
||||||
|
|
||||||
|
state_manager.set_transition(
|
||||||
|
model_name="glue::job_run", transition={"progression": "manual", "times": 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
client = create_glue_client()
|
||||||
|
job_name = create_test_job(client)
|
||||||
|
client.start_job_run(JobName=job_name)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.start_job_run(JobName=job_name)
|
||||||
|
exc.value.response["Error"]["Code"].should.equal("ConcurrentRunsExceededException")
|
||||||
|
exc.value.response["Error"]["Message"].should.match(
|
||||||
|
f"Job with name {job_name} already running"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_job_run():
|
||||||
|
state_manager.unset_transition("glue::job_run")
|
||||||
|
client = create_glue_client()
|
||||||
|
job_name = create_test_job(client)
|
||||||
|
job_run_id = client.start_job_run(JobName=job_name)["JobRunId"]
|
||||||
|
|
||||||
|
response = client.get_job_run(JobName=job_name, RunId=job_run_id)
|
||||||
|
response["JobRun"].should.have.key("Id").equals(job_run_id)
|
||||||
|
assert response["JobRun"]["Attempt"]
|
||||||
|
assert response["JobRun"]["PreviousRunId"]
|
||||||
|
assert response["JobRun"]["TriggerName"]
|
||||||
|
assert response["JobRun"]["StartedOn"]
|
||||||
|
assert response["JobRun"]["LastModifiedOn"]
|
||||||
|
assert response["JobRun"]["CompletedOn"]
|
||||||
|
response["JobRun"].should.have.key("JobRunState").equals("SUCCEEDED")
|
||||||
|
assert response["JobRun"]["Arguments"]
|
||||||
|
assert response["JobRun"]["ErrorMessage"] == ""
|
||||||
|
assert response["JobRun"]["PredecessorRuns"]
|
||||||
|
assert response["JobRun"]["AllocatedCapacity"]
|
||||||
|
assert response["JobRun"]["ExecutionTime"]
|
||||||
|
assert response["JobRun"]["Timeout"]
|
||||||
|
assert response["JobRun"]["MaxCapacity"]
|
||||||
|
assert response["JobRun"]["WorkerType"]
|
||||||
|
assert response["JobRun"]["NumberOfWorkers"]
|
||||||
|
assert response["JobRun"]["SecurityConfiguration"]
|
||||||
|
assert response["JobRun"]["LogGroupName"]
|
||||||
|
assert response["JobRun"]["NotificationProperty"]
|
||||||
|
assert response["JobRun"]["GlueVersion"]
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_job_run_that_doesnt_exist():
|
||||||
|
client = create_glue_client()
|
||||||
|
job_name = create_test_job(client)
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.get_job_run(JobName=job_name, RunId="unknown")
|
||||||
|
err = exc.value.response["Error"]
|
||||||
|
err["Code"].should.equal("EntityNotFoundException")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_job_run_transition():
|
||||||
|
if settings.TEST_SERVER_MODE:
|
||||||
|
raise SkipTest("Can't set transition directly in ServerMode")
|
||||||
|
|
||||||
|
state_manager.set_transition(
|
||||||
|
model_name="glue::job_run", transition={"progression": "manual", "times": 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
client = create_glue_client()
|
||||||
|
job_name = create_test_job(client)
|
||||||
|
# set transition
|
||||||
|
run_id = client.start_job_run(JobName=job_name)["JobRunId"]
|
||||||
|
|
||||||
|
# The job should change over time
|
||||||
|
expect_job_state(client, job_name, run_id, expected_state="STARTING")
|
||||||
|
expect_job_state(client, job_name, run_id, expected_state="RUNNING")
|
||||||
|
expect_job_state(client, job_name, run_id, expected_state="RUNNING")
|
||||||
|
# But finishes afterwards
|
||||||
|
expect_job_state(client, job_name, run_id, expected_state="SUCCEEDED")
|
||||||
|
|
||||||
|
# unset transition
|
||||||
|
state_manager.unset_transition("glue::job_run")
|
||||||
|
|
||||||
|
|
||||||
|
def expect_job_state(client, job_name, run_id, expected_state):
|
||||||
|
client.get_job_run(JobName=job_name, RunId=run_id)["JobRun"][
|
||||||
|
"JobRunState"
|
||||||
|
].should.equal(expected_state)
|
Loading…
x
Reference in New Issue
Block a user