Skip to content

Commit

Permalink
New Feature: New Downscaler/Grace-Period Annotation To Override Globa…
Browse files Browse the repository at this point in the history
…l Grace Period for Individual Resources (#74)

* added downscaler/grace-period annotation support

* added grace period annotation unit tests

* refactored documentations

* refactored docs
  • Loading branch information
samuel-esp authored Jul 22, 2024
1 parent c217f31 commit 3dcdd6e
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 1 deletion.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,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 @@ -536,6 +539,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 @@ -39,6 +39,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 @@ -80,6 +81,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 @@ -89,6 +97,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 @@ -110,6 +142,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

0 comments on commit 3dcdd6e

Please sign in to comment.