Merge lp:~sandy-walsh/nova/zones3 into lp:~hudson-openstack/nova/trunk
- zones3
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~sandy-walsh/nova/zones3 |
Merge into: | lp:~hudson-openstack/nova/trunk |
Prerequisite: | lp:~sandy-walsh/nova/zones2 |
Diff against target: |
681 lines (+292/-71) 17 files modified
nova/api/openstack/zones.py (+12/-4) nova/compute/manager.py (+3/-2) nova/flags.py (+3/-2) nova/manager.py (+30/-1) nova/network/manager.py (+3/-2) nova/rpc.py (+59/-18) nova/scheduler/api.py (+37/-22) nova/scheduler/driver.py (+7/-0) nova/scheduler/manager.py (+13/-1) nova/scheduler/zone_manager.py (+32/-0) nova/service.py (+8/-2) nova/tests/api/openstack/test_zones.py (+26/-3) nova/tests/test_rpc.py (+2/-2) nova/tests/test_service.py (+19/-9) nova/tests/test_test.py (+1/-1) nova/tests/test_zones.py (+34/-0) nova/volume/manager.py (+3/-2) |
To merge this branch: | bzr merge lp:~sandy-walsh/nova/zones3 |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rick Harris (community) | Approve | ||
Matt Dietz (community) | Approve | ||
Joshua McKenty (community) | Needs Information | ||
Todd Willey (community) | Approve | ||
Review via email: mp+52565@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-03-24.
Commit message
Aggregates capabilities from Compute, Network, Volume to the ZoneManager in Scheduler.
Description of the change
This branch adds a Fanout (broadcast) Queue to all services. This lets anyone talk to all services of a particular type without having to iterate through each.
Compute, Volume and Network now derive from nova.SchedulerD
These capabilities are stored in each Scheduler Zone Manager and available via the Scheduler.API
The OS API '/zones/info' call now returns the aggregated (min, max) values of each of the service capabilities: http://
To integration test this, simply fire up another scheduler (or more), add some capabilities to Compute, Network or Volume via the services update_
Sandy Walsh (sandy-walsh) wrote : | # |
Todd, thanks for the feedback.
I think the meaningful values for caps will come from the distributed scheduler effort. That branch really needs to drive what is required from the services.
RE flags: do you mean "x=1,x=
RE storing the capabilities: yes, storing it in the db is a possibility. Since many capabilities are not static (disk remaining, bandwidth usage, cpu, etc) I had concerns about the frequency of the updates and the added load on the db. But this could use the same periodic storage scheme as the fanout mechanism.
Sandy Walsh (sandy-walsh) wrote : | # |
Oh, perhaps the multi value keys where "x:1;2;3, y;9;8;7" ? That would be an easy change. Perhaps move the parser into utils.py as well?
Todd Willey (xtoddx) wrote : | # |
From nova/flags.py:
flags.DEFINE_
I think that is a good pattern to follow. Your argument ends up looking like
--zone_
You'll end up with the list
['hypervisor=
You're definitely treating it as a list by splitting and iterating over it, so DEFINE_list seems reasonable. Based on your comments it looks like you might specify multiple values for any individual capability, in which case using a comma would break the flag list, maybe using a colon as the item separator would be reasonable?
Eric Day (eday) wrote : | # |
8: Ahh! How did nova.db get into nova.api? I must have missed this in the last branch. It would be nice to abstract this in nova/scheduler or a nova/zones/ package.
Should SchedulerDepend
As for using a DB to store this data (Todd's comment), we need to move away from compute/
Sandy Walsh (sandy-walsh) wrote : | # |
eric, agree on moving SchedulerDepend
Todd, colon is bad for URI's. I'll mess around the the List option.
Sandy Walsh (sandy-walsh) wrote : | # |
Todd, how about x=1;2;3, y=9;8;7 .. that way we can use DEFINE_list and have multi-values?
Sandy Walsh (sandy-walsh) wrote : | # |
All fixed up as requested ... thanks again for the feedback!
Todd Willey (xtoddx) wrote : | # |
I'm on board with that style of flag.
Joshua McKenty (joshua-mckenty) wrote : | # |
Typo on 79 (missing i).
Seems like service_name should be a class property rather than an instance property, any reason we can't do it this way? Then the super __init__ call doesn't need an extra arg.
I think TopicConsumer was the last of my code from the first nova-hacking weekend - I'm glad to see it go.
Log message on 231 seems wrong - is it actually "writing" at that point, or just initing?
I like how you've dropped _call_scheduler out of the API class - but do we even NEED that class any more? The rest of the class methods could be dropped down to functions as well.
467 - can we call this zone_capabilities? Caps seems like a pseudonym for "limits".
Looks like we need a simple test of the fanout in test_rpc.py.
Doesn't seem like there's any way to remove a host from service capabilities once it's been added - is this necessary?
Sandy Walsh (sandy-walsh) wrote : | # |
Joshua, great feedback ... thanks for that. All good suggestions. I'll get on them.
Hmm, I was going to add something to decay the hosts after time. I may have forgotten that, lemme see.
If not, I can easily add it to Zones4 (coming soon).
If you'd like to see the direction this is heading have a look here (WIP):
https:/
Cheers!
Sandy Walsh (sandy-walsh) wrote : | # |
Changes made ... the fanout test should be an integration test vs. a unit test. We'll need to revisit that once we get the integration test framework ironed out.
Rick Harris (rconradharris) wrote : | # |
Overall this patch looks really good Sandy. Nice tests, and some really
helpful comments in there (as usual).
Generally speaking, I think a few whitespaces cleanups would make a make this
patch even better (in particular lining up arg-lists). Not a huge deal,
obviously, but worth mentioning.
In terms of design and implementation, I think it looks good. This matches up
with what we whiteboarded (AFAICT), so I'm all for it.
Specifics
=========
> 10 +from nova import log as logging
Imported but not used.
> 35 + for cap in caps:
> 36 + key_values = cap.split('=')
> 37 + zone[key_values[0]] = key_values[1]
Might be slightly easier to read with tuple-unpacking (also, passing 1 to
split in case the value has a '=' in it):
for cap in caps:
key, value = cap.split('=', 1)
zone[key] = value
> 62 + super(ComputeMa
> 63 + *args, **kwargs)
Might be better if args aligned:
super(
> 77 +DEFINE_
> 78 + ['hypervisor=
> 79 + 'Key/Multi-value list representng capabilities of this zone')
Same as above:
DEFINE_
> 88 +from nova import log as logging
Might be worth using the LOG pattern here to get more specific logging:
LOG = logging.
> 106 + update_
> 107 + def __init__(self, host=None, db_driver=None, service_
Spacing. Might be better as:
"""
def __init__(self, host=None, db_driver=None, service_
> 313 + params=
Couple spaces ---> thataway (to line up with open paren +1)
> 209 + LOG.info(_("Created '%(exchange)s' fanout exchange "
> 210 + "with '%(key)s' routing key"),
> 211 + dict(exchange=
Could use a bit of formatting work:
LOG.
Although, this might be personal preference on my part, so, if that change
doesn't look good to you, please feel free to ignore! :-)
> 638 + svc10_a=(99, 99), svc10_b=(99, 99)))
Spacing, a couple of spaces <---- thattaway.
Matt Dietz (cerberus) wrote : | # |
I don't see anything glaring ;-)
lgtm. Just waiting on Rick
Sandy Walsh (sandy-walsh) wrote : | # |
Thanks for the feedback Rick ... I agree with your suggestions. Doing the code reviews on glance and seeing the work you and jay have been doing might make me change my style :)
Stay tuned.
Rick Harris (rconradharris) wrote : | # |
Looks great, thanks for the fix-ups, Sandy!
OpenStack Infra (hudson-openstack) wrote : | # |
No proposals found for merge of lp:~sandy-walsh/nova/zones2 into lp:nova.
Unmerged revisions
Preview Diff
1 | === modified file 'nova/api/openstack/zones.py' | |||
2 | --- nova/api/openstack/zones.py 2011-03-11 19:49:32 +0000 | |||
3 | +++ nova/api/openstack/zones.py 2011-03-23 19:38:07 +0000 | |||
4 | @@ -15,9 +15,9 @@ | |||
5 | 15 | 15 | ||
6 | 16 | import common | 16 | import common |
7 | 17 | 17 | ||
8 | 18 | from nova import db | ||
9 | 18 | from nova import flags | 19 | from nova import flags |
10 | 19 | from nova import wsgi | 20 | from nova import wsgi |
11 | 20 | from nova import db | ||
12 | 21 | from nova.scheduler import api | 21 | from nova.scheduler import api |
13 | 22 | 22 | ||
14 | 23 | 23 | ||
15 | @@ -52,7 +52,7 @@ | |||
16 | 52 | """Return all zones in brief""" | 52 | """Return all zones in brief""" |
17 | 53 | # Ask the ZoneManager in the Scheduler for most recent data, | 53 | # Ask the ZoneManager in the Scheduler for most recent data, |
18 | 54 | # or fall-back to the database ... | 54 | # or fall-back to the database ... |
20 | 55 | items = api.API().get_zone_list(req.environ['nova.context']) | 55 | items = api.get_zone_list(req.environ['nova.context']) |
21 | 56 | if not items: | 56 | if not items: |
22 | 57 | items = db.zone_get_all(req.environ['nova.context']) | 57 | items = db.zone_get_all(req.environ['nova.context']) |
23 | 58 | 58 | ||
24 | @@ -67,8 +67,16 @@ | |||
25 | 67 | 67 | ||
26 | 68 | def info(self, req): | 68 | def info(self, req): |
27 | 69 | """Return name and capabilities for this zone.""" | 69 | """Return name and capabilities for this zone.""" |
30 | 70 | return dict(zone=dict(name=FLAGS.zone_name, | 70 | items = api.get_zone_capabilities(req.environ['nova.context']) |
31 | 71 | capabilities=FLAGS.zone_capabilities)) | 71 | |
32 | 72 | zone = dict(name=FLAGS.zone_name) | ||
33 | 73 | caps = FLAGS.zone_capabilities | ||
34 | 74 | for cap in caps: | ||
35 | 75 | key, value = cap.split('=') | ||
36 | 76 | zone[key] = value | ||
37 | 77 | for item, (min_value, max_value) in items.iteritems(): | ||
38 | 78 | zone[item] = "%s,%s" % (min_value, max_value) | ||
39 | 79 | return dict(zone=zone) | ||
40 | 72 | 80 | ||
41 | 73 | def show(self, req, id): | 81 | def show(self, req, id): |
42 | 74 | """Return data about the given zone id""" | 82 | """Return data about the given zone id""" |
43 | 75 | 83 | ||
44 | === modified file 'nova/compute/manager.py' | |||
45 | --- nova/compute/manager.py 2011-03-21 15:41:03 +0000 | |||
46 | +++ nova/compute/manager.py 2011-03-23 19:38:07 +0000 | |||
47 | @@ -105,7 +105,7 @@ | |||
48 | 105 | return decorated_function | 105 | return decorated_function |
49 | 106 | 106 | ||
50 | 107 | 107 | ||
52 | 108 | class ComputeManager(manager.Manager): | 108 | class ComputeManager(manager.SchedulerDependentManager): |
53 | 109 | 109 | ||
54 | 110 | """Manages the running instances from creation to destruction.""" | 110 | """Manages the running instances from creation to destruction.""" |
55 | 111 | 111 | ||
56 | @@ -124,7 +124,8 @@ | |||
57 | 124 | 124 | ||
58 | 125 | self.network_manager = utils.import_object(FLAGS.network_manager) | 125 | self.network_manager = utils.import_object(FLAGS.network_manager) |
59 | 126 | self.volume_manager = utils.import_object(FLAGS.volume_manager) | 126 | self.volume_manager = utils.import_object(FLAGS.volume_manager) |
61 | 127 | super(ComputeManager, self).__init__(*args, **kwargs) | 127 | super(ComputeManager, self).__init__(service_name="compute", |
62 | 128 | *args, **kwargs) | ||
63 | 128 | 129 | ||
64 | 129 | def init_host(self): | 130 | def init_host(self): |
65 | 130 | """Do any initialization that needs to be run if this is a | 131 | """Do any initialization that needs to be run if this is a |
66 | 131 | 132 | ||
67 | === modified file 'nova/flags.py' | |||
68 | --- nova/flags.py 2011-03-18 11:35:00 +0000 | |||
69 | +++ nova/flags.py 2011-03-23 19:38:07 +0000 | |||
70 | @@ -358,5 +358,6 @@ | |||
71 | 358 | 'availability zone of this node') | 358 | 'availability zone of this node') |
72 | 359 | 359 | ||
73 | 360 | DEFINE_string('zone_name', 'nova', 'name of this zone') | 360 | DEFINE_string('zone_name', 'nova', 'name of this zone') |
76 | 361 | DEFINE_string('zone_capabilities', 'kypervisor:xenserver;os:linux', | 361 | DEFINE_list('zone_capabilities', |
77 | 362 | 'Key/Value tags which represent capabilities of this zone') | 362 | ['hypervisor=xenserver;kvm', 'os=linux;windows'], |
78 | 363 | 'Key/Multi-value list representng capabilities of this zone') | ||
79 | 363 | 364 | ||
80 | === modified file 'nova/manager.py' | |||
81 | --- nova/manager.py 2010-12-15 00:05:39 +0000 | |||
82 | +++ nova/manager.py 2011-03-23 19:38:07 +0000 | |||
83 | @@ -53,11 +53,14 @@ | |||
84 | 53 | 53 | ||
85 | 54 | from nova import utils | 54 | from nova import utils |
86 | 55 | from nova import flags | 55 | from nova import flags |
87 | 56 | from nova import log as logging | ||
88 | 56 | from nova.db import base | 57 | from nova.db import base |
90 | 57 | 58 | from nova.scheduler import api | |
91 | 58 | 59 | ||
92 | 59 | FLAGS = flags.FLAGS | 60 | FLAGS = flags.FLAGS |
93 | 60 | 61 | ||
94 | 62 | LOG = logging.getLogger('nova.manager') | ||
95 | 63 | |||
96 | 61 | 64 | ||
97 | 62 | class Manager(base.Base): | 65 | class Manager(base.Base): |
98 | 63 | def __init__(self, host=None, db_driver=None): | 66 | def __init__(self, host=None, db_driver=None): |
99 | @@ -74,3 +77,29 @@ | |||
100 | 74 | """Do any initialization that needs to be run if this is a standalone | 77 | """Do any initialization that needs to be run if this is a standalone |
101 | 75 | service. Child classes should override this method.""" | 78 | service. Child classes should override this method.""" |
102 | 76 | pass | 79 | pass |
103 | 80 | |||
104 | 81 | |||
105 | 82 | class SchedulerDependentManager(Manager): | ||
106 | 83 | """Periodically send capability updates to the Scheduler services. | ||
107 | 84 | Services that need to update the Scheduler of their capabilities | ||
108 | 85 | should derive from this class. Otherwise they can derive from | ||
109 | 86 | manager.Manager directly. Updates are only sent after | ||
110 | 87 | update_service_capabilities is called with non-None values.""" | ||
111 | 88 | |||
112 | 89 | def __init__(self, host=None, db_driver=None, service_name="undefined"): | ||
113 | 90 | self.last_capabilities = None | ||
114 | 91 | self.service_name = service_name | ||
115 | 92 | super(SchedulerDependentManager, self).__init__(host, db_driver) | ||
116 | 93 | |||
117 | 94 | def update_service_capabilities(self, capabilities): | ||
118 | 95 | """Remember these capabilities to send on next periodic update.""" | ||
119 | 96 | self.last_capabilities = capabilities | ||
120 | 97 | |||
121 | 98 | def periodic_tasks(self, context=None): | ||
122 | 99 | """Pass data back to the scheduler at a periodic interval""" | ||
123 | 100 | if self.last_capabilities: | ||
124 | 101 | LOG.debug(_("Notifying Schedulers of capabilities ...")) | ||
125 | 102 | api.update_service_capabilities(context, self.service_name, | ||
126 | 103 | self.host, self.last_capabilities) | ||
127 | 104 | |||
128 | 105 | super(SchedulerDependentManager, self).periodic_tasks(context) | ||
129 | 77 | 106 | ||
130 | === modified file 'nova/network/manager.py' | |||
131 | --- nova/network/manager.py 2011-03-23 05:29:32 +0000 | |||
132 | +++ nova/network/manager.py 2011-03-23 19:38:07 +0000 | |||
133 | @@ -105,7 +105,7 @@ | |||
134 | 105 | pass | 105 | pass |
135 | 106 | 106 | ||
136 | 107 | 107 | ||
138 | 108 | class NetworkManager(manager.Manager): | 108 | class NetworkManager(manager.SchedulerDependentManager): |
139 | 109 | """Implements common network manager functionality. | 109 | """Implements common network manager functionality. |
140 | 110 | 110 | ||
141 | 111 | This class must be subclassed to support specific topologies. | 111 | This class must be subclassed to support specific topologies. |
142 | @@ -116,7 +116,8 @@ | |||
143 | 116 | if not network_driver: | 116 | if not network_driver: |
144 | 117 | network_driver = FLAGS.network_driver | 117 | network_driver = FLAGS.network_driver |
145 | 118 | self.driver = utils.import_object(network_driver) | 118 | self.driver = utils.import_object(network_driver) |
147 | 119 | super(NetworkManager, self).__init__(*args, **kwargs) | 119 | super(NetworkManager, self).__init__(service_name='network', |
148 | 120 | *args, **kwargs) | ||
149 | 120 | 121 | ||
150 | 121 | def init_host(self): | 122 | def init_host(self): |
151 | 122 | """Do any initialization that needs to be run if this is a | 123 | """Do any initialization that needs to be run if this is a |
152 | 123 | 124 | ||
153 | === modified file 'nova/rpc.py' | |||
154 | --- nova/rpc.py 2011-03-18 13:56:05 +0000 | |||
155 | +++ nova/rpc.py 2011-03-23 19:38:07 +0000 | |||
156 | @@ -137,24 +137,7 @@ | |||
157 | 137 | return timer | 137 | return timer |
158 | 138 | 138 | ||
159 | 139 | 139 | ||
178 | 140 | class Publisher(messaging.Publisher): | 140 | class AdapterConsumer(Consumer): |
161 | 141 | """Publisher base class""" | ||
162 | 142 | pass | ||
163 | 143 | |||
164 | 144 | |||
165 | 145 | class TopicConsumer(Consumer): | ||
166 | 146 | """Consumes messages on a specific topic""" | ||
167 | 147 | exchange_type = "topic" | ||
168 | 148 | |||
169 | 149 | def __init__(self, connection=None, topic="broadcast"): | ||
170 | 150 | self.queue = topic | ||
171 | 151 | self.routing_key = topic | ||
172 | 152 | self.exchange = FLAGS.control_exchange | ||
173 | 153 | self.durable = False | ||
174 | 154 | super(TopicConsumer, self).__init__(connection=connection) | ||
175 | 155 | |||
176 | 156 | |||
177 | 157 | class AdapterConsumer(TopicConsumer): | ||
179 | 158 | """Calls methods on a proxy object based on method and args""" | 141 | """Calls methods on a proxy object based on method and args""" |
180 | 159 | def __init__(self, connection=None, topic="broadcast", proxy=None): | 142 | def __init__(self, connection=None, topic="broadcast", proxy=None): |
181 | 160 | LOG.debug(_('Initing the Adapter Consumer for %s') % topic) | 143 | LOG.debug(_('Initing the Adapter Consumer for %s') % topic) |
182 | @@ -207,6 +190,41 @@ | |||
183 | 207 | return | 190 | return |
184 | 208 | 191 | ||
185 | 209 | 192 | ||
186 | 193 | class Publisher(messaging.Publisher): | ||
187 | 194 | """Publisher base class""" | ||
188 | 195 | pass | ||
189 | 196 | |||
190 | 197 | |||
191 | 198 | class TopicAdapterConsumer(AdapterConsumer): | ||
192 | 199 | """Consumes messages on a specific topic""" | ||
193 | 200 | exchange_type = "topic" | ||
194 | 201 | |||
195 | 202 | def __init__(self, connection=None, topic="broadcast", proxy=None): | ||
196 | 203 | self.queue = topic | ||
197 | 204 | self.routing_key = topic | ||
198 | 205 | self.exchange = FLAGS.control_exchange | ||
199 | 206 | self.durable = False | ||
200 | 207 | super(TopicAdapterConsumer, self).__init__(connection=connection, | ||
201 | 208 | topic=topic, proxy=proxy) | ||
202 | 209 | |||
203 | 210 | |||
204 | 211 | class FanoutAdapterConsumer(AdapterConsumer): | ||
205 | 212 | """Consumes messages from a fanout exchange""" | ||
206 | 213 | exchange_type = "fanout" | ||
207 | 214 | |||
208 | 215 | def __init__(self, connection=None, topic="broadcast", proxy=None): | ||
209 | 216 | self.exchange = "%s_fanout" % topic | ||
210 | 217 | self.routing_key = topic | ||
211 | 218 | unique = uuid.uuid4().hex | ||
212 | 219 | self.queue = "%s_fanout_%s" % (topic, unique) | ||
213 | 220 | self.durable = False | ||
214 | 221 | LOG.info(_("Created '%(exchange)s' fanout exchange " | ||
215 | 222 | "with '%(key)s' routing key"), | ||
216 | 223 | dict(exchange=self.exchange, key=self.routing_key)) | ||
217 | 224 | super(FanoutAdapterConsumer, self).__init__(connection=connection, | ||
218 | 225 | topic=topic, proxy=proxy) | ||
219 | 226 | |||
220 | 227 | |||
221 | 210 | class TopicPublisher(Publisher): | 228 | class TopicPublisher(Publisher): |
222 | 211 | """Publishes messages on a specific topic""" | 229 | """Publishes messages on a specific topic""" |
223 | 212 | exchange_type = "topic" | 230 | exchange_type = "topic" |
224 | @@ -218,6 +236,19 @@ | |||
225 | 218 | super(TopicPublisher, self).__init__(connection=connection) | 236 | super(TopicPublisher, self).__init__(connection=connection) |
226 | 219 | 237 | ||
227 | 220 | 238 | ||
228 | 239 | class FanoutPublisher(Publisher): | ||
229 | 240 | """Publishes messages to a fanout exchange.""" | ||
230 | 241 | exchange_type = "fanout" | ||
231 | 242 | |||
232 | 243 | def __init__(self, topic, connection=None): | ||
233 | 244 | self.exchange = "%s_fanout" % topic | ||
234 | 245 | self.queue = "%s_fanout" % topic | ||
235 | 246 | self.durable = False | ||
236 | 247 | LOG.info(_("Creating '%(exchange)s' fanout exchange"), | ||
237 | 248 | dict(exchange=self.exchange)) | ||
238 | 249 | super(FanoutPublisher, self).__init__(connection=connection) | ||
239 | 250 | |||
240 | 251 | |||
241 | 221 | class DirectConsumer(Consumer): | 252 | class DirectConsumer(Consumer): |
242 | 222 | """Consumes messages directly on a channel specified by msg_id""" | 253 | """Consumes messages directly on a channel specified by msg_id""" |
243 | 223 | exchange_type = "direct" | 254 | exchange_type = "direct" |
244 | @@ -360,6 +391,16 @@ | |||
245 | 360 | publisher.close() | 391 | publisher.close() |
246 | 361 | 392 | ||
247 | 362 | 393 | ||
248 | 394 | def fanout_cast(context, topic, msg): | ||
249 | 395 | """Sends a message on a fanout exchange without waiting for a response""" | ||
250 | 396 | LOG.debug(_("Making asynchronous fanout cast...")) | ||
251 | 397 | _pack_context(msg, context) | ||
252 | 398 | conn = Connection.instance() | ||
253 | 399 | publisher = FanoutPublisher(topic, connection=conn) | ||
254 | 400 | publisher.send(msg) | ||
255 | 401 | publisher.close() | ||
256 | 402 | |||
257 | 403 | |||
258 | 363 | def generic_response(message_data, message): | 404 | def generic_response(message_data, message): |
259 | 364 | """Logs a result and exits""" | 405 | """Logs a result and exits""" |
260 | 365 | LOG.debug(_('response %s'), message_data) | 406 | LOG.debug(_('response %s'), message_data) |
261 | 366 | 407 | ||
262 | === modified file 'nova/scheduler/api.py' | |||
263 | --- nova/scheduler/api.py 2011-02-25 21:40:15 +0000 | |||
264 | +++ nova/scheduler/api.py 2011-03-23 19:38:07 +0000 | |||
265 | @@ -25,25 +25,40 @@ | |||
266 | 25 | LOG = logging.getLogger('nova.scheduler.api') | 25 | LOG = logging.getLogger('nova.scheduler.api') |
267 | 26 | 26 | ||
268 | 27 | 27 | ||
291 | 28 | class API(object): | 28 | def _call_scheduler(method, context, params=None): |
292 | 29 | """API for interacting with the scheduler.""" | 29 | """Generic handler for RPC calls to the scheduler. |
293 | 30 | 30 | ||
294 | 31 | def _call_scheduler(self, method, context, params=None): | 31 | :param params: Optional dictionary of arguments to be passed to the |
295 | 32 | """Generic handler for RPC calls to the scheduler. | 32 | scheduler worker |
296 | 33 | 33 | ||
297 | 34 | :param params: Optional dictionary of arguments to be passed to the | 34 | :retval: Result returned by scheduler worker |
298 | 35 | scheduler worker | 35 | """ |
299 | 36 | 36 | if not params: | |
300 | 37 | :retval: Result returned by scheduler worker | 37 | params = {} |
301 | 38 | """ | 38 | queue = FLAGS.scheduler_topic |
302 | 39 | if not params: | 39 | kwargs = {'method': method, 'args': params} |
303 | 40 | params = {} | 40 | return rpc.call(context, queue, kwargs) |
304 | 41 | queue = FLAGS.scheduler_topic | 41 | |
305 | 42 | kwargs = {'method': method, 'args': params} | 42 | |
306 | 43 | return rpc.call(context, queue, kwargs) | 43 | def get_zone_list(context): |
307 | 44 | 44 | """Return a list of zones assoicated with this zone.""" | |
308 | 45 | def get_zone_list(self, context): | 45 | items = _call_scheduler('get_zone_list', context) |
309 | 46 | items = self._call_scheduler('get_zone_list', context) | 46 | for item in items: |
310 | 47 | for item in items: | 47 | item['api_url'] = item['api_url'].replace('\\/', '/') |
311 | 48 | item['api_url'] = item['api_url'].replace('\\/', '/') | 48 | return items |
312 | 49 | return items | 49 | |
313 | 50 | |||
314 | 51 | def get_zone_capabilities(context, service=None): | ||
315 | 52 | """Returns a dict of key, value capabilities for this zone, | ||
316 | 53 | or for a particular class of services running in this zone.""" | ||
317 | 54 | return _call_scheduler('get_zone_capabilities', context=context, | ||
318 | 55 | params=dict(service=service)) | ||
319 | 56 | |||
320 | 57 | |||
321 | 58 | def update_service_capabilities(context, service_name, host, capabilities): | ||
322 | 59 | """Send an update to all the scheduler services informing them | ||
323 | 60 | of the capabilities of this service.""" | ||
324 | 61 | kwargs = dict(method='update_service_capabilities', | ||
325 | 62 | args=dict(service_name=service_name, host=host, | ||
326 | 63 | capabilities=capabilities)) | ||
327 | 64 | return rpc.fanout_cast(context, 'scheduler', kwargs) | ||
328 | 50 | 65 | ||
329 | === modified file 'nova/scheduler/driver.py' | |||
330 | --- nova/scheduler/driver.py 2011-03-10 04:30:52 +0000 | |||
331 | +++ nova/scheduler/driver.py 2011-03-23 19:38:07 +0000 | |||
332 | @@ -49,6 +49,13 @@ | |||
333 | 49 | class Scheduler(object): | 49 | class Scheduler(object): |
334 | 50 | """The base class that all Scheduler clases should inherit from.""" | 50 | """The base class that all Scheduler clases should inherit from.""" |
335 | 51 | 51 | ||
336 | 52 | def __init__(self): | ||
337 | 53 | self.zone_manager = None | ||
338 | 54 | |||
339 | 55 | def set_zone_manager(self, zone_manager): | ||
340 | 56 | """Called by the Scheduler Service to supply a ZoneManager.""" | ||
341 | 57 | self.zone_manager = zone_manager | ||
342 | 58 | |||
343 | 52 | @staticmethod | 59 | @staticmethod |
344 | 53 | def service_is_up(service): | 60 | def service_is_up(service): |
345 | 54 | """Check whether a service is up based on last heartbeat.""" | 61 | """Check whether a service is up based on last heartbeat.""" |
346 | 55 | 62 | ||
347 | === modified file 'nova/scheduler/manager.py' | |||
348 | --- nova/scheduler/manager.py 2011-03-14 17:59:41 +0000 | |||
349 | +++ nova/scheduler/manager.py 2011-03-23 19:38:07 +0000 | |||
350 | @@ -41,10 +41,11 @@ | |||
351 | 41 | class SchedulerManager(manager.Manager): | 41 | class SchedulerManager(manager.Manager): |
352 | 42 | """Chooses a host to run instances on.""" | 42 | """Chooses a host to run instances on.""" |
353 | 43 | def __init__(self, scheduler_driver=None, *args, **kwargs): | 43 | def __init__(self, scheduler_driver=None, *args, **kwargs): |
354 | 44 | self.zone_manager = zone_manager.ZoneManager() | ||
355 | 44 | if not scheduler_driver: | 45 | if not scheduler_driver: |
356 | 45 | scheduler_driver = FLAGS.scheduler_driver | 46 | scheduler_driver = FLAGS.scheduler_driver |
357 | 46 | self.driver = utils.import_object(scheduler_driver) | 47 | self.driver = utils.import_object(scheduler_driver) |
359 | 47 | self.zone_manager = zone_manager.ZoneManager() | 48 | self.driver.set_zone_manager(self.zone_manager) |
360 | 48 | super(SchedulerManager, self).__init__(*args, **kwargs) | 49 | super(SchedulerManager, self).__init__(*args, **kwargs) |
361 | 49 | 50 | ||
362 | 50 | def __getattr__(self, key): | 51 | def __getattr__(self, key): |
363 | @@ -59,6 +60,17 @@ | |||
364 | 59 | """Get a list of zones from the ZoneManager.""" | 60 | """Get a list of zones from the ZoneManager.""" |
365 | 60 | return self.zone_manager.get_zone_list() | 61 | return self.zone_manager.get_zone_list() |
366 | 61 | 62 | ||
367 | 63 | def get_zone_capabilities(self, context=None, service=None): | ||
368 | 64 | """Get the normalized set of capabilites for this zone, | ||
369 | 65 | or for a particular service.""" | ||
370 | 66 | return self.zone_manager.get_zone_capabilities(context, service) | ||
371 | 67 | |||
372 | 68 | def update_service_capabilities(self, context=None, service_name=None, | ||
373 | 69 | host=None, capabilities={}): | ||
374 | 70 | """Process a capability update from a service node.""" | ||
375 | 71 | self.zone_manager.update_service_capabilities(service_name, | ||
376 | 72 | host, capabilities) | ||
377 | 73 | |||
378 | 62 | def _schedule(self, method, context, topic, *args, **kwargs): | 74 | def _schedule(self, method, context, topic, *args, **kwargs): |
379 | 63 | """Tries to call schedule_* method on the driver to retrieve host. | 75 | """Tries to call schedule_* method on the driver to retrieve host. |
380 | 64 | 76 | ||
381 | 65 | 77 | ||
382 | === modified file 'nova/scheduler/zone_manager.py' | |||
383 | --- nova/scheduler/zone_manager.py 2011-03-03 14:55:02 +0000 | |||
384 | +++ nova/scheduler/zone_manager.py 2011-03-23 19:38:07 +0000 | |||
385 | @@ -105,12 +105,36 @@ | |||
386 | 105 | def __init__(self): | 105 | def __init__(self): |
387 | 106 | self.last_zone_db_check = datetime.min | 106 | self.last_zone_db_check = datetime.min |
388 | 107 | self.zone_states = {} | 107 | self.zone_states = {} |
389 | 108 | self.service_states = {} # { <service> : { <host> : { cap k : v }}} | ||
390 | 108 | self.green_pool = greenpool.GreenPool() | 109 | self.green_pool = greenpool.GreenPool() |
391 | 109 | 110 | ||
392 | 110 | def get_zone_list(self): | 111 | def get_zone_list(self): |
393 | 111 | """Return the list of zones we know about.""" | 112 | """Return the list of zones we know about.""" |
394 | 112 | return [zone.to_dict() for zone in self.zone_states.values()] | 113 | return [zone.to_dict() for zone in self.zone_states.values()] |
395 | 113 | 114 | ||
396 | 115 | def get_zone_capabilities(self, context, service=None): | ||
397 | 116 | """Roll up all the individual host info to generic 'service' | ||
398 | 117 | capabilities. Each capability is aggregated into | ||
399 | 118 | <cap>_min and <cap>_max values.""" | ||
400 | 119 | service_dict = self.service_states | ||
401 | 120 | if service: | ||
402 | 121 | service_dict = {service: self.service_states.get(service, {})} | ||
403 | 122 | |||
404 | 123 | # TODO(sandy) - be smarter about fabricating this structure. | ||
405 | 124 | # But it's likely to change once we understand what the Best-Match | ||
406 | 125 | # code will need better. | ||
407 | 126 | combined = {} # { <service>_<cap> : (min, max), ... } | ||
408 | 127 | for service_name, host_dict in service_dict.iteritems(): | ||
409 | 128 | for host, caps_dict in host_dict.iteritems(): | ||
410 | 129 | for cap, value in caps_dict.iteritems(): | ||
411 | 130 | key = "%s_%s" % (service_name, cap) | ||
412 | 131 | min_value, max_value = combined.get(key, (value, value)) | ||
413 | 132 | min_value = min(min_value, value) | ||
414 | 133 | max_value = max(max_value, value) | ||
415 | 134 | combined[key] = (min_value, max_value) | ||
416 | 135 | |||
417 | 136 | return combined | ||
418 | 137 | |||
419 | 114 | def _refresh_from_db(self, context): | 138 | def _refresh_from_db(self, context): |
420 | 115 | """Make our zone state map match the db.""" | 139 | """Make our zone state map match the db.""" |
421 | 116 | # Add/update existing zones ... | 140 | # Add/update existing zones ... |
422 | @@ -141,3 +165,11 @@ | |||
423 | 141 | self.last_zone_db_check = datetime.now() | 165 | self.last_zone_db_check = datetime.now() |
424 | 142 | self._refresh_from_db(context) | 166 | self._refresh_from_db(context) |
425 | 143 | self._poll_zones(context) | 167 | self._poll_zones(context) |
426 | 168 | |||
427 | 169 | def update_service_capabilities(self, service_name, host, capabilities): | ||
428 | 170 | """Update the per-service capabilities based on this notification.""" | ||
429 | 171 | logging.debug(_("Received %(service_name)s service update from " | ||
430 | 172 | "%(host)s: %(capabilities)s") % locals()) | ||
431 | 173 | service_caps = self.service_states.get(service_name, {}) | ||
432 | 174 | service_caps[host] = capabilities | ||
433 | 175 | self.service_states[service_name] = service_caps | ||
434 | 144 | 176 | ||
435 | === modified file 'nova/service.py' | |||
436 | --- nova/service.py 2011-03-18 13:56:05 +0000 | |||
437 | +++ nova/service.py 2011-03-23 19:38:07 +0000 | |||
438 | @@ -97,18 +97,24 @@ | |||
439 | 97 | 97 | ||
440 | 98 | conn1 = rpc.Connection.instance(new=True) | 98 | conn1 = rpc.Connection.instance(new=True) |
441 | 99 | conn2 = rpc.Connection.instance(new=True) | 99 | conn2 = rpc.Connection.instance(new=True) |
442 | 100 | conn3 = rpc.Connection.instance(new=True) | ||
443 | 100 | if self.report_interval: | 101 | if self.report_interval: |
445 | 101 | consumer_all = rpc.AdapterConsumer( | 102 | consumer_all = rpc.TopicAdapterConsumer( |
446 | 102 | connection=conn1, | 103 | connection=conn1, |
447 | 103 | topic=self.topic, | 104 | topic=self.topic, |
448 | 104 | proxy=self) | 105 | proxy=self) |
450 | 105 | consumer_node = rpc.AdapterConsumer( | 106 | consumer_node = rpc.TopicAdapterConsumer( |
451 | 106 | connection=conn2, | 107 | connection=conn2, |
452 | 107 | topic='%s.%s' % (self.topic, self.host), | 108 | topic='%s.%s' % (self.topic, self.host), |
453 | 108 | proxy=self) | 109 | proxy=self) |
454 | 110 | fanout = rpc.FanoutAdapterConsumer( | ||
455 | 111 | connection=conn3, | ||
456 | 112 | topic=self.topic, | ||
457 | 113 | proxy=self) | ||
458 | 109 | 114 | ||
459 | 110 | self.timers.append(consumer_all.attach_to_eventlet()) | 115 | self.timers.append(consumer_all.attach_to_eventlet()) |
460 | 111 | self.timers.append(consumer_node.attach_to_eventlet()) | 116 | self.timers.append(consumer_node.attach_to_eventlet()) |
461 | 117 | self.timers.append(fanout.attach_to_eventlet()) | ||
462 | 112 | 118 | ||
463 | 113 | pulse = utils.LoopingCall(self.report_state) | 119 | pulse = utils.LoopingCall(self.report_state) |
464 | 114 | pulse.start(interval=self.report_interval, now=False) | 120 | pulse.start(interval=self.report_interval, now=False) |
465 | 115 | 121 | ||
466 | === modified file 'nova/tests/api/openstack/test_zones.py' | |||
467 | --- nova/tests/api/openstack/test_zones.py 2011-03-11 19:49:32 +0000 | |||
468 | +++ nova/tests/api/openstack/test_zones.py 2011-03-23 19:38:07 +0000 | |||
469 | @@ -75,6 +75,10 @@ | |||
470 | 75 | ] | 75 | ] |
471 | 76 | 76 | ||
472 | 77 | 77 | ||
473 | 78 | def zone_capabilities(method, context, params): | ||
474 | 79 | return dict() | ||
475 | 80 | |||
476 | 81 | |||
477 | 78 | class ZonesTest(test.TestCase): | 82 | class ZonesTest(test.TestCase): |
478 | 79 | def setUp(self): | 83 | def setUp(self): |
479 | 80 | super(ZonesTest, self).setUp() | 84 | super(ZonesTest, self).setUp() |
480 | @@ -93,13 +97,18 @@ | |||
481 | 93 | self.stubs.Set(nova.db, 'zone_create', zone_create) | 97 | self.stubs.Set(nova.db, 'zone_create', zone_create) |
482 | 94 | self.stubs.Set(nova.db, 'zone_delete', zone_delete) | 98 | self.stubs.Set(nova.db, 'zone_delete', zone_delete) |
483 | 95 | 99 | ||
484 | 100 | self.old_zone_name = FLAGS.zone_name | ||
485 | 101 | self.old_zone_capabilities = FLAGS.zone_capabilities | ||
486 | 102 | |||
487 | 96 | def tearDown(self): | 103 | def tearDown(self): |
488 | 97 | self.stubs.UnsetAll() | 104 | self.stubs.UnsetAll() |
489 | 98 | FLAGS.allow_admin_api = self.allow_admin | 105 | FLAGS.allow_admin_api = self.allow_admin |
490 | 106 | FLAGS.zone_name = self.old_zone_name | ||
491 | 107 | FLAGS.zone_capabilities = self.old_zone_capabilities | ||
492 | 99 | super(ZonesTest, self).tearDown() | 108 | super(ZonesTest, self).tearDown() |
493 | 100 | 109 | ||
494 | 101 | def test_get_zone_list_scheduler(self): | 110 | def test_get_zone_list_scheduler(self): |
496 | 102 | self.stubs.Set(api.API, '_call_scheduler', zone_get_all_scheduler) | 111 | self.stubs.Set(api, '_call_scheduler', zone_get_all_scheduler) |
497 | 103 | req = webob.Request.blank('/v1.0/zones') | 112 | req = webob.Request.blank('/v1.0/zones') |
498 | 104 | res = req.get_response(fakes.wsgi_app()) | 113 | res = req.get_response(fakes.wsgi_app()) |
499 | 105 | res_dict = json.loads(res.body) | 114 | res_dict = json.loads(res.body) |
500 | @@ -108,8 +117,7 @@ | |||
501 | 108 | self.assertEqual(len(res_dict['zones']), 2) | 117 | self.assertEqual(len(res_dict['zones']), 2) |
502 | 109 | 118 | ||
503 | 110 | def test_get_zone_list_db(self): | 119 | def test_get_zone_list_db(self): |
506 | 111 | self.stubs.Set(api.API, '_call_scheduler', | 120 | self.stubs.Set(api, '_call_scheduler', zone_get_all_scheduler_empty) |
505 | 112 | zone_get_all_scheduler_empty) | ||
507 | 113 | self.stubs.Set(nova.db, 'zone_get_all', zone_get_all_db) | 121 | self.stubs.Set(nova.db, 'zone_get_all', zone_get_all_db) |
508 | 114 | req = webob.Request.blank('/v1.0/zones') | 122 | req = webob.Request.blank('/v1.0/zones') |
509 | 115 | req.headers["Content-Type"] = "application/json" | 123 | req.headers["Content-Type"] = "application/json" |
510 | @@ -167,3 +175,18 @@ | |||
511 | 167 | self.assertEqual(res_dict['zone']['id'], 1) | 175 | self.assertEqual(res_dict['zone']['id'], 1) |
512 | 168 | self.assertEqual(res_dict['zone']['api_url'], 'http://example.com') | 176 | self.assertEqual(res_dict['zone']['api_url'], 'http://example.com') |
513 | 169 | self.assertFalse('username' in res_dict['zone']) | 177 | self.assertFalse('username' in res_dict['zone']) |
514 | 178 | |||
515 | 179 | def test_zone_info(self): | ||
516 | 180 | FLAGS.zone_name = 'darksecret' | ||
517 | 181 | FLAGS.zone_capabilities = ['cap1=a;b', 'cap2=c;d'] | ||
518 | 182 | self.stubs.Set(api, '_call_scheduler', zone_capabilities) | ||
519 | 183 | |||
520 | 184 | body = dict(zone=dict(username='zeb', password='sneaky')) | ||
521 | 185 | req = webob.Request.blank('/v1.0/zones/info') | ||
522 | 186 | |||
523 | 187 | res = req.get_response(fakes.wsgi_app()) | ||
524 | 188 | res_dict = json.loads(res.body) | ||
525 | 189 | self.assertEqual(res.status_int, 200) | ||
526 | 190 | self.assertEqual(res_dict['zone']['name'], 'darksecret') | ||
527 | 191 | self.assertEqual(res_dict['zone']['cap1'], 'a;b') | ||
528 | 192 | self.assertEqual(res_dict['zone']['cap2'], 'c;d') | ||
529 | 170 | 193 | ||
530 | === modified file 'nova/tests/test_rpc.py' | |||
531 | --- nova/tests/test_rpc.py 2011-01-19 20:26:09 +0000 | |||
532 | +++ nova/tests/test_rpc.py 2011-03-23 19:38:07 +0000 | |||
533 | @@ -36,7 +36,7 @@ | |||
534 | 36 | super(RpcTestCase, self).setUp() | 36 | super(RpcTestCase, self).setUp() |
535 | 37 | self.conn = rpc.Connection.instance(True) | 37 | self.conn = rpc.Connection.instance(True) |
536 | 38 | self.receiver = TestReceiver() | 38 | self.receiver = TestReceiver() |
538 | 39 | self.consumer = rpc.AdapterConsumer(connection=self.conn, | 39 | self.consumer = rpc.TopicAdapterConsumer(connection=self.conn, |
539 | 40 | topic='test', | 40 | topic='test', |
540 | 41 | proxy=self.receiver) | 41 | proxy=self.receiver) |
541 | 42 | self.consumer.attach_to_eventlet() | 42 | self.consumer.attach_to_eventlet() |
542 | @@ -97,7 +97,7 @@ | |||
543 | 97 | 97 | ||
544 | 98 | nested = Nested() | 98 | nested = Nested() |
545 | 99 | conn = rpc.Connection.instance(True) | 99 | conn = rpc.Connection.instance(True) |
547 | 100 | consumer = rpc.AdapterConsumer(connection=conn, | 100 | consumer = rpc.TopicAdapterConsumer(connection=conn, |
548 | 101 | topic='nested', | 101 | topic='nested', |
549 | 102 | proxy=nested) | 102 | proxy=nested) |
550 | 103 | consumer.attach_to_eventlet() | 103 | consumer.attach_to_eventlet() |
551 | 104 | 104 | ||
552 | === modified file 'nova/tests/test_service.py' | |||
553 | --- nova/tests/test_service.py 2011-03-10 06:16:03 +0000 | |||
554 | +++ nova/tests/test_service.py 2011-03-23 19:38:07 +0000 | |||
555 | @@ -109,20 +109,29 @@ | |||
556 | 109 | app = service.Service.create(host=host, binary=binary) | 109 | app = service.Service.create(host=host, binary=binary) |
557 | 110 | 110 | ||
558 | 111 | self.mox.StubOutWithMock(rpc, | 111 | self.mox.StubOutWithMock(rpc, |
562 | 112 | 'AdapterConsumer', | 112 | 'TopicAdapterConsumer', |
563 | 113 | use_mock_anything=True) | 113 | use_mock_anything=True) |
564 | 114 | rpc.AdapterConsumer(connection=mox.IgnoreArg(), | 114 | self.mox.StubOutWithMock(rpc, |
565 | 115 | 'FanoutAdapterConsumer', | ||
566 | 116 | use_mock_anything=True) | ||
567 | 117 | rpc.TopicAdapterConsumer(connection=mox.IgnoreArg(), | ||
568 | 115 | topic=topic, | 118 | topic=topic, |
569 | 116 | proxy=mox.IsA(service.Service)).AndReturn( | 119 | proxy=mox.IsA(service.Service)).AndReturn( |
571 | 117 | rpc.AdapterConsumer) | 120 | rpc.TopicAdapterConsumer) |
572 | 118 | 121 | ||
574 | 119 | rpc.AdapterConsumer(connection=mox.IgnoreArg(), | 122 | rpc.TopicAdapterConsumer(connection=mox.IgnoreArg(), |
575 | 120 | topic='%s.%s' % (topic, host), | 123 | topic='%s.%s' % (topic, host), |
576 | 121 | proxy=mox.IsA(service.Service)).AndReturn( | 124 | proxy=mox.IsA(service.Service)).AndReturn( |
581 | 122 | rpc.AdapterConsumer) | 125 | rpc.TopicAdapterConsumer) |
582 | 123 | 126 | ||
583 | 124 | rpc.AdapterConsumer.attach_to_eventlet() | 127 | rpc.FanoutAdapterConsumer(connection=mox.IgnoreArg(), |
584 | 125 | rpc.AdapterConsumer.attach_to_eventlet() | 128 | topic=topic, |
585 | 129 | proxy=mox.IsA(service.Service)).AndReturn( | ||
586 | 130 | rpc.FanoutAdapterConsumer) | ||
587 | 131 | |||
588 | 132 | rpc.TopicAdapterConsumer.attach_to_eventlet() | ||
589 | 133 | rpc.TopicAdapterConsumer.attach_to_eventlet() | ||
590 | 134 | rpc.FanoutAdapterConsumer.attach_to_eventlet() | ||
591 | 126 | 135 | ||
592 | 127 | service_create = {'host': host, | 136 | service_create = {'host': host, |
593 | 128 | 'binary': binary, | 137 | 'binary': binary, |
594 | @@ -279,6 +288,7 @@ | |||
595 | 279 | self.mox.StubOutWithMock(service.rpc.Connection, 'instance') | 288 | self.mox.StubOutWithMock(service.rpc.Connection, 'instance') |
596 | 280 | service.rpc.Connection.instance(new=mox.IgnoreArg()) | 289 | service.rpc.Connection.instance(new=mox.IgnoreArg()) |
597 | 281 | service.rpc.Connection.instance(new=mox.IgnoreArg()) | 290 | service.rpc.Connection.instance(new=mox.IgnoreArg()) |
598 | 291 | service.rpc.Connection.instance(new=mox.IgnoreArg()) | ||
599 | 282 | self.mox.StubOutWithMock(serv.manager.driver, | 292 | self.mox.StubOutWithMock(serv.manager.driver, |
600 | 283 | 'update_available_resource') | 293 | 'update_available_resource') |
601 | 284 | serv.manager.driver.update_available_resource(mox.IgnoreArg(), host) | 294 | serv.manager.driver.update_available_resource(mox.IgnoreArg(), host) |
602 | 285 | 295 | ||
603 | === modified file 'nova/tests/test_test.py' | |||
604 | --- nova/tests/test_test.py 2011-02-21 22:55:06 +0000 | |||
605 | +++ nova/tests/test_test.py 2011-03-23 19:38:07 +0000 | |||
606 | @@ -34,7 +34,7 @@ | |||
607 | 34 | 34 | ||
608 | 35 | def test_rpc_consumer_isolation(self): | 35 | def test_rpc_consumer_isolation(self): |
609 | 36 | connection = rpc.Connection.instance(new=True) | 36 | connection = rpc.Connection.instance(new=True) |
611 | 37 | consumer = rpc.TopicConsumer(connection, topic='compute') | 37 | consumer = rpc.TopicAdapterConsumer(connection, topic='compute') |
612 | 38 | consumer.register_callback( | 38 | consumer.register_callback( |
613 | 39 | lambda x, y: self.fail('I should never be called')) | 39 | lambda x, y: self.fail('I should never be called')) |
614 | 40 | consumer.attach_to_eventlet() | 40 | consumer.attach_to_eventlet() |
615 | 41 | 41 | ||
616 | === modified file 'nova/tests/test_zones.py' | |||
617 | --- nova/tests/test_zones.py 2011-03-03 14:55:02 +0000 | |||
618 | +++ nova/tests/test_zones.py 2011-03-23 19:38:07 +0000 | |||
619 | @@ -76,6 +76,40 @@ | |||
620 | 76 | self.assertEquals(len(zm.zone_states), 1) | 76 | self.assertEquals(len(zm.zone_states), 1) |
621 | 77 | self.assertEquals(zm.zone_states[1].username, 'user1') | 77 | self.assertEquals(zm.zone_states[1].username, 'user1') |
622 | 78 | 78 | ||
623 | 79 | def test_service_capabilities(self): | ||
624 | 80 | zm = zone_manager.ZoneManager() | ||
625 | 81 | caps = zm.get_zone_capabilities(self, None) | ||
626 | 82 | self.assertEquals(caps, {}) | ||
627 | 83 | |||
628 | 84 | zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2)) | ||
629 | 85 | caps = zm.get_zone_capabilities(self, None) | ||
630 | 86 | self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2))) | ||
631 | 87 | |||
632 | 88 | zm.update_service_capabilities("svc1", "host1", dict(a=2, b=3)) | ||
633 | 89 | caps = zm.get_zone_capabilities(self, None) | ||
634 | 90 | self.assertEquals(caps, dict(svc1_a=(2, 2), svc1_b=(3, 3))) | ||
635 | 91 | |||
636 | 92 | zm.update_service_capabilities("svc1", "host2", dict(a=20, b=30)) | ||
637 | 93 | caps = zm.get_zone_capabilities(self, None) | ||
638 | 94 | self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30))) | ||
639 | 95 | |||
640 | 96 | zm.update_service_capabilities("svc10", "host1", dict(a=99, b=99)) | ||
641 | 97 | caps = zm.get_zone_capabilities(self, None) | ||
642 | 98 | self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), | ||
643 | 99 | svc10_a=(99, 99), svc10_b=(99, 99))) | ||
644 | 100 | |||
645 | 101 | zm.update_service_capabilities("svc1", "host3", dict(c=5)) | ||
646 | 102 | caps = zm.get_zone_capabilities(self, None) | ||
647 | 103 | self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), | ||
648 | 104 | svc1_c=(5, 5), svc10_a=(99, 99), | ||
649 | 105 | svc10_b=(99, 99))) | ||
650 | 106 | |||
651 | 107 | caps = zm.get_zone_capabilities(self, 'svc1') | ||
652 | 108 | self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30), | ||
653 | 109 | svc1_c=(5, 5))) | ||
654 | 110 | caps = zm.get_zone_capabilities(self, 'svc10') | ||
655 | 111 | self.assertEquals(caps, dict(svc10_a=(99, 99), svc10_b=(99, 99))) | ||
656 | 112 | |||
657 | 79 | def test_refresh_from_db_replace_existing(self): | 113 | def test_refresh_from_db_replace_existing(self): |
658 | 80 | zm = zone_manager.ZoneManager() | 114 | zm = zone_manager.ZoneManager() |
659 | 81 | zone_state = zone_manager.ZoneState() | 115 | zone_state = zone_manager.ZoneState() |
660 | 82 | 116 | ||
661 | === modified file 'nova/volume/manager.py' | |||
662 | --- nova/volume/manager.py 2011-03-03 13:54:11 +0000 | |||
663 | +++ nova/volume/manager.py 2011-03-23 19:38:07 +0000 | |||
664 | @@ -64,14 +64,15 @@ | |||
665 | 64 | 'if True, will not discover local volumes') | 64 | 'if True, will not discover local volumes') |
666 | 65 | 65 | ||
667 | 66 | 66 | ||
669 | 67 | class VolumeManager(manager.Manager): | 67 | class VolumeManager(manager.SchedulerDependentManager): |
670 | 68 | """Manages attachable block storage devices.""" | 68 | """Manages attachable block storage devices.""" |
671 | 69 | def __init__(self, volume_driver=None, *args, **kwargs): | 69 | def __init__(self, volume_driver=None, *args, **kwargs): |
672 | 70 | """Load the driver from the one specified in args, or from flags.""" | 70 | """Load the driver from the one specified in args, or from flags.""" |
673 | 71 | if not volume_driver: | 71 | if not volume_driver: |
674 | 72 | volume_driver = FLAGS.volume_driver | 72 | volume_driver = FLAGS.volume_driver |
675 | 73 | self.driver = utils.import_object(volume_driver) | 73 | self.driver = utils.import_object(volume_driver) |
677 | 74 | super(VolumeManager, self).__init__(*args, **kwargs) | 74 | super(VolumeManager, self).__init__(service_name='volume', |
678 | 75 | *args, **kwargs) | ||
679 | 75 | # NOTE(vish): Implementation specific db handling is done | 76 | # NOTE(vish): Implementation specific db handling is done |
680 | 76 | # by the driver. | 77 | # by the driver. |
681 | 77 | self.driver.db = self.db | 78 | self.driver.db = self.db |
I think this looks pretty good.
Is there any information (RST docs) regarding what meaningful values for capabilities are?
Also, in some flags we are using x=y,a=b syntax for multi-valued flags (defualt_ log_levels, etc). Perhaps we can keep the same format? Long term perhaps we could make a new DEFINE_dict or some-such that would parse those into a dict.
It looks like the only information that is being fanned out is the service capabilities, but would it make sense to keep abilities in the database pointing to the services themselves? In other words, why are service capabilities ephemeral with the services starting and stopping? If we keep them in the db we can modify them while running and modify the capabilities with nova-manage. This of course doesn't make sense for things like changing the hypervisor for compute since that would always require a restart, but would it be practical for other capabilities?