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

bridge: assorted channel-related goodies #20269

Conversation

allisonkarlitskaya
Copy link
Member

Here's some assorted cleanups and improvements that will help with #20160. Let's merge these first, since they're not directly related (and are big enough that they might cause regressions in their own right).

We have a lot of places that want to accept a particular constrained set
of string values.  Add a helper for that.
@allisonkarlitskaya allisonkarlitskaya force-pushed the pre-bridge-send-acks branch 2 times, most recently from 2552dde to a0472e5 Compare April 8, 2024 10:41
...in a couple of places that come to mind.

This catches and fixes a minor confusing issue: we were using a default
value of `msg` for the `err` option on stream channels.  This is not
(and has never been) a valid value, but nothing noticed that until now.
Change that to be `message` which is the correct spelling as per the
protocol documentation (and the unit tests).
We currently use `b''` to mean 'EOF' on `AsyncChannel.read()`, just as
it means on the `read()` system call.  This is a problem, because it's
possible to send an empty message, and in some channels (`fsreplace1`)
this even has a semantic meaning.  Currently that empty message will be
interpreted as EOF.

Let's change things around a bit here.  We now use `None` to mean `EOF`.
That's going to make it more difficult to use with a while-walrus, but
we can't do that anyway since we're stuck at Python 3.6. Firm up the
typing a bit while we're at it.
Instead of waiting around for the channel to read() EOF when the channel
is closed, let's just cancel the task that's running the `.run()`
method.

This simplifies channel implementation.

While we're at it, let's up our exception-handling game a bit for
unexpected exceptions: report those as internal errors in the close
message, complete with tracebacks (but let them raise as well, so that
they're also reported to stderr).
If we raise a CockpitProblem (or, for example, ChannelError) like:

  raise ChannelError('internal-error') from exc

Then we get the original exception set on the ChannelError as the
`__cause__`.  When we go to turn that into an error message to send, and
if the problem code was `internal-error` then give this as a traceback.

We can't do this from `__init__()` unfortunately, as the attribute gets
set after the fact.  We can add a manual getter though, that handles it
at the time we return the attributes.
Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

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

Thanks! This all looks nice to me, I just have a question. If you do want to push again to update the comment, please consider no-test.

async def read(self):
async def read(self) -> 'bytes | None':
# Three possibilities for what we'll find:
# - None (EOF) → return b''
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit confusing as that's not what the code below does -- it returns None, not b''. Given that you changed everything to None, is this a leftover from a previous version?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. Oops.

if isinstance(item, bytes):
if item is None:
return None
if isinstance(item, Mapping):
Copy link
Member

Choose a reason for hiding this comment

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

so this is a "map-ping", right?

😁

Copy link
Member

Choose a reason for hiding this comment

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

Without look into the code, what does the ping Mapping look like?

Copy link
Member Author

Choose a reason for hiding this comment

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

It just happens to be the only time we shove a dict into the queue. Here's the other end:

    def do_ping(self, message: JsonObject) -> None:
        self.receive_queue.put_nowait(message)

so if we find a dict in there, we can be pretty sure that it's a ping and we should pong it...

@@ -114,7 +115,7 @@ class FsReadChannel(GeneratorChannel):

def do_yield_data(self, options: JsonObject) -> GeneratorChannel.DataGenerator:
path = get_str(options, 'path')
binary = get_str(options, 'binary', None)
binary = get_enum(options, 'binary', ['raw'], None) is not None
Copy link
Member

Choose a reason for hiding this comment

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

So the binary option can be bool or 'raw' that is rather confusing. Maybe an idea we deprecate the raw option?

Copy link
Member Author

Choose a reason for hiding this comment

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

It can never be bool. This is a common source of confusion. It must always be either absent or the literal string "raw"

Copy link
Member

Choose a reason for hiding this comment

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

Then this is an issue maybe, since:

111   │         const options = {
 112   │             method: "GET",
 113   │             path: client.VERSION + "libpod/containers/" + this.props.containerId + "/logs",
 114   │             body: "",
 115   │             binary: true,
 116   │             params: {
 117   │                 follow: true,
 118   │                 stdout: true,
 119   │                 stderr: true,
 120   │             },
 121   │         };

Copy link
Member

Choose a reason for hiding this comment

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

Oh it's the filesystem channel sigh. Well confusing regardless :)

if isinstance(item, bytes):
if item is None:
return None
if isinstance(item, Mapping):
Copy link
Member

Choose a reason for hiding this comment

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

Without look into the code, what does the ping Mapping look like?

@allisonkarlitskaya
Copy link
Member Author

Thanks! This all looks nice to me, I just have a question. If you do want to push again to update the comment, please consider no-test.

#20160 builds on this. I'll push a tiny fixup there.

@allisonkarlitskaya allisonkarlitskaya merged commit 2cd0b26 into cockpit-project:main Apr 8, 2024
76 checks passed
@allisonkarlitskaya allisonkarlitskaya deleted the pre-bridge-send-acks branch April 8, 2024 13:58
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.

None yet

3 participants