diff --git a/nova/cells/state.py b/nova/cells/state.py index a53ae50cafea..e83396cf5686 100644 --- a/nova/cells/state.py +++ b/nova/cells/state.py @@ -16,6 +16,7 @@ """ CellState Manager """ +import collections import copy import datetime import functools @@ -259,7 +260,10 @@ class CellStateManager(base.Base): ctxt = context.get_admin_context() reserve_level = CONF.cells.reserve_percent / 100.0 - compute_hosts = {} + + def _defaultdict_int(): + return collections.defaultdict(int) + compute_hosts = collections.defaultdict(_defaultdict_int) def _get_compute_hosts(): service_refs = {service.host: service @@ -273,11 +277,13 @@ class CellStateManager(base.Base): if not service or service['disabled']: continue - compute_hosts[host] = { - 'free_ram_mb': compute['free_ram_mb'], - 'free_disk_mb': compute['free_disk_gb'] * 1024, - 'total_ram_mb': compute['memory_mb'], - 'total_disk_mb': compute['local_gb'] * 1024} + chost = compute_hosts[host] + chost['free_ram_mb'] += compute['free_ram_mb'] + free_disk = compute['free_disk_gb'] * 1024 + chost['free_disk_mb'] += free_disk + chost['total_ram_mb'] += compute['memory_mb'] + total_disk = compute['local_gb'] * 1024 + chost['total_disk_mb'] += total_disk _get_compute_hosts() if not compute_hosts: diff --git a/nova/tests/unit/cells/test_cells_state_manager.py b/nova/tests/unit/cells/test_cells_state_manager.py index a6179082a0d4..a35415e85928 100644 --- a/nova/tests/unit/cells/test_cells_state_manager.py +++ b/nova/tests/unit/cells/test_cells_state_manager.py @@ -38,6 +38,13 @@ FAKE_COMPUTES = [ ('host4', 1024, 100, 300, 30), ] +FAKE_COMPUTES_N_TO_ONE = [ + ('host1', 1024, 100, 0, 0), + ('host1', 1024, 100, -1, -1), + ('host2', 1024, 100, 1024, 100), + ('host2', 1024, 100, 300, 30), +] + # NOTE(alaski): It's important to have multiple types that end up having the # same memory and disk requirements. So two types need the same first value, # and two need the second and third values to add up to the same thing. @@ -49,16 +56,12 @@ FAKE_ITYPES = [ ] -@classmethod -def _fake_compute_node_get_all(cls, context): - def _node(host, total_mem, total_disk, free_mem, free_disk): - return objects.ComputeNode(host=host, - memory_mb=total_mem, - local_gb=total_disk, - free_ram_mb=free_mem, - free_disk_gb=free_disk) - - return [_node(*fake) for fake in FAKE_COMPUTES] +def _create_fake_node(host, total_mem, total_disk, free_mem, free_disk): + return objects.ComputeNode(host=host, + memory_mb=total_mem, + local_gb=total_disk, + free_ram_mb=free_mem, + free_disk_gb=free_disk) @classmethod @@ -69,6 +72,16 @@ def _fake_service_get_all_by_binary(cls, context, binary): return [_node(*fake) for fake in FAKE_COMPUTES] +@classmethod +def _fake_compute_node_get_all(cls, context): + return [_create_fake_node(*fake) for fake in FAKE_COMPUTES] + + +@classmethod +def _fake_compute_node_n_to_one_get_all(cls, context): + return [_create_fake_node(*fake) for fake in FAKE_COMPUTES_N_TO_ONE] + + def _fake_cell_get_all(context): return [] @@ -187,6 +200,35 @@ class TestCellsStateManager(test.NoDBTestCase): return my_state.capacities +class TestCellsStateManagerNToOne(TestCellsStateManager): + def setUp(self): + super(TestCellsStateManagerNToOne, self).setUp() + + self.stubs.Set(objects.ComputeNodeList, 'get_all', + _fake_compute_node_n_to_one_get_all) + + def test_capacity_part_reserve(self): + # utilize half the cell's free capacity + cap = self._capacity(50.0) + + cell_free_ram = sum(compute[3] for compute in FAKE_COMPUTES_N_TO_ONE) + self.assertEqual(cell_free_ram, cap['ram_free']['total_mb']) + + cell_free_disk = (1024 * + sum(compute[4] for compute in FAKE_COMPUTES_N_TO_ONE)) + self.assertEqual(cell_free_disk, cap['disk_free']['total_mb']) + + self.assertEqual(0, cap['ram_free']['units_by_mb']['0']) + self.assertEqual(0, cap['disk_free']['units_by_mb']['0']) + + units = 6 # 6 from host 2 + self.assertEqual(units, cap['ram_free']['units_by_mb']['50']) + + sz = 25 * 1024 + units = 1 # 1 on host 2 + self.assertEqual(units, cap['disk_free']['units_by_mb'][str(sz)]) + + class TestCellStateManagerException(test.NoDBTestCase): @mock.patch.object(time, 'sleep') def test_init_db_error(self, mock_sleep):