def test_downscale_stack_with_autoscaling():
    stack = Stack(
        None,
        {
            "metadata": {
                "name": "my-stack",
                "namespace": "my-ns",
                "creationTimestamp": "2018-10-23T21:55:00Z",
            },
            "spec": {
                "horizontalPodAutoscaler": {
                    "maxReplicas": 4
                }
            },
        },
    )

    now = datetime.strptime("2018-10-23T21:56:00Z",
                            "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
    assert stack.replicas == 4
    autoscale_resource(
        stack,
        upscale_period="never",
        downscale_period="never",
        default_uptime="never",
        default_downtime="always",
        forced_uptime=False,
        dry_run=True,
        now=now,
    )
    assert stack.replicas == 0
def test_set_annotation():
    api = MagicMock()
    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')
    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'])
    # ensure the original replicas annotation is send to the server
    assert patch_data == {
        "metadata": {
            "name": "foo",
            "creationTimestamp": "2019-03-15T21:55:00Z",
            'annotations': {
                ORIGINAL_REPLICAS_ANNOTATION: '1'
            }
        },
        "spec": {
            "replicas": 0
        }
    }
def test_downtime_replicas_valid(resource):
    resource.replicas = 2
    now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'never', 'always', False, False, now, 0, 1)
    assert resource.replicas == 1
    resource.update.assert_called_once()
def test_set_annotation():
    api = MagicMock()
    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").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"])
    # ensure the original replicas annotation is send to the server
    assert patch_data == {
        "metadata": {
            "name": "foo",
            "creationTimestamp": "2019-03-15T21:55:00Z",
            "annotations": {
                ORIGINAL_REPLICAS_ANNOTATION: "1"
            },
        },
        "spec": {
            "replicas": 0
        },
    }
def test_exclude_until_invalid_time(resource, caplog):
    caplog.set_level(logging.WARNING)
    resource.annotations = {EXCLUDE_UNTIL_ANNOTATION: "some-invalid-timestamp"}
    resource.replicas = 1
    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",
        forced_uptime=False,
        dry_run=True,
        now=now,
    )
    assert resource.replicas == 0
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == "1"
    # dry run will update the object properties, but won't call the Kubernetes API (update)
    resource.update.assert_not_called()

    # check that the warning was logged
    msg = "Invalid annotation value for 'downscaler/exclude-until' on mock/res-1: time data 'some-invalid-timestamp' does not match any format (%Y-%m-%dT%H:%M:%SZ, %Y-%m-%dT%H:%M, %Y-%m-%d %H:%M, %Y-%m-%d)"
    assert caplog.record_tuples == [("kube_downscaler.scaler", logging.WARNING,
                                     msg)]
예제 #6
0
def test_scale_up_downtime_replicas_annotation(resource):
    """Cli argument downtime-replicas is 1, but for 1 specific deployment we want 0.
    """
    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").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
    resource.update.assert_called_once()
def test_upscale_hpa_with_autoscaling():
    hpa = HorizontalPodAutoscaler(
        None,
        {
            "metadata": {
                "name": "my-hpa",
                "namespace": "my-ns",
                "creationTimestamp": "2018-10-23T21:55:00Z",
                "annotations": {
                    DOWNTIME_REPLICAS_ANNOTATION: str(1),
                    ORIGINAL_REPLICAS_ANNOTATION: str(4),
                },
            },
            "spec": {
                "minReplicas": 1
            },
        },
    )
    now = datetime.strptime("2018-10-23T22:15:00Z",
                            "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
    autoscale_resource(
        hpa,
        upscale_period="never",
        downscale_period="never",
        default_uptime="always",
        default_downtime="never",
        forced_uptime=False,
        dry_run=True,
        now=now,
    )
    assert hpa.obj["spec"]["minReplicas"] == 4
    assert hpa.obj["metadata"]["annotations"][
        ORIGINAL_REPLICAS_ANNOTATION] is None
def test_swallow_exception_with_event(monkeypatch, resource, caplog):
    monkeypatch.setattr("kube_downscaler.scaler.helper.add_event",
                        MagicMock(return_value=None))
    caplog.set_level(logging.ERROR)
    resource.annotations = {}
    resource.replicas = 1
    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,
        enable_events=True,
    )
    assert resource.replicas == 1
    resource.update.assert_not_called()
    # check that the failure was logged
    msg = "Failed to process MockResource mock/res-1: time data 'invalid-timestamp!' does not match any format (%Y-%m-%dT%H:%M:%SZ, %Y-%m-%dT%H:%M, %Y-%m-%d %H:%M, %Y-%m-%d)"
    assert caplog.record_tuples == [("kube_downscaler.scaler", logging.ERROR,
                                     msg)]
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')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'never', 'always', False, False, now, 0, 0)
    assert resource.replicas == 2
    resource.update.assert_not_called()
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')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'never', 'always', False, False, now, 0, 0)
    assert resource.replicas == 1
    resource.update.assert_not_called()
    assert ORIGINAL_REPLICAS_ANNOTATION not in resource.annotations
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')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'Mon-Fri 07:30-20:30 Europe/Berlin', 'always',
                       True, False, now, 0, 0)
    assert resource.replicas == 1
    resource.update.assert_not_called()
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')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'never', 'always', False, False, now, 0, 0)
    assert resource.replicas == 0
    resource.update.assert_called_once()
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == '1'
def test_downtime_replicas_valid(resource):
    resource.replicas = 2
    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
    resource.update.assert_called_once()
def test_autoscale_bad_resource():
    now = datetime.strptime("2018-10-23T21:56:00Z",
                            "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
    try:
        autoscale_resource(None, "never", "never", "never", "always", False,
                           False, now, 0, 0)
        raise AssertionError("Failed to error out with a bad resource")
    except Exception:
        pass
def test_downscale_period_not_match(resource):
    resource.annotations = {DOWNTIME_REPLICAS_ANNOTATION: 'x'}
    resource.replicas = 2
    now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ')
    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
    resource.update.assert_not_called()
예제 #16
0
def test_downtime_replicas_invalid(resource):
    resource.replicas = 2
    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
    resource.update.assert_not_called()
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").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
    resource.update.assert_not_called()
예제 #18
0
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').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
    resource.update.assert_called_once()
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == '1'
예제 #19
0
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').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
    resource.update.assert_called_once()
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == '2'
예제 #20
0
def test_downtime_interval():
    resource = MagicMock()
    resource.annotations = {EXCLUDE_ANNOTATION: 'false'}
    resource.get_replicas.return_value = 1
    now = datetime.strptime('2018-10-23T21:56:00Z', '%Y-%m-%dT%H:%M:%SZ')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'Mon-Fri 07:30-20:30 Europe/Berlin', 'always',
                       False, False, now, 0)
    resource.set_replicas.assert_called_once_with(0)
    resource.update.assert_called_once()
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == '1'
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").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
    resource.update.assert_not_called()
    assert ORIGINAL_REPLICAS_ANNOTATION not in resource.annotations
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')
    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
    resource.update.assert_called_once()
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')
    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
    resource.upd
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").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
    resource.update.assert_called_once()
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')
    resource.metadata = {'creationTimestamp': '2018-10-23T21:55:00Z'}
    autoscale_resource(resource, 'Mon-Fri 07:30-20:30 Europe/Berlin', 'never',
                       False, False, now, 0, 0)
    assert resource.replicas == 3
    resource.update.assert_called_once()
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").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
    resource.upd
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')
    resource.metadata = {'creationTimestamp': 'invalid-timestamp!'}
    autoscale_resource(resource, 'never', 'always', False, False, now, 0, 0)
    assert resource.replicas == 1
    resource.update.assert_not_called()
    # check that the failure was logged
    msg = "Failed to process MockResource mock/res-1 : time data 'invalid-timestamp!' does not match format '%Y-%m-%dT%H:%M:%SZ'"
    assert caplog.record_tuples == [('kube_downscaler.scaler', logging.ERROR,
                                     msg)]
예제 #28
0
def test_scale_up_downtime_replicas_annotation(resource):
    """Cli argument downtime-replicas is 1, but for 1 specific deployment we want 0.
    """
    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')
    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
    resource.update.assert_called_once()
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").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
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == "3"
    autoscale_resource(resource, "never", "never", "never", "always", False,
                       False, now, 0, 1)
    assert resource.replicas == 1
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == "3"
    resource.update.assert_called_once()
예제 #30
0
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').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
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == '3'
    autoscale_resource(resource, 'never', 'never', 'never', 'always', False,
                       False, now, 0, 1)
    assert resource.replicas == 1
    assert resource.annotations[ORIGINAL_REPLICAS_ANNOTATION] == '3'
    resource.update.assert_called_once()