Merge lp:~gandelman-a/ubuntu/precise/python-cinderclient/trunk into lp:~ubuntu-cloud-archive/ubuntu/precise/python-cinderclient/trunk
- Precise (12.04)
- trunk
- Merge into trunk
Proposed by
Adam Gandelman
Status: | Approved |
---|---|
Approved by: | Chuck Short |
Approved revision: | 7 |
Proposed branch: | lp:~gandelman-a/ubuntu/precise/python-cinderclient/trunk |
Merge into: | lp:~ubuntu-cloud-archive/ubuntu/precise/python-cinderclient/trunk |
Diff against target: |
3386 lines (+1838/-424) 41 files modified
.gitignore (+0/-13) .gitreview (+0/-4) AUTHORS (+7/-0) ChangeLog (+465/-0) MANIFEST.in (+2/-7) PKG-INFO (+2/-1) cinderclient/__init__.py (+36/-0) cinderclient/client.py (+111/-62) cinderclient/exceptions.py (+11/-8) cinderclient/openstack/common/setup.py (+84/-46) cinderclient/openstack/common/version.py (+148/-0) cinderclient/shell.py (+22/-8) cinderclient/utils.py (+14/-6) cinderclient/v1/client.py (+8/-5) cinderclient/v1/contrib/list_extensions.py (+47/-0) cinderclient/v1/shell.py (+131/-10) cinderclient/v1/volume_snapshots.py (+19/-0) cinderclient/v1/volume_types.py (+46/-1) cinderclient/v1/volumes.py (+51/-46) cinderclient/versioninfo (+1/-1) debian/changelog (+22/-0) debian/control (+6/-2) debian/copyright (+0/-5) openstack-common.conf (+7/-0) python_cinderclient.egg-info/PKG-INFO (+2/-1) python_cinderclient.egg-info/SOURCES.txt (+6/-2) python_cinderclient.egg-info/requires.txt (+1/-1) setup.py (+4/-1) tests/test_client.py (+0/-3) tests/test_http.py (+135/-17) tests/test_shell.py (+29/-36) tests/test_utils.py (+1/-0) tests/utils.py (+33/-5) tests/v1/contrib/test_list_extensions.py (+21/-0) tests/v1/fakes.py (+157/-42) tests/v1/test_auth.py (+83/-75) tests/v1/test_shell.py (+74/-9) tests/v1/test_types.py (+35/-0) tests/v1/test_volumes.py (+11/-2) tools/pip-requires (+1/-1) tools/test-requires (+5/-4) |
To merge this branch: | bzr merge lp:~gandelman-a/ubuntu/precise/python-cinderclient/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Cloud Archive Team | Pending | ||
Review via email: mp+142798@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 7. By Adam Gandelman
-
* New upstream release for the Ubuntu Cloud Archive.
* New upstream release.
* New upstream release.
* debian/control: Add python-requests as a build dep
* debian/control: Add python-testtools and python-fixtures as a build dep.
* debian/control: add python-setuptools- git dependency
* debian/copyright: Removed remaining dh boilerplate template.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file '.gitignore' |
2 | --- .gitignore 2012-08-16 12:03:50 +0000 |
3 | +++ .gitignore 1970-01-01 00:00:00 +0000 |
4 | @@ -1,13 +0,0 @@ |
5 | -.coverage |
6 | -.venv |
7 | -*,cover |
8 | -cover |
9 | -*.pyc |
10 | -.idea |
11 | -*.swp |
12 | -*~ |
13 | -AUTHORS |
14 | -build |
15 | -dist |
16 | -.*egg-info |
17 | -cinderclient/versioninfo |
18 | |
19 | === removed file '.gitreview' |
20 | --- .gitreview 2012-08-16 12:03:50 +0000 |
21 | +++ .gitreview 1970-01-01 00:00:00 +0000 |
22 | @@ -1,4 +0,0 @@ |
23 | -[gerrit] |
24 | -host=review.openstack.org |
25 | -port=29418 |
26 | -project=openstack/python-cinderclient.git |
27 | |
28 | === modified file 'AUTHORS' |
29 | --- AUTHORS 2012-10-01 12:05:26 +0000 |
30 | +++ AUTHORS 2013-01-10 22:15:27 +0000 |
31 | @@ -1,16 +1,23 @@ |
32 | Adam Gandelman <adam.gandelman@canonical.com> |
33 | +Alessandro Pilotti <ap@pilotti.it> |
34 | Anthony Young <sleepsonthefloor@gmail.com> |
35 | Avishay Traeger <avishay@il.ibm.com> |
36 | Bhuvan Arumugam <bhuvan@apache.org> |
37 | Chmouel Boudjnah <chmouel.boudjnah@rackspace.co.uk> |
38 | +Christian Berendt <berendt@b1-systems.de> |
39 | Chuck Short <chuck.short@canonical.com> |
40 | Clark Boylan <clark.boylan@gmail.com> |
41 | Clay Gerrard <clay.gerrard@gmail.com> |
42 | +Cory Stone <corystone@gmail.com> |
43 | Dean Troyer <dtroyer@gmail.com> |
44 | +Doug Hellmann <doug.hellmann@dreamhost.com> |
45 | +Eric Harney <eharney@redhat.com> |
46 | jakedahn <jake@ansolabs.com> |
47 | +john-griffith <john.griffith@solidfire.com> |
48 | John Griffith <john.griffith@solidfire.com> |
49 | Josh Durgin <josh.durgin@inktank.com> |
50 | lrqrun <lrqrun@gmail.com> |
51 | +Mike Perez <thingee@gmail.com> |
52 | Monty Taylor <mordred@inaugust.com> |
53 | Rongze Zhu <zrzhit@gmail.com> |
54 | Vincent Hou <sbhou@cn.ibm.com> |
55 | |
56 | === modified file 'ChangeLog' |
57 | --- ChangeLog 2012-10-01 12:05:26 +0000 |
58 | +++ ChangeLog 2013-01-10 22:15:27 +0000 |
59 | @@ -1,3 +1,468 @@ |
60 | +commit 61e2a4237f0d7a4c5c9591ac364a01c844309757 |
61 | +Merge: 06acb0c 765cb3d |
62 | +Author: Jenkins <jenkins@review.openstack.org> |
63 | +Date: Wed Jan 9 07:57:09 2013 +0000 |
64 | + |
65 | + Merge "Add access to update volume metadata." |
66 | + |
67 | +commit 765cb3ddbea2f0e770a2ad911b1ba17760b50818 |
68 | +Author: John Griffith <john.griffith@solidfire.com> |
69 | +Date: Sat Jan 5 01:12:59 2013 +0000 |
70 | + |
71 | + Add access to update volume metadata. |
72 | + |
73 | + This implements the cinderclient side of |
74 | + blueprint: update-vol-metadata |
75 | + |
76 | + Change-Id: I97c120d69b6d30843009f6a297bb51299799f4ec |
77 | + |
78 | + cinderclient/v1/shell.py | 25 +++++++++++++++++++++++++ |
79 | + cinderclient/v1/volumes.py | 30 ++++++++++++++++++++++++++++++ |
80 | + tests/v1/fakes.py | 15 +++++++++++++++ |
81 | + tests/v1/test_shell.py | 15 +++++++++++++++ |
82 | + tests/v1/test_volumes.py | 13 +++++++++++-- |
83 | + 5 files changed, 96 insertions(+), 2 deletions(-) |
84 | + |
85 | +commit 06acb0c009ac53777884cf5b0aa8942184d06622 |
86 | +Author: Monty Taylor <mordred@inaugust.com> |
87 | +Date: Mon Dec 24 21:49:34 2012 -0600 |
88 | + |
89 | + Move from unittest2 to testtools |
90 | + |
91 | + Part of blueprint grizzly-testtools |
92 | + |
93 | + Change-Id: I13e068ca156f12114eaa3a65bdb557e4eb2c988d |
94 | + |
95 | + tests/test_client.py | 3 --- |
96 | + tests/test_shell.py | 43 ++++++++++++++++++++++++------------------- |
97 | + tests/test_utils.py | 1 + |
98 | + tests/utils.py | 4 ++-- |
99 | + tests/v1/test_shell.py | 24 +++++++++++++++--------- |
100 | + tools/test-requires | 7 ++++--- |
101 | + 6 files changed, 46 insertions(+), 36 deletions(-) |
102 | + |
103 | +commit bf7b86748a49455ae1545f01fe5cfc9b801223aa |
104 | +Merge: 9a49690 2473311 |
105 | +Author: Jenkins <jenkins@review.openstack.org> |
106 | +Date: Fri Dec 21 08:46:11 2012 +0000 |
107 | + |
108 | + Merge "Add clone volume support to cinderclient." |
109 | + |
110 | +commit 9a496900964c7037db994db800bab456d57c2164 |
111 | +Author: john-griffith <john.griffith@solidfire.com> |
112 | +Date: Mon Dec 17 14:52:05 2012 -0700 |
113 | + |
114 | + Add list-extensions capability to cinderclient. |
115 | + |
116 | + Implements the list extension functionality from novaclient |
117 | + written by harlowja (Change-Id: I5b72f5ea73c00f1c1a0f09f670d744c820e05837) |
118 | + |
119 | + This provides a mechanism to view available extensions, and also |
120 | + starts actually using the cinderclient contrib dir. |
121 | + |
122 | + Original Author: harlowja |
123 | + |
124 | + Change-Id: I4f9d04cb8a2aa05c978a2a5a926c9175ee3614bb |
125 | + |
126 | + cinderclient/v1/contrib/list_extensions.py | 47 ++++++++++++++++++++++++++++ |
127 | + tests/v1/contrib/test_list_extensions.py | 21 +++++++++++++ |
128 | + tests/v1/fakes.py | 30 ++++++++++++++++-- |
129 | + 3 files changed, 96 insertions(+), 2 deletions(-) |
130 | + |
131 | +commit 82e47d0866397b702ae777972add66b33afae69e |
132 | +Author: Dean Troyer <dtroyer@gmail.com> |
133 | +Date: Thu Dec 13 12:01:07 2012 -0600 |
134 | + |
135 | + Use requests module for HTTP/HTTPS |
136 | + |
137 | + * Implement correct certificate verification |
138 | + * Add --os-cacert |
139 | + * Rework tests for requests |
140 | + |
141 | + Pinned requests module to < 1.0 as 1.0.2 is now current in pipi |
142 | + as of 17Dec2012. |
143 | + |
144 | + Blueprint: tls-verify |
145 | + |
146 | + Change-Id: I71066ff7297f3b70c08b7ae1c8ae8b6a1b82bbae |
147 | + |
148 | + cinderclient/client.py | 95 +++++++++++++++++------------ |
149 | + cinderclient/exceptions.py | 19 +++--- |
150 | + cinderclient/shell.py | 17 ++++-- |
151 | + cinderclient/v1/client.py | 6 +- |
152 | + tests/test_http.py | 87 +++++++++++++++------------ |
153 | + tests/test_shell.py | 6 -- |
154 | + tests/utils.py | 30 +++++++++- |
155 | + tests/v1/fakes.py | 109 ++++++++++++++++++---------------- |
156 | + tests/v1/test_auth.py | 142 +++++++++++++++++++++++--------------------- |
157 | + tools/pip-requires | 2 +- |
158 | + 10 files changed, 296 insertions(+), 217 deletions(-) |
159 | + |
160 | +commit d3603535d2f47580bf49520048af1f27adea3e7c |
161 | +Author: Dean Troyer <dtroyer@gmail.com> |
162 | +Date: Thu Dec 13 14:52:54 2012 -0600 |
163 | + |
164 | + Port some additional logging changes from novaclient |
165 | + |
166 | + * Allows capture of timestamps prior to and after request for timing |
167 | + https://review.openstack.org/11519 |
168 | + * Add -X to DELETE and PUT in debug mode |
169 | + https://review.openstack.org/12069 |
170 | + * Show request body in curl command |
171 | + https://review.openstack.org/12203 |
172 | + |
173 | + Change-Id: I0d87ab6b3c2b35ff843323cb818915e03993a844 |
174 | + |
175 | + cinderclient/client.py | 18 +++++++++++------- |
176 | + 1 file changed, 11 insertions(+), 7 deletions(-) |
177 | + |
178 | +commit e30724df5b55f5b989f294f82ad7209e150b3559 |
179 | +Author: Dean Troyer <dtroyer@gmail.com> |
180 | +Date: Thu Dec 13 15:25:01 2012 -0600 |
181 | + |
182 | + Bring back the output from client.http_log() |
183 | + |
184 | + Ported from novaclient https://review.openstack.org/9241 |
185 | + Support CINDERCLIENT_DEBUG as synonym for --debug |
186 | + |
187 | + Change-Id: Ic03b9e7d84c8db14f6e193ca2b478fd0d70d1299 |
188 | + |
189 | + cinderclient/client.py | 34 ++++++++++++++++++---------------- |
190 | + cinderclient/shell.py | 6 ++++-- |
191 | + cinderclient/v1/client.py | 6 ++++-- |
192 | + 3 files changed, 26 insertions(+), 20 deletions(-) |
193 | + |
194 | +commit 24733119fe5da11bccf16d345f6f27cec8bbd06f |
195 | +Author: John Griffith <john.griffith@solidfire.com> |
196 | +Date: Thu Dec 13 21:21:55 2012 +0000 |
197 | + |
198 | + Add clone volume support to cinderclient. |
199 | + |
200 | + Adds the option to create a clone of a volume, using --source-volid xxx |
201 | + |
202 | + relies on Change-Id: I72bf90baf22bec2d4806d00e2b827a594ed213f4 |
203 | + |
204 | + Change-Id: Iad2178deb66c328625fbff747e123044f9d86a88 |
205 | + |
206 | + cinderclient/v1/shell.py | 9 +++++++++ |
207 | + cinderclient/v1/volumes.py | 4 +++- |
208 | + 2 files changed, 12 insertions(+), 1 deletion(-) |
209 | + |
210 | +commit 9201cee6ef79dd4955f6f762e6adfaee65885ca1 |
211 | +Author: Monty Taylor <mordred@inaugust.com> |
212 | +Date: Fri Nov 30 14:22:54 2012 -0800 |
213 | + |
214 | + Update to swapped versioninfo logic. |
215 | + |
216 | + Change-Id: I6a8dd1c84df0a92c21f468c7dcad1ce79f69c463 |
217 | + |
218 | + cinderclient/openstack/common/setup.py | 18 ++++++++++-------- |
219 | + 1 file changed, 10 insertions(+), 8 deletions(-) |
220 | + |
221 | +commit 5adf79136042004ceb76d3d1e68e0ab08edf1917 |
222 | +Author: Monty Taylor <mordred@inaugust.com> |
223 | +Date: Fri Nov 30 14:05:54 2012 -0800 |
224 | + |
225 | + Align cinderclient version code. |
226 | + |
227 | + Change-Id: I81d6a279a52656720626357a1c4ca8bb382ef1f8 |
228 | + |
229 | + .gitignore | 2 + |
230 | + MANIFEST.in | 9 +- |
231 | + cinderclient/__init__.py | 36 ++++++++ |
232 | + cinderclient/openstack/common/version.py | 148 ++++++++++++++++++++++++++++++ |
233 | + openstack-common.conf | 2 +- |
234 | + setup.py | 4 +- |
235 | + 6 files changed, 192 insertions(+), 9 deletions(-) |
236 | + |
237 | +commit a74dee000e682b5b227cd9032df6719aa22ffb00 |
238 | +Merge: 3ca2c99 62eb92a |
239 | +Author: Jenkins <jenkins@review.openstack.org> |
240 | +Date: Thu Nov 22 07:55:01 2012 +0000 |
241 | + |
242 | + Merge "Pin pep8 to 1.3.3" |
243 | + |
244 | +commit 3ca2c999583a57ef785ee011ac8d805571b8624c |
245 | +Merge: 79dc21d c01e782 |
246 | +Author: Jenkins <jenkins@review.openstack.org> |
247 | +Date: Thu Nov 22 06:13:15 2012 +0000 |
248 | + |
249 | + Merge "Adding bootable column to volume list view" |
250 | + |
251 | +commit c01e7822f9de3b17b8cca8d0b10cbf03e6890f8f |
252 | +Author: Mike Perez <thingee@gmail.com> |
253 | +Date: Sun Nov 18 19:55:49 2012 -0800 |
254 | + |
255 | + Adding bootable column to volume list view |
256 | + |
257 | + displays whether a volume is bootable based on cinder's api response for |
258 | + /volumes |
259 | + |
260 | + Change-Id: I8f6cc6e02be8226914f65717dcb2e0367553e51f |
261 | + |
262 | + cinderclient/v1/shell.py | 2 +- |
263 | + tests/v1/fakes.py | 1 + |
264 | + 2 files changed, 2 insertions(+), 1 deletion(-) |
265 | + |
266 | +commit 62eb92a1f2cffa5a81778018932b539f436b50f0 |
267 | +Author: Chuck Short <chuck.short@canonical.com> |
268 | +Date: Tue Nov 20 09:41:28 2012 -0600 |
269 | + |
270 | + Pin pep8 to 1.3.3 |
271 | + |
272 | + Apart of making pep8 version standard across all openstack |
273 | + projects. |
274 | + |
275 | + Change-Id: If5ef6be394e557b3554a80f1ad9b44d472c273b6 |
276 | + Signed-off-by: Chuck Short <chuck.short@canonical.com> |
277 | + |
278 | + tools/test-requires | 2 +- |
279 | + 1 file changed, 1 insertion(+), 1 deletion(-) |
280 | + |
281 | +commit 79dc21d17183fa78126515f9d8436c207fbed7b7 |
282 | +Author: Christian Berendt <berendt@b1-systems.de> |
283 | +Date: Thu Nov 8 23:33:14 2012 +0100 |
284 | + |
285 | + show help when calling without arguments |
286 | + |
287 | + When calling cinder without arguments you'll receive the following |
288 | + output: |
289 | + |
290 | + error: too few arguments |
291 | + Try 'cinder help ' for more information. |
292 | + |
293 | + With this change the help is also shown when calling cinder |
294 | + without arguments. I think that's the expected behavior. |
295 | + |
296 | + Change-Id: I8f46f67b7fef472ac344bb74f80cf1b77c4c4745 |
297 | + |
298 | + cinderclient/shell.py | 2 +- |
299 | + tests/test_shell.py | 18 ++++++------------ |
300 | + 2 files changed, 7 insertions(+), 13 deletions(-) |
301 | + |
302 | +commit 112bd60d4e17ad435c274d52585f70faaaf9f70d |
303 | +Author: Cory Stone <corystone@gmail.com> |
304 | +Date: Thu Nov 1 14:27:52 2012 -0500 |
305 | + |
306 | + Add retries to cinderclient. |
307 | + |
308 | + HTTPClient now supports a retries argument. It will reissue requests |
309 | + for any 5xx or socket (400 with n/a) errors. This retry loop was |
310 | + "inspired" by swiftclient's loop. It reauths one extra time if |
311 | + necessary. It uses backoff times of 1, 2, 4... seconds. |
312 | + |
313 | + The default is 0 retries. It is also exposed to the shell as well with |
314 | + a --retries arg. |
315 | + |
316 | + Change-Id: I67bed02d65155f4a4d5d879bb233f56cc78849fa |
317 | + |
318 | + cinderclient/client.py | 54 ++++++++++++++++------ |
319 | + cinderclient/shell.py | 9 +++- |
320 | + cinderclient/v1/client.py | 5 +- |
321 | + tests/test_http.py | 113 +++++++++++++++++++++++++++++++++++++++++++-- |
322 | + 4 files changed, 159 insertions(+), 22 deletions(-) |
323 | + |
324 | +commit 1abc0b4edfa70485cd32bac2060b1b223850752e |
325 | +Author: Alessandro Pilotti <ap@pilotti.it> |
326 | +Date: Tue Nov 6 19:38:28 2012 +0200 |
327 | + |
328 | + Fixes setup compatibility issue on Windows |
329 | + |
330 | + Fixes Bug #1052161 |
331 | + |
332 | + "python setup.py build" fails on Windows due to a hardcoded shell path: |
333 | + /bin/sh |
334 | + |
335 | + setup.py updated using openstack-common/update.py |
336 | + |
337 | + Change-Id: Iafae444a43c76560020a84e3a1c5c8cb4b6860da |
338 | + |
339 | + cinderclient/openstack/common/setup.py | 120 +++++++++++++++++++++----------- |
340 | + openstack-common.conf | 7 ++ |
341 | + 2 files changed, 85 insertions(+), 42 deletions(-) |
342 | + |
343 | +commit dcbebd7b78235008e36fe6b8c8cc4a75f5031042 |
344 | +Author: John Griffith <john.griffith@solidfire.com> |
345 | +Date: Fri Nov 2 22:17:03 2012 +0000 |
346 | + |
347 | + Revert "Add retries to cinderclient." |
348 | + |
349 | + This reverts commit 3b1eda3b3bb6678e9c9ec3e783e3e12b0c42f0a4 |
350 | + |
351 | + cinderclient/client.py | 47 +++++++++++----------------------- |
352 | + cinderclient/shell.py | 8 +----- |
353 | + cinderclient/v1/client.py | 5 ++-- |
354 | + tests/test_http.py | 62 +-------------------------------------------- |
355 | + 4 files changed, 19 insertions(+), 103 deletions(-) |
356 | + |
357 | +commit 3b1eda3b3bb6678e9c9ec3e783e3e12b0c42f0a4 |
358 | +Author: Cory Stone <corystone@gmail.com> |
359 | +Date: Thu Nov 1 14:27:52 2012 -0500 |
360 | + |
361 | + Add retries to cinderclient. |
362 | + |
363 | + HTTPClient now supports a retries argument. It will reissue requests |
364 | + for any 5xx or socket (400 with n/a) errors. This retry loop was |
365 | + "inspired" by swiftclient's loop. It reauths one time if necessary. |
366 | + It uses backoff times of 1, 2, 4... seconds. |
367 | + |
368 | + The default is 0 retries. It is also exposed to the shell as well with |
369 | + a --retries arg. |
370 | + |
371 | + Change-Id: I75d9a13d6c4ba16a5da13d4bf5cad78a777d67d7 |
372 | + |
373 | + cinderclient/client.py | 47 +++++++++++++++++++++++----------- |
374 | + cinderclient/shell.py | 8 +++++- |
375 | + cinderclient/v1/client.py | 5 ++-- |
376 | + tests/test_http.py | 62 ++++++++++++++++++++++++++++++++++++++++++++- |
377 | + 4 files changed, 103 insertions(+), 19 deletions(-) |
378 | + |
379 | +commit 7d3749e7c3c3f66900a4b08563a3b1e8d1dd0c35 |
380 | +Merge: 0288e7f a8be1b2 |
381 | +Author: Jenkins <jenkins@review.openstack.org> |
382 | +Date: Thu Nov 1 19:04:42 2012 +0000 |
383 | + |
384 | + Merge "Remove extra-specs from types-list command output" |
385 | + |
386 | +commit 0288e7fd095037898fe451275a3ecfc183eac134 |
387 | +Merge: 92bd08b 28d4d6b |
388 | +Author: Jenkins <jenkins@review.openstack.org> |
389 | +Date: Thu Nov 1 18:59:45 2012 +0000 |
390 | + |
391 | + Merge "Remove attach/detach code from cinderclient" |
392 | + |
393 | +commit a8be1b2a205dfba0a23d92454be67adfe0a0aaf4 |
394 | +Author: John Griffith <john.griffith@solidfire.com> |
395 | +Date: Wed Oct 31 18:10:54 2012 -0600 |
396 | + |
397 | + Remove extra-specs from types-list command output |
398 | + |
399 | + type-list was including the extra-specs info in it's response, |
400 | + however extra-specs are admin accessible only. As a result only admin |
401 | + users could run type-list which is not what we want. |
402 | + |
403 | + Add a seperate admin only call to provide volume-types with associated |
404 | + extra-specs info (extra-specs-list). |
405 | + |
406 | + Change-Id: I2a716cd7076fb17fd5f2ceb94363eef0009e9bce |
407 | + |
408 | + cinderclient/v1/shell.py | 12 +++++++++++- |
409 | + 1 file changed, 11 insertions(+), 1 deletion(-) |
410 | + |
411 | +commit 92bd08b9e1082620d41e827db2eeade5da6c8e19 |
412 | +Merge: 4649420 942ab5c |
413 | +Author: Jenkins <jenkins@review.openstack.org> |
414 | +Date: Wed Oct 31 20:24:16 2012 +0000 |
415 | + |
416 | + Merge "Add volume_type extra_specs support to client" |
417 | + |
418 | +commit 28d4d6bfd09063e8d814cf64ef504aa01a9a90e7 |
419 | +Author: John Griffith <john.griffith@solidfire.com> |
420 | +Date: Tue Oct 30 10:24:47 2012 -0600 |
421 | + |
422 | + Remove attach/detach code from cinderclient |
423 | + |
424 | + Attach and Detach actions are done in the novaclient, however |
425 | + during the creation of Cinder a number of these methods were copied into |
426 | + the Cinder client. This is dead code and just adds confusion, so let's |
427 | + rip it out. |
428 | + |
429 | + Change-Id: Ib96fe0cac26f19e7d9f2aa01c71ba9762e4f8b8e |
430 | + Fixes: bug #1071003 |
431 | + |
432 | + cinderclient/v1/volumes.py | 46 -------------------------------------------- |
433 | + 1 file changed, 46 deletions(-) |
434 | + |
435 | +commit 4649420ca2e8e24c8e651d141839d62fbbb53416 |
436 | +Merge: c20d7d9 01dad32 |
437 | +Author: Jenkins <jenkins@review.openstack.org> |
438 | +Date: Tue Oct 30 16:15:09 2012 +0000 |
439 | + |
440 | + Merge "Fix support for Unicode volume names" |
441 | + |
442 | +commit c20d7d902ee6e2ef50f20fa850f47afa310b5998 |
443 | +Author: Dean Troyer <dtroyer@gmail.com> |
444 | +Date: Tue Oct 23 14:48:38 2012 -0500 |
445 | + |
446 | + Add python_cinderclient.egg-info to .gitignore |
447 | + |
448 | + Change-Id: I24a7a5f423e99a9b6839b5f3a1b1fbf05297dc53 |
449 | + |
450 | + .gitignore | 2 +- |
451 | + 1 file changed, 1 insertion(+), 1 deletion(-) |
452 | + |
453 | +commit 01dad32c07ad500f2e0000dcd24a50f5c47cd206 |
454 | +Author: Eric Harney <eharney@redhat.com> |
455 | +Date: Tue Oct 23 15:40:17 2012 -0400 |
456 | + |
457 | + Fix support for Unicode volume names |
458 | + |
459 | + It is possible to create a Unicode volume from the command line, |
460 | + but it cannot be manipulated by name for operations such as delete. |
461 | + This is because the find_resource function tries to match the |
462 | + Unicode string to a regular byte string, and a UnicodeWarning is |
463 | + issued, failing the match. Fix by decoding the Unicode name when |
464 | + trying to match. |
465 | + |
466 | + Fixes bug 1065275. |
467 | + |
468 | + Change-Id: I8e19a78bbc1ccb503ccd39dc3b904fc4f6f77858 |
469 | + |
470 | + cinderclient/utils.py | 20 ++++++++++++++------ |
471 | + 1 file changed, 14 insertions(+), 6 deletions(-) |
472 | + |
473 | +commit 5a3a18d0cbe71ae7e150fe69efe5239601413f9b |
474 | +Author: Doug Hellmann <doug.hellmann@dreamhost.com> |
475 | +Date: Mon Oct 22 18:42:34 2012 -0400 |
476 | + |
477 | + Add OpenStack trove classifier for PyPI |
478 | + |
479 | + Add trove classifier to have the client listed among the |
480 | + other OpenStack-related projets on PyPI. |
481 | + |
482 | + Change-Id: I904372caf0a8eaa44dd048729b4d87e5333f04cc |
483 | + Signed-off-by: Doug Hellmann <doug.hellmann@dreamhost.com> |
484 | + |
485 | + setup.py | 1 + |
486 | + 1 file changed, 1 insertion(+) |
487 | + |
488 | +commit 942ab5c7fe901dd925ee38328c91e1cd160bc437 |
489 | +Author: John Griffith <john.griffith@solidfire.com> |
490 | +Date: Tue Oct 9 18:45:41 2012 -0600 |
491 | + |
492 | + Add volume_type extra_specs support to client |
493 | + |
494 | + Adds ability to set/clear volume_type extra_specs |
495 | + via python-cinderclient. |
496 | + |
497 | + * Adds extra_specs info to the list-types output |
498 | + * Adds missing tests for volume_types features |
499 | + * Fixes unset loop so it actually iterates through all of the supplied keys |
500 | + |
501 | + Change-Id: I3552de722f76389cfef6d4f12320720e022ebfac |
502 | + |
503 | + cinderclient/v1/client.py | 4 +-- |
504 | + cinderclient/v1/shell.py | 63 ++++++++++++++++++++++++++++++++------- |
505 | + cinderclient/v1/volume_types.py | 47 ++++++++++++++++++++++++++++- |
506 | + tests/v1/fakes.py | 32 ++++++++++++++++++++ |
507 | + tests/v1/test_types.py | 35 ++++++++++++++++++++++ |
508 | + 5 files changed, 167 insertions(+), 14 deletions(-) |
509 | + |
510 | +commit 7cba8eb7863705fcf0ef5c677cef2d334bd8ad26 |
511 | +Author: Clay Gerrard <clay.gerrard@gmail.com> |
512 | +Date: Tue Aug 28 20:20:35 2012 +0000 |
513 | + |
514 | + add rename and snapshot-rename commands |
515 | + |
516 | + Change-Id: I06549f19b846d886fabd611d2167b894c4d02df8 |
517 | + |
518 | + cinderclient/v1/shell.py | 34 +++++++++++++++++++++++++++ |
519 | + cinderclient/v1/volume_snapshots.py | 19 +++++++++++++++ |
520 | + cinderclient/v1/volumes.py | 19 +++++++++++++++ |
521 | + tests/v1/fakes.py | 36 +++++++++++++++++++++++++++- |
522 | + tests/v1/test_shell.py | 44 +++++++++++++++++++++++++++++++++++ |
523 | + 5 files changed, 151 insertions(+), 1 deletion(-) |
524 | + |
525 | commit 2e6be694c17d505bf52e339bccf6848776493ffe |
526 | Author: Vishvananda Ishaya <vishvananda@gmail.com> |
527 | Date: Thu Sep 20 14:29:21 2012 +0000 |
528 | |
529 | === modified file 'MANIFEST.in' |
530 | --- MANIFEST.in 2012-08-16 12:03:50 +0000 |
531 | +++ MANIFEST.in 2013-01-10 22:15:27 +0000 |
532 | @@ -1,10 +1,5 @@ |
533 | include AUTHORS |
534 | include ChangeLog |
535 | -include HACKING |
536 | -include LICENSE |
537 | -include README.rst |
538 | -include run_tests.sh tox.ini |
539 | include cinderclient/versioninfo |
540 | -recursive-include doc * |
541 | -recursive-include tests * |
542 | -recursive-include tools * |
543 | +exclude .gitignore |
544 | +exclude .gitreview |
545 | |
546 | === modified file 'PKG-INFO' |
547 | --- PKG-INFO 2012-10-01 12:05:26 +0000 |
548 | +++ PKG-INFO 2013-01-10 22:15:27 +0000 |
549 | @@ -1,6 +1,6 @@ |
550 | Metadata-Version: 1.1 |
551 | Name: python-cinderclient |
552 | -Version: 1.0.0 |
553 | +Version: 1.0.2.3.g61e2a42 |
554 | Summary: Client library for OpenStack Cinder API. |
555 | Home-page: https://github.com/openstack/python-cinderclient |
556 | Author: Rackspace, based on work by Jacob Kaplan-Moss |
557 | @@ -161,6 +161,7 @@ |
558 | Platform: UNKNOWN |
559 | Classifier: Development Status :: 5 - Production/Stable |
560 | Classifier: Environment :: Console |
561 | +Classifier: Environment :: OpenStack |
562 | Classifier: Intended Audience :: Developers |
563 | Classifier: Intended Audience :: Information Technology |
564 | Classifier: License :: OSI Approved :: Apache Software License |
565 | |
566 | === modified file 'cinderclient/__init__.py' |
567 | --- cinderclient/__init__.py 2012-06-27 13:32:14 +0000 |
568 | +++ cinderclient/__init__.py 2013-01-10 22:15:27 +0000 |
569 | @@ -0,0 +1,36 @@ |
570 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
571 | + |
572 | +# Copyright 2012 OpenStack LLC |
573 | +# |
574 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
575 | +# not use this file except in compliance with the License. You may obtain |
576 | +# a copy of the License at |
577 | +# |
578 | +# http://www.apache.org/licenses/LICENSE-2.0 |
579 | +# |
580 | +# Unless required by applicable law or agreed to in writing, software |
581 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
582 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
583 | +# License for the specific language governing permissions and limitations |
584 | +# under the License. |
585 | + |
586 | +import os |
587 | +import inspect |
588 | + |
589 | + |
590 | +def _get_client_version(): |
591 | + """Read version from versioninfo file.""" |
592 | + mod_abspath = inspect.getabsfile(inspect.currentframe()) |
593 | + client_path = os.path.dirname(mod_abspath) |
594 | + version_path = os.path.join(client_path, 'versioninfo') |
595 | + |
596 | + if os.path.exists(version_path): |
597 | + version = open(version_path).read().strip() |
598 | + else: |
599 | + version = "Unknown, couldn't find versioninfo file at %s"\ |
600 | + % version_path |
601 | + |
602 | + return version |
603 | + |
604 | + |
605 | +__version__ = _get_client_version() |
606 | |
607 | === modified file 'cinderclient/client.py' |
608 | --- cinderclient/client.py 2012-10-01 12:05:26 +0000 |
609 | +++ cinderclient/client.py 2013-01-10 22:15:27 +0000 |
610 | @@ -7,10 +7,14 @@ |
611 | OpenStack Client interface. Handles the REST calls and responses. |
612 | """ |
613 | |
614 | -import httplib2 |
615 | import logging |
616 | import os |
617 | +import sys |
618 | import urlparse |
619 | +try: |
620 | + from eventlet import sleep |
621 | +except ImportError: |
622 | + from time import sleep |
623 | |
624 | try: |
625 | import json |
626 | @@ -22,28 +26,27 @@ |
627 | import cgi |
628 | urlparse.parse_qsl = cgi.parse_qsl |
629 | |
630 | +import requests |
631 | + |
632 | from cinderclient import exceptions |
633 | from cinderclient import service_catalog |
634 | from cinderclient import utils |
635 | |
636 | |
637 | -_logger = logging.getLogger(__name__) |
638 | -if 'CINDERCLIENT_DEBUG' in os.environ and os.environ['CINDERCLIENT_DEBUG']: |
639 | - ch = logging.StreamHandler() |
640 | - _logger.setLevel(logging.DEBUG) |
641 | - _logger.addHandler(ch) |
642 | - |
643 | - |
644 | -class HTTPClient(httplib2.Http): |
645 | +class HTTPClient(object): |
646 | |
647 | USER_AGENT = 'python-cinderclient' |
648 | |
649 | + requests_config = { |
650 | + 'danger_mode': False, |
651 | + } |
652 | + |
653 | def __init__(self, user, password, projectid, auth_url, insecure=False, |
654 | timeout=None, tenant_id=None, proxy_tenant_id=None, |
655 | proxy_token=None, region_name=None, |
656 | endpoint_type='publicURL', service_type=None, |
657 | - service_name=None, volume_service_name=None): |
658 | - super(HTTPClient, self).__init__(timeout=timeout) |
659 | + service_name=None, volume_service_name=None, retries=None, |
660 | + http_log_debug=False, cacert=None): |
661 | self.user = user |
662 | self.password = password |
663 | self.projectid = projectid |
664 | @@ -55,23 +58,36 @@ |
665 | self.service_type = service_type |
666 | self.service_name = service_name |
667 | self.volume_service_name = volume_service_name |
668 | + self.retries = int(retries or 0) |
669 | + self.http_log_debug = http_log_debug |
670 | |
671 | self.management_url = None |
672 | self.auth_token = None |
673 | self.proxy_token = proxy_token |
674 | self.proxy_tenant_id = proxy_tenant_id |
675 | |
676 | - # httplib2 overrides |
677 | - self.force_exception_to_status_code = True |
678 | - self.disable_ssl_certificate_validation = insecure |
679 | - |
680 | - def http_log(self, args, kwargs, resp, body): |
681 | - if not _logger.isEnabledFor(logging.DEBUG): |
682 | + if insecure: |
683 | + self.verify_cert = False |
684 | + else: |
685 | + if cacert: |
686 | + self.verify_cert = cacert |
687 | + else: |
688 | + self.verify_cert = True |
689 | + |
690 | + self._logger = logging.getLogger(__name__) |
691 | + if self.http_log_debug: |
692 | + ch = logging.StreamHandler() |
693 | + self._logger.setLevel(logging.DEBUG) |
694 | + self._logger.addHandler(ch) |
695 | + self.requests_config['verbose'] = sys.stderr |
696 | + |
697 | + def http_log_req(self, args, kwargs): |
698 | + if not self.http_log_debug: |
699 | return |
700 | |
701 | string_parts = ['curl -i'] |
702 | for element in args: |
703 | - if element in ('GET', 'POST'): |
704 | + if element in ('GET', 'POST', 'DELETE', 'PUT'): |
705 | string_parts.append(' -X %s' % element) |
706 | else: |
707 | string_parts.append(' %s' % element) |
708 | @@ -80,59 +96,94 @@ |
709 | header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) |
710 | string_parts.append(header) |
711 | |
712 | - _logger.debug("REQ: %s\n" % "".join(string_parts)) |
713 | if 'body' in kwargs: |
714 | - _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) |
715 | - _logger.debug("RESP:%s %s\n", resp, body) |
716 | - |
717 | - def request(self, *args, **kwargs): |
718 | + string_parts.append(" -d '%s'" % (kwargs['body'])) |
719 | + self._logger.debug("\nREQ: %s\n" % "".join(string_parts)) |
720 | + |
721 | + def http_log_resp(self, resp): |
722 | + if not self.http_log_debug: |
723 | + return |
724 | + self._logger.debug( |
725 | + "RESP: [%s] %s\nRESP BODY: %s\n", |
726 | + resp.status_code, |
727 | + resp.headers, |
728 | + resp.text) |
729 | + |
730 | + def request(self, url, method, **kwargs): |
731 | kwargs.setdefault('headers', kwargs.get('headers', {})) |
732 | kwargs['headers']['User-Agent'] = self.USER_AGENT |
733 | kwargs['headers']['Accept'] = 'application/json' |
734 | if 'body' in kwargs: |
735 | kwargs['headers']['Content-Type'] = 'application/json' |
736 | - kwargs['body'] = json.dumps(kwargs['body']) |
737 | - |
738 | - resp, body = super(HTTPClient, self).request(*args, **kwargs) |
739 | - |
740 | - self.http_log(args, kwargs, resp, body) |
741 | - |
742 | - if body: |
743 | + kwargs['data'] = json.dumps(kwargs['body']) |
744 | + del kwargs['body'] |
745 | + |
746 | + self.http_log_req((url, method,), kwargs) |
747 | + resp = requests.request( |
748 | + method, |
749 | + url, |
750 | + verify=self.verify_cert, |
751 | + config=self.requests_config, |
752 | + **kwargs) |
753 | + self.http_log_resp(resp) |
754 | + |
755 | + if resp.text: |
756 | try: |
757 | - body = json.loads(body) |
758 | + body = json.loads(resp.text) |
759 | except ValueError: |
760 | pass |
761 | + body = None |
762 | else: |
763 | body = None |
764 | |
765 | - if resp.status >= 400: |
766 | + if resp.status_code >= 400: |
767 | raise exceptions.from_response(resp, body) |
768 | |
769 | return resp, body |
770 | |
771 | def _cs_request(self, url, method, **kwargs): |
772 | - if not self.management_url: |
773 | - self.authenticate() |
774 | - |
775 | - # Perform the request once. If we get a 401 back then it |
776 | - # might be because the auth token expired, so try to |
777 | - # re-authenticate and try again. If it still fails, bail. |
778 | - try: |
779 | + auth_attempts = 0 |
780 | + attempts = 0 |
781 | + backoff = 1 |
782 | + while True: |
783 | + attempts += 1 |
784 | + if not self.management_url or not self.auth_token: |
785 | + self.authenticate() |
786 | kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token |
787 | if self.projectid: |
788 | kwargs['headers']['X-Auth-Project-Id'] = self.projectid |
789 | - |
790 | - resp, body = self.request(self.management_url + url, method, |
791 | - **kwargs) |
792 | - return resp, body |
793 | - except exceptions.Unauthorized, ex: |
794 | try: |
795 | - self.authenticate() |
796 | resp, body = self.request(self.management_url + url, method, |
797 | **kwargs) |
798 | return resp, body |
799 | + except exceptions.BadRequest as e: |
800 | + if attempts > self.retries: |
801 | + raise |
802 | except exceptions.Unauthorized: |
803 | - raise ex |
804 | + if auth_attempts > 0: |
805 | + raise |
806 | + self._logger.debug("Unauthorized, reauthenticating.") |
807 | + self.management_url = self.auth_token = None |
808 | + # First reauth. Discount this attempt. |
809 | + attempts -= 1 |
810 | + auth_attempts += 1 |
811 | + continue |
812 | + except exceptions.ClientException as e: |
813 | + if attempts > self.retries: |
814 | + raise |
815 | + if 500 <= e.code <= 599: |
816 | + pass |
817 | + else: |
818 | + raise |
819 | + except requests.exceptions.ConnectionError as e: |
820 | + # Catch a connection refused from requests.request |
821 | + self._logger.debug("Connection refused: %s" % e) |
822 | + raise |
823 | + self._logger.debug( |
824 | + "Failed attempt(%s of %s), retrying in %s seconds" % |
825 | + (attempts, self.retries, backoff)) |
826 | + sleep(backoff) |
827 | + backoff *= 2 |
828 | |
829 | def get(self, url, **kwargs): |
830 | return self._cs_request(url, 'GET', **kwargs) |
831 | @@ -151,7 +202,7 @@ |
832 | We may get redirected to another site, fail or actually get |
833 | back a service catalog with a token and our endpoints.""" |
834 | |
835 | - if resp.status == 200: # content must always present |
836 | + if resp.status_code == 200: # content must always present |
837 | try: |
838 | self.auth_url = url |
839 | self.service_catalog = \ |
840 | @@ -179,7 +230,7 @@ |
841 | print "Could not find any suitable endpoint. Correct region?" |
842 | raise |
843 | |
844 | - elif resp.status == 305: |
845 | + elif resp.status_code == 305: |
846 | return resp['location'] |
847 | else: |
848 | raise exceptions.from_response(resp, body) |
849 | @@ -199,7 +250,7 @@ |
850 | # GET ...:5001/v2.0/tokens/#####/endpoints |
851 | url = '/'.join([url, 'tokens', '%s?belongsTo=%s' |
852 | % (self.proxy_token, self.proxy_tenant_id)]) |
853 | - _logger.debug("Using Endpoint URL: %s" % url) |
854 | + self._logger.debug("Using Endpoint URL: %s" % url) |
855 | resp, body = self.request(url, "GET", |
856 | headers={'X-Auth_Token': self.auth_token}) |
857 | return self._extract_service_catalog(url, resp, body, |
858 | @@ -262,16 +313,16 @@ |
859 | headers['X-Auth-Project-Id'] = self.projectid |
860 | |
861 | resp, body = self.request(url, 'GET', headers=headers) |
862 | - if resp.status in (200, 204): # in some cases we get No Content |
863 | + if resp.status_code in (200, 204): # in some cases we get No Content |
864 | try: |
865 | mgmt_header = 'x-server-management-url' |
866 | - self.management_url = resp[mgmt_header].rstrip('/') |
867 | - self.auth_token = resp['x-auth-token'] |
868 | + self.management_url = resp.headers[mgmt_header].rstrip('/') |
869 | + self.auth_token = resp.headers['x-auth-token'] |
870 | self.auth_url = url |
871 | - except KeyError: |
872 | + except (KeyError, TypeError): |
873 | raise exceptions.AuthorizationFailure() |
874 | - elif resp.status == 305: |
875 | - return resp['location'] |
876 | + elif resp.status_code == 305: |
877 | + return resp.headers['location'] |
878 | else: |
879 | raise exceptions.from_response(resp, body) |
880 | |
881 | @@ -303,13 +354,11 @@ |
882 | token_url = url + "/tokens" |
883 | |
884 | # Make sure we follow redirects when trying to reach Keystone |
885 | - tmp_follow_all_redirects = self.follow_all_redirects |
886 | - self.follow_all_redirects = True |
887 | - |
888 | - try: |
889 | - resp, body = self.request(token_url, "POST", body=body) |
890 | - finally: |
891 | - self.follow_all_redirects = tmp_follow_all_redirects |
892 | + resp, body = self.request( |
893 | + token_url, |
894 | + "POST", |
895 | + body=body, |
896 | + allow_redirects=True) |
897 | |
898 | return self._extract_service_catalog(url, resp, body) |
899 | |
900 | |
901 | === modified file 'cinderclient/exceptions.py' |
902 | --- cinderclient/exceptions.py 2012-06-27 13:32:14 +0000 |
903 | +++ cinderclient/exceptions.py 2013-01-10 22:15:27 +0000 |
904 | @@ -124,16 +124,19 @@ |
905 | def from_response(response, body): |
906 | """ |
907 | Return an instance of an ClientException or subclass |
908 | - based on an httplib2 response. |
909 | + based on an requests response. |
910 | |
911 | Usage:: |
912 | |
913 | - resp, body = http.request(...) |
914 | - if resp.status != 200: |
915 | - raise exception_from_response(resp, body) |
916 | + resp, body = requests.request(...) |
917 | + if resp.status_code != 200: |
918 | + raise exception_from_response(resp, rest.text) |
919 | """ |
920 | - cls = _code_map.get(response.status, ClientException) |
921 | - request_id = response.get('x-compute-request-id') |
922 | + cls = _code_map.get(response.status_code, ClientException) |
923 | + if response.headers: |
924 | + request_id = response.headers.get('x-compute-request-id') |
925 | + else: |
926 | + request_id = None |
927 | if body: |
928 | message = "n/a" |
929 | details = "n/a" |
930 | @@ -141,7 +144,7 @@ |
931 | error = body[body.keys()[0]] |
932 | message = error.get('message', None) |
933 | details = error.get('details', None) |
934 | - return cls(code=response.status, message=message, details=details, |
935 | + return cls(code=response.status_code, message=message, details=details, |
936 | request_id=request_id) |
937 | else: |
938 | - return cls(code=response.status, request_id=request_id) |
939 | + return cls(code=response.status_code, request_id=request_id) |
940 | |
941 | === modified file 'cinderclient/openstack/common/setup.py' |
942 | --- cinderclient/openstack/common/setup.py 2012-08-16 12:03:50 +0000 |
943 | +++ cinderclient/openstack/common/setup.py 2013-01-10 22:15:27 +0000 |
944 | @@ -31,13 +31,13 @@ |
945 | def parse_mailmap(mailmap='.mailmap'): |
946 | mapping = {} |
947 | if os.path.exists(mailmap): |
948 | - fp = open(mailmap, 'r') |
949 | - for l in fp: |
950 | - l = l.strip() |
951 | - if not l.startswith('#') and ' ' in l: |
952 | - canonical_email, alias = [x for x in l.split(' ') |
953 | - if x.startswith('<')] |
954 | - mapping[alias] = canonical_email |
955 | + with open(mailmap, 'r') as fp: |
956 | + for l in fp: |
957 | + l = l.strip() |
958 | + if not l.startswith('#') and ' ' in l: |
959 | + canonical_email, alias = [x for x in l.split(' ') |
960 | + if x.startswith('<')] |
961 | + mapping[alias] = canonical_email |
962 | return mapping |
963 | |
964 | |
965 | @@ -52,10 +52,10 @@ |
966 | |
967 | # Get requirements from the first file that exists |
968 | def get_reqs_from_files(requirements_files): |
969 | - reqs_in = [] |
970 | for requirements_file in requirements_files: |
971 | if os.path.exists(requirements_file): |
972 | - return open(requirements_file, 'r').read().split('\n') |
973 | + with open(requirements_file, 'r') as fil: |
974 | + return fil.read().split('\n') |
975 | return [] |
976 | |
977 | |
978 | @@ -117,8 +117,12 @@ |
979 | |
980 | |
981 | def _run_shell_command(cmd): |
982 | - output = subprocess.Popen(["/bin/sh", "-c", cmd], |
983 | - stdout=subprocess.PIPE) |
984 | + if os.name == 'nt': |
985 | + output = subprocess.Popen(["cmd.exe", "/C", cmd], |
986 | + stdout=subprocess.PIPE) |
987 | + else: |
988 | + output = subprocess.Popen(["/bin/sh", "-c", cmd], |
989 | + stdout=subprocess.PIPE) |
990 | out = output.communicate() |
991 | if len(out) == 0: |
992 | return None |
993 | @@ -136,11 +140,19 @@ |
994 | _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") |
995 | milestone_cmd = "git show meta/openstack/release:%s" % branch_name |
996 | milestonever = _run_shell_command(milestone_cmd) |
997 | - if not milestonever: |
998 | - milestonever = "" |
999 | + if milestonever: |
1000 | + first_half = "%s~%s" % (milestonever, datestamp) |
1001 | + else: |
1002 | + first_half = datestamp |
1003 | + |
1004 | post_version = _get_git_post_version() |
1005 | - revno = post_version.split(".")[-1] |
1006 | - return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno) |
1007 | + # post version should look like: |
1008 | + # 0.1.1.4.gcc9e28a |
1009 | + # where the bit after the last . is the short sha, and the bit between |
1010 | + # the last and second to last is the revno count |
1011 | + (revno, sha) = post_version.split(".")[-2:] |
1012 | + second_half = "%s%s.%s" % (revno_prefix, revno, sha) |
1013 | + return ".".join((first_half, second_half)) |
1014 | |
1015 | |
1016 | def _get_git_current_tag(): |
1017 | @@ -162,39 +174,48 @@ |
1018 | cmd = "git --no-pager log --oneline" |
1019 | out = _run_shell_command(cmd) |
1020 | revno = len(out.split("\n")) |
1021 | + sha = _run_shell_command("git describe --always") |
1022 | else: |
1023 | tag_infos = tag_info.split("-") |
1024 | base_version = "-".join(tag_infos[:-2]) |
1025 | - revno = tag_infos[-2] |
1026 | - return "%s.%s" % (base_version, revno) |
1027 | + (revno, sha) = tag_infos[-2:] |
1028 | + return "%s.%s.%s" % (base_version, revno, sha) |
1029 | |
1030 | |
1031 | def write_git_changelog(): |
1032 | """Write a changelog based on the git changelog.""" |
1033 | - if os.path.isdir('.git'): |
1034 | - git_log_cmd = 'git log --stat' |
1035 | - changelog = _run_shell_command(git_log_cmd) |
1036 | - mailmap = parse_mailmap() |
1037 | - with open("ChangeLog", "w") as changelog_file: |
1038 | - changelog_file.write(canonicalize_emails(changelog, mailmap)) |
1039 | + new_changelog = 'ChangeLog' |
1040 | + if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): |
1041 | + if os.path.isdir('.git'): |
1042 | + git_log_cmd = 'git log --stat' |
1043 | + changelog = _run_shell_command(git_log_cmd) |
1044 | + mailmap = parse_mailmap() |
1045 | + with open(new_changelog, "w") as changelog_file: |
1046 | + changelog_file.write(canonicalize_emails(changelog, mailmap)) |
1047 | + else: |
1048 | + open(new_changelog, 'w').close() |
1049 | |
1050 | |
1051 | def generate_authors(): |
1052 | """Create AUTHORS file using git commits.""" |
1053 | - jenkins_email = 'jenkins@review.openstack.org' |
1054 | + jenkins_email = 'jenkins@review.(openstack|stackforge).org' |
1055 | old_authors = 'AUTHORS.in' |
1056 | new_authors = 'AUTHORS' |
1057 | - if os.path.isdir('.git'): |
1058 | - # don't include jenkins email address in AUTHORS file |
1059 | - git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " |
1060 | - "grep -v " + jenkins_email) |
1061 | - changelog = _run_shell_command(git_log_cmd) |
1062 | - mailmap = parse_mailmap() |
1063 | - with open(new_authors, 'w') as new_authors_fh: |
1064 | - new_authors_fh.write(canonicalize_emails(changelog, mailmap)) |
1065 | - if os.path.exists(old_authors): |
1066 | - with open(old_authors, "r") as old_authors_fh: |
1067 | - new_authors_fh.write('\n' + old_authors_fh.read()) |
1068 | + if not os.getenv('SKIP_GENERATE_AUTHORS'): |
1069 | + if os.path.isdir('.git'): |
1070 | + # don't include jenkins email address in AUTHORS file |
1071 | + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " |
1072 | + "egrep -v '" + jenkins_email + "'") |
1073 | + changelog = _run_shell_command(git_log_cmd) |
1074 | + mailmap = parse_mailmap() |
1075 | + with open(new_authors, 'w') as new_authors_fh: |
1076 | + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) |
1077 | + if os.path.exists(old_authors): |
1078 | + with open(old_authors, "r") as old_authors_fh: |
1079 | + new_authors_fh.write('\n' + old_authors_fh.read()) |
1080 | + else: |
1081 | + open(new_authors, 'w').close() |
1082 | + |
1083 | |
1084 | _rst_template = """%(heading)s |
1085 | %(underline)s |
1086 | @@ -206,9 +227,24 @@ |
1087 | """ |
1088 | |
1089 | |
1090 | +def read_versioninfo(project): |
1091 | + """Read the versioninfo file. If it doesn't exist, we're in a github |
1092 | + zipball, and there's really no way to know what version we really |
1093 | + are, but that should be ok, because the utility of that should be |
1094 | + just about nil if this code path is in use in the first place.""" |
1095 | + versioninfo_path = os.path.join(project, 'versioninfo') |
1096 | + if os.path.exists(versioninfo_path): |
1097 | + with open(versioninfo_path, 'r') as vinfo: |
1098 | + version = vinfo.read().strip() |
1099 | + else: |
1100 | + version = None |
1101 | + return version |
1102 | + |
1103 | + |
1104 | def write_versioninfo(project, version): |
1105 | """Write a simple file containing the version of the package.""" |
1106 | - open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) |
1107 | + with open(os.path.join(project, 'versioninfo'), 'w') as fil: |
1108 | + fil.write("%s\n" % version) |
1109 | |
1110 | |
1111 | def get_cmdclass(): |
1112 | @@ -299,8 +335,10 @@ |
1113 | |
1114 | |
1115 | def get_pre_version(projectname, base_version): |
1116 | - """Return a version which is based""" |
1117 | - if os.path.isdir('.git'): |
1118 | + """Return a version which is leading up to a version that will |
1119 | + be released in the future.""" |
1120 | + version = read_versioninfo(projectname) |
1121 | + if not version and os.path.isdir('.git'): |
1122 | current_tag = _get_git_current_tag() |
1123 | if current_tag is not None: |
1124 | version = current_tag |
1125 | @@ -311,11 +349,9 @@ |
1126 | version_suffix = _get_git_next_version_suffix(branch_name) |
1127 | version = "%s~%s" % (base_version, version_suffix) |
1128 | write_versioninfo(projectname, version) |
1129 | - return version.split('~')[0] |
1130 | - else: |
1131 | - with open(os.path.join(projectname, 'versioninfo'), 'r') as vinfo: |
1132 | - full_version = vinfo.read().strip() |
1133 | - return full_version.split('~')[0] |
1134 | + if not version: |
1135 | + version = "0.0.0" |
1136 | + return version |
1137 | |
1138 | |
1139 | def get_post_version(projectname): |
1140 | @@ -323,8 +359,10 @@ |
1141 | revision if there is one, or tag plus number of additional revisions |
1142 | if the current revision has no tag.""" |
1143 | |
1144 | - if os.path.isdir('.git'): |
1145 | + version = read_versioninfo(projectname) |
1146 | + if not version and os.path.isdir('.git'): |
1147 | version = _get_git_post_version() |
1148 | write_versioninfo(projectname, version) |
1149 | - return version |
1150 | - return open(os.path.join(projectname, 'versioninfo'), 'r').read().strip() |
1151 | + if not version: |
1152 | + version = "0.0.0" |
1153 | + return version |
1154 | |
1155 | === added file 'cinderclient/openstack/common/version.py' |
1156 | --- cinderclient/openstack/common/version.py 1970-01-01 00:00:00 +0000 |
1157 | +++ cinderclient/openstack/common/version.py 2013-01-10 22:15:27 +0000 |
1158 | @@ -0,0 +1,148 @@ |
1159 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
1160 | + |
1161 | +# Copyright 2012 OpenStack LLC |
1162 | +# |
1163 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1164 | +# not use this file except in compliance with the License. You may obtain |
1165 | +# a copy of the License at |
1166 | +# |
1167 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1168 | +# |
1169 | +# Unless required by applicable law or agreed to in writing, software |
1170 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1171 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1172 | +# License for the specific language governing permissions and limitations |
1173 | +# under the License. |
1174 | + |
1175 | +""" |
1176 | +Utilities for consuming the auto-generated versioninfo files. |
1177 | +""" |
1178 | + |
1179 | +import datetime |
1180 | +import pkg_resources |
1181 | + |
1182 | +import setup |
1183 | + |
1184 | + |
1185 | +class _deferred_version_string(object): |
1186 | + """Internal helper class which provides delayed version calculation.""" |
1187 | + def __init__(self, version_info, prefix): |
1188 | + self.version_info = version_info |
1189 | + self.prefix = prefix |
1190 | + |
1191 | + def __str__(self): |
1192 | + return "%s%s" % (self.prefix, self.version_info.version_string()) |
1193 | + |
1194 | + def __repr__(self): |
1195 | + return "%s%s" % (self.prefix, self.version_info.version_string()) |
1196 | + |
1197 | + |
1198 | +class VersionInfo(object): |
1199 | + |
1200 | + def __init__(self, package, python_package=None, pre_version=None): |
1201 | + """Object that understands versioning for a package |
1202 | + :param package: name of the top level python namespace. For glance, |
1203 | + this would be "glance" for python-glanceclient, it |
1204 | + would be "glanceclient" |
1205 | + :param python_package: optional name of the project name. For |
1206 | + glance this can be left unset. For |
1207 | + python-glanceclient, this would be |
1208 | + "python-glanceclient" |
1209 | + :param pre_version: optional version that the project is working to |
1210 | + """ |
1211 | + self.package = package |
1212 | + if python_package is None: |
1213 | + self.python_package = package |
1214 | + else: |
1215 | + self.python_package = python_package |
1216 | + self.pre_version = pre_version |
1217 | + self.version = None |
1218 | + |
1219 | + def _generate_version(self): |
1220 | + """Defer to the openstack.common.setup routines for making a |
1221 | + version from git.""" |
1222 | + if self.pre_version is None: |
1223 | + return setup.get_post_version(self.python_package) |
1224 | + else: |
1225 | + return setup.get_pre_version(self.python_package, self.pre_version) |
1226 | + |
1227 | + def _newer_version(self, pending_version): |
1228 | + """Check to see if we're working with a stale version or not. |
1229 | + We expect a version string that either looks like: |
1230 | + 2012.2~f3~20120708.10.4426392 |
1231 | + which is an unreleased version of a pre-version, or: |
1232 | + 0.1.1.4.gcc9e28a |
1233 | + which is an unreleased version of a post-version, or: |
1234 | + 0.1.1 |
1235 | + Which is a release and which should match tag. |
1236 | + For now, if we have a date-embedded version, check to see if it's |
1237 | + old, and if so re-generate. Otherwise, just deal with it. |
1238 | + """ |
1239 | + try: |
1240 | + version_date = int(self.version.split("~")[-1].split('.')[0]) |
1241 | + if version_date < int(datetime.date.today().strftime('%Y%m%d')): |
1242 | + return self._generate_version() |
1243 | + else: |
1244 | + return pending_version |
1245 | + except Exception: |
1246 | + return pending_version |
1247 | + |
1248 | + def version_string_with_vcs(self, always=False): |
1249 | + """Return the full version of the package including suffixes indicating |
1250 | + VCS status. |
1251 | + |
1252 | + For instance, if we are working towards the 2012.2 release, |
1253 | + canonical_version_string should return 2012.2 if this is a final |
1254 | + release, or else something like 2012.2~f1~20120705.20 if it's not. |
1255 | + |
1256 | + :param always: if true, skip all version caching |
1257 | + """ |
1258 | + if always: |
1259 | + self.version = self._generate_version() |
1260 | + |
1261 | + if self.version is None: |
1262 | + |
1263 | + requirement = pkg_resources.Requirement.parse(self.python_package) |
1264 | + versioninfo = "%s/versioninfo" % self.package |
1265 | + try: |
1266 | + raw_version = pkg_resources.resource_string(requirement, |
1267 | + versioninfo) |
1268 | + self.version = self._newer_version(raw_version.strip()) |
1269 | + except (IOError, pkg_resources.DistributionNotFound): |
1270 | + self.version = self._generate_version() |
1271 | + |
1272 | + return self.version |
1273 | + |
1274 | + def canonical_version_string(self, always=False): |
1275 | + """Return the simple version of the package excluding any suffixes. |
1276 | + |
1277 | + For instance, if we are working towards the 2012.2 release, |
1278 | + canonical_version_string should return 2012.2 in all cases. |
1279 | + |
1280 | + :param always: if true, skip all version caching |
1281 | + """ |
1282 | + return self.version_string_with_vcs(always).split('~')[0] |
1283 | + |
1284 | + def version_string(self, always=False): |
1285 | + """Return the base version of the package. |
1286 | + |
1287 | + For instance, if we are working towards the 2012.2 release, |
1288 | + version_string should return 2012.2 if this is a final release, or |
1289 | + 2012.2-dev if it is not. |
1290 | + |
1291 | + :param always: if true, skip all version caching |
1292 | + """ |
1293 | + version_parts = self.version_string_with_vcs(always).split('~') |
1294 | + if len(version_parts) == 1: |
1295 | + return version_parts[0] |
1296 | + else: |
1297 | + return '%s-dev' % (version_parts[0],) |
1298 | + |
1299 | + def deferred_version_string(self, prefix=""): |
1300 | + """Generate an object which will expand in a string context to |
1301 | + the results of version_string(). We do this so that don't |
1302 | + call into pkg_resources every time we start up a program when |
1303 | + passing version information into the CONF constructor, but |
1304 | + rather only do the calculation when and if a version is requested |
1305 | + """ |
1306 | + return _deferred_version_string(self, prefix) |
1307 | |
1308 | === modified file 'cinderclient/shell.py' |
1309 | --- cinderclient/shell.py 2012-09-07 11:35:32 +0000 |
1310 | +++ cinderclient/shell.py 2013-01-10 22:15:27 +0000 |
1311 | @@ -20,7 +20,6 @@ |
1312 | |
1313 | import argparse |
1314 | import glob |
1315 | -import httplib2 |
1316 | import imp |
1317 | import itertools |
1318 | import os |
1319 | @@ -81,8 +80,9 @@ |
1320 | help=argparse.SUPPRESS) |
1321 | |
1322 | parser.add_argument('--debug', |
1323 | - default=False, |
1324 | action='store_true', |
1325 | + default=utils.env('CINDERCLIENT_DEBUG', |
1326 | + default=False), |
1327 | help="Print debugging output") |
1328 | |
1329 | parser.add_argument('--os-username', |
1330 | @@ -163,12 +163,25 @@ |
1331 | parser.add_argument('--os_volume_api_version', |
1332 | help=argparse.SUPPRESS) |
1333 | |
1334 | + parser.add_argument('--os-cacert', |
1335 | + metavar='<ca-certificate>', |
1336 | + default=utils.env('OS_CACERT', default=None), |
1337 | + help='Specify a CA bundle file to use in ' |
1338 | + 'verifying a TLS (https) server certificate. ' |
1339 | + 'Defaults to env[OS_CACERT]') |
1340 | + |
1341 | parser.add_argument('--insecure', |
1342 | default=utils.env('CINDERCLIENT_INSECURE', |
1343 | default=False), |
1344 | action='store_true', |
1345 | help=argparse.SUPPRESS) |
1346 | |
1347 | + parser.add_argument('--retries', |
1348 | + metavar='<retries>', |
1349 | + type=int, |
1350 | + default=0, |
1351 | + help='Number of retries.') |
1352 | + |
1353 | # FIXME(dtroyer): The args below are here for diablo compatibility, |
1354 | # remove them in folsum cycle |
1355 | |
1356 | @@ -301,8 +314,6 @@ |
1357 | logger.setLevel(logging.DEBUG) |
1358 | logger.addHandler(streamhandler) |
1359 | |
1360 | - httplib2.debuglevel = 1 |
1361 | - |
1362 | def main(self, argv): |
1363 | # Parse args once to find version and debug settings |
1364 | parser = self.get_base_parser() |
1365 | @@ -318,7 +329,7 @@ |
1366 | options.os_volume_api_version) |
1367 | self.parser = subcommand_parser |
1368 | |
1369 | - if options.help and len(args) == 0: |
1370 | + if options.help or not argv: |
1371 | subcommand_parser.print_help() |
1372 | return 0 |
1373 | |
1374 | @@ -336,14 +347,14 @@ |
1375 | (os_username, os_password, os_tenant_name, os_auth_url, |
1376 | os_region_name, endpoint_type, insecure, |
1377 | service_type, service_name, volume_service_name, |
1378 | - username, apikey, projectid, url, region_name) = ( |
1379 | + username, apikey, projectid, url, region_name, cacert) = ( |
1380 | args.os_username, args.os_password, |
1381 | args.os_tenant_name, args.os_auth_url, |
1382 | args.os_region_name, args.endpoint_type, |
1383 | args.insecure, args.service_type, args.service_name, |
1384 | args.volume_service_name, args.username, |
1385 | args.apikey, args.projectid, |
1386 | - args.url, args.region_name) |
1387 | + args.url, args.region_name, args.os_cacert) |
1388 | |
1389 | if not endpoint_type: |
1390 | endpoint_type = DEFAULT_CINDER_ENDPOINT_TYPE |
1391 | @@ -408,7 +419,10 @@ |
1392 | extensions=self.extensions, |
1393 | service_type=service_type, |
1394 | service_name=service_name, |
1395 | - volume_service_name=volume_service_name) |
1396 | + volume_service_name=volume_service_name, |
1397 | + retries=options.retries, |
1398 | + http_log_debug=args.debug, |
1399 | + cacert=cacert) |
1400 | |
1401 | try: |
1402 | if not utils.isunauthenticated(args.func): |
1403 | |
1404 | === modified file 'cinderclient/utils.py' |
1405 | --- cinderclient/utils.py 2012-06-27 13:32:14 +0000 |
1406 | +++ cinderclient/utils.py 2013-01-10 22:15:27 +0000 |
1407 | @@ -1,3 +1,4 @@ |
1408 | +import locale |
1409 | import os |
1410 | import re |
1411 | import sys |
1412 | @@ -179,12 +180,19 @@ |
1413 | return manager.find(name=name_or_id) |
1414 | except exceptions.NotFound: |
1415 | try: |
1416 | - # Volumes does not have name, but display_name |
1417 | - return manager.find(display_name=name_or_id) |
1418 | - except exceptions.NotFound: |
1419 | - msg = "No %s with a name or ID of '%s' exists." % \ |
1420 | - (manager.resource_class.__name__.lower(), name_or_id) |
1421 | - raise exceptions.CommandError(msg) |
1422 | + # For command-line arguments that are in Unicode |
1423 | + encoding = (locale.getpreferredencoding() or |
1424 | + sys.stdin.encoding or |
1425 | + 'UTF-8') |
1426 | + return manager.find(display_name=(name_or_id.decode(encoding))) |
1427 | + except (UnicodeDecodeError, exceptions.NotFound): |
1428 | + try: |
1429 | + # Volumes does not have name, but display_name |
1430 | + return manager.find(display_name=name_or_id) |
1431 | + except exceptions.NotFound: |
1432 | + msg = "No %s with a name or ID of '%s' exists." % \ |
1433 | + (manager.resource_class.__name__.lower(), name_or_id) |
1434 | + raise exceptions.CommandError(msg) |
1435 | except exceptions.NoUniqueMatch: |
1436 | msg = ("Multiple %s matches found for '%s', use an ID to be more" |
1437 | " specific." % (manager.resource_class.__name__.lower(), |
1438 | |
1439 | === modified file 'cinderclient/v1/client.py' |
1440 | --- cinderclient/v1/client.py 2012-10-01 12:05:26 +0000 |
1441 | +++ cinderclient/v1/client.py 2013-01-10 22:15:27 +0000 |
1442 | @@ -17,9 +17,7 @@ |
1443 | |
1444 | Then call methods on its managers:: |
1445 | |
1446 | - >>> client.servers.list() |
1447 | - ... |
1448 | - >>> client.flavors.list() |
1449 | + >>> client.volumes.list() |
1450 | ... |
1451 | |
1452 | """ |
1453 | @@ -29,7 +27,9 @@ |
1454 | proxy_tenant_id=None, proxy_token=None, region_name=None, |
1455 | endpoint_type='publicURL', extensions=None, |
1456 | service_type='volume', service_name=None, |
1457 | - volume_service_name=None): |
1458 | + volume_service_name=None, retries=None, |
1459 | + http_log_debug=False, |
1460 | + cacert=None): |
1461 | # FIXME(comstud): Rename the api_key argument above when we |
1462 | # know it's not being used as keyword argument |
1463 | password = api_key |
1464 | @@ -63,7 +63,10 @@ |
1465 | endpoint_type=endpoint_type, |
1466 | service_type=service_type, |
1467 | service_name=service_name, |
1468 | - volume_service_name=volume_service_name) |
1469 | + volume_service_name=volume_service_name, |
1470 | + retries=retries, |
1471 | + http_log_debug=http_log_debug, |
1472 | + cacert=cacert) |
1473 | |
1474 | def authenticate(self): |
1475 | """ |
1476 | |
1477 | === added file 'cinderclient/v1/contrib/list_extensions.py' |
1478 | --- cinderclient/v1/contrib/list_extensions.py 1970-01-01 00:00:00 +0000 |
1479 | +++ cinderclient/v1/contrib/list_extensions.py 2013-01-10 22:15:27 +0000 |
1480 | @@ -0,0 +1,47 @@ |
1481 | +# Copyright 2011 OpenStack LLC. |
1482 | +# All Rights Reserved. |
1483 | +# |
1484 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1485 | +# not use this file except in compliance with the License. You may obtain |
1486 | +# a copy of the License at |
1487 | +# |
1488 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1489 | +# |
1490 | +# Unless required by applicable law or agreed to in writing, software |
1491 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1492 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1493 | +# License for the specific language governing permissions and limitations |
1494 | +# under the License. |
1495 | + |
1496 | +from cinderclient import base |
1497 | +from cinderclient import utils |
1498 | + |
1499 | + |
1500 | +class ListExtResource(base.Resource): |
1501 | + @property |
1502 | + def summary(self): |
1503 | + descr = self.description.strip() |
1504 | + if not descr: |
1505 | + return '??' |
1506 | + lines = descr.split("\n") |
1507 | + if len(lines) == 1: |
1508 | + return lines[0] |
1509 | + else: |
1510 | + return lines[0] + "..." |
1511 | + |
1512 | + |
1513 | +class ListExtManager(base.Manager): |
1514 | + resource_class = ListExtResource |
1515 | + |
1516 | + def show_all(self): |
1517 | + return self._list("/extensions", 'extensions') |
1518 | + |
1519 | + |
1520 | +@utils.service_type('volume') |
1521 | +def do_list_extensions(client, _args): |
1522 | + """ |
1523 | + List all the os-api extensions that are available. |
1524 | + """ |
1525 | + extensions = client.list_extensions.show_all() |
1526 | + fields = ["Name", "Summary", "Alias", "Updated"] |
1527 | + utils.print_list(extensions, fields) |
1528 | |
1529 | === modified file 'cinderclient/v1/shell.py' |
1530 | --- cinderclient/v1/shell.py 2012-10-01 12:05:26 +0000 |
1531 | +++ cinderclient/v1/shell.py 2013-01-10 22:15:27 +0000 |
1532 | @@ -20,6 +20,7 @@ |
1533 | import sys |
1534 | import time |
1535 | |
1536 | +from cinderclient import exceptions |
1537 | from cinderclient import utils |
1538 | |
1539 | |
1540 | @@ -91,14 +92,17 @@ |
1541 | setattr(item, to_key, item._info[from_key]) |
1542 | |
1543 | |
1544 | -def _extract_metadata(arg_list): |
1545 | +def _extract_metadata(args): |
1546 | metadata = {} |
1547 | - for metadatum in arg_list: |
1548 | - assert(metadatum.find('=') > -1), "Improperly formatted metadata "\ |
1549 | - "input (%s)" % metadatum |
1550 | - (key, value) = metadatum.split('=', 1) |
1551 | + for metadatum in args.metadata[0]: |
1552 | + # unset doesn't require a val, so we have the if/else |
1553 | + if '=' in metadatum: |
1554 | + (key, value) = metadatum.split('=', 1) |
1555 | + else: |
1556 | + key = metadatum |
1557 | + value = None |
1558 | + |
1559 | metadata[key] = value |
1560 | - |
1561 | return metadata |
1562 | |
1563 | |
1564 | @@ -144,7 +148,7 @@ |
1565 | servers = [s.get('server_id') for s in vol.attachments] |
1566 | setattr(vol, 'attached_to', ','.join(map(str, servers))) |
1567 | utils.print_list(volumes, ['ID', 'Status', 'Display Name', |
1568 | - 'Size', 'Volume Type', 'Attached to']) |
1569 | + 'Size', 'Volume Type', 'Bootable', 'Attached to']) |
1570 | |
1571 | |
1572 | @utils.arg('volume', metavar='<volume>', help='ID of the volume.') |
1573 | @@ -168,6 +172,14 @@ |
1574 | '--snapshot_id', |
1575 | help=argparse.SUPPRESS) |
1576 | @utils.arg( |
1577 | + '--source-volid', |
1578 | + metavar='<source-volid>', |
1579 | + default=None, |
1580 | + help='Create volume from volume id (Optional, Default=None)') |
1581 | +@utils.arg( |
1582 | + '--source_volid', |
1583 | + help=argparse.SUPPRESS) |
1584 | +@utils.arg( |
1585 | '--image-id', |
1586 | metavar='<image-id>', |
1587 | default=None, |
1588 | @@ -219,10 +231,11 @@ |
1589 | |
1590 | volume_metadata = None |
1591 | if args.metadata is not None: |
1592 | - volume_metadata = _extract_metadata(args.metadata) |
1593 | + volume_metadata = _extract_metadata(args) |
1594 | |
1595 | volume = cs.volumes.create(args.size, |
1596 | args.snapshot_id, |
1597 | + args.source_volid, |
1598 | args.display_name, |
1599 | args.display_description, |
1600 | args.volume_type, |
1601 | @@ -240,6 +253,48 @@ |
1602 | volume.delete() |
1603 | |
1604 | |
1605 | +@utils.arg('volume', metavar='<volume>', help='ID of the volume to rename.') |
1606 | +@utils.arg('display_name', nargs='?', metavar='<display-name>', |
1607 | + help='New display-name for the volume.') |
1608 | +@utils.arg('--display-description', metavar='<display-description>', |
1609 | + help='Optional volume description. (Default=None)', |
1610 | + default=None) |
1611 | +@utils.service_type('volume') |
1612 | +def do_rename(cs, args): |
1613 | + """Rename a volume.""" |
1614 | + kwargs = {} |
1615 | + if args.display_name is not None: |
1616 | + kwargs['display_name'] = args.display_name |
1617 | + if args.display_description is not None: |
1618 | + kwargs['display_description'] = args.display_description |
1619 | + _find_volume(cs, args.volume).update(**kwargs) |
1620 | + |
1621 | + |
1622 | +@utils.arg('volume', |
1623 | + metavar='<volume>', |
1624 | + help='ID of the volume to update metadata on.') |
1625 | +@utils.arg('action', |
1626 | + metavar='<action>', |
1627 | + choices=['set', 'unset'], |
1628 | + help="Actions: 'set' or 'unset'") |
1629 | +@utils.arg('metadata', |
1630 | + metavar='<key=value>', |
1631 | + nargs='+', |
1632 | + action='append', |
1633 | + default=[], |
1634 | + help='Metadata to set/unset (only key is necessary on unset)') |
1635 | +@utils.service_type('volume') |
1636 | +def do_metadata(cs, args): |
1637 | + """Set or Delete metadata on a volume.""" |
1638 | + volume = _find_volume(cs, args.volume) |
1639 | + metadata = _extract_metadata(args) |
1640 | + |
1641 | + if args.action == 'set': |
1642 | + cs.volumes.set_metadata(volume, metadata) |
1643 | + elif args.action == 'unset': |
1644 | + cs.volumes.delete_metadata(volume, metadata.keys()) |
1645 | + |
1646 | + |
1647 | @utils.arg( |
1648 | '--all-tenants', |
1649 | dest='all_tenants', |
1650 | @@ -340,10 +395,32 @@ |
1651 | snapshot.delete() |
1652 | |
1653 | |
1654 | +@utils.arg('snapshot', metavar='<snapshot>', help='ID of the snapshot.') |
1655 | +@utils.arg('display_name', nargs='?', metavar='<display-name>', |
1656 | + help='New display-name for the snapshot.') |
1657 | +@utils.arg('--display-description', metavar='<display-description>', |
1658 | + help='Optional snapshot description. (Default=None)', |
1659 | + default=None) |
1660 | +@utils.service_type('volume') |
1661 | +def do_snapshot_rename(cs, args): |
1662 | + """Rename a snapshot.""" |
1663 | + kwargs = {} |
1664 | + if args.display_name is not None: |
1665 | + kwargs['display_name'] = args.display_name |
1666 | + if args.display_description is not None: |
1667 | + kwargs['display_description'] = args.display_description |
1668 | + _find_volume_snapshot(cs, args.snapshot).update(**kwargs) |
1669 | + |
1670 | + |
1671 | def _print_volume_type_list(vtypes): |
1672 | utils.print_list(vtypes, ['ID', 'Name']) |
1673 | |
1674 | |
1675 | +def _print_type_and_extra_specs_list(vtypes): |
1676 | + formatters = {'extra_specs': _print_type_extra_specs} |
1677 | + utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'], formatters) |
1678 | + |
1679 | + |
1680 | @utils.service_type('volume') |
1681 | def do_type_list(cs, args): |
1682 | """Print a list of available 'volume types'.""" |
1683 | @@ -351,9 +428,16 @@ |
1684 | _print_volume_type_list(vtypes) |
1685 | |
1686 | |
1687 | +@utils.service_type('volume') |
1688 | +def do_extra_specs_list(cs, args): |
1689 | + """Print a list of current 'volume types and extra specs' (Admin Only).""" |
1690 | + vtypes = cs.volume_types.list() |
1691 | + _print_type_and_extra_specs_list(vtypes) |
1692 | + |
1693 | + |
1694 | @utils.arg('name', |
1695 | metavar='<name>', |
1696 | - help="Name of the new flavor") |
1697 | + help="Name of the new volume type") |
1698 | @utils.service_type('volume') |
1699 | def do_type_create(cs, args): |
1700 | """Create a new volume type.""" |
1701 | @@ -366,10 +450,35 @@ |
1702 | help="Unique ID of the volume type to delete") |
1703 | @utils.service_type('volume') |
1704 | def do_type_delete(cs, args): |
1705 | - """Delete a specific flavor""" |
1706 | + """Delete a specific volume type""" |
1707 | cs.volume_types.delete(args.id) |
1708 | |
1709 | |
1710 | +@utils.arg('vtype', |
1711 | + metavar='<vtype>', |
1712 | + help="Name or ID of the volume type") |
1713 | +@utils.arg('action', |
1714 | + metavar='<action>', |
1715 | + choices=['set', 'unset'], |
1716 | + help="Actions: 'set' or 'unset'") |
1717 | +@utils.arg('metadata', |
1718 | + metavar='<key=value>', |
1719 | + nargs='+', |
1720 | + action='append', |
1721 | + default=[], |
1722 | + help='Extra_specs to set/unset (only key is necessary on unset)') |
1723 | +@utils.service_type('volume') |
1724 | +def do_type_key(cs, args): |
1725 | + "Set or unset extra_spec for a volume type.""" |
1726 | + vtype = _find_volume_type(cs, args.vtype) |
1727 | + keypair = _extract_metadata(args) |
1728 | + |
1729 | + if args.action == 'set': |
1730 | + vtype.set_keys(keypair) |
1731 | + elif args.action == 'unset': |
1732 | + vtype.unset_keys(keypair.keys()) |
1733 | + |
1734 | + |
1735 | def do_endpoints(cs, args): |
1736 | """Discover endpoints that get returned from the authenticate services""" |
1737 | catalog = cs.client.service_catalog.catalog |
1738 | @@ -479,3 +588,15 @@ |
1739 | limits = cs.limits.get().rate |
1740 | columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available'] |
1741 | utils.print_list(limits, columns) |
1742 | + |
1743 | + |
1744 | +def _print_type_extra_specs(vol_type): |
1745 | + try: |
1746 | + return vol_type.get_keys() |
1747 | + except exceptions.NotFound: |
1748 | + return "N/A" |
1749 | + |
1750 | + |
1751 | +def _find_volume_type(cs, vtype): |
1752 | + """Get a volume type by name or ID.""" |
1753 | + return utils.find_resource(cs.volume_types, vtype) |
1754 | |
1755 | === modified file 'cinderclient/v1/volume_snapshots.py' |
1756 | --- cinderclient/v1/volume_snapshots.py 2012-10-01 12:05:26 +0000 |
1757 | +++ cinderclient/v1/volume_snapshots.py 2013-01-10 22:15:27 +0000 |
1758 | @@ -34,6 +34,12 @@ |
1759 | """ |
1760 | self.manager.delete(self) |
1761 | |
1762 | + def update(self, **kwargs): |
1763 | + """ |
1764 | + Update the display_name or display_description for this snapshot. |
1765 | + """ |
1766 | + self.manager.update(self, **kwargs) |
1767 | + |
1768 | @property |
1769 | def progress(self): |
1770 | return self._info.get('os-extended-snapshot-attributes:progress') |
1771 | @@ -109,3 +115,16 @@ |
1772 | :param snapshot: The :class:`Snapshot` to delete. |
1773 | """ |
1774 | self._delete("/snapshots/%s" % base.getid(snapshot)) |
1775 | + |
1776 | + def update(self, snapshot, **kwargs): |
1777 | + """ |
1778 | + Update the display_name or display_description for a snapshot. |
1779 | + |
1780 | + :param snapshot: The :class:`Snapshot` to delete. |
1781 | + """ |
1782 | + if not kwargs: |
1783 | + return |
1784 | + |
1785 | + body = {"snapshot": kwargs} |
1786 | + |
1787 | + self._update("/snapshots/%s" % base.getid(snapshot), body) |
1788 | |
1789 | === modified file 'cinderclient/v1/volume_types.py' |
1790 | --- cinderclient/v1/volume_types.py 2012-06-27 13:32:14 +0000 |
1791 | +++ cinderclient/v1/volume_types.py 2013-01-10 22:15:27 +0000 |
1792 | @@ -26,7 +26,52 @@ |
1793 | A Volume Type is the type of volume to be created |
1794 | """ |
1795 | def __repr__(self): |
1796 | - return "<Volume Type: %s>" % self.name |
1797 | + return "<VolumeType: %s>" % self.name |
1798 | + |
1799 | + def get_keys(self): |
1800 | + """ |
1801 | + Get extra specs from a volume type. |
1802 | + |
1803 | + :param vol_type: The :class:`VolumeType` to get extra specs from |
1804 | + """ |
1805 | + _resp, body = self.manager.api.client.get( |
1806 | + "/types/%s/extra_specs" % |
1807 | + base.getid(self)) |
1808 | + return body["extra_specs"] |
1809 | + |
1810 | + def set_keys(self, metadata): |
1811 | + """ |
1812 | + Set extra specs on a volume type. |
1813 | + |
1814 | + :param type : The :class:`VolumeType` to set extra spec on |
1815 | + :param metadata: A dict of key/value pairs to be set |
1816 | + """ |
1817 | + body = {'extra_specs': metadata} |
1818 | + return self.manager._create( |
1819 | + "/types/%s/extra_specs" % base.getid(self), |
1820 | + body, |
1821 | + "extra_specs", |
1822 | + return_raw=True) |
1823 | + |
1824 | + def unset_keys(self, keys): |
1825 | + """ |
1826 | + Unset extra specs on a volue type. |
1827 | + |
1828 | + :param type_id: The :class:`VolumeType` to unset extra spec on |
1829 | + :param keys: A list of keys to be unset |
1830 | + """ |
1831 | + |
1832 | + # NOTE(jdg): This wasn't actually doing all of the keys before |
1833 | + # the return in the loop resulted in ony ONE key being unset. |
1834 | + # since on success the return was NONE, we'll only interrupt the loop |
1835 | + # and return if there's an error |
1836 | + result = None |
1837 | + for k in keys: |
1838 | + resp = self.manager._delete( |
1839 | + "/types/%s/extra_specs/%s" % ( |
1840 | + base.getid(self), k)) |
1841 | + if resp is not None: |
1842 | + return resp |
1843 | |
1844 | |
1845 | class VolumeTypeManager(base.ManagerWithFind): |
1846 | |
1847 | === modified file 'cinderclient/v1/volumes.py' |
1848 | --- cinderclient/v1/volumes.py 2012-10-01 12:05:26 +0000 |
1849 | +++ cinderclient/v1/volumes.py 2013-01-10 22:15:27 +0000 |
1850 | @@ -34,6 +34,12 @@ |
1851 | """ |
1852 | self.manager.delete(self) |
1853 | |
1854 | + def update(self, **kwargs): |
1855 | + """ |
1856 | + Update the display_name or display_description for this volume. |
1857 | + """ |
1858 | + self.manager.update(self, **kwargs) |
1859 | + |
1860 | def attach(self, instance_uuid, mountpoint): |
1861 | """ |
1862 | Set attachment metadata. |
1863 | @@ -89,6 +95,15 @@ |
1864 | """ |
1865 | return self.manager.terminate_connection(self, connector) |
1866 | |
1867 | + def set_metadata(self, volume, metadata): |
1868 | + """ |
1869 | + Set or Append metadata to a volume. |
1870 | + |
1871 | + :param type : The :class: `Volume` to set metadata on |
1872 | + :param metadata: A dict of key/value pairs to set |
1873 | + """ |
1874 | + return self.manager.set_metadata(self, metadata) |
1875 | + |
1876 | |
1877 | class VolumeManager(base.ManagerWithFind): |
1878 | """ |
1879 | @@ -96,7 +111,7 @@ |
1880 | """ |
1881 | resource_class = Volume |
1882 | |
1883 | - def create(self, size, snapshot_id=None, |
1884 | + def create(self, size, snapshot_id=None, source_volid=None, |
1885 | display_name=None, display_description=None, |
1886 | volume_type=None, user_id=None, |
1887 | project_id=None, availability_zone=None, |
1888 | @@ -115,6 +130,7 @@ |
1889 | :param availability_zone: Availability Zone to use |
1890 | :param metadata: Optional metadata to set on volume creation |
1891 | :param imageRef: reference to an image stored in glance |
1892 | + :param source_volid: ID of source volume to clone from |
1893 | """ |
1894 | |
1895 | if metadata is None: |
1896 | @@ -134,6 +150,7 @@ |
1897 | 'attach_status': "detached", |
1898 | 'metadata': volume_metadata, |
1899 | 'imageRef': imageRef, |
1900 | + 'source_volid': source_volid, |
1901 | }} |
1902 | return self._create('/volumes', body, 'volume') |
1903 | |
1904 | @@ -178,51 +195,18 @@ |
1905 | """ |
1906 | self._delete("/volumes/%s" % base.getid(volume)) |
1907 | |
1908 | - def create_server_volume(self, server_id, volume_id, device): |
1909 | - """ |
1910 | - Attach a volume identified by the volume ID to the given server ID |
1911 | - |
1912 | - :param server_id: The ID of the server |
1913 | - :param volume_id: The ID of the volume to attach. |
1914 | - :param device: The device name |
1915 | - :rtype: :class:`Volume` |
1916 | - """ |
1917 | - body = {'volumeAttachment': {'volumeId': volume_id, |
1918 | - 'device': device}} |
1919 | - return self._create("/servers/%s/os-volume_attachments" % server_id, |
1920 | - body, "volumeAttachment") |
1921 | - |
1922 | - def get_server_volume(self, server_id, attachment_id): |
1923 | - """ |
1924 | - Get the volume identified by the attachment ID, that is attached to |
1925 | - the given server ID |
1926 | - |
1927 | - :param server_id: The ID of the server |
1928 | - :param attachment_id: The ID of the attachment |
1929 | - :rtype: :class:`Volume` |
1930 | - """ |
1931 | - return self._get("/servers/%s/os-volume_attachments/%s" % (server_id, |
1932 | - attachment_id,), "volumeAttachment") |
1933 | - |
1934 | - def get_server_volumes(self, server_id): |
1935 | - """ |
1936 | - Get a list of all the attached volumes for the given server ID |
1937 | - |
1938 | - :param server_id: The ID of the server |
1939 | - :rtype: list of :class:`Volume` |
1940 | - """ |
1941 | - return self._list("/servers/%s/os-volume_attachments" % server_id, |
1942 | - "volumeAttachments") |
1943 | - |
1944 | - def delete_server_volume(self, server_id, attachment_id): |
1945 | - """ |
1946 | - Detach a volume identified by the attachment ID from the given server |
1947 | - |
1948 | - :param server_id: The ID of the server |
1949 | - :param attachment_id: The ID of the attachment |
1950 | - """ |
1951 | - self._delete("/servers/%s/os-volume_attachments/%s" % |
1952 | - (server_id, attachment_id,)) |
1953 | + def update(self, volume, **kwargs): |
1954 | + """ |
1955 | + Update the display_name or display_description for a volume. |
1956 | + |
1957 | + :param volume: The :class:`Volume` to delete. |
1958 | + """ |
1959 | + if not kwargs: |
1960 | + return |
1961 | + |
1962 | + body = {"volume": kwargs} |
1963 | + |
1964 | + self._update("/volumes/%s" % base.getid(volume), body) |
1965 | |
1966 | def _action(self, action, volume, info=None, **kwargs): |
1967 | """ |
1968 | @@ -311,3 +295,24 @@ |
1969 | """ |
1970 | self._action('os-terminate_connection', volume, |
1971 | {'connector': connector}) |
1972 | + |
1973 | + def set_metadata(self, volume, metadata): |
1974 | + """ |
1975 | + Update/Set a volumes metadata. |
1976 | + |
1977 | + :param volume: The :class:`Volume`. |
1978 | + :param metadata: A list of keys to be set. |
1979 | + """ |
1980 | + body = {'metadata': metadata} |
1981 | + return self._create("/volumes/%s/metadata" % base.getid(volume), |
1982 | + body, "metadata") |
1983 | + |
1984 | + def delete_metadata(self, volume, keys): |
1985 | + """ |
1986 | + Delete specified keys from volumes metadata. |
1987 | + |
1988 | + :param volume: The :class:`Volume`. |
1989 | + :param metadata: A list of keys to be removed. |
1990 | + """ |
1991 | + for k in keys: |
1992 | + self._delete("/volumes/%s/metadata/%s" % (base.getid(volume), k)) |
1993 | |
1994 | === modified file 'cinderclient/versioninfo' |
1995 | --- cinderclient/versioninfo 2012-10-01 12:05:26 +0000 |
1996 | +++ cinderclient/versioninfo 2013-01-10 22:15:27 +0000 |
1997 | @@ -1,1 +1,1 @@ |
1998 | -1.0.0 |
1999 | +1.0.2.3.g61e2a42 |
2000 | |
2001 | === modified file 'debian/changelog' |
2002 | --- debian/changelog 2012-10-01 12:11:12 +0000 |
2003 | +++ debian/changelog 2013-01-10 22:15:27 +0000 |
2004 | @@ -1,3 +1,25 @@ |
2005 | +python-cinderclient (1:1.0.2.3.g61e2a42-0ubuntu1~cloud0) precise-grizzly; urgency=low |
2006 | + |
2007 | + * New upstream release for the Ubuntu Cloud Archive. |
2008 | + |
2009 | + -- Adam Gandelman <adamg@ubuntu.com> Thu, 10 Jan 2013 14:13:47 -0800 |
2010 | + |
2011 | +python-cinderclient (1:1.0.2.3.g61e2a42-0ubuntu1) raring; urgency=low |
2012 | + |
2013 | + [ Adam Gandelman ] |
2014 | + * New upstream release. |
2015 | + |
2016 | + [ Chuck Short ] |
2017 | + * New upstream release. |
2018 | + * debian/control: Add python-requests as a build dep |
2019 | + * debian/control: Add python-testtools and python-fixtures as a build dep. |
2020 | + |
2021 | + [ Yolanda Robla ] |
2022 | + * debian/control: add python-setuptools-git dependency |
2023 | + * debian/copyright: Removed remaining dh boilerplate template. |
2024 | + |
2025 | + -- Adam Gandelman <adamg@ubuntu.com> Thu, 10 Jan 2013 11:35:58 -0800 |
2026 | + |
2027 | python-cinderclient (1:1.0.0-0ubuntu1~cloud0) precise-folsom; urgency=low |
2028 | |
2029 | * New upstream release for the Ubuntu Cloud Archive. |
2030 | |
2031 | === modified file 'debian/control' |
2032 | --- debian/control 2012-10-01 12:05:26 +0000 |
2033 | +++ debian/control 2013-01-10 22:15:27 +0000 |
2034 | @@ -6,19 +6,23 @@ |
2035 | python-setuptools, |
2036 | python-nose, |
2037 | python-httplib2, |
2038 | + python-requests, |
2039 | python-prettytable, |
2040 | python-simplejson, |
2041 | python-mock, |
2042 | pep8, |
2043 | python-sphinx, |
2044 | - python-unittest2 |
2045 | + python-unittest2, |
2046 | + python-setuptools-git (>= 0.4), |
2047 | + python-testtools, |
2048 | + python-fixtures |
2049 | Standards-Version: 3.9.3 |
2050 | X-Python-Version: >= 2.5 |
2051 | |
2052 | Package: python-cinderclient |
2053 | Architecture: all |
2054 | Depends: ${python:Depends}, ${misc:Depends}, python-httplib2, |
2055 | - python-prettytable, python-simplejson |
2056 | + python-prettytable, python-requests, python-simplejson |
2057 | Description: python bindings to the OpenStack Volume API |
2058 | This is a client for the OpenStack Volume API. There's a Python API (the |
2059 | ``cinderclient`` module), and a command-line script (``cinder``). Each |
2060 | |
2061 | === modified file 'debian/copyright' |
2062 | --- debian/copyright 2012-06-27 13:32:14 +0000 |
2063 | +++ debian/copyright 2013-01-10 22:15:27 +0000 |
2064 | @@ -41,8 +41,3 @@ |
2065 | . |
2066 | On Debian systems, the complete text of the GNU General |
2067 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". |
2068 | - |
2069 | -# Please also look if there are files or directories which have a |
2070 | -# different copyright/license attached and list them here. |
2071 | -# Please avoid to pick license terms that are more restrictive than the |
2072 | -# packaged work, as it may make Debian's contributions unacceptable upstream. |
2073 | |
2074 | === added file 'openstack-common.conf' |
2075 | --- openstack-common.conf 1970-01-01 00:00:00 +0000 |
2076 | +++ openstack-common.conf 2013-01-10 22:15:27 +0000 |
2077 | @@ -0,0 +1,7 @@ |
2078 | +[DEFAULT] |
2079 | + |
2080 | +# The list of modules to copy from openstack-common |
2081 | +modules=setup,version |
2082 | + |
2083 | +# The base module to hold the copy of openstack.common |
2084 | +base=cinderclient |
2085 | |
2086 | === modified file 'python_cinderclient.egg-info/PKG-INFO' |
2087 | --- python_cinderclient.egg-info/PKG-INFO 2012-10-01 12:05:26 +0000 |
2088 | +++ python_cinderclient.egg-info/PKG-INFO 2013-01-10 22:15:27 +0000 |
2089 | @@ -1,6 +1,6 @@ |
2090 | Metadata-Version: 1.1 |
2091 | Name: python-cinderclient |
2092 | -Version: 1.0.0 |
2093 | +Version: 1.0.2.3.g61e2a42 |
2094 | Summary: Client library for OpenStack Cinder API. |
2095 | Home-page: https://github.com/openstack/python-cinderclient |
2096 | Author: Rackspace, based on work by Jacob Kaplan-Moss |
2097 | @@ -161,6 +161,7 @@ |
2098 | Platform: UNKNOWN |
2099 | Classifier: Development Status :: 5 - Production/Stable |
2100 | Classifier: Environment :: Console |
2101 | +Classifier: Environment :: OpenStack |
2102 | Classifier: Intended Audience :: Developers |
2103 | Classifier: Intended Audience :: Information Technology |
2104 | Classifier: License :: OSI Approved :: Apache Software License |
2105 | |
2106 | === modified file 'python_cinderclient.egg-info/SOURCES.txt' |
2107 | --- python_cinderclient.egg-info/SOURCES.txt 2012-10-01 12:05:26 +0000 |
2108 | +++ python_cinderclient.egg-info/SOURCES.txt 2013-01-10 22:15:27 +0000 |
2109 | @@ -1,5 +1,3 @@ |
2110 | -.gitignore |
2111 | -.gitreview |
2112 | .mailmap |
2113 | AUTHORS |
2114 | ChangeLog |
2115 | @@ -7,6 +5,7 @@ |
2116 | LICENSE |
2117 | MANIFEST.in |
2118 | README.rst |
2119 | +openstack-common.conf |
2120 | run_tests.sh |
2121 | setup.cfg |
2122 | setup.py |
2123 | @@ -23,6 +22,7 @@ |
2124 | cinderclient/openstack/__init__.py |
2125 | cinderclient/openstack/common/__init__.py |
2126 | cinderclient/openstack/common/setup.py |
2127 | +cinderclient/openstack/common/version.py |
2128 | cinderclient/v1/__init__.py |
2129 | cinderclient/v1/client.py |
2130 | cinderclient/v1/limits.py |
2131 | @@ -33,6 +33,7 @@ |
2132 | cinderclient/v1/volume_types.py |
2133 | cinderclient/v1/volumes.py |
2134 | cinderclient/v1/contrib/__init__.py |
2135 | +cinderclient/v1/contrib/list_extensions.py |
2136 | doc/.gitignore |
2137 | doc/Makefile |
2138 | doc/source/api.rst |
2139 | @@ -68,9 +69,12 @@ |
2140 | tests/v1/test_quota_classes.py |
2141 | tests/v1/test_quotas.py |
2142 | tests/v1/test_shell.py |
2143 | +tests/v1/test_types.py |
2144 | tests/v1/test_volumes.py |
2145 | tests/v1/testfile.txt |
2146 | tests/v1/utils.py |
2147 | +tests/v1/contrib/__init__.py |
2148 | +tests/v1/contrib/test_list_extensions.py |
2149 | tools/cinder.bash_completion |
2150 | tools/generate_authors.sh |
2151 | tools/install_venv.py |
2152 | |
2153 | === modified file 'python_cinderclient.egg-info/requires.txt' |
2154 | --- python_cinderclient.egg-info/requires.txt 2012-08-16 12:03:50 +0000 |
2155 | +++ python_cinderclient.egg-info/requires.txt 2013-01-10 22:15:27 +0000 |
2156 | @@ -1,3 +1,3 @@ |
2157 | -httplib2 |
2158 | prettytable |
2159 | +requests<1.0 |
2160 | simplejson |
2161 | \ No newline at end of file |
2162 | |
2163 | === modified file 'setup.py' |
2164 | --- setup.py 2012-09-07 11:35:32 +0000 |
2165 | +++ setup.py 2013-01-10 22:15:27 +0000 |
2166 | @@ -21,6 +21,7 @@ |
2167 | |
2168 | requires = setup.parse_requirements() |
2169 | depend_links = setup.parse_dependency_links() |
2170 | +tests_require = setup.parse_requirements(['tools/test-requires']) |
2171 | |
2172 | |
2173 | def read_file(file_name): |
2174 | @@ -39,12 +40,14 @@ |
2175 | packages=setuptools.find_packages(exclude=['tests', 'tests.*']), |
2176 | cmdclass=setup.get_cmdclass(), |
2177 | install_requires=requires, |
2178 | + tests_require=tests_require, |
2179 | + setup_requires=['setuptools-git>=0.4'], |
2180 | dependency_links=depend_links, |
2181 | - tests_require=["nose", "mock"], |
2182 | test_suite="nose.collector", |
2183 | classifiers=[ |
2184 | "Development Status :: 5 - Production/Stable", |
2185 | "Environment :: Console", |
2186 | + "Environment :: OpenStack", |
2187 | "Intended Audience :: Developers", |
2188 | "Intended Audience :: Information Technology", |
2189 | "License :: OSI Approved :: Apache Software License", |
2190 | |
2191 | === modified file 'tests/test_client.py' |
2192 | --- tests/test_client.py 2012-06-27 13:32:14 +0000 |
2193 | +++ tests/test_client.py 2013-01-10 22:15:27 +0000 |
2194 | @@ -6,9 +6,6 @@ |
2195 | |
2196 | class ClientTest(utils.TestCase): |
2197 | |
2198 | - def setUp(self): |
2199 | - pass |
2200 | - |
2201 | def test_get_client_class_v1(self): |
2202 | output = cinderclient.client.get_client_class('1') |
2203 | self.assertEqual(output, cinderclient.v1.client.Client) |
2204 | |
2205 | === modified file 'tests/test_http.py' |
2206 | --- tests/test_http.py 2012-06-27 13:32:14 +0000 |
2207 | +++ tests/test_http.py 2013-01-10 22:15:27 +0000 |
2208 | @@ -1,24 +1,45 @@ |
2209 | -import httplib2 |
2210 | import mock |
2211 | |
2212 | +import requests |
2213 | + |
2214 | from cinderclient import client |
2215 | from cinderclient import exceptions |
2216 | from tests import utils |
2217 | |
2218 | |
2219 | -fake_response = httplib2.Response({"status": 200}) |
2220 | -fake_body = '{"hi": "there"}' |
2221 | -mock_request = mock.Mock(return_value=(fake_response, fake_body)) |
2222 | - |
2223 | - |
2224 | -def get_client(): |
2225 | +fake_response = utils.TestResponse({ |
2226 | + "status_code": 200, |
2227 | + "text": '{"hi": "there"}', |
2228 | +}) |
2229 | +mock_request = mock.Mock(return_value=(fake_response)) |
2230 | + |
2231 | +bad_400_response = utils.TestResponse({ |
2232 | + "status_code": 400, |
2233 | + "text": '{"error": {"message": "n/a", "details": "Terrible!"}}', |
2234 | +}) |
2235 | +bad_400_request = mock.Mock(return_value=(bad_400_response)) |
2236 | + |
2237 | +bad_401_response = utils.TestResponse({ |
2238 | + "status_code": 401, |
2239 | + "text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}', |
2240 | +}) |
2241 | +bad_401_request = mock.Mock(return_value=(bad_401_response)) |
2242 | + |
2243 | +bad_500_response = utils.TestResponse({ |
2244 | + "status_code": 500, |
2245 | + "text": '{"error": {"message": "FAILED!", "details": "DETAILS!"}}', |
2246 | +}) |
2247 | +bad_500_request = mock.Mock(return_value=(bad_500_response)) |
2248 | + |
2249 | + |
2250 | +def get_client(retries=0): |
2251 | cl = client.HTTPClient("username", "password", |
2252 | - "project_id", "auth_test") |
2253 | + "project_id", "auth_test", retries=retries) |
2254 | return cl |
2255 | |
2256 | |
2257 | -def get_authed_client(): |
2258 | - cl = get_client() |
2259 | +def get_authed_client(retries=0): |
2260 | + cl = get_client(retries=retries) |
2261 | cl.management_url = "http://example.com" |
2262 | cl.auth_token = "token" |
2263 | return cl |
2264 | @@ -29,7 +50,7 @@ |
2265 | def test_get(self): |
2266 | cl = get_authed_client() |
2267 | |
2268 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
2269 | + @mock.patch.object(requests, "request", mock_request) |
2270 | @mock.patch('time.time', mock.Mock(return_value=1234)) |
2271 | def test_get_call(): |
2272 | resp, body = cl.get("/hi") |
2273 | @@ -37,17 +58,110 @@ |
2274 | "X-Auth-Project-Id": "project_id", |
2275 | "User-Agent": cl.USER_AGENT, |
2276 | 'Accept': 'application/json', } |
2277 | - mock_request.assert_called_with("http://example.com/hi", |
2278 | - "GET", headers=headers) |
2279 | + mock_request.assert_called_with( |
2280 | + "GET", |
2281 | + "http://example.com/hi", |
2282 | + headers=headers, |
2283 | + **self.TEST_REQUEST_BASE) |
2284 | # Automatic JSON parsing |
2285 | self.assertEqual(body, {"hi": "there"}) |
2286 | |
2287 | test_get_call() |
2288 | |
2289 | + def test_get_reauth_0_retries(self): |
2290 | + cl = get_authed_client(retries=0) |
2291 | + |
2292 | + self.requests = [bad_401_request, mock_request] |
2293 | + |
2294 | + def request(*args, **kwargs): |
2295 | + next_request = self.requests.pop(0) |
2296 | + return next_request(*args, **kwargs) |
2297 | + |
2298 | + def reauth(): |
2299 | + cl.management_url = "http://example.com" |
2300 | + cl.auth_token = "token" |
2301 | + |
2302 | + @mock.patch.object(cl, 'authenticate', reauth) |
2303 | + @mock.patch.object(requests, "request", request) |
2304 | + @mock.patch('time.time', mock.Mock(return_value=1234)) |
2305 | + def test_get_call(): |
2306 | + resp, body = cl.get("/hi") |
2307 | + |
2308 | + test_get_call() |
2309 | + self.assertEqual(self.requests, []) |
2310 | + |
2311 | + def test_get_retry_500(self): |
2312 | + cl = get_authed_client(retries=1) |
2313 | + |
2314 | + self.requests = [bad_500_request, mock_request] |
2315 | + |
2316 | + def request(*args, **kwargs): |
2317 | + next_request = self.requests.pop(0) |
2318 | + return next_request(*args, **kwargs) |
2319 | + |
2320 | + @mock.patch.object(requests, "request", request) |
2321 | + @mock.patch('time.time', mock.Mock(return_value=1234)) |
2322 | + def test_get_call(): |
2323 | + resp, body = cl.get("/hi") |
2324 | + |
2325 | + test_get_call() |
2326 | + self.assertEqual(self.requests, []) |
2327 | + |
2328 | + def test_retry_limit(self): |
2329 | + cl = get_authed_client(retries=1) |
2330 | + |
2331 | + self.requests = [bad_500_request, bad_500_request, mock_request] |
2332 | + |
2333 | + def request(*args, **kwargs): |
2334 | + next_request = self.requests.pop(0) |
2335 | + return next_request(*args, **kwargs) |
2336 | + |
2337 | + @mock.patch.object(requests, "request", request) |
2338 | + @mock.patch('time.time', mock.Mock(return_value=1234)) |
2339 | + def test_get_call(): |
2340 | + resp, body = cl.get("/hi") |
2341 | + |
2342 | + self.assertRaises(exceptions.ClientException, test_get_call) |
2343 | + self.assertEqual(self.requests, [mock_request]) |
2344 | + |
2345 | + def test_get_no_retry_400(self): |
2346 | + cl = get_authed_client(retries=0) |
2347 | + |
2348 | + self.requests = [bad_400_request, mock_request] |
2349 | + |
2350 | + def request(*args, **kwargs): |
2351 | + next_request = self.requests.pop(0) |
2352 | + return next_request(*args, **kwargs) |
2353 | + |
2354 | + @mock.patch.object(requests, "request", request) |
2355 | + @mock.patch('time.time', mock.Mock(return_value=1234)) |
2356 | + def test_get_call(): |
2357 | + resp, body = cl.get("/hi") |
2358 | + |
2359 | + self.assertRaises(exceptions.BadRequest, test_get_call) |
2360 | + self.assertEqual(self.requests, [mock_request]) |
2361 | + |
2362 | + def test_get_retry_400_socket(self): |
2363 | + cl = get_authed_client(retries=1) |
2364 | + |
2365 | + self.requests = [bad_400_request, mock_request] |
2366 | + |
2367 | + def request(*args, **kwargs): |
2368 | + next_request = self.requests.pop(0) |
2369 | + return next_request(*args, **kwargs) |
2370 | + |
2371 | + @mock.patch.object(requests, "request", request) |
2372 | + @mock.patch('time.time', mock.Mock(return_value=1234)) |
2373 | + def test_get_call(): |
2374 | + resp, body = cl.get("/hi") |
2375 | + |
2376 | + test_get_call() |
2377 | + self.assertEqual(self.requests, []) |
2378 | + |
2379 | def test_post(self): |
2380 | cl = get_authed_client() |
2381 | |
2382 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
2383 | + @mock.patch.object(requests, "request", mock_request) |
2384 | def test_post_call(): |
2385 | cl.post("/hi", body=[1, 2, 3]) |
2386 | headers = { |
2387 | @@ -57,8 +171,12 @@ |
2388 | 'Accept': 'application/json', |
2389 | "User-Agent": cl.USER_AGENT |
2390 | } |
2391 | - mock_request.assert_called_with("http://example.com/hi", "POST", |
2392 | - headers=headers, body='[1, 2, 3]') |
2393 | + mock_request.assert_called_with( |
2394 | + "POST", |
2395 | + "http://example.com/hi", |
2396 | + headers=headers, |
2397 | + data='[1, 2, 3]', |
2398 | + **self.TEST_REQUEST_BASE) |
2399 | |
2400 | test_post_call() |
2401 | |
2402 | @@ -66,7 +184,7 @@ |
2403 | cl = get_client() |
2404 | |
2405 | # response must not have x-server-management-url header |
2406 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
2407 | + @mock.patch.object(requests, "request", mock_request) |
2408 | def test_auth_call(): |
2409 | self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate) |
2410 | |
2411 | |
2412 | === modified file 'tests/test_shell.py' |
2413 | --- tests/test_shell.py 2012-06-27 13:32:14 +0000 |
2414 | +++ tests/test_shell.py 2013-01-10 22:15:27 +0000 |
2415 | @@ -1,8 +1,11 @@ |
2416 | import cStringIO |
2417 | import os |
2418 | -import httplib2 |
2419 | +import re |
2420 | import sys |
2421 | |
2422 | +import fixtures |
2423 | +from testtools import matchers |
2424 | + |
2425 | from cinderclient import exceptions |
2426 | import cinderclient.shell |
2427 | from tests import utils |
2428 | @@ -10,16 +13,19 @@ |
2429 | |
2430 | class ShellTest(utils.TestCase): |
2431 | |
2432 | + FAKE_ENV = { |
2433 | + 'OS_USERNAME': 'username', |
2434 | + 'OS_PASSWORD': 'password', |
2435 | + 'OS_TENANT_NAME': 'tenant_name', |
2436 | + 'OS_AUTH_URL': 'http://no.where', |
2437 | + } |
2438 | + |
2439 | # Patch os.environ to avoid required auth info. |
2440 | def setUp(self): |
2441 | - global _old_env |
2442 | - fake_env = { |
2443 | - 'OS_USERNAME': 'username', |
2444 | - 'OS_PASSWORD': 'password', |
2445 | - 'OS_TENANT_NAME': 'tenant_name', |
2446 | - 'OS_AUTH_URL': 'http://no.where', |
2447 | - } |
2448 | - _old_env, os.environ = os.environ, fake_env.copy() |
2449 | + super(ShellTest, self).setUp() |
2450 | + for var in self.FAKE_ENV: |
2451 | + self.useFixture(fixtures.EnvironmentVariable(var, |
2452 | + self.FAKE_ENV[var])) |
2453 | |
2454 | def shell(self, argstr): |
2455 | orig = sys.stdout |
2456 | @@ -37,39 +43,26 @@ |
2457 | |
2458 | return out |
2459 | |
2460 | - def tearDown(self): |
2461 | - global _old_env |
2462 | - os.environ = _old_env |
2463 | - |
2464 | def test_help_unknown_command(self): |
2465 | self.assertRaises(exceptions.CommandError, self.shell, 'help foofoo') |
2466 | |
2467 | - def test_debug(self): |
2468 | - httplib2.debuglevel = 0 |
2469 | - self.shell('--debug help') |
2470 | - assert httplib2.debuglevel == 1 |
2471 | - |
2472 | def test_help(self): |
2473 | required = [ |
2474 | - '^usage: ', |
2475 | - '(?m)^\s+create\s+Add a new volume.', |
2476 | - '(?m)^See "cinder help COMMAND" for help on a specific command', |
2477 | + '.*?^usage: ', |
2478 | + '.*?(?m)^\s+create\s+Add a new volume.', |
2479 | + '.*?(?m)^See "cinder help COMMAND" for help on a specific command', |
2480 | ] |
2481 | - for argstr in ['--help', 'help']: |
2482 | - help_text = self.shell(argstr) |
2483 | - for r in required: |
2484 | - self.assertRegexpMatches(help_text, r) |
2485 | + help_text = self.shell('help') |
2486 | + for r in required: |
2487 | + self.assertThat(help_text, |
2488 | + matchers.MatchesRegex(r, re.DOTALL|re.MULTILINE)) |
2489 | |
2490 | def test_help_on_subcommand(self): |
2491 | required = [ |
2492 | - '^usage: cinder list', |
2493 | - '(?m)^List all the volumes.', |
2494 | - ] |
2495 | - argstrings = [ |
2496 | - 'list --help', |
2497 | - 'help list', |
2498 | - ] |
2499 | - for argstr in argstrings: |
2500 | - help_text = self.shell(argstr) |
2501 | - for r in required: |
2502 | - self.assertRegexpMatches(help_text, r) |
2503 | + '.*?^usage: cinder list', |
2504 | + '.*?(?m)^List all the volumes.', |
2505 | + ] |
2506 | + help_text = self.shell('help list') |
2507 | + for r in required: |
2508 | + self.assertThat(help_text, |
2509 | + matchers.MatchesRegex(r, re.DOTALL|re.MULTILINE)) |
2510 | |
2511 | === modified file 'tests/test_utils.py' |
2512 | --- tests/test_utils.py 2012-06-27 13:32:14 +0000 |
2513 | +++ tests/test_utils.py 2013-01-10 22:15:27 +0000 |
2514 | @@ -45,6 +45,7 @@ |
2515 | class FindResourceTestCase(test_utils.TestCase): |
2516 | |
2517 | def setUp(self): |
2518 | + super(FindResourceTestCase, self).setUp() |
2519 | self.manager = FakeManager(None) |
2520 | |
2521 | def test_find_none(self): |
2522 | |
2523 | === modified file 'tests/utils.py' |
2524 | --- tests/utils.py 2012-06-27 13:32:14 +0000 |
2525 | +++ tests/utils.py 2013-01-10 22:15:27 +0000 |
2526 | @@ -1,5 +1,33 @@ |
2527 | -import unittest2 |
2528 | - |
2529 | - |
2530 | -class TestCase(unittest2.TestCase): |
2531 | - pass |
2532 | +import testtools |
2533 | + |
2534 | +import requests |
2535 | + |
2536 | + |
2537 | +class TestCase(testtools.TestCase): |
2538 | + TEST_REQUEST_BASE = { |
2539 | + 'config': {'danger_mode': False}, |
2540 | + 'verify': True, |
2541 | + } |
2542 | + |
2543 | + |
2544 | +class TestResponse(requests.Response): |
2545 | + """ Class used to wrap requests.Response and provide some |
2546 | + convenience to initialize with a dict """ |
2547 | + |
2548 | + def __init__(self, data): |
2549 | + self._text = None |
2550 | + super(TestResponse, self) |
2551 | + if isinstance(data, dict): |
2552 | + self.status_code = data.get('status_code', None) |
2553 | + self.headers = data.get('headers', None) |
2554 | + # Fake the text attribute to streamline Response creation |
2555 | + self._text = data.get('text', None) |
2556 | + else: |
2557 | + self.status_code = data |
2558 | + |
2559 | + def __eq__(self, other): |
2560 | + return self.__dict__ == other.__dict__ |
2561 | + |
2562 | + @property |
2563 | + def text(self): |
2564 | + return self._text |
2565 | |
2566 | === added directory 'tests/v1/contrib' |
2567 | === added file 'tests/v1/contrib/__init__.py' |
2568 | === added file 'tests/v1/contrib/test_list_extensions.py' |
2569 | --- tests/v1/contrib/test_list_extensions.py 1970-01-01 00:00:00 +0000 |
2570 | +++ tests/v1/contrib/test_list_extensions.py 2013-01-10 22:15:27 +0000 |
2571 | @@ -0,0 +1,21 @@ |
2572 | +from cinderclient import extension |
2573 | +from cinderclient.v1.contrib import list_extensions |
2574 | + |
2575 | +from tests import utils |
2576 | +from tests.v1 import fakes |
2577 | + |
2578 | + |
2579 | +extensions = [ |
2580 | + extension.Extension(list_extensions.__name__.split(".")[-1], |
2581 | + list_extensions), |
2582 | +] |
2583 | +cs = fakes.FakeClient(extensions=extensions) |
2584 | + |
2585 | + |
2586 | +class ListExtensionsTests(utils.TestCase): |
2587 | + def test_list_extensions(self): |
2588 | + all_exts = cs.list_extensions.show_all() |
2589 | + cs.assert_called('GET', '/extensions') |
2590 | + self.assertTrue(len(all_exts) > 0) |
2591 | + for r in all_exts: |
2592 | + self.assertTrue(len(r.summary) > 0) |
2593 | |
2594 | === modified file 'tests/v1/fakes.py' |
2595 | --- tests/v1/fakes.py 2012-10-01 12:05:26 +0000 |
2596 | +++ tests/v1/fakes.py 2013-01-10 22:15:27 +0000 |
2597 | @@ -13,12 +13,34 @@ |
2598 | # See the License for the specific language governing permissions and |
2599 | # limitations under the License. |
2600 | |
2601 | -import httplib2 |
2602 | import urlparse |
2603 | |
2604 | from cinderclient import client as base_client |
2605 | from cinderclient.v1 import client |
2606 | from tests import fakes |
2607 | +import tests.utils as utils |
2608 | + |
2609 | + |
2610 | +def _stub_volume(**kwargs): |
2611 | + volume = { |
2612 | + 'id': '1234', |
2613 | + 'display_name': None, |
2614 | + 'display_description': None, |
2615 | + "attachments": [], |
2616 | + "bootable": "false", |
2617 | + "availability_zone": "cinder", |
2618 | + "created_at": "2012-08-27T00:00:00.000000", |
2619 | + "display_description": None, |
2620 | + "display_name": None, |
2621 | + "id": '00000000-0000-0000-0000-000000000000', |
2622 | + "metadata": {}, |
2623 | + "size": 1, |
2624 | + "snapshot_id": None, |
2625 | + "status": "available", |
2626 | + "volume_type": "None", |
2627 | + } |
2628 | + volume.update(kwargs) |
2629 | + return volume |
2630 | |
2631 | |
2632 | def _stub_snapshot(**kwargs): |
2633 | @@ -39,7 +61,8 @@ |
2634 | |
2635 | def __init__(self, *args, **kwargs): |
2636 | client.Client.__init__(self, 'username', 'password', |
2637 | - 'project_id', 'auth_url') |
2638 | + 'project_id', 'auth_url', |
2639 | + extensions=kwargs.get('extensions')) |
2640 | self.client = FakeHTTPClient(**kwargs) |
2641 | |
2642 | |
2643 | @@ -74,28 +97,47 @@ |
2644 | |
2645 | # Note the call |
2646 | self.callstack.append((method, url, kwargs.get('body', None))) |
2647 | + status, headers, body = getattr(self, callback)(**kwargs) |
2648 | + r = utils.TestResponse({ |
2649 | + "status_code": status, |
2650 | + "text": body, |
2651 | + "headers": headers, |
2652 | + }) |
2653 | + return r, body |
2654 | |
2655 | - status, body = getattr(self, callback)(**kwargs) |
2656 | if hasattr(status, 'items'): |
2657 | - return httplib2.Response(status), body |
2658 | + return utils.TestResponse(status), body |
2659 | else: |
2660 | - return httplib2.Response({"status": status}), body |
2661 | + return utils.TestResponse({"status": status}), body |
2662 | |
2663 | # |
2664 | # Snapshots |
2665 | # |
2666 | |
2667 | def get_snapshots_detail(self, **kw): |
2668 | - return (200, {'snapshots': [ |
2669 | + return (200, {}, {'snapshots': [ |
2670 | _stub_snapshot(), |
2671 | ]}) |
2672 | |
2673 | - # |
2674 | - # volumes |
2675 | - # |
2676 | + def get_snapshots_1234(self, **kw): |
2677 | + return (200, {}, {'snapshot': _stub_snapshot(id='1234')}) |
2678 | + |
2679 | + def put_snapshots_1234(self, **kw): |
2680 | + snapshot = _stub_snapshot(id='1234') |
2681 | + snapshot.update(kw['body']['snapshot']) |
2682 | + return (200, {}, {'snapshot': snapshot}) |
2683 | + |
2684 | + # |
2685 | + # Volumes |
2686 | + # |
2687 | + |
2688 | + def put_volumes_1234(self, **kw): |
2689 | + volume = _stub_volume(id='1234') |
2690 | + volume.update(kw['body']['volume']) |
2691 | + return (200, {}, {'volume': volume}) |
2692 | |
2693 | def get_volumes(self, **kw): |
2694 | - return (200, {"volumes": [ |
2695 | + return (200, {}, {"volumes": [ |
2696 | {'id': 1234, 'name': 'sample-volume'}, |
2697 | {'id': 5678, 'name': 'sample-volume2'} |
2698 | ]}) |
2699 | @@ -103,15 +145,15 @@ |
2700 | # TODO(jdg): This will need to change |
2701 | # at the very least it's not complete |
2702 | def get_volumes_detail(self, **kw): |
2703 | - return (200, {"volumes": [ |
2704 | + return (200, {}, {"volumes": [ |
2705 | {'id': 1234, |
2706 | 'name': 'sample-volume', |
2707 | 'attachments': [{'server_id': 1234}]}, |
2708 | ]}) |
2709 | |
2710 | def get_volumes_1234(self, **kw): |
2711 | - r = {'volume': self.get_volumes_detail()[1]['volumes'][0]} |
2712 | - return (200, r) |
2713 | + r = {'volume': self.get_volumes_detail()[2]['volumes'][0]} |
2714 | + return (200, {}, r) |
2715 | |
2716 | def post_volumes_1234_action(self, body, **kw): |
2717 | _body = None |
2718 | @@ -128,7 +170,7 @@ |
2719 | assert body[action] is None |
2720 | elif action == 'os-initialize_connection': |
2721 | assert body[action].keys() == ['connector'] |
2722 | - return (202, {'connection_info': 'foos'}) |
2723 | + return (202, {}, {'connection_info': 'foos'}) |
2724 | elif action == 'os-terminate_connection': |
2725 | assert body[action].keys() == ['connector'] |
2726 | elif action == 'os-begin_detaching': |
2727 | @@ -137,59 +179,132 @@ |
2728 | assert body[action] is None |
2729 | else: |
2730 | raise AssertionError("Unexpected server action: %s" % action) |
2731 | - return (resp, _body) |
2732 | + return (resp, {}, _body) |
2733 | |
2734 | def post_volumes(self, **kw): |
2735 | - return (202, {'volume': {}}) |
2736 | + return (202, {}, {'volume': {}}) |
2737 | |
2738 | def delete_volumes_1234(self, **kw): |
2739 | - return (202, None) |
2740 | + return (202, {}, None) |
2741 | |
2742 | # |
2743 | # Quotas |
2744 | # |
2745 | |
2746 | def get_os_quota_sets_test(self, **kw): |
2747 | - return (200, {'quota_set': { |
2748 | - 'tenant_id': 'test', |
2749 | - 'metadata_items': [], |
2750 | - 'volumes': 1, |
2751 | - 'gigabytes': 1}}) |
2752 | + return (200, {}, {'quota_set': { |
2753 | + 'tenant_id': 'test', |
2754 | + 'metadata_items': [], |
2755 | + 'volumes': 1, |
2756 | + 'gigabytes': 1}}) |
2757 | |
2758 | def get_os_quota_sets_test_defaults(self): |
2759 | - return (200, {'quota_set': { |
2760 | - 'tenant_id': 'test', |
2761 | - 'metadata_items': [], |
2762 | - 'volumes': 1, |
2763 | - 'gigabytes': 1}}) |
2764 | + return (200, {}, {'quota_set': { |
2765 | + 'tenant_id': 'test', |
2766 | + 'metadata_items': [], |
2767 | + 'volumes': 1, |
2768 | + 'gigabytes': 1}}) |
2769 | |
2770 | def put_os_quota_sets_test(self, body, **kw): |
2771 | assert body.keys() == ['quota_set'] |
2772 | fakes.assert_has_keys(body['quota_set'], |
2773 | required=['tenant_id']) |
2774 | - return (200, {'quota_set': { |
2775 | - 'tenant_id': 'test', |
2776 | - 'metadata_items': [], |
2777 | - 'volumes': 2, |
2778 | - 'gigabytes': 1}}) |
2779 | + return (200, {}, {'quota_set': { |
2780 | + 'tenant_id': 'test', |
2781 | + 'metadata_items': [], |
2782 | + 'volumes': 2, |
2783 | + 'gigabytes': 1}}) |
2784 | |
2785 | # |
2786 | # Quota Classes |
2787 | # |
2788 | |
2789 | def get_os_quota_class_sets_test(self, **kw): |
2790 | - return (200, {'quota_class_set': { |
2791 | - 'class_name': 'test', |
2792 | - 'metadata_items': [], |
2793 | - 'volumes': 1, |
2794 | - 'gigabytes': 1}}) |
2795 | + return (200, {}, {'quota_class_set': { |
2796 | + 'class_name': 'test', |
2797 | + 'metadata_items': [], |
2798 | + 'volumes': 1, |
2799 | + 'gigabytes': 1}}) |
2800 | |
2801 | def put_os_quota_class_sets_test(self, body, **kw): |
2802 | assert body.keys() == ['quota_class_set'] |
2803 | fakes.assert_has_keys(body['quota_class_set'], |
2804 | required=['class_name']) |
2805 | - return (200, {'quota_class_set': { |
2806 | - 'class_name': 'test', |
2807 | - 'metadata_items': [], |
2808 | - 'volumes': 2, |
2809 | - 'gigabytes': 1}}) |
2810 | + return (200, {}, {'quota_class_set': { |
2811 | + 'class_name': 'test', |
2812 | + 'metadata_items': [], |
2813 | + 'volumes': 2, |
2814 | + 'gigabytes': 1}}) |
2815 | + |
2816 | + # |
2817 | + # VolumeTypes |
2818 | + # |
2819 | + def get_types(self, **kw): |
2820 | + return (200, {}, { |
2821 | + 'volume_types': [{'id': 1, |
2822 | + 'name': 'test-type-1', |
2823 | + 'extra_specs':{}}, |
2824 | + {'id': 2, |
2825 | + 'name': 'test-type-2', |
2826 | + 'extra_specs':{}}]}) |
2827 | + |
2828 | + def get_types_1(self, **kw): |
2829 | + return (200, {}, {'volume_type': {'id': 1, |
2830 | + 'name': 'test-type-1', |
2831 | + 'extra_specs': {}}}) |
2832 | + |
2833 | + def post_types(self, body, **kw): |
2834 | + return (202, {}, {'volume_type': {'id': 3, |
2835 | + 'name': 'test-type-3', |
2836 | + 'extra_specs': {}}}) |
2837 | + |
2838 | + def post_types_1_extra_specs(self, body, **kw): |
2839 | + assert body.keys() == ['extra_specs'] |
2840 | + return (200, {}, {'extra_specs': {'k': 'v'}}) |
2841 | + |
2842 | + def delete_types_1_extra_specs_k(self, **kw): |
2843 | + return(204, {}, None) |
2844 | + |
2845 | + def delete_types_1(self, **kw): |
2846 | + return (202, {}, None) |
2847 | + |
2848 | + # |
2849 | + # Set/Unset metadata |
2850 | + # |
2851 | + def delete_volumes_1234_metadata_test_key(self, **kw): |
2852 | + return (204, {}, None) |
2853 | + |
2854 | + def delete_volumes_1234_metadata_key1(self, **kw): |
2855 | + return (204, {}, None) |
2856 | + |
2857 | + def delete_volumes_1234_metadata_key2(self, **kw): |
2858 | + return (204, {}, None) |
2859 | + |
2860 | + def post_volumes_1234_metadata(self, **kw): |
2861 | + return (204, {}, {'metadata': {'test_key': 'test_value'}}) |
2862 | + |
2863 | + # |
2864 | + # List all extensions |
2865 | + # |
2866 | + def get_extensions(self, **kw): |
2867 | + exts = [ |
2868 | + { |
2869 | + "alias": "FAKE-1", |
2870 | + "description": "Fake extension number 1", |
2871 | + "links": [], |
2872 | + "name": "Fake1", |
2873 | + "namespace": ("http://docs.openstack.org/" |
2874 | + "/ext/fake1/api/v1.1"), |
2875 | + "updated": "2011-06-09T00:00:00+00:00" |
2876 | + }, |
2877 | + { |
2878 | + "alias": "FAKE-2", |
2879 | + "description": "Fake extension number 2", |
2880 | + "links": [], |
2881 | + "name": "Fake2", |
2882 | + "namespace": ("http://docs.openstack.org/" |
2883 | + "/ext/fake1/api/v1.1"), |
2884 | + "updated": "2011-06-09T00:00:00+00:00" |
2885 | + }, |
2886 | + ] |
2887 | + return (200, {}, {"extensions": exts, }) |
2888 | |
2889 | === modified file 'tests/v1/test_auth.py' |
2890 | --- tests/v1/test_auth.py 2012-10-01 12:05:26 +0000 |
2891 | +++ tests/v1/test_auth.py 2013-01-10 22:15:27 +0000 |
2892 | @@ -1,20 +1,13 @@ |
2893 | -import httplib2 |
2894 | import json |
2895 | import mock |
2896 | |
2897 | +import requests |
2898 | + |
2899 | from cinderclient.v1 import client |
2900 | from cinderclient import exceptions |
2901 | from tests import utils |
2902 | |
2903 | |
2904 | -def to_http_response(resp_dict): |
2905 | - """Converts dict of response attributes to httplib response.""" |
2906 | - resp = httplib2.Response(resp_dict) |
2907 | - for k, v in resp_dict['headers'].items(): |
2908 | - resp[k] = v |
2909 | - return resp |
2910 | - |
2911 | - |
2912 | class AuthenticateAgainstKeystoneTests(utils.TestCase): |
2913 | def test_authenticate_success(self): |
2914 | cs = client.Client("username", "password", "project_id", |
2915 | @@ -40,14 +33,14 @@ |
2916 | ], |
2917 | }, |
2918 | } |
2919 | - auth_response = httplib2.Response({ |
2920 | - "status": 200, |
2921 | - "body": json.dumps(resp), }) |
2922 | - |
2923 | - mock_request = mock.Mock(return_value=(auth_response, |
2924 | - json.dumps(resp))) |
2925 | - |
2926 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
2927 | + auth_response = utils.TestResponse({ |
2928 | + "status_code": 200, |
2929 | + "text": json.dumps(resp), |
2930 | + }) |
2931 | + |
2932 | + mock_request = mock.Mock(return_value=(auth_response)) |
2933 | + |
2934 | + @mock.patch.object(requests, "request", mock_request) |
2935 | def test_auth_call(): |
2936 | cs.client.authenticate() |
2937 | headers = { |
2938 | @@ -66,9 +59,13 @@ |
2939 | } |
2940 | |
2941 | token_url = cs.client.auth_url + "/tokens" |
2942 | - mock_request.assert_called_with(token_url, "POST", |
2943 | - headers=headers, |
2944 | - body=json.dumps(body)) |
2945 | + mock_request.assert_called_with( |
2946 | + "POST", |
2947 | + token_url, |
2948 | + headers=headers, |
2949 | + data=json.dumps(body), |
2950 | + allow_redirects=True, |
2951 | + **self.TEST_REQUEST_BASE) |
2952 | |
2953 | endpoints = resp["access"]["serviceCatalog"][0]['endpoints'] |
2954 | public_url = endpoints[0]["publicURL"].rstrip('/') |
2955 | @@ -108,14 +105,14 @@ |
2956 | ], |
2957 | }, |
2958 | } |
2959 | - auth_response = httplib2.Response({ |
2960 | - "status": 200, |
2961 | - "body": json.dumps(resp), }) |
2962 | - |
2963 | - mock_request = mock.Mock(return_value=(auth_response, |
2964 | - json.dumps(resp))) |
2965 | - |
2966 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
2967 | + auth_response = utils.TestResponse({ |
2968 | + "status_code": 200, |
2969 | + "text": json.dumps(resp), |
2970 | + }) |
2971 | + |
2972 | + mock_request = mock.Mock(return_value=(auth_response)) |
2973 | + |
2974 | + @mock.patch.object(requests, "request", mock_request) |
2975 | def test_auth_call(): |
2976 | cs.client.authenticate() |
2977 | headers = { |
2978 | @@ -134,9 +131,13 @@ |
2979 | } |
2980 | |
2981 | token_url = cs.client.auth_url + "/tokens" |
2982 | - mock_request.assert_called_with(token_url, "POST", |
2983 | - headers=headers, |
2984 | - body=json.dumps(body)) |
2985 | + mock_request.assert_called_with( |
2986 | + "POST", |
2987 | + token_url, |
2988 | + headers=headers, |
2989 | + data=json.dumps(body), |
2990 | + allow_redirects=True, |
2991 | + **self.TEST_REQUEST_BASE) |
2992 | |
2993 | endpoints = resp["access"]["serviceCatalog"][0]['endpoints'] |
2994 | public_url = endpoints[0]["publicURL"].rstrip('/') |
2995 | @@ -152,14 +153,14 @@ |
2996 | cs = client.Client("username", "password", "project_id", |
2997 | "auth_url/v2.0") |
2998 | resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}} |
2999 | - auth_response = httplib2.Response({ |
3000 | - "status": 401, |
3001 | - "body": json.dumps(resp), }) |
3002 | - |
3003 | - mock_request = mock.Mock(return_value=(auth_response, |
3004 | - json.dumps(resp))) |
3005 | - |
3006 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
3007 | + auth_response = utils.TestResponse({ |
3008 | + "status_code": 401, |
3009 | + "text": json.dumps(resp), |
3010 | + }) |
3011 | + |
3012 | + mock_request = mock.Mock(return_value=(auth_response)) |
3013 | + |
3014 | + @mock.patch.object(requests, "request", mock_request) |
3015 | def test_auth_call(): |
3016 | self.assertRaises(exceptions.Unauthorized, cs.client.authenticate) |
3017 | |
3018 | @@ -192,29 +193,28 @@ |
3019 | correct_response = json.dumps(dict_correct_response) |
3020 | dict_responses = [ |
3021 | {"headers": {'location':'http://127.0.0.1:5001'}, |
3022 | - "status": 305, |
3023 | - "body": "Use proxy"}, |
3024 | + "status_code": 305, |
3025 | + "text": "Use proxy"}, |
3026 | # Configured on admin port, cinder redirects to v2.0 port. |
3027 | # When trying to connect on it, keystone auth succeed by v1.0 |
3028 | # protocol (through headers) but tokens are being returned in |
3029 | # body (looks like keystone bug). Leaved for compatibility. |
3030 | {"headers": {}, |
3031 | - "status": 200, |
3032 | - "body": correct_response}, |
3033 | + "status_code": 200, |
3034 | + "text": correct_response}, |
3035 | {"headers": {}, |
3036 | - "status": 200, |
3037 | - "body": correct_response} |
3038 | + "status_code": 200, |
3039 | + "text": correct_response} |
3040 | ] |
3041 | |
3042 | - responses = [(to_http_response(resp), resp['body']) |
3043 | - for resp in dict_responses] |
3044 | + responses = [(utils.TestResponse(resp)) for resp in dict_responses] |
3045 | |
3046 | def side_effect(*args, **kwargs): |
3047 | return responses.pop(0) |
3048 | |
3049 | mock_request = mock.Mock(side_effect=side_effect) |
3050 | |
3051 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
3052 | + @mock.patch.object(requests, "request", mock_request) |
3053 | def test_auth_call(): |
3054 | cs.client.authenticate() |
3055 | headers = { |
3056 | @@ -233,9 +233,13 @@ |
3057 | } |
3058 | |
3059 | token_url = cs.client.auth_url + "/tokens" |
3060 | - mock_request.assert_called_with(token_url, "POST", |
3061 | - headers=headers, |
3062 | - body=json.dumps(body)) |
3063 | + mock_request.assert_called_with( |
3064 | + "POST", |
3065 | + token_url, |
3066 | + headers=headers, |
3067 | + data=json.dumps(body), |
3068 | + allow_redirects=True, |
3069 | + **self.TEST_REQUEST_BASE) |
3070 | |
3071 | resp = dict_correct_response |
3072 | endpoints = resp["access"]["serviceCatalog"][0]['endpoints'] |
3073 | @@ -282,16 +286,14 @@ |
3074 | ], |
3075 | }, |
3076 | } |
3077 | - auth_response = httplib2.Response( |
3078 | - { |
3079 | - "status": 200, |
3080 | - "body": json.dumps(resp), |
3081 | - }) |
3082 | - |
3083 | - mock_request = mock.Mock(return_value=(auth_response, |
3084 | - json.dumps(resp))) |
3085 | - |
3086 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
3087 | + auth_response = utils.TestResponse({ |
3088 | + "status_code": 200, |
3089 | + "text": json.dumps(resp), |
3090 | + }) |
3091 | + |
3092 | + mock_request = mock.Mock(return_value=(auth_response)) |
3093 | + |
3094 | + @mock.patch.object(requests, "request", mock_request) |
3095 | def test_auth_call(): |
3096 | self.assertRaises(exceptions.AmbiguousEndpoints, |
3097 | cs.client.authenticate) |
3098 | @@ -302,15 +304,17 @@ |
3099 | class AuthenticationTests(utils.TestCase): |
3100 | def test_authenticate_success(self): |
3101 | cs = client.Client("username", "password", "project_id", "auth_url") |
3102 | - management_url = 'https://servers.api.rackspacecloud.com/v1.1/443470' |
3103 | - auth_response = httplib2.Response({ |
3104 | - 'status': 204, |
3105 | - 'x-server-management-url': management_url, |
3106 | - 'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1', |
3107 | + management_url = 'https://localhost/v1.1/443470' |
3108 | + auth_response = utils.TestResponse({ |
3109 | + 'status_code': 204, |
3110 | + 'headers': { |
3111 | + 'x-server-management-url': management_url, |
3112 | + 'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1', |
3113 | + }, |
3114 | }) |
3115 | - mock_request = mock.Mock(return_value=(auth_response, None)) |
3116 | + mock_request = mock.Mock(return_value=(auth_response)) |
3117 | |
3118 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
3119 | + @mock.patch.object(requests, "request", mock_request) |
3120 | def test_auth_call(): |
3121 | cs.client.authenticate() |
3122 | headers = { |
3123 | @@ -320,21 +324,25 @@ |
3124 | 'X-Auth-Project-Id': 'project_id', |
3125 | 'User-Agent': cs.client.USER_AGENT |
3126 | } |
3127 | - mock_request.assert_called_with(cs.client.auth_url, 'GET', |
3128 | - headers=headers) |
3129 | + mock_request.assert_called_with( |
3130 | + "GET", |
3131 | + cs.client.auth_url, |
3132 | + headers=headers, |
3133 | + **self.TEST_REQUEST_BASE) |
3134 | + |
3135 | self.assertEqual(cs.client.management_url, |
3136 | - auth_response['x-server-management-url']) |
3137 | + auth_response.headers['x-server-management-url']) |
3138 | self.assertEqual(cs.client.auth_token, |
3139 | - auth_response['x-auth-token']) |
3140 | + auth_response.headers['x-auth-token']) |
3141 | |
3142 | test_auth_call() |
3143 | |
3144 | def test_authenticate_failure(self): |
3145 | cs = client.Client("username", "password", "project_id", "auth_url") |
3146 | - auth_response = httplib2.Response({'status': 401}) |
3147 | - mock_request = mock.Mock(return_value=(auth_response, None)) |
3148 | + auth_response = utils.TestResponse({"status_code": 401}) |
3149 | + mock_request = mock.Mock(return_value=(auth_response)) |
3150 | |
3151 | - @mock.patch.object(httplib2.Http, "request", mock_request) |
3152 | + @mock.patch.object(requests, "request", mock_request) |
3153 | def test_auth_call(): |
3154 | self.assertRaises(exceptions.Unauthorized, cs.client.authenticate) |
3155 | |
3156 | |
3157 | === modified file 'tests/v1/test_shell.py' |
3158 | --- tests/v1/test_shell.py 2012-10-01 12:05:26 +0000 |
3159 | +++ tests/v1/test_shell.py 2013-01-10 22:15:27 +0000 |
3160 | @@ -17,6 +17,8 @@ |
3161 | |
3162 | import os |
3163 | |
3164 | +import fixtures |
3165 | + |
3166 | from cinderclient import client |
3167 | from cinderclient import shell |
3168 | from tests.v1 import fakes |
3169 | @@ -25,17 +27,21 @@ |
3170 | |
3171 | class ShellTest(utils.TestCase): |
3172 | |
3173 | + FAKE_ENV = { |
3174 | + 'CINDER_USERNAME': 'username', |
3175 | + 'CINDER_PASSWORD': 'password', |
3176 | + 'CINDER_PROJECT_ID': 'project_id', |
3177 | + 'OS_COMPUTE_API_VERSION': '1.1', |
3178 | + 'CINDER_URL': 'http://no.where', |
3179 | + } |
3180 | + |
3181 | # Patch os.environ to avoid required auth info. |
3182 | def setUp(self): |
3183 | """Run before each test.""" |
3184 | - self.old_environment = os.environ.copy() |
3185 | - os.environ = { |
3186 | - 'CINDER_USERNAME': 'username', |
3187 | - 'CINDER_PASSWORD': 'password', |
3188 | - 'CINDER_PROJECT_ID': 'project_id', |
3189 | - 'OS_COMPUTE_API_VERSION': '1.1', |
3190 | - 'CINDER_URL': 'http://no.where', |
3191 | - } |
3192 | + super(ShellTest, self).setUp() |
3193 | + for var in self.FAKE_ENV: |
3194 | + self.useFixture(fixtures.EnvironmentVariable(var, |
3195 | + self.FAKE_ENV[var])) |
3196 | |
3197 | self.shell = shell.OpenStackCinderShell() |
3198 | |
3199 | @@ -44,7 +50,6 @@ |
3200 | client.get_client_class = lambda *_: fakes.FakeClient |
3201 | |
3202 | def tearDown(self): |
3203 | - os.environ = self.old_environment |
3204 | # For some method like test_image_meta_bad_action we are |
3205 | # testing a SystemExit to be thrown and object self.shell has |
3206 | # no time to get instantatiated which is OK in this case, so |
3207 | @@ -54,6 +59,7 @@ |
3208 | |
3209 | #HACK(bcwaldon): replace this when we start using stubs |
3210 | client.get_client_class = self.old_get_client_class |
3211 | + super(ShellTest, self).tearDown() |
3212 | |
3213 | def run_command(self, cmd): |
3214 | self.shell.main(cmd.split()) |
3215 | @@ -87,6 +93,7 @@ |
3216 | |
3217 | def test_delete(self): |
3218 | self.run_command('delete 1234') |
3219 | + self.assert_called('DELETE', '/volumes/1234') |
3220 | |
3221 | def test_snapshot_list_filter_volume_id(self): |
3222 | self.run_command('snapshot-list --volume-id=1234') |
3223 | @@ -96,3 +103,61 @@ |
3224 | self.run_command('snapshot-list --status=available --volume-id=1234') |
3225 | self.assert_called('GET', '/snapshots/detail?' |
3226 | 'status=available&volume_id=1234') |
3227 | + |
3228 | + def test_rename(self): |
3229 | + # basic rename with positional agruments |
3230 | + self.run_command('rename 1234 new-name') |
3231 | + expected = {'volume': {'display_name': 'new-name'}} |
3232 | + self.assert_called('PUT', '/volumes/1234', body=expected) |
3233 | + # change description only |
3234 | + self.run_command('rename 1234 --display-description=new-description') |
3235 | + expected = {'volume': {'display_description': 'new-description'}} |
3236 | + self.assert_called('PUT', '/volumes/1234', body=expected) |
3237 | + # rename and change description |
3238 | + self.run_command('rename 1234 new-name ' |
3239 | + '--display-description=new-description') |
3240 | + expected = {'volume': { |
3241 | + 'display_name': 'new-name', |
3242 | + 'display_description': 'new-description', |
3243 | + }} |
3244 | + self.assert_called('PUT', '/volumes/1234', body=expected) |
3245 | + # noop, the only all will be the lookup |
3246 | + self.run_command('rename 1234') |
3247 | + self.assert_called('GET', '/volumes/1234') |
3248 | + |
3249 | + def test_rename_snapshot(self): |
3250 | + # basic rename with positional agruments |
3251 | + self.run_command('snapshot-rename 1234 new-name') |
3252 | + expected = {'snapshot': {'display_name': 'new-name'}} |
3253 | + self.assert_called('PUT', '/snapshots/1234', body=expected) |
3254 | + # change description only |
3255 | + self.run_command('snapshot-rename 1234 ' |
3256 | + '--display-description=new-description') |
3257 | + expected = {'snapshot': {'display_description': 'new-description'}} |
3258 | + self.assert_called('PUT', '/snapshots/1234', body=expected) |
3259 | + # snapshot-rename and change description |
3260 | + self.run_command('snapshot-rename 1234 new-name ' |
3261 | + '--display-description=new-description') |
3262 | + expected = {'snapshot': { |
3263 | + 'display_name': 'new-name', |
3264 | + 'display_description': 'new-description', |
3265 | + }} |
3266 | + self.assert_called('PUT', '/snapshots/1234', body=expected) |
3267 | + # noop, the only all will be the lookup |
3268 | + self.run_command('snapshot-rename 1234') |
3269 | + self.assert_called('GET', '/snapshots/1234') |
3270 | + |
3271 | + def test_set_metadata_set(self): |
3272 | + self.run_command('metadata 1234 set key1=val1 key2=val2') |
3273 | + self.assert_called('POST', '/volumes/1234/metadata', |
3274 | + {'metadata': {'key1': 'val1', 'key2': 'val2'}}) |
3275 | + |
3276 | + def test_set_metadata_delete_dict(self): |
3277 | + self.run_command('metadata 1234 unset key1=val1 key2=val2') |
3278 | + self.assert_called('DELETE', '/volumes/1234/metadata/key1') |
3279 | + self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2) |
3280 | + |
3281 | + def test_set_metadata_delete_keys(self): |
3282 | + self.run_command('metadata 1234 unset key1 key2') |
3283 | + self.assert_called('DELETE', '/volumes/1234/metadata/key1') |
3284 | + self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2) |
3285 | |
3286 | === added file 'tests/v1/test_types.py' |
3287 | --- tests/v1/test_types.py 1970-01-01 00:00:00 +0000 |
3288 | +++ tests/v1/test_types.py 2013-01-10 22:15:27 +0000 |
3289 | @@ -0,0 +1,35 @@ |
3290 | +from cinderclient import exceptions |
3291 | +from cinderclient.v1 import volume_types |
3292 | +from tests import utils |
3293 | +from tests.v1 import fakes |
3294 | + |
3295 | +cs = fakes.FakeClient() |
3296 | + |
3297 | + |
3298 | +class TypesTest(utils.TestCase): |
3299 | + def test_list_types(self): |
3300 | + tl = cs.volume_types.list() |
3301 | + cs.assert_called('GET', '/types') |
3302 | + for t in tl: |
3303 | + self.assertTrue(isinstance(t, volume_types.VolumeType)) |
3304 | + |
3305 | + def test_create(self): |
3306 | + t = cs.volume_types.create('test-type-3') |
3307 | + cs.assert_called('POST', '/types') |
3308 | + self.assertTrue(isinstance(t, volume_types.VolumeType)) |
3309 | + |
3310 | + def test_set_key(self): |
3311 | + t = cs.volume_types.get(1) |
3312 | + t.set_keys({'k': 'v'}) |
3313 | + cs.assert_called('POST', |
3314 | + '/types/1/extra_specs', |
3315 | + {'extra_specs': {'k': 'v'}}) |
3316 | + |
3317 | + def test_unsset_keys(self): |
3318 | + t = cs.volume_types.get(1) |
3319 | + t.unset_keys(['k']) |
3320 | + cs.assert_called('DELETE', '/types/1/extra_specs/k') |
3321 | + |
3322 | + def test_delete(self): |
3323 | + cs.volume_types.delete(1) |
3324 | + cs.assert_called('DELETE', '/types/1') |
3325 | |
3326 | === modified file 'tests/v1/test_volumes.py' |
3327 | --- tests/v1/test_volumes.py 2012-10-01 12:05:26 +0000 |
3328 | +++ tests/v1/test_volumes.py 2013-01-10 22:15:27 +0000 |
3329 | @@ -1,4 +1,3 @@ |
3330 | -from cinderclient.v1 import volumes |
3331 | from tests import utils |
3332 | from tests.v1 import fakes |
3333 | |
3334 | @@ -18,7 +17,7 @@ |
3335 | cs.assert_called('DELETE', '/volumes/1234') |
3336 | |
3337 | def test_create_keypair(self): |
3338 | - kp = cs.volumes.create(1) |
3339 | + cs.volumes.create(1) |
3340 | cs.assert_called('POST', '/volumes') |
3341 | |
3342 | def test_attach(self): |
3343 | @@ -60,3 +59,13 @@ |
3344 | v = cs.volumes.get('1234') |
3345 | cs.volumes.terminate_connection(v, {}) |
3346 | cs.assert_called('POST', '/volumes/1234/action') |
3347 | + |
3348 | + def test_set_metadata(self): |
3349 | + cs.volumes.set_metadata(1234, {'k1': 'v1'}) |
3350 | + cs.assert_called('POST', '/volumes/1234/metadata', |
3351 | + {'metadata': {'k1': 'v1'}}) |
3352 | + |
3353 | + def test_delete_metadata(self): |
3354 | + keys = ['key1'] |
3355 | + cs.volumes.delete_metadata(1234, keys) |
3356 | + cs.assert_called('DELETE', '/volumes/1234/metadata/key1') |
3357 | |
3358 | === modified file 'tools/pip-requires' |
3359 | --- tools/pip-requires 2012-06-27 13:32:14 +0000 |
3360 | +++ tools/pip-requires 2013-01-10 22:15:27 +0000 |
3361 | @@ -1,4 +1,4 @@ |
3362 | argparse |
3363 | -httplib2 |
3364 | prettytable |
3365 | +requests<1.0 |
3366 | simplejson |
3367 | |
3368 | === modified file 'tools/test-requires' |
3369 | --- tools/test-requires 2012-09-07 11:35:32 +0000 |
3370 | +++ tools/test-requires 2013-01-10 22:15:27 +0000 |
3371 | @@ -1,10 +1,11 @@ |
3372 | +distribute>=0.6.24 |
3373 | |
3374 | -distribute>=0.6.24 |
3375 | +fixtures |
3376 | mock |
3377 | nose |
3378 | +nosehtmloutput |
3379 | nosexcover |
3380 | openstack.nose_plugin |
3381 | -nosehtmloutput |
3382 | -pep8==1.2 |
3383 | +pep8==1.3.3 |
3384 | sphinx>=1.1.2 |
3385 | -unittest2 |
3386 | +testtools>=0.9.22 |