diff --git a/README.md b/README.md
index a9cc95e..edcd5af 100644
--- a/README.md
+++ b/README.md
@@ -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`
@@ -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
+**Important:**
+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
+
+**Important:**
+
**Deleting Policies:** if for some reason you want to delete all resources blocking jobs, you can use these commands:
Gatekeeper
diff --git a/kube_downscaler/scaler.py b/kube_downscaler/scaler.py
index 3ee5f06..5a37b8f 100644
--- a/kube_downscaler/scaler.py
+++ b/kube_downscaler/scaler.py
@@ -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,
@@ -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,
@@ -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)
@@ -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)
diff --git a/tests/test_grace_period.py b/tests/test_grace_period.py
index 0df728c..0b24ede 100644
--- a/tests/test_grace_period.py
+++ b/tests/test_grace_period.py
@@ -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():
@@ -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)