Merge lp:~fwereade/pyjuju/fix-docstrings into lp:pyjuju
- fix-docstrings
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jim Baker (community) | Approve | ||
Gustavo Niemeyer | Approve | ||
Review via email: mp+74113@code.launchpad.net |
Commit message
Description of the change
This adds (1) automatic doc generation and (2) hopefully-
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 :).
- 344. By William Reade
-
merge parent
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:
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.
- 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
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.
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.
ensemble.
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
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"]) |
As mentioned on IRC:
<niemeyer> fwereade: Thanks for the docstring changes `ensemble. machine. ProviderMachine ` you want.
<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:
Either way, I'm happy to experiment with this. +1!