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

Socket activation and systemd supervision #171

Closed
wants to merge 3 commits into from

Conversation

dkg
Copy link

@dkg dkg commented Oct 4, 2019

Please consider these changes to enable eaiser integration of aiosmtpd implementations with systemd and other comparable systems.

@dkg
Copy link
Author

dkg commented Oct 5, 2019

fwiw, i think the travis CI test is failing because the test wants to listen on port 8025 when something else (maybe another test) is already listening on it. i don't think that failure is due to any of the changes proposed here.

@waynew
Copy link
Collaborator

waynew commented Oct 14, 2019

Hi @dkg, thanks for the contribution! Have you run these tests locally?

My experience is that there's no such thing as unrelated test failures. Either the tests are poorly written or the code has side effects that weren't accounted for. I'm thinking that might be the case here, as I haven't seen this error show up before... but I could be wrong!

@dkg
Copy link
Author

dkg commented Oct 23, 2019

@waynew -- you were right, somehow i'd mixed up my local tests. I've just pushed a fix (my changes to Controller.__init__ were accidentally ignoring the port argument) and now pytest-3 succeeds for me locally.

Looks like pytest-3 succeeds on Travis now too, but the coverage tests are failing, i think because of the changes i've made. I'm not sure how to increase the coverage effectively for these changes though -- can you help me figure that out?

aiosmtpd/main.py Outdated
loop.create_server(factory, host=args.host, port=args.port))
if args.supervised:
log.info('Server listening on socket-activated file descriptor')
stage = loop.create_server(factory, sock=socket.fromfd(3, socket.AF_INET, socket.SOCK_STREAM))
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like these are the lines that aren't actually getting tested, according to what I can glean from Travis.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@dkg
Copy link
Author

dkg commented Oct 24, 2019

I've just tried to add tests to exercise that code, but i'm not sure whether they are what you want. please let me know if you'd rather they were done differently!

@waynew
Copy link
Collaborator

waynew commented Oct 24, 2019

@dkg I think that fits with the existing pattern, so it seems reasonable to me.

Looks like there are a couple of more problems though:

https://travis-ci.com/aio-libs/aiosmtpd/jobs/248965906#L487
and
https://travis-ci.com/aio-libs/aiosmtpd/jobs/248965906#L466

@dkg
Copy link
Author

dkg commented Oct 24, 2019

@waynew -- i've fixed the flake complaints, but i don't understand the coverage complaint:

Name                        Stmts   Miss Branch BrPart  Cover   Missing
464-----------------------------------------------------------------------
465aiosmtpd/__init__.py            0      0      0      0   100%
466aiosmtpd/controller.py         56      0     12      2    97%   39->44, 42->44
467aiosmtpd/docs/__init__.py       0      0      0      0   100%
468aiosmtpd/handlers.py          143      0     50      0   100%
469aiosmtpd/lmtp.py               11      0      2      0   100%
470aiosmtpd/main.py               91      0     20      0   100%
471aiosmtpd/smtp.py              506      0    188      0   100%
472-----------------------------------------------------------------------
473TOTAL                         807      0    272      2    99%
474ERROR: InvocationError for command /home/travis/build/aio-libs/aiosmtpd/.tox/py37-cov/bin/python -m coverage report -m --rcfile=/home/travis/build/aio-libs/aiosmtpd/.coverage.ini --fail-under=100 (exited with code 2)

looking at the relevant lines in aiosmtpd/controller.py, i see:

    36	    def _run(self, ready_event):
    37	        asyncio.set_event_loop(self.loop)
    38	        try:
    39	            if self.sock is None:
    40	                if self.port is None:
    41	                    self.port = 8025
    42	                if self.hostname is None:
    43	                    self.hostname = '::1'
    44	            self.server = self.loop.run_until_complete(
    45	                self.loop.create_server(

i don't understand this for two reasons:

  • why is there an overlap in the indicated ranges?
  • why aren't these cases already covered by the tests in tests/test_server.py ?

@dkg dkg force-pushed the socket-activation branch 3 times, most recently from 9e4dedb to dfe3636 Compare October 24, 2019 16:08
@dkg
Copy link
Author

dkg commented Oct 24, 2019

OK, flake complaints are all actually fixed now, but i'm stumped on how to get this additional coverage, because it looks like it should already be covered to me. If you can help me understand the coverage test suite better, i'd be happy to take pointers.

@dkg
Copy link
Author

dkg commented Dec 9, 2019

@waynew, any hints ?

@waynew
Copy link
Collaborator

waynew commented Dec 10, 2019

The overlap is because there are two branches there - neither is getting exercised.

Which tests should be exercising that code? I'm not seeing anything...

systemd-style socket-activation is a nice safe way to launch a process
that is already unprivileged.

It works by the parent process opening a socket, and then doing its
own privilege dropping.  This means the child process starts with an
already-listening file descriptor, but no elevated privileges.

To make use of this, the user needs to be able to pass an existing
socket to the aiosmtpd controller.

Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
We only try to test --supervised on non-Windows platforms.

This uses the strategy outlined in sd_listen_fds(), where file
descriptor 3 is the first socket passed in.

I chose an arbitrary different port (19666) to make sure that the
right socket is getting through.
@dkg
Copy link
Author

dkg commented Dec 10, 2019

if the branch from 39-44 is not getting exercised at all, then surely none of the sub-branches within it are being exercised. so either i would expect no sub-branches to be listed, or both sub-branches to be listed. but instead, only the 42-44 sub-branch is listed, and the 40-41 sub-branch is not listed. This makes me doubt my understanding of the report :/

Anyway, the two cases identified are in Controller.run where:

  • 39-44: self.sock is None
  • 42-44: self.hostname is None

But aiosmtpd/tests/test_server.py contains test_socket_error, test_server_attribute, and test_smtp_utf8, each of which instantiates a Controller object with both hostname and sock left unset (thereby defaulting to None), which are then started or otherwise indirectly run, right?

Sorry if i'm asking obvious questions -- feel free to point me to answers even if they're things that you think i should already know.

@waynew
Copy link
Collaborator

waynew commented Dec 10, 2019

Sorry if i'm asking obvious questions -- feel free to point me to answers even if they're things that you think i should already know.

You're doing fine 🙂

I'm not super familiar with those tests myself. What I would do is run the tests I'm expecting to hit that code, and perhaps throw assert self.sock is None. You should see that fail, given the coverage report.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@pepoluan
Copy link
Collaborator

This is an old PR, more than 6 months old, only brought up recently due to CLAassistant.

Is there desire to continue with this PR? If not, I will close this.

@pepoluan
Copy link
Collaborator

pepoluan commented Dec 1, 2020

Note to anyone wanting to implement similar functionality, either by picking up this PR or submitting a new one:

I'm -1 on changing Controller class. Especially considering the complexity added by #213 when it gets merged.

If systemd/supervised support is needed, IMHO we should create a new subclass of Controller, say, SupervisedController. In that class, _testconn() can (and should) be overridden to cater for the different method of activation when using supervised.

Adding a new class will also make merging much easier :-)

@pepoluan
Copy link
Collaborator

Okay, no activities for more than 3 weeks. I'm closing this PR for now.

If anyone wants to pick this up, feel free to reopen.

@pepoluan pepoluan closed this Dec 22, 2020
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.

4 participants