Merge lp:~javier.collado/utah/utah_client_common_docs into lp:utah
- utah_client_common_docs
- Merge into dev
Proposed by
Javier Collado
Status: | Merged |
---|---|
Approved by: | Javier Collado |
Approved revision: | 823 |
Merged at revision: | 824 |
Proposed branch: | lp:~javier.collado/utah/utah_client_common_docs |
Merge into: | lp:utah |
Diff against target: |
511 lines (+245/-63) 2 files modified
docs/source/reference.rst (+3/-0) utah/client/common.py (+242/-63) |
To merge this branch: | bzr merge lp:~javier.collado/utah/utah_client_common_docs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joe Talbott (community) | Approve | ||
UTAH Dev | Pending | ||
Review via email:
|
Commit message
Description of the change
This branch add some text to the documentation strings in utah.client.common
module.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'docs/source/reference.rst' |
2 | --- docs/source/reference.rst 2012-12-12 20:58:59 +0000 |
3 | +++ docs/source/reference.rst 2013-02-27 09:07:24 +0000 |
4 | @@ -59,6 +59,9 @@ |
5 | |
6 | .. automodule:: utah.client |
7 | |
8 | +``utah.client.common`` |
9 | +---------------------- |
10 | + |
11 | .. automodule:: utah.client.common |
12 | :members: |
13 | |
14 | |
15 | === modified file 'utah/client/common.py' |
16 | --- utah/client/common.py 2013-02-15 08:55:48 +0000 |
17 | +++ utah/client/common.py 2013-02-27 09:07:24 +0000 |
18 | @@ -13,9 +13,7 @@ |
19 | # You should have received a copy of the GNU General Public License along |
20 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
21 | |
22 | -""" |
23 | -Common methods. |
24 | -""" |
25 | +"""UTAH client common classes and functions.""" |
26 | |
27 | import datetime |
28 | import re |
29 | @@ -75,16 +73,39 @@ |
30 | |
31 | |
32 | def do_nothing(_obj=None): |
33 | - """ |
34 | - Do nothing method used a placeholder for save_state_callbacks. |
35 | - """ |
36 | - pass |
37 | + """Placeholder for save_state_callbacks. |
38 | + |
39 | + .. seealso:: :class:`.TestSuite`, :class:`.TestCase` |
40 | + |
41 | + """ |
42 | |
43 | |
44 | # Inspired by: |
45 | # http://stackoverflow.com/a/3326559 |
46 | def run_cmd(command, cwd=None, timeout=0, cmd_type=CMD_TC_TEST, run_as=None, |
47 | battery_measurements=False): |
48 | + """Run command and return result using the client's format. |
49 | + |
50 | + :param command: Command as it would be written in a console |
51 | + :type command: str |
52 | + :param cwd: Current working directory path |
53 | + :type cwd: str |
54 | + :param timeout: |
55 | + Maximum amount of time in seconds to wait for the command to complete |
56 | + :type timeout: int |
57 | + :param cmd_type: Command type as displayed in the result object |
58 | + :type cmd_type: str |
59 | + :param run_as: Username to use to run the command |
60 | + :type run_as: int |
61 | + :param battery_measurements: |
62 | + Flag to configure when to get battery related information |
63 | + :type battery_measurements: bool |
64 | + :returns: Command execution result |
65 | + :rtype: dict |
66 | + |
67 | + .. seealso:: :func:`make_result` |
68 | + |
69 | + """ |
70 | |
71 | if run_as is not None: |
72 | # add -n so sudo will not prompt for a password on the tty |
73 | @@ -172,6 +193,13 @@ |
74 | Make sure that byte strings are used only for ascii data and unicode |
75 | strings for strings using any other encoding. |
76 | |
77 | + :param value: Data string that should be normalized |
78 | + :type value: str |
79 | + :param encoding: Encoding used to decode the given string |
80 | + :type encoding: str |
81 | + :returns: Data string reencoded using in ascii if possible |
82 | + :rtype: str |
83 | + |
84 | """ |
85 | unicode_value = value.decode(encoding) |
86 | try: |
87 | @@ -185,10 +213,37 @@ |
88 | # TODO: it might make sense to have a result object that keeps track of |
89 | # test pass, fail, error and serializes it's data. |
90 | def make_result(command, retcode, stdout='', stderr='', start_time='', |
91 | - time_delta='', cmd_type=CMD_TC_TEST, user="unknown", |
92 | + time_delta='', cmd_type=CMD_TC_TEST, user='unknown', |
93 | start_battery=None, end_battery=None): |
94 | - """ |
95 | - Make a result dictionary. |
96 | + """Make a result data structure. |
97 | + |
98 | + .. note:: |
99 | + Battery information will only be included if some value is passed as |
100 | + argument. |
101 | + |
102 | + :param command: Command that was executed |
103 | + :type command: str |
104 | + :param retcode: Return code |
105 | + :type retcode: int |
106 | + :param stdout: Standard output |
107 | + :type stdout: str |
108 | + :param stderr: Standard error |
109 | + :type stderr: str |
110 | + :param start_time: Time in which command execution started |
111 | + :type start_time: str |
112 | + :param time_delta: Amount of time that the command took to complete |
113 | + :type time_delta: str |
114 | + :param cmd_type: Command type to be displayed in the report |
115 | + :type cmd_type: str |
116 | + :param user: User name the executed the command |
117 | + :type user: str |
118 | + :param start_battery: Battery information before running the command |
119 | + :type start_battery: dict |
120 | + :param end_battery: Battery information after running the command |
121 | + :type end_battery: dict |
122 | + :returns: Result data |
123 | + :rtype: dict |
124 | + |
125 | """ |
126 | res = { |
127 | 'command': command, |
128 | @@ -212,6 +267,14 @@ |
129 | |
130 | |
131 | def get_process_children(pid): |
132 | + """Get the children processes of a given one. |
133 | + |
134 | + :param pid: Process ID of the parent process |
135 | + :type pid: int |
136 | + :returns: Process ID for the childern processes |
137 | + :rtype: list(int) |
138 | + |
139 | + """ |
140 | p = subprocess.Popen('ps --no-headers -o pid --ppid %d' % pid, shell=True, |
141 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
142 | stdout, _stderr = p.communicate() |
143 | @@ -220,9 +283,18 @@ |
144 | |
145 | |
146 | def parse_yaml_file(filename): |
147 | - """ |
148 | - Parse yaml file and raise exception with error location |
149 | - in case of error |
150 | + """Parse yaml file. |
151 | + |
152 | + :param filename: Path to the file that should be read |
153 | + :type filename: str |
154 | + :returns: Parsed data |
155 | + :rtype: object |
156 | + :raises YAMLParsingError: |
157 | + If there's a problem while parsing the file with the information about |
158 | + where in the file the problem was detected. |
159 | + |
160 | + .. seealso:: :func:`parse_control_file` |
161 | + |
162 | """ |
163 | try: |
164 | with open(filename, 'r') as fp: |
165 | @@ -247,7 +319,16 @@ |
166 | # Validator that sets values to defaults as explained in: |
167 | # https://github.com/Julian/jsonschema/issues/4 |
168 | class DefaultValidator(jsonschema.Validator): |
169 | + |
170 | + """jsonschema validator that sets default values. |
171 | + |
172 | + During the validation, if some field is missing in the data, it will be set |
173 | + to the default value specified in the schema if defined. |
174 | + |
175 | + """ |
176 | + |
177 | def validate_properties(self, properties, instance, schema): |
178 | + """Set missing properties to default value.""" |
179 | (super(DefaultValidator, self) |
180 | .validate_properties(properties, instance, schema)) |
181 | instance.update((k, v['default']) |
182 | @@ -256,8 +337,19 @@ |
183 | |
184 | |
185 | def parse_control_file(filename, schema): |
186 | - """ |
187 | - Parse a control file and check against the schema |
188 | + """Parse a control file and check against a jsonschema. |
189 | + |
190 | + :param filename: Path to the yaml file to be parsed |
191 | + :type filename: str |
192 | + :param schema: jsonschema to validate data against |
193 | + :type schema: dict |
194 | + :returns: Parsed data |
195 | + :rtype: object |
196 | + :raises jsonschema.ValidationError: |
197 | + If file contents doesn't follow the schema definitions |
198 | + |
199 | + .. seealso:: :func:`parse_yaml_file`, :class:`DefaultValidator` |
200 | + |
201 | """ |
202 | control_data = parse_yaml_file(filename) |
203 | validator = DefaultValidator() |
204 | @@ -266,8 +358,17 @@ |
205 | |
206 | |
207 | def debug_print(data, force=False): |
208 | - """ |
209 | - Print debugging information if CONFIG['DEBUG'] is defined and True |
210 | + """Print debugging information according to ``CONFIG['DEBUG']`` value. |
211 | + |
212 | + Data will be printed with the ``DEBUG:`` string prepended to it. |
213 | + |
214 | + :param data: Data to be printed |
215 | + :type data: object |
216 | + :param force: Print data regardless of the configuration |
217 | + :type force: bool |
218 | + :returns: Whether something was printed or not |
219 | + :rtype: bool |
220 | + |
221 | """ |
222 | try: |
223 | debug = CONFIG['DEBUG'] |
224 | @@ -284,8 +385,11 @@ |
225 | |
226 | |
227 | def raise_privileges(orig_privs): |
228 | - """ |
229 | - Raise privileges to original effective privileges. |
230 | + """Raise privileges to original effective privileges. |
231 | + |
232 | + :param orig_privs: Original privileges |
233 | + :type orig_privs: dict |
234 | + |
235 | """ |
236 | os.seteuid(orig_privs['euid']) |
237 | os.setegid(orig_privs['egid']) |
238 | @@ -293,12 +397,18 @@ |
239 | |
240 | |
241 | def drop_privileges(user='nobody', group='nogroup', full=False): |
242 | - """ |
243 | - Change user and/or group to decrease privileges. |
244 | + """Change user and/or group to decrease privileges. |
245 | |
246 | Currently only drops effective privileges. |
247 | |
248 | - Returns a dictionary of the original values. |
249 | + :param user: User name to change to. |
250 | + :type user: str |
251 | + :param group: Group name to change to. |
252 | + :type group: str |
253 | + |
254 | + :returns: Original prrivileges |
255 | + :rtype: dict |
256 | + |
257 | """ |
258 | |
259 | orig_privs = {} |
260 | @@ -333,8 +443,13 @@ |
261 | |
262 | |
263 | def gather_artifacts(extras=None, excludes=None): |
264 | - """ |
265 | - Include a set of useful artifacts (i.e. files) for the logs. |
266 | + """Print files contents, so that it's included in the logs. |
267 | + |
268 | + :param extras: Path to files to be included in the artifacts |
269 | + :type extras: list(str) |
270 | + :param excludes: Path to files to be exluded from the artifacts |
271 | + :type excludes: list(str) |
272 | + |
273 | """ |
274 | if extras is None: |
275 | extras = [] |
276 | @@ -366,12 +481,14 @@ |
277 | |
278 | |
279 | def get_media_info(): |
280 | - """ |
281 | - Get the contents of the media-info file if available. |
282 | - |
283 | - NOTE: This is only a best-effort approach. |
284 | - |
285 | - Returns the contents of the media-info file or None. |
286 | + """Get the contents of the media-info file if available. |
287 | + |
288 | + :returns: |
289 | + The contents of the media-info file or ``'unknown'`` if not available. |
290 | + :rtype: str |
291 | + |
292 | + .. note:: This is only a best-effort approach. |
293 | + |
294 | """ |
295 | |
296 | filename = '/var/log/installer/media-info' |
297 | @@ -386,12 +503,13 @@ |
298 | |
299 | |
300 | def get_product_uuid(): |
301 | - """ |
302 | - Get the product_uuid of the machine under test. |
303 | - |
304 | - NOTE: This is only a best-effort approach. |
305 | - |
306 | - Returns the contents of the product_uuid file or None. |
307 | + """Get the product_uuid of the machine under test. |
308 | + |
309 | + :returns: |
310 | + The contents of the product_uuid file or ``None`` if not available. |
311 | + |
312 | + .. note:: This is only a best-effort approach. |
313 | + |
314 | """ |
315 | |
316 | filename = '/sys/class/dmi/id/product_uuid' |
317 | @@ -406,8 +524,13 @@ |
318 | |
319 | |
320 | def get_host_info(): |
321 | - """ |
322 | - Get host info, useful for debugging. |
323 | + """Get host info, useful for debugging. |
324 | + |
325 | + :returns: Host uname, media-info and product_uuid together |
326 | + :rtype: dict |
327 | + |
328 | + .. seealso:: :func:`get_media_info`, :func:`get_product_uuid` |
329 | + |
330 | """ |
331 | |
332 | retval = {} |
333 | @@ -421,10 +544,11 @@ |
334 | |
335 | |
336 | def get_arch(): |
337 | - """ |
338 | - The host's architecture. |
339 | - |
340 | - Returns the human readable architecture or 'unknown'. |
341 | + """Get the host's architecture. |
342 | + |
343 | + :returns: The human readable architecture or ``'unknown'`` |
344 | + :rtype: str |
345 | + |
346 | """ |
347 | arches = { |
348 | 'x86_64': 'amd64', |
349 | @@ -436,15 +560,20 @@ |
350 | |
351 | |
352 | def get_release(): |
353 | - """ |
354 | - The host's release name (i.e. precise, quantal, etc.) |
355 | - """ |
356 | - return platform.dist()[2] |
357 | + """Get the host's release name. |
358 | + |
359 | + :returns: Release name (i.e. quantal, raring, etc.) |
360 | + :rtype: str |
361 | + |
362 | + """ |
363 | + return platform.linux_distribution()[2] |
364 | |
365 | |
366 | def get_api_config(): |
367 | - """ |
368 | - Parse the client configuration file for API configuration data. |
369 | + """Parse the client configuration file for API configuration data. |
370 | + |
371 | + :returns: The value for the ``API`` field in the client configuration file. |
372 | + |
373 | """ |
374 | |
375 | data = {} |
376 | @@ -457,6 +586,14 @@ |
377 | |
378 | |
379 | def get_build_number(): |
380 | + """Get build number. |
381 | + |
382 | + :returns: Build number as according to media-info or ``'?'`` if not found |
383 | + :rtype: str |
384 | + |
385 | + .. seealso:: :func:`get_host_info` |
386 | + |
387 | + """ |
388 | host_info = get_host_info() |
389 | pattern = re.compile('.*\(([0-9.]+)\)$') |
390 | match = pattern.match(host_info['media-info']) |
391 | @@ -469,8 +606,19 @@ |
392 | |
393 | |
394 | class VCSHandler(object): |
395 | - """ |
396 | - Base class for Version Ccontrol System support. |
397 | + |
398 | + """Base class for Version Control System support. |
399 | + |
400 | + :param repo: Repository location |
401 | + :type repo: str |
402 | + :param destination: |
403 | + Local directory where the repository should be made available |
404 | + :type destination: str |
405 | + :param battery_measurements: |
406 | + Whether battery information should be gathered when running any of the |
407 | + VCS commands |
408 | + :type battery_measurements: bool |
409 | + |
410 | """ |
411 | |
412 | def __init__(self, repo, destination='', battery_measurements=False): |
413 | @@ -479,8 +627,18 @@ |
414 | self.battery_measurements = battery_measurements |
415 | |
416 | def get(self, directory=REPO_DEFAULT_DIR): |
417 | - """ |
418 | - Method to retrieve a copy of the repository |
419 | + """Execute VCS get command. |
420 | + |
421 | + The get command will make a copy of a given repository to the directory |
422 | + specified as destination. |
423 | + |
424 | + :param directory: Working directory |
425 | + :type directory: str |
426 | + :returns: Get command execution result |
427 | + :rtype: dict |
428 | + |
429 | + .. seealso:: :func:`run_cmd` |
430 | + |
431 | """ |
432 | return run_cmd(self.get_command, |
433 | cwd=directory, |
434 | @@ -488,8 +646,15 @@ |
435 | battery_measurements=self.battery_measurements) |
436 | |
437 | def revision(self, directory=REPO_DEFAULT_DIR): |
438 | - """ |
439 | - Return the revision identifier. |
440 | + """Execute revision command. |
441 | + |
442 | + :param directory: Working directory |
443 | + :type directory: str |
444 | + :returns: Revision command execution result. |
445 | + :rtype: dict |
446 | + |
447 | + .. seealso:: :func:`run_cmd`, :meth:`get_revision` |
448 | + |
449 | """ |
450 | return run_cmd(self.rev_command, |
451 | cwd=directory, |
452 | @@ -497,8 +662,18 @@ |
453 | battery_measurements=self.battery_measurements) |
454 | |
455 | def get_revision(self, directory=REPO_DEFAULT_DIR): |
456 | - """ |
457 | - get the revision information |
458 | + """Print revision information. |
459 | + |
460 | + This is a wrapper around :meth:`revision` to make sure that the command |
461 | + returned a success code. |
462 | + |
463 | + :param directory: Working directory |
464 | + :type directory: str |
465 | + :returns: Revision command returncode |
466 | + :rtype: int |
467 | + |
468 | + .. seealso:: :meth:`revision` |
469 | + |
470 | """ |
471 | retval = None |
472 | result = self.revision(directory=directory) |
473 | @@ -512,9 +687,8 @@ |
474 | |
475 | |
476 | class BzrHandler(VCSHandler): |
477 | - """ |
478 | - bazaar handler. |
479 | - """ |
480 | + |
481 | + """Bazaar VCS handler.""" |
482 | |
483 | def __init__(self, repo, branch=True, options="", destination="", |
484 | **kwargs): |
485 | @@ -540,9 +714,8 @@ |
486 | |
487 | |
488 | class GitHandler(VCSHandler): |
489 | - """ |
490 | - git handler. |
491 | - """ |
492 | + |
493 | + """Git VCS handler.""" |
494 | |
495 | def __init__(self, repo, options="", destination="", **kwargs): |
496 | super(GitHandler, self).__init__(repo, destination, **kwargs) |
497 | @@ -558,7 +731,13 @@ |
498 | |
499 | |
500 | class DevHandler(VCSHandler): |
501 | - """ Copy from a local directory for development. """ |
502 | + |
503 | + """Development VCS handler. |
504 | + |
505 | + This isn't a handler for any VCS, but a helper to ease development and let |
506 | + runlists point to a path that can be copied locally. |
507 | + |
508 | + """ |
509 | |
510 | def __init__(self, repo, destination=".", **kwargs): |
511 | super(DevHandler, self).__init__(repo, destination, **kwargs) |
Looks good. Thanks.