Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Feature: New Downscaler/Grace-Period Annotation To Override Global Grace Period for Individual Resources #74

Merged
merged 4 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,10 @@ Available command line options:
: Grace period in seconds for new deployments before scaling them down
(default: 15min). The grace period counts from time of creation of
the deployment, i.e. updated deployments will immediately be scaled
down regardless of the grace period.
down regardless of the grace period. If the `downscaler/grace-period`
annotation is present in a resource and its value is shorter than
the global grace period, the annotation's value will override the
global grace period for that specific resource.

`--upscale-period`

Expand Down Expand Up @@ -449,6 +452,12 @@ annotations are not supported if specified directly inside the Job definition du
on computing days of the week inside the policies. However you can still use
these annotations at Namespace level to downscale/upscale Jobs

**<u>Important</u>:**
global `--grace-period` is not supported for this feature at the moment, however `downscaler/downscale-period` annotation is
supported at namespace level when used to scale down jobs with Admission Controllers

**<u>Important</u>:**

**Deleting Policies:** if for some reason you want to delete all resources blocking jobs, you can use these commands:

Gatekeeper
Expand Down
56 changes: 56 additions & 0 deletions kube_downscaler/scaler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
UPTIME_ANNOTATION = "downscaler/uptime"
DOWNTIME_ANNOTATION = "downscaler/downtime"
DOWNTIME_REPLICAS_ANNOTATION = "downscaler/downtime-replicas"
GRACE_PERIOD_ANNOTATION="downscaler/grace-period"

RESOURCE_CLASSES = [
Deployment,
Expand Down Expand Up @@ -79,6 +80,13 @@ def parse_time(timestamp: str) -> datetime.datetime:
f"time data '{timestamp}' does not match any format ({', '.join(TIMESTAMP_FORMATS)})"
)

def is_grace_period_annotation_integer(value):
try:
int(value) # Attempt to convert the string to an integer
return True
except ValueError:
return False


def within_grace_period(
resource,
Expand All @@ -88,6 +96,30 @@ def within_grace_period(
):
update_time = parse_time(resource.metadata["creationTimestamp"])

grace_period_annotation = resource.annotations.get(GRACE_PERIOD_ANNOTATION, None)

if grace_period_annotation is not None and is_grace_period_annotation_integer(grace_period_annotation):
grace_period_annotation_integer = int(grace_period_annotation)

if grace_period_annotation_integer > 0:
if grace_period_annotation_integer <= grace_period:
logger.debug(
f"Grace period annotation found for {resource.kind} {resource.name} in namespace {resource.namespace}. "
f"Since the grace period specified in the annotation is shorter than the global grace period, "
f"the downscaler will use the annotation's grace period for this resource."
)
grace_period = grace_period_annotation_integer
else:
logger.debug(
f"Grace period annotation found for {resource.kind} {resource.name} in namespace {resource.namespace}. "
f"The global grace period is shorter, so the downscaler will use the global grace period for this resource."
)
else:
logger.debug(
f"Grace period annotation found for {resource.kind} {resource.name} in namespace {resource.namespace} "
f"but cannot be a negative integer"
)

if deployment_time_annotation:
annotations = resource.metadata.get("annotations", {})
deployment_time = annotations.get(deployment_time_annotation)
Expand All @@ -109,6 +141,30 @@ def within_grace_period_namespace(
):
update_time = parse_time(resource.metadata["creationTimestamp"])

grace_period_annotation = resource.annotations.get(GRACE_PERIOD_ANNOTATION, None)

if grace_period_annotation is not None and is_grace_period_annotation_integer(grace_period_annotation):
grace_period_annotation_integer = int(grace_period_annotation)

if grace_period_annotation_integer > 0:
if grace_period_annotation_integer <= grace_period:
logger.debug(
f"Grace period annotation found for namespace {resource.name}. "
f"Since the grace period specified in the annotation is shorter than the global grace period, "
f"the downscaler will use the annotation's grace period for this resource."
)
grace_period = grace_period_annotation_integer
else:
logger.debug(
f"Grace period annotation found for namespace {resource.name}. "
f"The global grace period is shorter, so the downscaler will use the global grace period for this resource."
)
else:
logger.debug(
f"Grace period annotation found for namespace {resource.name} "
f"but cannot be a negative integer"
)

if deployment_time_annotation:
annotations = resource.metadata.get("annotations", {})
deployment_time = annotations.get(deployment_time_annotation)
Expand Down
42 changes: 42 additions & 0 deletions tests/test_grace_period.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from kube_downscaler.scaler import within_grace_period

ANNOTATION_NAME = "my-deployment-time"
GRACE_PERIOD_ANNOTATION="downscaler/grace-period"


def test_within_grace_period_creation_time():
Expand All @@ -18,6 +19,47 @@ def test_within_grace_period_creation_time():
assert within_grace_period(deploy, 900, now)
assert not within_grace_period(deploy, 180, now)

def test_within_grace_period_override_annotation():
now = datetime.now(timezone.utc)
ts = now - timedelta(minutes=2)
deploy = Deployment(
None,
{
"metadata":
{
"name": "grace-period-test-deployment",
"namespace": "test-namespace",
"creationTimestamp": ts.strftime("%Y-%m-%dT%H:%M:%SZ"),
"annotations": {
GRACE_PERIOD_ANNOTATION: "300"
}
}
}
)

assert within_grace_period(deploy, 900, now)
assert not within_grace_period(deploy, 119, now)
assert within_grace_period(deploy, 123, now)

def test_within_grace_period_override_wrong_annotation_value():
now = datetime.now(timezone.utc)
ts = now - timedelta(minutes=5)
deploy = Deployment(
None,
{
"metadata":
{
"name": "grace-period-test-deployment",
"namespace": "test-namespace",
"creationTimestamp": ts.strftime("%Y-%m-%dT%H:%M:%SZ"),
"annotations": {
GRACE_PERIOD_ANNOTATION: "wrong"
}
}
}
)
assert within_grace_period(deploy, 900, now)
assert not within_grace_period(deploy, 180, now)

def test_within_grace_period_deployment_time_annotation():
now = datetime.now(timezone.utc)
Expand Down