Revision d7cdb55d
b/daemons/ganeti-masterd | ||
---|---|---|
53 | 53 |
from ganeti import logger |
54 | 54 |
from ganeti import workerpool |
55 | 55 |
from ganeti import rpc |
56 |
from ganeti import bootstrap |
|
56 | 57 |
|
57 | 58 |
|
58 | 59 |
CLIENT_REQUEST_WORKERS = 16 |
... | ... | |
374 | 375 |
future we could collect the current node list from our (possibly |
375 | 376 |
obsolete) known nodes. |
376 | 377 |
|
378 |
In order to account for cold-start of all nodes, we retry for up to |
|
379 |
a minute until we get a real answer as the top-voted one. If the |
|
380 |
nodes are more out-of-sync, for now manual startup of the master |
|
381 |
should be attempted. |
|
382 |
|
|
383 |
Note that for a even number of nodes cluster, we need at least half |
|
384 |
of the nodes (beside ourselves) to vote for us. This creates a |
|
385 |
problem on two-node clusters, since in this case we require the |
|
386 |
other node to be up too to confirm our status. |
|
387 |
|
|
377 | 388 |
""" |
378 | 389 |
myself = utils.HostInfo().name |
379 | 390 |
#temp instantiation of a config writer, used only to get the node list |
380 | 391 |
cfg = config.ConfigWriter() |
381 | 392 |
node_list = cfg.GetNodeList() |
382 | 393 |
del cfg |
383 |
try: |
|
384 |
node_list.remove(myself) |
|
385 |
except KeyError: |
|
386 |
pass |
|
387 |
if not node_list: |
|
388 |
# either single node cluster, or a misconfiguration, but I won't |
|
389 |
# break any other node, so I can proceed |
|
390 |
return True |
|
391 |
results = rpc.RpcRunner.call_master_info(node_list) |
|
392 |
if not isinstance(results, dict): |
|
393 |
# this should not happen (unless internal error in rpc) |
|
394 |
logging.critical("Can't complete rpc call, aborting master startup") |
|
395 |
return False |
|
396 |
positive = negative = 0 |
|
397 |
other_masters = {} |
|
398 |
for node in results: |
|
399 |
if not isinstance(results[node], (tuple, list)) or len(results[node]) < 3: |
|
400 |
logging.warning("Can't contact node %s", node) |
|
394 |
retries = 6 |
|
395 |
while retries > 0: |
|
396 |
votes = bootstrap.GatherMasterVotes(node_list) |
|
397 |
if not votes: |
|
398 |
# empty node list, this is a one node cluster |
|
399 |
return True |
|
400 |
if votes[0][0] is None: |
|
401 |
retries -= 1 |
|
402 |
time.sleep(10) |
|
401 | 403 |
continue |
402 |
master_node = results[node][2] |
|
403 |
if master_node == myself: |
|
404 |
positive += 1 |
|
405 |
else: |
|
406 |
negative += 1 |
|
407 |
if not master_node in other_masters: |
|
408 |
other_masters[master_node] = 0 |
|
409 |
other_masters[master_node] += 1 |
|
410 |
if positive <= negative: |
|
411 |
# bad! |
|
404 |
break |
|
405 |
if retries == 0: |
|
406 |
logging.critical("Cluster inconsistent, most of the nodes didn't answer" |
|
407 |
" after multiple retries. Aborting startup") |
|
408 |
return False |
|
409 |
# here a real node is at the top of the list |
|
410 |
all_votes = sum(item[1] for item in votes) |
|
411 |
top_node, top_votes = votes[0] |
|
412 |
result = False |
|
413 |
if top_node != myself: |
|
414 |
logging.critical("It seems we are not the master (top-voted node" |
|
415 |
" is %s)", top_node) |
|
416 |
elif top_votes < all_votes - top_votes: |
|
412 | 417 |
logging.critical("It seems we are not the master (%d votes for," |
413 |
" %d votes against)", positive, negative) |
|
414 |
if len(other_masters) > 1: |
|
415 |
logging.critical("The other nodes do not agree on a single master") |
|
416 |
elif other_masters: |
|
417 |
# TODO: resync my files from the master |
|
418 |
logging.critical("It seems the real master is %s", |
|
419 |
other_masters.keys()[0]) |
|
420 |
else: |
|
421 |
logging.critical("Can't contact any node for data, aborting startup") |
|
422 |
return False |
|
423 |
return True |
|
418 |
" %d votes against)", top_votes, all_votes - top_votes) |
|
419 |
else: |
|
420 |
result = True |
|
421 |
|
|
422 |
return result |
|
424 | 423 |
|
425 | 424 |
|
426 | 425 |
def main(): |
b/lib/bootstrap.py | ||
---|---|---|
384 | 384 |
rcode = 1 |
385 | 385 |
|
386 | 386 |
return rcode |
387 |
|
|
388 |
|
|
389 |
def GatherMasterVotes(node_list): |
|
390 |
"""Check the agreement on who is the master. |
|
391 |
|
|
392 |
This function will return a list of (node, number of votes), ordered |
|
393 |
by the number of votes. Errors will be denoted by the key 'None'. |
|
394 |
|
|
395 |
Note that the sum of votes is the number of nodes this machine |
|
396 |
knows, whereas the number of entries in the list could be different |
|
397 |
(if some nodes vote for another master). |
|
398 |
|
|
399 |
We remove ourselves from the list since we know that (bugs aside) |
|
400 |
since we use the same source for configuration information for both |
|
401 |
backend and boostrap, we'll always vote for ourselves. |
|
402 |
|
|
403 |
@type node_list: list |
|
404 |
@param node_list: the list of nodes to query for master info; the current |
|
405 |
node wil be removed if it is in the list |
|
406 |
@rtype: list |
|
407 |
@return: list of (node, votes) |
|
408 |
|
|
409 |
""" |
|
410 |
myself = utils.HostInfo().name |
|
411 |
try: |
|
412 |
node_list.remove(myself) |
|
413 |
except ValueError: |
|
414 |
pass |
|
415 |
if not node_list: |
|
416 |
# no nodes left (eventually after removing myself) |
|
417 |
return [] |
|
418 |
results = rpc.RpcRunner.call_master_info(node_list) |
|
419 |
if not isinstance(results, dict): |
|
420 |
# this should not happen (unless internal error in rpc) |
|
421 |
logging.critical("Can't complete rpc call, aborting master startup") |
|
422 |
return [(None, len(node_list))] |
|
423 |
positive = negative = 0 |
|
424 |
other_masters = {} |
|
425 |
votes = {} |
|
426 |
for node in results: |
|
427 |
if not isinstance(results[node], (tuple, list)) or len(results[node]) < 3: |
|
428 |
# here the rpc layer should have already logged errors |
|
429 |
if None not in votes: |
|
430 |
votes[None] = 0 |
|
431 |
votes[None] += 1 |
|
432 |
continue |
|
433 |
master_node = results[node][2] |
|
434 |
if master_node not in votes: |
|
435 |
votes[master_node] = 0 |
|
436 |
votes[master_node] += 1 |
|
437 |
|
|
438 |
vote_list = [v for v in votes.items()] |
|
439 |
# sort first on number of votes then on name, since we want None |
|
440 |
# sorted later if we have the half of the nodes not responding, and |
|
441 |
# half voting all for the same master |
|
442 |
vote_list.sort(key=lambda x: (x[1], x[0]), reverse=True) |
|
443 |
|
|
444 |
return vote_list |
Also available in: Unified diff