Skip to content

Commit

Permalink
Merge pull request #11 from citizensadvice/add-tests
Browse files Browse the repository at this point in the history
Add tests
  • Loading branch information
michelesr authored Apr 17, 2024
2 parents ae62e3b + 519d0be commit 73cbd39
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,13 @@ $ kubectl logs -n kube-schedule-scaler kube-schedule-scaler-844b6d5888-p9tc4 | g
```

You can change the log level using the `LOG_LEVEL` environment variable (e.g. `LOG_LEVEL=DEBUG`)

## Testing

To run the tests you will need:

- kube-schedule-scaler installed in a working k8s cluster (e.g. Minikube)
- pytest (can be installed with pip, in a virtualenv for example)

The tests will create some deployment and hpa resources with the proper annotation and verify that they are scaled properly.
If you want to test your local build, you can [run it in Minikube](https://stackoverflow.com/a/42564211).
105 changes: 105 additions & 0 deletions tests/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: test-kube-schedule-scaler
labels:
name: test-kube-schedule-scaler
product: test
component: kube-schedule-scaler
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
namespace: test-kube-schedule-scaler
labels:
app: test-kube-schedule-scaler
product: test
component: sleep
spec:
selector:
matchLabels:
component: sleep
replicas: 1
template:
metadata:
labels:
app: test-kube-schedule-scaler
product: test
component: sleep
spec:
terminationGracePeriodSeconds: 1
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody
runAsGroup: 65534
containers:
- name: sleep
image: 979633842206.dkr.ecr.eu-west-1.amazonaws.com/docker-hub/library/busybox:1.33.1
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 16Mi
limits:
cpu: 100m
memory: 32Mi
command: ['sleep', 'infinity']
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep-with-autoscaling
namespace: test-kube-schedule-scaler
labels:
app: test-kube-schedule-scaler
product: test
component: sleep-with-autoscaling
spec:
selector:
matchLabels:
component: sleep-with-autoscaling
replicas: 1
template:
metadata:
labels:
app: test-kube-schedule-scaler
product: test
component: sleep-with-autoscaling
spec:
terminationGracePeriodSeconds: 1
securityContext:
runAsNonRoot: true
runAsUser: 65534 # nobody
runAsGroup: 65534
containers:
- name: sleep
image: 979633842206.dkr.ecr.eu-west-1.amazonaws.com/docker-hub/library/busybox:1.33.1
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 16Mi
limits:
cpu: 100m
memory: 32Mi
command: ['sleep', 'infinity']
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: sleep-with-autoscaling
namespace: test-kube-schedule-scaler
labels:
app: test-kube-schedule-scaler
product: test
component: sleep-with-autoscaling
spec:
minReplicas: 1
maxReplicas: 2
targetCPUUtilizationPercentage: 75
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: sleep-with-autoscaling
127 changes: 127 additions & 0 deletions tests/test_kube_schedule_scaler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
from collections.abc import Sequence
import datetime
import json
from os import path
import subprocess
from time import sleep


def run(args: Sequence[str]) -> None:
"""Runs a subprocess and asserts that the exit code is 0"""
assert subprocess.call(args) == 0


def setup_module(module):
base_dir = path.dirname(path.realpath(__file__))
# deploy the manifest
run(["kubectl", "apply", "-f", f"{base_dir}/manifest.yml"])
# wait for resources to be ready
sleep(20)


def teardown_module(module):
base_dir = path.dirname(path.realpath(__file__))
# delete the resources
run(["kubectl", "delete", "-f", f"{base_dir}/manifest.yml"])


def get_cron_formatted_time():
'''Return the cron expr prefix for the next minute, e.g. "43 14"'''
now = datetime.datetime.now(datetime.UTC)

# get the minute and hour of the next minute from now
return (now + datetime.timedelta(minutes=1)).strftime("%M %H")


def get_json_patch(schedule_actions):
"""Return the JSON string used to patch the Deployment objects"""
return json.dumps(
{
"metadata": {
"annotations": {
# schedule action must be a JSON string, so we encode it here
"zalando.org/schedule-actions": json.dumps(schedule_actions)
}
}
}
)


def patch_deployment(deployment_name, patch):
"""Inject the scheduler annotation in the target Deployment"""
res = subprocess.run(
[
"kubectl",
"patch",
"-n",
"test-kube-schedule-scaler",
"deployment",
deployment_name,
"-p",
patch,
]
)
assert res.returncode == 0


def test_deployment_scaling():
"""Check that schedule actions are applied to the Deployment object"""
t = get_cron_formatted_time()
schedule_actions = [{"schedule": f"{t} * * *", "replicas": "2"}]
patch = get_json_patch(schedule_actions)
patch_deployment("sleep", patch)

# wait a minute
sleep(60)

# check the deployment number of replicas
res = subprocess.check_output(
[
"kubectl",
"get",
"-n",
"test-kube-schedule-scaler",
"deployment",
"sleep",
"-o",
"json",
]
)

data = json.loads(res)

# check the number of replicas
assert data["spec"]["replicas"] == 2


def test_hpa_scaling():
"""Check that schedule actions are applied to the relevant HPA for the Deployment object"""
t = get_cron_formatted_time()
schedule_actions = [
{"schedule": f"{t} * * *", "minReplicas": "2", "maxReplicas": "3"}
]
patch = get_json_patch(schedule_actions)
patch_deployment("sleep-with-autoscaling", patch)

# wait a minute
sleep(60)

# check the deployment number of replicas
res = subprocess.check_output(
[
"kubectl",
"get",
"-n",
"test-kube-schedule-scaler",
"hpa",
"sleep-with-autoscaling",
"-o",
"json",
]
)

data = json.loads(res)

# check the number of replicas
assert data["spec"]["minReplicas"] == 2
assert data["spec"]["maxReplicas"] == 3

0 comments on commit 73cbd39

Please sign in to comment.