Merge lp:~fwereade/pyjuju/fix-docstrings into lp:pyjuju

Proposed by William Reade
Status: Merged
Approved by: Jim Baker
Approved revision: 344
Merged at revision: 343
Proposed branch: lp:~fwereade/pyjuju/fix-docstrings
Merge into: lp:pyjuju
Prerequisite: lp:~fwereade/pyjuju/cloud-init-class-used
Diff against target: 1294 lines (+547/-171)
25 files modified
.bzrignore (+1/-0)
docs/Makefile (+2/-1)
docs/source/conf.py (+6/-3)
docs/source/generate_modules.py (+107/-0)
docs/source/index.rst (+1/-0)
ensemble/providers/__init__.py (+1/-0)
ensemble/providers/common/__init__.py (+1/-0)
ensemble/providers/common/base.py (+67/-41)
ensemble/providers/common/bootstrap.py (+4/-2)
ensemble/providers/common/cloudinit.py (+68/-0)
ensemble/providers/common/connect.py (+10/-7)
ensemble/providers/common/findzookeepers.py (+10/-0)
ensemble/providers/common/launch.py (+32/-15)
ensemble/providers/common/state.py (+18/-0)
ensemble/providers/common/utils.py (+27/-25)
ensemble/providers/ec2/__init__.py (+32/-14)
ensemble/providers/ec2/files.py (+21/-16)
ensemble/providers/ec2/launch.py (+12/-8)
ensemble/providers/ec2/machine.py (+6/-0)
ensemble/providers/ec2/utils.py (+10/-0)
ensemble/providers/orchestra/__init__.py (+28/-13)
ensemble/providers/orchestra/cobbler.py (+36/-14)
ensemble/providers/orchestra/files.py (+25/-0)
ensemble/providers/orchestra/launch.py (+14/-9)
ensemble/providers/orchestra/machine.py (+8/-3)
To merge this branch: bzr merge lp:~fwereade/pyjuju/fix-docstrings
Reviewer Review Type Date Requested Status
Jim Baker (community) Approve
Gustavo Niemeyer Approve
Review via email: mp+74113@code.launchpad.net

Description of the change

This adds (1) automatic doc generation and (2) hopefully-consistent docstrings in ensemble.providers; so, it doesn't really the currently-linked bug as it currently stands. I was thinking I might rewrite that one, and open a series of others for other packages, but that doesn't feel like quite the right approach, so I'm open to suggestions.

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

As mentioned on IRC:

<niemeyer> fwereade: Thanks for the docstring changes
<niemeyer> fwereade: Very nice to have it being standardized
<niemeyer> fwereade: I'm slightly concerned it might be going overboard in terms of syntax in some cases.. not sure if the benefit of the linkage is worth the reduced readability in the code itself
<niemeyer> fwereade: Simple example:
<niemeyer> - @param instance_id: instance_id of the `ProviderMachine` you want.
<niemeyer> vs.
<niemeyer> 316 + :param str instance_id: :attr:`instance_id` of the
<niemeyer> 317 + :class:`ensemble.machine.ProviderMachine` you want.

Either way, I'm happy to experiment with this. +1!

review: Approve
Revision history for this message
William Reade (fwereade) wrote :

> <niemeyer> fwereade: I'm slightly concerned it might be going overboard in
> terms of syntax in some cases.. not sure if the benefit of the linkage is
> worth the reduced readability in the code itself

IMO, if you're reading the docs, you're trying to figure things out at a high level, and the linkage is beneficial (what exactly is the distinction between a MachineProvider and a ProviderMachine, again?); if you're reading the code, you're reading the *code*, and your attitude towards docstrings and comments alike is one of bitter cynicism (on a good day, you might get around to fixing them...).

> Either way, I'm happy to experiment with this. +1!

Cool -- we'll see how it goes :).

lp:~fwereade/pyjuju/fix-docstrings updated
344. By William Reade

merge parent

Revision history for this message
Jim Baker (jimbaker) wrote :

+1, overall this looks very good. It would seem possible to cross-reference imported names without being explicit as to their path (eg :class:`ProviderMachine` instead of :class:`ensemble.machine.ProviderMachine`) as long as that was unambiguous, but a quick experiment shows that Sphinx as configured right now won't do that.

Minor nits: when using singlehtml, links to source do not work. Also, the headings in that view really don't work well with the size of the left column. Maybe in general we would want to collapse the headings to the second level of the package namespace, under ensemble, that is just ensemble.agents instead of also seeing ensemble.agents.base, ensemble.agents.dummy, etc.

review: Approve
lp:~fwereade/pyjuju/fix-docstrings updated
345. By William Reade

merge trunk

346. By William Reade

singlehtml builds eschew source links; module documentation titles are now abbreviated if > 2 packages deep

Revision history for this message
William Reade (fwereade) wrote :

> Minor nits: when using singlehtml, links to source do not work.

AFAICT, that's a Sphinx issue, sadly. I've (somewhat nastily...) disabled the viewcode extension for singlehtml builds.

> Also, the
> headings in that view really don't work well with the size of the left column.
> Maybe in general we would want to collapse the headings to the second level of
> the package namespace, under ensemble, that is just ensemble.agents instead of
> also seeing ensemble.agents.base, ensemble.agents.dummy, etc.

IMO batching all docs at the second level is too much -- you certainly don't want all of ensemble.providers in a single file (do you? maybe I'm missing context). I strongly favour batching them up in exactly the same way we do the source code -- by choosing module boundaries, we've implicitly chosen a "sensible" chunking for the concepts in play, and if there's a problem with those divisions we should be rearranging the code rather than the documentation. IMO ;).

Given the undeniable ugliness, though, module titles are now abbreviated as follows (approximately inspired by twisted's documentation):

ensemble -> ensemble
ensemble.providers -> ensemble.providers
ensemble.providers.orchestra -> e.providers.orchestra
ensemble.providers.orchestra.cobbler -> e.p.orchestra.cobbler

I think it's a reasonable tradeoff: you're unlikely to get lost with two unabbreviated names' worth of context, and while titles still sometimes overflow the sidebar, it's only by a few chars.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-02-08 10:16:30 +0000
3+++ .bzrignore 2011-09-13 12:37:42 +0000
4@@ -1,5 +1,6 @@
5 /debian/files
6 /docs/build
7+/docs/source/generated
8 /_trial_temp
9 /tags
10 zookeeper.log
11
12=== modified file 'docs/Makefile'
13--- docs/Makefile 2011-03-17 17:59:16 +0000
14+++ docs/Makefile 2011-09-13 12:37:42 +0000
15@@ -3,7 +3,7 @@
16
17 # You can set these variables from the command line.
18 SPHINXOPTS =
19-SPHINXBUILD = sphinx-build
20+SPHINXBUILD = python source/generate_modules.py ../ensemble source/generated && sphinx-build
21 PAPER =
22 BUILDDIR = build
23
24@@ -36,6 +36,7 @@
25
26 clean:
27 -rm -rf $(BUILDDIR)/*
28+ -rm -rf source/generated
29
30 html:
31 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
32
33=== modified file 'docs/source/conf.py'
34--- docs/source/conf.py 2011-05-04 16:04:46 +0000
35+++ docs/source/conf.py 2011-09-13 12:37:42 +0000
36@@ -16,7 +16,7 @@
37 # If extensions (or modules to document with autodoc) are in another directory,
38 # add these directories to sys.path here. If the directory is relative to the
39 # documentation root, use os.path.abspath to make it absolute, like shown here.
40-#sys.path.append(os.path.abspath('.'))
41+sys.path.insert(0, os.path.abspath('../..'))
42
43 # -- General configuration -----------------------------------------------------
44
45@@ -30,7 +30,10 @@
46 extensions = ['sphinx.ext.autodoc']
47
48 if [int(x) for x in sphinx.__version__.split(".")] > [1, 0]:
49- extensions.append('sphinx.ext.viewcode')
50+ if "singlehtml" not in sys.argv:
51+ # singlehtml builder skips the step that would cause the _modules
52+ # directory to be created, so source links don't work
53+ extensions.append('sphinx.ext.viewcode')
54
55 # Add any paths that contain templates here, relative to this directory.
56 templates_path = ['_templates']
57@@ -144,7 +147,7 @@
58 #html_additional_pages = {}
59
60 # If false, no module index is generated.
61-#html_domain_indices = True
62+html_domain_indices = False
63
64 # If false, no index is generated.
65 #html_use_index = True
66
67=== added file 'docs/source/generate_modules.py'
68--- docs/source/generate_modules.py 1970-01-01 00:00:00 +0000
69+++ docs/source/generate_modules.py 2011-09-13 12:37:42 +0000
70@@ -0,0 +1,107 @@
71+import os
72+import sys
73+
74+INIT = "__init__.py"
75+TESTS = "tests"
76+
77+
78+def get_modules(names):
79+ if INIT in names:
80+ names.remove(INIT)
81+ return [n[:-3] for n in names if n.endswith(".py")]
82+
83+
84+def trim_dirs(root, dirs):
85+ for dir_ in dirs[:]:
86+ if dir_ == TESTS:
87+ dirs.remove(TESTS)
88+ if not os.path.exists(os.path.join(root, dir_, INIT)):
89+ dirs.remove(dir_)
90+ return dirs
91+
92+
93+def module_name(base, root, name=None):
94+ path = root[len(base) + 1:]
95+ if name:
96+ path = os.path.join(path, name)
97+ return path.replace("/", ".")
98+
99+
100+def collect_modules(src):
101+ src = os.path.abspath(src)
102+ base = os.path.dirname(src)
103+
104+ names = []
105+ for root, dirs, files in os.walk(src):
106+ modules = get_modules(files)
107+ packages = trim_dirs(root, dirs)
108+ if modules or packages:
109+ names.append(module_name(base, root))
110+ for name in modules:
111+ names.append(module_name(base, root, name))
112+ return sorted(names)
113+
114+
115+def subpackages(names, parent):
116+ return [name for name in names
117+ if name.startswith(parent) and name != parent]
118+
119+
120+def dst_file(dst, name):
121+ return open(os.path.join(dst, "%s.rst" % name), "w")
122+
123+
124+def write_title(f, name, kind):
125+ f.write("%s\n%s\n\n" % (name, kind * len(name)))
126+
127+
128+def write_packages(f, names):
129+ for name in names:
130+ f.write(" " * name.count("."))
131+ f.write("* :mod:`%s`\n" % name)
132+ f.write("\n")
133+
134+
135+def abbreviate(name):
136+ parts = name.split(".")
137+ short_parts = [part[0] for part in parts[:-2]]
138+ return ".".join(short_parts + parts[-2:])
139+
140+
141+def write_module(f, name, subs):
142+ write_title(f, abbreviate(name), "=")
143+ f.write(".. automodule:: %s\n"
144+ " :members:\n"
145+ " :undoc-members:\n"
146+ " :show-inheritance:\n\n"
147+ % name)
148+ if subs:
149+ write_title(f, "Subpackages", "-")
150+ write_packages(f, subs)
151+
152+
153+def write_module_list(f, names):
154+ write_title(f, "Ensemble modules", "=")
155+ write_packages(f, names)
156+ f.write(".. toctree::\n :hidden:\n\n")
157+ for name in names:
158+ f.write(" %s\n" % name)
159+
160+
161+def generate(src, dst):
162+ names = collect_modules(src)
163+
164+ if not os.path.exists(dst):
165+ os.makedirs(dst)
166+
167+ with dst_file(dst, "modules") as f:
168+ write_module_list(f, names)
169+
170+ for name in names:
171+ with dst_file(dst, name) as f:
172+ write_module(f, name, subpackages(names, name))
173+
174+
175+if __name__ == "__main__":
176+ src, dst = sys.argv[1:]
177+ generate(src, dst)
178
179=== modified file 'docs/source/index.rst'
180--- docs/source/index.rst 2011-08-10 22:47:54 +0000
181+++ docs/source/index.rst 2011-09-13 12:37:42 +0000
182@@ -23,6 +23,7 @@
183 ensemble-internals
184 ensemble-drafts
185 glossary
186+ generated/modules
187
188
189 Index and Glossary
190
191=== modified file 'ensemble/providers/__init__.py'
192--- ensemble/providers/__init__.py 2010-06-14 22:56:13 +0000
193+++ ensemble/providers/__init__.py 2011-09-13 12:37:42 +0000
194@@ -0,0 +1,1 @@
195+"""Contains code specific to the various machine provider backends"""
196
197=== modified file 'ensemble/providers/common/__init__.py'
198--- ensemble/providers/common/__init__.py 2011-07-13 02:26:21 +0000
199+++ ensemble/providers/common/__init__.py 2011-09-13 12:37:42 +0000
200@@ -0,0 +1,1 @@
201+"""Contains code common to more than one machine provider"""
202
203=== modified file 'ensemble/providers/common/base.py'
204--- ensemble/providers/common/base.py 2011-09-13 09:53:33 +0000
205+++ ensemble/providers/common/base.py 2011-09-13 12:37:42 +0000
206@@ -18,20 +18,20 @@
207 To write a working subclass, you will need to override the following
208 methods:
209
210- * get_file_storage
211- * start_machine
212- * get_machines
213- * shutdown_machines
214- * open_port
215- * close_port
216- * get_opened_ports
217+ * :meth:`get_file_storage`
218+ * :meth:`start_machine`
219+ * :meth:`get_machines`
220+ * :meth:`shutdown_machines`
221+ * :meth:`open_port`
222+ * :meth:`close_port`
223+ * :meth:`get_opened_ports`
224
225 You may want to override the following methods, but you should be careful
226- to call MachineProviderBase's implementation (or be very sure you don't
227- need to:
228+ to call :class:`MachineProviderBase`'s implementation (or be very sure you
229+ don't need to:
230
231- * __init__
232- * get_serialization_data
233+ * :meth:`__init__`
234+ * :meth:`get_serialization_data`
235
236 You probably shouldn't override anything else.
237 """
238@@ -65,29 +65,39 @@
239 def start_machine(self, machine_data, master=False):
240 """Start a machine in the provider.
241
242- `machine_data`: a dictionary describing the machine to be launched.
243+ :param dict machine_data: desired characteristics of the new machine;
244+ it must include a "machine-id" key, and may include a "constraints"
245+ key to specify the underlying OS and hardware (where available).
246
247- `master`: if True, machine will initialize the ensemble admin and run
248- a provisioning agent, in addition to running a machine agent
249+ :param bool master: if True, machine will initialize the ensemble admin
250+ and run a provisioning agent, in addition to running a machine
251+ agent.
252 """
253 raise NotImplementedError()
254
255 def get_machines(self, instance_ids=()):
256 """List machines running in the provider.
257
258- @param instance_ids: ids of instances you want to get. Leave empty
259- to list all machines owned by this provider.
260-
261- @return: a list of provider-specific ProviderMachines.
262-
263- @raise: MachinesNotFound
264+ :param list instance_ids: ids of instances you want to get. Leave empty
265+ to list every :class:`ensemble.machine.ProviderMachine` owned by
266+ this provider.
267+
268+ :return: a list of :class:`ensemble.machine.ProviderMachine` instances
269+ :rtype: :class:`twisted.internet.defer.Deferred`
270+
271+ :raises: :exc:`ensemble.errors.MachinesNotFound`
272 """
273 raise NotImplementedError()
274
275 def shutdown_machines(self, machines):
276 """Terminate machines associated with this provider.
277
278- @param machines: list of machines to shut down
279+ :param machines: machines to shut down
280+ :type machines: list of :class:`ensemble.machine.ProviderMachine`
281+
282+ :return: list of terminated :class:`ensemble.machine.ProviderMachine`
283+ instances
284+ :rtype: :class:`twisted.internet.defer.Deferred`
285 """
286 raise NotImplementedError()
287
288@@ -110,22 +120,27 @@
289 def get_zookeeper_machines(self):
290 """Find running zookeeper instances.
291
292- @return: the first valid instance found as a single element list.
293+ :return: all running or starting machines configured to run zookeeper,
294+ as a list of :class:`ensemble.machine.ProviderMachine`
295+ :rtype: :class:`twisted.internet.defer.Deferred`
296
297- @raise: `EnvironmentNotFound`
298+ :raises: :class:`ensemble.errors.EnvironmentNotFound`
299 """
300 return find_zookeepers(self)
301
302 def connect(self, share=False):
303 """Attempt to connect to a running zookeeper node.
304
305- @param share: where feasible, attempt to share a connection with other
306- clients
307-
308- @return: an open `txzookeeper.client.ZookeeperClient`
309-
310- @raise: `EnvironmentNotFound` when no zookeepers exist
311- @raise: `EnvironmentPending` when zookeepers exist but connect fails
312+ :param bool share: where feasible, attempt to share a connection with
313+ other clients
314+
315+ :return: an open :class:`txzookeeper.client.ZookeeperClient`
316+ :rtype: :class:`twisted.internet.defer.Deferred`
317+
318+ :raises: :exc:`ensemble.errors.EnvironmentNotFound` when no zookeepers
319+ exist
320+ :raises: :exc:`ensemble.errors.EnvironmentPending` when zookeepers
321+ exist but connection attempt fails
322 """
323 return ZookeeperConnect(self).run(share=share)
324
325@@ -136,11 +151,13 @@
326 def get_machine(self, instance_id):
327 """Retrieve a provider machine by instance id.
328
329- @param instance_id: instance_id of the `ProviderMachine` you want.
330-
331- @return: a single `ProviderMachine`
332-
333- @raise: `MachinesNotFound`
334+ :param str instance_id: :attr:`instance_id` of the
335+ :class:`ensemble.machine.ProviderMachine` you want.
336+
337+ :return: the requested :class:`ensemble.machine.ProviderMachine`
338+ :rtype: :class:`twisted.internet.defer.Deferred`
339+
340+ :raises: :exc:`ensemble.errors.MachinesNotFound`
341 """
342 d = self.get_machines([instance_id])
343 d.addCallback(itemgetter(0))
344@@ -149,9 +166,12 @@
345 def shutdown_machine(self, machine):
346 """Terminate one machine associated with this provider.
347
348- @param machine: ProviderMachine to shut down.
349-
350- @raise: MachinesNotFound
351+ :param machine: :class:`ensemble.machine.ProviderMachine` to shut down.
352+
353+ :return: the terminated :class:`ensemble.machine.ProviderMachine`.
354+ :rtype: :class:`twisted.internet.defer.Deferred`
355+
356+ :raises: :exc:`ensemble.errors.MachinesNotFound`
357 """
358 d = self.shutdown_machines([machine])
359 d.addCallback(itemgetter(0))
360@@ -159,7 +179,10 @@
361
362 @inlineCallbacks
363 def destroy_environment(self):
364- """Clear ensemble state and terminate all associated machines"""
365+ """Clear ensemble state and terminate all associated machines
366+
367+ :rtype: :class:`twisted.internet.defer.Deferred`
368+ """
369 try:
370 yield self.save_state({})
371 finally:
372@@ -170,13 +193,16 @@
373 def save_state(self, state):
374 """Save state to the provider.
375
376- @param state: a dictionary to persist
377+ :param dict state: state to persist
378+
379+ :rtype: :class:`twisted.internet.defer.Deferred`
380 """
381 return SaveState(self).run(state)
382
383 def load_state(self):
384 """Load state from the provider.
385
386- @return: a previously-persisted dictionary, or False
387+ :return: the current state dict, or False
388+ :rtype: :class:`twisted.internet.defer.Deferred`
389 """
390 return LoadState(self).run()
391
392=== modified file 'ensemble/providers/common/bootstrap.py'
393--- ensemble/providers/common/bootstrap.py 2011-08-17 12:04:56 +0000
394+++ ensemble/providers/common/bootstrap.py 2011-09-13 12:37:42 +0000
395@@ -16,8 +16,10 @@
396 def run(self):
397 """Get an existing zookeeper, or launch a new one.
398
399- @return: single-element list containing an appropriate
400- ProviderMachine instance for the zookeeper.
401+ :return: a single-element list containing an appropriate
402+ :class:`ensemble.machine.ProviderMachine` instance, set up to run
403+ zookeeper and a provisioning agent.
404+ :rtype: :class:`twisted.internet.defer.Deferred`
405 """
406 machines = self._provider.get_zookeeper_machines()
407 machines.addCallback(self._on_machines_found)
408
409=== modified file 'ensemble/providers/common/cloudinit.py'
410--- ensemble/providers/common/cloudinit.py 2011-09-13 09:53:33 +0000
411+++ ensemble/providers/common/cloudinit.py 2011-09-13 12:37:42 +0000
412@@ -51,6 +51,11 @@
413
414
415 class CloudInit(object):
416+ """Encapsulates ensemble-specific machine initialisation.
417+
418+ For more information on the mechanism used, see
419+ :func:`ensemble.providers.common.utils.format_cloud_init`.
420+ """
421
422 def __init__(self):
423 self._machine_id = None
424@@ -64,13 +69,38 @@
425 self._zookeeper_secret = None
426
427 def add_ssh_key(self, key):
428+ """Add an SSH public key.
429+
430+ :param key: an SSH key to allow to connect to the machine
431+
432+ You have to set at least one SSH key.
433+ """
434 self._ssh_keys.append(key)
435
436 def enable_bootstrap(self):
437+ """Make machine run a zookeeper and a provisioning agent."""
438 self._zookeeper = True
439 self._provision = True
440
441 def set_ensemble_source(self, branch=None, ppa=False, distro=False):
442+ """Set the version of ensemble the machine should run.
443+
444+ :param branch: location from which to check out ensemble; for example,
445+ "lp:~someone/ensemble/some-feature-branch".
446+ :type branch: str or None
447+
448+ :param bool ppa: if True, get the latest version of ensemble from its
449+ Private Package Archive.
450+
451+ :param bool distro: if True, get the default ensemble version from the
452+ OS distribution.
453+
454+ :raises: :exc:`ensemble.errors.CloudInitError` if more than one option,
455+ or fewer than one options, are specified.
456+
457+ Note that you don't need to call this method; the ensemble source
458+ defaults to the ensemble PPA.
459+ """
460 if len(filter(None, (branch, ppa, distro))) != 1:
461 raise CloudInitError("Please specify one source")
462 if branch:
463@@ -81,21 +111,59 @@
464 self._source = _DISTRO
465
466 def set_machine_id(self, id):
467+ """Specify the ensemble machine ID.
468+
469+ :param str id: the desired ID.
470+
471+ You have to set the machine ID.
472+ """
473 self._machine_id = id
474
475 def set_instance_id_accessor(self, expr):
476+ """Specify the provider-specific instance ID.
477+
478+ :param str expr: a snippet of shell script that will, when executed on
479+ the machine, evaluate to the machine's instance ID.
480+
481+ You have to set the instance ID.
482+ """
483 self._instance_id = expr
484
485 def set_provider_type(self, type_):
486+ """Specify the provider type for this machine.
487+
488+ :param str type_: the provider type.
489+
490+ You have to set the provider type.
491+ """
492 self._provider_type = type_
493
494 def set_zookeeper_machines(self, machines):
495+ """Specify master :class:`ensemble.machine.ProviderMachine` instances.
496+
497+ :param machines: machines running zookeeper which already exist.
498+ :type machines: list of :class:`ensemble.machine.ProviderMachine`
499+
500+ You don't have to set this, so long as the machine you are starting is
501+ itself a zookeeper instance.
502+ """
503 self._zookeeper_hosts = [m.private_dns_name for m in machines]
504
505 def set_zookeeper_secret(self, secret):
506+ """Specify the admin password for zookeepeer.
507+
508+ You only need to set this if this machine will be a zookeeper instance.
509+ """
510 self._zookeeper_secret = secret
511
512 def render(self):
513+ """Get content for a cloud-init file with appropriate specifications.
514+
515+ :rtype: str
516+
517+ :raises: :exc:`ensemble.errors.CloudInitError` if there isn't enough
518+ information to create a useful cloud-init.
519+ """
520 self._validate()
521 return format_cloud_init(
522 self._ssh_keys,
523
524=== modified file 'ensemble/providers/common/connect.py'
525--- ensemble/providers/common/connect.py 2011-08-23 09:17:27 +0000
526+++ ensemble/providers/common/connect.py 2011-09-13 12:37:42 +0000
527@@ -17,13 +17,16 @@
528 def run(self, share=False):
529 """Attempt to connect to a running zookeeper node.
530
531- @param share: where feasible, attempt to share a connection with other
532- clients
533-
534- @return: an open txzookeeper.client.ZookeeperClient
535-
536- @raise: EnvironmentNotFound when no zookeepers exist
537- @raise: EnvironmentPending when zookeepers exist but connect fails
538+ :param bool share: where feasible, attempt to share a connection with
539+ other clients
540+
541+ :return: an open :class:`txzookeeper.client.ZookeeperClient`
542+ :rtype: :class:`twisted.internet.defer.Deferred`
543+
544+ :raises: :exc:`ensemble.errors.EnvironmentNotFound` when no zookeepers
545+ exist
546+ :raises: :exc:`ensemble.errors.EnvironmentPending` when zookeepers
547+ exist but connection attempt fails
548 """
549 candidates = yield self._provider.get_zookeeper_machines()
550 chosen = yield self._pick_machine(candidates)
551
552=== modified file 'ensemble/providers/common/findzookeepers.py'
553--- ensemble/providers/common/findzookeepers.py 2011-08-18 13:00:47 +0000
554+++ ensemble/providers/common/findzookeepers.py 2011-09-13 12:37:42 +0000
555@@ -10,6 +10,16 @@
556
557 @inlineCallbacks
558 def find_zookeepers(provider):
559+ """Find running zookeeper instances.
560+
561+ :param provider: the MachineProvider in charge of the ensemble
562+
563+ :return: all running or starting machines configured to run zookeeper, as a
564+ list of :class:`ensemble.machine.ProviderMachine`
565+ :rtype: :class:`twisted.internet.defer.Deferred`
566+
567+ :raises: :class:`ensemble.errors.EnvironmentNotFound`
568+ """
569 state = yield provider.load_state()
570 _require(state)
571 instance_ids = state.get("zookeeper-instances")
572
573=== modified file 'ensemble/providers/common/launch.py'
574--- ensemble/providers/common/launch.py 2011-08-31 15:39:20 +0000
575+++ ensemble/providers/common/launch.py 2011-09-13 12:37:42 +0000
576@@ -8,13 +8,20 @@
577 """Abstract class with generic instance-launching logic.
578
579 To create your own subclass, you will certainly need to override
580- `start_machine`, and will very probably want to implement it with the aid
581- of a provider-specific subclass of `ProviderCloudInit`.
582-
583- `provider`: the necessary `MachineProvider`
584-
585- `master`: if True, the machine will run a zookeeper and a provisioning
586- agent, in addition to the machine agent that every machine runs
587+ :meth:`start_machine`, which will very probably want to use the incomplete
588+ :class:`ensemble.providers.common.cloudinit.CloudInit` returned by
589+ :meth:`_create_cloud_init`.
590+
591+ :param provider: the `MachineProvider` that will administer the machine
592+
593+ :param bool master: if True, the machine will run a zookeeper and a
594+ provisioning agent, in addition to the machine agent that every
595+ machine runs automatically.
596+
597+ :param dict constraints: specifies the underlying OS and hardware (where
598+ available)
599+
600+ .. automethod:: _create_cloud_init
601 """
602
603 def __init__(self, provider, master=False, constraints=None):
604@@ -26,7 +33,7 @@
605 def run(self, machine_id):
606 """Launch an instance node within the machine provider environment.
607
608- `machine_id`: the ensemble machine ID
609+ :param str machine_id: the ensemble machine ID to assign
610 """
611 # XXX at some point, we'll want to start multiple zookeepers
612 # that know about each other; for now, this'll do
613@@ -43,17 +50,27 @@
614 def start_machine(self, machine_id, zookeepers):
615 """Actually launch a machine for the appropriate provider.
616
617- `machine_id`: the ensemble machine ID
618-
619- `zookeepers`: a list of `ProviderMachine`s already running zookeeper,
620- which the machine will need to connect to. May be empty.
621-
622- Returns a singe-entry list containing a `ProviderMachine` for the
623- new instance
624+ :param str machine_id: the ensemble machine ID to assign
625+
626+ :param zookeepers: the machines currently running zookeeper, to which
627+ the new machine will need to connect
628+ :type zookeepers: list of :class:`ensemble.machine.ProviderMachine`
629+
630+ :return: a singe-entry list containing a provider-specific
631+ :class:`ensemble.machine.ProviderMachine` representing the newly-
632+ launched machine
633+ :rtype: :class:`twisted.internet.defer.Deferred`
634 """
635 raise NotImplementedError()
636
637 def _create_cloud_init(self, machine_id, zookeepers):
638+ """Construct a provider-independent but incomplete :class:`CloudInit`.
639+
640+ :return: a :class:`ensemble.providers.common.cloudinit.cloudInit`; it
641+ will not be ready to render, but will be configured to the greatest
642+ extent possible with the available information.
643+ :rtype: :class:`twisted.internet.defer.Deferred`
644+ """
645 config = self._provider.config
646 cloud_init = CloudInit()
647 cloud_init.add_ssh_key(get_user_authorized_keys(config))
648
649=== modified file 'ensemble/providers/common/state.py'
650--- ensemble/providers/common/state.py 2011-07-26 09:13:08 +0000
651+++ ensemble/providers/common/state.py 2011-09-13 12:37:42 +0000
652@@ -7,11 +7,20 @@
653
654
655 class LoadState(object):
656+ """Generic state-loading operation.
657+
658+ Note that most ensemble state should be stored in zookeeper nodes; this
659+ is only for state which must be knowable without access to a zookeeper.
660+ """
661
662 def __init__(self, provider):
663 self._provider = provider
664
665 def run(self):
666+ """Actually load the state.
667+
668+ :rtype: dict or False
669+ """
670 storage = self._provider.get_file_storage()
671 d = storage.get(_STATE_FILE)
672 d.addCallback(self._deserialize)
673@@ -27,11 +36,20 @@
674
675
676 class SaveState(object):
677+ """Generic state-saving operation.
678+
679+ Note that most ensemble state should be stored in zookeeper nodes; this
680+ is only for state which must be knowable without access to a zookeeper.
681+ """
682
683 def __init__(self, provider):
684 self._provider = provider
685
686 def run(self, state):
687+ """Actually save new state.
688+
689+ :param dict state: state to save.
690+ """
691 storage = self._provider.get_file_storage()
692 data = safe_dump(state)
693 return storage.put(_STATE_FILE, StringIO(data))
694
695=== modified file 'ensemble/providers/common/utils.py'
696--- ensemble/providers/common/utils.py 2011-09-13 10:55:35 +0000
697+++ ensemble/providers/common/utils.py 2011-09-13 12:37:42 +0000
698@@ -10,19 +10,20 @@
699
700
701 def convert_unknown_error(failure):
702- """Convert any non ensemble errors to a provider interaction error.
703+ """Convert any non-ensemble errors to a provider interaction error.
704
705 Supports both usage from within an except clause, and as an
706 errback handler ie. both the following forms are supported.
707
708 ...
709- try:
710- something()
711- except Exception, e:
712- convert_unknown_errors(e)
713+ try:
714+ something()
715+ except Exception, e:
716+ convert_unknown_errors(e)
717
718 ...
719- d.addErrback(convert_unknown_errors)
720+ d.addErrback(convert_unknown_errors)
721+
722 """
723 if isinstance(failure, Failure):
724 error = failure.value
725@@ -46,9 +47,10 @@
726 If neither "authorized-keys" nor "authorized-keys-path" is
727 present in config, will look in the user's .ssh directory.
728
729- @return: a public key.
730+ :return: an SSH public key
731+ :rtype: str
732
733- @raise LookupError: Raised if a public ssh key is not found.
734+ :raises: :exc:`LookupError` if an SSH public key is not found.
735 """
736 key_names = ["id_dsa.pub", "id_rsa.pub", "identity.pub"]
737 if config.get("authorized-keys"):
738@@ -83,23 +85,23 @@
739 Further documentation on the capabilities of cloud-init
740 https://help.ubuntu.com/community/CloudInit
741
742- @param authorized_keys The authorized SSH keys to be used when populating
743- the newly launched machine's.
744- @type list of strings
745-
746- @param packages A list of packages to be installed on a machine.
747- @type list of strings
748-
749- @param repository A listing of debian repostiories to add the machine's
750- apt sources. 'ppa:' syntax can be utilized.
751- @type list of strings
752-
753- @param scripts A list of scripts to be executed on machine start.
754- @type list of strings
755-
756- @param data An optional dictionary of data to be passed via cloud config.
757- It will be accessible via the key 'machine-data' from the yaml
758- data structure.
759+ :param authorized_keys: The authorized SSH key to be used when
760+ populating the newly launched machines.
761+ :type authorized_keys: list of strings
762+
763+ :param packages: The packages to be installed on a machine.
764+ :type packages: list of strings
765+
766+ :param repositories: Debian repostiories to be used as apt sources on the
767+ machine. 'ppa:' syntax can be used.
768+ :type repositories: list of strings
769+
770+ :param scripts: Scripts to be executed (in order) on machine start.
771+ :type scripts: list of strings
772+
773+ :param dict data: Optional additional data to be passed via cloud config.
774+ It will be accessible via the key 'machine-data' from the yaml data
775+ structure.
776 """
777 cloud_config = {
778 "apt-update": True,
779
780=== modified file 'ensemble/providers/ec2/__init__.py'
781--- ensemble/providers/ec2/__init__.py 2011-09-13 09:53:33 +0000
782+++ ensemble/providers/ec2/__init__.py 2011-09-13 12:37:42 +0000
783@@ -20,6 +20,7 @@
784
785
786 class MachineProvider(MachineProviderBase):
787+ """MachineProvider for use in an EC2/S3 environment"""
788
789 def __init__(self, environment_name, config):
790 super(MachineProvider, self).__init__(environment_name, config)
791@@ -48,16 +49,19 @@
792 return data
793
794 def get_file_storage(self):
795- """Retrieve an S3-backed `FileStorage`."""
796+ """Retrieve an S3-backed :class:`FileStorage`."""
797 return FileStorage(self.s3, self.config["control-bucket"])
798
799 def start_machine(self, machine_data, master=False):
800- """Start a machine in the provider.
801-
802- `machine_data`: a dictionary describing the machine to be launched.
803-
804- @param master: if True, machine will initialize the ensemble admin
805- and run a provisioning agent.
806+ """Start an EC2 machine.
807+
808+ :param dict machine_data: desired characteristics of the new machine;
809+ it must include a "machine-id" key, and may include a "constraints"
810+ key to specify the underlying OS and hardware.
811+
812+ :param bool master: if True, machine will initialize the ensemble admin
813+ and run a provisioning agent, in addition to running a machine
814+ agent.
815 """
816 if "machine-id" not in machine_data:
817 return fail(ProviderError(
818@@ -70,12 +74,17 @@
819 def get_machines(self, instance_ids=()):
820 """List machines running in the provider.
821
822- @param instance_ids: ids of instances you want to get. Leave empty
823- to list all machines owned by this provider.
824-
825- @return: a list of EC2ProviderMachines.
826-
827- @raise: MachinesNotFound
828+ :param list instance_ids: ids of instances you want to get. Leave empty
829+ to list every
830+ :class:`ensemble.providers.ec2.machine.EC2ProviderMachine` owned by
831+ this provider.
832+
833+ :return: a list of
834+ :class:`ensemble.providers.ec2.machine.EC2ProviderMachine`
835+ instances
836+ :rtype: :class:`twisted.internet.defer.Deferred`
837+
838+ :raises: :exc:`ensemble.errors.MachinesNotFound`
839 """
840 group_name = "ensemble-%s" % self.environment_name
841 try:
842@@ -115,6 +124,8 @@
843 The super defintion of this method terminates each machine in
844 the environment; this needs to be augmented here by also
845 removing the security group for the environment.
846+
847+ :rtype: :class:`twisted.internet.defer.Deferred`
848 """
849 try:
850 killed_machines = yield super(MachineProvider, self).\
851@@ -127,7 +138,14 @@
852 def shutdown_machines(self, machines):
853 """Terminate machines associated with this provider.
854
855- @param machines: list of machines to shut down
856+ :param machines: machines to shut down
857+ :type machines: list of
858+ :class:`ensemble.providers.ec2.machine.EC2ProviderMachine`
859+
860+ :return: list of terminated
861+ :class:`ensemble.providers.ec2.machine.EC2ProviderMachine`
862+ instances
863+ :rtype: :class:`twisted.internet.defer.Deferred`
864 """
865 if not machines:
866 returnValue([])
867
868=== modified file 'ensemble/providers/ec2/files.py'
869--- ensemble/providers/ec2/files.py 2011-08-26 14:52:48 +0000
870+++ ensemble/providers/ec2/files.py 2011-09-13 12:37:42 +0000
871@@ -27,6 +27,7 @@
872
873
874 class FileStorage(object):
875+ """S3-backed :class:`FileStorage` abstraction"""
876
877 def __init__(self, s3, bucket):
878 self._s3 = s3
879@@ -37,6 +38,11 @@
880
881 S3 time authenticated URL reference:
882 http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html
883+
884+ :param unicode name: the S3 key for which to provide a URL
885+
886+ :return: a signed URL, expiring 10 years from now
887+ :rtype: str
888 """
889 # URLs are good for 10 years.
890 expires = int(time.time()) + 365 * 24 * 3600 * 10
891@@ -55,13 +61,14 @@
892 return url
893
894 def get(self, name):
895- """
896- If the file exists in the storage, this method will download the file
897- and return a file object. If it doesn't exist then a FileNotFound
898- failure is returned.
899-
900- @return An open file object
901- @type Deferred
902+ """Get a file object from S3.
903+
904+ :param unicode name: S3 key for the desired file
905+
906+ :return: an open file object
907+ :rtype: :class:`twisted.internet.defer.Deferred`
908+
909+ :raises: :exc:`ensemble.errors.FileNotFound` if the file doesn't exist
910 """
911 # for now we do the simplest thing and just fetch the file
912 # in a single call, s3 limits this to some mb, as we grow
913@@ -85,15 +92,13 @@
914 return d
915
916 def put(self, remote_path, file_object):
917- """
918- Upload data to S3.
919-
920- @param remote_path: key on which to store the content
921-
922- @param file_object: open file object containing the content
923-
924- @return A deferred with a boolean value
925- @type Deferred
926+ """Upload a file to S3.
927+
928+ :param unicode remote_path: key on which to store the content
929+
930+ :param file_object: open file object containing the content
931+
932+ :rtype: :class:`twisted.internet.defer.Deferred`
933 """
934 content = file_object.read()
935 path = _safe_string(remote_path)
936
937=== modified file 'ensemble/providers/ec2/launch.py'
938--- ensemble/providers/ec2/launch.py 2011-09-13 09:53:33 +0000
939+++ ensemble/providers/ec2/launch.py 2011-09-13 12:37:42 +0000
940@@ -16,13 +16,17 @@
941 def start_machine(self, machine_id, zookeepers):
942 """Actually launch an instance on EC2.
943
944- `machine_id`: the ensemble machine ID
945-
946- `zookeepers`: a list of `ProviderMachine`s already running zookeeper,
947- which the machine will need to connect to. May be empty.
948-
949- Returns a singe-entry list containing a ProviderMachine for the
950- new instance
951+ :param str machine_id: the ensemble machine ID to assign
952+
953+ :param zookeepers: the machines currently running zookeeper, to which
954+ the new machine will need to connect
955+ :type zookeepers: list of
956+ :class:`ensemble.providers.ec2.machine.EC2ProviderMachine`
957+
958+ :return: a singe-entry list containing a
959+ :class:`ensemble.providers.ec2.machine.EC2ProviderMachine`
960+ representing the newly-launched machine
961+ :rtype: :class:`twisted.internet.defer.Deferred`
962 """
963 cloud_init = self._create_cloud_init(machine_id, zookeepers)
964 cloud_init.set_provider_type("ec2")
965@@ -56,7 +60,7 @@
966 specific machine security group is created for each machine,
967 so that its firewall rules can be configured per machine.
968
969- `machine_id`: The external machine ID of this machine instance
970+ :param machine_id: The ensemble machine ID of the new machine
971 """
972 ensemble_group = "ensemble-%s" % self._provider.environment_name
973 ensemble_machine_group = "ensemble-%s-%s" % (
974
975=== modified file 'ensemble/providers/ec2/machine.py'
976--- ensemble/providers/ec2/machine.py 2011-08-29 18:34:02 +0000
977+++ ensemble/providers/ec2/machine.py 2011-09-13 12:37:42 +0000
978@@ -9,6 +9,12 @@
979
980
981 def machine_from_instance(instance):
982+ """Create an :class:`EC2ProviderMachine` from a txaws :class:`Instance`
983+
984+ :param instance: the EC2 Instance
985+
986+ :return: a matching :class:`EC2ProviderMachine`
987+ """
988 return EC2ProviderMachine(
989 instance.instance_id,
990 instance.dns_name,
991
992=== modified file 'ensemble/providers/ec2/utils.py'
993--- ensemble/providers/ec2/utils.py 2011-09-06 13:11:30 +0000
994+++ ensemble/providers/ec2/utils.py 2011-09-13 12:37:42 +0000
995@@ -48,6 +48,16 @@
996
997
998 def get_image_id(config, constraints):
999+ """Get an EC2 image ID.
1000+
1001+ :param dict config: environment configuration (which can override
1002+ `constraints`)
1003+ :param dict constraints: specific requirements for requested machine; TODO
1004+ improve documentation once requirements firm up.
1005+
1006+ :return: An AMI ID
1007+ :rtype: :class:`twisted.internet.defer.Deferred`
1008+ """
1009 image_id = config.get("default-image-id", None)
1010 if image_id:
1011 return succeed(image_id)
1012
1013=== modified file 'ensemble/providers/orchestra/__init__.py'
1014--- ensemble/providers/orchestra/__init__.py 2011-09-13 09:53:33 +0000
1015+++ ensemble/providers/orchestra/__init__.py 2011-09-13 12:37:42 +0000
1016@@ -14,6 +14,7 @@
1017
1018
1019 class MachineProvider(MachineProviderBase):
1020+ """MachineProvider for use in an Orchestra environment"""
1021
1022 def __init__(self, environment_name, config):
1023 super(MachineProvider, self).__init__(environment_name, config)
1024@@ -27,12 +28,15 @@
1025 return FileStorage(self.config["storage-url"])
1026
1027 def start_machine(self, machine_data, master=False):
1028- """Start a machine in the provider.
1029-
1030- `machine_data`: a dictionary describing the machine to be launched.
1031-
1032- `master`: if True, machine will initialize the ensemble admin and run
1033- a provisioning agent, in addition to running a machine agent
1034+ """Start an Orchestra machine.
1035+
1036+ :param dict machine_data: desired characteristics of the new machine;
1037+ it must include a "machine-id" key, and may include a "constraints"
1038+ key (which is currently ignored by this provider).
1039+
1040+ :param bool master: if True, machine will initialize the ensemble admin
1041+ and run a provisioning agent, in addition to running a machine
1042+ agent.
1043 """
1044 if "machine-id" not in machine_data:
1045 return fail(ProviderError(
1046@@ -44,12 +48,16 @@
1047 def get_machines(self, instance_ids=()):
1048 """List machines running in the provider.
1049
1050- @param instance_ids: ids of instances you want to get. Leave empty
1051- to list all machines owned by this provider.
1052-
1053- @return: a list of OrchestraMachines.
1054-
1055- @raise: MachinesNotFound
1056+ :param list instance_ids: ids of instances you want to get. Leave empty
1057+ to list every
1058+ :class:`ensemble.providers.orchestra.machine.OrchestraMachine`
1059+ owned by this provider.
1060+
1061+ :return: a list of
1062+ :class:`ensemble.providers.orchestra.machine.OrchestraMachine`
1063+ :rtype: :class:`twisted.internet.defer.Deferred`
1064+
1065+ :raises: :exc:`ensemble.errors.MachinesNotFound`
1066 """
1067 instances = yield self.cobbler.describe_systems(*instance_ids)
1068 returnValue([machine_from_dict(i) for i in instances])
1069@@ -58,7 +66,14 @@
1070 def shutdown_machines(self, machines):
1071 """Terminate machines associated with this provider.
1072
1073- @param machines: list of machines to shut down
1074+ :param machines: machines to shut down
1075+ :type machines: list of
1076+ :class:`ensemble.providers.orchestra.machine.OrchestraMachine`
1077+
1078+ :return: list of terminated
1079+ :class:`ensemble.providers.orchestra.machine.OrchestraMachine`
1080+ instances
1081+ :rtype: :class:`twisted.internet.defer.Deferred`
1082 """
1083 if not machines:
1084 returnValue([])
1085
1086=== modified file 'ensemble/providers/orchestra/cobbler.py'
1087--- ensemble/providers/orchestra/cobbler.py 2011-08-26 12:24:00 +0000
1088+++ ensemble/providers/orchestra/cobbler.py 2011-09-13 12:37:42 +0000
1089@@ -96,13 +96,16 @@
1090 def set_on_system(self, instance_id, key, value):
1091 """Set an attribute on a Cobbler system.
1092
1093- @param instance_id: the Cobbler uid of the system
1094-
1095- @param key: the name of the attribute
1096-
1097- @param value: the desired value of the attribute
1098-
1099- @raise: MachinesNotFound, ProviderError
1100+ :param str instance_id: the Cobbler uid of the system
1101+
1102+ :param str key: the name of the attribute
1103+
1104+ :param value: the desired value of the attribute (appropriate types
1105+ depend on `value`)
1106+
1107+ :raises: :exc:`ensemble.errors.ProviderError` on invalid cobbler state
1108+ :raises: :exc:`ensemble.errors.MachinesNotFound` when `instance_id` is
1109+ not acquired
1110 """
1111 name = yield self._get_name(instance_id)
1112 try:
1113@@ -158,9 +161,11 @@
1114 def acquire_system(self):
1115 """Find a system marked as available and mark it as acquired.
1116
1117- @return: the instance id (cobbler uid) of the acquired system.
1118+ :return: the instance id (Cobbler uid) str of the acquired system.
1119+ :rtype: :class:`twisted.internet.defer.Deferred`
1120
1121- @raise: ProviderError if no suitable system can be found.
1122+ :raises: :exc:`ensemble.errors.ProviderError` if no suitable system can
1123+ be found.
1124 """
1125 instance_id, new_classes = yield self._get_available_system()
1126 yield self.set_on_system(instance_id, "mgmt_classes", new_classes)
1127@@ -168,7 +173,10 @@
1128
1129 @inlineCallbacks
1130 def release_system(self, instance_id):
1131- """Take a system marked as acquired, and make it available again"""
1132+ """Take a system marked as acquired, and make it available again
1133+
1134+ :rtype: :class:`twisted.internet.defer.Deferred`
1135+ """
1136 (system,) = yield self.describe_systems(instance_id)
1137 new_classes = map(self._class_swapper, system["mgmt_classes"])
1138 yield self.set_on_system(instance_id, "mgmt_classes", new_classes)
1139@@ -178,9 +186,14 @@
1140 def describe_systems(self, *instance_ids):
1141 """Get all available information about systems.
1142
1143- @return: a list of dictionaries
1144-
1145- @raise: MachineNotFound if any requested instance_id doesn't exist
1146+ :param instance_ids: Cobbler uids of requested systems; leave blank to
1147+ return information about all acquired systems.
1148+
1149+ :return: a list of dictionaries describing acquired systems
1150+ :rtype: :class:`twisted.internet.defer.Deferred`
1151+
1152+ :raises: :exc:`ensemble.errors.MachinesNotFound` if any requested
1153+ instance_id doesn't exist
1154 """
1155 all_systems = yield self._caller.call("get_systems")
1156 acquired_systems = [s for s in all_systems
1157@@ -207,7 +220,12 @@
1158 auth=True)
1159 @inlineCallbacks
1160 def start_system(self, instance_id):
1161- """Launch a cobbler system."""
1162+ """Launch a cobbler system.
1163+
1164+ :param instance_id: The Cobbler uid of the desired system.
1165+
1166+ :rtype: :class:`twisted.internet.defer.Deferred`
1167+ """
1168 name = yield self.set_on_system(instance_id, "netboot_enabled", True)
1169 yield self._power_call("on", [name])
1170
1171@@ -216,6 +234,10 @@
1172 """Shut down a cobbler system.
1173
1174 Also reenables netboot for next time.
1175+
1176+ :param instance_id: The Cobbler uid of the condemned system.
1177+
1178+ :rtype: :class:`twisted.internet.defer.Deferred`
1179 """
1180 name = yield self.set_on_system(instance_id, "netboot_enabled", True)
1181 yield self._power_call("off", [name])
1182
1183=== modified file 'ensemble/providers/orchestra/files.py'
1184--- ensemble/providers/orchestra/files.py 2011-08-29 09:41:12 +0000
1185+++ ensemble/providers/orchestra/files.py 2011-09-13 12:37:42 +0000
1186@@ -9,11 +9,19 @@
1187
1188
1189 class FileStorage(object):
1190+ """A WebDAV-backed :class:`FileStorage` abstraction"""
1191
1192 def __init__(self, base_url):
1193 self._base_url = base_url
1194
1195 def get_url(self, name):
1196+ """Return a URL that can be used to access a stored file.
1197+
1198+ :param unicode name: the file path for which to provide a URL
1199+
1200+ :return: a URL
1201+ :rtype: str
1202+ """
1203 url = u"/".join((self._base_url, name))
1204 # query and fragment are irrelevant to our purposes
1205 scheme, netloc, path = urlparse.urlsplit(url)[:3]
1206@@ -24,6 +32,15 @@
1207 "", ""))
1208
1209 def get(self, name):
1210+ """Get a file object from the Orchestra WebDAV server.
1211+
1212+ :param unicode name: path to for the desired file
1213+
1214+ :return: an open file object
1215+ :rtype: :class:`twisted.internet.defer.Deferred`
1216+
1217+ :raises: :exc:`ensemble.errors.FileNotFound` if the file doesn't exist
1218+ """
1219 url = self.get_url(name)
1220 d = getPage(url)
1221 d.addCallback(StringIO)
1222@@ -37,6 +54,14 @@
1223 return d
1224
1225 def put(self, remote_path, file_object):
1226+ """Upload a file to S3.
1227+
1228+ :param unicode remote_path: path on which to store the content
1229+
1230+ :param file_object: open file object containing the content
1231+
1232+ :rtype: :class:`twisted.internet.defer.Deferred`
1233+ """
1234 url = self.get_url(remote_path)
1235 data = file_object.read()
1236 d = getPage(url, method="PUT", postdata=data)
1237
1238=== modified file 'ensemble/providers/orchestra/launch.py'
1239--- ensemble/providers/orchestra/launch.py 2011-09-13 09:53:33 +0000
1240+++ ensemble/providers/orchestra/launch.py 2011-09-13 12:37:42 +0000
1241@@ -43,18 +43,23 @@
1242
1243
1244 class OrchestraLaunchMachine(LaunchMachine):
1245+ """rchestra operation for launching an instance"""
1246
1247 @inlineCallbacks
1248 def start_machine(self, machine_id, zookeepers):
1249- """Actually launch a Cobbler system.
1250-
1251- `machine_id`: the ensemble machine ID
1252-
1253- `zookeepers`: a list of `ProviderMachine`s already running zookeeper,
1254- which the machine will need to connect to. May be empty.
1255-
1256- Returns a singe-entry list containing a ProviderMachine for the
1257- new instance
1258+ """Actually launch an instance with Orchestra.
1259+
1260+ :param str machine_id: the ensemble machine ID to assign
1261+
1262+ :param zookeepers: the machines currently running zookeeper, to which
1263+ the new machine will need to connect
1264+ :type zookeepers: list of
1265+ :class:`ensemble.providers.orchestra.machine.OrchestraMachine`
1266+
1267+ :return: a singe-entry list containing a
1268+ :class:`ensemble.providers.orchestra.machine.OrchestraMachine`
1269+ representing the newly-launched machine
1270+ :rtype: :class:`twisted.internet.defer.Deferred`
1271 """
1272 cobbler = self._provider.cobbler
1273 instance_id = yield cobbler.acquire_system()
1274
1275=== modified file 'ensemble/providers/orchestra/machine.py'
1276--- ensemble/providers/orchestra/machine.py 2011-08-19 08:00:15 +0000
1277+++ ensemble/providers/orchestra/machine.py 2011-09-13 12:37:42 +0000
1278@@ -6,8 +6,13 @@
1279
1280
1281 def machine_from_dict(d):
1282- """Convert a `dict` into an `OrchestraMachine`.
1283-
1284- Expects dicts as returned by `CobblerClient.get_system[s]`
1285+ """Convert a `dict` into a :class:`OrchestraMachine`.
1286+
1287+ :param dict d: a dict as returned by
1288+ :meth:`ensemble.providers.orchestra.cobbler.CobblerClient.get_system`
1289+ or
1290+ :meth:`ensemble.providers.orchestra.cobbler.CobblerClient.get_systems`
1291+
1292+ :rtype: :class:`OrchestraMachine`
1293 """
1294 return OrchestraMachine(d["uid"], d["name"], d["name"])

Subscribers

People subscribed via source and target branches

to status/vote changes: