Merge lp:~hazmat/pyjuju/local-provider into lp:pyjuju
- local-provider
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Gustavo Niemeyer | ||||
Approved revision: | 375 | ||||
Merged at revision: | 356 | ||||
Proposed branch: | lp:~hazmat/pyjuju/local-provider | ||||
Merge into: | lp:pyjuju | ||||
Prerequisite: | lp:~hazmat/pyjuju/managed-agent | ||||
Diff against target: |
649 lines (+524/-9) 11 files modified
juju/lib/zk.py (+3/-1) juju/providers/common/connect.py (+2/-2) juju/providers/lxc/__init__.py (+210/-1) juju/providers/lxc/agent.py (+4/-3) juju/providers/lxc/container.py (+29/-0) juju/providers/lxc/pkg.py (+18/-0) juju/providers/lxc/tests/test_container.py (+38/-0) juju/providers/lxc/tests/test_pkg.py (+27/-0) juju/providers/lxc/tests/test_provider.py (+177/-0) juju/state/tests/test_utils.py (+13/-0) juju/state/utils.py (+3/-2) |
||||
To merge this branch: | bzr merge lp:~hazmat/pyjuju/local-provider | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
Review via email: mp+76299@code.launchpad.net |
This proposal supersedes a proposal from 2011-09-19.
Commit message
Description of the change
Local/lxc for juju development/
Implements a local machine provider using lxc.
Resubmit with pre-requisite
- 370. By Kapil Thangavelu
-
ensure wait for agent start, state save, and log file present
- 371. By Kapil Thangavelu
-
setup log file for the agent
- 372. By Kapil Thangavelu
-
fully initialize the managed agent with all arges on bootstrap
- 373. By Kapil Thangavelu
-
additional logging
- 374. By Kapil Thangavelu
-
correct pid file usage
- 375. By Kapil Thangavelu
-
address review comments, local provider tests don't require pkg install
Kapil Thangavelu (hazmat) wrote : | # |
Excerpts from Gustavo Niemeyer's message of Thu Sep 22 16:03:25 UTC 2011:
> Review: Approve
>
> Thanks Kapil. Good stuff in record time.
>
Thanks for the review!
>
> [1]
>
> Tests fail due to missing dependencies in the environment.
>
> =======
> [ERROR]
> Traceback (most recent call last):
> File "/usr/lib/
> result = g.send(result)
> File "/home/
> ", ".join(
> juju.errors.
>
> Not sure if we should just say "install the packages" as the answer for
> that one, though.
>
I'll move this to a package level global and patch it for testing.
>
> [2]
>
> + # Install required packages
>
> It's checking for, rather than installing. (which is fine, message just
> needs fixing)
>
>
> [3]
>
> Another one:
>
> File "/home/
> net_attributes = yield net.get_
> File "/usr/lib/
> result = context.call(ctx, function, *args, **kwargs)
> File "/usr/lib/
> return self.currentCon
> File "/usr/lib/
> return func(*args,**kw)
> File "/home/
> output = subprocess.
> File "/usr/lib/
> raise CalledProcessEr
> subprocess.
>
> juju.providers.
>
> Apparently virsh is being called for real in the system for tests to pass.
>
fixed, nice catch, i should do some runs without the required packages.
>
> [4]
>
> + log.debug("Starting zookeeper...")
>
> These debug messages feel like good info for the user. I suggest making them
> visible through log.info instead.
>
Done.
> [5]
>
> + # Default to running zookeeper as the user, unless the user is root.
> + zookeeper_user = os.geteuid() == 0 and "zookeeper" or None
>
> This shorthand version is actually not that short and makes things
> significantly less straightforward to parse when reading.
>
> Compare it to:
>
> zookeeper_user = None
> if os.getuid() == 0:
> zookeeper_user = "zookeeper"
>
> Please also tweak the comment if possible (or remove since the version above
> is clear). The comment is confusing since it's running as the zookeeper user
> if the user is root, and it's easy to read it as stating the...
Preview Diff
1 | === modified file 'juju/lib/zk.py' |
2 | --- juju/lib/zk.py 2011-09-16 00:36:31 +0000 |
3 | +++ juju/lib/zk.py 2011-09-22 19:36:25 +0000 |
4 | @@ -49,7 +49,7 @@ |
5 | |
6 | class Zookeeper(object): |
7 | |
8 | - def __init__(self, run_dir, port, host=None, |
9 | + def __init__(self, run_dir, port=None, host=None, |
10 | zk_location="system", user=None, group=None, |
11 | use_deferred=True): |
12 | """ |
13 | @@ -75,6 +75,8 @@ |
14 | self._use_deferred = use_deferred |
15 | |
16 | def start(self): |
17 | + assert self._port is not None |
18 | + |
19 | if self._use_deferred: |
20 | return deferToThread(self._start) |
21 | return self._start() |
22 | |
23 | === modified file 'juju/providers/common/connect.py' |
24 | --- juju/providers/common/connect.py 2011-09-15 18:50:23 +0000 |
25 | +++ juju/providers/common/connect.py 2011-09-22 19:36:25 +0000 |
26 | @@ -31,7 +31,7 @@ |
27 | candidates = yield self._provider.get_zookeeper_machines() |
28 | chosen = yield self._pick_machine(candidates) |
29 | client = yield self._connect_to_machine(chosen, share) |
30 | - yield self._wait_for_initialization(client) |
31 | + yield self.wait_for_initialization(client) |
32 | returnValue(client) |
33 | |
34 | def _pick_machine(self, machines): |
35 | @@ -55,7 +55,7 @@ |
36 | % (machine.instance_id, str(failure.value))) |
37 | |
38 | @inlineCallbacks |
39 | - def _wait_for_initialization(self, client): |
40 | + def wait_for_initialization(self, client): |
41 | exists_d, watch_d = client.exists_and_watch("/initialized") |
42 | exists = yield exists_d |
43 | if not exists: |
44 | |
45 | === modified file 'juju/providers/lxc/__init__.py' |
46 | --- juju/providers/lxc/__init__.py 2011-08-30 21:10:46 +0000 |
47 | +++ juju/providers/lxc/__init__.py 2011-09-22 19:36:25 +0000 |
48 | @@ -1,1 +1,210 @@ |
49 | -# |
50 | +import os |
51 | +import logging |
52 | + |
53 | +from twisted.internet.defer import succeed, fail, inlineCallbacks, returnValue |
54 | + |
55 | +from txzookeeper import ZookeeperClient |
56 | + |
57 | +from juju.errors import ProviderError, EnvironmentNotFound |
58 | +from juju.lib.lxc import LXCContainer |
59 | +from juju.lib.zk import Zookeeper |
60 | +from juju.providers.common.base import MachineProviderBase |
61 | +from juju.providers.common.files import FileStorage |
62 | +from juju.providers.common.connect import ZookeeperConnect |
63 | +from juju.providers.lxc.agent import ManagedMachineAgent |
64 | +from juju.providers.lxc.container import get_containers |
65 | +from juju.providers.lxc.machine import LocalMachine |
66 | +from juju.providers.lxc.network import Network |
67 | +from juju.providers.lxc.pkg import check_packages |
68 | +from juju.state.auth import make_identity |
69 | +from juju.state.initialize import StateHierarchy |
70 | +from juju.state.utils import get_open_port |
71 | + |
72 | +log = logging.getLogger("juju.local-dev") |
73 | + |
74 | +REQUIRED_PACKAGES = ["zookeeperd", "libvirt-bin", "lxc", "apt-cacher-ng"] |
75 | + |
76 | + |
77 | +class MachineProvider(MachineProviderBase): |
78 | + """LXC/Ubuntu local provider. |
79 | + |
80 | + Only the host machine is utilized. |
81 | + """ |
82 | + def __init__(self, environment_name, config): |
83 | + super(MachineProvider, self).__init__(environment_name, config) |
84 | + self._qualified_name = self._get_qualified_name() |
85 | + self._directory = os.path.join( |
86 | + self.config.get("data-dir", "/var/lib/juju/"), |
87 | + self._qualified_name) |
88 | + |
89 | + @inlineCallbacks |
90 | + def bootstrap(self): |
91 | + """Bootstrap a local development environment. |
92 | + """ |
93 | + # Check for existing environment |
94 | + state = yield self.load_state() |
95 | + if state is not False: |
96 | + raise ProviderError("Environment already bootstrapped") |
97 | + |
98 | + # Check for required packages |
99 | + log.info("Checking for required packages...") |
100 | + missing = check_packages(*REQUIRED_PACKAGES) |
101 | + if missing: |
102 | + raise ProviderError("Missing packages %s" % ( |
103 | + ", ".join(sorted(list(missing))))) |
104 | + |
105 | + # Get/create directory for zookeeper and files |
106 | + zookeeper_dir = os.path.join(self._directory, "zookeeper") |
107 | + if not os.path.exists(zookeeper_dir): |
108 | + os.makedirs(zookeeper_dir) |
109 | + |
110 | + # Start networking, and get an open port. |
111 | + log.info("Starting networking...") |
112 | + net = Network("default", subnet=122) |
113 | + # Start is a noop if its already started, which it is by default, |
114 | + # per libvirt-bin package installation |
115 | + yield net.start() |
116 | + net_attributes = yield net.get_attributes() |
117 | + port = get_open_port(net_attributes["ip"]["address"]) |
118 | + |
119 | + # Start zookeeper |
120 | + log.info("Starting zookeeper...") |
121 | + # Run zookeeper as the current user, unless we're being run as root |
122 | + # in which case run zookeeper as the 'zookeeper' user. |
123 | + zookeeper_user = None |
124 | + if os.geteuid() == 0: |
125 | + zookeeper_user = "zookeeper" |
126 | + zookeeper = Zookeeper(zookeeper_dir, |
127 | + port=port, |
128 | + host=net_attributes["ip"]["address"], |
129 | + user=zookeeper_user, group=zookeeper_user) |
130 | + |
131 | + yield zookeeper.start() |
132 | + |
133 | + # Save the zookeeper start to provider storage. |
134 | + yield self.save_state({"zookeeper-instances": ["local"], |
135 | + "zookeeper-address": zookeeper.address}) |
136 | + |
137 | + # Initialize the zookeeper state |
138 | + log.debug("Initializing state...") |
139 | + admin_identity = make_identity( |
140 | + "admin:%s" % self.config["admin-secret"]) |
141 | + client = ZookeeperClient(zookeeper.address) |
142 | + yield client.connect() |
143 | + hierarchy = StateHierarchy(client, admin_identity, "local") |
144 | + yield hierarchy.initialize() |
145 | + |
146 | + # Startup the machine agent |
147 | + log.info("Starting machine agent...") |
148 | + pid_file = os.path.join(self._directory, "machine-agent.pid") |
149 | + log_file = os.path.join(self._directory, "machine-agent.log") |
150 | + agent = ManagedMachineAgent(pid_file, |
151 | + zookeeper_hosts=zookeeper.address, |
152 | + machine_id="0", |
153 | + juju_directory=self._directory, |
154 | + log_file=log_file) |
155 | + yield agent.start() |
156 | + |
157 | + log.info("Environment bootstrapped") |
158 | + |
159 | + @inlineCallbacks |
160 | + def destroy_environment(self): |
161 | + """Shutdown the machine environment. |
162 | + """ |
163 | + # Stop all the containers |
164 | + log.info("Destroying unit containers...") |
165 | + yield self._destroy_containers() |
166 | + |
167 | + # Stop the machine agent |
168 | + log.debug("Stopping machine agent...") |
169 | + pid_file = os.path.join(self._directory, "machine-agent.pid") |
170 | + agent = ManagedMachineAgent(pid_file) |
171 | + yield agent.stop() |
172 | + |
173 | + # Stop zookeeper |
174 | + log.debug("Stopping zookeeper...") |
175 | + zookeeper_dir = os.path.join(self._directory, "zookeeper") |
176 | + zookeeper = Zookeeper(zookeeper_dir, None) |
177 | + yield zookeeper.stop() |
178 | + |
179 | + # Clean out local state |
180 | + yield self.save_state(False) |
181 | + |
182 | + # Don't stop the network since we're using the default libvirt |
183 | + log.debug("Environment destroyed.") |
184 | + |
185 | + @inlineCallbacks |
186 | + def _destroy_containers(self): |
187 | + container_map = yield get_containers(self._qualified_name) |
188 | + for container_name in container_map: |
189 | + container = LXCContainer(container_name) |
190 | + if container_map[container.container_name]: |
191 | + yield container.stop() |
192 | + yield container.destroy() |
193 | + |
194 | + @inlineCallbacks |
195 | + def connect(self, share=False): |
196 | + """Connect to juju's zookeeper. |
197 | + """ |
198 | + state = yield self.load_state() |
199 | + if not state: |
200 | + raise EnvironmentNotFound() |
201 | + client = yield ZookeeperClient(state["zookeeper-address"]).connect() |
202 | + yield ZookeeperConnect(self).wait_for_initialization(client) |
203 | + returnValue(client) |
204 | + |
205 | + def get_file_storage(self): |
206 | + """Retrieve the provider C{FileStorage} abstraction. |
207 | + """ |
208 | + storage_path = self.config.get( |
209 | + "storage-dir", os.path.join(self._directory, "files")) |
210 | + if not os.path.exists(storage_path): |
211 | + try: |
212 | + os.makedirs(storage_path) |
213 | + except OSError: |
214 | + raise ProviderError( |
215 | + "Unable to create file storage for environment") |
216 | + return FileStorage(storage_path) |
217 | + |
218 | + def start_machine(self, machine_data, master=False): |
219 | + """Start a machine in the provider. |
220 | + |
221 | + @param machine_data: a dictionary of data to pass along to the newly |
222 | + launched machine. |
223 | + |
224 | + @param master: if True, machine will initialize the juju admin |
225 | + and run a provisioning agent. |
226 | + """ |
227 | + return fail(ProviderError("Only local machine available")) |
228 | + |
229 | + def shutdown_machine(self, machine_id): |
230 | + return fail(ProviderError( |
231 | + "Not enabled for local dev, use remove-unit")) |
232 | + |
233 | + def get_machines(self, instance_ids=()): |
234 | + """List machines running in the provider. |
235 | + |
236 | + @param instance_ids: ids of instances you want to get. Leave blank |
237 | + to list all machines owned by juju. |
238 | + |
239 | + @return: a list of LocalMachine, always contains one item. |
240 | + |
241 | + @raise: MachinesNotFound |
242 | + """ |
243 | + if instance_ids and instance_ids != [LocalMachine.instance_id]: |
244 | + raise ProviderError("Only local machine available") |
245 | + return succeed([LocalMachine()]) |
246 | + |
247 | + def _get_qualified_name(self): |
248 | + """Get a qualified environment name for use by local dev resources |
249 | + """ |
250 | + # Ensure we namespace resources by user. |
251 | + user = os.environ.get("USER") |
252 | + |
253 | + # We need sudo access for lxc (till user namespaces), use the actual |
254 | + # user. |
255 | + if user == "root": |
256 | + sudo_user = os.environ.get("SUDO_USER") |
257 | + if sudo_user: |
258 | + user = sudo_user |
259 | + return "%s-%s" % (user, self.environment_name) |
260 | |
261 | === modified file 'juju/providers/lxc/agent.py' |
262 | --- juju/providers/lxc/agent.py 2011-09-17 00:07:16 +0000 |
263 | +++ juju/providers/lxc/agent.py 2011-09-22 19:36:25 +0000 |
264 | @@ -22,16 +22,16 @@ |
265 | :param juju_directory: The directory to use for all state and logs. |
266 | """ |
267 | self._pid_file = pid_file |
268 | - self._log_file = log_file |
269 | self._machine_id = machine_id |
270 | self._zookeeper_hosts = zookeeper_hosts |
271 | self._juju_directory = juju_directory |
272 | + self._log_file = log_file |
273 | |
274 | @inlineCallbacks |
275 | def start(self): |
276 | """Start the machine agent. |
277 | """ |
278 | - assert self._zookeeper_hosts |
279 | + assert self._zookeeper_hosts and self._log_file |
280 | |
281 | if (yield self.is_running()): |
282 | return |
283 | @@ -44,7 +44,8 @@ |
284 | "JUJU_HOME=%s" % self._juju_directory, |
285 | "PYTHONPATH=%s" % ":".join(sys.path), |
286 | sys.executable, "-m", self.agent_module, |
287 | - "-n", "--pidfile", self._pid_file, "--logfile", self._log_file] |
288 | + "-n", "--pidfile", self._pid_file, |
289 | + "--logfile", self._log_file] |
290 | |
291 | yield deferToThread(subprocess.check_call, args) |
292 | |
293 | |
294 | === added file 'juju/providers/lxc/container.py' |
295 | --- juju/providers/lxc/container.py 1970-01-01 00:00:00 +0000 |
296 | +++ juju/providers/lxc/container.py 2011-09-22 19:36:25 +0000 |
297 | @@ -0,0 +1,29 @@ |
298 | +from twisted.internet.threads import deferToThread |
299 | + |
300 | +import subprocess |
301 | + |
302 | + |
303 | +def get_containers(prefix=None): |
304 | + """Return a dictionary of containers key names to runtime boolean value. |
305 | + |
306 | + :param prefix: Optionally specify a prefix that the container should |
307 | + match any returned containers. |
308 | + """ |
309 | + return deferToThread(_list_containers, prefix) |
310 | + |
311 | + |
312 | +def _list_containers(prefix): |
313 | + output = subprocess.check_output(["lxc-ls"]) |
314 | + |
315 | + containers = {} |
316 | + for i in filter(None, output.split("\n")): |
317 | + if i in containers: |
318 | + containers[i] = True |
319 | + else: |
320 | + containers[i] = False |
321 | + |
322 | + if prefix: |
323 | + remove = [k for k in containers.keys() if not k.startswith(prefix)] |
324 | + map(containers.pop, remove) |
325 | + |
326 | + return containers |
327 | |
328 | === added file 'juju/providers/lxc/pkg.py' |
329 | --- juju/providers/lxc/pkg.py 1970-01-01 00:00:00 +0000 |
330 | +++ juju/providers/lxc/pkg.py 2011-09-22 19:36:25 +0000 |
331 | @@ -0,0 +1,18 @@ |
332 | +import apt |
333 | + |
334 | + |
335 | +def check_packages(*packages): |
336 | + """Given a list of packages, return the packages which are not installed. |
337 | + """ |
338 | + cache = apt.Cache() |
339 | + missing = set() |
340 | + for pkg_name in packages: |
341 | + try: |
342 | + pkg = cache[pkg_name] |
343 | + except KeyError: |
344 | + missing.add(pkg_name) |
345 | + continue |
346 | + if pkg.is_installed: |
347 | + continue |
348 | + missing.add(pkg_name) |
349 | + return missing |
350 | |
351 | === added file 'juju/providers/lxc/tests/test_container.py' |
352 | --- juju/providers/lxc/tests/test_container.py 1970-01-01 00:00:00 +0000 |
353 | +++ juju/providers/lxc/tests/test_container.py 2011-09-22 19:36:25 +0000 |
354 | @@ -0,0 +1,38 @@ |
355 | +import subprocess |
356 | + |
357 | +from twisted.internet.defer import inlineCallbacks |
358 | + |
359 | +from juju.lib.testing import TestCase |
360 | +from juju.providers.lxc.container import get_containers |
361 | + |
362 | +lxc_output_sample = "\ |
363 | +calendarserver\ncaprica\ngemini\nreconnoiter\nvirgo\ncalendarserver\n" |
364 | + |
365 | + |
366 | +class GetContainersTest(TestCase): |
367 | + |
368 | + @inlineCallbacks |
369 | + def test_get_containers(self): |
370 | + lxc_ls_mock = self.mocker.mock() |
371 | + self.patch(subprocess, "check_output", lxc_ls_mock) |
372 | + lxc_ls_mock(["lxc-ls"]) |
373 | + self.mocker.result(lxc_output_sample) |
374 | + self.mocker.replay() |
375 | + |
376 | + container_map = yield get_containers() |
377 | + self.assertEqual( |
378 | + dict(caprica=False, gemini=False, reconnoiter=False, virgo=False, |
379 | + calendarserver=True), |
380 | + container_map) |
381 | + |
382 | + @inlineCallbacks |
383 | + def test_get_containers_with_prefix(self): |
384 | + lxc_ls_mock = self.mocker.mock() |
385 | + self.patch(subprocess, "check_output", lxc_ls_mock) |
386 | + lxc_ls_mock(["lxc-ls"]) |
387 | + self.mocker.result(lxc_output_sample) |
388 | + self.mocker.replay() |
389 | + |
390 | + container_map = yield get_containers("ca") |
391 | + self.assertEqual( |
392 | + dict(calendarserver=True, caprica=False), container_map) |
393 | |
394 | === added file 'juju/providers/lxc/tests/test_pkg.py' |
395 | --- juju/providers/lxc/tests/test_pkg.py 1970-01-01 00:00:00 +0000 |
396 | +++ juju/providers/lxc/tests/test_pkg.py 2011-09-22 19:36:25 +0000 |
397 | @@ -0,0 +1,27 @@ |
398 | +import apt |
399 | +from juju.lib.testing import TestCase |
400 | +from juju.providers.lxc.pkg import check_packages |
401 | + |
402 | + |
403 | +class PackageInstallTest(TestCase): |
404 | + |
405 | + def test_package_reports_missing(self): |
406 | + pkg_name = "tryton-modules-sale-opportunity" |
407 | + missing_pkg = self.mocker.mock() |
408 | + mock_cache = self.mocker.patch(apt.Cache) |
409 | + mock_cache[pkg_name] |
410 | + self.mocker.result(missing_pkg) |
411 | + missing_pkg.is_installed |
412 | + self.mocker.result(False) |
413 | + self.mocker.replay() |
414 | + self.assertEqual( |
415 | + check_packages(pkg_name), set([pkg_name])) |
416 | + |
417 | + def test_package_reports_installed(self): |
418 | + self.assertEqual( |
419 | + check_packages("python-apt"), set()) |
420 | + |
421 | + def test_package_handles_unknown(self): |
422 | + self.assertEqual( |
423 | + check_packages("global-gook"), |
424 | + set(["global-gook"])) |
425 | |
426 | === added file 'juju/providers/lxc/tests/test_provider.py' |
427 | --- juju/providers/lxc/tests/test_provider.py 1970-01-01 00:00:00 +0000 |
428 | +++ juju/providers/lxc/tests/test_provider.py 2011-09-22 19:36:25 +0000 |
429 | @@ -0,0 +1,177 @@ |
430 | +import logging |
431 | +import os |
432 | +import pwd |
433 | +import subprocess |
434 | +from StringIO import StringIO |
435 | +import zookeeper |
436 | + |
437 | +from twisted.internet.defer import succeed, inlineCallbacks |
438 | + |
439 | +from txzookeeper.tests.utils import deleteTree |
440 | + |
441 | +from juju.errors import ProviderError, EnvironmentNotFound |
442 | +from juju.lib.lxc import LXCContainer |
443 | +from juju.lib.testing import TestCase |
444 | +from juju.tests.common import get_test_zookeeper_address |
445 | +from juju.lib.zk import Zookeeper |
446 | +from juju.providers.lxc import MachineProvider |
447 | +from juju.providers import lxc |
448 | +from juju.providers.lxc.agent import ManagedMachineAgent |
449 | +from juju.providers.lxc.network import Network |
450 | + |
451 | + |
452 | +class LocalProviderTest(TestCase): |
453 | + |
454 | + @inlineCallbacks |
455 | + def setUp(self): |
456 | + self.provider = MachineProvider( |
457 | + "local-test", { |
458 | + "admin-secret": "admin:abc", "data-dir": self.makeDir()}) |
459 | + self.output = self.capture_logging( |
460 | + "juju.local-dev", level=logging.DEBUG) |
461 | + zookeeper.set_debug_level(0) |
462 | + self.client = yield self.get_zookeeper_client().connect() |
463 | + |
464 | + def tearDown(self): |
465 | + deleteTree("/", self.client.handle) |
466 | + self.client.close() |
467 | + |
468 | + @property |
469 | + def qualified_name(self): |
470 | + user_name = pwd.getpwuid(os.getuid()).pw_name |
471 | + return "%s-%s" % (user_name, self.provider.environment_name) |
472 | + |
473 | + def assertDir(self, *path_parts): |
474 | + path = os.path.join(*path_parts) |
475 | + self.assertTrue(os.path.isdir(path)) |
476 | + |
477 | + def bootstrap_mock(self): |
478 | + self.patch(lxc, "REQUIRED_PACKAGES", []) |
479 | + mock_network = self.mocker.patch(Network) |
480 | + mock_network.start() |
481 | + self.mocker.result(succeed(True)) |
482 | + mock_network.get_attributes() |
483 | + self.mocker.result({"ip": {"address": "127.0.0.1"}}) |
484 | + |
485 | + mock_zookeeper = self.mocker.patch(Zookeeper) |
486 | + mock_zookeeper.start() |
487 | + self.mocker.result(succeed(True)) |
488 | + |
489 | + mock_zookeeper.address |
490 | + self.mocker.result(get_test_zookeeper_address()) |
491 | + self.mocker.count(3) |
492 | + |
493 | + mock_agent = self.mocker.patch(ManagedMachineAgent) |
494 | + mock_agent.start() |
495 | + self.mocker.result(succeed(True)) |
496 | + |
497 | + @inlineCallbacks |
498 | + def test_bootstrap(self): |
499 | + self.bootstrap_mock() |
500 | + self.mocker.replay() |
501 | + |
502 | + yield self.provider.bootstrap() |
503 | + |
504 | + children = yield self.client.get_children("/") |
505 | + self.assertEqual( |
506 | + sorted(['services', 'charms', 'relations', 'zookeeper', |
507 | + 'initialized', 'topology', 'machines', 'units']), |
508 | + sorted(children)) |
509 | + output = self.output.getvalue() |
510 | + self.assertIn("Starting networking...", output) |
511 | + self.assertIn("Starting zookeeper...", output) |
512 | + self.assertIn("Initializing state...", output) |
513 | + self.assertIn("Starting machine agent...", output) |
514 | + self.assertIn("Environment bootstrapped", output) |
515 | + |
516 | + self.assertDir( |
517 | + self.provider.config["data-dir"], self.qualified_name, "files") |
518 | + self.assertDir( |
519 | + self.provider.config["data-dir"], self.qualified_name, "zookeeper") |
520 | + |
521 | + self.assertEqual((yield self.provider.load_state()), |
522 | + {"zookeeper-address": get_test_zookeeper_address(), |
523 | + "zookeeper-instances": ["local"]}) |
524 | + |
525 | + @inlineCallbacks |
526 | + def test_boostrap_previously_bootstrapped(self): |
527 | + """Any local state is a sign that we had a previous bootstrap. |
528 | + """ |
529 | + yield self.provider.save_state({"xyz": 1}) |
530 | + error = yield self.assertFailure( |
531 | + self.provider.bootstrap(), ProviderError) |
532 | + self.assertEqual(str(error), "Environment already bootstrapped") |
533 | + |
534 | + @inlineCallbacks |
535 | + def test_destroy_environment(self): |
536 | + """Destroying a local environment kills units, zk, and machine agent. |
537 | + """ |
538 | + user_name = pwd.getpwuid(os.getuid()).pw_name |
539 | + # Mock container destruction, including stopping running contianers. |
540 | + lxc_ls_mock = self.mocker.mock() |
541 | + self.patch(subprocess, "check_output", lxc_ls_mock) |
542 | + lxc_ls_mock(["lxc-ls"]) |
543 | + self.mocker.result( |
544 | + "%(name)s-local-test-unit-1\n%(name)s-local-test-unit-2\n" |
545 | + "%(name)s-local-test-unit-3\n%(name)s-local-test-unit-1\n" |
546 | + "%(name)s-local-test-unit-2\n" % dict(name=user_name)) |
547 | + |
548 | + mock_container = self.mocker.patch(LXCContainer) |
549 | + mock_container.stop() |
550 | + self.mocker.count(2) |
551 | + mock_container.destroy() |
552 | + self.mocker.count(3) |
553 | + |
554 | + mock_agent = self.mocker.patch(ManagedMachineAgent) |
555 | + mock_agent.stop() |
556 | + |
557 | + mock_zookeeper = self.mocker.patch(Zookeeper) |
558 | + mock_zookeeper.stop() |
559 | + yield self.provider.save_state({"i-exist": 1}) |
560 | + self.mocker.replay() |
561 | + |
562 | + yield self.provider.destroy_environment() |
563 | + |
564 | + output = self.output.getvalue() |
565 | + |
566 | + self.assertIn("Destroying unit containers..", output) |
567 | + self.assertIn("Stopping machine agent..", output) |
568 | + self.assertIn("Stopping zookeeper..", output) |
569 | + self.assertIn("Environment destroyed", output) |
570 | + |
571 | + self.assertEqual((yield self.provider.load_state()), False) |
572 | + |
573 | + @inlineCallbacks |
574 | + def test_connect(self): |
575 | + self.bootstrap_mock() |
576 | + self.mocker.replay() |
577 | + yield self.provider.bootstrap() |
578 | + client = yield self.provider.connect() |
579 | + self.assertTrue((yield client.exists("/initialized"))) |
580 | + |
581 | + def test_connect_sans_environment(self): |
582 | + return self.assertFailure(self.provider.connect(), EnvironmentNotFound) |
583 | + |
584 | + def test_shutdown_machine(self): |
585 | + return self.assertFailure( |
586 | + self.provider.shutdown_machine("local"), ProviderError) |
587 | + |
588 | + def test_start_machine(self): |
589 | + return self.assertFailure( |
590 | + self.provider.start_machine({}), ProviderError) |
591 | + |
592 | + @inlineCallbacks |
593 | + def test_get_machines(self): |
594 | + machines = yield self.provider.get_machines() |
595 | + self.assertEqual(len(machines), 1) |
596 | + self.assertEqual(machines[0].instance_id, "local") |
597 | + self.assertEqual(machines[0].dns_name, "localhost") |
598 | + |
599 | + @inlineCallbacks |
600 | + def test_get_file_storage(self): |
601 | + storage = self.provider.get_file_storage() |
602 | + content = StringIO("put") |
603 | + yield storage.put("abc", content) |
604 | + data_dir = self.provider.config["data-dir"] |
605 | + self.assertTrue("abc" in os.listdir( |
606 | + os.path.join(data_dir, self.qualified_name, "files"))) |
607 | |
608 | === modified file 'juju/state/tests/test_utils.py' |
609 | --- juju/state/tests/test_utils.py 2011-09-15 18:50:23 +0000 |
610 | +++ juju/state/tests/test_utils.py 2011-09-22 19:36:25 +0000 |
611 | @@ -244,6 +244,19 @@ |
612 | sock.close() |
613 | del sock |
614 | |
615 | + def test_get_open_port_with_host(self): |
616 | + port = get_open_port("localhost") |
617 | + self.assertTrue(isinstance(port, int)) |
618 | + |
619 | + sock = socket.socket( |
620 | + socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) |
621 | + |
622 | + # would raise an error if we got it wrong. |
623 | + sock.bind(("127.0.0.1", port)) |
624 | + sock.listen(1) |
625 | + sock.close() |
626 | + del sock |
627 | + |
628 | |
629 | class ChangeItemsTest(TestCase): |
630 | """Tests the formatting of change items. |
631 | |
632 | === modified file 'juju/state/utils.py' |
633 | --- juju/state/utils.py 2011-09-15 18:50:23 +0000 |
634 | +++ juju/state/utils.py 2011-09-22 19:36:25 +0000 |
635 | @@ -83,11 +83,12 @@ |
636 | yield client.delete(path) |
637 | |
638 | |
639 | -def get_open_port(): |
640 | +def get_open_port(host=""): |
641 | """Get an open port on the machine. |
642 | """ |
643 | temp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
644 | - temp_sock.bind(("", 0)) |
645 | + temp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
646 | + temp_sock.bind((host, 0)) |
647 | port = temp_sock.getsockname()[1] |
648 | temp_sock.close() |
649 | del temp_sock |
Thanks Kapil. Good stuff in record time.
[1]
Tests fail due to missing dependencies in the environment.
======= ======= ======= ======= ======= ======= ======= ======= ======= ======= ======= == python2. 7/dist- packages/ twisted/ internet/ defer.py" , line 1020, in _inlineCallbacks niemeyer/ src/juju/ hazmat/ local-provider/ juju/providers/ lxc/__init_ _.py", line 53, in bootstrap sorted( list(missing) )))) ProviderError: Missing packages apt-cacher-ng, libvirt-bin, lxc, zookeeperd
[ERROR]
Traceback (most recent call last):
File "/usr/lib/
result = g.send(result)
File "/home/
", ".join(
juju.errors.
Not sure if we should just say "install the packages" as the answer for
that one, though.
[2]
+ # Install required packages
It's checking for, rather than installing. (which is fine, message just
needs fixing)
[3]
Another one:
File "/home/ niemeyer/ src/juju/ hazmat/ local-provider/ juju/providers/ lxc/__init_ _.py", line 64, in bootstrap attributes( ) python2. 7/dist- packages/ twisted/ python/ threadpool. py", line 207, in _worker python2. 7/dist- packages/ twisted/ python/ context. py", line 59, in callWithContext text(). callWithContext (ctx, func, *args, **kw) python2. 7/dist- packages/ twisted/ python/ context. py", line 37, in callWithContext niemeyer/ src/juju/ hazmat/ local-provider/ juju/providers/ lxc/network. py", line 135, in get_network_ attributes check_output( ["virsh" , "net-dumpxml", name]) python2. 7/subprocess. py", line 537, in check_output ror(retcode, cmd, output=output) CalledProcessEr ror: Command '['virsh', 'net-dumpxml', 'default']' returned non-zero exit status 1
net_attributes = yield net.get_
File "/usr/lib/
result = context.call(ctx, function, *args, **kwargs)
File "/usr/lib/
return self.currentCon
File "/usr/lib/
return func(*args,**kw)
File "/home/
output = subprocess.
File "/usr/lib/
raise CalledProcessEr
subprocess.
juju.providers. lxc.tests. test_provider. LocalProviderTe st.test_ bootstrap
Apparently virsh is being called for real in the system for tests to pass.
[4]
+ log.debug("Starting zookeeper...")
These debug messages feel like good info for the user. I suggest making them
visible through log.info instead.
[5]
+ # Default to running zookeeper as the user, unless the user is root.
+ zookeeper_user = os.geteuid() == 0 and "zookeeper" or None
This shorthand version is actually not that short and makes things
significantly less straightforward to parse when reading.
Compare it to:
zookeeper_user = None
zookeeper_ user = "zookeeper"
if os.getuid() == 0:
Please also tweak the comment if possible (or remove since the version above
is clear). The comment is confusing since it's running as the zookeeper user
if the user is root, and it's easy to read it as stating the exact opposite.
[6]
+ log.info( "Destroying unit containers..") "Environment bootstrapped")
(...)
+ log.debug("Stopping zookeeper..")
(...)
+ log.info(
There's some inconsisteny in the logging. Would be nice to have
the messages ending consistently in "..." or ".".
[7]
+ "local") # "lxc")
The comment looks like a brainstorm left over.
[8]
+ """
+ Shutdown the m...