看ironic的官方文档,通过给不同的node指定不同的driver可以实现使用ironic 部署管理异构的server,奥妙其实在于多conductor,
不同的node使用不同的driver,从而map到不同的conductor上(rpc call的时候也会相应的发到不同的conductor中),来看
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
在ironic/conductor/rpcapi.pu中get_topic_for方法:
ring = self.ring_manager[node.driver]
dest = ring.get_hosts(node.uuid)
return self.topic + "." + dest[0]
其中:ring_manager = hash_ring.HashRingManager()
self.ring_manager[node.driver]调用RingManager中的__getitem__(self, driver_name)方法,返回
self.ring[driver_name], 即
@property
def ring(self):
# Hot path, no lock
if self._hash_rings is not None:
return self._hash_rings
with self._lock:
if self._hash_rings is None:
rings = self._load_hash_rings()
self.__class__._hash_rings = rings
return self._hash_rings
def _load_hash_rings(self):
rings = {}
d2c = self.dbapi.get_active_driver_dict()
for driver_name, hosts in d2c.iteritems():
rings[driver_name] = HashRing(hosts)
return rings
最终通过rings[driver_name] = HashRing(hosts),由driver_name得到一个HashRing,先来看get_active_driver_dict(),再来看HashRing的get_hosts方法返回什么。
get_active_driver_dict()通过sqlalchemy查询数据中的conductors表,得到conductor配置时候所在的host以及该conductor支持的drivers
class Conductor(Base):
"""Represents a conductor service entry."""
__tablename__ = 'conductors'
__table_args__ = (
schema.UniqueConstraint('hostname', name='uniq_conductors0hostname'),
table_args()
)
id = Column(Integer, primary_key=True)
hostname = Column(String(255), nullable=False)
drivers = Column(JSONEncodedList)
online = Column(Boolean, default=True)
查询之后:
d2c = collections.defaultdict(set)
for row in result:
for driver in row['drivers']:
d2c[driver].add(row['hostname'])
return d2c
生成driver -> hosts的哈希表,每一个driver对应多个node(多个node使用同一个driver)
再来看HashRing,初始化时候传入一个host列表
for host in hosts:
key = str(host).encode('utf8')
key_hash = hashlib.md5(key)
for p in range(2 ** CONF.hash_partition_exponent):
key_hash.update(key)
hashed_key = self._hash2int(key_hash)
self._host_hashes[hashed_key] = host
# Gather the (possibly colliding) resulting hashes into a bisectable
# list.
self._partitions = sorted(self._host_hashes.keys())
在初始化Ring的时候
elf._host_hashes = {}
for host in hosts:
key = str(host).encode('utf8')
key_hash = hashlib.md5(key)
for p in range(2 ** CONF.hash_partition_exponent):
key_hash.update(key)
hashed_key = self._hash2int(key_hash)
self._host_hashes[hashed_key] = host
# Gather the (possibly colliding) resulting hashes into a bisectable
# list.
self._partitions = sorted(self._host_hashes.keys())
使用python的commandline看看_host_hashes table实际上是2**5个hashkey到host的映射
>>> key='ding'.encode('utf8')
>>> key_hash=hashlib.md5(key)
>>> key_hash.update(key)
>>> print key_hash
<md5 HASH object @ 0x7fd37197ca30>
>>> print int(key_hash.hexdigest(),16)
112829692254858769854237475965896089339
>>> key_hash.update(key)
>>> print int(key_hash.hexdigest(),16)
328510452650596731730110572716430698878(key差不多这个样子)
测试一下大概_host_hashes的样子是:
{210434336372988548812286100383594081614L: '192.168.1.12', 261341529905210078673684866260045400778L: '192.168.1.12', 323520237514182616212163730681614553619L: '192.168.1.13', 258950754859020420811173881044830400086L: '192.168.1.13', 33690298986936109064829557520707239091L: '192.168.1.12', 334166521183063638501421621663613889942L: '192.168.1.13', 121474934449745677398821877614671543723L: '192.168.1.12', 152336039920782842890743084167801731181L: '192.168.1.13', 193660519265317885493214682164688808777L: '192.168.1.12', 6517729748627730799505962620472763484L: '192.168.1.13', 274217068100375315285543740308286353911L: '192.168.1.13', 157991666289896217762832347933314497307L: '192.168.1.12', 117733835536317852163361767027400996369L: '192.168.1.13', 49230796137512190692836562479246773981L: '192.168.1.13', 165015105211970305330598773763350313205L: '192.168.1.13', 112418604685575404349027279429093348741L: '192.168.1.13', 95871703577230750888370464878643622。。。。。
所以最后给一个uuid的时候根据该uuid生成一个key,使用bisect.bisect,找到其在_partition中的位置,从而拿到对应的host
可能是我理解的还不到位,可能对于容错这种算法有好处,但是。。。可以更简单的解决的吧。。。