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
cleanbuild: use pylxd instead of lxd command line #904
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, thanks for working on this, before diving into it, do you mind fixing the tests?
There are some errors in the code like @patch('snapcraft.internal.lxd.Cleanbuilder._container_run')
where it probably is supposed to be @mock.patch
or some sort.
I am also interested in knowing what the details are wrt the differences in pylxd
between xenial, yakkety and zesty.
Thanks again
@@ -17,6 +17,7 @@ Build-Depends: bash-completion, | |||
python3-petname, | |||
python3-pkg-resources, | |||
python3-progressbar, | |||
python3-pylxd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this also need to be in the binary package below? Please also add it to the requirements.txt so our tests can obtain it (seems to be the reason for the failures).
import pylxd | ||
from six.moves.urllib import parse | ||
from ws4py.client import WebSocketBaseClient | ||
from ws4py.manager import WebSocketManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this require python3-ws4py?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see that's a dependency of pylxd. Should we depend on it directly?
from time import sleep | ||
import pylxd | ||
from six.moves.urllib import parse |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing six in python3-only code made me double-take. Would urllib.parse
be better here? If we need six, we should consider making it a direct dependency.
check_call(['lxc', 'exec', self._container_name, '--'] + cmd) | ||
if not self._container: | ||
raise CalledProcessError(-1, cmd) | ||
print('Executing {}'.format(cmd)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be using a logger?
self._pipe(sys.stdin), self._pipe(sys.stdout), self._pipe(sys.stderr) | ||
self._manager.start() | ||
while len(self._manager) > 0: | ||
sleep(.1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a possibility this will wait forever? Perhaps we should implement a timeout.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We wait til the pipes close, same as pylxd would out of the box. Different builds can take any amount of time, so a hard timeout would to my mind effectively prevent you from snapping certain things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough.
|
||
def _container_run(self, cmd): | ||
check_call(['lxc', 'exec', self._container_name, '--'] + cmd) | ||
if not self._container: | ||
raise CalledProcessError(-1, cmd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really an error from subprocess-- someone's trying to run a command without a container, right? Could we provide a more helpful error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where "somebody" is test cases which want a reliable error. As a user of snapcraft you won't get this.
if self._io == sys.stdin: | ||
return | ||
|
||
if len(message.data) == 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A simple if not message.data
would likely be more pythonic.
'ephemeral': True, | ||
'source': { | ||
'type': 'image', | ||
'fingerprint': fingerprint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems the API can handle 'image': 'ubuntu/xenial'
. Can it not also handle the arch? Or is there another benefit to using the fingerprint instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where the API takes a name... fingerprint only takes a fingerprint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps I misunderstand, but this is from the docs:
config = {'name': 'my-container', 'source': {'type': 'image', 'image': 'ubuntu/trusty'}}
container = client.containers.create(config, wait=True)
Isn't ubuntu/trusty
the name, there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those docs must be outdated. If you try that you'll get "Must specify one of alias, fingerprint or properties for init from image" (https://github.com/lxc/lxd/blob/master/lxd/containers_post.go#L122).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, indeed! Alright, sorry for the noise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curl -s --unix-socket /var/snap/lxd/common/lxd/unix.socket lxd/1.0/containers -X POST -d '{"name": "blah", "ephemeral": true, "source": {"type": "image", "mode": "pull", "server": "https://cloud-images.ubuntu.com/releases", "protocol": "simplestreams", "alias": "xenial"}}'
if not self._container: | ||
raise CalledProcessError(-1, cmd) | ||
print('Executing {}'.format(cmd)) | ||
# API call because self._container.execute doesn't expose IO streams |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain this a little more? execute()
blocks, so this makes me think that you simply want to be able to show scrolling output while the command executes within the container rather than blocking, but then you also pipe stdin
. How is that used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afair stdin needs to be added to the manager so it can be closed. That's also what pylxd does normally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, thanks for the explanation.
if exit_status is not 0: | ||
raise CalledProcessError(exit_status, cmd) | ||
|
||
def _pipe(self, io): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this should be called _connect_pipe()
? Its return value is never used.
self._parsed = parse.urlparse( | ||
self._client.api.operations[operation_id].websocket._api_endpoint) | ||
self._manager = WebSocketManager() | ||
self._pipe(sys.stdin), self._pipe(sys.stdout), self._pipe(sys.stderr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please put these statements on their own lines.
image_name = '{}:{}/{}'.format( | ||
props['os'], props['release'], props['architecture']) | ||
if name == image_name: | ||
return fingerprint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why the API is being used directly here instead of iterating over client.images.all()
and using its properties
, architecture
, and fingerprint
attributes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because that only returns the fingerprints - but we need to pick an image by name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong, but does this not do the same thing?
def _get_fingerprint_by_name(self, name):
for image in client.images.all():
image_name = '{}:{}/{}'.format(
image.properties['os'], image.properties['release'], image.architecture)
if image_name == name:
return image.fingerprint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually that does work. Pushed a slightly modified change. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(And in case you're wondering: .architecture and properties['architecture'] are not the same so I couldn't use the former)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay, makes sense.
This is looking good to me, but there's a problem in the tests:
|
Hmm, looks like this needs libssl-dev to build. |
This reverts commit 48d49e0.
Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>
This is what I get when I want to cleanbuild:
|
Apparently I have no fingerprints available |
closing due to lack of activity. Also pylxd in xenial differs greatly from the on in latter series of ubuntu |
Unfortunate to see this being closed, I had been waiting for some review/ help with the tests - the code has worked well for me. |
https://bugs.launchpad.net/snapcraft/+bug/1641520