This repository has been archived by the owner on Sep 14, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #230 from nolar/no-resumes-repeated
Prevent repeated resumes of a resource by remembering its resumed flag
- Loading branch information
Showing
16 changed files
with
274 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
""" | ||
A in-memory storage of arbitrary information per resource/object. | ||
The information is stored strictly in-memory and is not persistent. | ||
On the operator restart, all the memories are lost. | ||
It is used internally to track allocated system resources for each Kubernetes | ||
object, even if that object does not show up in the event streams for long time. | ||
""" | ||
import dataclasses | ||
from typing import MutableMapping | ||
|
||
from kopf.structs import bodies | ||
|
||
|
||
@dataclasses.dataclass(frozen=False) | ||
class ResourceMemory: | ||
""" A memo about a single resource/object. Usually stored in `Memories`. """ | ||
noticed_by_listing: bool = False | ||
fully_handled_once: bool = False | ||
|
||
|
||
class ResourceMemories: | ||
""" | ||
A container of all memos about every existing resource in a single operator. | ||
Distinct operator tasks have their own memory containers, which | ||
do not overlap. This solves the problem if storing the per-resource | ||
entries in the global or context variables. | ||
The memos can store anything the resource handlers need to persist within | ||
a single process/operator lifetime, but not persisted on the resource. | ||
For example, the runtime system resources: flags, threads, tasks, etc. | ||
Or the scalar values, which have meaning only for this operator process. | ||
The container is relatively async-safe: one individual resource is always | ||
handled sequentially, never in parallel with itself (different resources | ||
are handled in parallel through), so the same key will not be added/deleted | ||
in the background during the operation, so the locking is not needed. | ||
""" | ||
_items: MutableMapping[str, ResourceMemory] | ||
|
||
def __init__(self) -> None: | ||
super().__init__() | ||
self._items = {} | ||
|
||
async def recall( | ||
self, | ||
body: bodies.Body, | ||
*, | ||
noticed_by_listing: bool = False, | ||
) -> ResourceMemory: | ||
""" | ||
Either find a resource's memory, or create and remember a new one. | ||
""" | ||
key = self._build_key(body) | ||
if key not in self._items: | ||
memory = ResourceMemory(noticed_by_listing=noticed_by_listing) | ||
self._items[key] = memory | ||
return self._items[key] | ||
|
||
async def forget(self, body: bodies.Body) -> None: | ||
""" | ||
Forget the resource's memory if it exists; or ignore if it does not. | ||
""" | ||
key = self._build_key(body) | ||
if key in self._items: | ||
del self._items[key] | ||
|
||
def _build_key( | ||
self, | ||
body: bodies.Body, | ||
) -> str: | ||
""" | ||
Construct an immutable persistent key of a resource. | ||
Generally, a uid is sufficient, as it is unique within the cluster. | ||
But it can be e.g. plural/namespace/name triplet, or anything else, | ||
even of different types (as long as it satisfies the type checkers). | ||
But it must be consistent within a single process lifetime. | ||
""" | ||
return body.get('metadata', {}).get('uid') or '' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from kopf.structs.bodies import Body | ||
from kopf.structs.containers import ResourceMemory, ResourceMemories | ||
|
||
BODY: Body = { | ||
'metadata': { | ||
'uid': 'uid1', | ||
} | ||
} | ||
|
||
|
||
def test_creation_with_defaults(): | ||
ResourceMemory() | ||
|
||
|
||
async def test_recalling_creates_when_absent(): | ||
memories = ResourceMemories() | ||
memory = await memories.recall(BODY) | ||
assert isinstance(memory, ResourceMemory) | ||
|
||
|
||
async def test_recalling_reuses_when_present(): | ||
memories = ResourceMemories() | ||
memory1 = await memories.recall(BODY) | ||
memory2 = await memories.recall(BODY) | ||
assert memory1 is memory2 | ||
|
||
|
||
async def test_forgetting_deletes_when_present(): | ||
memories = ResourceMemories() | ||
memory1 = await memories.recall(BODY) | ||
await memories.forget(BODY) | ||
|
||
# Check by recalling -- it should be a new one. | ||
memory2 = await memories.recall(BODY) | ||
assert memory1 is not memory2 | ||
|
||
|
||
async def test_forgetting_ignores_when_absent(): | ||
memories = ResourceMemories() | ||
await memories.forget(BODY) |
Oops, something went wrong.