Skip to content

Commit

Permalink
Service: Scheduler (#6197)
Browse files Browse the repository at this point in the history
  • Loading branch information
bblommers authored Apr 10, 2023
1 parent 01e2d11 commit b4346e2
Show file tree
Hide file tree
Showing 16 changed files with 783 additions and 2 deletions.
19 changes: 18 additions & 1 deletion IMPLEMENTATION_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6172,6 +6172,24 @@
- [ ] update_workteam
</details>

## scheduler
<details>
<summary>100% implemented</summary>

- [X] create_schedule
- [X] create_schedule_group
- [X] delete_schedule
- [X] delete_schedule_group
- [X] get_schedule
- [X] get_schedule_group
- [X] list_schedule_groups
- [X] list_schedules
- [X] list_tags_for_resource
- [X] tag_resource
- [X] untag_resource
- [X] update_schedule
</details>

## sdb
<details>
<summary>50% implemented</summary>
Expand Down Expand Up @@ -7081,7 +7099,6 @@
- sagemaker-metrics
- sagemaker-runtime
- savingsplans
- scheduler
- schemas
- securityhub
- securitylake
Expand Down
58 changes: 58 additions & 0 deletions docs/docs/services/scheduler.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.. _implementedservice_scheduler:

.. |start-h3| raw:: html

<h3>

.. |end-h3| raw:: html

</h3>

=========
scheduler
=========

.. autoclass:: moto.scheduler.models.EventBridgeSchedulerBackend

|start-h3| Example usage |end-h3|

.. sourcecode:: python

@mock_scheduler
def test_scheduler_behaviour:
boto3.client("scheduler")
...



|start-h3| Implemented features for this service |end-h3|

- [X] create_schedule

The ClientToken parameter is not yet implemented


- [X] create_schedule_group

The ClientToken parameter is not yet implemented


- [X] delete_schedule
- [X] delete_schedule_group
- [X] get_schedule
- [X] get_schedule_group
- [X] list_schedule_groups

The MaxResults-parameter and pagination options are not yet implemented


- [ ] list_schedules
- [X] list_tags_for_resource
- [X] tag_resource
- [X] untag_resource
- [X] update_schedule

The ClientToken is not yet implemented



1 change: 1 addition & 0 deletions moto/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def f(*args, **kwargs):
mock_s3 = lazy_load(".s3", "mock_s3")
mock_s3control = lazy_load(".s3control", "mock_s3control")
mock_sagemaker = lazy_load(".sagemaker", "mock_sagemaker")
mock_scheduler = lazy_load(".scheduler", "mock_scheduler")
mock_sdb = lazy_load(".sdb", "mock_sdb")
mock_secretsmanager = lazy_load(".secretsmanager", "mock_secretsmanager")
mock_servicequotas = lazy_load(
Expand Down
1 change: 1 addition & 0 deletions moto/backend_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
re.compile("https?://([0-9]+)\\.s3-control\\.(.+)\\.amazonaws\\.com"),
),
("sagemaker", re.compile("https?://api\\.sagemaker\\.(.+)\\.amazonaws.com")),
("scheduler", re.compile("https?://scheduler\\.(.+)\\.amazonaws\\.com")),
("sdb", re.compile("https?://sdb\\.(.+)\\.amazonaws\\.com")),
("secretsmanager", re.compile("https?://secretsmanager\\.(.+)\\.amazonaws\\.com")),
(
Expand Down
5 changes: 5 additions & 0 deletions moto/scheduler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""scheduler module initialization; sets value for base decorator."""
from .models import scheduler_backends
from ..core.models import base_decorator

mock_scheduler = base_decorator(scheduler_backends)
12 changes: 12 additions & 0 deletions moto/scheduler/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Exceptions raised by the scheduler service."""
from moto.core.exceptions import JsonRESTError


class ScheduleNotFound(JsonRESTError):
def __init__(self) -> None:
super().__init__("ResourceNotFoundException", "Schedule not found")


class ScheduleGroupNotFound(JsonRESTError):
def __init__(self) -> None:
super().__init__("ResourceNotFoundException", "ScheduleGroup not found")
240 changes: 240 additions & 0 deletions moto/scheduler/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
"""EventBridgeSchedulerBackend class with methods for supported APIs."""
from typing import Any, Dict, List, Iterable, Optional

from moto.core import BaseBackend, BackendDict, BaseModel
from moto.core.utils import unix_time
from moto.utilities.tagging_service import TaggingService

from .exceptions import ScheduleNotFound, ScheduleGroupNotFound


class Schedule(BaseModel):
def __init__(
self,
region: str,
account_id: str,
group_name: str,
name: str,
description: Optional[str],
schedule_expression: str,
schedule_expression_timezone: Optional[str],
flexible_time_window: Dict[str, Any],
target: Dict[str, Any],
state: Optional[str],
kms_key_arn: Optional[str],
start_date: Optional[str],
end_date: Optional[str],
):
self.name = name
self.group_name = group_name
self.description = description
self.arn = (
f"arn:aws:scheduler:{region}:{account_id}:schedule/{group_name}/{name}"
)
self.schedule_expression = schedule_expression
self.schedule_expression_timezone = schedule_expression_timezone
self.flexible_time_window = flexible_time_window
self.target = Schedule.validate_target(target)
self.state = state or "ENABLED"
self.kms_key_arn = kms_key_arn
self.start_date = start_date
self.end_date = end_date

@staticmethod
def validate_target(target: Dict[str, Any]) -> Dict[str, Any]: # type: ignore[misc]
if "RetryPolicy" not in target:
target["RetryPolicy"] = {
"MaximumEventAgeInSeconds": 86400,
"MaximumRetryAttempts": 185,
}
return target

def to_dict(self, short: bool = False) -> Dict[str, Any]:
dct: Dict[str, Any] = {
"Arn": self.arn,
"Name": self.name,
"GroupName": self.group_name,
"Description": self.description,
"ScheduleExpression": self.schedule_expression,
"ScheduleExpressionTimezone": self.schedule_expression_timezone,
"FlexibleTimeWindow": self.flexible_time_window,
"Target": self.target,
"State": self.state,
"KmsKeyArn": self.kms_key_arn,
"StartDate": self.start_date,
"EndDate": self.end_date,
}
if short:
dct["Target"] = {"Arn": dct["Target"]["Arn"]}
return dct


class ScheduleGroup(BaseModel):
def __init__(self, region: str, account_id: str, name: str):
self.name = name
self.arn = f"arn:aws:scheduler:{region}:{account_id}:schedule-group/{name}"
self.schedules: Dict[str, Schedule] = dict()
self.created_on = None if self.name == "default" else unix_time()
self.last_modified = None if self.name == "default" else unix_time()

def add_schedule(self, schedule: Schedule) -> None:
self.schedules[schedule.name] = schedule

def get_schedule(self, name: str) -> Schedule:
if name not in self.schedules:
raise ScheduleNotFound
return self.schedules[name]

def delete_schedule(self, name: str) -> None:
self.schedules.pop(name)

def to_dict(self) -> Dict[str, Any]:
return {
"Arn": self.arn,
"CreationDate": self.created_on,
"LastModificationDate": self.last_modified,
"Name": self.name,
"State": "ACTIVE",
}


class EventBridgeSchedulerBackend(BaseBackend):
"""Implementation of EventBridgeScheduler APIs."""

def __init__(self, region_name: str, account_id: str):
super().__init__(region_name, account_id)
self.schedules: List[Schedule] = list()
self.schedule_groups = {
"default": ScheduleGroup(
region=region_name, account_id=account_id, name="default"
)
}
self.tagger = TaggingService()

def create_schedule(
self,
description: str,
end_date: str,
flexible_time_window: Dict[str, Any],
group_name: str,
kms_key_arn: str,
name: str,
schedule_expression: str,
schedule_expression_timezone: str,
start_date: str,
state: str,
target: Dict[str, Any],
) -> Schedule:
"""
The ClientToken parameter is not yet implemented
"""
group = self.schedule_groups[group_name or "default"]
schedule = Schedule(
region=self.region_name,
account_id=self.account_id,
group_name=group.name,
name=name,
description=description,
schedule_expression=schedule_expression,
schedule_expression_timezone=schedule_expression_timezone,
flexible_time_window=flexible_time_window,
target=target,
state=state,
kms_key_arn=kms_key_arn,
start_date=start_date,
end_date=end_date,
)
group.add_schedule(schedule)
return schedule

def get_schedule(self, group_name: Optional[str], name: str) -> Schedule:
group = self.get_schedule_group(group_name)
return group.get_schedule(name)

def delete_schedule(self, group_name: Optional[str], name: str) -> None:
group = self.get_schedule_group(group_name)
group.delete_schedule(name)

def update_schedule(
self,
description: str,
end_date: str,
flexible_time_window: Dict[str, Any],
group_name: str,
kms_key_arn: str,
name: str,
schedule_expression: str,
schedule_expression_timezone: str,
start_date: str,
state: str,
target: Dict[str, Any],
) -> Schedule:
"""
The ClientToken is not yet implemented
"""
schedule = self.get_schedule(group_name=group_name, name=name)
schedule.schedule_expression = schedule_expression
schedule.schedule_expression_timezone = schedule_expression_timezone
schedule.flexible_time_window = flexible_time_window
schedule.target = Schedule.validate_target(target)
schedule.description = description
schedule.state = state
schedule.kms_key_arn = kms_key_arn
schedule.start_date = start_date
schedule.end_date = end_date
return schedule

def list_schedules(
self, group_names: Optional[str], state: Optional[str]
) -> Iterable[Schedule]:
"""
The following parameters are not yet implemented: MaxResults, NamePrefix, NextToken
"""
results = []
for group in self.schedule_groups.values():
if not group_names or group.name in group_names:
for schedule in group.schedules.values():
if not state or schedule.state == state:
results.append(schedule)
return results

def create_schedule_group(
self, name: str, tags: List[Dict[str, str]]
) -> ScheduleGroup:
"""
The ClientToken parameter is not yet implemented
"""
group = ScheduleGroup(
region=self.region_name, account_id=self.account_id, name=name
)
self.schedule_groups[name] = group
self.tagger.tag_resource(group.arn, tags)
return group

def get_schedule_group(self, group_name: Optional[str]) -> ScheduleGroup:
if (group_name or "default") not in self.schedule_groups:
raise ScheduleGroupNotFound
return self.schedule_groups[group_name or "default"]

def list_schedule_groups(self) -> Iterable[ScheduleGroup]:
"""
The MaxResults-parameter and pagination options are not yet implemented
"""
return self.schedule_groups.values()

def delete_schedule_group(self, name: Optional[str]) -> None:
self.schedule_groups.pop(name or "default")

def list_tags_for_resource(
self, resource_arn: str
) -> Dict[str, List[Dict[str, str]]]:
return self.tagger.list_tags_for_resource(resource_arn)

def tag_resource(self, resource_arn: str, tags: List[Dict[str, str]]) -> None:
self.tagger.tag_resource(resource_arn, tags)

def untag_resource(self, resource_arn: str, tag_keys: List[str]) -> None:
self.tagger.untag_resource_using_names(resource_arn, tag_keys)


scheduler_backends = BackendDict(EventBridgeSchedulerBackend, "scheduler")
Loading

0 comments on commit b4346e2

Please sign in to comment.