diff --git a/ironic/common/hash_ring.py b/ironic/common/hash_ring.py
index 95cfbdef30..61e1b385f0 100644
--- a/ironic/common/hash_ring.py
+++ b/ironic/common/hash_ring.py
@@ -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(
diff --git a/ironic/db/api.py b/ironic/db/api.py
index d9f16e95ae..92357f6cd8 100644
--- a/ironic/db/api.py
+++ b/ironic/db/api.py
@@ -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).
diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py
index da7c6f5252..69ddbca942 100644
--- a/ironic/db/sqlalchemy/api.py
+++ b/ironic/db/sqlalchemy/api.py
@@ -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)
diff --git a/ironic/tests/unit/common/test_hash_ring.py b/ironic/tests/unit/common/test_hash_ring.py
index 798980fd79..94cfbb56a6 100644
--- a/ironic/tests/unit/common/test_hash_ring.py
+++ b/ironic/tests/unit/common/test_hash_ring.py
@@ -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,
diff --git a/ironic/tests/unit/db/test_conductor.py b/ironic/tests/unit/db/test_conductor.py
index 32e055e826..76b065da51 100644
--- a/ironic/tests/unit/db/test_conductor.py
+++ b/ironic/tests/unit/db/test_conductor.py
@@ -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')
diff --git a/releasenotes/notes/hardware-types-in-hash-ring-d3f097e332c4d395.yaml b/releasenotes/notes/hardware-types-in-hash-ring-d3f097e332c4d395.yaml
new file mode 100644
index 0000000000..23624507de
--- /dev/null
+++ b/releasenotes/notes/hardware-types-in-hash-ring-d3f097e332c4d395.yaml
@@ -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.