Merge lp:~sandy-walsh/nova/zones2 into lp:~hudson-openstack/nova/trunk

Proposed by Sandy Walsh
Status: Merged
Approved by: Jay Pipes
Approved revision: 673
Merged at revision: 782
Proposed branch: lp:~sandy-walsh/nova/zones2
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 655 lines (+443/-22)
9 files modified
nova/api/openstack/__init__.py (+3/-3)
nova/api/openstack/zones.py (+20/-4)
nova/flags.py (+4/-0)
nova/scheduler/api.py (+49/-0)
nova/scheduler/manager.py (+10/-0)
nova/scheduler/zone_manager.py (+143/-0)
nova/tests/api/openstack/test_zones.py (+41/-15)
nova/tests/test_zones.py (+172/-0)
tools/pip-requires (+1/-0)
To merge this branch: bzr merge lp:~sandy-walsh/nova/zones2
Reviewer Review Type Date Requested Status
Devin Carlen (community) Approve
Eric Day (community) Approve
Jay Pipes (community) Approve
Review via email: mp+50247@code.launchpad.net

Commit message

Introduces the ZoneManager to the Scheduler which polls the child zones and caches their availability and capabilities.

Description of the change

This branch establishes a ZoneManager within the scheduler.

The ZoneManager takes the child zone structure created in the first phase of this bp and polls the child zones using the public API (and python-novatools, a new pip requirement).

From this polling, the parent zone determines the name & capabilities of the child zones. It also tracks if a zone is online or offline (after successive errors).

Additionally 'novatools zone-list' will now show the status of the child zones as gathered by the scheduler if available. It can take a minute or so for the first polling to occur (configurable).

To post a comment you must log in.
Revision history for this message
Eric Day (eday) wrote :

Scheduler API - I would create a scheduler API much like we did for compute/volume/network, and use that API interface instead of making RPC calls directly from nova/api/openstack/zones.py. We want to keep nova.api modules thin wrappers over the core modules so we can reuse those core calls easily (say if some other nova.api module or another core component wants to grab the same info).

zone_capabilities flag - Should this be a list of k/v pairs rather than just a tag list? Something like "hypervisors=xen,kvm;volume=iscsi". Eventually we probably want to think about how this could be auto-generated from child zones/plugins. For example, when a compute worker configured for xen it automatically sets this in the scheduler/API so other zones can discover it. This isn't a blocker for merge since we can figure this out later.

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote :

Hi!

Good second round of coding! :)

72 + for item in items:
73 + item['api_url'] = item['api_url'].replace('\\/', '/')

Are there a lot of URLs with \/ in them?

76 + if len(items) == 0:

Could just shorten the above to: if not items:

106 +DEFINE_string('zone_capabilities', 'xen, linux',
107 + 'comma-delimited list of tags which represent boolean'
108 + ' capabilities of this zone')

I was under the impression that we wanted to store these kind of things in the database, no? I think a flag of boolean-like capabilities is a bit limiting.

277 + def _poll_zones(self, context):
278 + """Try to connect to each child zone and get update."""
279 + green_pool = GreenPool()
280 + green_pool.imap(_poll_zone, self.zone_states.values())

Would it be a bit more efficient to have the ZoneManager initialize a GreenPool in its constructure and have ZoneManager._poll_zones() simply use that pool?

148 +# Copyright (c) 2010 Openstack, LLC.

s/2010/2011 :)

286 + logging.debug("Updating zone cache from db.")

Missed an i18n above

Other than those little nits, looks good. :)

I encourage you to add a bit of overview documentation to the doc/source/ RST files when you get a chance in a future commit.

-jay

review: Needs Fixing
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Good feedback guys ... I'm on it!

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

> zone_capabilities flag - Should this be a list of k/v pairs rather than just a
> tag list? Something like "hypervisors=xen,kvm;volume=iscsi". Eventually we
> probably want to think about how this could be auto-generated from child
> zones/plugins. For example, when a compute worker configured for xen it
> automatically sets this in the scheduler/API so other zones can discover it.

Agreed. Phase 3 is going to be getting Compute, Volume, etc to send messages into the Scheduler service as well with their capabilities (so your API note is timely).

I meant to put the feature in this branch that aggregates the child zone capabilities collected into the /zone/info query ... forgot. I'll do that next in Phase 3.

Revision history for this message
Ed Leafe (ed-leafe) wrote :

235 +def _call_novatools(zone):
236 + """Call novatools. Broken out for testing purposes."""
237 + os = novatools.OpenStack(zone.username, zone.password, zone.api_url)
238 + return os.zones.info()._info
   In this code, I would prefer a name that isn't likely to conflict with common Python module names. While 'os' isn't imported into the namespace for that code, it's a good practice not to use common names, especially when you can pick any name, as they can introduce hard-to-detect bugs.

272 + # Cleanup zones removed from db ...
273 + for zone_id in self.zone_states.keys():
   Simpler: for zone_id in self.zone_states:
(.keys() is redundant)

299 +def zone_get_all_scheduler(x, y, z):
 - and -
308 +def zone_get_all_scheduler_empty(x, y, z):
   Is there a reason for three args named x,y,z?

393 +def exploding_novatools(zone):
   Love it!

454 + zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
455 + username='user1', password='pass1'))
   Fake URLs for testing should use 'example.com'. 'foo.com' is an actual domain name.

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

> 73 + item['api_url'] = item['api_url'].replace('\\/', '/')

The data coming back from the queue seems to be escaped/not unmarshalled properly. I need to investigate this further.

Revision history for this message
Jay Pipes (jaypipes) wrote :

Hi Sandy! Please set the merge prop to Work In Progress while you work on any changes, then set back to Needs Review when you've pushed your changes up to LP. That way, reviewers are sent an email asking them to re-review the patches. Thanks!

Revision history for this message
Jay Pipes (jaypipes) wrote :

excellent work, Sandy. looking forward to phase 3.

review: Approve
Revision history for this message
Eric Day (eday) wrote :

36: still importing rpc, not needed
37,160,215,348: according to the hacking file, "thou shalt not import objects, only modules"

Otherwise, lgtm!

review: Needs Fixing
Revision history for this message
Eric Day (eday) wrote :

lgtm

review: Approve
Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (23.1 KiB)

The attempt to merge lp:~sandy-walsh/nova/zones2 into lp:nova failed. Below is the output from the failed tests.

AdminAPITest
    test_admin_disabled ok
    test_admin_enabled ok
APITest
    test_exceptions_are_converted_to_faults ok
Test
    test_authorize_token ok
    test_authorize_user ok
    test_bad_token ok
    test_bad_user ok
    test_no_user ok
    test_token_expiry ok
TestLimiter
    test_authorize_token ok
LimiterTest
    test_limiter_custom_max_limit ok
    test_limiter_limit_and_offset ok
    test_limiter_limit_medium ok
    test_limiter_limit_over_max ok
    test_limiter_limit_zero ok
    test_limiter_nothing ok
    test_limiter_offset_bad ok
    test_limiter_offset_blank ok
    test_limiter_offset_medium ok
    test_limiter_offset_over_max ok
    test_limiter_offset_zero ok
TestFaults
    test_fault_parts ok
    test_raise ok
    test_retry_header ok
FlavorsTest
    test_get_flavor_by_id ok
    test_get_flavor_list ok
GlanceImageServiceTest
    test_create ok
    test_create_and_show_non_existing_image ok
    test_delete ok
    test_update ok
ImageControllerWithGlanceServiceTest
    test_get_image_details ok
    test_get_image_index ok
LocalImageServiceTest
    test_create ok
    test_create_and_show_non_existing_image ok
    test_delete ok
    test_update ok
LimiterTest
    test_minute ok
    test_one_per_period ok
    test_second ok
    test_users_get_separate_buckets ok
    test_we_can_go_indefinitely_if_we_spread_out_requests ok
WSGIAppProxyTest
    test_200 ok
    test_403 ok
    test_failure ...

Revision history for this message
Jay Pipes (jaypipes) wrote :

we need to install novatools on Hudson...

Revision history for this message
Thierry Carrez (ttx) wrote :

I'll quickly look into packaging it for Ubuntu, which should simplify this operation.

Revision history for this message
Todd Willey (xtoddx) wrote :

Any updates on the state of hudson + novaclient? Also, why is it `class API:` and not `class API(object):`?

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Heh, thanks Todd. I missed that.

Latest news on novaclient: license issues are settled, just need to get a tarball to base the PPA on.

Revision history for this message
Thierry Carrez (ttx) wrote :

python-novaclient is now packaged in PPA and available on Hudson, no more known blockers in getting this in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-03-03 01:50:48 +0000
+++ nova/api/openstack/__init__.py 2011-03-09 12:55:18 +0000
@@ -77,8 +77,8 @@
7777
78 server_members['pause'] = 'POST'78 server_members['pause'] = 'POST'
79 server_members['unpause'] = 'POST'79 server_members['unpause'] = 'POST'
80 server_members["diagnostics"] = "GET"80 server_members['diagnostics'] = 'GET'
81 server_members["actions"] = "GET"81 server_members['actions'] = 'GET'
82 server_members['suspend'] = 'POST'82 server_members['suspend'] = 'POST'
83 server_members['resume'] = 'POST'83 server_members['resume'] = 'POST'
84 server_members['rescue'] = 'POST'84 server_members['rescue'] = 'POST'
@@ -87,7 +87,7 @@
87 server_members['inject_network_info'] = 'POST'87 server_members['inject_network_info'] = 'POST'
8888
89 mapper.resource("zone", "zones", controller=zones.Controller(),89 mapper.resource("zone", "zones", controller=zones.Controller(),
90 collection={'detail': 'GET'})90 collection={'detail': 'GET', 'info': 'GET'}),
9191
92 mapper.resource("server", "servers", controller=servers.Controller(),92 mapper.resource("server", "servers", controller=servers.Controller(),
93 collection={'detail': 'GET'},93 collection={'detail': 'GET'},
9494
=== modified file 'nova/api/openstack/zones.py'
--- nova/api/openstack/zones.py 2011-02-21 07:16:10 +0000
+++ nova/api/openstack/zones.py 2011-03-09 12:55:18 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010 OpenStack LLC.1# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.2# All Rights Reserved.
3#3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may4# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -18,6 +18,7 @@
18from nova import flags18from nova import flags
19from nova import wsgi19from nova import wsgi
20from nova import db20from nova import db
21from nova.scheduler import api
2122
2223
23FLAGS = flags.FLAGS24FLAGS = flags.FLAGS
@@ -32,6 +33,10 @@
32 return dict((k, v) for k, v in item.iteritems() if k in keys)33 return dict((k, v) for k, v in item.iteritems() if k in keys)
3334
3435
36def _exclude_keys(item, keys):
37 return dict((k, v) for k, v in item.iteritems() if k not in keys)
38
39
35def _scrub_zone(zone):40def _scrub_zone(zone):
36 return _filter_keys(zone, ('id', 'api_url'))41 return _filter_keys(zone, ('id', 'api_url'))
3742
@@ -41,19 +46,30 @@
41 _serialization_metadata = {46 _serialization_metadata = {
42 'application/xml': {47 'application/xml': {
43 "attributes": {48 "attributes": {
44 "zone": ["id", "api_url"]}}}49 "zone": ["id", "api_url", "name", "capabilities"]}}}
4550
46 def index(self, req):51 def index(self, req):
47 """Return all zones in brief"""52 """Return all zones in brief"""
48 items = db.zone_get_all(req.environ['nova.context'])53 # Ask the ZoneManager in the Scheduler for most recent data,
54 # or fall-back to the database ...
55 items = api.API().get_zone_list(req.environ['nova.context'])
56 if not items:
57 items = db.zone_get_all(req.environ['nova.context'])
58
49 items = common.limited(items, req)59 items = common.limited(items, req)
50 items = [_scrub_zone(item) for item in items]60 items = [_exclude_keys(item, ['username', 'password'])
61 for item in items]
51 return dict(zones=items)62 return dict(zones=items)
5263
53 def detail(self, req):64 def detail(self, req):
54 """Return all zones in detail"""65 """Return all zones in detail"""
55 return self.index(req)66 return self.index(req)
5667
68 def info(self, req):
69 """Return name and capabilities for this zone."""
70 return dict(zone=dict(name=FLAGS.zone_name,
71 capabilities=FLAGS.zone_capabilities))
72
57 def show(self, req, id):73 def show(self, req, id):
58 """Return data about the given zone id"""74 """Return data about the given zone id"""
59 zone_id = int(id)75 zone_id = int(id)
6076
=== modified file 'nova/flags.py'
--- nova/flags.py 2011-02-25 01:04:25 +0000
+++ nova/flags.py 2011-03-09 12:55:18 +0000
@@ -354,3 +354,7 @@
354354
355DEFINE_string('node_availability_zone', 'nova',355DEFINE_string('node_availability_zone', 'nova',
356 'availability zone of this node')356 'availability zone of this node')
357
358DEFINE_string('zone_name', 'nova', 'name of this zone')
359DEFINE_string('zone_capabilities', 'kypervisor:xenserver;os:linux',
360 'Key/Value tags which represent capabilities of this zone')
357361
=== added file 'nova/scheduler/api.py'
--- nova/scheduler/api.py 1970-01-01 00:00:00 +0000
+++ nova/scheduler/api.py 2011-03-09 12:55:18 +0000
@@ -0,0 +1,49 @@
1# Copyright (c) 2011 Openstack, LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""
17Handles all requests relating to schedulers.
18"""
19
20from nova import flags
21from nova import log as logging
22from nova import rpc
23
24FLAGS = flags.FLAGS
25LOG = logging.getLogger('nova.scheduler.api')
26
27
28class API(object):
29 """API for interacting with the scheduler."""
30
31 def _call_scheduler(self, method, context, params=None):
32 """Generic handler for RPC calls to the scheduler.
33
34 :param params: Optional dictionary of arguments to be passed to the
35 scheduler worker
36
37 :retval: Result returned by scheduler worker
38 """
39 if not params:
40 params = {}
41 queue = FLAGS.scheduler_topic
42 kwargs = {'method': method, 'args': params}
43 return rpc.call(context, queue, kwargs)
44
45 def get_zone_list(self, context):
46 items = self._call_scheduler('get_zone_list', context)
47 for item in items:
48 item['api_url'] = item['api_url'].replace('\\/', '/')
49 return items
050
=== modified file 'nova/scheduler/manager.py'
--- nova/scheduler/manager.py 2011-01-19 15:41:30 +0000
+++ nova/scheduler/manager.py 2011-03-09 12:55:18 +0000
@@ -29,6 +29,7 @@
29from nova import manager29from nova import manager
30from nova import rpc30from nova import rpc
31from nova import utils31from nova import utils
32from nova.scheduler import zone_manager
3233
33LOG = logging.getLogger('nova.scheduler.manager')34LOG = logging.getLogger('nova.scheduler.manager')
34FLAGS = flags.FLAGS35FLAGS = flags.FLAGS
@@ -43,12 +44,21 @@
43 if not scheduler_driver:44 if not scheduler_driver:
44 scheduler_driver = FLAGS.scheduler_driver45 scheduler_driver = FLAGS.scheduler_driver
45 self.driver = utils.import_object(scheduler_driver)46 self.driver = utils.import_object(scheduler_driver)
47 self.zone_manager = zone_manager.ZoneManager()
46 super(SchedulerManager, self).__init__(*args, **kwargs)48 super(SchedulerManager, self).__init__(*args, **kwargs)
4749
48 def __getattr__(self, key):50 def __getattr__(self, key):
49 """Converts all method calls to use the schedule method"""51 """Converts all method calls to use the schedule method"""
50 return functools.partial(self._schedule, key)52 return functools.partial(self._schedule, key)
5153
54 def periodic_tasks(self, context=None):
55 """Poll child zones periodically to get status."""
56 self.zone_manager.ping(context)
57
58 def get_zone_list(self, context=None):
59 """Get a list of zones from the ZoneManager."""
60 return self.zone_manager.get_zone_list()
61
52 def _schedule(self, method, context, topic, *args, **kwargs):62 def _schedule(self, method, context, topic, *args, **kwargs):
53 """Tries to call schedule_* method on the driver to retrieve host.63 """Tries to call schedule_* method on the driver to retrieve host.
5464
5565
=== added file 'nova/scheduler/zone_manager.py'
--- nova/scheduler/zone_manager.py 1970-01-01 00:00:00 +0000
+++ nova/scheduler/zone_manager.py 2011-03-09 12:55:18 +0000
@@ -0,0 +1,143 @@
1# Copyright (c) 2011 Openstack, LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""
17ZoneManager oversees all communications with child Zones.
18"""
19
20import novaclient
21import thread
22import traceback
23
24from datetime import datetime
25from eventlet import greenpool
26
27from nova import db
28from nova import flags
29from nova import log as logging
30
31FLAGS = flags.FLAGS
32flags.DEFINE_integer('zone_db_check_interval', 60,
33 'Seconds between getting fresh zone info from db.')
34flags.DEFINE_integer('zone_failures_to_offline', 3,
35 'Number of consecutive errors before marking zone offline')
36
37
38class ZoneState(object):
39 """Holds the state of all connected child zones."""
40 def __init__(self):
41 self.is_active = True
42 self.name = None
43 self.capabilities = None
44 self.attempt = 0
45 self.last_seen = datetime.min
46 self.last_exception = None
47 self.last_exception_time = None
48
49 def update_credentials(self, zone):
50 """Update zone credentials from db"""
51 self.zone_id = zone.id
52 self.api_url = zone.api_url
53 self.username = zone.username
54 self.password = zone.password
55
56 def update_metadata(self, zone_metadata):
57 """Update zone metadata after successful communications with
58 child zone."""
59 self.last_seen = datetime.now()
60 self.attempt = 0
61 self.name = zone_metadata["name"]
62 self.capabilities = zone_metadata["capabilities"]
63 self.is_active = True
64
65 def to_dict(self):
66 return dict(name=self.name, capabilities=self.capabilities,
67 is_active=self.is_active, api_url=self.api_url,
68 id=self.zone_id)
69
70 def log_error(self, exception):
71 """Something went wrong. Check to see if zone should be
72 marked as offline."""
73 self.last_exception = exception
74 self.last_exception_time = datetime.now()
75 api_url = self.api_url
76 logging.warning(_("'%(exception)s' error talking to "
77 "zone %(api_url)s") % locals())
78
79 max_errors = FLAGS.zone_failures_to_offline
80 self.attempt += 1
81 if self.attempt >= max_errors:
82 self.is_active = False
83 logging.error(_("No answer from zone %(api_url)s "
84 "after %(max_errors)d "
85 "attempts. Marking inactive.") % locals())
86
87
88def _call_novaclient(zone):
89 """Call novaclient. Broken out for testing purposes."""
90 client = novaclient.OpenStack(zone.username, zone.password, zone.api_url)
91 return client.zones.info()._info
92
93
94def _poll_zone(zone):
95 """Eventlet worker to poll a zone."""
96 logging.debug(_("Polling zone: %s") % zone.api_url)
97 try:
98 zone.update_metadata(_call_novaclient(zone))
99 except Exception, e:
100 zone.log_error(traceback.format_exc())
101
102
103class ZoneManager(object):
104 """Keeps the zone states updated."""
105 def __init__(self):
106 self.last_zone_db_check = datetime.min
107 self.zone_states = {}
108 self.green_pool = greenpool.GreenPool()
109
110 def get_zone_list(self):
111 """Return the list of zones we know about."""
112 return [zone.to_dict() for zone in self.zone_states.values()]
113
114 def _refresh_from_db(self, context):
115 """Make our zone state map match the db."""
116 # Add/update existing zones ...
117 zones = db.zone_get_all(context)
118 existing = self.zone_states.keys()
119 db_keys = []
120 for zone in zones:
121 db_keys.append(zone.id)
122 if zone.id not in existing:
123 self.zone_states[zone.id] = ZoneState()
124 self.zone_states[zone.id].update_credentials(zone)
125
126 # Cleanup zones removed from db ...
127 keys = self.zone_states.keys() # since we're deleting
128 for zone_id in keys:
129 if zone_id not in db_keys:
130 del self.zone_states[zone_id]
131
132 def _poll_zones(self, context):
133 """Try to connect to each child zone and get update."""
134 self.green_pool.imap(_poll_zone, self.zone_states.values())
135
136 def ping(self, context=None):
137 """Ping should be called periodically to update zone status."""
138 diff = datetime.now() - self.last_zone_db_check
139 if diff.seconds >= FLAGS.zone_db_check_interval:
140 logging.debug(_("Updating zone cache from db."))
141 self.last_zone_db_check = datetime.now()
142 self._refresh_from_db(context)
143 self._poll_zones(context)
0144
=== modified file 'nova/tests/api/openstack/test_zones.py'
--- nova/tests/api/openstack/test_zones.py 2011-02-28 19:44:17 +0000
+++ nova/tests/api/openstack/test_zones.py 2011-03-09 12:55:18 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010 OpenStack LLC.1# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.2# All Rights Reserved.
3#3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may4# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -24,6 +24,7 @@
24from nova import test24from nova import test
25from nova.api.openstack import zones25from nova.api.openstack import zones
26from nova.tests.api.openstack import fakes26from nova.tests.api.openstack import fakes
27from nova.scheduler import api
2728
2829
29FLAGS = flags.FLAGS30FLAGS = flags.FLAGS
@@ -31,7 +32,7 @@
3132
3233
33def zone_get(context, zone_id):34def zone_get(context, zone_id):
34 return dict(id=1, api_url='http://foo.com', username='bob',35 return dict(id=1, api_url='http://example.com', username='bob',
35 password='xxx')36 password='xxx')
3637
3738
@@ -42,7 +43,7 @@
4243
4344
44def zone_update(context, zone_id, values):45def zone_update(context, zone_id, values):
45 zone = dict(id=zone_id, api_url='http://foo.com', username='bob',46 zone = dict(id=zone_id, api_url='http://example.com', username='bob',
46 password='xxx')47 password='xxx')
47 zone.update(values)48 zone.update(values)
48 return zone49 return zone
@@ -52,12 +53,26 @@
52 pass53 pass
5354
5455
55def zone_get_all(context):56def zone_get_all_scheduler(*args):
56 return [57 return [
57 dict(id=1, api_url='http://foo.com', username='bob',58 dict(id=1, api_url='http://example.com', username='bob',
58 password='xxx'),59 password='xxx'),
59 dict(id=2, api_url='http://blah.com', username='alice',60 dict(id=2, api_url='http://example.org', username='alice',
60 password='qwerty')]61 password='qwerty')
62 ]
63
64
65def zone_get_all_scheduler_empty(*args):
66 return []
67
68
69def zone_get_all_db(context):
70 return [
71 dict(id=1, api_url='http://example.com', username='bob',
72 password='xxx'),
73 dict(id=2, api_url='http://example.org', username='alice',
74 password='qwerty')
75 ]
6176
6277
63class ZonesTest(test.TestCase):78class ZonesTest(test.TestCase):
@@ -74,7 +89,6 @@
74 FLAGS.allow_admin_api = True89 FLAGS.allow_admin_api = True
7590
76 self.stubs.Set(nova.db, 'zone_get', zone_get)91 self.stubs.Set(nova.db, 'zone_get', zone_get)
77 self.stubs.Set(nova.db, 'zone_get_all', zone_get_all)
78 self.stubs.Set(nova.db, 'zone_update', zone_update)92 self.stubs.Set(nova.db, 'zone_update', zone_update)
79 self.stubs.Set(nova.db, 'zone_create', zone_create)93 self.stubs.Set(nova.db, 'zone_create', zone_create)
80 self.stubs.Set(nova.db, 'zone_delete', zone_delete)94 self.stubs.Set(nova.db, 'zone_delete', zone_delete)
@@ -84,7 +98,19 @@
84 FLAGS.allow_admin_api = self.allow_admin98 FLAGS.allow_admin_api = self.allow_admin
85 super(ZonesTest, self).tearDown()99 super(ZonesTest, self).tearDown()
86100
87 def test_get_zone_list(self):101 def test_get_zone_list_scheduler(self):
102 self.stubs.Set(api.API, '_call_scheduler', zone_get_all_scheduler)
103 req = webob.Request.blank('/v1.0/zones')
104 res = req.get_response(fakes.wsgi_app())
105 res_dict = json.loads(res.body)
106
107 self.assertEqual(res.status_int, 200)
108 self.assertEqual(len(res_dict['zones']), 2)
109
110 def test_get_zone_list_db(self):
111 self.stubs.Set(api.API, '_call_scheduler',
112 zone_get_all_scheduler_empty)
113 self.stubs.Set(nova.db, 'zone_get_all', zone_get_all_db)
88 req = webob.Request.blank('/v1.0/zones')114 req = webob.Request.blank('/v1.0/zones')
89 res = req.get_response(fakes.wsgi_app())115 res = req.get_response(fakes.wsgi_app())
90 res_dict = json.loads(res.body)116 res_dict = json.loads(res.body)
@@ -98,7 +124,7 @@
98 res_dict = json.loads(res.body)124 res_dict = json.loads(res.body)
99125
100 self.assertEqual(res_dict['zone']['id'], 1)126 self.assertEqual(res_dict['zone']['id'], 1)
101 self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')127 self.assertEqual(res_dict['zone']['api_url'], 'http://example.com')
102 self.assertFalse('password' in res_dict['zone'])128 self.assertFalse('password' in res_dict['zone'])
103 self.assertEqual(res.status_int, 200)129 self.assertEqual(res.status_int, 200)
104130
@@ -109,7 +135,7 @@
109 self.assertEqual(res.status_int, 200)135 self.assertEqual(res.status_int, 200)
110136
111 def test_zone_create(self):137 def test_zone_create(self):
112 body = dict(zone=dict(api_url='http://blah.zoo', username='fred',138 body = dict(zone=dict(api_url='http://example.com', username='fred',
113 password='fubar'))139 password='fubar'))
114 req = webob.Request.blank('/v1.0/zones')140 req = webob.Request.blank('/v1.0/zones')
115 req.method = 'POST'141 req.method = 'POST'
@@ -120,7 +146,7 @@
120146
121 self.assertEqual(res.status_int, 200)147 self.assertEqual(res.status_int, 200)
122 self.assertEqual(res_dict['zone']['id'], 1)148 self.assertEqual(res_dict['zone']['id'], 1)
123 self.assertEqual(res_dict['zone']['api_url'], 'http://blah.zoo')149 self.assertEqual(res_dict['zone']['api_url'], 'http://example.com')
124 self.assertFalse('username' in res_dict['zone'])150 self.assertFalse('username' in res_dict['zone'])
125151
126 def test_zone_update(self):152 def test_zone_update(self):
@@ -134,5 +160,5 @@
134160
135 self.assertEqual(res.status_int, 200)161 self.assertEqual(res.status_int, 200)
136 self.assertEqual(res_dict['zone']['id'], 1)162 self.assertEqual(res_dict['zone']['id'], 1)
137 self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')163 self.assertEqual(res_dict['zone']['api_url'], 'http://example.com')
138 self.assertFalse('username' in res_dict['zone'])164 self.assertFalse('username' in res_dict['zone'])
139165
=== added file 'nova/tests/test_zones.py'
--- nova/tests/test_zones.py 1970-01-01 00:00:00 +0000
+++ nova/tests/test_zones.py 2011-03-09 12:55:18 +0000
@@ -0,0 +1,172 @@
1# Copyright 2010 United States Government as represented by the
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""
16Tests For ZoneManager
17"""
18
19import datetime
20import mox
21import novaclient
22
23from nova import context
24from nova import db
25from nova import flags
26from nova import service
27from nova import test
28from nova import rpc
29from nova import utils
30from nova.auth import manager as auth_manager
31from nova.scheduler import zone_manager
32
33FLAGS = flags.FLAGS
34
35
36class FakeZone:
37 """Represents a fake zone from the db"""
38 def __init__(self, *args, **kwargs):
39 for k, v in kwargs.iteritems():
40 setattr(self, k, v)
41
42
43def exploding_novaclient(zone):
44 """Used when we want to simulate a novaclient call failing."""
45 raise Exception("kaboom")
46
47
48class ZoneManagerTestCase(test.TestCase):
49 """Test case for zone manager"""
50 def test_ping(self):
51 zm = zone_manager.ZoneManager()
52 self.mox.StubOutWithMock(zm, '_refresh_from_db')
53 self.mox.StubOutWithMock(zm, '_poll_zones')
54 zm._refresh_from_db(mox.IgnoreArg())
55 zm._poll_zones(mox.IgnoreArg())
56
57 self.mox.ReplayAll()
58 zm.ping(None)
59 self.mox.VerifyAll()
60
61 def test_refresh_from_db_new(self):
62 zm = zone_manager.ZoneManager()
63
64 self.mox.StubOutWithMock(db, 'zone_get_all')
65 db.zone_get_all(mox.IgnoreArg()).AndReturn([
66 FakeZone(id=1, api_url='http://foo.com', username='user1',
67 password='pass1'),
68 ])
69
70 self.assertEquals(len(zm.zone_states), 0)
71
72 self.mox.ReplayAll()
73 zm._refresh_from_db(None)
74 self.mox.VerifyAll()
75
76 self.assertEquals(len(zm.zone_states), 1)
77 self.assertEquals(zm.zone_states[1].username, 'user1')
78
79 def test_refresh_from_db_replace_existing(self):
80 zm = zone_manager.ZoneManager()
81 zone_state = zone_manager.ZoneState()
82 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
83 username='user1', password='pass1'))
84 zm.zone_states[1] = zone_state
85
86 self.mox.StubOutWithMock(db, 'zone_get_all')
87 db.zone_get_all(mox.IgnoreArg()).AndReturn([
88 FakeZone(id=1, api_url='http://foo.com', username='user2',
89 password='pass2'),
90 ])
91
92 self.assertEquals(len(zm.zone_states), 1)
93
94 self.mox.ReplayAll()
95 zm._refresh_from_db(None)
96 self.mox.VerifyAll()
97
98 self.assertEquals(len(zm.zone_states), 1)
99 self.assertEquals(zm.zone_states[1].username, 'user2')
100
101 def test_refresh_from_db_missing(self):
102 zm = zone_manager.ZoneManager()
103 zone_state = zone_manager.ZoneState()
104 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
105 username='user1', password='pass1'))
106 zm.zone_states[1] = zone_state
107
108 self.mox.StubOutWithMock(db, 'zone_get_all')
109 db.zone_get_all(mox.IgnoreArg()).AndReturn([])
110
111 self.assertEquals(len(zm.zone_states), 1)
112
113 self.mox.ReplayAll()
114 zm._refresh_from_db(None)
115 self.mox.VerifyAll()
116
117 self.assertEquals(len(zm.zone_states), 0)
118
119 def test_refresh_from_db_add_and_delete(self):
120 zm = zone_manager.ZoneManager()
121 zone_state = zone_manager.ZoneState()
122 zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
123 username='user1', password='pass1'))
124 zm.zone_states[1] = zone_state
125
126 self.mox.StubOutWithMock(db, 'zone_get_all')
127
128 db.zone_get_all(mox.IgnoreArg()).AndReturn([
129 FakeZone(id=2, api_url='http://foo.com', username='user2',
130 password='pass2'),
131 ])
132 self.assertEquals(len(zm.zone_states), 1)
133
134 self.mox.ReplayAll()
135 zm._refresh_from_db(None)
136 self.mox.VerifyAll()
137
138 self.assertEquals(len(zm.zone_states), 1)
139 self.assertEquals(zm.zone_states[2].username, 'user2')
140
141 def test_poll_zone(self):
142 self.mox.StubOutWithMock(zone_manager, '_call_novaclient')
143 zone_manager._call_novaclient(mox.IgnoreArg()).AndReturn(
144 dict(name='zohan', capabilities='hairdresser'))
145
146 zone_state = zone_manager.ZoneState()
147 zone_state.update_credentials(FakeZone(id=2,
148 api_url='http://foo.com', username='user2',
149 password='pass2'))
150 zone_state.attempt = 1
151
152 self.mox.ReplayAll()
153 zone_manager._poll_zone(zone_state)
154 self.mox.VerifyAll()
155 self.assertEquals(zone_state.attempt, 0)
156 self.assertEquals(zone_state.name, 'zohan')
157
158 def test_poll_zone_fails(self):
159 self.stubs.Set(zone_manager, "_call_novaclient", exploding_novaclient)
160
161 zone_state = zone_manager.ZoneState()
162 zone_state.update_credentials(FakeZone(id=2,
163 api_url='http://foo.com', username='user2',
164 password='pass2'))
165 zone_state.attempt = FLAGS.zone_failures_to_offline - 1
166
167 self.mox.ReplayAll()
168 zone_manager._poll_zone(zone_state)
169 self.mox.VerifyAll()
170 self.assertEquals(zone_state.attempt, 3)
171 self.assertFalse(zone_state.is_active)
172 self.assertEquals(zone_state.name, None)
0173
=== modified file 'tools/pip-requires'
--- tools/pip-requires 2011-01-23 20:52:09 +0000
+++ tools/pip-requires 2011-03-09 12:55:18 +0000
@@ -10,6 +10,7 @@
10carrot==0.10.510carrot==0.10.5
11eventlet==0.9.1211eventlet==0.9.12
12lockfile==0.812lockfile==0.8
13python-novaclient==2.3
13python-daemon==1.5.514python-daemon==1.5.5
14python-gflags==1.315python-gflags==1.3
15redis==2.0.016redis==2.0.0