Skip to content

Commit

Permalink
Update schedule crd to report progress
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnGarbutt committed Mar 12, 2024
1 parent 0ec97b8 commit c67caca
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 21 deletions.
8 changes: 4 additions & 4 deletions azimuth_schedule_operator/models/v1alpha1/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ class ScheduleStatus(schema.BaseModel):
ref_found: schema.Optional[bool] = False
# updated when delete has been triggered
delete_triggered: schema.Optional[bool] = False
updatedAt: schema.Optional[datetime.datetime] = None
updated_at: schema.Optional[datetime.datetime] = None


class ScheduleRef(schema.BaseModel):
apiVersion: str
api_version: str
kind: str
name: str


class ScheduleSpec(schema.BaseModel):
ref: ScheduleRef
notBefore: datetime.datetime
notAfter: datetime.datetime
not_before: datetime.datetime
not_after: datetime.datetime


class Schedule(
Expand Down
47 changes: 38 additions & 9 deletions azimuth_schedule_operator/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,29 +64,58 @@ async def delete_reference(namespace: str, ref: schedule_crd.ScheduleRef):
await resource.delete(ref.name, namespace=namespace)


async def delete_after_delay(delay_seconds, namespace, ref: schedule_crd.ScheduleRef):
LOG.info(f"Delete of {ref} will be executed after {delay_seconds} seconds")
async def delete_after_delay(delay_seconds, namespace, schedule: schedule_crd.Schedule):
LOG.info(
f"Delete of {schedule.metadata.name} will be executed "
f"after {delay_seconds} seconds"
)
await asyncio.sleep(delay_seconds)

await delete_reference(namespace, ref)
# TODO(johngarbutt): update crd to say delete has triggered
LOG.info(f"Delete complete for {namespace} and {ref}.")
await delete_reference(namespace, schedule.spec.ref)
await update_schedule(schedule.metadata.name, namespace, delete_triggered=True)
LOG.info(f"Delete complete for {namespace} and {schedule.metadata.name}.")


async def schedule_delete_task(memo, namespace, schedule):
async def schedule_delete_task(memo, namespace, schedule: schedule_crd.Schedule):
if memo.get("delete_task"):
# TODO(johngarbutt): maybe we don't always need to cancel?
memo["delete_task"].cancel()

time_to_delete = datetime.timedelta(minutes=15)
scheduled_at = schedule.spec.notBefore - time_to_delete
scheduled_at = schedule.spec.not_before - time_to_delete
delay_seconds = (scheduled_at - datetime.datetime.now()).total_seconds()
if delay_seconds < 0:
delay_seconds = 0
memo["delete_scheduled_at"] = scheduled_at
memo["delete_scheduled_ref"] = schedule.spec.ref
memo["delete_task"] = asyncio.create_task(
delete_after_delay(delay_seconds, namespace, schedule.spec.ref)
delete_after_delay(delay_seconds, namespace, schedule)
)


async def update_schedule(
name: str,
namespace: str,
ref_found: bool = None,
delete_triggered: bool = None,
):
now = datetime.datetime.utcnow()
now_string = now.strftime("%Y-%m-%dT%H:%M:%SZ")
status_updates = dict(updated_at=now_string)

if ref_found is not None:
status_updates["ref_found"] = ref_found
if delete_triggered is not None:
status_updates["delete_triggered"] = delete_triggered

status_resource = await K8S_CLIENT.api(registry.API_VERSION).resource(
"schedule/status"
)
LOG.info(f"patching {name} in {namespace} with: {status_updates}")
await status_resource.patch(
name,
dict(status=status_updates),
namespace=namespace,
)


Expand All @@ -99,7 +128,7 @@ async def schedule_changed(memo: kopf.Memo, body, namespace, **_):
# check we can get the object we are supposed to be managing
object = await get_reference(namespace, schedule.spec.ref)
LOG.info(f"object found {object}")
# TODO(johngarbutt): update object to show we have found the reference
await update_schedule(schedule.metadata.name, namespace, ref_found=True)
# TODO(johngarbutt): maybe check we have an owner relationship?

await schedule_delete_task(memo, namespace, schedule)
24 changes: 17 additions & 7 deletions azimuth_schedule_operator/tests/test_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ async def test_cleanup_calls_aclose(self, mock_client):
await operator.cleanup()
mock_client.aclose.assert_awaited_once_with()

@mock.patch.object(operator, "update_schedule")
@mock.patch.object(operator, "schedule_delete_task")
@mock.patch.object(operator, "get_reference")
async def test_schedule_changed(
self, mock_get_reference, mock_schedule_delete_task
self, mock_get_reference, mock_schedule_delete_task, mock_update_schedule
):
memo = kopf.Memo()
body = schedule_crd.get_fake_dict()
Expand All @@ -67,6 +68,9 @@ async def test_schedule_changed(
# Assert the expected behavior
mock_get_reference.assert_awaited_once_with(namespace, fake.spec.ref)
mock_schedule_delete_task.assert_awaited_once_with(memo, namespace, mock.ANY)
mock_update_schedule.assert_awaited_once_with(
fake.metadata.name, namespace, ref_found=True
)

@mock.patch.object(asyncio, "create_task")
@mock.patch.object(operator, "delete_after_delay", new_callable=mock.Mock)
Expand All @@ -81,23 +85,29 @@ async def test_schedule_delete_task(
await operator.schedule_delete_task(memo, namespace, schedule)

# Assert the expected behavior
mock_delete_after_delay.assert_called_once_with(0, namespace, schedule.spec.ref)
mock_delete_after_delay.assert_called_once_with(0, namespace, schedule)
self.assertEqual(
memo["delete_scheduled_at"],
schedule.spec.notBefore - datetime.timedelta(minutes=15),
schedule.spec.not_before - datetime.timedelta(minutes=15),
)
self.assertEqual(memo["delete_scheduled_ref"], schedule.spec.ref)
mock_create_task.assert_called_once_with(mock.ANY)
self.assertEqual(memo["delete_task"], "sentinel")

@mock.patch.object(operator, "update_schedule")
@mock.patch.object(asyncio, "sleep")
@mock.patch.object(operator, "delete_reference")
async def test_delete_after_delay(self, mock_delete_reference, mock_sleep):
async def test_delete_after_delay(
self, mock_delete_reference, mock_sleep, mock_update_schedule
):
delay_seconds = 10
namespace = "ns1"
ref = schedule_crd.get_fake().spec.ref
schedule = schedule_crd.get_fake()

await operator.delete_after_delay(delay_seconds, namespace, ref)
await operator.delete_after_delay(delay_seconds, namespace, schedule)

mock_delete_reference.assert_awaited_once_with(namespace, ref)
mock_delete_reference.assert_awaited_once_with(namespace, schedule.spec.ref)
mock_sleep.assert_awaited_once_with(delay_seconds)
mock_update_schedule.assert_awaited_once_with(
schedule.metadata.name, namespace, delete_triggered=True
)
6 changes: 5 additions & 1 deletion tools/functional_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@ export BEFORE=$(date --date="-2 hour" +"%Y-%m-%dT%H:%M:%SZ")
export AFTER=$(date --date="-1 hour" +"%Y-%m-%dT%H:%M:%SZ")
envsubst < $SCRIPT_DIR/test_schedule.yaml | kubectl apply -f -

# until kubectl wait --for=jsonpath='{.status.phase}'=Available clustertype quick-test; do echo "wait for status to appear"; sleep 5; done
kubectl get schedule caas-mycluster -o yaml
# until kubectl wait --for=jsonpath='{.status.refFound}'=true schedule caas-mycluster; do echo "wait for status to appear"; sleep 5; done
# kubectl get schedule caas-mycluster -o yaml

# for debugging get the logs from the operator
kubectl logs -n azimuth-schedule-operator deployment/azimuth-schedule-operator

0 comments on commit c67caca

Please sign in to comment.