Merge lp:~sandy-walsh/nova/zone-add-uses-zone-name into lp:~hudson-openstack/nova/trunk

Proposed by Sandy Walsh
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
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-of-communication from the original Zones implementation (where the endpoint was supplied by the admin in the 'nova zone-add' command). Now a zone name is supplied and used as the key in the 'nova' Service Catalog for the endpoint.

Watch this video for a demo: http://www.darksecretsoftware.com/static/videos/keystone-nova-zones.mp4
(the 404 bug is fixed now)

To post a comment you must log in.
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

I should add that this merge prop requires the following keystone branch
https://review.openstack.org/#change,359

and the python-novaclient 2.6.5 pull request
https://github.com/rackspace/python-novaclient/pull/118

to land first.

Revision history for this message
Jason Kölker (jason-koelker) wrote :

Is Bueno. There is a stray import of webob in scheduler/api.py that is never used.

review: Approve
Revision history for this message
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.

Revision history for this message
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(function, arg1, arg2):
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(_process, func), zone_list)]
378 + _wrap_method(_process, func, context), zone_list)]

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?

review: Needs Fixing
1534. By Sandy Walsh

trunk merge fixup

Revision history for this message
Matt Dietz (cerberus) wrote :

Delicious

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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