Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix lifespan end check for detached ResourceClaim #104

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/redhat-cop/python-kopf-s2i:v1.36
FROM quay.io/redhat-cop/python-kopf-s2i:v1.37

USER root

Expand Down
2 changes: 1 addition & 1 deletion build-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ parameters:
- name: GIT_REF
value: main
- name: KOPF_S2I_IMAGE
value: quay.io/redhat-cop/python-kopf-s2i:v1.36
value: quay.io/redhat-cop/python-kopf-s2i:v1.37
- name: PYTHON_S2I_IMAGE
value: registry.access.redhat.com/ubi8/python-38:latest

Expand Down
2 changes: 1 addition & 1 deletion devfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ components:
value: "5"
- name: OPERATOR_DOMAIN
value: poolboy.dev.local
image: quay.io/redhat-cop/python-kopf-s2i:v1.36
image: quay.io/redhat-cop/python-kopf-s2i:v1.37
mountSources: true
sourceMapping: /tmp/projects
name: s2i-builder
Expand Down
4 changes: 2 additions & 2 deletions operator/poolboy_templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
class TimeStamp(object):
def __init__(self, set_datetime=None):
if not set_datetime:
self.datetime = datetime.utcnow()
self.datetime = datetime.now(timezone.utc)
elif isinstance(set_datetime, datetime):
self.datetime = set_datetime
elif isinstance(set_datetime, str):
self.datetime = datetime.strptime(set_datetime, "%Y-%m-%dT%H:%M:%SZ")
self.datetime = datetime.strptime(set_datetime, "%Y-%m-%dT%H:%M:%S%z")
else:
self.datetime = set_datetime

Expand Down
20 changes: 10 additions & 10 deletions operator/resourceclaim.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import kubernetes_asyncio

from copy import deepcopy
from datetime import datetime
from datetime import datetime, timezone
from typing import List, Mapping, Optional, TypeVar, Union

from deep_merge import deep_merge
Expand Down Expand Up @@ -148,7 +148,7 @@ def claim_is_initialized(self) -> bool:

@property
def creation_datetime(self):
return datetime.strptime(self.creation_timestamp, "%Y-%m-%dT%H:%H:%SZ")
return datetime.strptime(self.creation_timestamp, "%Y-%m-%dT%H:%H:%S%z")

@property
def creation_timestamp(self) -> str:
Expand Down Expand Up @@ -205,7 +205,7 @@ def lifespan_end_datetime(self) -> Optional[datetime]:
"""
timestamp = self.lifespan_end_timestamp
if timestamp:
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S%z")

@property
def lifespan_end_timestamp(self) -> Optional[str]:
Expand All @@ -232,7 +232,7 @@ def lifespan_relative_maximum(self) -> Optional[str]:
def lifespan_start_datetime(self) -> Optional[datetime]:
timestamp = self.lifespan_start_timestamp
if timestamp:
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S%z")

@property
def lifespan_start_timestamp(self) -> Optional[str]:
Expand All @@ -257,7 +257,7 @@ def provider_name(self) -> Optional[str]:
def requested_lifespan_end_datetime(self):
timestamp = self.requested_lifespan_end_timestamp
if timestamp:
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S%z")

@property
def requested_lifespan_end_timestamp(self) -> Optional[str]:
Expand All @@ -269,7 +269,7 @@ def requested_lifespan_end_timestamp(self) -> Optional[str]:
def requested_lifespan_start_datetime(self):
timestamp = self.requested_lifespan_start_timestamp
if timestamp:
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S%z")

@property
def requested_lifespan_start_timestamp(self) -> Optional[str]:
Expand Down Expand Up @@ -366,7 +366,7 @@ async def bind_resource_handle(self,
}]

lifespan_value = {
"start": datetime.utcnow().strftime('%FT%TZ'),
"start": datetime.now(timezone.utc).strftime('%FT%TZ'),
"end": resource_handle.lifespan_end_timestamp,
"maximum": resource_handle.get_lifespan_maximum(resource_claim=self),
"relativeMaximum": resource_handle.get_lifespan_relative_maximum(resource_claim=self),
Expand Down Expand Up @@ -626,7 +626,7 @@ async def initialize_claim(self, logger):
patch = {
"metadata": {
"annotations": {
f"{Poolboy.operator_domain}/resource-claim-init-timestamp": datetime.utcnow().strftime('%FT%TZ')
f"{Poolboy.operator_domain}/resource-claim-init-timestamp": datetime.now(timezone.utc).strftime('%FT%TZ')
}
},
}
Expand Down Expand Up @@ -667,14 +667,14 @@ async def json_patch_status(self, patch: List[Mapping]) -> None:
async def manage(self, logger) -> None:
async with self.lock:
if self.lifespan_start_datetime \
and self.lifespan_start_datetime > datetime.utcnow():
and self.lifespan_start_datetime > datetime.now(timezone.utc):
return

if self.is_detached:
# Normally lifespan end is tracked by the ResourceHandle.
# Detached ResourceClaims have no handle.
if self.lifespan_end_datetime \
and self.lifespan_start_datetime < datetime.utcnow():
and self.lifespan_end_datetime < datetime.now(timezone.utc):
logger.info(f"Deleting detacthed {self} at end of lifespan")
await self.delete()
# No further processing for detached ResourceClaim
Expand Down
24 changes: 12 additions & 12 deletions operator/resourcehandle.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytimeparse

from copy import deepcopy
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Any, List, Mapping, Optional, TypeVar, Union

import poolboy_k8s
Expand Down Expand Up @@ -164,7 +164,7 @@ async def bind_handle_to_claim(
"op": "add",
"path": "/spec/lifespan/end",
"value": (
datetime.utcnow() + matched_resource_handle.get_lifespan_default_timedelta(resource_claim)
datetime.now(timezone.utc) + matched_resource_handle.get_lifespan_default_timedelta(resource_claim)
).strftime('%FT%TZ'),
})

Expand Down Expand Up @@ -262,7 +262,7 @@ async def create_for_claim(


lifespan_end_datetime = None
lifespan_start_datetime = datetime.utcnow()
lifespan_start_datetime = datetime.now(timezone.utc)
requested_lifespan_end_datetime = resource_claim.requested_lifespan_end_datetime
if requested_lifespan_end_datetime:
lifespan_end_datetime = requested_lifespan_end_datetime
Expand Down Expand Up @@ -350,7 +350,7 @@ async def create_for_pool(
definition['spec']['lifespan']['relativeMaximum'] = resource_pool.lifespan_relative_maximum
if resource_pool.lifespan_unclaimed:
definition['spec']['lifespan']['end'] = (
datetime.utcnow() + resource_pool.lifespan_unclaimed_timedelta
datetime.now(timezone.utc) + resource_pool.lifespan_unclaimed_timedelta
).strftime("%FT%TZ")

definition = await Poolboy.custom_objects_api.create_namespaced_custom_object(
Expand Down Expand Up @@ -524,7 +524,7 @@ def __unregister(self) -> None:

@property
def creation_datetime(self):
return datetime.strptime(self.creation_timestamp, "%Y-%m-%dT%H:%M:%SZ")
return datetime.strptime(self.creation_timestamp, "%Y-%m-%dT%H:%M:%S%z")

@property
def creation_timestamp(self) -> str:
Expand Down Expand Up @@ -570,13 +570,13 @@ def is_past_lifespan_end(self) -> bool:
dt = self.lifespan_end_datetime
if not dt:
return False
return dt < datetime.utcnow()
return dt < datetime.now(timezone.utc)

@property
def lifespan_end_datetime(self) -> Any:
timestamp = self.lifespan_end_timestamp
if timestamp:
return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%SZ')
return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S%z')

@property
def lifespan_end_timestamp(self) -> Optional[str]:
Expand Down Expand Up @@ -627,7 +627,7 @@ def vars(self) -> Mapping:
def timedelta_to_lifespan_end(self) -> Any:
dt = self.lifespan_end_datetime
if dt:
return dt - datetime.utcnow()
return dt - datetime.now(timezone.utc)

def __lifespan_value(self, name, resource_claim):
value = self.spec.get('lifespan', {}).get(name)
Expand Down Expand Up @@ -679,7 +679,7 @@ def get_lifespan_end_maximum_datetime(self, resource_claim=None):
maximum_timedelta = self.get_lifespan_maximum_timedelta(resource_claim=resource_claim)
maximum_end = lifespan_start_datetime + maximum_timedelta if maximum_timedelta else None
relative_maximum_timedelta = self.get_lifespan_relative_maximum_timedelta(resource_claim=resource_claim)
relative_maximum_end = datetime.utcnow() + relative_maximum_timedelta if relative_maximum_timedelta else None
relative_maximum_end = datetime.now(timezone.utc) + relative_maximum_timedelta if relative_maximum_timedelta else None

if relative_maximum_end \
and (not maximum_end or relative_maximum_end < maximum_end):
Expand Down Expand Up @@ -752,7 +752,7 @@ async def get_resource_states(self, logger: kopf.ObjectLogger) -> List[Mapping]:
elif (
self.resource_states[i] and
self.resource_refresh_datetime[i] and
(datetime.utcnow() - self.resource_refresh_datetime[i]).total_seconds() > Poolboy.resource_refresh_interval
(datetime.now(timezone.utc) - self.resource_refresh_datetime[i]).total_seconds() > Poolboy.resource_refresh_interval
):
continue

Expand All @@ -771,7 +771,7 @@ async def get_resource_states(self, logger: kopf.ObjectLogger) -> List[Mapping]:
name = name,
namespace = namespace,
)
self.resource_refresh_datetime[i] = datetime.utcnow()
self.resource_refresh_datetime[i] = datetime.now(timezone.utc)
except kubernetes_asyncio.client.exceptions.ApiException as e:
if e.status == 404:
_name = f"{name} in {namespace}" if namespace else name
Expand Down Expand Up @@ -832,7 +832,7 @@ async def handle_resource_event(self,
[None] * (1 + resource_index - len(self.resource_refresh_datetime))
)
self.resource_states[resource_index] = resource_state
self.resource_refresh_datetime[resource_index] = datetime.utcnow()
self.resource_refresh_datetime[resource_index] = datetime.now(timezone.utc)

async def manage(self, logger: kopf.ObjectLogger) -> None:
async with self.lock:
Expand Down
10 changes: 5 additions & 5 deletions operator/resourcewatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import kubernetes_asyncio
import logging

from datetime import datetime
from datetime import datetime, timezone
from typing import Mapping, Optional

import poolboy_k8s
Expand Down Expand Up @@ -105,24 +105,24 @@ async def watch(self):
kwargs = {}

while True:
watch_start_dt = datetime.utcnow()
watch_start_dt = datetime.now(timezone.utc)
try:
await self.__watch(method, **kwargs)
except asyncio.CancelledError:
return
except ResourceWatchRestartError as e:
logger.info(f"{self} restart: {e}")
watch_duration = (datetime.utcnow() - watch_start_dt).total_seconds()
watch_duration = (datetime.now(timezone.utc) - watch_start_dt).total_seconds()
if watch_duration < 10:
await asyncio.sleep(10 - watch_duration)
except ResourceWatchFailedError as e:
logger.warning(f"{self} failed: {e}")
watch_duration = (datetime.utcnow() - watch_start_dt).total_seconds()
watch_duration = (datetime.now(timezone.utc) - watch_start_dt).total_seconds()
if watch_duration < 60:
await asyncio.sleep(60 - watch_duration)
except Exception as e:
logger.exception(f"{self} exception")
watch_duration = (datetime.utcnow() - watch_start_dt).total_seconds()
watch_duration = (datetime.now(timezone.utc) - watch_start_dt).total_seconds()
if watch_duration < 60:
await asyncio.sleep(60 - watch_duration)
logger.info(f"Restarting {self}")
Expand Down
10 changes: 5 additions & 5 deletions test/roles/poolboy_test_simple/tasks/test-02.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@
- __state.status.lifespan.relativeMaximum == '1h'
- >-
30 * 60 == (
__state.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') -
__state.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')
__state.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') -
__state.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')
).total_seconds()

- name: Verify creation of ResourceClaimTest test-02-a
Expand Down Expand Up @@ -145,7 +145,7 @@
lifespan:
end: >-
{{ "%FT%TZ" | strftime(
(__state.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')).strftime("%s") | int + 45 * 60
(__state.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')).strftime("%s") | int + 45 * 60
)
}}

Expand All @@ -160,8 +160,8 @@
__state: "{{ r_get_resource_claim.resources[0] }}"
failed_when: >-
45 * 60 != (
__state.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') -
__state.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')
__state.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') -
__state.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')
).total_seconds()
until: r_get_resource_claim is success
delay: 1
Expand Down
4 changes: 2 additions & 2 deletions test/roles/poolboy_test_simple/tasks/test-pool-02.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@
that:
- >-
6 * 60 * 60 == (
__state.spec.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') -
__state.metadata.creationTimestamp | to_datetime('%Y-%m-%dT%H:%M:%SZ')
__state.spec.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') -
__state.metadata.creationTimestamp | to_datetime('%Y-%m-%dT%H:%M:%S%z')
).total_seconds()

- name: Create ResourceClaim test-pool-02
Expand Down
28 changes: 16 additions & 12 deletions test/roles/poolboy_test_simple/tasks/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@
assert:
that:
- resource.kind == 'ResourceClaimTest'
- resource_annotations['{{ poolboy_domain }}/resource-claim-name'] == 'test-1'
- resource_annotations['{{ poolboy_domain }}/resource-claim-namespace'] == poolboy_test_namespace
- resource_annotations['{{ poolboy_domain }}/resource-handle-name'] == handle.metadata.name
- resource_annotations['{{ poolboy_domain }}/resource-handle-namespace'] == handle.metadata.namespace
- resource_annotations['{{ poolboy_domain }}/resource-handle-uid'] == handle.metadata.uid
- resource_annotations['{{ poolboy_domain }}/resource-provider-name'] == 'test'
- resource_annotations['{{ poolboy_domain }}/resource-provider-namespace'] == poolboy_namespace
- resource_annotations[poolboy_domain ~ '/resource-claim-name'] == 'test-1'
- resource_annotations[poolboy_domain ~ '/resource-claim-namespace'] == poolboy_test_namespace
- resource_annotations[poolboy_domain ~ '/resource-handle-name'] == handle.metadata.name
- resource_annotations[poolboy_domain ~ '/resource-handle-namespace'] == handle.metadata.namespace
- resource_annotations[poolboy_domain ~ '/resource-handle-uid'] == handle.metadata.uid
- resource_annotations[poolboy_domain ~ '/resource-provider-name'] == 'test'
- resource_annotations[poolboy_domain ~ '/resource-provider-namespace'] == poolboy_namespace
fail_msg: test-1 resource not found as expected
success_msg: test-1 resource looks good
vars:
Expand Down Expand Up @@ -488,6 +488,10 @@
set_fact:
first_pool_resource_handle_name: "{{ r_get_pool_resource_handle.resources[0].metadata.name }}"

- name: Report first pool resource handle name
debug:
msg: ResourceClaim should bind {{ first_pool_resource_handle_name }}

- name: Scale up resource pool
kubernetes.core.k8s:
api_version: "{{ poolboy_domain }}/v1"
Expand Down Expand Up @@ -812,7 +816,7 @@
__test_lifespan_1.status.lifespan.start is undefined or
__test_lifespan_1.status.lifespan.end is undefined or
(
(__test_lifespan_1.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') - __test_lifespan_1.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')).total_seconds() - 40
(__test_lifespan_1.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') - __test_lifespan_1.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')).total_seconds() - 40
) | abs > 1
until: r_get_test_lifespan_1 is success
delay: 5
Expand Down Expand Up @@ -864,7 +868,7 @@
failed_when: >-
__test_lifespan_2.status.lifespan.start is undefined or
__test_lifespan_2.status.lifespan.end is undefined or
40 != (__test_lifespan_2.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') - __test_lifespan_2.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')).total_seconds()
40 != (__test_lifespan_2.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') - __test_lifespan_2.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')).total_seconds()
until: r_get_test_lifespan_2 is success
delay: 5
retries: 10
Expand Down Expand Up @@ -897,7 +901,7 @@
failed_when: >-
__test_lifespan_2.status.lifespan.start is undefined or
__test_lifespan_2.status.lifespan.end is undefined or
50 != (__test_lifespan_2.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') - __test_lifespan_2.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')).total_seconds()
50 != (__test_lifespan_2.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') - __test_lifespan_2.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')).total_seconds()
until: r_get_test_lifespan_2 is success
delay: 5
retries: 10
Expand Down Expand Up @@ -936,7 +940,7 @@
__handle.spec.lifespan.default != '23s' or
__handle.spec.lifespan.maximum != '2h' or
__handle.spec.lifespan.relativeMaximum != '1h' or
((__handle.spec.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') - __handle.metadata.creationTimestamp | to_datetime('%Y-%m-%dT%H:%M:%SZ')).total_seconds() - 3600) | abs > 2
((__handle.spec.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') - __handle.metadata.creationTimestamp | to_datetime('%Y-%m-%dT%H:%M:%S%z')).total_seconds() - 3600) | abs > 2
until: r_get_pool_resource_handle is success
retries: 10
delay: 1
Expand Down Expand Up @@ -974,7 +978,7 @@
failed_when: >-
__test_lifespan_3.status.lifespan.start is undefined or
__test_lifespan_3.status.lifespan.end is undefined or
23 != (__test_lifespan_3.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%SZ') - __test_lifespan_3.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%SZ')).total_seconds()
23 != (__test_lifespan_3.status.lifespan.end | to_datetime('%Y-%m-%dT%H:%M:%S%z') - __test_lifespan_3.status.lifespan.start | to_datetime('%Y-%m-%dT%H:%M:%S%z')).total_seconds()
until: r_get_test_lifespan_3 is success
delay: 5
retries: 10
Expand Down
Loading