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

cleanbuild: use pylxd instead of lxd command line #904

Closed
wants to merge 18 commits into from

Conversation

kalikiana
Copy link
Contributor

@sergiusens sergiusens changed the title WIP: Use pylxd instead of lxd command line (LP: #1641520) WIP: Use pylxd instead of lxd command line Nov 15, 2016
@sergiusens sergiusens changed the title WIP: Use pylxd instead of lxd command line WIP: cleanbuild: use pylxd instead of lxd command line Nov 15, 2016
@kalikiana kalikiana changed the title WIP: cleanbuild: use pylxd instead of lxd command line cleanbuild: use pylxd instead of lxd command line Nov 25, 2016
Copy link
Collaborator

@sergiusens sergiusens left a 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,
Copy link
Contributor

@kyrofa kyrofa Dec 8, 2016

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
Copy link
Contributor

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?

Copy link
Contributor

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
Copy link
Contributor

@kyrofa kyrofa Dec 8, 2016

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))
Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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:
Copy link
Contributor

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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).

Copy link
Contributor

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.

Copy link
Collaborator

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
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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):
Copy link
Contributor

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)
Copy link
Contributor

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
Copy link
Contributor

@kyrofa kyrofa Dec 8, 2016

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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!

Copy link
Contributor Author

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)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay, makes sense.

@sergiusens sergiusens closed this Dec 12, 2016
@sergiusens sergiusens reopened this Dec 12, 2016
@kyrofa
Copy link
Contributor

kyrofa commented Dec 13, 2016

This is looking good to me, but there's a problem in the tests:

Collecting python3-pylxd==2.0.5 (from -r requirements.txt (line 7))

  Could not find a version that satisfies the requirement python3-pylxd==2.0.5 (from -r requirements.txt (line 7)) (from versions: )

No matching distribution found for python3-pylxd==2.0.5 (from -r requirements.txt (line 7))

@kyrofa
Copy link
Contributor

kyrofa commented Dec 14, 2016

Hmm, looks like this needs libssl-dev to build.

@sergiusens
Copy link
Collaborator

This is what I get when I want to cleanbuild:

sergiusens@snapcraft:~/snapcraft/demos/godd$ snapcraft cleanbuild --debug
"grade" property not specified: defaulting to "stable"
Setting up part 'godd' with plugin 'go' and properties {'source': 'https://github.com/mvo5/godd', 'go-importpath': 'github.com/mvo5/godd', 'build-packages': ['gcc', 'libgudev-1.0-dev'], 'plugin': 'go', 'snap': [], 'stage': [], 'source-type': 'git', 'prime': []}.
"GET /1.0 HTTP/1.1" 200 None
"GET /1.0/images HTTP/1.1" 200 67
Creating container snapcraft-inadvertently-anthracitic-kip with fingerprint N/A
"POST /1.0/containers HTTP/1.1" 202 463
"GET /1.0/operations/d0a7bdbc-cb8b-4309-a55b-8c2a360b70c0 HTTP/1.1" 200 394
"GET /1.0/operations/d0a7bdbc-cb8b-4309-a55b-8c2a360b70c0/wait HTTP/1.1" 200 394
Stopping snapcraft-inadvertently-anthracitic-kip
Traceback (most recent call last):
  File "/home/sergiusens/snapcraft/bin/snapcraft", line 31, in <module>
    snapcraft.main.main()
  File "/home/sergiusens/snapcraft/snapcraft/main.py", line 236, in main
    return run(args, project_options)
  File "/home/sergiusens/snapcraft/snapcraft/main.py", line 278, in run
    lifecycle.cleanbuild(project_options),
  File "/home/sergiusens/snapcraft/snapcraft/internal/lifecycle.py", line 288, in cleanbuild
    lxd.Cleanbuilder(snap_filename, tar_filename, project_options).execute()
  File "/home/sergiusens/snapcraft/snapcraft/internal/lxd.py", line 171, in execute
    with self._create_container():
  File "/usr/lib/python3.5/contextlib.py", line 59, in __enter__
    return next(self.gen)
  File "/home/sergiusens/snapcraft/snapcraft/internal/lxd.py", line 161, in _create_container
    }, wait=True)
  File "/home/sergiusens/.local/lib/python3.5/site-packages/pylxd/models/container.py", line 132, in create
    client.operations.wait_for_operation(response.json()['operation'])
  File "/home/sergiusens/.local/lib/python3.5/site-packages/pylxd/models/operation.py", line 29, in wait_for_operation
    operation.wait()
  File "/home/sergiusens/.local/lib/python3.5/site-packages/pylxd/models/operation.py", line 51, in wait
    raise exceptions.LXDAPIException(response)
pylxd.exceptions.LXDAPIException: not found

@sergiusens
Copy link
Collaborator

Apparently I have no fingerprints available

@sergiusens
Copy link
Collaborator

closing due to lack of activity. Also pylxd in xenial differs greatly from the on in latter series of ubuntu

@sergiusens sergiusens closed this Feb 6, 2017
@kalikiana
Copy link
Contributor Author

kalikiana commented Feb 6, 2017

Unfortunate to see this being closed, I had been waiting for some review/ help with the tests - the code has worked well for me.
Not sure how pylxd xenial/others is relevant, as far as I'm aware Xenial is the only relevant target for stable snaps anyway.

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