Merge lp:~bcsaller/pyjuju/lxc-omega into lp:pyjuju

Proposed by Benjamin Saller
Status: Merged
Merge reported by: Benjamin Saller
Merged at revision: not available
Proposed branch: lp:~bcsaller/pyjuju/lxc-omega
Merge into: lp:pyjuju
Prerequisite: lp:~hazmat/pyjuju/local-provider-config
Diff against target: 643 lines (+152/-115)
16 files modified
examples/oneiric/mysql/hooks/db-relation-joined (+1/-5)
examples/oneiric/wordpress/hooks/config-changed (+1/-1)
examples/oneiric/wordpress/hooks/db-relation-changed (+1/-1)
examples/oneiric/wordpress/hooks/install (+1/-1)
juju/agents/machine.py (+2/-1)
juju/control/add_unit.py (+1/-0)
juju/lib/lxc/data/juju-create (+1/-0)
juju/machine/tests/test_unit_deployment.py (+54/-31)
juju/machine/unit.py (+49/-23)
juju/providers/lxc/__init__.py (+13/-4)
juju/providers/lxc/agent.py (+11/-1)
juju/providers/lxc/container.py (+0/-31)
juju/providers/lxc/tests/test_container.py (+7/-7)
juju/providers/lxc/tests/test_provider.py (+7/-7)
misc/bash_completion.d/juju (+2/-2)
setup.py (+1/-0)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/lxc-omega
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Approve
Kapil Thangavelu (community) Approve
Review via email: mp+77032@code.launchpad.net

This proposal supersedes a proposal from 2011-09-25.

Description of the change

LXC driven local development story

This branch includes the merge of many preceding branches related to the lxc local dev story. In its current state it can be used to do local lxc deployemnt and testing.

Add the following to your ~/.juju/environments.yaml

juju: environments
default: laptop

laptop
    type: lxc
    dmin-secret: whoknows
    data-dir: /tmp

I recommend adding dmsmasq to your /etc/resolv.conf with:

nameserver 192.168.122.1

However a subsequent branch makes status and ssh find the proper VM for you.

Machines are named via a pattern <username>-<env name>-<servicename>-<unitid>

So for me a wordpress deployment from the local repo would result in a machine at

http://bcsaller-laptop-wordpress-0/

To post a comment you must log in.
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote : Posted in a previous version of this proposal

What is the pre-req of this branch? I've already reviewed content in
this branch before.

review: Needs Information
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

There's still no information on how to review this branch without looking at
changes already reviewed in previous branches.

Please do mention what are the pre-requisite branches in the summary, when it
contains multiple branches.

One detail I could already figure on it:

[1]

+@contextlib.contextmanager
+def lock_manager(path):
+ """Use a file to represent a lock.
(...)
+ # This will raise if the file exists
+ fd = os.open(path, os.O_CREAT | os.O_RDWR | os.O_EXCL)

Please use the existing filesystem lock in Twisted. This version
will explode badly when the second process tries to get into the
mutex region.

review: Needs Fixing
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Kapil mentioned the file-lock branch that is missing from the kanban. I've moved this review there.

Revision history for this message
Benjamin Saller (bcsaller) wrote :

Uses twisted filelock impl
Uses latest lxc-lib
Examples based around IP address rather than hostname to avoid need to include resolv.conf change on host to address machines (the containers can address each other fine either way)

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Looks good to me, some minors

[0] Please use self.juju_home as the base for the lock

762 + lockfile = FilesystemLock(os.path.join(
763 + "/tmp/", "%s.lock" % container_template_name))

[1] I'd prefer the master template to be namespace'd to the environment, it makes its cleanup automatic (if it gets broken), and allows divergence in series across environments.

[2] There's a few minor style violations, mostly of the over 80 col variety, ie.

+ if self._public_key:
+ args.insert(1, "JUJU_PUBLIC_KEY=%s" % pipes.quote(self._public_key))

review: Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

There are a few details, but this looks generally nice, thanks.

[2]

+ self.master_template = LXCContainer(
+ container_template_name,
+ origin="ppa",

Shouldn't this be dependent on the source of juju itself?

Please check out the env-origin branch that hopefully Jim is landing tomorrow.
Should be trivial to hook on it.

[3]

-hostname=`curl http://169.254.169.254/latest/meta-data/local-hostname`
-
+hostname=`ip -f inet addr |grep 192.168| grep -v '192\.168\.1\.' | awk '{print $2;}'|cut -f 1 -d '/'`

Please drop the example changes for now. It break the examples in EC2, and consequently our waterfall.

[4]

I haven't followed carefully, but my gut feeling is that the branch is
a bit shallow on testing. Can you please verify if the following logic
is well covered:

- get_master_template
- start using get_master_template
- JUJU_PUBLIC_KEY handling in agent.py
- etc

[5]

+ log.debug("Created master container %s" %
+ container_template_name)

Indentation is off.

[6]

+ try:
+ lockfile.lock()

The locking should be outside the try.. if it fails for whatever
reason, it shouldn't be unlocked.

[7]

Considering that this is using Twisted, shouldn't the lock and unlock
be yielding? I suspect they might be a NOOP at the moment.

[8]

+ if public_key.startswith("'"):
+ public_key = public_key[1:-1]

Please strip it rather than assuming the position blindly. This logic
breaks if e.g. there are spaces at the end of the line.

[9]

+ lockfile = FilesystemLock(os.path.join(
+ "/tmp/", "%s.lock" % container_template_name))

That's a security bug. One should never write any files to a publicly
writable location with a known name.

[10]

After you do these changes, can you please ask for another review from
Kapil. I'll be traveling tomorrow, but if he's happy with the branch
I'm +1 on it too. Thank you!

review: Approve
Revision history for this message
Benjamin Saller (bcsaller) wrote :

> [2]
>
> +        self.master_template = LXCContainer(
> +            container_template_name,
> +            origin="ppa",
>
> Shouldn't this be dependent on the source of juju itself?
>
> Please check out the env-origin branch that hopefully Jim is landing tomorrow.
> Should be trivial to hook on it.

I'd like to push this to a later branch, for today this will work with
the ppa and enables local development.

>
> [3]
>
>
> -hostname=`curl http://169.254.169.254/latest/meta-data/local-hostname`
> -
> +hostname=`ip -f inet addr |grep 192.168| grep -v '192\.168\.1\.' | awk '{print $2;}'|cut -f 1 -d '/'`
>
> Please drop the example changes for now. It break the examples in EC2, and consequently our waterfall.
>

Changed.

>
> [4]
>
> I haven't followed carefully, but my gut feeling is that the branch is
> a bit shallow on testing.  Can you please verify if the following logic
> is well covered:
>
> - get_master_template
> - start using get_master_template
> - JUJU_PUBLIC_KEY handling in agent.py
> - etc
>

Ådded tests around _get_container which encapsulates
get_master_template, now _get_master_template and private. This change
marks those as internal and the interface made them a bit simpler to
mock in the tests.

[6,7,9] I've removed the lock file, it was a theoretical issue and
wouldn't prevent manual lxc commands from interfering anyway. Instead
we better namespace the template name and check to see if containers
are built to attempt to avoid other conflicts. This new behavior
should aid in restarts of the agent down the line.

> +        if public_key.startswith("'"):
> +            public_key = public_key[1:-1]
>
> Please strip it rather than assuming the position blindly. This logic
> breaks if e.g. there are spaces at the end of the line.

Done.

>
>
>
> [10]
>
> After you do these changes, can you please ask for another review from
> Kapil.  I'll be traveling tomorrow, but if he's happy with the branch
> I'm +1 on it too.  Thank you!

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

post commit review..

[0]
        if not master_template.is_constructed():
            yield self.master_template.create()
            log.debug("Created master container %s" % container_template_name)

That's a bug, there is no instance attribute self.master_template

[1] Lots of over 80 lines, diff to fix (also includes fix for 0)
http://paste.ubuntu.com/699361/

[2] test_containers should be subsumed along with the implmeentation into the
 lib/lxc package.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'examples/oneiric/mysql/hooks/db-relation-joined'
--- examples/oneiric/mysql/hooks/db-relation-joined 2011-09-15 18:56:08 +0000
+++ examples/oneiric/mysql/hooks/db-relation-joined 2011-09-29 06:40:31 +0000
@@ -2,11 +2,7 @@
22
3set -eu # -x for verbose logging to juju debug-log3set -eu # -x for verbose logging to juju debug-log
44
5# XXX The local hostname should be provided by juju itself in a5hostname=`hostname`
6# future release, so as to remove the dependency of the charm on the
7# cloud provider (EC2 in this case)
8hostname=`curl http://169.254.169.254/latest/meta-data/local-hostname`
9
10# Get the mysql password that was generated by the install hook6# Get the mysql password that was generated by the install hook
11password=`cat /var/lib/juju/mysql.passwd`7password=`cat /var/lib/juju/mysql.passwd`
128
139
=== modified file 'examples/oneiric/wordpress/hooks/config-changed'
--- examples/oneiric/wordpress/hooks/config-changed 2011-09-15 18:56:08 +0000
+++ examples/oneiric/wordpress/hooks/config-changed 2011-09-29 06:40:31 +0000
@@ -1,6 +1,6 @@
1#!/bin/bash1#!/bin/bash
22
3hostname=`curl -s http://169.254.169.254/latest/meta-data/public-hostname`3hostname=`hostname`
4plugin_dir="/var/www/$hostname/wp-content/plugins"4plugin_dir="/var/www/$hostname/wp-content/plugins"
5plugin_url=`config-get plugin`5plugin_url=`config-get plugin`
66
77
=== modified file 'examples/oneiric/wordpress/hooks/db-relation-changed'
--- examples/oneiric/wordpress/hooks/db-relation-changed 2011-09-15 18:56:08 +0000
+++ examples/oneiric/wordpress/hooks/db-relation-changed 2011-09-29 06:40:31 +0000
@@ -2,12 +2,12 @@
22
3set -eu # -x for verbose logging to juju debug-log3set -eu # -x for verbose logging to juju debug-log
44
5hostname=`hostname`
5UPLOAD_PATH="/var/www/wp-uploads"6UPLOAD_PATH="/var/www/wp-uploads"
67
7# XXX The public hostname should be provided by juju itself in a8# XXX The public hostname should be provided by juju itself in a
8# future release, so as to remove the dependency of the charm on the9# future release, so as to remove the dependency of the charm on the
9# cloud provider (EC2 in this case)10# cloud provider (EC2 in this case)
10hostname=`curl http://169.254.169.254/latest/meta-data/public-hostname`
11juju-log "Retrieved hostname: $hostname"11juju-log "Retrieved hostname: $hostname"
1212
13# Check we haven't already been setup.13# Check we haven't already been setup.
1414
=== modified file 'examples/oneiric/wordpress/hooks/install'
--- examples/oneiric/wordpress/hooks/install 2011-09-15 18:56:08 +0000
+++ examples/oneiric/wordpress/hooks/install 2011-09-29 06:40:31 +0000
@@ -3,4 +3,4 @@
3juju-log "Installing wordpress, pwgen via apt-get"3juju-log "Installing wordpress, pwgen via apt-get"
4set -eu # -x for verbose logging to juju debug-log4set -eu # -x for verbose logging to juju debug-log
55
6apt-get -y install wordpress pwgen unzip6apt-get -y install wordpress pwgen unzip curl
77
=== modified file 'juju/agents/machine.py'
--- juju/agents/machine.py 2011-09-28 09:48:30 +0000
+++ juju/agents/machine.py 2011-09-29 06:40:31 +0000
@@ -96,7 +96,7 @@
96 same charm.96 same charm.
97 """97 """
9898
99 log.debug("Downloading charm %s...", charm_state.id)99 log.debug("Downloading charm %s to %s", charm_state.id, self.charms_directory)
100 return download_charm(100 return download_charm(
101 self.client, charm_state.id, self.charms_directory)101 self.client, charm_state.id, self.charms_directory)
102102
@@ -151,6 +151,7 @@
151151
152 running = yield deployment.is_running()152 running = yield deployment.is_running()
153 if not running:153 if not running:
154 log.debug("Starting service unit %s", service_unit_name)
154 yield deployment.start(155 yield deployment.start(
155 self.get_machine_id(), self.client.servers, bundle)156 self.get_machine_id(), self.client.servers, bundle)
156 log.info("Started service unit %s", service_unit_name)157 log.info("Started service unit %s", service_unit_name)
157158
=== modified file 'juju/control/add_unit.py'
--- juju/control/add_unit.py 2011-09-27 02:14:56 +0000
+++ juju/control/add_unit.py 2011-09-29 06:40:31 +0000
@@ -3,6 +3,7 @@
3from twisted.internet.defer import inlineCallbacks3from twisted.internet.defer import inlineCallbacks
44
5from juju.control.utils import get_environment5from juju.control.utils import get_environment
6from juju.state.machine import MachineStateManager
6from juju.state.placement import place_unit7from juju.state.placement import place_unit
7from juju.state.service import ServiceStateManager8from juju.state.service import ServiceStateManager
89
910
=== modified file 'juju/lib/lxc/data/juju-create'
--- juju/lib/lxc/data/juju-create 2011-09-27 03:42:49 +0000
+++ juju/lib/lxc/data/juju-create 2011-09-29 06:40:31 +0000
@@ -19,6 +19,7 @@
1919
20 # By listing the container name first we can expect hostname and20 # By listing the container name first we can expect hostname and
21 # hostname -f to return the routable name21 # hostname -f to return the routable name
22 # note that lxc-clone will overwrite this
22 cat <<EOF >/etc/hosts23 cat <<EOF >/etc/hosts
23127.0.0.1 $JUJU_CONTAINER_NAME localhost24127.0.0.1 $JUJU_CONTAINER_NAME localhost
24EOF25EOF
2526
=== modified file 'juju/machine/tests/test_unit_deployment.py'
--- juju/machine/tests/test_unit_deployment.py 2011-09-28 09:48:30 +0000
+++ juju/machine/tests/test_unit_deployment.py 2011-09-29 06:40:31 +0000
@@ -3,18 +3,17 @@
3"""3"""
4import logging4import logging
5import os5import os
6import subprocess
7import sys6import sys
87
9from twisted.internet.protocol import ProcessProtocol8from twisted.internet.protocol import ProcessProtocol
10from twisted.internet.defer import inlineCallbacks, succeed9from twisted.internet.defer import inlineCallbacks, succeed
1110
12import juju11import juju
13
14from juju.charm import get_charm_from_path12from juju.charm import get_charm_from_path
15from juju.charm.tests import local_charm_id13from juju.charm.tests import local_charm_id
16from juju.charm.tests.test_repository import RepositoryTestBase14from juju.charm.tests.test_repository import RepositoryTestBase
17from juju.lib.mocker import MATCH15from juju.lib.lxc import LXCContainer
16from juju.lib.mocker import MATCH, ANY
18from juju.lib.twistutils import get_module_directory17from juju.lib.twistutils import get_module_directory
19from juju.machine.unit import UnitMachineDeployment, UnitContainerDeployment18from juju.machine.unit import UnitMachineDeployment, UnitContainerDeployment
20from juju.machine.errors import UnitDeploymentError19from juju.machine.errors import UnitDeploymentError
@@ -328,9 +327,11 @@
328upstart_job_sample = '''\327upstart_job_sample = '''\
329description "Unit agent for riak/0"328description "Unit agent for riak/0"
330author "Juju Team <juju@lists.canonical.com>"329author "Juju Team <juju@lists.canonical.com>"
331start on (runlevel [2345] and stopped networking RESULT=ok)330start on start on filesystem or runlevel [2345]
332stop on runlevel [!2345]331stop on runlevel [!2345]
332
333respawn333respawn
334
334env JUJU_MACHINE_ID="0"335env JUJU_MACHINE_ID="0"
335env JUJU_HOME="/var/lib/juju"336env JUJU_HOME="/var/lib/juju"
336env JUJU_ZOOKEEPER="127.0.1.1:2181"337env JUJU_ZOOKEEPER="127.0.1.1:2181"
@@ -368,33 +369,17 @@
368 def test_get_upstart_job(self):369 def test_get_upstart_job(self):
369 upstart_job = self.unit_deploy.get_upstart_unit_job(370 upstart_job = self.unit_deploy.get_upstart_unit_job(
370 0, "127.0.1.1:2181")371 0, "127.0.1.1:2181")
371 self.assertEqual(self.get_normalized(upstart_job), upstart_job_sample)372 job = self.get_normalized(upstart_job)
372373 self.assertIn('JUJU_ZOOKEEPER="127.0.1.1:2181"', job)
373 @inlineCallbacks374 self.assertIn('JUJU_MACHINE_ID="0"', job)
374 def test_is_running(self):375 self.assertIn('JUJU_UNIT_NAME="riak/0"', job)
375 name = self.unit_deploy.container_name
376 mock_lxc_ls = self.mocker.mock()
377
378 self.patch(subprocess, "check_output", mock_lxc_ls)
379 mock_lxc_ls(["lxc-ls"])
380 self.mocker.result("%s\n%s" % (name, name))
381
382 mock_lxc_ls(["lxc-ls"])
383 self.mocker.result("%s\n" % name)
384
385 mock_lxc_ls(["lxc-ls"])
386 self.mocker.result("abc-0\n")
387 self.mocker.replay()
388
389 self.assertTrue((yield self.unit_deploy.is_running()))
390 self.assertFalse((yield self.unit_deploy.is_running()))
391 self.assertFalse((yield self.unit_deploy.is_running()))
392376
393 @inlineCallbacks377 @inlineCallbacks
394 def test_destroy(self):378 def test_destroy(self):
395 mock_container = self.mocker.patch(self.unit_deploy.container)379 mock_container = self.mocker.patch(self.unit_deploy.container)
396 mock_container.destroy()380 mock_container.destroy()
397 self.mocker.replay()381 self.mocker.replay()
382
398 yield self.unit_deploy.destroy()383 yield self.unit_deploy.destroy()
399384
400 output = self.output.getvalue()385 output = self.output.getvalue()
@@ -403,19 +388,36 @@
403388
404 @inlineCallbacks389 @inlineCallbacks
405 def test_start(self):390 def test_start(self):
406 self.unit_deploy.directory = rootfs = self.makeDir()391 container = LXCContainer(self.unit_name, None, None, None)
392 rootfs = self.makeDir()
393 env = dict(os.environ)
394 env["JUJU_PUBLIC_KEY"] = "dsa ..."
395 self.change_environment(**env)
396
397 mock_deploy = self.mocker.patch(self.unit_deploy)
398 # this minimally validates that we are also called with the
399 # expect public key
400 mock_deploy._get_container(ANY, ANY, ANY, env["JUJU_PUBLIC_KEY"])
401 self.mocker.result((container, rootfs))
402
403 mock_container = self.mocker.patch(container)
404 mock_container.run()
405
406 self.mocker.replay()
407
408 self.unit_deploy.directory = rootfs
407 os.makedirs(os.path.join(rootfs, "etc", "init"))409 os.makedirs(os.path.join(rootfs, "etc", "init"))
408 mock_container = self.mocker.patch(self.unit_deploy.container)
409 mock_container.run()
410 self.mocker.replay()
411410
412 yield self.unit_deploy.start("0", "127.0.1.1:2181", self.bundle)411 yield self.unit_deploy.start("0", "127.0.1.1:2181", self.bundle)
413412
414 # Verify the upstart job413 # Verify the upstart job
415 upstart_agent_name = "%s-unit-agent" % self.unit_name.replace("/", "-")414 upstart_agent_name = "%s-unit-agent.conf" % self.unit_name.replace("/", "-")
416 content = open(415 content = open(
417 os.path.join(rootfs, "etc", "init", upstart_agent_name)).read()416 os.path.join(rootfs, "etc", "init", upstart_agent_name)).read()
418 self.assertEqual(self.get_normalized(content), upstart_job_sample)417 job = self.get_normalized(content)
418 self.assertIn('JUJU_ZOOKEEPER="127.0.1.1:2181"', job)
419 self.assertIn('JUJU_MACHINE_ID="0"', job)
420 self.assertIn('JUJU_UNIT_NAME="riak/0"', job)
419421
420 # Verify the symlink exists422 # Verify the symlink exists
421 self.assertTrue(os.path.lexists(os.path.join(423 self.assertTrue(os.path.lexists(os.path.join(
@@ -432,3 +434,24 @@
432 self.assertIn("Charm extracted into container", output)434 self.assertIn("Charm extracted into container", output)
433 self.assertIn("Started container for %s" % self.unit_deploy.unit_name,435 self.assertIn("Started container for %s" % self.unit_deploy.unit_name,
434 output)436 output)
437
438 @inlineCallbacks
439 def test_get_container(self):
440 rootfs = self.makeDir()
441 container = LXCContainer(self.unit_name, None, None, None)
442
443 mock_deploy = self.mocker.patch(self.unit_deploy)
444 mock_deploy._get_master_template(ANY, ANY, ANY)
445 self.mocker.result(container)
446
447 mock_container = self.mocker.patch(container)
448 mock_container.clone(ANY)
449 self.mocker.result(container)
450
451 self.mocker.replay()
452
453 container, rootfs = yield self.unit_deploy._get_container("0", "127.0.0.1:2181", None, "dsa...")
454
455 output = self.output.getvalue()
456 self.assertIn("Container created for %s" % self.unit_deploy.unit_name , output)
457
435458
=== modified file 'juju/machine/unit.py'
--- juju/machine/unit.py 2011-09-23 20:32:51 +0000
+++ juju/machine/unit.py 2011-09-29 06:40:31 +0000
@@ -13,8 +13,7 @@
1313
14from juju.charm.bundle import CharmBundle14from juju.charm.bundle import CharmBundle
15from juju.lib.twistutils import get_module_directory15from juju.lib.twistutils import get_module_directory
16from juju.lib.lxc import LXCContainer16from juju.lib.lxc import LXCContainer, get_containers, LXCError
17from juju.providers.lxc.container import get_containers
1817
19from .errors import UnitDeploymentError18from .errors import UnitDeploymentError
2019
@@ -155,7 +154,7 @@
155description "Unit agent for %(JUJU_UNIT_NAME)s"154description "Unit agent for %(JUJU_UNIT_NAME)s"
156author "Juju Team <juju@lists.canonical.com>"155author "Juju Team <juju@lists.canonical.com>"
157156
158start on (runlevel [2345] and stopped networking RESULT=ok)157start on start on filesystem or runlevel [2345]
159stop on runlevel [!2345]158stop on runlevel [!2345]
160159
161respawn160respawn
@@ -191,15 +190,8 @@
191 self._unit_namespace = os.environ.get("JUJU_UNIT_NS")190 self._unit_namespace = os.environ.get("JUJU_UNIT_NS")
192 assert self._unit_namespace is not None, "Required unit ns not found"191 assert self._unit_namespace is not None, "Required unit ns not found"
193192
194 self.container = LXCContainer(
195 self.container_name,
196 {"JUJU_ORIGIN": "ppa",
197 "JUJU_SOURCE": "ppa:juju/ppa"},
198 #console_log=os.path.join(self.directory, "console.log"),
199 #debug_log=os.path.join(self.directory, "container.log"))
200 )
201 self.directory = self.container.rootfs
202 self.pid_file = None193 self.pid_file = None
194 self.container = LXCContainer(self.container_name, None, None, None)
203195
204 @property196 @property
205 def container_name(self):197 def container_name(self):
@@ -251,32 +243,63 @@
251 os.makedirs(host_unit_dir)243 os.makedirs(host_unit_dir)
252244
253 @inlineCallbacks245 @inlineCallbacks
246 def _get_master_template(self, machine_id, zookeeper_hosts, public_key):
247 container_template_name = "%s-%s-template" % (self._unit_namespace, machine_id)
248
249 master_template = LXCContainer(container_template_name, origin="ppa", public_key=public_key)
250
251 if not master_template.is_constructed():
252 yield self.master_template.create()
253 log.debug("Created master container %s" % container_template_name)
254
255 # it wasn't constructed and we couldn't construct it
256 if not master_template.is_constructed():
257 raise LXCError("Unable to create master container")
258
259 returnValue(master_template)
260
261 @inlineCallbacks
262 def _get_container(self, machine_id, zookeeper_hosts, bundle, public_key):
263 master_template = yield self._get_master_template(machine_id, zookeeper_hosts, public_key)
264 log.info(
265 "Creating container %s...", os.path.basename(self.directory))
266
267 container = yield master_template.clone(self.container_name)
268 directory = container.rootfs
269 log.info("Container created for %s" % self.unit_name)
270 returnValue((container, directory))
271
272 @inlineCallbacks
254 def start(self, machine_id, zookeeper_hosts, bundle):273 def start(self, machine_id, zookeeper_hosts, bundle):
255 """Start the unit.274 """Start the unit.
256275
257 Creates and starts an lxc container for the unit.276 Creates and starts an lxc container for the unit.
258 """277 """
259 if not os.path.exists(self.directory):278 # remove any quoting around the key
260 self.container.configuration["JUJU_ZOOKEEPER"] = zookeeper_hosts279 public_key = os.environ.get("JUJU_PUBLIC_KEY", "")
261 log.info(280 public_key = public_key.strip("'\"")
262 "Creating container %s...", os.path.basename(self.directory))
263 yield self.container.create()
264 log.info("Container created for %s" % self.unit_name)
265281
282 # Build a template container that can be cloned in deploy
283 # we leave the loosely initialized self.container in place for
284 # the class as thats all we need for methods other than start.
285 container, directory = yield self._get_container(machine_id,
286 zookeeper_hosts,
287 bundle,
288 public_key)
266 # Create state directories for unit in the container289 # Create state directories for unit in the container
267 self.setup_directories()290 self.setup_directories()
268291
269 # Extract the charm bundle292 # Extract the charm bundle
270 charm_path = os.path.join(293 charm_path = os.path.join(
271 self.directory, "var", "lib", "juju", "units",294 directory, "var", "lib", "juju", "units",
272 self.unit_path_name, "charm")295 self.unit_path_name, "charm")
273 bundle.extract_to(charm_path)296 bundle.extract_to(charm_path)
274 log.debug("Charm extracted into container")297 log.debug("Charm extracted into container")
275298
276 # Write upstart file for the agent into the container299 # Write upstart file for the agent into the container
277 upstart_path = os.path.join(300 upstart_path = os.path.join(
278 self.directory, "etc", "init",301 directory, "etc", "init",
279 "%s-unit-agent" % self.unit_path_name)302 "%s-unit-agent.conf" % self.unit_path_name)
280 with open(upstart_path, "w") as fh:303 with open(upstart_path, "w") as fh:
281 fh.write(self.get_upstart_unit_job(machine_id, zookeeper_hosts))304 fh.write(self.get_upstart_unit_job(machine_id, zookeeper_hosts))
282305
@@ -285,12 +308,12 @@
285 self.juju_home, "units", self.unit_path_name, "unit.log")308 self.juju_home, "units", self.unit_path_name, "unit.log")
286 if not os.path.lexists(unit_log_path_host):309 if not os.path.lexists(unit_log_path_host):
287 os.symlink(310 os.symlink(
288 os.path.join(self.directory, "var", "log", "juju",311 os.path.join(directory, "var", "log", "juju",
289 "unit-%s.log" % self.unit_path_name),312 "unit-%s.log" % self.unit_path_name),
290 unit_log_path_host)313 unit_log_path_host)
291314
292 log.debug("Starting container...")315 log.debug("Starting container...")
293 yield self.container.run()316 yield container.run()
294 log.info("Started container for %s" % self.unit_name)317 log.info("Started container for %s" % self.unit_name)
295318
296 @inlineCallbacks319 @inlineCallbacks
@@ -308,7 +331,10 @@
308 # TODO: container running may not imply agent running. the331 # TODO: container running may not imply agent running. the
309 # pid file has the pid from the container, we need a container332 # pid file has the pid from the container, we need a container
310 # pid -> host pid mapping to query status from the machine agent.333 # pid -> host pid mapping to query status from the machine agent.
311 # alternatively querying zookeeper for the unit agent presence node.334 # alternatively querying zookeeper for the unit agent presence
335 # node.
336 if not self.container:
337 returnValue(False)
312 container_map = yield get_containers(338 container_map = yield get_containers(
313 prefix=self.container.container_name)339 prefix=self.container.container_name)
314 returnValue(container_map.get(self.container.container_name, False))340 returnValue(container_map.get(self.container.container_name, False))
315341
=== modified file 'juju/providers/lxc/__init__.py'
--- juju/providers/lxc/__init__.py 2011-09-27 02:51:59 +0000
+++ juju/providers/lxc/__init__.py 2011-09-29 06:40:31 +0000
@@ -6,13 +6,13 @@
6from txzookeeper import ZookeeperClient6from txzookeeper import ZookeeperClient
77
8from juju.errors import ProviderError, EnvironmentNotFound8from juju.errors import ProviderError, EnvironmentNotFound
9from juju.lib.lxc import LXCContainer9from juju.lib.lxc import LXCContainer, get_containers
10from juju.lib.zk import Zookeeper10from juju.lib.zk import Zookeeper
11from juju.providers.common.base import MachineProviderBase11from juju.providers.common.base import MachineProviderBase
12from juju.providers.common.files import FileStorage12from juju.providers.common.files import FileStorage
13from juju.providers.common.connect import ZookeeperConnect13from juju.providers.common.connect import ZookeeperConnect
14from juju.providers.common.utils import get_user_authorized_keys
14from juju.providers.lxc.agent import ManagedMachineAgent15from juju.providers.lxc.agent import ManagedMachineAgent
15from juju.providers.lxc.container import get_containers
16from juju.providers.lxc.machine import LocalMachine16from juju.providers.lxc.machine import LocalMachine
17from juju.providers.lxc.network import Network17from juju.providers.lxc.network import Network
18from juju.providers.lxc.pkg import check_packages18from juju.providers.lxc.pkg import check_packages
@@ -107,6 +107,13 @@
107 hierarchy = StateHierarchy(client, admin_identity, "local", "lxc")107 hierarchy = StateHierarchy(client, admin_identity, "local", "lxc")
108 yield hierarchy.initialize()108 yield hierarchy.initialize()
109109
110 # Store user credentials from the running user
111 try:
112 public_key = get_user_authorized_keys(self.config)
113 public_key = public_key.strip()
114 except LookupError, e:
115 raise ProviderError(str(e))
116
110 # Startup the machine agent117 # Startup the machine agent
111 log.info("Starting machine agent...")118 log.info("Starting machine agent...")
112 pid_file = os.path.join(self._directory, "machine-agent.pid")119 pid_file = os.path.join(self._directory, "machine-agent.pid")
@@ -115,7 +122,9 @@
115 zookeeper_hosts=zookeeper.address,122 zookeeper_hosts=zookeeper.address,
116 machine_id="0",123 machine_id="0",
117 juju_directory=self._directory,124 juju_directory=self._directory,
118 log_file=log_file)125 log_file=log_file,
126 juju_unit_namespace=self._qualified_name,
127 public_key=public_key)
119 yield agent.start()128 yield agent.start()
120129
121 log.info("Environment bootstrapped")130 log.info("Environment bootstrapped")
@@ -150,7 +159,7 @@
150 def _destroy_containers(self):159 def _destroy_containers(self):
151 container_map = yield get_containers(self._qualified_name)160 container_map = yield get_containers(self._qualified_name)
152 for container_name in container_map:161 for container_name in container_map:
153 container = LXCContainer(container_name)162 container = LXCContainer(container_name, None, None, None)
154 if container_map[container.container_name]:163 if container_map[container.container_name]:
155 yield container.stop()164 yield container.stop()
156 yield container.destroy()165 yield container.destroy()
157166
=== modified file 'juju/providers/lxc/agent.py'
--- juju/providers/lxc/agent.py 2011-09-23 20:32:51 +0000
+++ juju/providers/lxc/agent.py 2011-09-29 06:40:31 +0000
@@ -1,5 +1,6 @@
1import errno1import errno
2import os2import os
3import pipes
3import subprocess4import subprocess
4import sys5import sys
56
@@ -13,7 +14,9 @@
1314
14 def __init__(15 def __init__(
15 self, pid_file, zookeeper_hosts=None, machine_id="0",16 self, pid_file, zookeeper_hosts=None, machine_id="0",
16 log_file=None, juju_directory="/var/lib/juju", juju_unit_namespace=""):17 log_file=None, juju_directory="/var/lib/juju",
18 juju_unit_namespace="",
19 public_key=None):
17 """20 """
18 :param pid_file: Path to file used to store process id.21 :param pid_file: Path to file used to store process id.
19 :param machine_id: machine id for the machine agent (0 in lxc provider)22 :param machine_id: machine id for the machine agent (0 in lxc provider)
@@ -24,6 +27,8 @@
24 a known a prefix to allow for multiple users and multiple27 a known a prefix to allow for multiple users and multiple
25 environments to create containers. The namespace should be28 environments to create containers. The namespace should be
26 unique per user and per environment.29 unique per user and per environment.
30 :param public_key: An SSH public key (string) that will be
31 used in the container for access.
27 """32 """
28 self._pid_file = pid_file33 self._pid_file = pid_file
29 self._machine_id = machine_id34 self._machine_id = machine_id
@@ -31,6 +36,7 @@
31 self._juju_directory = juju_directory36 self._juju_directory = juju_directory
32 self._juju_unit_namespace = juju_unit_namespace37 self._juju_unit_namespace = juju_unit_namespace
33 self._log_file = log_file38 self._log_file = log_file
39 self._public_key = public_key
3440
35 @inlineCallbacks41 @inlineCallbacks
36 def start(self):42 def start(self):
@@ -52,6 +58,10 @@
52 sys.executable, "-m", self.agent_module,58 sys.executable, "-m", self.agent_module,
53 "-n", "--pidfile", self._pid_file,59 "-n", "--pidfile", self._pid_file,
54 "--logfile", self._log_file]60 "--logfile", self._log_file]
61
62 if self._public_key:
63 args.insert(1, "JUJU_PUBLIC_KEY=%s" % pipes.quote(self._public_key))
64
55 yield deferToThread(subprocess.check_call, args)65 yield deferToThread(subprocess.check_call, args)
5666
57 @inlineCallbacks67 @inlineCallbacks
5868
=== removed file 'juju/providers/lxc/container.py'
--- juju/providers/lxc/container.py 2011-09-23 17:25:37 +0000
+++ juju/providers/lxc/container.py 1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
1from twisted.internet.threads import deferToThread
2
3import subprocess
4
5
6def get_containers(prefix=None):
7 """Return a dictionary of containers key names to boolean values.
8
9 A True value indicates the container is currently running. A False value
10 indicates the container is defined but in the stopped state.
11
12 :param prefix: Optionally specify a prefix that the container should
13 match any returned containers.
14 """
15 return deferToThread(_list_containers, prefix)
16
17
18def _list_containers(prefix):
19 output = subprocess.check_output(["lxc-ls"])
20 containers = {}
21 for i in filter(None, output.split("\n")):
22 if i in containers:
23 containers[i] = True
24 else:
25 containers[i] = False
26
27 if prefix:
28 remove = [k for k in containers.keys() if not k.startswith(prefix)]
29 map(containers.pop, remove)
30
31 return containers
320
=== modified file 'juju/providers/lxc/tests/test_container.py'
--- juju/providers/lxc/tests/test_container.py 2011-09-18 18:58:46 +0000
+++ juju/providers/lxc/tests/test_container.py 2011-09-29 06:40:31 +0000
@@ -3,8 +3,8 @@
3from twisted.internet.defer import inlineCallbacks3from twisted.internet.defer import inlineCallbacks
44
5from juju.lib.testing import TestCase5from juju.lib.testing import TestCase
6from juju.providers.lxc.container import get_containers6from juju.lib.lxc import get_containers
77from juju.lib import lxc
8lxc_output_sample = "\8lxc_output_sample = "\
9calendarserver\ncaprica\ngemini\nreconnoiter\nvirgo\ncalendarserver\n"9calendarserver\ncaprica\ngemini\nreconnoiter\nvirgo\ncalendarserver\n"
1010
@@ -14,12 +14,12 @@
14 @inlineCallbacks14 @inlineCallbacks
15 def test_get_containers(self):15 def test_get_containers(self):
16 lxc_ls_mock = self.mocker.mock()16 lxc_ls_mock = self.mocker.mock()
17 self.patch(subprocess, "check_output", lxc_ls_mock)17 self.patch(lxc, "_cmd", lxc_ls_mock)
18 lxc_ls_mock(["lxc-ls"])18 lxc_ls_mock(["lxc-ls"])
19 self.mocker.result(lxc_output_sample)19 self.mocker.result((0, lxc_output_sample))
20 self.mocker.replay()20 self.mocker.replay()
2121
22 container_map = yield get_containers()22 container_map = yield get_containers(None)
23 self.assertEqual(23 self.assertEqual(
24 dict(caprica=False, gemini=False, reconnoiter=False, virgo=False,24 dict(caprica=False, gemini=False, reconnoiter=False, virgo=False,
25 calendarserver=True),25 calendarserver=True),
@@ -28,9 +28,9 @@
28 @inlineCallbacks28 @inlineCallbacks
29 def test_get_containers_with_prefix(self):29 def test_get_containers_with_prefix(self):
30 lxc_ls_mock = self.mocker.mock()30 lxc_ls_mock = self.mocker.mock()
31 self.patch(subprocess, "check_output", lxc_ls_mock)31 self.patch(lxc, "_cmd", lxc_ls_mock)
32 lxc_ls_mock(["lxc-ls"])32 lxc_ls_mock(["lxc-ls"])
33 self.mocker.result(lxc_output_sample)33 self.mocker.result((0, lxc_output_sample))
34 self.mocker.replay()34 self.mocker.replay()
3535
36 container_map = yield get_containers("ca")36 container_map = yield get_containers("ca")
3737
=== modified file 'juju/providers/lxc/tests/test_provider.py'
--- juju/providers/lxc/tests/test_provider.py 2011-09-27 02:51:59 +0000
+++ juju/providers/lxc/tests/test_provider.py 2011-09-29 06:40:31 +0000
@@ -11,6 +11,7 @@
1111
12from juju.errors import ProviderError, EnvironmentNotFound12from juju.errors import ProviderError, EnvironmentNotFound
13from juju.lib.lxc import LXCContainer13from juju.lib.lxc import LXCContainer
14from juju.lib import lxc as lxc_lib
14from juju.lib.testing import TestCase15from juju.lib.testing import TestCase
15from juju.tests.common import get_test_zookeeper_address16from juju.tests.common import get_test_zookeeper_address
16from juju.lib.zk import Zookeeper17from juju.lib.zk import Zookeeper
@@ -43,8 +44,7 @@
4344
44 def test_get_placement_policy(self):45 def test_get_placement_policy(self):
45 """Lxc provider only supports local placement."""46 """Lxc provider only supports local placement."""
46 self.assertEqual(47 self.assertEqual(self.provider.get_placement_policy(), "local")
47 self.provider.get_placement_policy(), "local")
48 provider = MachineProvider(48 provider = MachineProvider(
49 "test", {"placement": "unassigned",49 "test", {"placement": "unassigned",
50 "data-dir": self.makeDir()})50 "data-dir": self.makeDir()})
@@ -122,12 +122,12 @@
122 user_name = pwd.getpwuid(os.getuid()).pw_name122 user_name = pwd.getpwuid(os.getuid()).pw_name
123 # Mock container destruction, including stopping running contianers.123 # Mock container destruction, including stopping running contianers.
124 lxc_ls_mock = self.mocker.mock()124 lxc_ls_mock = self.mocker.mock()
125 self.patch(subprocess, "check_output", lxc_ls_mock)125 self.patch(lxc_lib, "_cmd", lxc_ls_mock)
126 lxc_ls_mock(["lxc-ls"])126 lxc_ls_mock(["lxc-ls"])
127 self.mocker.result(127 self.mocker.result((0,
128 "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n"128 "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n"
129 "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n"129 "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n"
130 "%(name)s-local-test-unit-2\n" % dict(name=user_name))130 "%(name)s-local-test-unit-2\n" % dict(name=user_name)))
131131
132 mock_container = self.mocker.patch(LXCContainer)132 mock_container = self.mocker.patch(LXCContainer)
133 mock_container.stop()133 mock_container.stop()
134134
=== modified file 'misc/bash_completion.d/juju'
--- misc/bash_completion.d/juju 2011-09-15 18:58:54 +0000
+++ misc/bash_completion.d/juju 2011-09-29 06:40:31 +0000
@@ -6,7 +6,7 @@
66
7 COMPREPLY=()7 COMPREPLY=()
8 cur=${COMP_WORDS[COMP_CWORD]}8 cur=${COMP_WORDS[COMP_CWORD]}
9 cmds='add-relation add-unit bootstrap debug-hooks debug-log deploy destroy-service expose open-tunnel remove-relation remove-unit resolved set shutdown ssh status terminate-machine unexpose upgrade-charm'9 cmds='add-relation add-unit bootstrap debug-hooks debug-log deploy destroy-environment destroy-service expose open-tunnel remove-relation remove-unit resolved set ssh status terminate-machine unexpose upgrade-charm'
10 globalOpts=( -h --verbose -v --log-file)10 globalOpts=( -h --verbose -v --log-file)
1111
12 # do ordinary expansion if we are anywhere after a -- argument12 # do ordinary expansion if we are anywhere after a -- argument
@@ -47,7 +47,7 @@
47 optEnums=( )47 optEnums=( )
48 fixedWords=( )48 fixedWords=( )
49 case "$cmd" in49 case "$cmd" in
50 add-relation|debug-hooks|destory-service|expose-service|unexpose-service|open-tunnel|remove-relation|remove-unit|set|ssh|terminate-machine)50 add-relation|debug-hooks|destroy-environment|destory-service|expose-service|unexpose-service|open-tunnel|remove-relation|remove-unit|set|ssh|terminate-machine)
51 cmdOpts=( --environment )51 cmdOpts=( --environment )
52 ;;52 ;;
53 bootstrap)53 bootstrap)
5454
=== modified file 'setup.py'
--- setup.py 2011-09-15 19:07:58 +0000
+++ setup.py 2011-09-29 06:40:31 +0000
@@ -30,6 +30,7 @@
30 url="https://launchpad.net/juju",30 url="https://launchpad.net/juju",
31 license="GPL",31 license="GPL",
32 packages=find_packages(),32 packages=find_packages(),
33 package_data={"juju.lib.lxc": ["data/juju-create", "data/lxc.conf"]},
33 scripts=glob("./bin/*"),34 scripts=glob("./bin/*"),
34 classifiers=[35 classifiers=[
35 "Development Status :: 4 - Beta",36 "Development Status :: 4 - Beta",

Subscribers

People subscribed via source and target branches

to status/vote changes: