# coding: utf-8
__author__ = 'zhenhang.sun@gmail.com'
__version__ = '1.0.0'
import os
import json
import time
import socket
import random
import logging
from log import Log
logging.basicConfig(level=logging.INFO, format='%(asctime)s-%(name)s-%(levelname)s-%(message)s')
class Node(object):
def __init__(self, conf):
self.role = 'follower'
self.id = conf['id']
self.addr = conf['addr']
self.peers = conf['peers']
# persistent state
self.current_term = 0
self.voted_for = None
if not os.path.exists(self.id):
os.mkdir(self.id)
# init persistent state
self.load()
self.log = Log(self.id)
# volatile state
# rule 1, 2
self.commit_index = 0
self.last_applied = 0
# volatile state on leaders
# rule 1, 2
self.next_index = {_id: self.log.last_log_index + 1 for _id in self.peers}
self.match_index = {_id: -1 for _id in self.peers}
# append entries
self.leader_id = None
# request vote
self.vote_ids = {_id: 0 for _id in self.peers}
# client request
self.client_addr = None
# tick
self.wait_ms = (10, 20)
self.next_leader_election_time = time.time() + random.randint(*self.wait_ms)
self.next_heartbeat_time = 0
# msg send and recv
self.ss = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.ss.bind(self.addr)
self.ss.settimeout(2)
self.cs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def load(self):
file_path = self.id + '/key.json'
if os.path.exists(file_path):
with open(file_path, 'r') as f:
data = json.load(f)
self.current_term = data['current_term']
self.voted_for = data['voted_for']
else:
self.save()
def save(self):
data = {'current_term': self.current_term,
'voted_for': self.voted_for,
}
file_path = self.id + '/key.json'
with open(file_path, 'w') as f:
json.dump(data, f)
def send(self, msg, addr):
msg = json.dumps(msg).encode('utf-8')
self.cs.sendto(msg, addr)
def recv(self):
msg, addr = self.ss.recvfrom(65535)
return json.loads(msg), addr
def redirect(self, data, addr):
if data == None:
return None
if data['type'] == 'client_append_entries':
if self.role != 'leader':
if self.leader_id:
logging.info('redirect: client_append_entries to leader')
self.send(data, self.peers[self.leader_id])
return None
else:
self.client_addr = addr
return data
if data['dst_id'] != self.id:
logging.info('redirect: to ' + data['dst_id'])
# logging.info('redirec to leader')
self.send(data, self.peers[data['dst_id']])
return None
else:
re