From 4376ebb060305b7973777f946178239600dfff02 Mon Sep 17 00:00:00 2001 From: Kevin Gessner Date: Wed, 16 Oct 2019 01:52:13 -0400 Subject: [PATCH] use timezone-aware datetime for comparison with absolute timestamps (#73) --- kube_downscaler/scaler.py | 4 +-- tests/test_autoscale_resource.py | 50 ++++++++++++++++---------------- tests/test_grace_period.py | 4 +-- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/kube_downscaler/scaler.py b/kube_downscaler/scaler.py index 4810ea2..54bc5b2 100644 --- a/kube_downscaler/scaler.py +++ b/kube_downscaler/scaler.py @@ -19,7 +19,7 @@ def within_grace_period(deploy, grace_period: int, now: datetime.datetime): - creation_time = datetime.datetime.strptime(deploy.metadata['creationTimestamp'], '%Y-%m-%dT%H:%M:%SZ') + creation_time = datetime.datetime.strptime(deploy.metadata['creationTimestamp'], '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=datetime.timezone.utc) delta = now - creation_time return delta.total_seconds() <= grace_period @@ -159,7 +159,7 @@ def scale(namespace: str, upscale_period: str, downscale_period: str, downtime_replicas: int): api = helper.get_kube_api() - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.timezone.utc) forced_uptime = pods_force_uptime(api, namespace) if 'deployments' in include_resources: diff --git a/tests/test_autoscale_resource.py b/tests/test_autoscale_resource.py index 80029fc..7214145 100644 --- a/tests/test_autoscale_resource.py +++ b/tests/test_autoscale_resource.py @@ -3,7 +3,7 @@ import pytest import logging -from datetime import datetime +from datetime import datetime, timezone from pykube import Deployment from unittest.mock import MagicMock @@ -26,7 +26,7 @@ def test_swallow_exception(resource, caplog): caplog.set_level(logging.ERROR) resource.annotations = {} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': 'invalid-timestamp!'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) assert resource.replicas == 1 @@ -41,7 +41,7 @@ def test_swallow_exception(resource, caplog): def test_exclude(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'true'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) assert resource.replicas == 1 @@ -52,7 +52,7 @@ def test_exclude(resource): def test_dry_run(resource): resource.annotations = {} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, dry_run=True, now=now, grace_period=0, downtime_replicas=0) assert resource.replicas == 0 @@ -64,7 +64,7 @@ def test_dry_run(resource): def test_grace_period(resource): resource.annotations = {} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} # resource was only created 1 minute ago, grace period is 5 minutes autoscale_resource(resource, 'never', 'never', 'never', 'always', False, dry_run=False, now=now, grace_period=300, downtime_replicas=0) @@ -76,7 +76,7 @@ def test_grace_period(resource): def test_downtime_always(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) assert resource.replicas == 0 @@ -87,7 +87,7 @@ def test_downtime_always(resource): def test_downtime_interval(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'Mon-Fri 07:30-20:30 Europe/Berlin', 'always', False, False, now, 0, 0) assert resource.replicas == 0 @@ -98,7 +98,7 @@ def test_downtime_interval(resource): def test_forced_uptime(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'Mon-Fri 07:30-20:30 Europe/Berlin', 'always', True, False, now, 0, 0) assert resource.replicas == 1 @@ -108,7 +108,7 @@ def test_forced_uptime(resource): def test_scale_up(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false', ORIGINAL_REPLICAS_ANNOTATION: "3"} resource.replicas = 0 - now = datetime.strptime('2018-10-23T15:00:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T15:00:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'Mon-Fri 07:30-20:30 Europe/Berlin', 'never', False, False, now, 0, 0) assert resource.replicas == 3 @@ -120,7 +120,7 @@ def test_scale_up_downtime_replicas_annotation(resource): """ resource.annotations = {DOWNTIME_REPLICAS_ANNOTATION: '0', ORIGINAL_REPLICAS_ANNOTATION: "1"} resource.replicas = 0 - now = datetime.strptime('2018-10-23T15:00:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T15:00:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'Mon-Fri 07:30-20:30 Europe/Berlin', 'never', False, False, now, 0, 1) assert resource.replicas == 1 @@ -130,7 +130,7 @@ def test_scale_up_downtime_replicas_annotation(resource): def test_downtime_replicas_annotation_invalid(resource): resource.annotations = {DOWNTIME_REPLICAS_ANNOTATION: 'x'} resource.replicas = 2 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) assert resource.replicas == 2 @@ -140,7 +140,7 @@ def test_downtime_replicas_annotation_invalid(resource): def test_downtime_replicas_annotation_valid(resource): resource.annotations = {DOWNTIME_REPLICAS_ANNOTATION: '1'} resource.replicas = 2 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) assert resource.replicas == 1 @@ -150,7 +150,7 @@ def test_downtime_replicas_annotation_valid(resource): def test_downtime_replicas_invalid(resource): resource.replicas = 2 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, "x") assert resource.replicas == 2 @@ -159,7 +159,7 @@ def test_downtime_replicas_invalid(resource): def test_downtime_replicas_valid(resource): resource.replicas = 2 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 1) assert resource.replicas == 1 @@ -171,7 +171,7 @@ def test_set_annotation(): api.config.namespace = 'myns' resource = pykube.StatefulSet(api, {'metadata': {'name': 'foo', 'creationTimestamp': '2019-03-15T21:55:00Z'}, 'spec': {}}) resource.replicas = 1 - now = datetime.strptime('2019-03-15T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2019-03-15T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) api.patch.assert_called_once() patch_data = json.loads(api.patch.call_args[1]['data']) @@ -183,7 +183,7 @@ def test_set_annotation(): def test_downscale_always(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'always', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 0 @@ -194,7 +194,7 @@ def test_downscale_always(resource): def test_downscale_period(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'Mon-Fri 20:30-24:00 Europe/Berlin', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 0 @@ -205,7 +205,7 @@ def test_downscale_period(resource): def test_downscale_period_overlaps(resource): resource.annotations = {DOWNTIME_REPLICAS_ANNOTATION: '1'} resource.replicas = 2 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'Mon-Fri 20:30-24:00 Europe/Berlin', 'Mon-Fri 20:30-24:00 Europe/Berlin', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 2 @@ -215,7 +215,7 @@ def test_downscale_period_overlaps(resource): def test_downscale_period_not_match(resource): resource.annotations = {DOWNTIME_REPLICAS_ANNOTATION: '1'} resource.replicas = 2 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'Mon-Fri 07:30-10:00 Europe/Berlin', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 2 @@ -225,7 +225,7 @@ def test_downscale_period_not_match(resource): def test_downscale_period_resource_overrides_never(resource): resource.annotations = {DOWNSCALE_PERIOD_ANNOTATION: 'Mon-Fri 20:30-24:00 Europe/Berlin'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 0 @@ -235,7 +235,7 @@ def test_downscale_period_resource_overrides_never(resource): def test_downscale_period_resource_overrides_namespace(resource): resource.annotations = {DOWNSCALE_PERIOD_ANNOTATION: 'Mon-Fri 20:30-24:00 Europe/Berlin'} resource.replicas = 1 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'Mon-Fri 22:00-24:00 Europe/Berlin', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 0 @@ -245,7 +245,7 @@ def test_downscale_period_resource_overrides_namespace(resource): def test_upscale_period_resource_overrides_never(resource): resource.annotations = {UPSCALE_PERIOD_ANNOTATION: 'Mon-Fri 20:30-24:00 Europe/Berlin', ORIGINAL_REPLICAS_ANNOTATION: 1} resource.replicas = 0 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 1 @@ -255,7 +255,7 @@ def test_upscale_period_resource_overrides_never(resource): def test_upscale_period_resource_overrides_namespace(resource): resource.annotations = {UPSCALE_PERIOD_ANNOTATION: 'Mon-Fri 20:30-24:00 Europe/Berlin', ORIGINAL_REPLICAS_ANNOTATION: 1} resource.replicas = 0 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'Mon-Fri 22:00-24:00 Europe/Berlin', 'never', 'always', 'never', False, False, now, 0, 0) assert resource.replicas == 1 @@ -277,7 +277,7 @@ def test_downscale_stack_deployment_ignored(): resource.replicas = 1 resource.annotations = {} - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 0) assert resource.replicas == 1 resource.update.assert_not_called() @@ -287,7 +287,7 @@ def test_downscale_stack_deployment_ignored(): def test_downscale_replicas_not_zero(resource): resource.annotations = {EXCLUDE_ANNOTATION: 'false'} resource.replicas = 3 - now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ') + now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc) resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'} autoscale_resource(resource, 'never', 'never', 'never', 'always', False, False, now, 0, 1) assert resource.replicas == 1 diff --git a/tests/test_grace_period.py b/tests/test_grace_period.py index e0da62f..6b5fd72 100644 --- a/tests/test_grace_period.py +++ b/tests/test_grace_period.py @@ -1,10 +1,10 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from pykube import Deployment from kube_downscaler.scaler import within_grace_period def test_within_grace_period(): - now = datetime.utcnow() + now = datetime.now(timezone.utc) ts = now - timedelta(minutes=5) deploy = Deployment(None, {'metadata': {'creationTimestamp': ts.strftime('%Y-%m-%dT%H:%M:%SZ')}}) assert within_grace_period(deploy, 900, now)