Add hardware types to the hash ring
This loads hardware types into the hash ring, along with the drivers that are currently there. This allows us to make RPC calls for nodes that are using a dynamic driver. Adds a dbapi method `get_active_hardware_type_dict`, similar to `get_active_driver_dict`, to get a mapping of hardware types to active conductors. Change-Id: I50c1428568bd8cbe4ef252d56a6278f1a54dfcdb Partial-Bug: #1524745
This commit is contained in:
parent
da6b0f2aad
commit
3c45f2fd1b
@ -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(
|
||||
|
@ -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).
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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')
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user