Merge "Add hardware types to the hash ring"

This commit is contained in:
Jenkins 2017-01-20 11:02:56 +00:00 committed by Gerrit Code Review
commit bd29a4d39d
6 changed files with 164 additions and 15 deletions

View File

@ -50,6 +50,7 @@ class HashRingManager(object):
def _load_hash_rings(self):
rings = {}
d2c = self.dbapi.get_active_driver_dict()
d2c.update(self.dbapi.get_active_hardware_type_dict())
for driver_name, hosts in d2c.items():
rings[driver_name] = hashring.HashRing(

View File

@ -530,6 +530,19 @@ class Connection(object):
driverB: set([host2, host3])}
"""
@abc.abstractmethod
def get_active_hardware_type_dict(self):
"""Retrieve hardware types for the registered and active conductors.
:returns: A dict which maps hardware type names to the set of hosts
which support them. For example:
::
{hardware-type-a: set([host1, host2]),
hardware-type-b: set([host2, host3])}
"""
@abc.abstractmethod
def get_offline_conductors(self):
"""Get a list conductor hostnames that are offline (dead).

View File

@ -177,6 +177,16 @@ def _paginate_query(model, limit=None, marker=None, sort_key=None,
return query.all()
def _filter_active_conductors(query, interval=None):
if interval is None:
interval = CONF.conductor.heartbeat_timeout
limit = timeutils.utcnow() - datetime.timedelta(seconds=interval)
query = (query.filter(models.Conductor.online.is_(True))
.filter(models.Conductor.updated_at >= limit))
return query
class Connection(api.Connection):
"""SqlAlchemy connection."""
@ -782,11 +792,8 @@ class Connection(api.Connection):
if interval is None:
interval = CONF.conductor.heartbeat_timeout
limit = timeutils.utcnow() - datetime.timedelta(seconds=interval)
result = (model_query(models.Conductor)
.filter_by(online=True)
.filter(models.Conductor.updated_at >= limit)
.all())
query = model_query(models.Conductor)
result = _filter_active_conductors(query, interval=interval)
# build mapping of drivers to the set of hosts which support them
d2c = collections.defaultdict(set)
@ -795,6 +802,17 @@ class Connection(api.Connection):
d2c[driver].add(row['hostname'])
return d2c
def get_active_hardware_type_dict(self):
query = (model_query(models.ConductorHardwareInterfaces,
models.Conductor)
.join(models.Conductor))
result = _filter_active_conductors(query)
d2c = collections.defaultdict(set)
for iface_row, cdr_row in result:
d2c[iface_row['hardware_type']].add(cdr_row['hostname'])
return d2c
def get_offline_conductors(self):
interval = CONF.conductor.heartbeat_timeout
limit = timeutils.utcnow() - datetime.timedelta(seconds=interval)

View File

@ -31,20 +31,28 @@ class HashRingManagerTestCase(db_base.DbTestCase):
self.ring_manager = hash_ring.HashRingManager()
def register_conductors(self):
self.dbapi.register_conductor({
c1 = self.dbapi.register_conductor({
'hostname': 'host1',
'drivers': ['driver1', 'driver2'],
})
self.dbapi.register_conductor({
c2 = self.dbapi.register_conductor({
'hostname': 'host2',
'drivers': ['driver1'],
})
for c in (c1, c2):
self.dbapi.register_conductor_hardware_interfaces(
c.id, 'hardware-type', 'deploy', ['iscsi', 'direct'], 'iscsi')
def test_hash_ring_manager_get_ring_success(self):
self.register_conductors()
ring = self.ring_manager['driver1']
self.assertEqual(sorted(['host1', 'host2']), sorted(ring.nodes))
def test_hash_ring_manager_hardware_type_success(self):
self.register_conductors()
ring = self.ring_manager['hardware-type']
self.assertEqual(sorted(['host1', 'host2']), sorted(ring.nodes))
def test_hash_ring_manager_driver_not_found(self):
self.register_conductors()
self.assertRaises(exception.DriverNotFound,

View File

@ -40,9 +40,16 @@ class DbConductorTestCase(base.DbTestCase):
self.dbapi.register_conductor(c)
self.dbapi.register_conductor(c, update_existing=True)
def _create_test_cdr(self, **kwargs):
def _create_test_cdr(self, hardware_types=None, **kwargs):
hardware_types = hardware_types or []
c = utils.get_test_conductor(**kwargs)
return self.dbapi.register_conductor(c)
cdr = self.dbapi.register_conductor(c)
for ht in hardware_types:
self.dbapi.register_conductor_hardware_interfaces(cdr.id, ht,
'power',
['ipmi', 'fake'],
'ipmi')
return cdr
def test_register_conductor_hardware_interfaces(self):
c = self._create_test_cdr()
@ -187,7 +194,7 @@ class DbConductorTestCase(base.DbTestCase):
def test_get_active_driver_dict_one_host_one_driver(self, mock_utcnow):
h = 'fake-host'
d = 'fake-driver'
expected = {d: set([h])}
expected = {d: {h}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(hostname=h, drivers=[d])
@ -199,7 +206,7 @@ class DbConductorTestCase(base.DbTestCase):
h = 'fake-host'
d1 = 'driver-one'
d2 = 'driver-two'
expected = {d1: set([h]), d2: set([h])}
expected = {d1: {h}, d2: {h}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(hostname=h, drivers=[d1, d2])
@ -211,7 +218,7 @@ class DbConductorTestCase(base.DbTestCase):
h1 = 'host-one'
h2 = 'host-two'
d = 'fake-driver'
expected = {d: set([h1, h2])}
expected = {d: {h1, h2}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(id=1, hostname=h1, drivers=[d])
@ -226,7 +233,7 @@ class DbConductorTestCase(base.DbTestCase):
h3 = 'host-three'
d1 = 'driver-one'
d2 = 'driver-two'
expected = {d1: set([h1, h2]), d2: set([h2, h3])}
expected = {d1: {h1, h2}, d2: {h2, h3}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(id=1, hostname=h1, drivers=[d1])
@ -254,16 +261,114 @@ class DbConductorTestCase(base.DbTestCase):
# verify that old-host does not show up in current list
one_minute = 60
expected = {d: set([h2]), d2: set([h2])}
expected = {d: {h2}, d2: {h2}}
result = self.dbapi.get_active_driver_dict(interval=one_minute)
self.assertEqual(expected, result)
# change the interval, and verify that old-host appears
two_minute = one_minute * 2
expected = {d: set([h1, h2]), d1: set([h1]), d2: set([h2])}
expected = {d: {h1, h2}, d1: {h1}, d2: {h2}}
result = self.dbapi.get_active_driver_dict(interval=two_minute)
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_one_host_no_ht(self, mock_utcnow):
h = 'fake-host'
expected = {}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(hostname=h, drivers=[], hardware_types=[])
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_one_host_one_ht(self, mock_utcnow):
h = 'fake-host'
ht = 'hardware-type'
expected = {ht: {h}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(hostname=h, drivers=[], hardware_types=[ht])
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_one_host_many_ht(self, mock_utcnow):
h = 'fake-host'
ht1 = 'hardware-type'
ht2 = 'another-hardware-type'
expected = {ht1: {h}, ht2: {h}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(hostname=h, drivers=[],
hardware_types=[ht1, ht2])
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_many_host_one_ht(self, mock_utcnow):
h1 = 'host-one'
h2 = 'host-two'
ht = 'hardware-type'
expected = {ht: {h1, h2}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(id=1, hostname=h1, drivers=[],
hardware_types=[ht])
self._create_test_cdr(id=2, hostname=h2, drivers=[],
hardware_types=[ht])
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_many_host_many_ht(self,
mock_utcnow):
h1 = 'host-one'
h2 = 'host-two'
ht1 = 'hardware-type'
ht2 = 'another-hardware-type'
expected = {ht1: {h1, h2}, ht2: {h1, h2}}
mock_utcnow.return_value = datetime.datetime.utcnow()
self._create_test_cdr(id=1, hostname=h1, drivers=[],
hardware_types=[ht1, ht2])
self._create_test_cdr(id=2, hostname=h2, drivers=[],
hardware_types=[ht1, ht2])
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_active_hardware_type_dict_with_old_conductor(self,
mock_utcnow):
past = datetime.datetime(2000, 1, 1, 0, 0)
present = past + datetime.timedelta(minutes=2)
ht = 'hardware-type'
h1 = 'old-host'
ht1 = 'old-hardware-type'
mock_utcnow.return_value = past
self._create_test_cdr(id=1, hostname=h1, drivers=[],
hardware_types=[ht, ht1])
h2 = 'new-host'
ht2 = 'new-hardware-type'
mock_utcnow.return_value = present
self._create_test_cdr(id=2, hostname=h2, drivers=[],
hardware_types=[ht, ht2])
# verify that old-host does not show up in current list
self.config(heartbeat_timeout=60, group='conductor')
expected = {ht: {h2}, ht2: {h2}}
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
# change the heartbeat timeout, and verify that old-host appears
self.config(heartbeat_timeout=120, group='conductor')
expected = {ht: {h1, h2}, ht1: {h1}, ht2: {h2}}
result = self.dbapi.get_active_hardware_type_dict()
self.assertEqual(expected, result)
@mock.patch.object(timeutils, 'utcnow', autospec=True)
def test_get_offline_conductors(self, mock_utcnow):
self.config(heartbeat_timeout=60, group='conductor')

View File

@ -0,0 +1,4 @@
---
features:
- Using a dynamic driver in a node's driver field is now possible, though
customizing the interfaces is not yet exposed in the REST API.