1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright (c) 2012 OpenStack, LLC.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
18
"""Tests for compute resource tracking"""
22
from nova.compute import resource_tracker
23
from nova.compute import task_states
24
from nova.compute import vm_states
26
from nova import exception
27
from nova.openstack.common import timeutils
29
from nova.virt import driver
32
class FakeContext(object):
33
def __init__(self, is_admin=False):
34
self.is_admin = is_admin
37
return FakeContext(is_admin=True)
40
class UnsupportedVirtDriver(driver.ComputeDriver):
41
"""Pretend version of a lame virt driver"""
42
def get_available_resource(self):
43
# no support for getting resource usage info
47
class FakeVirtDriver(driver.ComputeDriver):
54
self.memory_mb_used = 0
55
self.local_gb_used = 0
57
def get_available_resource(self):
60
'memory_mb': self.memory_mb,
61
'local_gb': self.local_gb,
63
'memory_mb_used': self.memory_mb_used,
64
'local_gb_used': self.local_gb_used,
65
'hypervisor_type': 'fake',
66
'hypervisor_version': 0,
67
'hypervisor_hostname': 'fakehost',
73
class BaseTestCase(test.TestCase):
76
super(BaseTestCase, self).setUp()
78
self.context = FakeContext()
84
"vm_state": vm_states.BUILDING,
91
self.stubs.Set(db, 'instance_get_all_by_filters',
92
self._fake_instance_get_all_by_filters)
94
def _create_compute_node(self, values=None):
106
"current_workload": 1,
109
"stats": [{"key": "num_instances", "value": "1"}]
112
compute.update(values)
115
def _create_service(self, host="fakehost", compute=None):
122
"binary": "nova-compute",
124
"compute_node": compute,
128
def _fake_instance_get_all_by_filters(self, ctx, filters, **kwargs):
131
def _tracker(self, unsupported=False):
135
driver = UnsupportedVirtDriver()
137
driver = FakeVirtDriver()
139
tracker = resource_tracker.ResourceTracker(host, driver)
143
class UnsupportedDriverTestCase(BaseTestCase):
144
"""Resource tracking should be disabled when the virt driver doesn't
148
super(UnsupportedDriverTestCase, self).setUp()
149
self.tracker = self._tracker(unsupported=True)
150
# seed tracker with data:
151
self.tracker.update_available_resource(self.context)
153
def testDisabled(self):
154
# disabled = no compute node stats
155
self.assertTrue(self.tracker.disabled)
156
self.assertEqual(None, self.tracker.compute_node)
158
def testDisabledClaim(self):
160
claim = self.tracker.begin_resource_claim(self.context, 1, 1)
161
self.assertEqual(None, claim)
163
def testDisabledContextClaim(self):
164
# basic context manager variation:
165
with self.tracker.resource_claim(self.context, 1, 1):
167
self.assertEqual(0, len(self.tracker.claims))
169
def testDisabledInstanceClaim(self):
170
# instance variation:
171
claim = self.tracker.begin_instance_resource_claim(self.context,
173
self.assertEqual(None, claim)
175
def testDisabledInstanceContextClaim(self):
176
# instance context manager variation:
177
with self.tracker.instance_resource_claim(self.context,
180
self.assertEqual(0, len(self.tracker.claims))
182
def testDisabledFinishClaim(self):
183
self.assertEqual(None, self.tracker.finish_resource_claim(None))
185
def testDisabledAbortClaim(self):
186
self.assertEqual(None, self.tracker.abort_resource_claim(self.context,
189
def testDisabledFreeResources(self):
190
self.tracker.free_resources(self.context)
191
self.assertTrue(self.tracker.disabled)
192
self.assertEqual(None, self.tracker.compute_node)
195
class MissingServiceTestCase(BaseTestCase):
197
super(MissingServiceTestCase, self).setUp()
198
self.context = FakeContext(is_admin=True)
199
self.tracker = self._tracker()
201
def testMissingService(self):
202
"""No service record in DB."""
203
self.tracker.update_available_resource(self.context)
204
self.assertTrue(self.tracker.disabled)
207
class MissingComputeNodeTestCase(BaseTestCase):
209
super(MissingComputeNodeTestCase, self).setUp()
210
self.tracker = self._tracker()
212
self.stubs.Set(db, 'service_get_all_compute_by_host',
213
self._fake_service_get_all_compute_by_host)
214
self.stubs.Set(db, 'compute_node_create',
215
self._fake_create_compute_node)
217
def _fake_create_compute_node(self, context, values):
219
return self._create_compute_node()
221
def _fake_service_get_all_compute_by_host(self, ctx, host):
222
# return a service with no joined compute
223
service = self._create_service()
226
def testCreatedComputeNode(self):
227
self.tracker.update_available_resource(self.context)
228
self.assertTrue(self.created)
230
def testEnabled(self):
231
self.tracker.update_available_resource(self.context)
232
self.assertFalse(self.tracker.disabled)
235
class ResourceTestCase(BaseTestCase):
237
super(ResourceTestCase, self).setUp()
238
self.tracker = self._tracker()
239
self.stubs.Set(db, 'service_get_all_compute_by_host',
240
self._fake_service_get_all_compute_by_host)
241
self.stubs.Set(db, 'compute_node_update',
242
self._fake_compute_node_update)
244
self.tracker.update_available_resource(self.context)
246
def _fake_service_get_all_compute_by_host(self, ctx, host):
247
self.compute = self._create_compute_node()
248
self.service = self._create_service(host, compute=self.compute)
249
return [self.service]
251
def _fake_compute_node_update(self, ctx, compute_node_id, values,
254
values['stats'] = [{"key": "num_instances", "value": "1"}]
255
self.compute.update(values)
258
def testFreeRamResourceValue(self):
259
driver = FakeVirtDriver()
260
mem_free = driver.memory_mb - driver.memory_mb_used
261
self.assertEqual(mem_free, self.tracker.compute_node['free_ram_mb'])
263
def testFreeDiskResourceValue(self):
264
driver = FakeVirtDriver()
265
mem_free = driver.local_gb - driver.local_gb_used
266
self.assertEqual(mem_free, self.tracker.compute_node['free_disk_gb'])
268
def testUpdateComputeNode(self):
269
self.assertFalse(self.tracker.disabled)
270
self.assertTrue(self.updated)
272
def testInsufficientMemoryClaim(self):
273
"""Exceed memory limit of 5MB"""
274
claim = self.tracker.begin_resource_claim(self.context, memory_mb=2,
276
self.assertNotEqual(None, claim)
278
claim = self.tracker.begin_resource_claim(self.context, memory_mb=3,
280
self.assertNotEqual(None, claim)
282
claim = self.tracker.begin_resource_claim(self.context, memory_mb=1,
284
self.assertEqual(None, claim)
286
def testInsufficientMemoryClaimWithOversubscription(self):
287
"""Exceed oversubscribed memory limit of 10MB"""
288
claim = self.tracker.begin_resource_claim(self.context, memory_mb=10,
289
disk_gb=0, memory_mb_limit=10)
290
self.assertNotEqual(None, claim)
292
claim = self.tracker.begin_resource_claim(self.context, memory_mb=1,
293
disk_gb=0, memory_mb_limit=10)
294
self.assertEqual(None, claim)
296
def testInsufficientDiskClaim(self):
297
"""Exceed disk limit of 5GB"""
298
claim = self.tracker.begin_resource_claim(self.context, memory_mb=0,
300
self.assertNotEqual(None, claim)
302
claim = self.tracker.begin_resource_claim(self.context, memory_mb=0,
304
self.assertNotEqual(None, claim)
306
claim = self.tracker.begin_resource_claim(self.context, memory_mb=0,
308
self.assertEqual(None, claim)
310
def testClaimAndFinish(self):
311
self.assertEqual(5, self.tracker.compute_node['memory_mb'])
312
self.assertEqual(0, self.tracker.compute_node['memory_mb_used'])
314
self.assertEqual(6, self.tracker.compute_node['local_gb'])
315
self.assertEqual(0, self.tracker.compute_node['local_gb_used'])
319
claim = self.tracker.begin_resource_claim(self.context, claim_mem,
322
self.assertEqual(5, self.compute["memory_mb"])
323
self.assertEqual(claim_mem, self.compute["memory_mb_used"])
324
self.assertEqual(5 - claim_mem, self.compute["free_ram_mb"])
326
self.assertEqual(6, self.compute["local_gb"])
327
self.assertEqual(claim_disk, self.compute["local_gb_used"])
328
self.assertEqual(6 - claim_disk, self.compute["free_disk_gb"])
330
# 1st pretend that the compute operation finished and claimed the
331
# desired resources from the virt layer
332
driver = self.tracker.driver
333
driver.memory_mb_used = claim_mem
334
driver.local_gb_used = claim_disk
336
# 2nd update compute node from the virt layer. because the claim is
337
# in-progress (unfinished), the audit will actually mark the resources
339
self.tracker.update_available_resource(self.context)
341
self.assertEqual(2 * claim_mem,
342
self.compute['memory_mb_used'])
343
self.assertEqual(5 - (2 * claim_mem),
344
self.compute['free_ram_mb'])
346
self.assertEqual(2 * claim_disk,
347
self.compute['local_gb_used'])
348
self.assertEqual(6 - (2 * claim_disk),
349
self.compute['free_disk_gb'])
351
# Finally, finish the claimm and update from the virt layer again.
352
# Resource usage will be consistent again:
353
self.tracker.finish_resource_claim(claim)
354
self.tracker.update_available_resource(self.context)
356
self.assertEqual(claim_mem,
357
self.compute['memory_mb_used'])
358
self.assertEqual(5 - claim_mem,
359
self.compute['free_ram_mb'])
361
self.assertEqual(claim_disk,
362
self.compute['local_gb_used'])
363
self.assertEqual(6 - claim_disk,
364
self.compute['free_disk_gb'])
366
def testClaimAndAbort(self):
367
self.assertEqual(5, self.tracker.compute_node['memory_mb'])
368
self.assertEqual(0, self.tracker.compute_node['memory_mb_used'])
370
self.assertEqual(6, self.tracker.compute_node['local_gb'])
371
self.assertEqual(0, self.tracker.compute_node['local_gb_used'])
375
claim = self.tracker.begin_resource_claim(self.context, claim_mem,
378
self.assertEqual(5, self.compute["memory_mb"])
379
self.assertEqual(claim_mem, self.compute["memory_mb_used"])
380
self.assertEqual(5 - claim_mem, self.compute["free_ram_mb"])
382
self.assertEqual(6, self.compute["local_gb"])
383
self.assertEqual(claim_disk, self.compute["local_gb_used"])
384
self.assertEqual(6 - claim_disk, self.compute["free_disk_gb"])
386
self.tracker.abort_resource_claim(self.context, claim)
388
self.assertEqual(5, self.compute["memory_mb"])
389
self.assertEqual(0, self.compute["memory_mb_used"])
390
self.assertEqual(5, self.compute["free_ram_mb"])
392
self.assertEqual(6, self.compute["local_gb"])
393
self.assertEqual(0, self.compute["local_gb_used"])
394
self.assertEqual(6, self.compute["free_disk_gb"])
396
def testExpiredClaims(self):
397
"""Test that old claims get cleaned up automatically if not finished
398
or aborted explicitly.
400
claim = self.tracker.begin_resource_claim(self.context, memory_mb=2,
402
claim.expire_ts = timeutils.utcnow_ts() - 1
403
self.assertTrue(claim.is_expired())
405
# and an unexpired claim
406
claim2 = self.tracker.begin_resource_claim(self.context, memory_mb=1,
409
self.assertEqual(2, len(self.tracker.claims))
410
self.assertEqual(2 + 1, self.tracker.compute_node['memory_mb_used'])
411
self.assertEqual(2 + 1, self.tracker.compute_node['local_gb_used'])
413
# expired claims get expunged when audit runs:
414
self.tracker.update_available_resource(self.context)
416
self.assertEqual(1, len(self.tracker.claims))
417
self.assertEqual(1, self.tracker.compute_node['memory_mb_used'])
418
self.assertEqual(1, self.tracker.compute_node['local_gb_used'])
420
# and just call finish & abort to ensure expired claims do not cause
421
# any other explosions:
422
self.tracker.abort_resource_claim(self.context, claim)
423
self.tracker.finish_resource_claim(claim)
425
def testInstanceClaim(self):
426
self.tracker.begin_instance_resource_claim(self.context,
428
self.assertEqual(1, self.tracker.compute_node['memory_mb_used'])
429
self.assertEqual(2, self.tracker.compute_node['local_gb_used'])
431
def testContextClaim(self):
432
with self.tracker.resource_claim(self.context, memory_mb=1, disk_gb=1):
433
# <insert exciting things that utilize resources>
434
self.assertEqual(1, self.tracker.compute_node['memory_mb_used'])
435
self.assertEqual(1, self.tracker.compute_node['local_gb_used'])
436
self.assertEqual(1, self.compute['memory_mb_used'])
437
self.assertEqual(1, self.compute['local_gb_used'])
439
self.tracker.update_available_resource(self.context)
440
self.assertEqual(0, self.tracker.compute_node['memory_mb_used'])
441
self.assertEqual(0, self.tracker.compute_node['local_gb_used'])
442
self.assertEqual(0, self.compute['memory_mb_used'])
443
self.assertEqual(0, self.compute['local_gb_used'])
445
def testContextClaimWithException(self):
447
with self.tracker.resource_claim(self.context, memory_mb=1,
449
# <insert exciting things that utilize resources>
450
raise Exception("THE SKY IS FALLING")
454
self.tracker.update_available_resource(self.context)
455
self.assertEqual(0, self.tracker.compute_node['memory_mb_used'])
456
self.assertEqual(0, self.tracker.compute_node['local_gb_used'])
457
self.assertEqual(0, self.compute['memory_mb_used'])
458
self.assertEqual(0, self.compute['local_gb_used'])
460
def testInstanceContextClaim(self):
461
with self.tracker.instance_resource_claim(self.context,
463
# <insert exciting things that utilize resources>
464
self.assertEqual(1, self.tracker.compute_node['memory_mb_used'])
465
self.assertEqual(2, self.tracker.compute_node['local_gb_used'])
466
self.assertEqual(1, self.compute['memory_mb_used'])
467
self.assertEqual(2, self.compute['local_gb_used'])
469
self.tracker.update_available_resource(self.context)
470
self.assertEqual(0, self.tracker.compute_node['memory_mb_used'])
471
self.assertEqual(0, self.tracker.compute_node['local_gb_used'])
472
self.assertEqual(0, self.compute['memory_mb_used'])
473
self.assertEqual(0, self.compute['local_gb_used'])
475
def testUpdateLoadStatsForInstance(self):
476
self.assertFalse(self.tracker.disabled)
477
self.assertEqual(0, self.tracker.compute_node['current_workload'])
479
old_ref = self.instance_ref
480
old_ref['task_state'] = task_states.SCHEDULING
481
with self.tracker.instance_resource_claim(self.context, old_ref):
484
self.assertEqual(1, self.tracker.compute_node['current_workload'])
486
new_ref = copy.copy(old_ref)
487
new_ref['vm_state'] = vm_states.ACTIVE
488
new_ref['task_state'] = None
490
self.tracker.update_load_stats_for_instance(self.context, old_ref,
492
self.assertEqual(0, self.tracker.compute_node['current_workload'])