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

feat: add support for persistent recursive watches #715

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jeblair
Copy link
Contributor

@jeblair jeblair commented Mar 21, 2023

Why is this needed?

ZooKeeper 3.6.0 added support for persistent, and persistent recursive watches. This adds the corresponding support to the Kazoo client class.

Does this PR introduce any breaking change?

No breaking changes.

@codecov
Copy link

codecov bot commented Mar 21, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.56%. Comparing base (2fb93a8) to head (415dc93).
Report is 2 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #715      +/-   ##
==========================================
- Coverage   96.81%   96.56%   -0.26%     
==========================================
  Files          27       27              
  Lines        3549     3639      +90     
==========================================
+ Hits         3436     3514      +78     
- Misses        113      125      +12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jeblair
Copy link
Contributor Author

jeblair commented Mar 22, 2023

It looks like the test failures are not related to this change.

westphahl pushed a commit to westphahl/nodepool that referenced this pull request May 26, 2023
Some changes to Kazoo are needed to support persistent recursive
watches.  Until those merge upstream, vendor and update the parts
of Kazoo we need.

Upstream PR: python-zk/kazoo#715

Change-Id: Id6372e4075b5b3ffeeef3e0f4751a71e59001ef9
@kt315
Copy link

kt315 commented Jan 6, 2024

Hi! Why not merge this?

@kt315
Copy link

kt315 commented Jan 18, 2024

Hi! Whose approval is need? 🤔

@StephenSorriaux
Copy link
Member

Hello,

Approval from @python-zk/maintainers is needed. Will try to take a look by end of the week.

Can somebody that already tried the feature can confirm it is working as expected?

@jeblair
Copy link
Contributor Author

jeblair commented Jan 18, 2024

Hello,

Approval from @python-zk/maintainers is needed. Will try to take a look by end of the week.

Can somebody that already tried the feature can confirm it is working as expected?

While awaiting review/merge we in the Zuul project have vendored a copy[1] of this and used it successfully in production for about nine months (though it does look like our vendored copy omits the ability to remove watches since we never do so, so we'll have to rely on the tests for that). Thanks for taking a look. :)

[1] https://opendev.org/zuul/nodepool/src/branch/master/nodepool/zk/vendor

Copy link
Member

@StephenSorriaux StephenSorriaux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR, the implementation is great, I just added some questions. Would it be possible to take into account the warning from codecov and cover some of the new lines with a test?

kazoo/client.py Outdated Show resolved Hide resolved
kazoo/client.py Show resolved Hide resolved
kazoo/client.py Show resolved Hide resolved
kazoo/protocol/connection.py Outdated Show resolved Hide resolved
kazoo/protocol/connection.py Outdated Show resolved Hide resolved
kazoo/protocol/connection.py Outdated Show resolved Hide resolved
kazoo/tests/test_client.py Show resolved Hide resolved
kazoo/tests/test_client.py Show resolved Hide resolved
kazoo/client.py Outdated Show resolved Hide resolved
kazoo/tests/test_client.py Outdated Show resolved Hide resolved
kazoo/exceptions.py Outdated Show resolved Hide resolved
ZooKeeper 3.6.0 added support for persistent, and persistent
recursive watches.  This adds the corresponding support to the
Kazoo client class.
@StephenSorriaux
Copy link
Member

@jeffwidman @ceache @a-ungurianu this one is now ready to be reviewed
@jeblair I made some small changes, mostly based on my previous review, please let me now if you see anything wrong

@jeblair
Copy link
Contributor Author

jeblair commented Mar 26, 2024

@jeffwidman @ceache @a-ungurianu this one is now ready to be reviewed @jeblair I made some small changes, mostly based on my previous review, please let me now if you see anything wrong

The changes LGTM, thanks! And sorry I wasn't able to address those myself in a timely manner.

@StephenSorriaux
Copy link
Member

Thank you @jeblair

@jeffwidman @ceache @a-ungurianu gentle bump!

Copy link
Member

@jeffwidman jeffwidman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

I made a few mostly cosmetic suggestions, but functionally it all looks good to me.

There's enough complexity in the protocol that even with the nice unit test coverage it wouldn't surprise me if there was a bug or two in here, either on our end or zookeeper's end, but given the solid UT coverage I think the best way to flush them out at this point is to merge and let folks bang on it for a while. The nice part is that this is a new feature, so any problems are unlikely to affect existing functionality.


class TransactionRequest(object):

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this blank line extraneous? I don't normally recall a blank line between class definition and docstring...

Comment on lines +476 to +483
if request.mode == AddWatchMode.PERSISTENT:
client._persistent_watchers[request.path].add(
watcher
)
elif request.mode == AddWatchMode.PERSISTENT_RECURSIVE:
client._persistent_recursive_watchers[
request.path
].add(watcher)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this might benefit from an else clause?

I realize it's a no-op, but it still took me a moment to realize that's because it might be a non-persistant watch that's being added... so might be easier for a reader to understand with a code comment...

But code comments can go out of sync, so a simple assertion that it's the expected mode might be useful like:

else:
    if request.mode not in [expected modes]:
       raise <relevant exception>

that way both clarifies the code and also catches stupid errors if somehow a case was missed down the road.

Alternatively, this might read even simpler as a switch statement with a default case... but I realize those require Python 3.10 so maybe leave a:

# TODO: switch these if/else clauses to switch statements where appropriate once we drop Python 3.9 support

Comment on lines +489 to +493
if request.watcher_type == WatcherType.CHILDREN:
client._child_watchers.pop(request.path, None)
elif request.watcher_type == WatcherType.DATA:
client._data_watchers.pop(request.path, None)
elif request.watcher_type == WatcherType.ANY:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might read simpler as a switch statement rather than the if/elif/elif series? technically it should be more performant too

Oh, NVM, just realized switch statements won't work in Python 3.8/3.9 😢


.. attribute:: PERSISTENT

The watch is not removed when trigged.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The watch is not removed when trigged.
The watch is not removed when triggered.


.. attribute:: PERSISTENT_RECURSIVE

The watch is not removed when trigged, and applies to all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The watch is not removed when trigged, and applies to all
The watch is not removed when triggered, and applies to all

pytest.skip("Must use Zookeeper %s.%s or above" % (major, minor))

def test_persistent_recursive_watch(self):
# This tests adding and removing a persistent recursive watch.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# This tests adding and removing a persistent recursive watch.
"""This tests adding and removing a persistent recursive watch."""

]

def test_persistent_watch(self):
# This tests adding and removing a persistent watch.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# This tests adding and removing a persistent watch.
"""This tests adding and removing a persistent watch."""

dict(type=EventType.DELETED, path="/a"),
]

def test_persistent_watch(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd probably order the tests:

  1. test_persistent_watch
  2. test_persistent_recursive_watch

As 2️⃣ feels like a variant of 1️⃣ , so typically we put the simpler one first.

]

def test_remove_data_watch(self):
# Test that removing a data watch leaves a child watch in place.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Test that removing a data watch leaves a child watch in place.
"""Test that removing a data watch leaves a child watch in place."""

callback_event.wait(30)

def test_remove_children_watch(self):
# Test that removing a children watch leaves a data watch in place.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Test that removing a children watch leaves a data watch in place.
"""Test that removing a children watch leaves a data watch in place."""

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants