Merge lp:~james-page/nova/folsom-resync into lp:~openstack-ubuntu-testing/nova/folsom
- folsom-resync
- Merge into folsom
Proposed by
James Page
Status: | Merged |
---|---|
Approved by: | Chuck Short |
Approved revision: | 479 |
Merged at revision: | 479 |
Proposed branch: | lp:~james-page/nova/folsom-resync |
Merge into: | lp:~openstack-ubuntu-testing/nova/folsom |
Diff against target: |
922 lines (+887/-0) 4 files modified
debian/changelog (+31/-0) debian/patches/CVE-2013-0335.patch (+378/-0) debian/patches/CVE-2013-1838.patch (+476/-0) debian/patches/series (+2/-0) |
To merge this branch: | bzr merge lp:~james-page/nova/folsom-resync |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Openstack Ubuntu Testers | Pending | ||
Review via email: mp+154955@code.launchpad.net |
Commit message
Description of the change
Resync of pending SRU with security updates.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2013-02-21 20:49:05 +0000 |
3 | +++ debian/changelog 2013-03-22 13:42:13 +0000 |
4 | @@ -12,6 +12,23 @@ |
5 | |
6 | -- Adam Gandelman <adamg@ubuntu.com> Thu, 21 Feb 2013 12:40:55 -0400 |
7 | |
8 | +nova (2012.2.3-0ubuntu2) quantal-proposed; urgency=low |
9 | + |
10 | + * Re-sync with latest security updates. |
11 | + * SECURITY UPDATE: fix denial of service via fixed IPs when using extensions |
12 | + - debian/patches/CVE-2013-1838.patch: add explicit quota for fixed IP |
13 | + - CVE-2013-1838 |
14 | + * SECURITY UPDATE: fix VNC token validation |
15 | + - debian/patches/CVE-2013-0335.patch: force console auth service to flush |
16 | + all tokens associated with an instance when it is deleted |
17 | + - CVE-2013-0335 |
18 | + * SECURITY UPDATE: fix denial of service |
19 | + - CVE-2013-1664.patch: Add a new utils.safe_minidom_parse_string function |
20 | + and update external API facing Nova modules to use it |
21 | + - CVE-2013-1664 |
22 | + |
23 | + -- James Page <james.page@ubuntu.com> Fri, 22 Mar 2013 12:40:07 +0000 |
24 | + |
25 | nova (2012.2.3-0ubuntu1) quantal-proposed; urgency=low |
26 | |
27 | * Dropped patches, applied upstream: |
28 | @@ -45,6 +62,20 @@ |
29 | |
30 | -- Adam Gandelman <adamg@ubuntu.com> Tue, 05 Feb 2013 14:11:49 -0400 |
31 | |
32 | +nova (2012.2.1+stable-20121212-a99a802e-0ubuntu1.4) quantal-security; urgency=low |
33 | + |
34 | + * SECURITY UPDATE: fix denial of service via fixed IPs when using extensions |
35 | + - debian/patches/CVE-2013-1838.patch: add explicit quota for fixed IP |
36 | + - CVE-2013-1838 |
37 | + - LP: #1125468 |
38 | + * SECURITY UPDATE: fix VNC token validation |
39 | + - debian/patches/CVE-2013-0335.patch: force console auth service to flush |
40 | + all tokens associated with an instance when it is deleted |
41 | + - CVE-2013-0335 |
42 | + - LP: #1125378 |
43 | + |
44 | + -- Jamie Strandboge <jamie@ubuntu.com> Wed, 20 Mar 2013 09:53:55 -0500 |
45 | + |
46 | nova (2012.2.1+stable-20121212-a99a802e-0ubuntu1.2) quantal-security; urgency=low |
47 | |
48 | * SECURITY UPDATE: fix denial of service |
49 | |
50 | === added file 'debian/patches/CVE-2013-0335.patch' |
51 | --- debian/patches/CVE-2013-0335.patch 1970-01-01 00:00:00 +0000 |
52 | +++ debian/patches/CVE-2013-0335.patch 2013-03-22 13:42:13 +0000 |
53 | @@ -0,0 +1,378 @@ |
54 | +From: John Herndon <john.herndon@hp.com> |
55 | +Date: Fri, 22 Feb 2013 20:43:58 +0000 (+0000) |
56 | +Subject: VNC Token Validation |
57 | +X-Git-Url: https://review.openstack.org/gitweb?p=openstack%2Fnova.git;a=commitdiff_plain;h=05a3374992bc8ba53ddc9c491b51c4b59eed0a72 |
58 | + |
59 | +VNC Token Validation |
60 | + |
61 | +Force console auth service to flush all tokens |
62 | +associated with an instance when it is deleted. |
63 | +This will fix a bug where the console for the |
64 | +wrong instance can be connected to via the console |
65 | +if the correct circumstances occur. This change also |
66 | +makes a call to veriry vnc console tokens when a |
67 | +user attempts to connect to a console. This ensures |
68 | +the user is connecting to the correct console. |
69 | + |
70 | +bug 1125378 |
71 | +Change-Id: I0d83ec6c4dbfef1af912a200ee15f8052f72da96 |
72 | +--- |
73 | + |
74 | +--- a/nova/common/memorycache.py |
75 | ++++ b/nova/common/memorycache.py |
76 | +@@ -62,3 +62,8 @@ class Client(object): |
77 | + new_value = int(value) + delta |
78 | + self.cache[key] = (self.cache[key][0], str(new_value)) |
79 | + return new_value |
80 | ++ |
81 | ++ def delete(self, key, time=0): |
82 | ++ """Deletes the value associated with a key.""" |
83 | ++ if key in self.cache: |
84 | ++ del self.cache[key] |
85 | +--- a/nova/compute/api.py |
86 | ++++ b/nova/compute/api.py |
87 | +@@ -1852,7 +1852,8 @@ class API(base.Base): |
88 | + |
89 | + self.consoleauth_rpcapi.authorize_console(context, |
90 | + connect_info['token'], console_type, connect_info['host'], |
91 | +- connect_info['port'], connect_info['internal_access_path']) |
92 | ++ connect_info['port'], connect_info['internal_access_path'], |
93 | ++ instance["uuid"]) |
94 | + |
95 | + return {'url': connect_info['access_url']} |
96 | + |
97 | +--- a/nova/compute/manager.py |
98 | ++++ b/nova/compute/manager.py |
99 | +@@ -52,6 +52,7 @@ from nova.compute import rpcapi as compu |
100 | + from nova.compute import task_states |
101 | + from nova.compute import utils as compute_utils |
102 | + from nova.compute import vm_states |
103 | ++from nova import consoleauth |
104 | + import nova.context |
105 | + from nova import exception |
106 | + from nova import flags |
107 | +@@ -235,6 +236,7 @@ class ComputeManager(manager.SchedulerDe |
108 | + self.compute_api = compute.API() |
109 | + self.compute_rpcapi = compute_rpcapi.ComputeAPI() |
110 | + self.scheduler_rpcapi = scheduler_rpcapi.SchedulerAPI() |
111 | ++ self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI() |
112 | + |
113 | + super(ComputeManager, self).__init__(service_name="compute", |
114 | + *args, **kwargs) |
115 | +@@ -926,6 +928,10 @@ class ComputeManager(manager.SchedulerDe |
116 | + self._notify_about_instance_usage(context, instance, "delete.end", |
117 | + system_metadata=system_meta) |
118 | + |
119 | ++ if FLAGS.vnc_enabled: |
120 | ++ self.consoleauth_rpcapi.delete_tokens_for_instance(context, |
121 | ++ instance["uuid"]) |
122 | ++ |
123 | + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) |
124 | + @wrap_instance_fault |
125 | + def terminate_instance(self, context, instance): |
126 | +@@ -1989,6 +1995,12 @@ class ComputeManager(manager.SchedulerDe |
127 | + return connection_info |
128 | + |
129 | + @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) |
130 | ++ @wrap_instance_fault |
131 | ++ def validate_console_port(self, ctxt, instance, port, console_type): |
132 | ++ console_info = self.driver.get_vnc_console(instance) |
133 | ++ return console_info['port'] == port |
134 | ++ |
135 | ++ @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) |
136 | + @reverts_task_state |
137 | + @wrap_instance_fault |
138 | + def reserve_block_device_name(self, context, instance, device): |
139 | +--- a/nova/compute/rpcapi.py |
140 | ++++ b/nova/compute/rpcapi.py |
141 | +@@ -259,6 +259,13 @@ class ComputeAPI(nova.openstack.common.r |
142 | + instance=instance_p, console_type=console_type), |
143 | + topic=_compute_topic(self.topic, ctxt, None, instance)) |
144 | + |
145 | ++ def validate_console_port(self, ctxt, instance, port, console_type): |
146 | ++ instance_p = jsonutils.to_primitive(instance) |
147 | ++ return self.call(ctxt, self.make_msg('validate_console_port', |
148 | ++ instance=instance_p, port=port, console_type=console_type), |
149 | ++ topic=_compute_topic(self.topic, ctxt, |
150 | ++ None, instance)) |
151 | ++ |
152 | + def host_maintenance_mode(self, ctxt, host_param, mode, host): |
153 | + '''Set host maintenance mode |
154 | + |
155 | +--- a/nova/consoleauth/manager.py |
156 | ++++ b/nova/consoleauth/manager.py |
157 | +@@ -20,6 +20,8 @@ |
158 | + |
159 | + import time |
160 | + |
161 | ++from nova.compute import rpcapi as compute_rpcapi |
162 | ++from nova.db import api as db |
163 | + from nova import flags |
164 | + from nova import manager |
165 | + from nova.openstack.common import cfg |
166 | +@@ -56,10 +58,21 @@ class ConsoleAuthManager(manager.Manager |
167 | + from nova.common import memorycache as memcache |
168 | + self.mc = memcache.Client(FLAGS.memcached_servers, |
169 | + debug=0) |
170 | ++ self.compute_rpcapi = compute_rpcapi.ComputeAPI() |
171 | ++ |
172 | ++ def _get_tokens_for_instance(self, instance_uuid): |
173 | ++ tokens_str = self.mc.get(instance_uuid.encode('UTF-8')) |
174 | ++ if not tokens_str: |
175 | ++ tokens = [] |
176 | ++ else: |
177 | ++ tokens = jsonutils.loads(tokens_str) |
178 | ++ return tokens |
179 | + |
180 | + def authorize_console(self, context, token, console_type, host, port, |
181 | +- internal_access_path): |
182 | ++ internal_access_path, instance_uuid=None): |
183 | ++ |
184 | + token_dict = {'token': token, |
185 | ++ 'instance_uuid': instance_uuid, |
186 | + 'console_type': console_type, |
187 | + 'host': host, |
188 | + 'port': port, |
189 | +@@ -67,11 +80,35 @@ class ConsoleAuthManager(manager.Manager |
190 | + 'last_activity_at': time.time()} |
191 | + data = jsonutils.dumps(token_dict) |
192 | + self.mc.set(token.encode('UTF-8'), data, FLAGS.console_token_ttl) |
193 | ++ if instance_uuid is not None: |
194 | ++ tokens = self._get_tokens_for_instance(instance_uuid) |
195 | ++ tokens.append(token) |
196 | ++ self.mc.set(instance_uuid.encode('UTF-8'), |
197 | ++ jsonutils.dumps(tokens)) |
198 | ++ |
199 | + LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals()) |
200 | + |
201 | ++ def _validate_token(self, context, token): |
202 | ++ instance_uuid = token['instance_uuid'] |
203 | ++ if instance_uuid is None: |
204 | ++ return False |
205 | ++ instance = db.instance_get_by_uuid(context, instance_uuid) |
206 | ++ return self.compute_rpcapi.validate_console_port(context, |
207 | ++ instance, |
208 | ++ token['port'], |
209 | ++ token['console_type']) |
210 | ++ |
211 | + def check_token(self, context, token): |
212 | + token_str = self.mc.get(token.encode('UTF-8')) |
213 | + token_valid = (token_str is not None) |
214 | + LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals()) |
215 | + if token_valid: |
216 | +- return jsonutils.loads(token_str) |
217 | ++ token = jsonutils.loads(token_str) |
218 | ++ if self._validate_token(context, token): |
219 | ++ return token |
220 | ++ |
221 | ++ def delete_tokens_for_instance(self, context, instance_uuid): |
222 | ++ tokens = self._get_tokens_for_instance(instance_uuid) |
223 | ++ for token in tokens: |
224 | ++ self.mc.delete(token) |
225 | ++ self.mc.delete(instance_uuid.encode('UTF-8')) |
226 | +--- a/nova/consoleauth/rpcapi.py |
227 | ++++ b/nova/consoleauth/rpcapi.py |
228 | +@@ -49,14 +49,20 @@ class ConsoleAuthAPI(nova.openstack.comm |
229 | + default_version=self.BASE_RPC_API_VERSION) |
230 | + |
231 | + def authorize_console(self, ctxt, token, console_type, host, port, |
232 | +- internal_access_path): |
233 | ++ internal_access_path, instance_uuid=None): |
234 | + # The remote side doesn't return anything, but we want to block |
235 | + # until it completes. |
236 | + return self.call(ctxt, |
237 | + self.make_msg('authorize_console', |
238 | + token=token, console_type=console_type, |
239 | + host=host, port=port, |
240 | +- internal_access_path=internal_access_path)) |
241 | ++ internal_access_path=internal_access_path, |
242 | ++ instance_uuid=instance_uuid)) |
243 | + |
244 | + def check_token(self, ctxt, token): |
245 | + return self.call(ctxt, self.make_msg('check_token', token=token)) |
246 | ++ |
247 | ++ def delete_tokens_for_instance(self, ctxt, instance_uuid): |
248 | ++ return self.call(ctxt, |
249 | ++ self.make_msg('delete_tokens_for_instance', |
250 | ++ instance_uuid=instance_uuid)) |
251 | +--- a/nova/tests/compute/test_compute.py |
252 | ++++ b/nova/tests/compute/test_compute.py |
253 | +@@ -1372,6 +1372,24 @@ class ComputeTestCase(BaseTestCase): |
254 | + self.compute._delete_instance(self.context, |
255 | + instance=jsonutils.to_primitive(instance)) |
256 | + |
257 | ++ def test_delete_instance_deletes_console_auth_tokens(self): |
258 | ++ instance = self._create_fake_instance() |
259 | ++ self.flags(vnc_enabled=True) |
260 | ++ |
261 | ++ self.tokens_deleted = False |
262 | ++ |
263 | ++ def fake_delete_tokens(*args, **kwargs): |
264 | ++ self.tokens_deleted = True |
265 | ++ |
266 | ++ cauth_rpcapi = self.compute.consoleauth_rpcapi |
267 | ++ self.stubs.Set(cauth_rpcapi, 'delete_tokens_for_instance', |
268 | ++ fake_delete_tokens) |
269 | ++ |
270 | ++ self.compute._delete_instance(self.context, |
271 | ++ instance=jsonutils.to_primitive(instance)) |
272 | ++ |
273 | ++ self.assertTrue(self.tokens_deleted) |
274 | ++ |
275 | + def test_instance_termination_exception_sets_error(self): |
276 | + """Test that we handle InstanceTerminationFailure |
277 | + which is propagated up from the underlying driver. |
278 | +@@ -4465,7 +4483,9 @@ class ComputeAPITestCase(BaseTestCase): |
279 | + 'console_type': fake_console_type, |
280 | + 'host': 'fake_console_host', |
281 | + 'port': 'fake_console_port', |
282 | +- 'internal_access_path': 'fake_access_path'} |
283 | ++ 'internal_access_path': 'fake_access_path', |
284 | ++ 'instance_uuid': fake_instance["uuid"]} |
285 | ++ |
286 | + fake_connect_info2 = copy.deepcopy(fake_connect_info) |
287 | + fake_connect_info2['access_url'] = 'fake_console_url' |
288 | + |
289 | +@@ -4499,6 +4519,36 @@ class ComputeAPITestCase(BaseTestCase): |
290 | + |
291 | + db.instance_destroy(self.context, instance['uuid']) |
292 | + |
293 | ++ def test_validate_console_port(self): |
294 | ++ self.flags(vnc_enabled=True) |
295 | ++ instance = jsonutils.to_primitive(self._create_fake_instance()) |
296 | ++ |
297 | ++ def fake_driver_get_console(*args, **kwargs): |
298 | ++ return {'host': "fake_host", 'port': "5900", |
299 | ++ 'internal_access_path': None} |
300 | ++ self.stubs.Set(self.compute.driver, "get_vnc_console", |
301 | ++ fake_driver_get_console) |
302 | ++ |
303 | ++ self.assertTrue(self.compute.validate_console_port(self.context, |
304 | ++ instance, |
305 | ++ "5900", |
306 | ++ "novnc")) |
307 | ++ |
308 | ++ def test_validate_console_port_wrong_port(self): |
309 | ++ self.flags(vnc_enabled=True) |
310 | ++ instance = jsonutils.to_primitive(self._create_fake_instance()) |
311 | ++ |
312 | ++ def fake_driver_get_console(*args, **kwargs): |
313 | ++ return {'host': "fake_host", 'port': "5900", |
314 | ++ 'internal_access_path': None} |
315 | ++ self.stubs.Set(self.compute.driver, "get_vnc_console", |
316 | ++ fake_driver_get_console) |
317 | ++ |
318 | ++ self.assertFalse(self.compute.validate_console_port(self.context, |
319 | ++ instance, |
320 | ++ "wrongport", |
321 | ++ "novnc")) |
322 | ++ |
323 | + def test_console_output(self): |
324 | + fake_instance = {'uuid': 'fake_uuid', |
325 | + 'host': 'fake_compute_host'} |
326 | +--- a/nova/tests/compute/test_rpcapi.py |
327 | ++++ b/nova/tests/compute/test_rpcapi.py |
328 | +@@ -168,6 +168,11 @@ class ComputeRpcAPITestCase(test.TestCas |
329 | + self._test_compute_api('get_vnc_console', 'call', |
330 | + instance=self.fake_instance, console_type='type') |
331 | + |
332 | ++ def test_validate_console_port(self): |
333 | ++ self._test_compute_api('validate_console_port', 'call', |
334 | ++ instance=self.fake_instance, port="5900", |
335 | ++ console_type="novnc") |
336 | ++ |
337 | + def test_host_maintenance_mode(self): |
338 | + self._test_compute_api('host_maintenance_mode', 'call', |
339 | + host_param='param', mode='mode', host='host') |
340 | +--- a/nova/tests/consoleauth/test_consoleauth.py |
341 | ++++ b/nova/tests/consoleauth/test_consoleauth.py |
342 | +@@ -45,8 +45,73 @@ class ConsoleauthTestCase(test.TestCase) |
343 | + """Test that tokens expire correctly.""" |
344 | + token = 'mytok' |
345 | + self.flags(console_token_ttl=1) |
346 | ++ |
347 | ++ def fake_validate_token(*args, **kwargs): |
348 | ++ return True |
349 | ++ self.stubs.Set(self.manager, |
350 | ++ "_validate_token", |
351 | ++ fake_validate_token) |
352 | ++ |
353 | + self.manager.authorize_console(self.context, token, 'novnc', |
354 | +- '127.0.0.1', 'host', '') |
355 | ++ '127.0.0.1', '8080', 'host', "1234") |
356 | + self.assertTrue(self.manager.check_token(self.context, token)) |
357 | + time.sleep(1.1) |
358 | + self.assertFalse(self.manager.check_token(self.context, token)) |
359 | ++ |
360 | ++ def test_multiple_tokens_for_instance(self): |
361 | ++ tokens = ["token" + str(i) for i in xrange(10)] |
362 | ++ instance = "12345" |
363 | ++ |
364 | ++ def fake_validate_token(*args, **kwargs): |
365 | ++ return True |
366 | ++ |
367 | ++ self.stubs.Set(self.manager, "_validate_token", |
368 | ++ fake_validate_token) |
369 | ++ for token in tokens: |
370 | ++ self.manager.authorize_console(self.context, token, 'novnc', |
371 | ++ '127.0.0.1', '8080', 'host', |
372 | ++ instance) |
373 | ++ |
374 | ++ for token in tokens: |
375 | ++ self.assertTrue(self.manager.check_token(self.context, token)) |
376 | ++ |
377 | ++ def test_delete_tokens_for_instance(self): |
378 | ++ instance = "12345" |
379 | ++ tokens = ["token" + str(i) for i in xrange(10)] |
380 | ++ |
381 | ++ def fake_validate_token(*args, **kwargs): |
382 | ++ return True |
383 | ++ self.stubs.Set(self.manager, "_validate_token", |
384 | ++ fake_validate_token) |
385 | ++ |
386 | ++ for token in tokens: |
387 | ++ self.manager.authorize_console(self.context, token, 'novnc', |
388 | ++ '127.0.0.1', '8080', 'host', |
389 | ++ instance) |
390 | ++ self.manager.delete_tokens_for_instance(self.context, instance) |
391 | ++ stored_tokens = self.manager._get_tokens_for_instance(instance) |
392 | ++ |
393 | ++ self.assertEqual(len(stored_tokens), 0) |
394 | ++ |
395 | ++ for token in tokens: |
396 | ++ self.assertFalse(self.manager.check_token(self.context, token)) |
397 | ++ |
398 | ++ def test_wrong_token_has_port(self): |
399 | ++ token = 'mytok' |
400 | ++ |
401 | ++ def fake_validate_token(*args, **kwargs): |
402 | ++ return False |
403 | ++ |
404 | ++ self.stubs.Set(self.manager, "_validate_token", |
405 | ++ fake_validate_token) |
406 | ++ |
407 | ++ self.manager.authorize_console(self.context, token, 'novnc', |
408 | ++ '127.0.0.1', '8080', 'host', |
409 | ++ instance_uuid='instance') |
410 | ++ self.assertFalse(self.manager.check_token(self.context, token)) |
411 | ++ |
412 | ++ def test_console_no_instance_uuid(self): |
413 | ++ self.manager.authorize_console(self.context, "token", 'novnc', |
414 | ++ '127.0.0.1', '8080', 'host', |
415 | ++ instance_uuid=None) |
416 | ++ self.assertFalse(self.manager.check_token(self.context, "token")) |
417 | +--- a/nova/tests/consoleauth/test_rpcapi.py |
418 | ++++ b/nova/tests/consoleauth/test_rpcapi.py |
419 | +@@ -68,7 +68,11 @@ class ConsoleAuthRpcAPITestCase(test.Tes |
420 | + def test_authorize_console(self): |
421 | + self._test_consoleauth_api('authorize_console', token='token', |
422 | + console_type='ctype', host='h', port='p', |
423 | +- internal_access_path='iap') |
424 | ++ internal_access_path='iap', instance_uuid="1234") |
425 | + |
426 | + def test_check_token(self): |
427 | + self._test_consoleauth_api('check_token', token='t') |
428 | ++ |
429 | ++ def test_delete_tokens_for_instnace(self): |
430 | ++ self._test_consoleauth_api('delete_tokens_for_instance', |
431 | ++ instance_uuid="instance") |
432 | |
433 | === added file 'debian/patches/CVE-2013-1838.patch' |
434 | --- debian/patches/CVE-2013-1838.patch 1970-01-01 00:00:00 +0000 |
435 | +++ debian/patches/CVE-2013-1838.patch 2013-03-22 13:42:13 +0000 |
436 | @@ -0,0 +1,476 @@ |
437 | +commit dbe94187193da8741b4b8c270c62eb4d9cb0bd8a |
438 | +Author: Michael Still <mikal@stillhq.com> |
439 | +Date: Fri Mar 1 20:22:39 2013 +0000 |
440 | + |
441 | + Add quotas for fixed ips. |
442 | + |
443 | + DocImpact: there is now a default quota of 10 fixed ips per tenant. |
444 | + This will need to be adjusted by deployers if that number does not |
445 | + meet their needs. |
446 | + |
447 | + Resolves bug 1125468 for folsom. |
448 | + |
449 | + Change-Id: I970d540cfa6a61b7e903703f845a6453ff55f225 |
450 | + |
451 | +--- a/nova/db/api.py |
452 | ++++ b/nova/db/api.py |
453 | +@@ -507,6 +507,12 @@ def fixed_ip_update(context, address, va |
454 | + """Create a fixed ip from the values dictionary.""" |
455 | + return IMPL.fixed_ip_update(context, address, values) |
456 | + |
457 | ++ |
458 | ++def fixed_ip_count_by_project(context, project_id, session=None): |
459 | ++ """Count fixed ips used by project.""" |
460 | ++ return IMPL.fixed_ip_count_by_project(context, project_id, |
461 | ++ session=session) |
462 | ++ |
463 | + #################### |
464 | + |
465 | + |
466 | +--- a/nova/db/sqlalchemy/api.py |
467 | ++++ b/nova/db/sqlalchemy/api.py |
468 | +@@ -1273,6 +1273,27 @@ def fixed_ip_update(context, address, va |
469 | + fixed_ip_ref.save(session=session) |
470 | + |
471 | + |
472 | ++@require_context |
473 | ++def fixed_ip_count_by_project(context, project_id, session=None): |
474 | ++ authorize_project_context(context, project_id) |
475 | ++ |
476 | ++ # NOTE(mikal): Yes I know this is horrible, but I couldn't |
477 | ++ # get a query using a join working, mainly because of a failure |
478 | ++ # to be able to express the where clause sensibly. Patches |
479 | ++ # welcome. |
480 | ++ session = get_session() |
481 | ++ with session.begin(): |
482 | ++ instance_uuid_query = model_query(context, models.Instance.uuid, |
483 | ++ read_deleted="no", session=session).\ |
484 | ++ filter(models.Instance.project_id == \ |
485 | ++ project_id) |
486 | ++ uuid_filter = models.FixedIp.instance_uuid.in_(instance_uuid_query) |
487 | ++ return model_query(context, models.FixedIp, read_deleted="no", |
488 | ++ session=session).\ |
489 | ++ filter(uuid_filter).\ |
490 | ++ count() |
491 | ++ |
492 | ++ |
493 | + ################### |
494 | + |
495 | + |
496 | +--- a/nova/exception.py |
497 | ++++ b/nova/exception.py |
498 | +@@ -998,6 +998,10 @@ class FloatingIpLimitExceeded(QuotaError |
499 | + message = _("Maximum number of floating ips exceeded") |
500 | + |
501 | + |
502 | ++class FixedIpLimitExceeded(QuotaError): |
503 | ++ message = _("Maximum number of fixed ips exceeded") |
504 | ++ |
505 | ++ |
506 | + class MetadataLimitExceeded(QuotaError): |
507 | + message = _("Maximum number of metadata items exceeds %(allowed)d") |
508 | + |
509 | +--- a/nova/network/manager.py |
510 | ++++ b/nova/network/manager.py |
511 | +@@ -1294,37 +1294,53 @@ class NetworkManager(manager.SchedulerDe |
512 | + address = None |
513 | + instance_ref = self.db.instance_get(context, instance_id) |
514 | + |
515 | +- if network['cidr']: |
516 | +- address = kwargs.get('address', None) |
517 | +- if address: |
518 | +- address = self.db.fixed_ip_associate(context, |
519 | +- address, |
520 | +- instance_ref['uuid'], |
521 | +- network['id']) |
522 | +- else: |
523 | +- address = self.db.fixed_ip_associate_pool(context.elevated(), |
524 | +- network['id'], |
525 | +- instance_ref['uuid']) |
526 | +- self._do_trigger_security_group_members_refresh_for_instance( |
527 | +- instance_id) |
528 | +- get_vif = self.db.virtual_interface_get_by_instance_and_network |
529 | +- vif = get_vif(context, instance_ref['uuid'], network['id']) |
530 | +- values = {'allocated': True, |
531 | +- 'virtual_interface_id': vif['id']} |
532 | +- self.db.fixed_ip_update(context, address, values) |
533 | +- |
534 | +- name = instance_ref['display_name'] |
535 | +- |
536 | +- if self._validate_instance_zone_for_dns_domain(context, instance_ref): |
537 | +- uuid = instance_ref['uuid'] |
538 | +- self.instance_dns_manager.create_entry(name, address, |
539 | +- "A", |
540 | +- self.instance_dns_domain) |
541 | +- self.instance_dns_manager.create_entry(uuid, address, |
542 | +- "A", |
543 | +- self.instance_dns_domain) |
544 | +- self._setup_network_on_host(context, network) |
545 | +- return address |
546 | ++ # Check the quota; can't put this in the API because we get |
547 | ++ # called into from other places |
548 | ++ try: |
549 | ++ reservations = QUOTAS.reserve(context, fixed_ips=1) |
550 | ++ except exception.OverQuota: |
551 | ++ pid = context.project_id |
552 | ++ LOG.warn(_("Quota exceeded for %(pid)s, tried to allocate " |
553 | ++ "fixed IP") % locals()) |
554 | ++ raise exception.FixedIpLimitExceeded() |
555 | ++ |
556 | ++ try: |
557 | ++ if network['cidr']: |
558 | ++ address = kwargs.get('address', None) |
559 | ++ if address: |
560 | ++ address = self.db.fixed_ip_associate(context, |
561 | ++ address, |
562 | ++ instance_ref['uuid'], |
563 | ++ network['id']) |
564 | ++ else: |
565 | ++ address = self.db.fixed_ip_associate_pool( |
566 | ++ context.elevated(), network['id'], |
567 | ++ instance_ref['uuid']) |
568 | ++ self._do_trigger_security_group_members_refresh_for_instance( |
569 | ++ instance_id) |
570 | ++ get_vif = self.db.virtual_interface_get_by_instance_and_network |
571 | ++ vif = get_vif(context, instance_ref['uuid'], network['id']) |
572 | ++ values = {'allocated': True, |
573 | ++ 'virtual_interface_id': vif['id']} |
574 | ++ self.db.fixed_ip_update(context, address, values) |
575 | ++ |
576 | ++ name = instance_ref['display_name'] |
577 | ++ |
578 | ++ if self._validate_instance_zone_for_dns_domain(context, |
579 | ++ instance_ref): |
580 | ++ uuid = instance_ref['uuid'] |
581 | ++ self.instance_dns_manager.create_entry( |
582 | ++ name, address, "A", self.instance_dns_domain) |
583 | ++ self.instance_dns_manager.create_entry( |
584 | ++ uuid, address, "A", self.instance_dns_domain) |
585 | ++ self._setup_network_on_host(context, network) |
586 | ++ |
587 | ++ QUOTAS.commit(context, reservations) |
588 | ++ return address |
589 | ++ |
590 | ++ except Exception: |
591 | ++ with excutils.save_and_reraise_exception(): |
592 | ++ QUOTAS.rollback(context, reservations) |
593 | + |
594 | + def deallocate_fixed_ip(self, context, address, host=None, teardown=True): |
595 | + """Returns a fixed ip to the pool.""" |
596 | +@@ -1334,6 +1350,13 @@ class NetworkManager(manager.SchedulerDe |
597 | + context.elevated(read_deleted='yes'), |
598 | + fixed_ip_ref['instance_uuid']) |
599 | + |
600 | ++ try: |
601 | ++ reservations = QUOTAS.reserve(context, fixed_ips=-1) |
602 | ++ except Exception: |
603 | ++ reservations = None |
604 | ++ LOG.exception(_("Failed to update usages deallocating " |
605 | ++ "fixed IP")) |
606 | ++ |
607 | + self._do_trigger_security_group_members_refresh_for_instance( |
608 | + instance['uuid']) |
609 | + |
610 | +@@ -1373,6 +1396,10 @@ class NetworkManager(manager.SchedulerDe |
611 | + # callback will get called by nova-dhcpbridge. |
612 | + self.driver.release_dhcp(dev, address, vif['address']) |
613 | + |
614 | ++ # Commit the reservations |
615 | ++ if reservations: |
616 | ++ QUOTAS.commit(context, reservations) |
617 | ++ |
618 | + def lease_fixed_ip(self, context, address): |
619 | + """Called by dhcp-bridge when ip is leased.""" |
620 | + LOG.debug(_('Leased IP |%(address)s|'), locals(), context=context) |
621 | +--- a/nova/quota.py |
622 | ++++ b/nova/quota.py |
623 | +@@ -50,6 +50,10 @@ quota_opts = [ |
624 | + cfg.IntOpt('quota_floating_ips', |
625 | + default=10, |
626 | + help='number of floating ips allowed per project'), |
627 | ++ cfg.IntOpt('quota_fixed_ips', |
628 | ++ default=10, |
629 | ++ help=('number of fixed ips allowed per project (this should be ' |
630 | ++ 'at least the number of instances allowed)')), |
631 | + cfg.IntOpt('quota_metadata_items', |
632 | + default=128, |
633 | + help='number of metadata items allowed per instance'), |
634 | +@@ -778,6 +782,11 @@ def _sync_floating_ips(context, project_ |
635 | + context, project_id, session=session)) |
636 | + |
637 | + |
638 | ++def _sync_fixed_ips(context, project_id, session): |
639 | ++ return dict(fixed_ips=db.fixed_ip_count_by_project( |
640 | ++ context, project_id, session=session)) |
641 | ++ |
642 | ++ |
643 | + def _sync_security_groups(context, project_id, session): |
644 | + return dict(security_groups=db.security_group_count_by_project( |
645 | + context, project_id, session=session)) |
646 | +@@ -794,6 +803,7 @@ resources = [ |
647 | + ReservableResource('gigabytes', _sync_volumes, 'quota_gigabytes'), |
648 | + ReservableResource('floating_ips', _sync_floating_ips, |
649 | + 'quota_floating_ips'), |
650 | ++ ReservableResource('fixed_ips', _sync_fixed_ips, 'quota_fixed_ips'), |
651 | + AbsoluteResource('metadata_items', 'quota_metadata_items'), |
652 | + AbsoluteResource('injected_files', 'quota_injected_files'), |
653 | + AbsoluteResource('injected_file_content_bytes', |
654 | +--- a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py |
655 | ++++ b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py |
656 | +@@ -25,10 +25,11 @@ from nova.tests.api.openstack import fak |
657 | + def quota_set(class_name): |
658 | + return {'quota_class_set': {'id': class_name, 'metadata_items': 128, |
659 | + 'volumes': 10, 'gigabytes': 1000, 'ram': 51200, |
660 | +- 'floating_ips': 10, 'instances': 10, 'injected_files': 5, |
661 | +- 'cores': 20, 'injected_file_content_bytes': 10240, |
662 | +- 'security_groups': 10, 'security_group_rules': 20, |
663 | +- 'key_pairs': 100, 'injected_file_path_bytes': 255}} |
664 | ++ 'floating_ips': 10, 'fixed_ips': 10, 'instances': 10, |
665 | ++ 'injected_files': 5, 'cores': 20, |
666 | ++ 'injected_file_content_bytes': 10240, 'security_groups': 10, |
667 | ++ 'security_group_rules': 20, 'key_pairs': 100, |
668 | ++ 'injected_file_path_bytes': 255}} |
669 | + |
670 | + |
671 | + class QuotaClassSetsTest(test.TestCase): |
672 | +@@ -44,6 +45,7 @@ class QuotaClassSetsTest(test.TestCase): |
673 | + 'ram': 51200, |
674 | + 'volumes': 10, |
675 | + 'floating_ips': 10, |
676 | ++ 'fixed_ips': 10, |
677 | + 'metadata_items': 128, |
678 | + 'gigabytes': 1000, |
679 | + 'injected_files': 5, |
680 | +@@ -91,7 +93,8 @@ class QuotaClassSetsTest(test.TestCase): |
681 | + body = {'quota_class_set': {'instances': 50, 'cores': 50, |
682 | + 'ram': 51200, 'volumes': 10, |
683 | + 'gigabytes': 1000, 'floating_ips': 10, |
684 | +- 'metadata_items': 128, 'injected_files': 5, |
685 | ++ 'fixed_ips': 10, 'metadata_items': 128, |
686 | ++ 'injected_files': 5, |
687 | + 'injected_file_content_bytes': 10240, |
688 | + 'injected_file_path_bytes': 255, |
689 | + 'security_groups': 10, |
690 | +@@ -139,6 +142,7 @@ class QuotaTemplateXMLSerializerTest(tes |
691 | + gigabytes=40, |
692 | + ram=50, |
693 | + floating_ips=60, |
694 | ++ fixed_ips=10, |
695 | + instances=70, |
696 | + injected_files=80, |
697 | + security_groups=10, |
698 | +--- a/nova/tests/api/openstack/compute/contrib/test_quotas.py |
699 | ++++ b/nova/tests/api/openstack/compute/contrib/test_quotas.py |
700 | +@@ -26,11 +26,12 @@ from nova.tests.api.openstack import fak |
701 | + |
702 | + def quota_set(id): |
703 | + return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10, |
704 | +- 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, |
705 | +- 'instances': 10, 'injected_files': 5, 'cores': 20, |
706 | +- 'injected_file_content_bytes': 10240, |
707 | +- 'security_groups': 10, 'security_group_rules': 20, |
708 | +- 'key_pairs': 100, 'injected_file_path_bytes': 255}} |
709 | ++ 'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10, |
710 | ++ 'fixed_ips': 10, 'instances': 10, |
711 | ++ 'injected_files': 5, 'cores': 20, |
712 | ++ 'injected_file_content_bytes': 10240, |
713 | ++ 'security_groups': 10, 'security_group_rules': 20, |
714 | ++ 'key_pairs': 100, 'injected_file_path_bytes': 255}} |
715 | + |
716 | + |
717 | + class QuotaSetsTest(test.TestCase): |
718 | +@@ -46,6 +47,7 @@ class QuotaSetsTest(test.TestCase): |
719 | + 'ram': 51200, |
720 | + 'volumes': 10, |
721 | + 'floating_ips': 10, |
722 | ++ 'fixed_ips': 10, |
723 | + 'metadata_items': 128, |
724 | + 'gigabytes': 1000, |
725 | + 'injected_files': 5, |
726 | +@@ -88,6 +90,7 @@ class QuotaSetsTest(test.TestCase): |
727 | + 'volumes': 10, |
728 | + 'gigabytes': 1000, |
729 | + 'floating_ips': 10, |
730 | ++ 'fixed_ips': 10, |
731 | + 'metadata_items': 128, |
732 | + 'injected_files': 5, |
733 | + 'injected_file_path_bytes': 255, |
734 | +@@ -120,7 +123,7 @@ class QuotaSetsTest(test.TestCase): |
735 | + 'injected_file_path_bytes': 255, |
736 | + 'security_groups': 10, |
737 | + 'security_group_rules': 20, |
738 | +- 'key_pairs': 100}} |
739 | ++ 'key_pairs': 100, 'fixed_ips': 10}} |
740 | + |
741 | + req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me', |
742 | + use_admin_context=True) |
743 | +@@ -171,6 +174,7 @@ class QuotaXMLSerializerTest(test.TestCa |
744 | + gigabytes=40, |
745 | + ram=50, |
746 | + floating_ips=60, |
747 | ++ fixed_ips=10, |
748 | + instances=70, |
749 | + injected_files=80, |
750 | + security_groups=10, |
751 | +--- a/nova/tests/network/test_manager.py |
752 | ++++ b/nova/tests/network/test_manager.py |
753 | +@@ -30,6 +30,7 @@ from nova.openstack.common import import |
754 | + from nova.openstack.common import log as logging |
755 | + from nova.openstack.common import rpc |
756 | + import nova.policy |
757 | ++from nova import quota |
758 | + from nova import test |
759 | + from nova.tests import fake_network |
760 | + from nova import utils |
761 | +@@ -278,6 +279,7 @@ class FlatNetworkTestCase(test.TestCase) |
762 | + self.mox.StubOutWithMock(db, |
763 | + 'virtual_interface_get_by_instance_and_network') |
764 | + self.mox.StubOutWithMock(db, 'fixed_ip_update') |
765 | ++ self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') |
766 | + |
767 | + db.fixed_ip_update(mox.IgnoreArg(), |
768 | + mox.IgnoreArg(), |
769 | +@@ -291,6 +293,10 @@ class FlatNetworkTestCase(test.TestCase) |
770 | + db.instance_get(mox.IgnoreArg(), |
771 | + mox.IgnoreArg()).AndReturn({'security_groups': |
772 | + [{'id': 0}]}) |
773 | ++ |
774 | ++ quota.QUOTAS.reserve(mox.IgnoreArg(), |
775 | ++ fixed_ips=mox.IgnoreArg()).AndReturn(None) |
776 | ++ |
777 | + db.fixed_ip_associate_pool(mox.IgnoreArg(), |
778 | + mox.IgnoreArg(), |
779 | + mox.IgnoreArg()).AndReturn('192.168.0.101') |
780 | +@@ -310,6 +316,7 @@ class FlatNetworkTestCase(test.TestCase) |
781 | + self.mox.StubOutWithMock(db, |
782 | + 'virtual_interface_get_by_instance_and_network') |
783 | + self.mox.StubOutWithMock(db, 'fixed_ip_update') |
784 | ++ self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') |
785 | + |
786 | + db.fixed_ip_update(mox.IgnoreArg(), |
787 | + mox.IgnoreArg(), |
788 | +@@ -323,6 +330,10 @@ class FlatNetworkTestCase(test.TestCase) |
789 | + db.instance_get(mox.IgnoreArg(), |
790 | + mox.IgnoreArg()).AndReturn({'security_groups': |
791 | + [{'id': 0}]}) |
792 | ++ |
793 | ++ quota.QUOTAS.reserve(mox.IgnoreArg(), |
794 | ++ fixed_ips=mox.IgnoreArg()).AndReturn(None) |
795 | ++ |
796 | + db.fixed_ip_associate_pool(mox.IgnoreArg(), |
797 | + mox.IgnoreArg(), |
798 | + mox.IgnoreArg()).AndReturn('192.168.0.101') |
799 | +@@ -376,6 +387,7 @@ class FlatNetworkTestCase(test.TestCase) |
800 | + self.mox.StubOutWithMock(db, |
801 | + 'virtual_interface_get_by_instance_and_network') |
802 | + self.mox.StubOutWithMock(db, 'fixed_ip_update') |
803 | ++ self.mox.StubOutWithMock(quota.QUOTAS, 'reserve') |
804 | + |
805 | + db.fixed_ip_update(mox.IgnoreArg(), |
806 | + mox.IgnoreArg(), |
807 | +@@ -390,6 +402,9 @@ class FlatNetworkTestCase(test.TestCase) |
808 | + mox.IgnoreArg()).AndReturn({'security_groups': |
809 | + [{'id': 0}]}) |
810 | + |
811 | ++ quota.QUOTAS.reserve(mox.IgnoreArg(), |
812 | ++ fixed_ips=mox.IgnoreArg()).AndReturn(None) |
813 | ++ |
814 | + db.fixed_ip_associate_pool(mox.IgnoreArg(), |
815 | + mox.IgnoreArg(), |
816 | + mox.IgnoreArg()).AndReturn(fixedip) |
817 | +--- a/nova/tests/test_quota.py |
818 | ++++ b/nova/tests/test_quota.py |
819 | +@@ -723,6 +723,7 @@ class DbQuotaDriverTestCase(test.TestCas |
820 | + quota_volumes=10, |
821 | + quota_gigabytes=1000, |
822 | + quota_floating_ips=10, |
823 | ++ quota_fixed_ips=10, |
824 | + quota_metadata_items=128, |
825 | + quota_injected_files=5, |
826 | + quota_injected_file_content_bytes=10 * 1024, |
827 | +@@ -755,6 +756,7 @@ class DbQuotaDriverTestCase(test.TestCas |
828 | + volumes=10, |
829 | + gigabytes=1000, |
830 | + floating_ips=10, |
831 | ++ fixed_ips=10, |
832 | + metadata_items=128, |
833 | + injected_files=5, |
834 | + injected_file_content_bytes=10 * 1024, |
835 | +@@ -791,6 +793,7 @@ class DbQuotaDriverTestCase(test.TestCas |
836 | + volumes=10, |
837 | + gigabytes=500, |
838 | + floating_ips=10, |
839 | ++ fixed_ips=10, |
840 | + metadata_items=64, |
841 | + injected_files=5, |
842 | + injected_file_content_bytes=5 * 1024, |
843 | +@@ -847,6 +850,7 @@ class DbQuotaDriverTestCase(test.TestCas |
844 | + self._stub_quota_class_get_all_by_name() |
845 | + |
846 | + def test_get_project_quotas(self): |
847 | ++ self.maxDiff = None |
848 | + self._stub_get_by_project() |
849 | + result = self.driver.get_project_quotas( |
850 | + FakeContext('test_project', 'test_class'), |
851 | +@@ -888,6 +892,11 @@ class DbQuotaDriverTestCase(test.TestCas |
852 | + in_use=2, |
853 | + reserved=0, |
854 | + ), |
855 | ++ fixed_ips=dict( |
856 | ++ limit=10, |
857 | ++ in_use=0, |
858 | ++ reserved=0, |
859 | ++ ), |
860 | + metadata_items=dict( |
861 | + limit=64, |
862 | + in_use=0, |
863 | +@@ -926,6 +935,7 @@ class DbQuotaDriverTestCase(test.TestCas |
864 | + )) |
865 | + |
866 | + def test_get_project_quotas_alt_context_no_class(self): |
867 | ++ self.maxDiff = None |
868 | + self._stub_get_by_project() |
869 | + result = self.driver.get_project_quotas( |
870 | + FakeContext('other_project', 'other_class'), |
871 | +@@ -966,6 +976,11 @@ class DbQuotaDriverTestCase(test.TestCas |
872 | + in_use=2, |
873 | + reserved=0, |
874 | + ), |
875 | ++ fixed_ips=dict( |
876 | ++ limit=10, |
877 | ++ in_use=0, |
878 | ++ reserved=0, |
879 | ++ ), |
880 | + metadata_items=dict( |
881 | + limit=128, |
882 | + in_use=0, |
883 | +@@ -1004,6 +1019,7 @@ class DbQuotaDriverTestCase(test.TestCas |
884 | + )) |
885 | + |
886 | + def test_get_project_quotas_alt_context_with_class(self): |
887 | ++ self.maxDiff = None |
888 | + self._stub_get_by_project() |
889 | + result = self.driver.get_project_quotas( |
890 | + FakeContext('other_project', 'other_class'), |
891 | +@@ -1045,6 +1061,11 @@ class DbQuotaDriverTestCase(test.TestCas |
892 | + in_use=2, |
893 | + reserved=0, |
894 | + ), |
895 | ++ fixed_ips=dict( |
896 | ++ limit=10, |
897 | ++ in_use=0, |
898 | ++ reserved=0, |
899 | ++ ), |
900 | + metadata_items=dict( |
901 | + limit=64, |
902 | + in_use=0, |
903 | +@@ -1145,6 +1166,9 @@ class DbQuotaDriverTestCase(test.TestCas |
904 | + floating_ips=dict( |
905 | + limit=10, |
906 | + ), |
907 | ++ fixed_ips=dict( |
908 | ++ limit=10, |
909 | ++ ), |
910 | + metadata_items=dict( |
911 | + limit=64, |
912 | + ), |
913 | |
914 | === modified file 'debian/patches/series' |
915 | --- debian/patches/series 2012-11-02 13:33:39 +0000 |
916 | +++ debian/patches/series 2013-03-22 13:42:13 +0000 |
917 | @@ -4,3 +4,5 @@ |
918 | fix-docs-build-without-network.patch |
919 | avoid_setuptools_git_dependency.patch |
920 | rbd-security.patch |
921 | +CVE-2013-1838.patch |
922 | +CVE-2013-0335.patch |