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
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-09-21 15:54:30 +0000
+++ nova/api/openstack/servers.py 2011-09-21 20:37:24 +0000
@@ -17,6 +17,7 @@
17import os17import os
18import traceback18import traceback
1919
20from novaclient import exceptions as novaclient_exceptions
20from lxml import etree21from lxml import etree
21from webob import exc22from webob import exc
22import webob23import webob
@@ -45,6 +46,27 @@
45FLAGS = flags.FLAGS46FLAGS = flags.FLAGS
4647
4748
49class ConvertedException(exc.WSGIHTTPException):
50 def __init__(self, code, title, explanation):
51 self.code = code
52 self.title = title
53 self.explanation = explanation
54 super(ConvertedException, self).__init__()
55
56
57def novaclient_exception_converter(f):
58 """Convert novaclient ClientException HTTP codes to webob exceptions.
59 Has to be the outer-most decorator.
60 """
61 def new_f(*args, **kwargs):
62 try:
63 ret = f(*args, **kwargs)
64 return ret
65 except novaclient_exceptions.ClientException, e:
66 raise ConvertedException(e.code, e.message, e.details)
67 return new_f
68
69
48class Controller(object):70class Controller(object):
49 """ The Server API base controller class for the OpenStack API """71 """ The Server API base controller class for the OpenStack API """
5072
@@ -135,6 +157,7 @@
135157
136 return dict(servers=servers)158 return dict(servers=servers)
137159
160 @novaclient_exception_converter
138 @scheduler_api.redirect_handler161 @scheduler_api.redirect_handler
139 def show(self, req, id):162 def show(self, req, id):
140 """ Returns server details by server id """163 """ Returns server details by server id """
@@ -210,6 +233,7 @@
210 def _update(self, context, req, id, inst_dict):233 def _update(self, context, req, id, inst_dict):
211 return exc.HTTPNotImplemented()234 return exc.HTTPNotImplemented()
212235
236 @novaclient_exception_converter
213 @scheduler_api.redirect_handler237 @scheduler_api.redirect_handler
214 def action(self, req, id, body):238 def action(self, req, id, body):
215 """Multi-purpose method used to take actions on a server"""239 """Multi-purpose method used to take actions on a server"""
@@ -348,6 +372,7 @@
348 raise exc.HTTPUnprocessableEntity()372 raise exc.HTTPUnprocessableEntity()
349 return webob.Response(status_int=202)373 return webob.Response(status_int=202)
350374
375 @novaclient_exception_converter
351 @scheduler_api.redirect_handler376 @scheduler_api.redirect_handler
352 def lock(self, req, id):377 def lock(self, req, id):
353 """378 """
@@ -364,6 +389,7 @@
364 raise exc.HTTPUnprocessableEntity()389 raise exc.HTTPUnprocessableEntity()
365 return webob.Response(status_int=202)390 return webob.Response(status_int=202)
366391
392 @novaclient_exception_converter
367 @scheduler_api.redirect_handler393 @scheduler_api.redirect_handler
368 def unlock(self, req, id):394 def unlock(self, req, id):
369 """395 """
@@ -380,6 +406,7 @@
380 raise exc.HTTPUnprocessableEntity()406 raise exc.HTTPUnprocessableEntity()
381 return webob.Response(status_int=202)407 return webob.Response(status_int=202)
382408
409 @novaclient_exception_converter
383 @scheduler_api.redirect_handler410 @scheduler_api.redirect_handler
384 def get_lock(self, req, id):411 def get_lock(self, req, id):
385 """412 """
@@ -395,6 +422,7 @@
395 raise exc.HTTPUnprocessableEntity()422 raise exc.HTTPUnprocessableEntity()
396 return webob.Response(status_int=202)423 return webob.Response(status_int=202)
397424
425 @novaclient_exception_converter
398 @scheduler_api.redirect_handler426 @scheduler_api.redirect_handler
399 def reset_network(self, req, id):427 def reset_network(self, req, id):
400 """428 """
@@ -410,6 +438,7 @@
410 raise exc.HTTPUnprocessableEntity()438 raise exc.HTTPUnprocessableEntity()
411 return webob.Response(status_int=202)439 return webob.Response(status_int=202)
412440
441 @novaclient_exception_converter
413 @scheduler_api.redirect_handler442 @scheduler_api.redirect_handler
414 def inject_network_info(self, req, id):443 def inject_network_info(self, req, id):
415 """444 """
@@ -425,6 +454,7 @@
425 raise exc.HTTPUnprocessableEntity()454 raise exc.HTTPUnprocessableEntity()
426 return webob.Response(status_int=202)455 return webob.Response(status_int=202)
427456
457 @novaclient_exception_converter
428 @scheduler_api.redirect_handler458 @scheduler_api.redirect_handler
429 def pause(self, req, id):459 def pause(self, req, id):
430 """ Permit Admins to Pause the server. """460 """ Permit Admins to Pause the server. """
@@ -437,6 +467,7 @@
437 raise exc.HTTPUnprocessableEntity()467 raise exc.HTTPUnprocessableEntity()
438 return webob.Response(status_int=202)468 return webob.Response(status_int=202)
439469
470 @novaclient_exception_converter
440 @scheduler_api.redirect_handler471 @scheduler_api.redirect_handler
441 def unpause(self, req, id):472 def unpause(self, req, id):
442 """ Permit Admins to Unpause the server. """473 """ Permit Admins to Unpause the server. """
@@ -449,6 +480,7 @@
449 raise exc.HTTPUnprocessableEntity()480 raise exc.HTTPUnprocessableEntity()
450 return webob.Response(status_int=202)481 return webob.Response(status_int=202)
451482
483 @novaclient_exception_converter
452 @scheduler_api.redirect_handler484 @scheduler_api.redirect_handler
453 def suspend(self, req, id):485 def suspend(self, req, id):
454 """permit admins to suspend the server"""486 """permit admins to suspend the server"""
@@ -461,6 +493,7 @@
461 raise exc.HTTPUnprocessableEntity()493 raise exc.HTTPUnprocessableEntity()
462 return webob.Response(status_int=202)494 return webob.Response(status_int=202)
463495
496 @novaclient_exception_converter
464 @scheduler_api.redirect_handler497 @scheduler_api.redirect_handler
465 def resume(self, req, id):498 def resume(self, req, id):
466 """permit admins to resume the server from suspend"""499 """permit admins to resume the server from suspend"""
@@ -473,6 +506,7 @@
473 raise exc.HTTPUnprocessableEntity()506 raise exc.HTTPUnprocessableEntity()
474 return webob.Response(status_int=202)507 return webob.Response(status_int=202)
475508
509 @novaclient_exception_converter
476 @scheduler_api.redirect_handler510 @scheduler_api.redirect_handler
477 def migrate(self, req, id):511 def migrate(self, req, id):
478 try:512 try:
@@ -482,6 +516,7 @@
482 raise exc.HTTPBadRequest()516 raise exc.HTTPBadRequest()
483 return webob.Response(status_int=202)517 return webob.Response(status_int=202)
484518
519 @novaclient_exception_converter
485 @scheduler_api.redirect_handler520 @scheduler_api.redirect_handler
486 def rescue(self, req, id, body={}):521 def rescue(self, req, id, body={}):
487 """Permit users to rescue the server."""522 """Permit users to rescue the server."""
@@ -500,6 +535,7 @@
500535
501 return {'adminPass': password}536 return {'adminPass': password}
502537
538 @novaclient_exception_converter
503 @scheduler_api.redirect_handler539 @scheduler_api.redirect_handler
504 def unrescue(self, req, id):540 def unrescue(self, req, id):
505 """Permit users to unrescue the server."""541 """Permit users to unrescue the server."""
@@ -512,6 +548,7 @@
512 raise exc.HTTPUnprocessableEntity()548 raise exc.HTTPUnprocessableEntity()
513 return webob.Response(status_int=202)549 return webob.Response(status_int=202)
514550
551 @novaclient_exception_converter
515 @scheduler_api.redirect_handler552 @scheduler_api.redirect_handler
516 def get_ajax_console(self, req, id):553 def get_ajax_console(self, req, id):
517 """Returns a url to an instance's ajaxterm console."""554 """Returns a url to an instance's ajaxterm console."""
@@ -522,6 +559,7 @@
522 raise exc.HTTPNotFound()559 raise exc.HTTPNotFound()
523 return webob.Response(status_int=202)560 return webob.Response(status_int=202)
524561
562 @novaclient_exception_converter
525 @scheduler_api.redirect_handler563 @scheduler_api.redirect_handler
526 def get_vnc_console(self, req, id):564 def get_vnc_console(self, req, id):
527 """Returns a url to an instance's ajaxterm console."""565 """Returns a url to an instance's ajaxterm console."""
@@ -532,6 +570,7 @@
532 raise exc.HTTPNotFound()570 raise exc.HTTPNotFound()
533 return webob.Response(status_int=202)571 return webob.Response(status_int=202)
534572
573 @novaclient_exception_converter
535 @scheduler_api.redirect_handler574 @scheduler_api.redirect_handler
536 def diagnostics(self, req, id):575 def diagnostics(self, req, id):
537 """Permit Admins to retrieve server diagnostics."""576 """Permit Admins to retrieve server diagnostics."""
@@ -574,6 +613,7 @@
574class ControllerV10(Controller):613class ControllerV10(Controller):
575 """v1.0 OpenStack API controller"""614 """v1.0 OpenStack API controller"""
576615
616 @novaclient_exception_converter
577 @scheduler_api.redirect_handler617 @scheduler_api.redirect_handler
578 def delete(self, req, id):618 def delete(self, req, id):
579 """ Destroys a server """619 """ Destroys a server """
@@ -652,6 +692,7 @@
652class ControllerV11(Controller):692class ControllerV11(Controller):
653 """v1.1 OpenStack API controller"""693 """v1.1 OpenStack API controller"""
654694
695 @novaclient_exception_converter
655 @scheduler_api.redirect_handler696 @scheduler_api.redirect_handler
656 def delete(self, req, id):697 def delete(self, req, id):
657 """ Destroys a server """698 """ Destroys a server """
658699
=== modified file 'nova/api/openstack/zones.py'
--- nova/api/openstack/zones.py 2011-08-05 13:01:55 +0000
+++ nova/api/openstack/zones.py 2011-09-21 20:37:24 +0000
@@ -46,7 +46,7 @@
4646
4747
48def _exclude_keys(item, keys):48def _exclude_keys(item, keys):
49 return dict((k, v) for k, v in item.iteritems() if k not in keys)49 return dict((k, v) for k, v in item.iteritems() if k and (k not in keys))
5050
5151
52def _scrub_zone(zone):52def _scrub_zone(zone):
5353
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-09-21 15:54:30 +0000
+++ nova/compute/api.py 2011-09-21 20:37:24 +0000
@@ -993,10 +993,8 @@
993 if not recurse_zones:993 if not recurse_zones:
994 return instances994 return instances
995995
996 # Recurse zones. Need admin context for this. Send along996 # Recurse zones. Send along the un-modified search options we received.
997 # the un-modified search options we received..997 children = scheduler_api.call_zone_method(context,
998 admin_context = context.elevated()
999 children = scheduler_api.call_zone_method(admin_context,
1000 "list",998 "list",
1001 errors_to_ignore=[novaclient.exceptions.NotFound],999 errors_to_ignore=[novaclient.exceptions.NotFound],
1002 novaclient_collection_name="servers",1000 novaclient_collection_name="servers",
10031001
=== added file 'nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py'
--- nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py 1970-01-01 00:00:00 +0000
+++ nova/db/sqlalchemy/migrate_repo/versions/048_add_zone_name.py 2011-09-21 20:37:24 +0000
@@ -0,0 +1,35 @@
1# Copyright 2011 OpenStack LLC.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15from sqlalchemy import Column, Integer, MetaData, String, Table
16
17meta = MetaData()
18
19zones = Table('zones', meta,
20 Column('id', Integer(), primary_key=True, nullable=False),
21 )
22
23name = Column('name', String(255))
24
25
26def upgrade(migrate_engine):
27 meta.bind = migrate_engine
28
29 zones.create_column(name)
30
31
32def downgrade(migrate_engine):
33 meta.bind = migrate_engine
34
35 zones.drop_column(name)
036
=== modified file 'nova/db/sqlalchemy/models.py'
--- nova/db/sqlalchemy/models.py 2011-09-14 15:19:03 +0000
+++ nova/db/sqlalchemy/models.py 2011-09-21 20:37:24 +0000
@@ -837,6 +837,7 @@
837 """Represents a child zone of this zone."""837 """Represents a child zone of this zone."""
838 __tablename__ = 'zones'838 __tablename__ = 'zones'
839 id = Column(Integer, primary_key=True)839 id = Column(Integer, primary_key=True)
840 name = Column(String(255))
840 api_url = Column(String(255))841 api_url = Column(String(255))
841 username = Column(String(255))842 username = Column(String(255))
842 password = Column(String(255))843 password = Column(String(255))
843844
=== modified file 'nova/rpc/impl_kombu.py'
--- nova/rpc/impl_kombu.py 2011-08-31 18:54:19 +0000
+++ nova/rpc/impl_kombu.py 2011-09-21 20:37:24 +0000
@@ -728,7 +728,6 @@
728 wait_msg = MulticallWaiter(conn)728 wait_msg = MulticallWaiter(conn)
729 conn.declare_direct_consumer(msg_id, wait_msg)729 conn.declare_direct_consumer(msg_id, wait_msg)
730 conn.topic_send(topic, msg)730 conn.topic_send(topic, msg)
731
732 return wait_msg731 return wait_msg
733732
734733
735734
=== modified file 'nova/scheduler/abstract_scheduler.py'
--- nova/scheduler/abstract_scheduler.py 2011-09-09 20:27:22 +0000
+++ nova/scheduler/abstract_scheduler.py 2011-09-21 20:37:24 +0000
@@ -118,7 +118,8 @@
118 ". ReservationID=%(reservation_id)s") % locals())118 ". ReservationID=%(reservation_id)s") % locals())
119 nova = None119 nova = None
120 try:120 try:
121 nova = novaclient.Client(zone.username, zone.password, None, url)121 nova = novaclient.Client(zone.username, zone.password, None, url,
122 token=context.auth_token)
122 nova.authenticate()123 nova.authenticate()
123 except novaclient_exceptions.BadRequest, e:124 except novaclient_exceptions.BadRequest, e:
124 raise exception.NotAuthorized(_("Bad credentials attempting "125 raise exception.NotAuthorized(_("Bad credentials attempting "
125126
=== modified file 'nova/scheduler/api.py'
--- nova/scheduler/api.py 2011-09-08 06:45:11 +0000
+++ nova/scheduler/api.py 2011-09-21 20:37:24 +0000
@@ -17,6 +17,8 @@
17Handles all requests relating to schedulers.17Handles all requests relating to schedulers.
18"""18"""
1919
20import functools
21
20from novaclient import v1_1 as novaclient22from novaclient import v1_1 as novaclient
21from novaclient import exceptions as novaclient_exceptions23from novaclient import exceptions as novaclient_exceptions
2224
@@ -117,13 +119,16 @@
117 zones = db.zone_get_all(context)119 zones = db.zone_get_all(context)
118 for zone in zones:120 for zone in zones:
119 try:121 try:
122 # Do this on behalf of the user ...
120 nova = novaclient.Client(zone.username, zone.password, None,123 nova = novaclient.Client(zone.username, zone.password, None,
121 zone.api_url)124 zone.api_url, region_name=zone.name,
125 token=context.auth_token)
122 nova.authenticate()126 nova.authenticate()
123 except novaclient_exceptions.BadRequest, e:127 except novaclient_exceptions.BadRequest, e:
124 url = zone.api_url128 url = zone.api_url
125 LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s")129 name = zone.name
126 % locals())130 LOG.warn(_("Authentication failed to zone "
131 "'%(name)s' URL=%(url)s: %(e)s") % locals())
127 #TODO (dabo) - add logic for failure counts per zone,132 #TODO (dabo) - add logic for failure counts per zone,
128 # with escalation after a given number of failures.133 # with escalation after a given number of failures.
129 continue134 continue
@@ -144,25 +149,20 @@
144 return [(zone.id, res.wait()) for zone, res in results]149 return [(zone.id, res.wait()) for zone, res in results]
145150
146151
147def child_zone_helper(zone_list, func):152def child_zone_helper(context, zone_list, func):
148 """Fire off a command to each zone in the list.153 """Fire off a command to each zone in the list.
149 The return is [novaclient return objects] from each child zone.154 The return is [novaclient return objects] from each child zone.
150 For example, if you are calling server.pause(), the list will155 For example, if you are calling server.pause(), the list will
151 be whatever the response from server.pause() is. One entry156 be whatever the response from server.pause() is. One entry
152 per child zone called."""157 per child zone called."""
153158
154 def _wrap_method(function, arg1):159 def _process(func, context, zone):
155 """Wrap method to supply an argument."""
156 def _wrap(*args, **kwargs):
157 return function(arg1, *args, **kwargs)
158 return _wrap
159
160 def _process(func, zone):
161 """Worker stub for green thread pool. Give the worker160 """Worker stub for green thread pool. Give the worker
162 an authenticated nova client and zone info."""161 an authenticated nova client and zone info."""
163 try:162 try:
164 nova = novaclient.Client(zone.username, zone.password, None,163 nova = novaclient.Client(zone.username, zone.password, None,
165 zone.api_url)164 zone.api_url, region_name=zone.name,
165 token=context.auth_token)
166 nova.authenticate()166 nova.authenticate()
167 except novaclient_exceptions.BadRequest, e:167 except novaclient_exceptions.BadRequest, e:
168 url = zone.api_url168 url = zone.api_url
@@ -174,11 +174,15 @@
174 # there if no other zones had a response.174 # there if no other zones had a response.
175 return exception.ZoneRequestError()175 return exception.ZoneRequestError()
176 else:176 else:
177 return func(nova, zone)177 try:
178 answer = func(nova, zone)
179 return answer
180 except Exception, e:
181 return e
178182
179 green_pool = greenpool.GreenPool()183 green_pool = greenpool.GreenPool()
180 return [result for result in green_pool.imap(184 return [result for result in green_pool.imap(
181 _wrap_method(_process, func), zone_list)]185 functools.partial(_process, func, context), zone_list)]
182186
183187
184def _issue_novaclient_command(nova, zone, collection,188def _issue_novaclient_command(nova, zone, collection,
@@ -211,11 +215,11 @@
211 item = args.pop(0)215 item = args.pop(0)
212 try:216 try:
213 result = manager.get(item)217 result = manager.get(item)
214 except novaclient_exceptions.NotFound:218 except novaclient_exceptions.NotFound, e:
215 url = zone.api_url219 url = zone.api_url
216 LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" %220 LOG.debug(_("%(collection)s '%(item)s' not found on '%(url)s'" %
217 locals()))221 locals()))
218 return None222 raise e
219223
220 if method_name.lower() != 'get':224 if method_name.lower() != 'get':
221 # if we're doing something other than 'get', call it passing args.225 # if we're doing something other than 'get', call it passing args.
@@ -278,7 +282,7 @@
278282
279 # Ask the children to provide an answer ...283 # Ask the children to provide an answer ...
280 LOG.debug(_("Asking child zones ..."))284 LOG.debug(_("Asking child zones ..."))
281 result = self._call_child_zones(zones,285 result = self._call_child_zones(context, zones,
282 wrap_novaclient_function(_issue_novaclient_command,286 wrap_novaclient_function(_issue_novaclient_command,
283 collection, self.method_name, item_uuid))287 collection, self.method_name, item_uuid))
284 # Scrub the results and raise another exception288 # Scrub the results and raise another exception
@@ -318,10 +322,10 @@
318322
319 return wrapped_f323 return wrapped_f
320324
321 def _call_child_zones(self, zones, function):325 def _call_child_zones(self, context, zones, function):
322 """Ask the child zones to perform this operation.326 """Ask the child zones to perform this operation.
323 Broken out for testing."""327 Broken out for testing."""
324 return child_zone_helper(zones, function)328 return child_zone_helper(context, zones, function)
325329
326 def get_collection_context_and_id(self, args, kwargs):330 def get_collection_context_and_id(self, args, kwargs):
327 """Returns a tuple of (novaclient collection name, security331 """Returns a tuple of (novaclient collection name, security
@@ -369,11 +373,19 @@
369 del server[k]373 del server[k]
370374
371 reduced_response.append(dict(server=server))375 reduced_response.append(dict(server=server))
376
377 # Boil the responses down to a single response.
378 #
379 # If we get a happy response use that, ignore all the
380 # complaint repsonses ...
372 if reduced_response:381 if reduced_response:
373 return reduced_response[0] # first for now.382 return reduced_response[0] # first for now.
374 elif found_exception:383 elif found_exception:
375 raise found_exception384 return found_exception
376 raise exception.InstanceNotFound(instance_id=self.item_uuid)385
386 # Some operations, like delete(), don't send back any results
387 # on success. We'll do the same.
388 return None
377389
378390
379def redirect_handler(f):391def redirect_handler(f):
@@ -381,5 +393,9 @@
381 try:393 try:
382 return f(*args, **kwargs)394 return f(*args, **kwargs)
383 except RedirectResult, e:395 except RedirectResult, e:
396 # Remember: exceptions are returned, not thrown, in the decorator.
397 # At this point it's safe to throw it.
398 if isinstance(e.results, BaseException):
399 raise e.results
384 return e.results400 return e.results
385 return new_f401 return new_f
386402
=== modified file 'nova/scheduler/zone_manager.py'
--- nova/scheduler/zone_manager.py 2011-08-11 20:45:55 +0000
+++ nova/scheduler/zone_manager.py 2011-09-21 20:37:24 +0000
@@ -51,16 +51,18 @@
51 def update_credentials(self, zone):51 def update_credentials(self, zone):
52 """Update zone credentials from db"""52 """Update zone credentials from db"""
53 self.zone_id = zone.id53 self.zone_id = zone.id
54 self.name = zone.name
54 self.api_url = zone.api_url55 self.api_url = zone.api_url
55 self.username = zone.username56 self.username = zone.username
56 self.password = zone.password57 self.password = zone.password
58 self.weight_offset = zone.weight_offset
59 self.weight_scale = zone.weight_scale
5760
58 def update_metadata(self, zone_metadata):61 def update_metadata(self, zone_metadata):
59 """Update zone metadata after successful communications with62 """Update zone metadata after successful communications with
60 child zone."""63 child zone."""
61 self.last_seen = utils.utcnow()64 self.last_seen = utils.utcnow()
62 self.attempt = 065 self.attempt = 0
63 self.name = zone_metadata.get("name", "n/a")
64 self.capabilities = ", ".join(["%s=%s" % (k, v)66 self.capabilities = ", ".join(["%s=%s" % (k, v)
65 for k, v in zone_metadata.iteritems() if k != 'name'])67 for k, v in zone_metadata.iteritems() if k != 'name'])
66 self.is_active = True68 self.is_active = True
@@ -68,7 +70,8 @@
68 def to_dict(self):70 def to_dict(self):
69 return dict(name=self.name, capabilities=self.capabilities,71 return dict(name=self.name, capabilities=self.capabilities,
70 is_active=self.is_active, api_url=self.api_url,72 is_active=self.is_active, api_url=self.api_url,
71 id=self.zone_id)73 id=self.zone_id, weight_scale=self.weight_scale,
74 weight_offset=self.weight_offset)
7275
73 def log_error(self, exception):76 def log_error(self, exception):
74 """Something went wrong. Check to see if zone should be77 """Something went wrong. Check to see if zone should be
@@ -89,15 +92,19 @@
8992
9093
91def _call_novaclient(zone):94def _call_novaclient(zone):
92 """Call novaclient. Broken out for testing purposes."""95 """Call novaclient. Broken out for testing purposes. Note that
96 we have to use the admin credentials for this since there is no
97 available context."""
93 client = novaclient.Client(zone.username, zone.password, None,98 client = novaclient.Client(zone.username, zone.password, None,
94 zone.api_url)99 zone.api_url, region_name=zone.name)
95 return client.zones.info()._info100 return client.zones.info()._info
96101
97102
98def _poll_zone(zone):103def _poll_zone(zone):
99 """Eventlet worker to poll a zone."""104 """Eventlet worker to poll a zone."""
100 logging.debug(_("Polling zone: %s") % zone.api_url)105 name = zone.name
106 url = zone.api_url
107 logging.debug(_("Polling zone: %(name)s @ %(url)s") % locals())
101 try:108 try:
102 zone.update_metadata(_call_novaclient(zone))109 zone.update_metadata(_call_novaclient(zone))
103 except Exception, e:110 except Exception, e:
104111
=== modified file 'nova/tests/scheduler/test_scheduler.py'
--- nova/tests/scheduler/test_scheduler.py 2011-09-08 08:09:22 +0000
+++ nova/tests/scheduler/test_scheduler.py 2011-09-21 20:37:24 +0000
@@ -53,6 +53,10 @@
53FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'53FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
5454
5555
56class FakeContext(object):
57 auth_token = None
58
59
56class TestDriver(driver.Scheduler):60class TestDriver(driver.Scheduler):
57 """Scheduler Driver for Tests"""61 """Scheduler Driver for Tests"""
58 def schedule(context, topic, *args, **kwargs):62 def schedule(context, topic, *args, **kwargs):
@@ -956,11 +960,12 @@
956960
957961
958class FakeZone(object):962class FakeZone(object):
959 def __init__(self, id, api_url, username, password):963 def __init__(self, id, api_url, username, password, name='child'):
960 self.id = id964 self.id = id
961 self.api_url = api_url965 self.api_url = api_url
962 self.username = username966 self.username = username
963 self.password = password967 self.password = password
968 self.name = name
964969
965970
966ZONE_API_URL1 = "http://1.example.com"971ZONE_API_URL1 = "http://1.example.com"
@@ -986,7 +991,7 @@
986 super(FakeRerouteCompute, self).__init__(method_name)991 super(FakeRerouteCompute, self).__init__(method_name)
987 self.id_to_return = id_to_return992 self.id_to_return = id_to_return
988993
989 def _call_child_zones(self, zones, function):994 def _call_child_zones(self, context, zones, function):
990 return []995 return []
991996
992 def get_collection_context_and_id(self, args, kwargs):997 def get_collection_context_and_id(self, args, kwargs):
@@ -1071,8 +1076,8 @@
1071 def test_unmarshal_single_server(self):1076 def test_unmarshal_single_server(self):
1072 decorator = api.reroute_compute("foo")1077 decorator = api.reroute_compute("foo")
1073 decorator.item_uuid = 'fake_uuid'1078 decorator.item_uuid = 'fake_uuid'
1074 self.assertRaises(exception.InstanceNotFound,1079 result = decorator.unmarshall_result([])
1075 decorator.unmarshall_result, [])1080 self.assertEquals(decorator.unmarshall_result([]), None)
1076 self.assertEquals(decorator.unmarshall_result(1081 self.assertEquals(decorator.unmarshall_result(
1077 [FakeResource(dict(a=1, b=2)), ]),1082 [FakeResource(dict(a=1, b=2)), ]),
1078 dict(server=dict(a=1, b=2)))1083 dict(server=dict(a=1, b=2)))
@@ -1092,7 +1097,8 @@
1092 return None1097 return None
10931098
1094 class FakeNovaClientWithFailure(object):1099 class FakeNovaClientWithFailure(object):
1095 def __init__(self, username, password, method, api_url):1100 def __init__(self, username, password, method, api_url,
1101 token=None, region_name=None):
1096 self.api_url = api_url1102 self.api_url = api_url
10971103
1098 def authenticate(self):1104 def authenticate(self):
@@ -1107,8 +1113,11 @@
1107 def do_get(self, context, uuid):1113 def do_get(self, context, uuid):
1108 pass1114 pass
11091115
1110 self.assertRaises(exception.ZoneRequestError,1116 try:
1111 do_get, None, {}, FAKE_UUID)1117 do_get(None, FakeContext(), FAKE_UUID)
1118 self.fail("Should have got redirect exception.")
1119 except api.RedirectResult, e:
1120 self.assertTrue(isinstance(e.results, exception.ZoneRequestError))
11121121
1113 def test_one_zone_down_got_instance(self):1122 def test_one_zone_down_got_instance(self):
11141123
@@ -1120,7 +1129,8 @@
1120 return FakeServer()1129 return FakeServer()
11211130
1122 class FakeNovaClientWithFailure(object):1131 class FakeNovaClientWithFailure(object):
1123 def __init__(self, username, password, method, api_url):1132 def __init__(self, username, password, method, api_url,
1133 token=None, region_name=None):
1124 self.api_url = api_url1134 self.api_url = api_url
11251135
1126 def authenticate(self):1136 def authenticate(self):
@@ -1136,14 +1146,14 @@
1136 pass1146 pass
11371147
1138 try:1148 try:
1139 do_get(None, {}, FAKE_UUID)1149 do_get(None, FakeContext(), FAKE_UUID)
1140 except api.RedirectResult, e:1150 except api.RedirectResult, e:
1141 results = e.results1151 results = e.results
1142 self.assertIn('server', results)1152 self.assertIn('server', results)
1143 self.assertEqual(results['server']['id'], FAKE_UUID)1153 self.assertEqual(results['server']['id'], FAKE_UUID)
1144 self.assertEqual(results['server']['test'], '1234')1154 self.assertEqual(results['server']['test'], '1234')
1145 except Exception, e:1155 except Exception, e:
1146 self.fail(_("RedirectResult should have been raised"))1156 self.fail(_("RedirectResult should have been raised: %s" % e))
1147 else:1157 else:
1148 self.fail(_("RedirectResult should have been raised"))1158 self.fail(_("RedirectResult should have been raised"))
11491159
@@ -1153,7 +1163,8 @@
1153 return None1163 return None
11541164
1155 class FakeNovaClientNoFailure(object):1165 class FakeNovaClientNoFailure(object):
1156 def __init__(self, username, password, method, api_url):1166 def __init__(self, username, password, method, api_url,
1167 token=None, region_name=None):
1157 pass1168 pass
11581169
1159 def authenticate(self):1170 def authenticate(self):
@@ -1167,8 +1178,11 @@
1167 def do_get(self, context, uuid):1178 def do_get(self, context, uuid):
1168 pass1179 pass
11691180
1170 self.assertRaises(exception.InstanceNotFound,1181 try:
1171 do_get, None, {}, FAKE_UUID)1182 do_get(None, FakeContext(), FAKE_UUID)
1183 self.fail("Expected redirect exception")
1184 except api.RedirectResult, e:
1185 self.assertEquals(e.results, None)
11721186
11731187
1174class FakeServerCollection(object):1188class FakeServerCollection(object):
@@ -1209,17 +1223,19 @@
12091223
1210 def test_issue_novaclient_command_not_found(self):1224 def test_issue_novaclient_command_not_found(self):
1211 zone = FakeZone(1, 'http://example.com', 'bob', 'xxx')1225 zone = FakeZone(1, 'http://example.com', 'bob', 'xxx')
1212 self.assertEquals(api._issue_novaclient_command(1226 try:
1213 FakeNovaClient(FakeEmptyServerCollection()),1227 api._issue_novaclient_command(FakeNovaClient(
1214 zone, "servers", "get", 100), None)1228 FakeEmptyServerCollection()), zone, "servers", "get", 100)
12151229 self.fail("Expected NotFound exception")
1216 self.assertEquals(api._issue_novaclient_command(1230 except novaclient_exceptions.NotFound, e:
1217 FakeNovaClient(FakeEmptyServerCollection()),1231 pass
1218 zone, "servers", "find", name="test"), None)1232
12191233 try:
1220 self.assertEquals(api._issue_novaclient_command(1234 api._issue_novaclient_command(FakeNovaClient(
1221 FakeNovaClient(FakeEmptyServerCollection()),1235 FakeEmptyServerCollection()), zone, "servers", "any", "name")
1222 zone, "servers", "any", "name"), None)1236 self.fail("Expected NotFound exception")
1237 except novaclient_exceptions.NotFound, e:
1238 pass
12231239
12241240
1225class FakeZonesProxy(object):1241class FakeZonesProxy(object):
@@ -1250,7 +1266,7 @@
1250 super(CallZoneMethodTest, self).tearDown()1266 super(CallZoneMethodTest, self).tearDown()
12511267
1252 def test_call_zone_method(self):1268 def test_call_zone_method(self):
1253 context = {}1269 context = FakeContext()
1254 method = 'do_something'1270 method = 'do_something'
1255 results = api.call_zone_method(context, method)1271 results = api.call_zone_method(context, method)
1256 self.assertEqual(len(results), 2)1272 self.assertEqual(len(results), 2)
@@ -1258,12 +1274,12 @@
1258 self.assertIn((2, 42), results)1274 self.assertIn((2, 42), results)
12591275
1260 def test_call_zone_method_not_present(self):1276 def test_call_zone_method_not_present(self):
1261 context = {}1277 context = FakeContext()
1262 method = 'not_present'1278 method = 'not_present'
1263 self.assertRaises(AttributeError, api.call_zone_method,1279 self.assertRaises(AttributeError, api.call_zone_method,
1264 context, method)1280 context, method)
12651281
1266 def test_call_zone_method_generates_exception(self):1282 def test_call_zone_method_generates_exception(self):
1267 context = {}1283 context = FakeContext()
1268 method = 'raises_exception'1284 method = 'raises_exception'
1269 self.assertRaises(Exception, api.call_zone_method, context, method)1285 self.assertRaises(Exception, api.call_zone_method, context, method)
12701286
=== modified file 'nova/tests/test_zones.py'
--- nova/tests/test_zones.py 2011-08-04 17:43:42 +0000
+++ nova/tests/test_zones.py 2011-09-21 20:37:24 +0000
@@ -63,7 +63,8 @@
63 self.mox.StubOutWithMock(db, 'zone_get_all')63 self.mox.StubOutWithMock(db, 'zone_get_all')
64 db.zone_get_all(mox.IgnoreArg()).AndReturn([64 db.zone_get_all(mox.IgnoreArg()).AndReturn([
65 FakeZone(id=1, api_url='http://foo.com', username='user1',65 FakeZone(id=1, api_url='http://foo.com', username='user1',
66 password='pass1'),66 password='pass1', name='child', weight_offset=0.0,
67 weight_scale=1.0),
67 ])68 ])
6869
69 self.assertEquals(len(zm.zone_states), 0)70 self.assertEquals(len(zm.zone_states), 0)
@@ -107,13 +108,15 @@
107 zm = zone_manager.ZoneManager()108 zm = zone_manager.ZoneManager()
108 zone_state = zone_manager.ZoneState()109 zone_state = zone_manager.ZoneState()
109 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',110 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
110 username='user1', password='pass1'))111 username='user1', password='pass1', name='child',
112 weight_offset=0.0, weight_scale=1.0))
111 zm.zone_states[1] = zone_state113 zm.zone_states[1] = zone_state
112114
113 self.mox.StubOutWithMock(db, 'zone_get_all')115 self.mox.StubOutWithMock(db, 'zone_get_all')
114 db.zone_get_all(mox.IgnoreArg()).AndReturn([116 db.zone_get_all(mox.IgnoreArg()).AndReturn([
115 FakeZone(id=1, api_url='http://foo.com', username='user2',117 FakeZone(id=1, api_url='http://foo.com', username='user2',
116 password='pass2'),118 password='pass2', name='child',
119 weight_offset=0.0, weight_scale=1.0),
117 ])120 ])
118121
119 self.assertEquals(len(zm.zone_states), 1)122 self.assertEquals(len(zm.zone_states), 1)
@@ -129,7 +132,8 @@
129 zm = zone_manager.ZoneManager()132 zm = zone_manager.ZoneManager()
130 zone_state = zone_manager.ZoneState()133 zone_state = zone_manager.ZoneState()
131 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',134 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
132 username='user1', password='pass1'))135 username='user1', password='pass1', name='child',
136 weight_offset=0.0, weight_scale=1.0))
133 zm.zone_states[1] = zone_state137 zm.zone_states[1] = zone_state
134138
135 self.mox.StubOutWithMock(db, 'zone_get_all')139 self.mox.StubOutWithMock(db, 'zone_get_all')
@@ -147,14 +151,16 @@
147 zm = zone_manager.ZoneManager()151 zm = zone_manager.ZoneManager()
148 zone_state = zone_manager.ZoneState()152 zone_state = zone_manager.ZoneState()
149 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',153 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
150 username='user1', password='pass1'))154 username='user1', password='pass1', name='child',
155 weight_offset=2.0, weight_scale=3.0))
151 zm.zone_states[1] = zone_state156 zm.zone_states[1] = zone_state
152157
153 self.mox.StubOutWithMock(db, 'zone_get_all')158 self.mox.StubOutWithMock(db, 'zone_get_all')
154159
155 db.zone_get_all(mox.IgnoreArg()).AndReturn([160 db.zone_get_all(mox.IgnoreArg()).AndReturn([
156 FakeZone(id=2, api_url='http://foo.com', username='user2',161 FakeZone(id=2, api_url='http://foo.com', username='user2',
157 password='pass2'),162 password='pass2', name='child', weight_offset=2.0,
163 weight_scale=3.0),
158 ])164 ])
159 self.assertEquals(len(zm.zone_states), 1)165 self.assertEquals(len(zm.zone_states), 1)
160166
@@ -168,19 +174,20 @@
168 def test_poll_zone(self):174 def test_poll_zone(self):
169 self.mox.StubOutWithMock(zone_manager, '_call_novaclient')175 self.mox.StubOutWithMock(zone_manager, '_call_novaclient')
170 zone_manager._call_novaclient(mox.IgnoreArg()).AndReturn(176 zone_manager._call_novaclient(mox.IgnoreArg()).AndReturn(
171 dict(name='zohan', capabilities='hairdresser'))177 dict(name='child', capabilities='hairdresser'))
172178
173 zone_state = zone_manager.ZoneState()179 zone_state = zone_manager.ZoneState()
174 zone_state.update_credentials(FakeZone(id=2,180 zone_state.update_credentials(FakeZone(id=2,
175 api_url='http://foo.com', username='user2',181 api_url='http://foo.com', username='user2',
176 password='pass2'))182 password='pass2', name='child',
183 weight_offset=0.0, weight_scale=1.0))
177 zone_state.attempt = 1184 zone_state.attempt = 1
178185
179 self.mox.ReplayAll()186 self.mox.ReplayAll()
180 zone_manager._poll_zone(zone_state)187 zone_manager._poll_zone(zone_state)
181 self.mox.VerifyAll()188 self.mox.VerifyAll()
182 self.assertEquals(zone_state.attempt, 0)189 self.assertEquals(zone_state.attempt, 0)
183 self.assertEquals(zone_state.name, 'zohan')190 self.assertEquals(zone_state.name, 'child')
184191
185 def test_poll_zone_fails(self):192 def test_poll_zone_fails(self):
186 self.stubs.Set(zone_manager, "_call_novaclient", exploding_novaclient)193 self.stubs.Set(zone_manager, "_call_novaclient", exploding_novaclient)
@@ -188,7 +195,8 @@
188 zone_state = zone_manager.ZoneState()195 zone_state = zone_manager.ZoneState()
189 zone_state.update_credentials(FakeZone(id=2,196 zone_state.update_credentials(FakeZone(id=2,
190 api_url='http://foo.com', username='user2',197 api_url='http://foo.com', username='user2',
191 password='pass2'))198 password='pass2', name='child',
199 weight_offset=0.0, weight_scale=1.0))
192 zone_state.attempt = FLAGS.zone_failures_to_offline - 1200 zone_state.attempt = FLAGS.zone_failures_to_offline - 1
193201
194 self.mox.ReplayAll()202 self.mox.ReplayAll()
@@ -196,7 +204,6 @@
196 self.mox.VerifyAll()204 self.mox.VerifyAll()
197 self.assertEquals(zone_state.attempt, 3)205 self.assertEquals(zone_state.attempt, 3)
198 self.assertFalse(zone_state.is_active)206 self.assertFalse(zone_state.is_active)
199 self.assertEquals(zone_state.name, None)
200207
201 def test_host_service_caps_stale_no_stale_service(self):208 def test_host_service_caps_stale_no_stale_service(self):
202 zm = zone_manager.ZoneManager()209 zm = zone_manager.ZoneManager()
203210
=== modified file 'tools/pip-requires'
--- tools/pip-requires 2011-09-02 05:39:31 +0000
+++ tools/pip-requires 2011-09-21 20:37:24 +0000
@@ -11,7 +11,7 @@
11kombu11kombu
12lockfile==0.812lockfile==0.8
13lxml==2.313lxml==2.3
14python-novaclient==2.6.014python-novaclient==2.6.5
15python-daemon==1.5.515python-daemon==1.5.5
16python-gflags==1.316python-gflags==1.3
17redis==2.0.017redis==2.0.0