Merge lp:~sandy-walsh/nova/zone-add-uses-zone-name into lp:~hudson-openstack/nova/trunk
- zone-add-uses-zone-name
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Matt Dietz |
Approved revision: | 1534 |
Merged at revision: | 1611 |
Proposed branch: | lp:~sandy-walsh/nova/zone-add-uses-zone-name |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
800 lines (+193/-72) 12 files modified
nova/api/openstack/servers.py (+41/-0) nova/api/openstack/zones.py (+1/-1) nova/compute/api.py (+2/-4) nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py (+35/-0) nova/db/sqlalchemy/models.py (+1/-0) nova/rpc/impl_kombu.py (+0/-1) nova/scheduler/abstract_scheduler.py (+2/-1) nova/scheduler/api.py (+37/-21) nova/scheduler/zone_manager.py (+12/-5) nova/tests/scheduler/test_scheduler.py (+43/-27) nova/tests/test_zones.py (+18/-11) tools/pip-requires (+1/-1) |
To merge this branch: | bzr merge lp:~sandy-walsh/nova/zone-add-uses-zone-name |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Matt Dietz (community) | Approve | ||
Jason Kölker (community) | Approve | ||
Review via email: mp+75651@code.launchpad.net |
Commit message
Keystone support in Nova across Zones.
Description of the change
This branch allows nova to reuse keystone auth tokens in cross-zone situations. Additionally it cleans up some of the exception handling in sub-zones (converting novaclient exceptions to webob-compatible exceptions).
In the case where Nova is acting by itself (like a scheduler polling a child), it will use the admin credentials provided. Otherwise, if a user token exists, the operation will be relayed to the child zones as that user.
Endpoints from the Keystone Service Catalog are now used to identify Zone endpoints. This is a reversal of the direction-
Watch this video for a demo: http://
(the 404 bug is fixed now)
Sandy Walsh (sandy-walsh) wrote : | # |
Jason Kölker (jason-koelker) wrote : | # |
Is Bueno. There is a stray import of webob in scheduler/api.py that is never used.
Sandy Walsh (sandy-walsh) wrote : | # |
Whoopsy ... that was a straggler from an experiment. Thanks.
Don't want to pollute the service.api space with HTTP stuff.
Matt Dietz (cerberus) wrote : | # |
Channeling Rick, here's some femto-nits
17 + def __init__(self, code, title, explaination):
18 + self.code = code
19 + self.title = title
20 + self.explaination = explaination
(Only kind of) Sorry for being an ass, but it's "explanation"
345 + def _wrap_method(
346 """Wrap method to supply an argument."""
347 def _wrap(*args, **kwargs):
348 - return function(arg1, *args, **kwargs)
349 + return function(arg1, arg2, *args, **kwargs)
377 - _wrap_method(
378 + _wrap_method(
i think functools.partial would cover this, though I'm not entirely sure...
442 - return f(*args, **kwargs)
443 + ret = f(*args, **kwargs)
444 + return ret
This looks like a remnant of some debugging?
- 1534. By Sandy Walsh
-
trunk merge fixup
Preview Diff
1 | === modified file 'nova/api/openstack/servers.py' |
2 | --- nova/api/openstack/servers.py 2011-09-21 15:54:30 +0000 |
3 | +++ nova/api/openstack/servers.py 2011-09-21 20:37:24 +0000 |
4 | @@ -17,6 +17,7 @@ |
5 | import os |
6 | import traceback |
7 | |
8 | +from novaclient import exceptions as novaclient_exceptions |
9 | from lxml import etree |
10 | from webob import exc |
11 | import webob |
12 | @@ -45,6 +46,27 @@ |
13 | FLAGS = flags.FLAGS |
14 | |
15 | |
16 | +class ConvertedException(exc.WSGIHTTPException): |
17 | + def __init__(self, code, title, explanation): |
18 | + self.code = code |
19 | + self.title = title |
20 | + self.explanation = explanation |
21 | + super(ConvertedException, self).__init__() |
22 | + |
23 | + |
24 | +def novaclient_exception_converter(f): |
25 | + """Convert novaclient ClientException HTTP codes to webob exceptions. |
26 | + Has to be the outer-most decorator. |
27 | + """ |
28 | + def new_f(*args, **kwargs): |
29 | + try: |
30 | + ret = f(*args, **kwargs) |
31 | + return ret |
32 | + except novaclient_exceptions.ClientException, e: |
33 | + raise ConvertedException(e.code, e.message, e.details) |
34 | + return new_f |
35 | + |
36 | + |
37 | class Controller(object): |
38 | """ The Server API base controller class for the OpenStack API """ |
39 | |
40 | @@ -135,6 +157,7 @@ |
41 | |
42 | return dict(servers=servers) |
43 | |
44 | + @novaclient_exception_converter |
45 | @scheduler_api.redirect_handler |
46 | def show(self, req, id): |
47 | """ Returns server details by server id """ |
48 | @@ -210,6 +233,7 @@ |
49 | def _update(self, context, req, id, inst_dict): |
50 | return exc.HTTPNotImplemented() |
51 | |
52 | + @novaclient_exception_converter |
53 | @scheduler_api.redirect_handler |
54 | def action(self, req, id, body): |
55 | """Multi-purpose method used to take actions on a server""" |
56 | @@ -348,6 +372,7 @@ |
57 | raise exc.HTTPUnprocessableEntity() |
58 | return webob.Response(status_int=202) |
59 | |
60 | + @novaclient_exception_converter |
61 | @scheduler_api.redirect_handler |
62 | def lock(self, req, id): |
63 | """ |
64 | @@ -364,6 +389,7 @@ |
65 | raise exc.HTTPUnprocessableEntity() |
66 | return webob.Response(status_int=202) |
67 | |
68 | + @novaclient_exception_converter |
69 | @scheduler_api.redirect_handler |
70 | def unlock(self, req, id): |
71 | """ |
72 | @@ -380,6 +406,7 @@ |
73 | raise exc.HTTPUnprocessableEntity() |
74 | return webob.Response(status_int=202) |
75 | |
76 | + @novaclient_exception_converter |
77 | @scheduler_api.redirect_handler |
78 | def get_lock(self, req, id): |
79 | """ |
80 | @@ -395,6 +422,7 @@ |
81 | raise exc.HTTPUnprocessableEntity() |
82 | return webob.Response(status_int=202) |
83 | |
84 | + @novaclient_exception_converter |
85 | @scheduler_api.redirect_handler |
86 | def reset_network(self, req, id): |
87 | """ |
88 | @@ -410,6 +438,7 @@ |
89 | raise exc.HTTPUnprocessableEntity() |
90 | return webob.Response(status_int=202) |
91 | |
92 | + @novaclient_exception_converter |
93 | @scheduler_api.redirect_handler |
94 | def inject_network_info(self, req, id): |
95 | """ |
96 | @@ -425,6 +454,7 @@ |
97 | raise exc.HTTPUnprocessableEntity() |
98 | return webob.Response(status_int=202) |
99 | |
100 | + @novaclient_exception_converter |
101 | @scheduler_api.redirect_handler |
102 | def pause(self, req, id): |
103 | """ Permit Admins to Pause the server. """ |
104 | @@ -437,6 +467,7 @@ |
105 | raise exc.HTTPUnprocessableEntity() |
106 | return webob.Response(status_int=202) |
107 | |
108 | + @novaclient_exception_converter |
109 | @scheduler_api.redirect_handler |
110 | def unpause(self, req, id): |
111 | """ Permit Admins to Unpause the server. """ |
112 | @@ -449,6 +480,7 @@ |
113 | raise exc.HTTPUnprocessableEntity() |
114 | return webob.Response(status_int=202) |
115 | |
116 | + @novaclient_exception_converter |
117 | @scheduler_api.redirect_handler |
118 | def suspend(self, req, id): |
119 | """permit admins to suspend the server""" |
120 | @@ -461,6 +493,7 @@ |
121 | raise exc.HTTPUnprocessableEntity() |
122 | return webob.Response(status_int=202) |
123 | |
124 | + @novaclient_exception_converter |
125 | @scheduler_api.redirect_handler |
126 | def resume(self, req, id): |
127 | """permit admins to resume the server from suspend""" |
128 | @@ -473,6 +506,7 @@ |
129 | raise exc.HTTPUnprocessableEntity() |
130 | return webob.Response(status_int=202) |
131 | |
132 | + @novaclient_exception_converter |
133 | @scheduler_api.redirect_handler |
134 | def migrate(self, req, id): |
135 | try: |
136 | @@ -482,6 +516,7 @@ |
137 | raise exc.HTTPBadRequest() |
138 | return webob.Response(status_int=202) |
139 | |
140 | + @novaclient_exception_converter |
141 | @scheduler_api.redirect_handler |
142 | def rescue(self, req, id, body={}): |
143 | """Permit users to rescue the server.""" |
144 | @@ -500,6 +535,7 @@ |
145 | |
146 | return {'adminPass': password} |
147 | |
148 | + @novaclient_exception_converter |
149 | @scheduler_api.redirect_handler |
150 | def unrescue(self, req, id): |
151 | """Permit users to unrescue the server.""" |
152 | @@ -512,6 +548,7 @@ |
153 | raise exc.HTTPUnprocessableEntity() |
154 | return webob.Response(status_int=202) |
155 | |
156 | + @novaclient_exception_converter |
157 | @scheduler_api.redirect_handler |
158 | def get_ajax_console(self, req, id): |
159 | """Returns a url to an instance's ajaxterm console.""" |
160 | @@ -522,6 +559,7 @@ |
161 | raise exc.HTTPNotFound() |
162 | return webob.Response(status_int=202) |
163 | |
164 | + @novaclient_exception_converter |
165 | @scheduler_api.redirect_handler |
166 | def get_vnc_console(self, req, id): |
167 | """Returns a url to an instance's ajaxterm console.""" |
168 | @@ -532,6 +570,7 @@ |
169 | raise exc.HTTPNotFound() |
170 | return webob.Response(status_int=202) |
171 | |
172 | + @novaclient_exception_converter |
173 | @scheduler_api.redirect_handler |
174 | def diagnostics(self, req, id): |
175 | """Permit Admins to retrieve server diagnostics.""" |
176 | @@ -574,6 +613,7 @@ |
177 | class ControllerV10(Controller): |
178 | """v1.0 OpenStack API controller""" |
179 | |
180 | + @novaclient_exception_converter |
181 | @scheduler_api.redirect_handler |
182 | def delete(self, req, id): |
183 | """ Destroys a server """ |
184 | @@ -652,6 +692,7 @@ |
185 | class ControllerV11(Controller): |
186 | """v1.1 OpenStack API controller""" |
187 | |
188 | + @novaclient_exception_converter |
189 | @scheduler_api.redirect_handler |
190 | def delete(self, req, id): |
191 | """ Destroys a server """ |
192 | |
193 | === modified file 'nova/api/openstack/zones.py' |
194 | --- nova/api/openstack/zones.py 2011-08-05 13:01:55 +0000 |
195 | +++ nova/api/openstack/zones.py 2011-09-21 20:37:24 +0000 |
196 | @@ -46,7 +46,7 @@ |
197 | |
198 | |
199 | def _exclude_keys(item, keys): |
200 | - return dict((k, v) for k, v in item.iteritems() if k not in keys) |
201 | + return dict((k, v) for k, v in item.iteritems() if k and (k not in keys)) |
202 | |
203 | |
204 | def _scrub_zone(zone): |
205 | |
206 | === modified file 'nova/compute/api.py' |
207 | --- nova/compute/api.py 2011-09-21 15:54:30 +0000 |
208 | +++ nova/compute/api.py 2011-09-21 20:37:24 +0000 |
209 | @@ -993,10 +993,8 @@ |
210 | if not recurse_zones: |
211 | return instances |
212 | |
213 | - # Recurse zones. Need admin context for this. Send along |
214 | - # the un-modified search options we received.. |
215 | - admin_context = context.elevated() |
216 | - children = scheduler_api.call_zone_method(admin_context, |
217 | + # Recurse zones. Send along the un-modified search options we received. |
218 | + children = scheduler_api.call_zone_method(context, |
219 | "list", |
220 | errors_to_ignore=[novaclient.exceptions.NotFound], |
221 | novaclient_collection_name="servers", |
222 | |
223 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py' |
224 | --- nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py 1970-01-01 00:00:00 +0000 |
225 | +++ nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py 2011-09-21 20:37:24 +0000 |
226 | @@ -0,0 +1,35 @@ |
227 | +# Copyright 2011 OpenStack LLC. |
228 | +# |
229 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
230 | +# not use this file except in compliance with the License. You may obtain |
231 | +# a copy of the License at |
232 | +# |
233 | +# http://www.apache.org/licenses/LICENSE-2.0 |
234 | +# |
235 | +# Unless required by applicable law or agreed to in writing, software |
236 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
237 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
238 | +# License for the specific language governing permissions and limitations |
239 | +# under the License. |
240 | + |
241 | +from sqlalchemy import Column, Integer, MetaData, String, Table |
242 | + |
243 | +meta = MetaData() |
244 | + |
245 | +zones = Table('zones', meta, |
246 | + Column('id', Integer(), primary_key=True, nullable=False), |
247 | + ) |
248 | + |
249 | +name = Column('name', String(255)) |
250 | + |
251 | + |
252 | +def upgrade(migrate_engine): |
253 | + meta.bind = migrate_engine |
254 | + |
255 | + zones.create_column(name) |
256 | + |
257 | + |
258 | +def downgrade(migrate_engine): |
259 | + meta.bind = migrate_engine |
260 | + |
261 | + zones.drop_column(name) |
262 | |
263 | === modified file 'nova/db/sqlalchemy/models.py' |
264 | --- nova/db/sqlalchemy/models.py 2011-09-14 15:19:03 +0000 |
265 | +++ nova/db/sqlalchemy/models.py 2011-09-21 20:37:24 +0000 |
266 | @@ -837,6 +837,7 @@ |
267 | """Represents a child zone of this zone.""" |
268 | __tablename__ = 'zones' |
269 | id = Column(Integer, primary_key=True) |
270 | + name = Column(String(255)) |
271 | api_url = Column(String(255)) |
272 | username = Column(String(255)) |
273 | password = Column(String(255)) |
274 | |
275 | === modified file 'nova/rpc/impl_kombu.py' |
276 | --- nova/rpc/impl_kombu.py 2011-08-31 18:54:19 +0000 |
277 | +++ nova/rpc/impl_kombu.py 2011-09-21 20:37:24 +0000 |
278 | @@ -728,7 +728,6 @@ |
279 | wait_msg = MulticallWaiter(conn) |
280 | conn.declare_direct_consumer(msg_id, wait_msg) |
281 | conn.topic_send(topic, msg) |
282 | - |
283 | return wait_msg |
284 | |
285 | |
286 | |
287 | === modified file 'nova/scheduler/abstract_scheduler.py' |
288 | --- nova/scheduler/abstract_scheduler.py 2011-09-09 20:27:22 +0000 |
289 | +++ nova/scheduler/abstract_scheduler.py 2011-09-21 20:37:24 +0000 |
290 | @@ -118,7 +118,8 @@ |
291 | ". ReservationID=%(reservation_id)s") % locals()) |
292 | nova = None |
293 | try: |
294 | - nova = novaclient.Client(zone.username, zone.password, None, url) |
295 | + nova = novaclient.Client(zone.username, zone.password, None, url, |
296 | + token=context.auth_token) |
297 | nova.authenticate() |
298 | except novaclient_exceptions.BadRequest, e: |
299 | raise exception.NotAuthorized(_("Bad credentials attempting " |
300 | |
301 | === modified file 'nova/scheduler/api.py' |
302 | --- nova/scheduler/api.py 2011-09-08 06:45:11 +0000 |
303 | +++ nova/scheduler/api.py 2011-09-21 20:37:24 +0000 |
304 | @@ -17,6 +17,8 @@ |
305 | Handles all requests relating to schedulers. |
306 | """ |
307 | |
308 | +import functools |
309 | + |
310 | from novaclient import v1_1 as novaclient |
311 | from novaclient import exceptions as novaclient_exceptions |
312 | |
313 | @@ -117,13 +119,16 @@ |
314 | zones = db.zone_get_all(context) |
315 | for zone in zones: |
316 | try: |
317 | + # Do this on behalf of the user ... |
318 | nova = novaclient.Client(zone.username, zone.password, None, |
319 | - zone.api_url) |
320 | + zone.api_url, region_name=zone.name, |
321 | + token=context.auth_token) |
322 | nova.authenticate() |
323 | except novaclient_exceptions.BadRequest, e: |
324 | url = zone.api_url |
325 | - LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s") |
326 | - % locals()) |
327 | + name = zone.name |
328 | + LOG.warn(_("Authentication failed to zone " |
329 | + "'%(name)s' URL=%(url)s: %(e)s") % locals()) |
330 | #TODO (dabo) - add logic for failure counts per zone, |
331 | # with escalation after a given number of failures. |
332 | continue |
333 | @@ -144,25 +149,20 @@ |
334 | return [(zone.id, res.wait()) for zone, res in results] |
335 | |
336 | |
337 | -def child_zone_helper(zone_list, func): |
338 | +def child_zone_helper(context, zone_list, func): |
339 | """Fire off a command to each zone in the list. |
340 | The return is [novaclient return objects] from each child zone. |
341 | For example, if you are calling server.pause(), the list will |
342 | be whatever the response from server.pause() is. One entry |
343 | per child zone called.""" |
344 | |
345 | - def _wrap_method(function, arg1): |
346 | - """Wrap method to supply an argument.""" |
347 | - def _wrap(*args, **kwargs): |
348 | - return function(arg1, *args, **kwargs) |
349 | - return _wrap |
350 | - |
351 | - def _process(func, zone): |
352 | + def _process(func, context, zone): |
353 | """Worker stub for green thread pool. Give the worker |
354 | an authenticated nova client and zone info.""" |
355 | try: |
356 | nova = novaclient.Client(zone.username, zone.password, None, |
357 | - zone.api_url) |
358 | + zone.api_url, region_name=zone.name, |
359 | + token=context.auth_token) |
360 | nova.authenticate() |
361 | except novaclient_exceptions.BadRequest, e: |
362 | url = zone.api_url |
363 | @@ -174,11 +174,15 @@ |
364 | # there if no other zones had a response. |
365 | return exception.ZoneRequestError() |
366 | else: |
367 | - return func(nova, zone) |
368 | + try: |
369 | + answer = func(nova, zone) |
370 | + return answer |
371 | + except Exception, e: |
372 | + return e |
373 | |
374 | green_pool = greenpool.GreenPool() |
375 | return [result for result in green_pool.imap( |
376 | - _wrap_method(_process, func), zone_list)] |
377 | + functools.partial(_process, func, context), zone_list)] |
378 | |
379 | |
380 | def _issue_novaclient_command(nova, zone, collection, |
381 | @@ -211,11 +215,11 @@ |
382 | item = args.pop(0) |
383 | try: |
384 | result = manager.get(item) |
385 | - except novaclient_exceptions.NotFound: |
386 | + except novaclient_exceptions.NotFound, e: |
387 | url = zone.api_url |
388 | LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" % |
389 | locals())) |
390 | - return None |
391 | + raise e |
392 | |
393 | if method_name.lower() != 'get': |
394 | # if we're doing something other than 'get', call it passing args. |
395 | @@ -278,7 +282,7 @@ |
396 | |
397 | # Ask the children to provide an answer ... |
398 | LOG.debug(_("Asking child zones ...")) |
399 | - result = self._call_child_zones(zones, |
400 | + result = self._call_child_zones(context, zones, |
401 | wrap_novaclient_function(_issue_novaclient_command, |
402 | collection, self.method_name, item_uuid)) |
403 | # Scrub the results and raise another exception |
404 | @@ -318,10 +322,10 @@ |
405 | |
406 | return wrapped_f |
407 | |
408 | - def _call_child_zones(self, zones, function): |
409 | + def _call_child_zones(self, context, zones, function): |
410 | """Ask the child zones to perform this operation. |
411 | Broken out for testing.""" |
412 | - return child_zone_helper(zones, function) |
413 | + return child_zone_helper(context, zones, function) |
414 | |
415 | def get_collection_context_and_id(self, args, kwargs): |
416 | """Returns a tuple of (novaclient collection name, security |
417 | @@ -369,11 +373,19 @@ |
418 | del server[k] |
419 | |
420 | reduced_response.append(dict(server=server)) |
421 | + |
422 | + # Boil the responses down to a single response. |
423 | + # |
424 | + # If we get a happy response use that, ignore all the |
425 | + # complaint repsonses ... |
426 | if reduced_response: |
427 | return reduced_response[0] # first for now. |
428 | elif found_exception: |
429 | - raise found_exception |
430 | - raise exception.InstanceNotFound(instance_id=self.item_uuid) |
431 | + return found_exception |
432 | + |
433 | + # Some operations, like delete(), don't send back any results |
434 | + # on success. We'll do the same. |
435 | + return None |
436 | |
437 | |
438 | def redirect_handler(f): |
439 | @@ -381,5 +393,9 @@ |
440 | try: |
441 | return f(*args, **kwargs) |
442 | except RedirectResult, e: |
443 | + # Remember: exceptions are returned, not thrown, in the decorator. |
444 | + # At this point it's safe to throw it. |
445 | + if isinstance(e.results, BaseException): |
446 | + raise e.results |
447 | return e.results |
448 | return new_f |
449 | |
450 | === modified file 'nova/scheduler/zone_manager.py' |
451 | --- nova/scheduler/zone_manager.py 2011-08-11 20:45:55 +0000 |
452 | +++ nova/scheduler/zone_manager.py 2011-09-21 20:37:24 +0000 |
453 | @@ -51,16 +51,18 @@ |
454 | def update_credentials(self, zone): |
455 | """Update zone credentials from db""" |
456 | self.zone_id = zone.id |
457 | + self.name = zone.name |
458 | self.api_url = zone.api_url |
459 | self.username = zone.username |
460 | self.password = zone.password |
461 | + self.weight_offset = zone.weight_offset |
462 | + self.weight_scale = zone.weight_scale |
463 | |
464 | def update_metadata(self, zone_metadata): |
465 | """Update zone metadata after successful communications with |
466 | child zone.""" |
467 | self.last_seen = utils.utcnow() |
468 | self.attempt = 0 |
469 | - self.name = zone_metadata.get("name", "n/a") |
470 | self.capabilities = ", ".join(["%s=%s" % (k, v) |
471 | for k, v in zone_metadata.iteritems() if k != 'name']) |
472 | self.is_active = True |
473 | @@ -68,7 +70,8 @@ |
474 | def to_dict(self): |
475 | return dict(name=self.name, capabilities=self.capabilities, |
476 | is_active=self.is_active, api_url=self.api_url, |
477 | - id=self.zone_id) |
478 | + id=self.zone_id, weight_scale=self.weight_scale, |
479 | + weight_offset=self.weight_offset) |
480 | |
481 | def log_error(self, exception): |
482 | """Something went wrong. Check to see if zone should be |
483 | @@ -89,15 +92,19 @@ |
484 | |
485 | |
486 | def _call_novaclient(zone): |
487 | - """Call novaclient. Broken out for testing purposes.""" |
488 | + """Call novaclient. Broken out for testing purposes. Note that |
489 | + we have to use the admin credentials for this since there is no |
490 | + available context.""" |
491 | client = novaclient.Client(zone.username, zone.password, None, |
492 | - zone.api_url) |
493 | + zone.api_url, region_name=zone.name) |
494 | return client.zones.info()._info |
495 | |
496 | |
497 | def _poll_zone(zone): |
498 | """Eventlet worker to poll a zone.""" |
499 | - logging.debug(_("Polling zone: %s") % zone.api_url) |
500 | + name = zone.name |
501 | + url = zone.api_url |
502 | + logging.debug(_("Polling zone: %(name)s @ %(url)s") % locals()) |
503 | try: |
504 | zone.update_metadata(_call_novaclient(zone)) |
505 | except Exception, e: |
506 | |
507 | === modified file 'nova/tests/scheduler/test_scheduler.py' |
508 | --- nova/tests/scheduler/test_scheduler.py 2011-09-08 08:09:22 +0000 |
509 | +++ nova/tests/scheduler/test_scheduler.py 2011-09-21 20:37:24 +0000 |
510 | @@ -53,6 +53,10 @@ |
511 | FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' |
512 | |
513 | |
514 | +class FakeContext(object): |
515 | + auth_token = None |
516 | + |
517 | + |
518 | class TestDriver(driver.Scheduler): |
519 | """Scheduler Driver for Tests""" |
520 | def schedule(context, topic, *args, **kwargs): |
521 | @@ -956,11 +960,12 @@ |
522 | |
523 | |
524 | class FakeZone(object): |
525 | - def __init__(self, id, api_url, username, password): |
526 | + def __init__(self, id, api_url, username, password, name='child'): |
527 | self.id = id |
528 | self.api_url = api_url |
529 | self.username = username |
530 | self.password = password |
531 | + self.name = name |
532 | |
533 | |
534 | ZONE_API_URL1 = "http://1.example.com" |
535 | @@ -986,7 +991,7 @@ |
536 | super(FakeRerouteCompute, self).__init__(method_name) |
537 | self.id_to_return = id_to_return |
538 | |
539 | - def _call_child_zones(self, zones, function): |
540 | + def _call_child_zones(self, context, zones, function): |
541 | return [] |
542 | |
543 | def get_collection_context_and_id(self, args, kwargs): |
544 | @@ -1071,8 +1076,8 @@ |
545 | def test_unmarshal_single_server(self): |
546 | decorator = api.reroute_compute("foo") |
547 | decorator.item_uuid = 'fake_uuid' |
548 | - self.assertRaises(exception.InstanceNotFound, |
549 | - decorator.unmarshall_result, []) |
550 | + result = decorator.unmarshall_result([]) |
551 | + self.assertEquals(decorator.unmarshall_result([]), None) |
552 | self.assertEquals(decorator.unmarshall_result( |
553 | [FakeResource(dict(a=1, b=2)), ]), |
554 | dict(server=dict(a=1, b=2))) |
555 | @@ -1092,7 +1097,8 @@ |
556 | return None |
557 | |
558 | class FakeNovaClientWithFailure(object): |
559 | - def __init__(self, username, password, method, api_url): |
560 | + def __init__(self, username, password, method, api_url, |
561 | + token=None, region_name=None): |
562 | self.api_url = api_url |
563 | |
564 | def authenticate(self): |
565 | @@ -1107,8 +1113,11 @@ |
566 | def do_get(self, context, uuid): |
567 | pass |
568 | |
569 | - self.assertRaises(exception.ZoneRequestError, |
570 | - do_get, None, {}, FAKE_UUID) |
571 | + try: |
572 | + do_get(None, FakeContext(), FAKE_UUID) |
573 | + self.fail("Should have got redirect exception.") |
574 | + except api.RedirectResult, e: |
575 | + self.assertTrue(isinstance(e.results, exception.ZoneRequestError)) |
576 | |
577 | def test_one_zone_down_got_instance(self): |
578 | |
579 | @@ -1120,7 +1129,8 @@ |
580 | return FakeServer() |
581 | |
582 | class FakeNovaClientWithFailure(object): |
583 | - def __init__(self, username, password, method, api_url): |
584 | + def __init__(self, username, password, method, api_url, |
585 | + token=None, region_name=None): |
586 | self.api_url = api_url |
587 | |
588 | def authenticate(self): |
589 | @@ -1136,14 +1146,14 @@ |
590 | pass |
591 | |
592 | try: |
593 | - do_get(None, {}, FAKE_UUID) |
594 | + do_get(None, FakeContext(), FAKE_UUID) |
595 | except api.RedirectResult, e: |
596 | results = e.results |
597 | self.assertIn('server', results) |
598 | self.assertEqual(results['server']['id'], FAKE_UUID) |
599 | self.assertEqual(results['server']['test'], '1234') |
600 | except Exception, e: |
601 | - self.fail(_("RedirectResult should have been raised")) |
602 | + self.fail(_("RedirectResult should have been raised: %s" % e)) |
603 | else: |
604 | self.fail(_("RedirectResult should have been raised")) |
605 | |
606 | @@ -1153,7 +1163,8 @@ |
607 | return None |
608 | |
609 | class FakeNovaClientNoFailure(object): |
610 | - def __init__(self, username, password, method, api_url): |
611 | + def __init__(self, username, password, method, api_url, |
612 | + token=None, region_name=None): |
613 | pass |
614 | |
615 | def authenticate(self): |
616 | @@ -1167,8 +1178,11 @@ |
617 | def do_get(self, context, uuid): |
618 | pass |
619 | |
620 | - self.assertRaises(exception.InstanceNotFound, |
621 | - do_get, None, {}, FAKE_UUID) |
622 | + try: |
623 | + do_get(None, FakeContext(), FAKE_UUID) |
624 | + self.fail("Expected redirect exception") |
625 | + except api.RedirectResult, e: |
626 | + self.assertEquals(e.results, None) |
627 | |
628 | |
629 | class FakeServerCollection(object): |
630 | @@ -1209,17 +1223,19 @@ |
631 | |
632 | def test_issue_novaclient_command_not_found(self): |
633 | zone = FakeZone(1, 'http://example.com', 'bob', 'xxx') |
634 | - self.assertEquals(api._issue_novaclient_command( |
635 | - FakeNovaClient(FakeEmptyServerCollection()), |
636 | - zone, "servers", "get", 100), None) |
637 | - |
638 | - self.assertEquals(api._issue_novaclient_command( |
639 | - FakeNovaClient(FakeEmptyServerCollection()), |
640 | - zone, "servers", "find", name="test"), None) |
641 | - |
642 | - self.assertEquals(api._issue_novaclient_command( |
643 | - FakeNovaClient(FakeEmptyServerCollection()), |
644 | - zone, "servers", "any", "name"), None) |
645 | + try: |
646 | + api._issue_novaclient_command(FakeNovaClient( |
647 | + FakeEmptyServerCollection()), zone, "servers", "get", 100) |
648 | + self.fail("Expected NotFound exception") |
649 | + except novaclient_exceptions.NotFound, e: |
650 | + pass |
651 | + |
652 | + try: |
653 | + api._issue_novaclient_command(FakeNovaClient( |
654 | + FakeEmptyServerCollection()), zone, "servers", "any", "name") |
655 | + self.fail("Expected NotFound exception") |
656 | + except novaclient_exceptions.NotFound, e: |
657 | + pass |
658 | |
659 | |
660 | class FakeZonesProxy(object): |
661 | @@ -1250,7 +1266,7 @@ |
662 | super(CallZoneMethodTest, self).tearDown() |
663 | |
664 | def test_call_zone_method(self): |
665 | - context = {} |
666 | + context = FakeContext() |
667 | method = 'do_something' |
668 | results = api.call_zone_method(context, method) |
669 | self.assertEqual(len(results), 2) |
670 | @@ -1258,12 +1274,12 @@ |
671 | self.assertIn((2, 42), results) |
672 | |
673 | def test_call_zone_method_not_present(self): |
674 | - context = {} |
675 | + context = FakeContext() |
676 | method = 'not_present' |
677 | self.assertRaises(AttributeError, api.call_zone_method, |
678 | context, method) |
679 | |
680 | def test_call_zone_method_generates_exception(self): |
681 | - context = {} |
682 | + context = FakeContext() |
683 | method = 'raises_exception' |
684 | self.assertRaises(Exception, api.call_zone_method, context, method) |
685 | |
686 | === modified file 'nova/tests/test_zones.py' |
687 | --- nova/tests/test_zones.py 2011-08-04 17:43:42 +0000 |
688 | +++ nova/tests/test_zones.py 2011-09-21 20:37:24 +0000 |
689 | @@ -63,7 +63,8 @@ |
690 | self.mox.StubOutWithMock(db, 'zone_get_all') |
691 | db.zone_get_all(mox.IgnoreArg()).AndReturn([ |
692 | FakeZone(id=1, api_url='http://foo.com', username='user1', |
693 | - password='pass1'), |
694 | + password='pass1', name='child', weight_offset=0.0, |
695 | + weight_scale=1.0), |
696 | ]) |
697 | |
698 | self.assertEquals(len(zm.zone_states), 0) |
699 | @@ -107,13 +108,15 @@ |
700 | zm = zone_manager.ZoneManager() |
701 | zone_state = zone_manager.ZoneState() |
702 | zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com', |
703 | - username='user1', password='pass1')) |
704 | + username='user1', password='pass1', name='child', |
705 | + weight_offset=0.0, weight_scale=1.0)) |
706 | zm.zone_states[1] = zone_state |
707 | |
708 | self.mox.StubOutWithMock(db, 'zone_get_all') |
709 | db.zone_get_all(mox.IgnoreArg()).AndReturn([ |
710 | FakeZone(id=1, api_url='http://foo.com', username='user2', |
711 | - password='pass2'), |
712 | + password='pass2', name='child', |
713 | + weight_offset=0.0, weight_scale=1.0), |
714 | ]) |
715 | |
716 | self.assertEquals(len(zm.zone_states), 1) |
717 | @@ -129,7 +132,8 @@ |
718 | zm = zone_manager.ZoneManager() |
719 | zone_state = zone_manager.ZoneState() |
720 | zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com', |
721 | - username='user1', password='pass1')) |
722 | + username='user1', password='pass1', name='child', |
723 | + weight_offset=0.0, weight_scale=1.0)) |
724 | zm.zone_states[1] = zone_state |
725 | |
726 | self.mox.StubOutWithMock(db, 'zone_get_all') |
727 | @@ -147,14 +151,16 @@ |
728 | zm = zone_manager.ZoneManager() |
729 | zone_state = zone_manager.ZoneState() |
730 | zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com', |
731 | - username='user1', password='pass1')) |
732 | + username='user1', password='pass1', name='child', |
733 | + weight_offset=2.0, weight_scale=3.0)) |
734 | zm.zone_states[1] = zone_state |
735 | |
736 | self.mox.StubOutWithMock(db, 'zone_get_all') |
737 | |
738 | db.zone_get_all(mox.IgnoreArg()).AndReturn([ |
739 | FakeZone(id=2, api_url='http://foo.com', username='user2', |
740 | - password='pass2'), |
741 | + password='pass2', name='child', weight_offset=2.0, |
742 | + weight_scale=3.0), |
743 | ]) |
744 | self.assertEquals(len(zm.zone_states), 1) |
745 | |
746 | @@ -168,19 +174,20 @@ |
747 | def test_poll_zone(self): |
748 | self.mox.StubOutWithMock(zone_manager, '_call_novaclient') |
749 | zone_manager._call_novaclient(mox.IgnoreArg()).AndReturn( |
750 | - dict(name='zohan', capabilities='hairdresser')) |
751 | + dict(name='child', capabilities='hairdresser')) |
752 | |
753 | zone_state = zone_manager.ZoneState() |
754 | zone_state.update_credentials(FakeZone(id=2, |
755 | api_url='http://foo.com', username='user2', |
756 | - password='pass2')) |
757 | + password='pass2', name='child', |
758 | + weight_offset=0.0, weight_scale=1.0)) |
759 | zone_state.attempt = 1 |
760 | |
761 | self.mox.ReplayAll() |
762 | zone_manager._poll_zone(zone_state) |
763 | self.mox.VerifyAll() |
764 | self.assertEquals(zone_state.attempt, 0) |
765 | - self.assertEquals(zone_state.name, 'zohan') |
766 | + self.assertEquals(zone_state.name, 'child') |
767 | |
768 | def test_poll_zone_fails(self): |
769 | self.stubs.Set(zone_manager, "_call_novaclient", exploding_novaclient) |
770 | @@ -188,7 +195,8 @@ |
771 | zone_state = zone_manager.ZoneState() |
772 | zone_state.update_credentials(FakeZone(id=2, |
773 | api_url='http://foo.com', username='user2', |
774 | - password='pass2')) |
775 | + password='pass2', name='child', |
776 | + weight_offset=0.0, weight_scale=1.0)) |
777 | zone_state.attempt = FLAGS.zone_failures_to_offline - 1 |
778 | |
779 | self.mox.ReplayAll() |
780 | @@ -196,7 +204,6 @@ |
781 | self.mox.VerifyAll() |
782 | self.assertEquals(zone_state.attempt, 3) |
783 | self.assertFalse(zone_state.is_active) |
784 | - self.assertEquals(zone_state.name, None) |
785 | |
786 | def test_host_service_caps_stale_no_stale_service(self): |
787 | zm = zone_manager.ZoneManager() |
788 | |
789 | === modified file 'tools/pip-requires' |
790 | --- tools/pip-requires 2011-09-02 05:39:31 +0000 |
791 | +++ tools/pip-requires 2011-09-21 20:37:24 +0000 |
792 | @@ -11,7 +11,7 @@ |
793 | kombu |
794 | lockfile==0.8 |
795 | lxml==2.3 |
796 | -python-novaclient==2.6.0 |
797 | +python-novaclient==2.6.5 |
798 | python-daemon==1.5.5 |
799 | python-gflags==1.3 |
800 | redis==2.0.0 |
I should add that this merge prop requires the following keystone branch /review. openstack. org/#change, 359
https:/
and the python-novaclient 2.6.5 pull request /github. com/rackspace/ python- novaclient/ pull/118
https:/
to land first.