kopf icon indicating copy to clipboard operation
kopf copied to clipboard

Usage with pytransitions, maybe with kopf.Memo?

Open guhcampos opened this issue 3 years ago • 1 comments

Question

I have a CRD with a state machine that is a super set of what kubernetes provides, so my intention is to formalize all states and transitions using an actual state machine written using transitions, but this requires storing a global object representing each of my CRDs that will hold the state of the machine for that object and validate the state changes. That's particularly useful for reconciliation.

My initial thought was to leverage kopf.Memo for this, keeping a global registry of all my object instances, but it feels like I'm reinventing the wheel here. I looked into in-memory indexing, but with the indices being read-only it is not possible to use them.

Currently I store the object state in a custom property of my CRD, which works fine but does not allow me to leverage transitions for reconciliation, as each new handling cycle I get a new instance of the object.

So is this approach of using a state machine doable, or is it better to just drop the idea and embrace having new instances for each handler?

If it's possible, is there any better way of holding the global state other than kopf.Memo?

This operator is expected to handle thousands of concurrent resources of a single CRD (although with hopefully very infrequent updates), so I'm happy to trade-off a high memory usage for decent concurrency as a failure state might incur in a lot of objects being handled at the same time.

Checklist

  • [x] I have read the documentation and searched there for the problem
  • [x] I have searched in the GitHub Issues for similar questions

Keywords

transitions pytransitions state machine finite state machine global state stateful

guhcampos avatar May 09 '21 01:05 guhcampos

Sorry, I probably cannot help you with the state machines and transitions here.

I looked into in-memory indexing, but with the indices being read-only it is not possible to use them.

Can you please clarify here — why is it not possible? What is the limiting factor?

BTW, while indices are read-only, the indexed values are not. For every resource object, you can index a mutable container if needed. If you want that object to be persisted across all indexing calls, consider using the memo kwarg itself (or one of its fields) — the memo object is persisted for the resource object's lifetime.

@kopf.index(...)
def idx(name, namespace, memo, **_):
    if 'myfld' not in memo:
        memo.myfld = list()  # <<< PERSISTED by the framework
    memo.myfld.append(datetime.datetime.utcnow())
    return {(namespace, name): memo}

@kopf.on.startup()
def start_me(memo: kopf.Memo, idx: kopf.Index, **_):
    memo.doexit = threading.Event()
    memo.mythr = threading.Thread(target=some_thread, args=(memo.doexit, idx,))
    memo.mythr.start()

@kopf.on.cleanup()
def clean_me(memo: kopf.Memo, idx: kopf.Index, **_):
    memo.doexit.set()
    memo.mythr.join()

def some_thread(exitflag: threading.Event, idx: kopf.Index):
    while not exitflag.is_set():
        for (ns, name), memo in idx.items():
            print(memo.myfld)
            memo.myfld[:] = []  # <<< MUTABLE!
        exitflag.wait(timeout=5)  # time.sleep(5) but interruptable

Alternatively:

@kopf.index(...)
def idx(name, namespace, memo, **_):
    ………
    return {(namespace, name): memo.myfld}

def some_thread(exitflag: threading.Event, idx: kopf.Index):
    ………:
        for (ns, name), myfld in idx.items():
            print(myfld)
            myfld[:] = []  # <<< MUTABLE! and PERSISTED.
        ………

nolar avatar May 09 '21 09:05 nolar