Esempio n. 1
0
def test_append_to_empty():
    l = Log()

    new_entries = [Entry(1, 'first')]
    l.append(0, 1, new_entries)

    assert l.entries == [None] + new_entries
Esempio n. 2
0
def test_append_beyond_last():
    l = Log()

    new_entries = [Entry(1, 'first')]

    with pytest.raises(IndexBeyondLast):
        l.append(1, 1, new_entries)
Esempio n. 3
0
def test_append_wrong_term():
    initial_entries = [Entry(1, 'first')]
    l = Log(initial_entries)

    new_entries = [Entry(2, 'second')]
    with pytest.raises(InvalidTerm):
        l.append(1, 2, new_entries)
Esempio n. 4
0
def test_append_to_not_empty_beyond_last():
    initial_entries = [Entry(1, 'first')]
    l = Log(initial_entries)

    new_entries = [Entry(1, 'second')]
    with pytest.raises(IndexBeyondLast):
        l.append(2, 1, new_entries)
Esempio n. 5
0
class Shell:

  def __init__(self):
    self.builtins = Builtins(self)
    self.completion = Completion(self)
    self.history = History()
    self.javascript = Javascript()
    self.log = Log()
    self.prompt = Prompt()

  def execute(self, command):
    self.log.append(str(self.prompt) + command)
    self.history.append(command)

    if command:
      # execute builtins command
      try:
        self.builtins.execute(command.strip())
      except self.builtins.UnknownCommandError as e:
        self.log.append('websh: command not found: {0}'.format(e.command))
      except Exception as e:
        print 'Error in builtins: {0}'.format(e)

    return json.dumps({'javascript': str(self.javascript),
                       'log': str(self.log),
                       'prompt': str(self.prompt)})

  def template(self):
    # read template file
    file = open('data/template.html', 'r')
    template = string.Template(file.read())
    file.close()

    return template.substitute(log = str(self.log),
                               prompt = str(self.prompt))
Esempio n. 6
0
def test_append_prev_index_is_negative():
    l = Log()

    new_entries = [Entry(1, 'first')]

    with pytest.raises(NegativeIndex):
        l.append(-1, 1, new_entries)
Esempio n. 7
0
def test_append_ovewrite():
    initial_entries = [Entry(1, 'first'), Entry(1, 'second')]
    l = Log(initial_entries)

    new_entries = [Entry(1, 'third')]
    l.append(1, 1, new_entries)

    assert l.entries == [None] + initial_entries[:1] + new_entries
Esempio n. 8
0
def test_append_to_not_empty():
    initial_entries = [Entry(1, 'first')]
    l = Log(initial_entries)

    new_entries = [Entry(1, 'second')]
    l.append(1, 1, new_entries)

    assert l.entries == [None] + initial_entries + new_entries
Esempio n. 9
0
    def clean(self, ext):
        try:
            os.remove(self.get(ext))
        except FileNotFoundError:
            Log.append("Cannot clean " + self.get(ext) + ": Does not exist.")

            return False

        return True
Esempio n. 10
0
    def convert(self):
        try:
            subprocess.call("tex3ds " + self.png() + " -o " + self.t3x(),
                            shell=True)
        except subprocess.CalledProcessError:
            Log.append("Could not convert " + self.png() + ".")

            return False

        return True
Esempio n. 11
0
    def state(self):
        try:
            self.gp.output(self.pin_out, self.gp.HIGH)
            time.sleep(1)
            input_state = self.gp.input(self.pin_in)
            time.sleep(1)
            self.gp.output(self.pin_out, self.gp.LOW)
        except Exception as ex:
            Log.append('water level sensore error')
            Log.append(str(ex))
            return self.LOW

        if input_state == 1:
            return self.HIGH
        else:
            return self.LOW
Esempio n. 12
0
def search(path=None, file_list=None):
    if file_list is None:
        file_list = []

    if path is None:
        path = "game"

    try:
        for file in listdir(path):
            if isfile(path + "/" + file) and file[-4:] == ".png":
                no_ext_value = splitext(file)[0]
                file_list.append(File(path + "/" + no_ext_value))
            elif isdir(path + "/" + file):
                search(path + "/" + file, file_list)

        return file_list
    except FileNotFoundError:
        Log.append("Failed to find file listing for " + path)
Esempio n. 13
0
 def __init__(self):
     Log.append("Init Control.")
     # GPIO settings
     gp.setmode(gp.BCM)
     gp.setwarnings(False)
     # read state file
     State.sync()
     self.state = State.load_state_from_file()
     # init pumps
     self.pump_1 = Pump(gp, self.PUMP_1_pin, id=1)
     self.pump_2 = Pump(gp, self.PUMP_2_pin, id=2)
     # moisture sensors
     self.m_sensor_1 = MSensor(gp, self.M_SENSOR_1_pin,
                               self.M_SENSORS_RELAY_pin)
     self.m_sensor_2 = MSensor(gp, self.M_SENSOR_2_pin,
                               self.M_SENSORS_RELAY_pin)
     # water level sensor
     self.water_level_sensor = WaterLevel(gp,
                                          self.WATER_LEVEL_SENSOR_pin_out,
                                          self.WATER_LEVEL_SENSOR_pin_in)
Esempio n. 14
0
    def state(self):
        # turn on sensor, measure, turn off
        try:
            self.gp.output(self.relay_pin, self.gp.LOW)

            time.sleep(1)
            state = self.gp.input(self.sensor_pin)
            time.sleep(1)
            self.gp.output(self.relay_pin, self.gp.HIGH)
            time.sleep(1)
        except Exception as ex:
            Log.append('moisture sensore error')
            Log.append(str(ex))
            state = 1
        # 0 - over the threshold
        # 1 - below the threshold
        if state == 0:
            return self.WET
        else:
            return self.DRY
Esempio n. 15
0
 def turn_on(self):
     Log.append('Turning on pump... ' + str(self.id))
     try:
         self.gp.output(self.pin, 0)
         Log.append('...turned on')
     except Exception as ex:
         Log.append('pump \'on\' error ' + str(ex))
Esempio n. 16
0
 def turn_off(self):
     Log.append('Turning off pump... ' + str(self.id))
     try:
         self.gp.output(self.pin, 1)
         Log.append('...turned off')
     except Exception as ex:
         Log.append('pump \'off\' error ' + str(ex))
Esempio n. 17
0
    def sync():
        Ftp.download_file(State.state_server_file_path, 'state.json')
        time.sleep(0.5)
        server_state = State.load_state_from_server_file()
        local_state = State.load_state_from_file()
        local_state['pump_1_interval'] = server_state['pump_1_interval']
        local_state['pump_2_interval'] = server_state['pump_2_interval']
        local_state['pump_1_water_amount'] = server_state['pump_1_water_amount']
        local_state['pump_2_water_amount'] = server_state['pump_2_water_amount']
        local_state['tank_capacity'] = server_state['tank_capacity']
        if server_state['tank_refilled'] == 1: # tank refilled
            local_state['water_level'] = local_state['tank_capacity']
            local_state['tank_refilled'] = 0
            local_state['low_water_level_alert'] = 0
            Log.append('tank refilled')
        if server_state['run_test'] == 2: # test request
            if local_state['run_test'] == 0: # request not taken
                local_state['run_test'] = 2
            elif local_state['run_test'] == 1: # request satisfied
                local_state['run_test'] = 0


        State.save_state_to_file(local_state)
        Ftp.upload_file(State.state_file_path, 'state.json')
Esempio n. 18
0
def do_convert(file_list, arg):
    successful = True

    if file_list is None:
        return

    for i in range(len(file_list)):
        if arg["c"] == "":
            try:
                if file_list[i].convert():
                    print("\x1b[2K\rConverting " + file_list[i].png() + " (" +
                          str(i + 1) + " of " + str(len(file_list)) + ")",
                          end="",
                          flush=True)

                    if arg["mv"]:
                        file_list[i].move()
            except subprocess.CalledProcessError:
                Log.append("Could not convert file " + file_list[i].png())
            except KeyboardInterrupt:
                Log.append("Conversion stopped.")
            except FileNotFoundError as err:
                Log.append("Could not find file " + file_list[i].png() +
                           "\n\t(" + str(err) + ")")
        else:
            try:
                if file_list[i].clean(arg["c"]):
                    print("\x1b[2K\rCleaning " + file_list[i].t3x() + " (" +
                          str(i + 1) + " of " + str(len(file_list)) + ")",
                          end="",
                          flush=True)
                else:
                    successful = False
            except FileNotFoundError:
                Log.append("Could not clean file")

    if successful:
        print("\nDone.")
Esempio n. 19
0
class Server(threading.Thread):

    def __init__(self, queue, port, id):
        self.port = port
        self.id = id
        self.queue = queue
        self.title = constants.TITLE_FOLLOWER
        self.channel = network.Network(port, id)
        self.channel.start()
        self.leader = None
        self.running = True

        self.connected_servers = []

        self.last_heartbeat = 0
        self.heartbeat_timeout = 0
        self.process_heartbeat()
        self.heartbeat_frequency = 0.5
        self.election_start_time = 0
        self.election_timeout = 0  # Time to wait for heartbeat or voting for a candidate before calling election
        self.set_election_timeout()
        # Election variables
        self.id_received_votes = set()      # Id of servers who granted you votes
        self.id_refused_votes = set()       # Id of servers who refused to vote for you
        self.num_received_votes = 0         # Number of votes received in current election

        # Persistent state variables
        # TODO: PERSIST; On server boot, retrieve information from disk
        self.current_term = 0          # Latest term server has seen
        self.voted_for = None          # CandidateId that received vote in current term
        self.log = Log()

        self.next_index = None          # For leader: indices for updating follower logs
        self.latest_index_term = None        # For leader: tuples of latest entry index and term for each follower. Used for commit

        self.load_state()
        threading.Thread.__init__(self)

    def set_election_timeout(self):
        self.election_timeout = 1.5 * random() + 1.5

    def process_heartbeat(self):
        self.last_heartbeat = time.time()
        self.heartbeat_timeout = 1.5 * random() + 1.5

    def request_votes(self):
        if not self.log.data:
            # Log is empty
            last_log_index = -1
            last_log_term = -1
        else:
            last_log_index = self.log.get(-1).index
            last_log_term = self.log.get(-1).term

        msg = RequestVoteMessage(self.id, self.current_term, last_log_index, last_log_term)
        for server in self.connected_servers:
            self.channel.send(msg, id=host_to_id[server[0]])
            # print "Requesting vote from server", host_to_id[server[0]]

        print "Vote requests sent to other servers"

    def request_remaining_votes(self, id_all_voters):
        if not self.log.data:
            # Log is empty
            last_log_index = -1
            last_log_term = -1
        else:
            last_log_index = self.log.get(-1).index
            last_log_term = self.log.get(-1).term

        msg = RequestVoteMessage(self.id, self.current_term, last_log_index, last_log_term)

        for server in self.connected_servers:
            server_id = host_to_id[server[0]]
            if server_id not in id_all_voters:
                self.channel.send(msg, id=server_id)
                # print "Requesting vote from server", host_to_id[server[0]]

            print "Vote requests sent to remaining servers who have not responded"

    def check_status(self):
        current_time = time.time()
        if self.title == constants.TITLE_LEADER:
            # Send AppendEntries to update follower logs
            for server in self.connected_servers:
                server_id = host_to_id[server[0]]
                next_index = self.next_index[server_id]

                # Send entries that the server has not received yet, if any
                if self.log.last_log_index() >= next_index:
                    entries = self.construct_entries_list(next_index)
                    if next_index == 0:
                        prev_log_index = -1
                        prev_log_term = -1
                    else:
                        prev_log_index = self.log.get(next_index-1).index
                        prev_log_term = self.log.get(next_index-1).term
                    msg = AppendEntriesMessage(self.current_term, self.id, prev_log_index,
                                               prev_log_term, entries, self.log.last_commit_index)

                    self.channel.send(msg, id=server_id)
                    print "AppendEntries sent to ", server_id

            if current_time - self.last_heartbeat >= self.heartbeat_frequency:
                self.send_heartbeats()
        elif self.title == constants.TITLE_FOLLOWER and current_time - self.last_heartbeat > self.heartbeat_timeout:
            # Heartbeat timeout passed as follower: Start election
            print "Election timeout as follower. No heartbeat. Become candidate and start new election"
            self.start_election()
        elif self.title == constants.TITLE_CANDIDATE and current_time - self.election_start_time > self.election_timeout:
            # Election timeout passed as candidate, without conclusion of election: Start new election
            print "Election timeout as candidate. Election has not yet led to new leader. Starting new election"
            self.set_election_timeout()
            self.start_election()
        elif self.title == constants.TITLE_CANDIDATE and current_time - self.election_start_time < self.election_timeout:
            # Election timeout has not passed as candidate
            print "As candidate, election timeout has not passed. Request votes from servers that have not responded"
            id_all_voters = self.id_received_votes.union(self.id_refused_votes)
            self.request_remaining_votes(id_all_voters)

    def construct_entries_list(self, index):
        entries = []
        for i in range(index, len(self.log)):
            entries.append(self.log.get(i))
        return entries

    def start_election(self):
        self.title = constants.TITLE_CANDIDATE
        self.reset_election_info()
        self.current_term += 1
        self.save_state()
        # TODO: Voted_for must persist
        self.voted_for = self.id
        self.save_state()
        self.update_votes(self.id, True)
        self.election_start_time = time.time()
        self.check_election_status()

        self.request_votes()

    def send_heartbeats(self):
        heartbeat = AppendEntriesMessage(self.current_term, self.id, -1, -1, [], self.log.last_commit_index)
        for server in self.connected_servers:
            self.channel.send(heartbeat, id=host_to_id[server[0]])
        self.process_heartbeat()

    def step_down(self):
        # Step down as leader or candidate, convert to follower
        # Reset various election variables
        if self.title == constants.TITLE_LEADER or self.title == constants.TITLE_CANDIDATE:
            self.title = constants.TITLE_FOLLOWER
            self.process_heartbeat()
            self.reset_election_info()

    def grant_vote(self, candidate_id):
        # TODO: Voted_for must persist
        self.voted_for = candidate_id
        self.save_state()
        print "Grant vote to", candidate_id
        self.channel.send(VoteReplyMessage(self.id, self.current_term, True), id=candidate_id)

    def refuse_vote(self, candidate_id):
        self.channel.send(VoteReplyMessage(self.id, self.current_term, False), id=candidate_id)
        print "Refuse vote to", candidate_id

    def majority(self):
        return (len(self.connected_servers)+1) / 2 + 1

    def check_election_status(self):
        if self.num_received_votes >= self.majority():
            # Become leader when granted majority of votes
            self.become_leader()

    def become_leader(self):
        self.title = constants.TITLE_LEADER
        self.leader = self.id
        print "Election won - I am now LEADER"
        # TODO: Implement rest of leader initialization
        self.next_index = [len(self.log) for _ in range(len(addr_to_id))]

        if self.log.last_commit_index == -1:
            latest_index = None
        else:
            latest_index = self.log.last_commit_index

        if latest_index is None:
            latest_term = 0
        elif self.log.contains_at_index(latest_index):
            latest_term = self.log.get(latest_index).term
        else:
            latest_term = 0

        self.latest_index_term = [(latest_index, latest_term) for _ in range(len(addr_to_id))]
        self.latest_index_term[self.id] = (len(self.log)-1, self.current_term)
        self.reset_election_info()
        self.send_heartbeats()

    def reset_election_info(self):
        self.id_received_votes = set()
        self.id_refused_votes = set()
        self.voted_for = None
        self.num_received_votes = 0

    # server_id: server that sent vote reply; vote_granted: True if vote granted
    def update_votes(self, server_id, vote_granted):
        if vote_granted:
            print "Received vote from", server_id
            self.id_received_votes.add(server_id)
            self.num_received_votes = len(self.id_received_votes)
            print "Number of received votes is now", self.num_received_votes
        else:
            print "Denied vote from", server_id
            self.id_refused_votes.add(server_id)

    def update_commits(self):
        index = max(self.next_index)

        i_count = 0
        t_count = 0
        while i_count < self.majority() and index >= 0:
            if index < 0:
                print "Error: Update_commits: index is less than 0"
            index -= 1
            t_count = 0
            i_count = 0
            for (i, t) in self.latest_index_term:
                if t == self.current_term:
                    t_count += 1
                if i >= index:
                    i_count += 1

        if t_count >= self.majority() and i_count >= self.majority():
            if self.log.last_commit_index < index:
                self.log.last_commit_index = index
                self.save_state()
            elif self.log.last_commit_index > index:
                print "Error: Update_commits: new commit index is lower than current commit_index"

            for entry in self.log.data:
                if not entry.client_ack_sent:
                    # TODO: Send client ack
                    ack_message = AcknowledgeMessage(ack=True, msg_id=entry.msg_id)
                    self.channel.send(ack_message, id=entry.author)
                    entry.client_ack_sent = True

    def run(self):
        print "Server with id=", self.id, " up and running"
        while self.running:
            self.update_connected_servers()
            for server in list(addr_to_id.keys()):
                # if server not in self.connected_servers and not addr_to_id[server] == id:
                if server not in self.channel and not host_to_id[server[0]] == self.id:
                    connected = self.channel.connect(server)
                    if connected:
                        print str("Server: Connected to "+server[0])
                        if server not in self.connected_servers:
                            self.connected_servers.append(server)
                    # print "Connected: ", connected

                data = self.channel.receive(RECEIVE_FREQ)
                if data:
                    # print "There is data on channel"
                    for server_id, msg in data:
                        self.process_msg(server_id, msg)
                else:
                    self.check_status()

    def process_msg(self, sender_id, msg):

        #print "Processing message from", sender_id, "of type", msg.type
        if msg.type == constants.MESSAGE_TYPE_REQUEST_VOTE:
            self.process_request_vote(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_VOTE_REPLY:
            self.process_vote_reply(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_REQUEST_LEADER:
            msg = messages.RequestLeaderMessage(leader=self.leader)
            self.channel.send(msg, id=sender_id)

        elif msg.type == constants.MESSAGE_TYPE_LOOKUP:
            self.process_lookup(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_POST:
            self.process_post(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_APPEND_ENTRIES:
            self.process_append_entries(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_ACKNOWLEDGE:
            self.process_acknowledge(sender_id, msg)

        # Used for testing purposes
        elif msg.type == constants.MESSAGE_TYPE_TEXT:
            print "From", msg.sender_id, ":", msg.msg

        else:
            print "Error: Invalid message type"

    def process_lookup(self, sender_id, msg):
        if self.title == constants.TITLE_LEADER or msg.override:
            print "-----> Processing Lookup from client"
            posts = self.log.get_committed_entries()
            msg = messages.LookupMessage(msg_id=msg.msg_id, post=posts, server_id=self.id)
            self.channel.send(msg=msg, id=sender_id)
        else:
            print "Lookup to leader"
            msg = messages.RequestLeaderMessage(leader=self.leader)
            self.channel.send(msg=msg, id=sender_id)

    def process_post(self, sender_id, msg):
        if self.title == constants.TITLE_LEADER:
            # TODO: Implement adding entry
            # TODO: PERSIST; implement in log class?
            entry = Entry(msg.post, sender_id, self.current_term, len(self.log), msg_id=msg.msg_id)

            if self.log.append(entry):
                self.save_state()
                self.latest_index_term[self.id] = (len(self.log) - 1, self.current_term)
                print "---->Append entry from client to log"

        else:
            msg = messages.RequestLeaderMessage(leader=self.leader)
            self.channel.send(msg=msg, id=sender_id)

    def process_request_vote(self, sender_id, msg):
        if not self.log:
            # Log is empty
            last_log_index = -1
            last_log_term = -1
        else:
            last_log_index = self.log.get(-1).index
            last_log_term = self.log.get(-1).term

        # Handle message
        if msg.term < self.current_term:
            # If candidate's term is less than my term then refuse vote
            print "Refuse vote to server", sender_id, "because I have higher term"
            self.refuse_vote(msg.candidate_id)

        if msg.term > self.current_term:
            # If candidate's term is greater than my term then update current_term (latest term I've encountered),
            # Step down if leader or candidate
            self.current_term = msg.term
            self.save_state()
            # TODO: Step down if leader or candidate
            self.step_down()

        if msg.term >= self.current_term:
            # If candidate's term is at least as new as mine and I have granted anyone else a vote
            # and candidate's log is at least as complete as mine
            # then grant vote
            if self.voted_for is None or self.voted_for is msg.candidate_id:
                if last_log_term < msg.last_log_term or (
                        last_log_term == msg.last_log_term and last_log_index <= msg.last_log_index):
                    self.grant_vote(msg.candidate_id)
        else:
            # print "Cand term, current_term:", msg.term, self.current_term
            # print "Voted for:", self.voted_for
            # print "Cand log term, last_log_term", msg.last_log_term, last_log_term
            # print "Cand log index, last_log_index", msg.last_log_index, last_log_index
            self.refuse_vote(msg.candidate_id)

    def process_vote_reply(self, sender_id, msg):
        if msg.term > self.current_term and not msg.vote_granted:
            # Step down if reply from someone with higher term
            # Extra condition for security.
            # If responder's term is higher, then vote should not be granted with correct execution
            self.current_term = msg.term
            self.save_state()
            print "Denied vote from", msg.follower_id
            self.step_down()
        else:
            # Take care of grant or refusal of vote
            self.update_votes(msg.follower_id, msg.vote_granted)
            self.check_election_status()

    def process_acknowledge(self, sender_id, msg):
        if msg.ack:
            print "Process Acknowledge from server. ACK == TRUE"
            self.next_index[sender_id] = msg.next_index
            self.latest_index_term[sender_id] = msg.latest_index_term
            self.update_commits()
        else:
            print "Process Acknowledge from server. ACK == FALSE"
            if self.next_index[sender_id]-1 < 0:
                self.next_index[sender_id] = 0
            else:
                self.next_index[sender_id] -= 1
            if msg.term > self.current_term:
                self.current_term = msg.term
                self.save_state()
                self.step_down()

    def process_append_entries(self, sender_id, msg):
        if len(msg.entries) == 0:
            self.process_heartbeat()

            if msg.commit_index < len(self.log):
                self.log.last_commit_index = msg.commit_index
                self.save_state()

            self.leader = sender_id
            #print "Heartbeat received from server", sender_id

            if self.title == constants.TITLE_CANDIDATE or self.title == constants.TITLE_LEADER:
                self.step_down()

            elif self.title == constants.TITLE_LEADER:
                # TODO: If a "leader" receives a heartbeat,
                # it might have crashed and joined back in after an election (?)
                pass
        else:
            # TODO: Process AppendEntriesMessage
            print "-->Processing AppendEntriesMessage from leader"
            self.process_heartbeat()
            if msg.term > self.current_term:
                self.current_term = msg.term
                self.save_state()

            if self.title == constants.TITLE_CANDIDATE or self.title == constants.TITLE_LEADER:
                self.step_down()

            # Reject if my term is greater than leader term
            if self.current_term > msg.term:
                print "Error: Current term greater than leaders term"
                self.channel.send(AcknowledgeMessage(ack=False, term=self.current_term), id=sender_id)

            # Accept. Self.log is empty and leader is sending all entries
            elif self.log.is_empty() and msg.prev_log_index == -1:
                print "Appending entries"
                # First entry to append is at index 0
                if self.log.append_entries(msg.entries):
                    self.log.last_commit_index = msg.commit_index
                    self.save_state()
                    i = self.log.last_log_index()
                    t = self.log.get(i).term
                    self.channel.send(AcknowledgeMessage(
                        ack=True, next_index=len(self.log), latest_index_term=(i, t)), id=sender_id)
                    print "Log after appending entries:"
                    self.log.show_data()
                else:
                    print "DET HER SKAL IKKE SKJE 1"

            # Accept. Check if self.log has an element at msg.prev_log_index
            elif self.log.contains_at_index(msg.prev_log_index):
                # Check if the term corresponds with msg.prev_log_term
                if self.log.get(msg.prev_log_index).term == msg.prev_log_term:
                    if self.log.append_entries(msg.entries):
                        self.log.last_commit_index = msg.commit_index
                        self.save_state()
                        i = self.log.last_log_index()
                        t = self.log.get(i).term
                        self.channel.send(
                            AcknowledgeMessage(ack=True, next_index=len(self.log), latest_index_term=(i, t)), id=sender_id)
                        print "Log after appending entries:"
                        self.log.show_data()
                    else:
                        print "DET HER SKAL IKKE SKJE NUMMER 2"
                else:
                    self.log.remove(msg.prev_log_index)
                    self.channel.send(AcknowledgeMessage(ack=False), id=sender_id)
            else:
                print "Send ACK-False"
                self.channel.send(AcknowledgeMessage(ack=False),id=sender_id)

    def save_state(self):
        storage.save(self.id, self.voted_for, self.current_term, self.log)

    def load_state(self):
        self.voted_for, self.current_term, self.log = storage.load(self.id)
        # print "voted for", self.voted_for
        print self.current_term
        print self.log

    def update_connected_servers(self):
        for addr in list(addr_to_id.keys()):
            if addr in self.channel.address_to_connection.keys() and addr not in self.connected_servers:
                self.connected_servers.append(id)

            if addr not in self.channel.address_to_connection.keys() and addr in self.connected_servers:
                self.connected_servers.remove(addr)
Esempio n. 20
0
class RaftServer:

    # Miscellaneous constants.
    NO_LEADER = -1

    def __init__(self, id):
        self.id = id  # unique id for this RaftServer
        
        self.state_machine = StateMachine()

        # Persistent state on a server.
        self.current_term = 0
        self.voted_for = None  # candidate_id that received vote
                               # in current term
        self.log = Log()

        # Volatile state on a server.
        self.commit_index = 0   # index of highest log entry known
                                # to be committed
        self.last_applied = 0   # index of highest log entry applied
                                # to state machine

        # Volatile state on a leader. (Reinitialized after election.)
        # for each server, index of next log
        # entry to send to that server:
        self.next_index = []
        # for each server, index of highest
        # log entry known to be replicated on server:
        self.match_index = []

        self.leader_id = self.NO_LEADER
        
        self.num_votes = 0  # votes for this candidate
        self.state = self.State.FOLLOWER
        self.heartbeat_thread = None  # for leader to assert authority
        self.candidate_thread = None  # for candidate to grab votes
        self.election_timeout = 100  # random number for now

    def __del__(self):
        if self.heartbeat_thread != None:
            self.state = self.State.FOLLOWER  # kills thread
            self.heartbeat_thread.join()  # wait for thread to die

    class State(Enum):
        FOLLOWER = 1
        CANDIDATE = 2
        LEADER = 3

    def __got_majority_vote(self):
        return self.num_votes > Temp.NUM_SERVERS / 2

    def __request_votes(self):
        # Send out vote requests.
        # TODO: reset election timer
        # TODO: select new randomized election timeout
        # send Requestvote RPCs to all other servers:
        # for i in range(Temp.NUM_SERVERS):
        #     if i != self.id:
        #         response = Temp.servers[i].request_vote_RPC(
        #             self.current_term, self.id, self.log.size()-1,
        #             self.log.latest())
        #         print("Thread {}: thread {} sent {} back to me".format(
        #             self.id, i, response))

        # Keep spinning UNTIL either:
        # a) election timeout 
        # b) got majority vote
        while (not self.__got_majority_vote() and
            self.leader_id == self.NO_LEADER):
            pass
        print("Thread {} broke out candidate loop".format(self.id))

        if self.__got_majority_vote():  # this server was elected
            self.__become_leader()
        elif self.leader_id != self.NO_LEADER:  # other server was elected
            self.__become_follower()
        else:  # timed out
            self.__start_election()

    def __become_candidate(self):
        print("Thread {} has become candidate".format(self.id))
        # Start candidate thread.
        self.candidate_thread = Thread(target=self.__start_election)
        self.candidate_thread.start()

    # Called if no communication from leader and
    # election timeout reached.
    def __start_election(self):
        self.current_term += 1
        self.num_votes = 0
        assert(self.state != self.State.LEADER)  # can't start election if was Leader
        self.state = self.State.CANDIDATE
        self.voted_for = self.id
        self.leader_id = self.NO_LEADER
        self.__request_votes()

    # For heartbeat thread.
    # If leader, sends heartbeat to all followers,
    # then wait until must send next heartbeat.
    def __send_heartbeat(self):
        while self.state == self.State.LEADER:
            # for each follower
                # send empty AppendEntries RPC
            for i in range(Temp.NUM_SERVERS):
                if i != self.id:
                    prev_log_index = self.next_index[i] - 1
                    response = Temp.servers[i].append_entries_RPC(self.current_term,
                        self.id,
                        #  these next two params might be wrong
                        prev_log_index, self.log.get(prev_log_index).term,
                        [],  # because heartbeat
                        self.commit_index)
                    # print("Thread {}: thread {} sent {} back to me".format(
                        # self.id, i, response))
            # print("Thread id={} sending heartbeat".format(self.id))
            time.sleep(Temp.HEARTBEAT_PERIOD)

    def __become_leader(self):
        print("Thread {} has become leader".format(self.id))
        self.state = self.State.LEADER
        initial_next_index = self.last_applied + 1
        self.next_index = [initial_next_index] * Temp.NUM_SERVERS
        self.match_index = [0] * Temp.NUM_SERVERS

        # Start heartbeat thread.
        self.heartbeat_thread = Thread(target=self.__send_heartbeat)
        self.heartbeat_thread.start()

    def __become_follower(self):
        print("Thread {} has become follower".format(self.id))
        self.state = self.State.FOLLOWER
        if self.heartbeat_thread != None:  # kill heartbeat thread (if any)
            self.heartbeat_thread.join()
            self.heartbeat_thread = None
        # TODO: what else?

    def __handle_request(self, request):
        self.log.append(self.current_term, request.command)

    # Used to receive request from client.
    # @request must be instance of Request.
    def handle_request(self, request):
        if self.state == RaftServerState.LEADER:
            # print("Thread {} handling request".format(self.id))
            self.__handle_request(request)
        else:
            assert(self.leader_id != self.NO_LEADER)
            Temp.servers[self.leader_id].handle_request(request)

    # Invoked by the leader on other raft servers to replicate
    # log entries. Also used as a heartbeat.
    # Simulates an RPC.
    # Output: tuple (@term, @success), where @term is the
    # current term, and @success indicating success (boolean).
    def append_entries_RPC(self, term, leader_id, prev_log_index,
                           prev_log_term, entries,
                           leader_commit):
        # print("Thread {} handling AppendEntries RPC".format(self.id))

        if self.id == leader_id:  # if leader sent RPC to itself
            raise AssertionError("Leader sent AppendEntries RPC to itself")

        if term < self.current_term:
            return (self.current_term, False)
        if (self.log.len() <= prev_log_index
            or self.log.get(prev_log_index).term != prev_log_term):
            return (self.current_term, False)
        # TODO: #3 and 4 and 5
        # if leader_commit > self.commit_index:
            # self.commit_index = min(leader_commit, (index of last new entry))
        
        if self.leader_id == self.NO_LEADER:  # if hasn't acknowledged this leader
            self.leader_id = leader_id
            # if self.state != self.State.FOLLOWER:
            #     self.__become_follower()
        return (self.current_term, True)

    # Invoked by candidates on other raft servers to gather votes.
    def request_vote_RPC(term, candidate_id, last_log_index,
                         last_log_term):
        if term < self.current_term:  # requester's log is out of date
            return (self.current_term, False)
        if self.voted_for == None or self.voted_for == candidate_id:
            return (self.current_term, True)
        else:  # already voted for somene else
            return (self.current_term, False)

    # TODO: remove
    def make_leader(self):
        self.__become_leader()
    def make_follower(self):
        self.__become_follower()
    def make_candidate(self):
        self.__become_candidate()
    def leader_die(self):
        self.__become_follower()  # stop sending heartbeats
    def add_vote(self):
        self.num_votes += 1
Esempio n. 21
0
    def run(self):
        # check if all necessary variables are assigned in self.state
        if self.state['water_level'] is None:
            warning = 'Water level is none. Check if tank is full. Aborting.'
            Log.append(warning)
            print(warning)
            return

        if self.state[
                'pump_1_interval'] is None:  ## later --> or self.state['pump_2_interval']
            warning = 'pump 1 interval is None.'
            Log.append(warning)
            print(warning)
            return

        if self.state['pump_1_water_amount'] is None:
            warning = 'pump 1 water amount is None.'
            Log.append(warning)
            print(warning)
            return

        while True:
            if time.time() - self.last_test_time >= self.TEST_STEP_TIME:
                # test
                State.sync()
                self.state = State.load_state_from_file()
                if self.state['run_test'] == 2:
                    self.run_test()
                State.save_state_to_file(self.state)
                State.sync()
                self.last_test_time = time.time()

            if time.time(
            ) - self.moisture_last_check_time >= self.MOISTURE_CHECK_STEP:
                # moisture sensors check
                m1_state = self.m_sensor_1.state()
                if m1_state == MSensor.DRY:
                    if self.state['dry_alert_1'] == 0:
                        self.state['dry_alert_1'] = time.time()
                        Log.append('Moisturness sensor 1 is dry.')
                else:
                    if self.state['dry_alert_1'] != 0:
                        Log.append('Moisturness sensor 1 is wet again.')
                        self.state['dry_alert_1'] = 0

                m2_state = self.m_sensor_2.state()
                if m2_state == MSensor.DRY:
                    if self.state['dry_alert_2'] == 0:
                        self.state['dry_alert_2'] = time.time()
                        Log.append('Moisturness sensor 2 is dry.')
                else:
                    if self.state['dry_alert_2'] != 0:
                        Log.append('Moisturness sensor 2 is wet again.')
                        self.state['dry_alert_2'] = 0
                State.save_state_to_file(self.state)
                State.sync()
                self.moisture_last_check_time = time.time()

            if self.state['last_loop_time'] is None or time.time(
            ) - self.state['last_loop_time'] >= self.STEP_TIME:  # perform step
                #Log.append('--------------- New Loop ----------------')

                State.sync()
                self.state = State.load_state_from_file()

                # check if watering is scheduled
                # PUMP_1
                if self.state['pump_1_last_watering'] == 0 or time.time(
                ) - self.state['pump_1_last_watering'] >= self.state[
                        'pump_1_interval']:
                    # watering is needed
                    Log.append('Pump 1 watering is required.')
                    if self.state['low_water_level_alert'] == 1:
                        # send warning to user APK
                        Log.append("Low water level in main tank.")
                        if self.state['water_level'] < self.state[
                                'pump_1_water_amount']:
                            if self.state[
                                    'water_level'] > 400:  # pump whatever is left
                                if self.pump_1.pump_ml(
                                        self.state['water_level']):
                                    self.pumped(pump_nr=1)
                                else:
                                    Log.append('Pump 1 watering error.')
                            else:
                                # if no water, don't run pumps.
                                Log.append(
                                    "Water tank is empty. Skipping watering.")
                        else:
                            # run pump
                            if self.pump_1.pump_ml(
                                    self.state['pump_1_water_amount']):
                                self.pumped(pump_nr=1)
                            else:
                                # write to error log
                                Log.append('Pump 1 watering error.')
                    else:  # no water level alert
                        # run pump
                        if self.pump_1.pump_ml(
                                self.state['pump_1_water_amount']):
                            self.pumped(pump_nr=1)
                        else:
                            # write to an error log
                            Log.append('Pump 1 watering error.')

                # #
                # same for PUMP_2
                # #

                # check water level
                if self.water_level_sensor.state() == WaterLevel.LOW:
                    if self.state['low_water_level_alert'] == 0:
                        Log.append('Low water level reached.')
                        self.state['low_water_level_alert'] = 1
                        self.state['water_level'] = self.WATER_RESERVE_CAPACITY
                else:
                    if self.state['low_water_level_alert'] == 1:
                        self.tank_refilled(
                        )  # if alert was 1, and now is 0, tank was refilled.

                # on loop end.

                self.state['last_loop_time'] = time.time()
                State.save_state_to_file(self.state)
                State.sync()
                #Log.append('-------------- End of loop --------------')
            else:
                time.sleep(10)
Esempio n. 22
0
 def run_test(self):
     Log.append('Test run.')
     self.pump_1.turn_on_for_time(4)
     self.state['run_test'] = 1
     State.save_state_to_file(self.state)
Esempio n. 23
0
 def pumped(self, pump_nr):
     self.state['water_level'] -= self.state['pump_' + str(pump_nr) +
                                             '_water_amount']
     self.state['pump_' + str(pump_nr) + '_last_watering'] = time.time()
     Log.append('Pump '+str(pump_nr)+' pumped ' + str(self.state['pump_'+str(pump_nr)+'_water_amount']) + ' ml. ' +\
          str(self.state['water_level']) +' ml left in tank.')
Esempio n. 24
0
 def tank_refilled(self):
     Log.append('Tank refilled.')
     self.state['low_water_level_alert'] = 0
     self.state['water_level'] = self.state[
         'tank_capacity']  #  tank is always refilled fully.
     State.save_state_to_file(self.state)
Esempio n. 25
0
                # #

                # check water level
                if self.water_level_sensor.state() == WaterLevel.LOW:
                    if self.state['low_water_level_alert'] == 0:
                        Log.append('Low water level reached.')
                        self.state['low_water_level_alert'] = 1
                        self.state['water_level'] = self.WATER_RESERVE_CAPACITY
                else:
                    if self.state['low_water_level_alert'] == 1:
                        self.tank_refilled(
                        )  # if alert was 1, and now is 0, tank was refilled.

                # on loop end.

                self.state['last_loop_time'] = time.time()
                State.save_state_to_file(self.state)
                State.sync()
                #Log.append('-------------- End of loop --------------')
            else:
                time.sleep(10)


if __name__ == '__main__':
    if os.path.isfile(
            os.path.join(os.path.dirname(__file__), 'data', 'autorun')):
        Log.append('Autorun enabled.')
        c = Control()
        c.run()
    else:
        Log.append('Autorun disabled.')
Esempio n. 26
0
class Server(threading.Thread):
    def __init__(self, queue, port, id):
        self.port = port
        self.id = id
        self.queue = queue
        self.title = constants.TITLE_FOLLOWER
        self.channel = network.Network(port, id)
        self.channel.start()
        self.leader = None
        self.running = True

        self.connected_servers = []

        self.last_heartbeat = 0
        self.heartbeat_timeout = 0
        self.process_heartbeat()
        self.heartbeat_frequency = 0.5
        self.election_start_time = 0
        self.election_timeout = 0  # Time to wait for heartbeat or voting for a candidate before calling election
        self.set_election_timeout()
        # Election variables
        self.id_received_votes = set()  # Id of servers who granted you votes
        self.id_refused_votes = set(
        )  # Id of servers who refused to vote for you
        self.num_received_votes = 0  # Number of votes received in current election

        # Persistent state variables
        # TODO: PERSIST; On server boot, retrieve information from disk
        self.current_term = 0  # Latest term server has seen
        self.voted_for = None  # CandidateId that received vote in current term
        self.log = Log()

        self.next_index = None  # For leader: indices for updating follower logs
        self.latest_index_term = None  # For leader: tuples of latest entry index and term for each follower. Used for commit

        self.load_state()
        threading.Thread.__init__(self)

    def set_election_timeout(self):
        self.election_timeout = 1.5 * random() + 1.5

    def process_heartbeat(self):
        self.last_heartbeat = time.time()
        self.heartbeat_timeout = 1.5 * random() + 1.5

    def request_votes(self):
        if not self.log.data:
            # Log is empty
            last_log_index = -1
            last_log_term = -1
        else:
            last_log_index = self.log.get(-1).index
            last_log_term = self.log.get(-1).term

        msg = RequestVoteMessage(self.id, self.current_term, last_log_index,
                                 last_log_term)
        for server in self.connected_servers:
            self.channel.send(msg, id=host_to_id[server[0]])
            # print "Requesting vote from server", host_to_id[server[0]]

        print "Vote requests sent to other servers"

    def request_remaining_votes(self, id_all_voters):
        if not self.log.data:
            # Log is empty
            last_log_index = -1
            last_log_term = -1
        else:
            last_log_index = self.log.get(-1).index
            last_log_term = self.log.get(-1).term

        msg = RequestVoteMessage(self.id, self.current_term, last_log_index,
                                 last_log_term)

        for server in self.connected_servers:
            server_id = host_to_id[server[0]]
            if server_id not in id_all_voters:
                self.channel.send(msg, id=server_id)
                # print "Requesting vote from server", host_to_id[server[0]]

            print "Vote requests sent to remaining servers who have not responded"

    def check_status(self):
        current_time = time.time()
        if self.title == constants.TITLE_LEADER:
            # Send AppendEntries to update follower logs
            for server in self.connected_servers:
                server_id = host_to_id[server[0]]
                next_index = self.next_index[server_id]

                # Send entries that the server has not received yet, if any
                if self.log.last_log_index() >= next_index:
                    entries = self.construct_entries_list(next_index)
                    if next_index == 0:
                        prev_log_index = -1
                        prev_log_term = -1
                    else:
                        prev_log_index = self.log.get(next_index - 1).index
                        prev_log_term = self.log.get(next_index - 1).term
                    msg = AppendEntriesMessage(self.current_term, self.id,
                                               prev_log_index, prev_log_term,
                                               entries,
                                               self.log.last_commit_index)

                    self.channel.send(msg, id=server_id)
                    print "AppendEntries sent to ", server_id

            if current_time - self.last_heartbeat >= self.heartbeat_frequency:
                self.send_heartbeats()
        elif self.title == constants.TITLE_FOLLOWER and current_time - self.last_heartbeat > self.heartbeat_timeout:
            # Heartbeat timeout passed as follower: Start election
            print "Election timeout as follower. No heartbeat. Become candidate and start new election"
            self.start_election()
        elif self.title == constants.TITLE_CANDIDATE and current_time - self.election_start_time > self.election_timeout:
            # Election timeout passed as candidate, without conclusion of election: Start new election
            print "Election timeout as candidate. Election has not yet led to new leader. Starting new election"
            self.set_election_timeout()
            self.start_election()
        elif self.title == constants.TITLE_CANDIDATE and current_time - self.election_start_time < self.election_timeout:
            # Election timeout has not passed as candidate
            print "As candidate, election timeout has not passed. Request votes from servers that have not responded"
            id_all_voters = self.id_received_votes.union(self.id_refused_votes)
            self.request_remaining_votes(id_all_voters)

    def construct_entries_list(self, index):
        entries = []
        for i in range(index, len(self.log)):
            entries.append(self.log.get(i))
        return entries

    def start_election(self):
        self.title = constants.TITLE_CANDIDATE
        self.reset_election_info()
        self.current_term += 1
        self.save_state()
        # TODO: Voted_for must persist
        self.voted_for = self.id
        self.save_state()
        self.update_votes(self.id, True)
        self.election_start_time = time.time()
        self.check_election_status()

        self.request_votes()

    def send_heartbeats(self):
        heartbeat = AppendEntriesMessage(self.current_term, self.id, -1, -1,
                                         [], self.log.last_commit_index)
        for server in self.connected_servers:
            self.channel.send(heartbeat, id=host_to_id[server[0]])
        self.process_heartbeat()

    def step_down(self):
        # Step down as leader or candidate, convert to follower
        # Reset various election variables
        if self.title == constants.TITLE_LEADER or self.title == constants.TITLE_CANDIDATE:
            self.title = constants.TITLE_FOLLOWER
            self.process_heartbeat()
            self.reset_election_info()

    def grant_vote(self, candidate_id):
        # TODO: Voted_for must persist
        self.voted_for = candidate_id
        self.save_state()
        print "Grant vote to", candidate_id
        self.channel.send(VoteReplyMessage(self.id, self.current_term, True),
                          id=candidate_id)

    def refuse_vote(self, candidate_id):
        self.channel.send(VoteReplyMessage(self.id, self.current_term, False),
                          id=candidate_id)
        print "Refuse vote to", candidate_id

    def majority(self):
        return (len(self.connected_servers) + 1) / 2 + 1

    def check_election_status(self):
        if self.num_received_votes >= self.majority():
            # Become leader when granted majority of votes
            self.become_leader()

    def become_leader(self):
        self.title = constants.TITLE_LEADER
        self.leader = self.id
        print "Election won - I am now LEADER"
        # TODO: Implement rest of leader initialization
        self.next_index = [len(self.log) for _ in range(len(addr_to_id))]

        if self.log.last_commit_index == -1:
            latest_index = None
        else:
            latest_index = self.log.last_commit_index

        if latest_index is None:
            latest_term = 0
        elif self.log.contains_at_index(latest_index):
            latest_term = self.log.get(latest_index).term
        else:
            latest_term = 0

        self.latest_index_term = [(latest_index, latest_term)
                                  for _ in range(len(addr_to_id))]
        self.latest_index_term[self.id] = (len(self.log) - 1,
                                           self.current_term)
        self.reset_election_info()
        self.send_heartbeats()

    def reset_election_info(self):
        self.id_received_votes = set()
        self.id_refused_votes = set()
        self.voted_for = None
        self.num_received_votes = 0

    # server_id: server that sent vote reply; vote_granted: True if vote granted
    def update_votes(self, server_id, vote_granted):
        if vote_granted:
            print "Received vote from", server_id
            self.id_received_votes.add(server_id)
            self.num_received_votes = len(self.id_received_votes)
            print "Number of received votes is now", self.num_received_votes
        else:
            print "Denied vote from", server_id
            self.id_refused_votes.add(server_id)

    def update_commits(self):
        index = max(self.next_index)

        i_count = 0
        t_count = 0
        while i_count < self.majority() and index >= 0:
            if index < 0:
                print "Error: Update_commits: index is less than 0"
            index -= 1
            t_count = 0
            i_count = 0
            for (i, t) in self.latest_index_term:
                if t == self.current_term:
                    t_count += 1
                if i >= index:
                    i_count += 1

        if t_count >= self.majority() and i_count >= self.majority():
            if self.log.last_commit_index < index:
                self.log.last_commit_index = index
                self.save_state()
            elif self.log.last_commit_index > index:
                print "Error: Update_commits: new commit index is lower than current commit_index"

            for entry in self.log.data:
                if not entry.client_ack_sent:
                    # TODO: Send client ack
                    ack_message = AcknowledgeMessage(ack=True,
                                                     msg_id=entry.msg_id)
                    self.channel.send(ack_message, id=entry.author)
                    entry.client_ack_sent = True

    def run(self):
        print "Server with id=", self.id, " up and running"
        while self.running:
            self.update_connected_servers()
            for server in list(addr_to_id.keys()):
                # if server not in self.connected_servers and not addr_to_id[server] == id:
                if server not in self.channel and not host_to_id[
                        server[0]] == self.id:
                    connected = self.channel.connect(server)
                    if connected:
                        print str("Server: Connected to " + server[0])
                        if server not in self.connected_servers:
                            self.connected_servers.append(server)
                    # print "Connected: ", connected

                data = self.channel.receive(RECEIVE_FREQ)
                if data:
                    # print "There is data on channel"
                    for server_id, msg in data:
                        self.process_msg(server_id, msg)
                else:
                    self.check_status()

    def process_msg(self, sender_id, msg):

        #print "Processing message from", sender_id, "of type", msg.type
        if msg.type == constants.MESSAGE_TYPE_REQUEST_VOTE:
            self.process_request_vote(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_VOTE_REPLY:
            self.process_vote_reply(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_REQUEST_LEADER:
            msg = messages.RequestLeaderMessage(leader=self.leader)
            self.channel.send(msg, id=sender_id)

        elif msg.type == constants.MESSAGE_TYPE_LOOKUP:
            self.process_lookup(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_POST:
            self.process_post(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_APPEND_ENTRIES:
            self.process_append_entries(sender_id, msg)

        elif msg.type == constants.MESSAGE_TYPE_ACKNOWLEDGE:
            self.process_acknowledge(sender_id, msg)

        # Used for testing purposes
        elif msg.type == constants.MESSAGE_TYPE_TEXT:
            print "From", msg.sender_id, ":", msg.msg

        else:
            print "Error: Invalid message type"

    def process_lookup(self, sender_id, msg):
        if self.title == constants.TITLE_LEADER or msg.override:
            print "-----> Processing Lookup from client"
            posts = self.log.get_committed_entries()
            msg = messages.LookupMessage(msg_id=msg.msg_id,
                                         post=posts,
                                         server_id=self.id)
            self.channel.send(msg=msg, id=sender_id)
        else:
            print "Lookup to leader"
            msg = messages.RequestLeaderMessage(leader=self.leader)
            self.channel.send(msg=msg, id=sender_id)

    def process_post(self, sender_id, msg):
        if self.title == constants.TITLE_LEADER:
            # TODO: Implement adding entry
            # TODO: PERSIST; implement in log class?
            entry = Entry(msg.post,
                          sender_id,
                          self.current_term,
                          len(self.log),
                          msg_id=msg.msg_id)

            if self.log.append(entry):
                self.save_state()
                self.latest_index_term[self.id] = (len(self.log) - 1,
                                                   self.current_term)
                print "---->Append entry from client to log"

        else:
            msg = messages.RequestLeaderMessage(leader=self.leader)
            self.channel.send(msg=msg, id=sender_id)

    def process_request_vote(self, sender_id, msg):
        if not self.log:
            # Log is empty
            last_log_index = -1
            last_log_term = -1
        else:
            last_log_index = self.log.get(-1).index
            last_log_term = self.log.get(-1).term

        # Handle message
        if msg.term < self.current_term:
            # If candidate's term is less than my term then refuse vote
            print "Refuse vote to server", sender_id, "because I have higher term"
            self.refuse_vote(msg.candidate_id)

        if msg.term > self.current_term:
            # If candidate's term is greater than my term then update current_term (latest term I've encountered),
            # Step down if leader or candidate
            self.current_term = msg.term
            self.save_state()
            # TODO: Step down if leader or candidate
            self.step_down()

        if msg.term >= self.current_term:
            # If candidate's term is at least as new as mine and I have granted anyone else a vote
            # and candidate's log is at least as complete as mine
            # then grant vote
            if self.voted_for is None or self.voted_for is msg.candidate_id:
                if last_log_term < msg.last_log_term or (
                        last_log_term == msg.last_log_term
                        and last_log_index <= msg.last_log_index):
                    self.grant_vote(msg.candidate_id)
        else:
            # print "Cand term, current_term:", msg.term, self.current_term
            # print "Voted for:", self.voted_for
            # print "Cand log term, last_log_term", msg.last_log_term, last_log_term
            # print "Cand log index, last_log_index", msg.last_log_index, last_log_index
            self.refuse_vote(msg.candidate_id)

    def process_vote_reply(self, sender_id, msg):
        if msg.term > self.current_term and not msg.vote_granted:
            # Step down if reply from someone with higher term
            # Extra condition for security.
            # If responder's term is higher, then vote should not be granted with correct execution
            self.current_term = msg.term
            self.save_state()
            print "Denied vote from", msg.follower_id
            self.step_down()
        else:
            # Take care of grant or refusal of vote
            self.update_votes(msg.follower_id, msg.vote_granted)
            self.check_election_status()

    def process_acknowledge(self, sender_id, msg):
        if msg.ack:
            print "Process Acknowledge from server. ACK == TRUE"
            self.next_index[sender_id] = msg.next_index
            self.latest_index_term[sender_id] = msg.latest_index_term
            self.update_commits()
        else:
            print "Process Acknowledge from server. ACK == FALSE"
            if self.next_index[sender_id] - 1 < 0:
                self.next_index[sender_id] = 0
            else:
                self.next_index[sender_id] -= 1
            if msg.term > self.current_term:
                self.current_term = msg.term
                self.save_state()
                self.step_down()

    def process_append_entries(self, sender_id, msg):
        if len(msg.entries) == 0:
            self.process_heartbeat()

            if msg.commit_index < len(self.log):
                self.log.last_commit_index = msg.commit_index
                self.save_state()

            self.leader = sender_id
            #print "Heartbeat received from server", sender_id

            if self.title == constants.TITLE_CANDIDATE or self.title == constants.TITLE_LEADER:
                self.step_down()

            elif self.title == constants.TITLE_LEADER:
                # TODO: If a "leader" receives a heartbeat,
                # it might have crashed and joined back in after an election (?)
                pass
        else:
            # TODO: Process AppendEntriesMessage
            print "-->Processing AppendEntriesMessage from leader"
            self.process_heartbeat()
            if msg.term > self.current_term:
                self.current_term = msg.term
                self.save_state()

            if self.title == constants.TITLE_CANDIDATE or self.title == constants.TITLE_LEADER:
                self.step_down()

            # Reject if my term is greater than leader term
            if self.current_term > msg.term:
                print "Error: Current term greater than leaders term"
                self.channel.send(AcknowledgeMessage(ack=False,
                                                     term=self.current_term),
                                  id=sender_id)

            # Accept. Self.log is empty and leader is sending all entries
            elif self.log.is_empty() and msg.prev_log_index == -1:
                print "Appending entries"
                # First entry to append is at index 0
                if self.log.append_entries(msg.entries):
                    self.log.last_commit_index = msg.commit_index
                    self.save_state()
                    i = self.log.last_log_index()
                    t = self.log.get(i).term
                    self.channel.send(AcknowledgeMessage(
                        ack=True,
                        next_index=len(self.log),
                        latest_index_term=(i, t)),
                                      id=sender_id)
                    print "Log after appending entries:"
                    self.log.show_data()
                else:
                    print "DET HER SKAL IKKE SKJE 1"

            # Accept. Check if self.log has an element at msg.prev_log_index
            elif self.log.contains_at_index(msg.prev_log_index):
                # Check if the term corresponds with msg.prev_log_term
                if self.log.get(msg.prev_log_index).term == msg.prev_log_term:
                    if self.log.append_entries(msg.entries):
                        self.log.last_commit_index = msg.commit_index
                        self.save_state()
                        i = self.log.last_log_index()
                        t = self.log.get(i).term
                        self.channel.send(AcknowledgeMessage(
                            ack=True,
                            next_index=len(self.log),
                            latest_index_term=(i, t)),
                                          id=sender_id)
                        print "Log after appending entries:"
                        self.log.show_data()
                    else:
                        print "DET HER SKAL IKKE SKJE NUMMER 2"
                else:
                    self.log.remove(msg.prev_log_index)
                    self.channel.send(AcknowledgeMessage(ack=False),
                                      id=sender_id)
            else:
                print "Send ACK-False"
                self.channel.send(AcknowledgeMessage(ack=False), id=sender_id)

    def save_state(self):
        storage.save(self.id, self.voted_for, self.current_term, self.log)

    def load_state(self):
        self.voted_for, self.current_term, self.log = storage.load(self.id)
        # print "voted for", self.voted_for
        print self.current_term
        print self.log

    def update_connected_servers(self):
        for addr in list(addr_to_id.keys()):
            if addr in self.channel.address_to_connection.keys(
            ) and addr not in self.connected_servers:
                self.connected_servers.append(id)

            if addr not in self.channel.address_to_connection.keys(
            ) and addr in self.connected_servers:
                self.connected_servers.remove(addr)
Esempio n. 27
0
class Bounded_Semaphore_Thread(Thread):
    def __init__(self, target, name='', args=(), DEBUG=False):
        self.this_log = Log(prefix='thread_class',
                            log_dir=LOG_DIR,
                            DEBUG=DEBUG)
        self.this_log.append("created log file")

        msg = "thread_class.py --> Bounded_Semaphore_Thread --> inside init"
        self.this_log.append(msg)

        if not args:
            msg = "thread_class.py --> Bounded_Semaphore_Thread --> args: EMPTY!"
            self.this_log.append(msg)

        msg = "thread_class.py --> Bounded_Semaphore_Thread --> name: %s" % name
        self.this_log.append(msg)

        msg = "thread_class.py --> Bounded_Semaphore_Thread --> args: %s" % str(
            args)
        self.this_log.append(msg)
        Thread.__init__(self, target=target, name=name, args=args)

    def run(self, DEBUG=False):
        wait_start_time = datetime.datetime.now()

        msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s -->  waiting: %s" % (
            self.getName(), str(datetime.datetime.now().time()))
        self.this_log.append(msg)

        threadLimiter.acquire()
        msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s --> approved: %s" % (
            self.getName(), str(datetime.datetime.now().time()))
        self.this_log.append(msg)

        wait_finish_time = datetime.datetime.now()
        difference = evaluate_Time_Difference(wait_finish_time,
                                              wait_start_time)
        msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s -------> wait time: %s" % (
            self.getName(), difference)
        self.this_log.append(msg)
        try:
            run_start_time = datetime.datetime.now()

            msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s -->  running: %s" % (
                self.getName(), str(datetime.datetime.now().time()))
            self.this_log.append(msg)
            # http://www.pythonforbeginners.com/super/working-python-super-function
            super(Bounded_Semaphore_Thread, self).run()
        finally:
            msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s -> completed: %s" % (
                self.getName(), str(datetime.datetime.now().time()))
            self.this_log.append(msg)

            run_finish_time = datetime.datetime.now()
            difference = evaluate_Time_Difference(run_finish_time,
                                                  run_start_time)
            msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s --------> run time: %s" % (
                self.getName(), difference)
            self.this_log.append(msg)

            difference = evaluate_Time_Difference(run_finish_time,
                                                  wait_start_time)
            msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s ------> total time: %s" % (
                self.getName(), difference)
            print msg
            self.this_log.append(msg)

            threadLimiter.release()
            msg = "thread_class.py --> Bounded_Semaphore_Thread --> %s --> released: %s" % (
                self.getName(), str(datetime.datetime.now().time()))
            self.this_log.append(msg)
Esempio n. 28
0
class Server(object):

    def __init__(self, peers, host, port):
        self.host = host
        self.port = port
        self.peer_id = '{}:{}'.format(host, port)

        self._logger = logging.getLogger(__name__)
        self._loop = asyncio.get_event_loop()
        self._pool = Pool(self, peers)

        # heartbeat constants and bookkeeping variables
        self._heartbeat_interval = 1000  # ms
        self._last_interval = None

        self._min_heartbeat_timeout = 2000  # ms
        self._max_heartbeat_timeout = 4000  # ms
        self._heartbeat_timeout = None
        self._last_heartbeat = None

        self.reset_heartbeat()
        self.reset_timeout()

        self._log = Log(Machine())
        self.state = State.FOLLOWER
        self.term = 0
        self.voted = None
        self.votes = set()

        self._pending_clients = {}

        self.handlers = {'append_entries_req': self.handle_append_entries_req,
                         'append_entries_resp': self.handle_append_entries_resp,
                         'request_vote_req': self.handle_request_vote_req,
                         'request_vote_resp': self.handle_request_vote_resp}

    def reset_heartbeat(self):
        self._last_heartbeat = self._loop.time()

    def reset_interval(self):
        self._last_interval = self._loop.time()

    def reset_timeout(self):
        self._heartbeat_timeout = randint(self._min_heartbeat_timeout,
                                          self._max_heartbeat_timeout) / 1000

    @property
    def stale(self):
        return self._last_heartbeat + self._heartbeat_timeout < self._loop.time()

    @staticmethod
    def decode(data):
        return json.loads(data.decode())

    @staticmethod
    def encode(data):
        return json.dumps(data).encode()

    def broadcast(self, request):
        for peer in self._pool:
            self.send_async(peer, request)

    @asyncio.coroutine
    def run(self):
        self.reset_interval()

        while True:
            self._logger.debug('state: {}, term: {}'.format(self.state,
                                                            self.term))

            if self.state == State.LEADER:
                self.append_entries()

            if self.state in (State.CANDIDATE, State.FOLLOWER) and self.stale:
                self.request_vote()

            yield from self.wait()

    @asyncio.coroutine
    def send(self, peer, request):
        """
        Send a request to a peer (if available).
        """
        transport = yield from peer.get_transport()

        if transport:
            transport.write(self.encode(request))

    def send_async(self, peer, request):
        """
        Schedule the execution
        """
        asyncio.async(self.send(peer, request))

    @asyncio.coroutine
    def wait(self):
        """
        Wait for the next interval.
        """
        tic = self._heartbeat_interval / 1000 - self._loop.time() + self._last_interval
        yield from asyncio.sleep(tic)

        self.reset_interval()

    def to_leader(self):
        self.append_entries()
        self.state = State.LEADER
        self.voted = None
        self.votes = set()

        for peer in self._pool:
            peer.match = -1
            peer.next = self._log.index + 1

    def to_follower(self, term):
        self.state = State.FOLLOWER
        self.term = term
        self.voted = None
        self.votes = set()

    def append_entries(self, peer=None):
        """
        Append entries RPC.
        """
        peers = self._pool.all() if peer is None else [peer]

        for peer in peers:
            log_entries = self._log[peer.next:]
            log_index, log_term, _ = self._log[peer.next - 1]

            request = {'rpc': 'append_entries_req',
                       'peer_id': self.peer_id,
                       'term': self.term,
                       'log_commit': self._log.commit,
                       'log_entries': log_entries,
                       'log_index': log_index,
                       'log_term': log_term,
                       }

            self.send_async(peer, request)

        self._logger.debug('broadcasting append entries')

    def request_vote(self):
        """
        Request vote RPC.
        """
        self.reset_heartbeat()
        self.reset_timeout()

        self.state = State.CANDIDATE
        self.term += 1
        self.voted = self.peer_id
        self.votes = set([self.peer_id])

        request = {'rpc': 'request_vote_req',
                   'peer_id': self.peer_id,
                   'term': self.term,
                   'log_index': self._log.index,
                   'log_term': self._log.term,
                   }

        self.broadcast(request)
        self._logger.debug('broadcasting request vote')

    def handle_peer(self, request):
        """
        Dispatch requests to the appropriate handlers.
        """
        if self.term < request['term']:
            self.to_follower(request['term'])

        return self.handlers[request['rpc']](request)

    def handle_append_entries_req(self, request):
        self._logger.debug('append entries request received')

        if request['term'] < self.term:
            return

        self.reset_heartbeat()

        log_index = request['log_index']
        log_term = request['log_term']

        if not self._log.match(log_index, log_term):
            return {'rpc': 'append_entries_resp',
                    'peer_id': self.peer_id,
                    'term': self.term,
                    'log_index': self._log.index,
                    'success': False
                    }

        log_entries = request['log_entries']
        self._log.append(log_index, log_entries)

        log_commit = request['log_commit']
        if self._log.commit < log_commit:
            index = min(self._log.index, log_commit)
            self._log.commit = index
            self._log.apply(index)

        if not log_entries:  # no need to answer, the peer might have committed
            return  # new entries but has certainly not replicated new ones

        return {'rpc': 'append_entries_resp',
                'peer_id': self.peer_id,
                'term': self.term,
                'log_index': self._log.index,
                'log_term': self._log.term,
                'success': True,
                }

    def handle_append_entries_resp(self, response):
        if response['success']:
            self._logger.debug('append entries succeeded')

            log_index = response['log_index']
            log_term = response['log_term']
            peer_id = response['peer_id']

            self._pool[peer_id].match = log_index
            self._pool[peer_id].next = log_index + 1

            if (self._log.commit < log_index and
                self._pool.ack(log_index) and log_term == self.term):
                self._log.commit = log_index
                results = self._log.apply(log_index)
                self.return_results(results)

        else:
            peer = self._pool[response['peer_id']]
            peer.next -= 1
            self.append_entries(peer)

            # self._logger.debug('append entries failed')

    def handle_request_vote_req(self, request):
        self._logger.debug('request vote request received')

        if request['term'] < self.term:
            return

        log_index = request['log_index']
        log_term = request['log_term']
        peer_id = request['peer_id']

        if self.voted in (None, peer_id) and self._log.match(log_index, log_term):
            granted = True
            self.reset_heartbeat()
        else:
            granted = False

        return {'rpc': 'request_vote_resp',
                'peer_id': self.peer_id,
                'term': self.term,
                'granted': granted,
                }

    def handle_request_vote_resp(self, response):
        if self.term == response['term'] and response['granted']:
            self.votes.add(response['peer_id'])

            if self._pool.majority(len(self.votes)):
                self.to_leader()

    def handle_client(self, cmd, transport):
        self._log.add(self.term, cmd)
        self._pending_clients[(self.term, self._log.index)] = transport
        self.append_entries()

    def return_results(self, results):
        for result in results:
            term, index, result = result
            transport = self._pending_clients.pop((term, index))
            transport.write(self.encode(result))
            transport.close()