From abab0f9af8ceee7f159c6990041ab130df44f895 Mon Sep 17 00:00:00 2001 From: afederici <ajf5@illinois.edu> Date: Sun, 15 Nov 2020 17:12:34 -0600 Subject: [PATCH] init --- Distributed_System_MP2_Report_G24.pdf | Bin 0 -> 104628 bytes Makefile | 15 + README.md | 46 +- inc/FileObject.h | 25 + inc/HashRing.h | 39 + inc/Introducer.h | 30 + inc/Logger.h | 27 + inc/Member.h | 24 + inc/MessageTypes.h | 44 + inc/Messages.h | 20 + inc/Modes.h | 6 + inc/Node.h | 136 +++ inc/TcpSocket.h | 48 + inc/UdpSocket.h | 37 + src/FileObject.cpp | 24 + src/HashRing.cpp | 180 +++ src/Introducer.cpp | 38 + src/Logger.cpp | 83 ++ src/Member.cpp | 42 + src/Messages.cpp | 41 + src/Node.cpp | 1557 +++++++++++++++++++++++++ src/RandomGenerator.cpp | 69 ++ src/TcpSocket.cpp | 418 +++++++ src/Threads.cpp | 154 +++ src/UdpSocket.cpp | 183 +++ src/main.cpp | 220 ++++ 26 files changed, 3505 insertions(+), 1 deletion(-) create mode 100644 Distributed_System_MP2_Report_G24.pdf create mode 100644 Makefile create mode 100644 inc/FileObject.h create mode 100644 inc/HashRing.h create mode 100644 inc/Introducer.h create mode 100644 inc/Logger.h create mode 100644 inc/Member.h create mode 100644 inc/MessageTypes.h create mode 100644 inc/Messages.h create mode 100644 inc/Modes.h create mode 100644 inc/Node.h create mode 100644 inc/TcpSocket.h create mode 100644 inc/UdpSocket.h create mode 100644 src/FileObject.cpp create mode 100644 src/HashRing.cpp create mode 100644 src/Introducer.cpp create mode 100644 src/Logger.cpp create mode 100644 src/Member.cpp create mode 100644 src/Messages.cpp create mode 100644 src/Node.cpp create mode 100644 src/RandomGenerator.cpp create mode 100644 src/TcpSocket.cpp create mode 100644 src/Threads.cpp create mode 100644 src/UdpSocket.cpp create mode 100644 src/main.cpp diff --git a/Distributed_System_MP2_Report_G24.pdf b/Distributed_System_MP2_Report_G24.pdf new file mode 100644 index 0000000000000000000000000000000000000000..98ff47c896797c26594bf61584bde9cd170aba01 GIT binary patch literal 104628 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 literal 0 HcmV?d00001 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dce2206 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +APP := Node +INC_DIR := ./inc +CFLAGS := -g -Wall -std=c++11 -I$(INC_DIR) +LIBS := -lpthread +SRC_FILES := src/Node.cpp src/Messages.cpp src/Member.cpp src/UdpSocket.cpp src/Threads.cpp src/RandomGenerator.cpp src/Logger.cpp src/TcpSocket.cpp src/main.cpp src/HashRing.cpp src/FileObject.cpp + +.PHONY: clean + +all: clean app + +app: + $(CXX) -o $(APP) $(SRC_FILES) $(CFLAGS) $(LIBS) + +clean: + $(RM) -f $(APP) *.o diff --git a/README.md b/README.md index e178f0a..7406b84 100644 --- a/README.md +++ b/README.md @@ -1 +1,45 @@ -# MapReduce \ No newline at end of file +# MP2 Simple Distributed File System + +## Executing Instructions + * Building Node +``` +$ make all +``` + + * We are running All-to-All in MP2 (Note: Gossip-style is not tested) +``` +$ ./Node 0 +``` + + * Running time commands +``` +$ [join] join to a group via fixed introducer +$ [leave] leave the group +$ [id] print id (IP/PORT) +$ [member] print all membership list +$ [switch] switch to other mode (All-to-All to Gossip, and vice versa) +$ [mode] show in 0/1 [All-to-All/Gossip] modes +$ [exit] terminate process +$ === New since MP2 === +$ [put] localfilename sdfsfilename +$ [get] sdfsfilename localfilename +$ [delete] sdfsfilename +$ [ls] list all machine (VM) addresses where this file is currently being stored +$ [lsall] list all sdfsfilenames with positions +$ [store] list all files currently being stored at this machine +``` + + * Create files +``` +$ dd if=/dev/urandom of=test_file bs=2097152 count=2 +$ dd if=/dev/urandom of=test_file_07 bs=1000000 count=7 +``` + + + * All logs are in `logs.txt` under the mp2 folder + +## Acknowledgement + * [Beej's guide](http://beej.us/guide/bgnet/html/multi/index.html) + * [Multiple Threads](https://www.tutorialspoint.com/cplusplus/cpp_multithreading.htm) + * [String parser](https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c) + * [Wikicorpus](https://www.cs.upc.edu/~nlp/wikicorpus/) \ No newline at end of file diff --git a/inc/FileObject.h b/inc/FileObject.h new file mode 100644 index 0000000..7bbbdd4 --- /dev/null +++ b/inc/FileObject.h @@ -0,0 +1,25 @@ +#ifndef FILEOBJECT_H +#define FILEOBJECT_H + +#include <iostream> +#include <string> +#include <stdio.h> +#include <stdlib.h> +#include <fstream> + +#include "HashRing.h" + +using namespace std; + +class FileObject { +public: + string fileName; + string checksum; + int positionOnHashring; + FileObject(string fileName); + string toString(); + string getChecksum(); + int getPositionOnHashring(); +}; + +#endif //FILEOBJECT_H \ No newline at end of file diff --git a/inc/HashRing.h b/inc/HashRing.h new file mode 100644 index 0000000..7e0ff96 --- /dev/null +++ b/inc/HashRing.h @@ -0,0 +1,39 @@ +#ifndef HASHRING_H +#define HASHRING_H + +#include <iostream> +#include <string> +#include <stdio.h> +#include <stdlib.h> +#include <map> +#include <vector> +#include <algorithm> + +using namespace std; + +#define HASHMODULO 360 + +class HashRing { +public: + vector<int> nodePositions; //allow for quick finding of node positions on the ring. + map<int, string> ring; + map<int, int> fileToClosestNode; // match up each file to its closest node -> may not need to use this, since we have the locateClosestNode function which maps file position + //to node position on hash ring. + + HashRing(); + vector<string> splitString(string s, string delimiter); + int locateClosestNode(string filename); + string getValue(int key); + int addNode(string nodeInfo, int position); // Here we may have to change where other files point to + int addFile(string fileInfo, int position); + int removeNode(int position); // Here we will have to change where all files that point to this node point to + int removeFile(int position); + int getSuccessor(int nodePosition); //Get position of successor node + int getPredecessor(int nodePosition); //Get position of predecessor node + void debugHashRing(); + void clear(); + int getRandomNode(tuple<int, int, int> excludedNodes); + +}; + +#endif //HASHRING_H \ No newline at end of file diff --git a/inc/Introducer.h b/inc/Introducer.h new file mode 100644 index 0000000..06ce118 --- /dev/null +++ b/inc/Introducer.h @@ -0,0 +1,30 @@ +#ifndef INTRODUCER_H +#define INTRODUCER_H + +#include <iostream> +#include <string> +#include <vector> +#include <map> +#include "Messages.h" +#include "Member.h" + +using namespace std; + +#define T_timeout 5 +#define T_cleanup 5 + +#define INTRODUCER "172.22.94.78" +#define PORT "6000" + +class Introducer { +public: + map<pair<string, string>, pair<int, int>> membershipList; + Member nodeInformation; + + Introducer(); + int heartbeatToNode(Member destination); + int joinSystem(Member introdcuer); + int listenForHeartbeat(); +}; + +#endif //INTRODUCER_H \ No newline at end of file diff --git a/inc/Logger.h b/inc/Logger.h new file mode 100644 index 0000000..e6d189a --- /dev/null +++ b/inc/Logger.h @@ -0,0 +1,27 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include <iostream> +#include <string> +#include <fstream> +#include <stdio.h> + +#include "MessageTypes.h" + +#define LOGFILE "logs.txt" + +using namespace std; + +class Logger{ +public: + string filename; + //ofstream loggingFile; + Logger(); + Logger(string fileName); + int printTheLog(LogType type, string s); + //int closeFile(); +private: + int writeToFile(string messages); +}; + +#endif //LOGGER_H \ No newline at end of file diff --git a/inc/Member.h b/inc/Member.h new file mode 100644 index 0000000..41984b6 --- /dev/null +++ b/inc/Member.h @@ -0,0 +1,24 @@ +#ifndef MEMBER_H +#define MEMBER_H + +#include <iostream> +#include <string> + +using namespace std; + +class Member { +public: + string ip; + string port; + int timestamp; + int heartbeatCounter; + int failed_flag; + Member(); + Member(string nodeIp, string nodePort, int nodeTimestamp, int heartbeatCounter); + Member(string nodeIp, string nodePort); + Member(string nodeIp, string nodePort, int nodeTimestamp); + string toString(); +}; + + +#endif //MEMBER_H \ No newline at end of file diff --git a/inc/MessageTypes.h b/inc/MessageTypes.h new file mode 100644 index 0000000..4410644 --- /dev/null +++ b/inc/MessageTypes.h @@ -0,0 +1,44 @@ +#ifndef MESSAGESTYPES_H +#define MESSAGESTYPES_H + +enum MessageType { + ACK, + JOIN, + LEADERHEARTBEAT, + LEADERPENDING, + HEARTBEAT, + SWREQ, + SWRESP, + JOINRESPONSE, + JOINREJECT, + ELECTION, + ELECTIONACK, + PUT, + PUTACK, + LEADERACK, + DNS, + DNSANS, + DNSGET, + DELETE, + GETNULL, + REREPLICATE, + REREPLICATEGET}; + +enum PayloadType { + REGULAR=97, + FILEPAIR, + FILENAME, + FILEPOSITIONS}; + +enum LogType { + JOINGROUP, + UPDATE, + FAIL, + LEAVE, + REMOVE, + GOSSIPTO, + GOSSIPFROM, + BANDWIDTH, + MEMBERS}; + +#endif //MESSAGESTYPES_H \ No newline at end of file diff --git a/inc/Messages.h b/inc/Messages.h new file mode 100644 index 0000000..ee1ddb6 --- /dev/null +++ b/inc/Messages.h @@ -0,0 +1,20 @@ +#ifndef MESSAGES_H +#define MESSAGES_H + +#include <iostream> +#include <string> +#include "MessageTypes.h" + +using namespace std; + +class Messages { +public: + MessageType type; + string payload; + + Messages(string payloadMessage); + Messages(MessageType messageType, string payloadMessage); + string toString(); +}; + +#endif //MESSAGES_H \ No newline at end of file diff --git a/inc/Modes.h b/inc/Modes.h new file mode 100644 index 0000000..e45037e --- /dev/null +++ b/inc/Modes.h @@ -0,0 +1,6 @@ +#ifndef MODES_H +#define MODES_H + +enum ModeType {ALL2ALL, GOSSIP}; + +#endif //MODES_H \ No newline at end of file diff --git a/inc/Node.h b/inc/Node.h new file mode 100644 index 0000000..63931fa --- /dev/null +++ b/inc/Node.h @@ -0,0 +1,136 @@ +#ifndef NODE_H +#define NODE_H + +#include <iostream> +#include <string> +#include <vector> +#include <map> +#include <pthread.h> +#include <time.h> +#include <signal.h> + +#include "Messages.h" +#include "Modes.h" +#include "Member.h" +#include "UdpSocket.h" +#include "TcpSocket.h" +#include "Logger.h" +#include "HashRing.h" + +using namespace std; + +#define INTRODUCER "172.22.94.78" // VM1 +//#define INTRODUCER "172.22.158.81" // VM9 +#define PORT "6000" + +//#define LOG_VERBOSE 1 + +#define LOGGING_FILE_NAME "logs.txt" + +// --- parameters (stay tuned) --- +#define T_period 300000 // in microseconds +#define T_timeout 15 // in T_period +#define T_cleanup 15 // in T_period +#define N_b 5 // how many nodes GOSSIP want to use + +#define T_election 15 // in T_period +// ------ + +#define T_switch 3 // in seconds + +void *runUdpServer(void *udpSocket); +void *runTcpServer(void *tcpSocket); +void *runTcpSender(void *tcpSocket); +void *runSenderThread(void *node); + +class Node { +public: + // (ip_addr, port_num, timestamp at insertion) -> (hb_count, timestamp, fail_flag) + map<tuple<string, string, string>, tuple<int, int, int>> membershipList; + Member nodeInformation; + UdpSocket *udpServent; + TcpSocket *tcpServent; + int localTimestamp; + int heartbeatCounter; + time_t startTimestamp; + string joinTimestamp; + // unsigned long byteSent; + // unsigned long byteReceived; + + + ModeType runningMode; + Logger* logWriter; + bool activeRunning; + bool prepareToSwitch; + + bool isLeader; + bool isBlackout; + int leaderPosition; // -1 for no leader + int hashRingPosition; + int proposedTime; + int electedTime; + string possibleSuccessorIP; + string leaderIP; + string leaderPort; + HashRing *hashRing; + + map<string, vector<int>> fileList; //vector of node positions on a hashring for a given file where we can find that file stored + map<string, string> localFilelist; // Map sdfsfilename -> localfilename + map<string, tuple<int, int, int>> pendingRequests; + map<string, tuple<string, string, string>> pendingSenderRequests; + map<string, tuple<bool, bool, bool>> pendingRequestSent; + + + Node(); + Node(ModeType mode); + int getPositionOnHashring(); + int heartbeatToNode(); + int joinSystem(Member introdcuer); + int listenToHeartbeats(); + int failureDetection(); + void updateNodeHeartbeatAndTime(); + void computeAndPrintBW(double diff); + int requestSwitchingMode(); + int SwitchMyMode(); + void debugMembershipList(); + void startActive(); + // Added below Since MP2 + bool checkHashNodeCollision(int checkPosition); + bool checkLeaderExist(); + bool findWillBeLeader(); + void proposeToBeLeader(); + void processElection(Messages messages); + void processTcpMessages(); + void processRegMessages(); + void restartElection(); + void setUpLeader(string message, bool pending); + string encapsulateFileList(); + void decapsulateFileList(string payload); + string encapsulateMessage(map<PayloadType,string> payloads); + string decapsulateMessage(string payload); + bool isInVector(vector<int> v, int i); + int updateHashRing(); + // leader related functions here + void leaderCreateHashRing(); + int putFileSender(string filename, string sdfsfilename); + int putFileMaster(string sdfsfilename); + int putFileReeiver(string sdfsfilename); + void listLocalFiles(); + void findNodesWithFile(string sdfsfilename); + void debugSDFSFileList(); + void listSDFSFileList(string sdfsfilename); + string populateSDFSFileList(MessageType type, string mem_list_to_send); + void updateFileList(string sdfsfilename, int nodePosition); + void checkFileListConsistency(); + +private: + string populateMembershipMessage(); + string populateIntroducerMembershipMessage(); + void readMessage(string message); + void processHeartbeat(string message); + vector<string> splitString(string s, string delimiter); + vector<tuple<string,string, string>> getRandomNodesToGossipTo(); + int hashingId(Member nodeMember, string joinTime); +}; + +#endif //NODE_H \ No newline at end of file diff --git a/inc/TcpSocket.h b/inc/TcpSocket.h new file mode 100644 index 0000000..958e9f6 --- /dev/null +++ b/inc/TcpSocket.h @@ -0,0 +1,48 @@ +#ifndef TCPSOCKET_H +#define TCPSOCKET_H + +#include <iostream> +#include <string> +#include <queue> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <sys/wait.h> +#include <signal.h> + +// #include "Messages.h" +// #include "UdpSocket.h" + +using namespace std; + +#define DEFAULT_TCP_BLKSIZE (128 * 1024) +#define BACKLOG 10 + +#define TCPPORT "4950" + +// void *runTcpServer(void *tcpSocket); +// void *runTcpClient(void *tcpSocket); + +class TcpSocket { +public: + queue<string> qMessages; + queue<string> regMessages; + queue<string> pendSendMessages; + + void bindServer(string port); + void sendFile(string ip, string port, string localfilename, string sdfsfilename, string remoteLocalfilename); + void sendMessage(string ip, string port, string message); + TcpSocket(); +private: + string getFileMetadata(int size, string checksum, string sdfsfilename, string localfilename, string remoteLocalfilename); + vector<string> splitString(string s, string delimiter); +}; +#endif //TCPSOCKET_H \ No newline at end of file diff --git a/inc/UdpSocket.h b/inc/UdpSocket.h new file mode 100644 index 0000000..17402e0 --- /dev/null +++ b/inc/UdpSocket.h @@ -0,0 +1,37 @@ +#ifndef UDPSOCKET_H +#define UDPSOCKET_H + +#include <iostream> +#include <string> +#include <queue> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +using namespace std; + +#define MAXBUFLEN (128 * 1024) +#define LOSS_RATE 0 + +void *get_in_addr(struct sockaddr *sa); + +class UdpSocket { +public: + unsigned long byteSent; + unsigned long byteReceived; + queue<string> qMessages; + + void bindServer(string port); + void sendMessage(string ip, string port, string message); + UdpSocket(); + +}; +#endif //UDPSOCKET_H \ No newline at end of file diff --git a/src/FileObject.cpp b/src/FileObject.cpp new file mode 100644 index 0000000..1097041 --- /dev/null +++ b/src/FileObject.cpp @@ -0,0 +1,24 @@ +#include "../inc/FileObject.h" + +FileObject::FileObject(string fileName){ + this->fileName = fileName; + this->checksum = getChecksum(); +} + +string FileObject::getChecksum(){ + ifstream fileStream(fileName); + string fileContent((istreambuf_iterator<char>(fileStream)), + (istreambuf_iterator<char>())); + size_t hashResult = hash<string>{}(fileContent); + return to_string(hashResult); +} + +string FileObject::toString(){ + return fileName + "::" + checksum; +} + +int FileObject::getPositionOnHashring(){ + string toBeHashed = "FILE::" + fileName; + positionOnHashring = hash<string>{}(toBeHashed) % HASHMODULO; + return 0; +} diff --git a/src/HashRing.cpp b/src/HashRing.cpp new file mode 100644 index 0000000..ea42b6d --- /dev/null +++ b/src/HashRing.cpp @@ -0,0 +1,180 @@ +#include "../inc/HashRing.h" + + +HashRing::HashRing(){ + +} + +string HashRing::getValue(int key){ + if(key == -1){ + return "No node found"; + } + return ring[key]; +} + +void HashRing::clear() +{ + ring.clear(); + nodePositions.clear(); +} + +void HashRing::debugHashRing() +{ + cout << "Current Ring: " << endl; + for (auto& element: ring) { + int position = element.first; + string object = element.second; + cout << object << " at " << position << endl; + } +} + +vector<string> HashRing::splitString(string s, string delimiter){ + vector<string> result; + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + string token; + + while ((pos_end = s.find (delimiter, pos_start)) != string::npos) { + token = s.substr (pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + result.push_back (token); + } + + result.push_back (s.substr (pos_start)); + return result; +} + +int HashRing::addFile(string fileInfo, int position){ + ring[position] = fileInfo; + fileToClosestNode[position] = locateClosestNode(fileInfo); + //TODO: deal with hash collisions? + return 0; +} + +int HashRing::addNode(string nodeInfo, int position){ + ring[position] = nodeInfo; + nodePositions.push_back(position); //Add node position on hash ring to list of node positions + sort(nodePositions.begin(), nodePositions.end()); + //int insertPosition = -1; + //for(uint i = 0; i < nodePositions.size(); i++){ + // if(nodePositions[i] == position){ + // insertPosition = i; + // break; + // } + //} + /* commenting out this code since we are making hash ring node only + //Case where the newly inserted node is not at beginning of list + if(insertPosition != 0){ + for(map<int, int>::iterator it = fileToClosestNode.begin(); it != fileToClosestNode.end(); it++){ + if(it -> first <= nodePositions[insertPosition] && it -> first > nodePositions[insertPosition - 1]){ + it -> second = locateClosestNode(it -> first); + } + } + } + //If newly inserted not is at beginning of list, just need to look at files with indexes either less than that node's index, + //or at files with index greater than the highest node index + else{ + for(map<int, int>::iterator it = fileToClosestNode.begin(); it != fileToClosestNode.end(); it++){ + if(it -> first <= nodePositions[insertPosition] || it -> first > nodePositions[nodePositions.size() -1]){ + it -> second = locateClosestNode(it -> first); + } + } + } + */ + return 0; + //TODO: deal with hash collisions? +} + +int HashRing::removeFile(int position){ + ring.erase(position); + fileToClosestNode.erase(position); + return 0; + +} + +int HashRing::removeNode(int position){ + ring.erase(position); + for(uint i = 0; i < nodePositions.size(); i++){ + if(nodePositions[i] == position){ + nodePositions.erase(nodePositions.begin() + i); + } + } + //find all files that point to this node, and tell them to now point to their next closest node instead. + /* comment out this code since we are making hash ring node only at this point. + for(map<int, int>::iterator it = fileToClosestNode.begin(); it != fileToClosestNode.end(); it ++){ + if(it ->second == position){ + it ->second = locateClosestNode(it ->first); + } + }*/ + + return 0; +} + +int HashRing::locateClosestNode(string filename){ + int filePosition; + //Hash filename to get file position on hash ring, then use that to find closest node to it + filePosition = hash<string>{}(filename) % HASHMODULO; + for(int i : nodePositions){ + if(i >= filePosition){ + return i; + } + } + //If we cannnot find a Node at a entry on the hash circle greater than or equal to our file position, we wrap back around and take + // the first node's position as where that file should go. + return nodePositions[0]; +} + +int HashRing::getPredecessor(int nodePosition){ + if(nodePositions.size() == 1){ + return nodePosition; + } + unsigned int indexOfNode = -1; + for(uint i = 0; i < nodePositions.size(); i++){ + if(nodePositions[i] == nodePosition){ + indexOfNode = i; + break; + } + } + //If indexOfNode = 0, get the last entry in Node position as predecessor + if(indexOfNode == 0){ + return nodePositions[nodePositions.size() - 1]; + } + return nodePositions[indexOfNode - 1]; +} + +int HashRing::getSuccessor(int nodePosition){ + if(nodePositions.size() == 1){ + return nodePosition; + } + unsigned int indexOfNode = -1; + for(uint i = 0; i < nodePositions.size(); i++){ + if(nodePositions[i] == nodePosition){ + indexOfNode = i; + break; + } + } + //If indexOfNode = size of nodePosition - 1, get the first entry in Node position as successor + if(indexOfNode == nodePositions.size() - 1){ + return nodePositions[0]; + } + return nodePositions[indexOfNode + 1]; +} + + +//exclude the three positions in the tuple, +//return node position in hash ring of a random node that is not one of the +//three nodes in this tuple. +int HashRing::getRandomNode(tuple<int, int, int> excludedNodes){ + if(nodePositions.size() >= 4){ + //get random node + // return that random node + vector<int> indicesToPickFrom; + for(unsigned int i = 0; i < nodePositions.size(); i++){ + if(nodePositions[i] != get<0>(excludedNodes) && nodePositions[i] != get<1>(excludedNodes) && nodePositions[i] != get<2>(excludedNodes)){ + indicesToPickFrom.push_back(i); + } + } + int randomSelection = rand() % indicesToPickFrom.size(); + return nodePositions[indicesToPickFrom[randomSelection]]; + } + return -1; +} \ No newline at end of file diff --git a/src/Introducer.cpp b/src/Introducer.cpp new file mode 100644 index 0000000..1e948d3 --- /dev/null +++ b/src/Introducer.cpp @@ -0,0 +1,38 @@ +#include "../inc/Introducer.h" +#include <ctime> +#include <chrono> +Introducer::Introducer() +{ + +} +/** + * + * HeartbeatToNode: Sends a string version of the membership list to the receiving node. The receiving node will convert the string to + * a <string, long> map where the key is the Addresss (IP + PORT) and value is the heartbeat counter. We then compare the Member. + * + **/ +int Introducer::heartbeatToNode(Member destination){ + //The string we send will be seperated line by line --> IP,PORT,HeartbeatCounter + string mem_list_to_send = ""; + //Case 1 need to add destination to membership list + + + //Case 2: destination already exists in the membership list of introducer, just a normal heartbeat + for(auto& element: this->membershipList){ + pair<string, string> keyPair = element.first; + pair<int, int> valuePair = element.second; + mem_list_to_send += keyPair.first + "," + keyPair.second + ","; + mem_list_to_send += to_string(valuePair.first) + "\n"; + } + //Now we have messages ready to send, need to invoke UDP client to send + return 0; +} + +int Introducer::joinSystem(Member introducer){ + Messages join(JOIN, "IP: " + this->nodeInformation.ip + " PortNum: " + this->nodeInformation.port); + return 0; +} + +int Introducer::listenForHeartbeat(){ + +} diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..cc24aad --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,83 @@ +#include "../inc/Logger.h" + +Logger::Logger() +{ + filename = LOGFILE; + //loggingFile.open(filename); +} + +Logger::Logger(string fileName) +{ + filename = fileName; + //loggingFile.open(filename); +} + +int Logger::writeToFile(string messages) +{ + FILE *fp; + fp = fopen(filename.c_str(), "a"); + fprintf(fp, "%s", messages.c_str()); + fclose(fp); + return 0; +} + +int Logger::printTheLog(LogType type, string s) +{ + switch (type) { + case JOINGROUP: { + string messages = "[JOIN]"+s+"\n"; + writeToFile(messages); + break; + } + case UPDATE: { + string messages = "[UPDATE]"+s+"\n"; + writeToFile(messages); + break; + } + case FAIL: { + string messages = "[FAIL]"+s+"\n"; + writeToFile(messages); + break; + } + case REMOVE: { + string messages = "[REMOVE]"+s+"\n"; + writeToFile(messages); + break; + } + case LEAVE: { + string messages = "[LEAVE]"+s+"\n"; + writeToFile(messages); + break; + } + case GOSSIPTO: { + string messages = "[SENT]"+s+"\n"; + writeToFile(messages); + break; + } + case GOSSIPFROM: { + string messages = "[RECEIVED]"+s+"\n"; + writeToFile(messages); + break; + } + case BANDWIDTH: { + string messages = "[BANDWIDTH]"+s+"\n"; + writeToFile(messages); + break; + } + case MEMBERS: { + string messages = "[MembershipList] "+s+"\n"; + writeToFile(messages); + break; + } + default: + break; + } + //loggingFile << s; + return 0; +} + +/*int Logger::closeFile() +{ + loggingFile.close(); + return 0; +}*/ \ No newline at end of file diff --git a/src/Member.cpp b/src/Member.cpp new file mode 100644 index 0000000..a4177a0 --- /dev/null +++ b/src/Member.cpp @@ -0,0 +1,42 @@ +#include "../inc/Member.h" + +Member::Member(string nodeIp, string nodePort, int nodeTimestamp, int nodeHeartbeatCounter) +{ + ip = nodeIp; + port = nodePort; + failed_flag = 0; + timestamp = nodeTimestamp; + heartbeatCounter = nodeHeartbeatCounter; +} + +Member::Member(string nodeIp, string nodePort) +{ + ip = nodeIp; + port = nodePort; + timestamp = 0; + failed_flag = 0; + heartbeatCounter = 0; +} + +Member::Member(string nodeIp, string nodePort, int nodeTimestamp) +{ + ip = nodeIp; + port = nodePort; + timestamp = nodeTimestamp; + failed_flag = 0; + heartbeatCounter = 0; +} + +string Member::toString() +{ + string message = ip + "::" + port + "::" + to_string(timestamp); + return message; +} + +Member::Member(){ + ip = ""; + port = ""; + timestamp = 0; + failed_flag = 0; + heartbeatCounter = 0; +} \ No newline at end of file diff --git a/src/Messages.cpp b/src/Messages.cpp new file mode 100644 index 0000000..360efad --- /dev/null +++ b/src/Messages.cpp @@ -0,0 +1,41 @@ +#include "../inc/Messages.h" + +Messages::Messages(string payloadMessage) +{ + string delimiter = "::"; + size_t pos = 0; + int line = 0; + string token; + while ((pos = payloadMessage.find(delimiter)) != string::npos) { + token = payloadMessage.substr(0, pos); +//#ifdef LOG_VERBOSE +// cout << line << "/" << token << endl; +//#endif + switch (line) { + case 0: { + type = static_cast<MessageType>(stoi(token)); + break; + } + default: + break; + } + payloadMessage.erase(0, pos + delimiter.length()); + line++; + } +//#ifdef LOG_VERBOSE +// cout << payloadMessage << endl; +//#endif + payload = payloadMessage; +} + +Messages::Messages(MessageType messageType, string payloadMessage) +{ + type = messageType; + payload = payloadMessage; +} + +string Messages::toString() +{ + string message = to_string(type) + "::" + payload; + return message; +} diff --git a/src/Node.cpp b/src/Node.cpp new file mode 100644 index 0000000..33423c8 --- /dev/null +++ b/src/Node.cpp @@ -0,0 +1,1557 @@ +#include "../inc/Node.h" + + +//add another function to Node class for failure detection +//call function before sender (heartbeat) after listenForHeartbeat + +Node::Node() +{ + // create a udp object + udpServent = new UdpSocket(); + tcpServent = new TcpSocket(); + hashRing = new HashRing(); + localTimestamp = 0; + heartbeatCounter = 0; + //time(&startTimestamp); + // byteSent = 0; + // byteReceived = 0; + runningMode = ALL2ALL; + activeRunning = false; + prepareToSwitch = false; + logWriter = new Logger(LOGGING_FILE_NAME); + leaderPosition = -1; + proposedTime = 0; + electedTime = 0; + joinTimestamp = ""; + possibleSuccessorIP = ""; + leaderIP = ""; + leaderPort = ""; + isBlackout = true; +} + +Node::Node(ModeType mode) +{ + // create a udp object + udpServent = new UdpSocket(); + tcpServent = new TcpSocket(); + hashRing = new HashRing(); + localTimestamp = 0; + heartbeatCounter = 0; + //time(&startTimestamp); + // byteSent = 0; + // byteReceived = 0; + runningMode = mode; + activeRunning = false; + prepareToSwitch = false; + logWriter = new Logger(LOGGING_FILE_NAME); + leaderPosition = -1; + proposedTime = 0; + electedTime = 0; + joinTimestamp = ""; + possibleSuccessorIP = ""; + leaderIP = ""; + leaderPort = ""; + isBlackout = true; +} + +void Node::startActive() +{ + membershipList.clear(); + restartElection(); + // inserting its own into the list + time(&startTimestamp); + string startTime = ctime(&startTimestamp); + startTime = startTime.substr(0, startTime.find("\n")); + tuple<string,string,string> mapKey(nodeInformation.ip, nodeInformation.port, startTime); + tuple<int, int, int> valueTuple(nodeInformation.heartbeatCounter, nodeInformation.timestamp, 0); + membershipList[mapKey] = valueTuple; + + debugMembershipList(); + joinTimestamp = startTime; // for hashRing + getPositionOnHashring(); // update its hashRingPosition +} + +void Node::computeAndPrintBW(double diff) +{ +#ifdef LOG_VERBOSE + cout << "total " << udpServent->byteSent << " bytes sent" << endl; + cout << "total " << udpServent->byteReceived << " bytes received" << endl; + printf("elasped time is %.2f s\n", diff); +#endif + if (diff > 0) { + double bandwidth = udpServent->byteSent/diff; + string message = "["+to_string(this->localTimestamp)+"] B/W usage: "+to_string(bandwidth)+" bytes/s"; +#ifdef LOG_VERBOSE + printf("%s\n", message.c_str()); +#endif + this->logWriter->printTheLog(BANDWIDTH, message); + } +} + +void Node::updateNodeHeartbeatAndTime() +{ + string startTime = ctime(&startTimestamp); + startTime = startTime.substr(0, startTime.find("\n")); + tuple<string, string, string> keyTuple(nodeInformation.ip, nodeInformation.port,startTime); + tuple<int, int, int> valueTuple(heartbeatCounter, localTimestamp, 0); + this->membershipList[keyTuple] = valueTuple; +} + +string Node::populateMembershipMessage() +{ + //The string we send will be seperated line by line --> IP,PORT,HeartbeatCounter,FailFlag + string mem_list_to_send = ""; + //Assume destination already exists in the membership list of this node, just a normal heartbeat + switch (this->runningMode) + { + case GOSSIP: + for (auto& element: this->membershipList) { + tuple<string, string, string> keyTuple = element.first; + tuple<int, int, int> valueTuple = element.second; + mem_list_to_send += get<0>(keyTuple) + "," + get<1>(keyTuple) + "," + get<2>(keyTuple) + ","; + mem_list_to_send += to_string(get<0>(valueTuple)) + "," + to_string(get<2>(valueTuple)) + "\n"; + } + break; + + default: + string startTime = ctime(&startTimestamp); + startTime = startTime.substr(0, startTime.find("\n")); + mem_list_to_send += nodeInformation.ip + "," + nodeInformation.port + "," + startTime + ","; + mem_list_to_send += to_string(heartbeatCounter) + "," + to_string(0) + "\n"; + break; + } + return mem_list_to_send; +} + +string Node::populateIntroducerMembershipMessage(){ + string mem_list_to_send = ""; + for (auto& element: this->membershipList) { + tuple<string, string, string> keyTuple = element.first; + tuple<int, int, int> valueTuple = element.second; + mem_list_to_send += get<0>(keyTuple) + "," + get<1>(keyTuple) + "," + get<2>(keyTuple) + ","; + mem_list_to_send += to_string(get<0>(valueTuple)) + "," + to_string(get<2>(valueTuple)) + "\n"; + } + return mem_list_to_send; +} + +/** + * + * HeartbeatToNode: Sends a string version of the membership list to the receiving node. The receiving node will convert the string to + * a <string, long> map where the key is the Addresss (IP + PORT) and value is the heartbeat counter. We then compare the Member. + * + **/ +int Node::heartbeatToNode() +{ + // 3. prepare to send heartbeating, and + string mem_list_to_send = populateMembershipMessage(); + vector<tuple<string,string,string>> targetNodes = getRandomNodesToGossipTo(); + + //Now we have messages ready to send, need to invoke UDP client to send +#ifdef LOG_VERBOSE + cout << "pick " << targetNodes.size() << " of " << this->membershipList.size()-1; + cout << " members" << endl; +#endif + + // 4. do gossiping + for (uint i=0; i<targetNodes.size(); i++) { + //cout << targetNodes[i].first << "/" << targetNodes[i].second << endl; + Member destination(get<0>(targetNodes[i]), get<1>(targetNodes[i])); + + string message = "["+to_string(this->localTimestamp)+"] node "+destination.ip+"/"+destination.port+"/"+get<2>(targetNodes[i]); +#ifdef LOG_VERBOSE + cout << "[Gossip]" << message.c_str() << endl; +#endif + this->logWriter->printTheLog(GOSSIPTO, message); + + //cout << mem_list_to_send.size() << " Bytes sent..." << endl; + // byteSent += mem_list_to_send.size(); + if (isLeader) { + //Messages msg(LEADERHEARTBEAT, mem_list_to_send); + if (isBlackout) { + string msg = populateSDFSFileList(LEADERPENDING, mem_list_to_send); + udpServent->sendMessage(destination.ip, destination.port, msg); + } else { + string msg = populateSDFSFileList(LEADERHEARTBEAT, mem_list_to_send); + udpServent->sendMessage(destination.ip, destination.port, msg); + } + } else { + //Messages msg(HEARTBEAT, mem_list_to_send); + string msg = populateSDFSFileList(HEARTBEAT, mem_list_to_send); + udpServent->sendMessage(destination.ip, destination.port, msg); + } + } + + return 0; +} + +int Node::failureDetection(){ + //1. check local membership list for any timestamps whose curr_time - timestamp > T_fail + //2. If yes, mark node as local failure, update fail flag to 1 and update timestamp to current time + //3. for already failed nodes, check to see if curr_time - time stamp > T_cleanup + //4. If yes, remove node from membership list + vector<tuple<string,string,string>> removedVec; + for(auto& element: this->membershipList){ + tuple<string,string,string> keyTuple = element.first; + tuple<int, int, int> valueTuple = element.second; +#ifdef LOG_VERBOSE + cout << "checking " << get<0>(keyTuple) << "/" << get<1>(keyTuple) << "/" << get<2>(keyTuple) << endl; +#endif + if ((get<0>(keyTuple).compare(nodeInformation.ip) == 0) && (get<1>(keyTuple).compare(nodeInformation.port) == 0)) { + // do not check itself +#ifdef LOG_VERBOSE + cout << "skip it" << endl; +#endif + continue; + } + if(get<2>(valueTuple) == 0){ + if(localTimestamp - get<1>(valueTuple) > T_timeout){ + //cout << "Got " << get<0>(keyTuple) << "/" << get<1>(keyTuple) << "/" << get<2>(keyTuple) << endl; + //cout << "local time " << localTimestamp << " vs. " << get<1>(valueTuple) << endl; + get<1>(this->membershipList[keyTuple]) = localTimestamp; + get<2>(this->membershipList[keyTuple]) = 1; + + string message = "["+to_string(this->localTimestamp)+"] node "+get<0>(keyTuple)+"/"+get<1>(keyTuple)+"/"+get<2>(keyTuple)+": Local Failure"; + cout << "[FAIL]" << message.c_str() << endl; + this->logWriter->printTheLog(FAIL, message); + if(isLeader){ + // clearn up fileList + Member deletedNode(get<0>(keyTuple), get<1>(keyTuple)); + int deletedNodePostion = hashingId(deletedNode, get<2>(keyTuple)); + hashRing->removeNode(deletedNodePostion); + for (auto& element: fileList) { + vector<int> newEntry; + for(unsigned int i = 0; i < element.second.size(); i++){ + if(element.second[i] != deletedNodePostion){ + newEntry.push_back(element.second[i]); + } + } + fileList[element.first] = newEntry; + } + + // chech if the failure is the sender in pending requests + for (auto& senders: pendingSenderRequests) { + string sdfsfilename = senders.first; + tuple<string,string,string> sender = senders.second; + if ((get<0>(keyTuple).compare(get<0>(sender))==0) && + get<0>(pendingRequestSent[sdfsfilename]) && + (get<0>(pendingRequests[sdfsfilename])!=-1)) { + // it sent out, and is not finished, cannot help + // we lost the data from the client + cout << "[PUT] client itself fails, we cannot help, remove request" << endl; + isBlackout = false; + pendingRequests.erase(sdfsfilename); + pendingRequestSent.erase(sdfsfilename); + continue; + } + if ((get<0>(keyTuple).compare(get<1>(sender))==0) && + get<1>(pendingRequestSent[sdfsfilename]) && + (get<1>(pendingRequests[sdfsfilename])!=-1)) { + // the sender fails during 2nd pass + // replace the sent + cout << "[PUT] One of the sender " << get<0>(keyTuple) << " failed, try again" << endl; + if (get<2>(pendingRequests[sdfsfilename])!=-1) { + tuple<int, int, int>(get<0>(pendingRequestSent[sdfsfilename]), false, get<2>(pendingRequestSent[sdfsfilename])); + } else { + pendingRequests.erase(sdfsfilename); + } + continue; + } + if ((get<0>(keyTuple).compare(get<2>(sender))==0) && + get<2>(pendingRequestSent[sdfsfilename]) && + (get<2>(pendingRequests[sdfsfilename])!=-1)) { + // it sent out, but replicates are failed + // restart again + cout << "[PUT/REREPLICATE] The sender " << get<0>(keyTuple) << " failed, try again" << endl; + pendingRequests.erase(sdfsfilename); + } + } + + } + + } + } + else{ + if(localTimestamp - get<1>(valueTuple) > T_cleanup){ + // core dumped happened here; bug fix + auto iter = this->membershipList.find(keyTuple); + if (iter != this->membershipList.end()) { + //cout << "Got " << get<0>(iter->first) << "/" << get<1>(iter->first) << "/" << get<2>(iter->first); + //cout << " with " << to_string(get<0>(iter->second)) << "/"; + //cout << to_string(get<1>(iter->second)) << "/"; + //cout << to_string(get<2>(iter->second)) << endl; + //cout << this->membershipList[keyTuple] + //this->membershipList.erase(iter); + removedVec.push_back(keyTuple); + } + } + } + } + + // O(c*n) operation, but it ensures safety + bool leaderRemoved = false; + for (uint i=0; i<removedVec.size(); i++) { + auto iter = this->membershipList.find(removedVec[i]); + if (iter != this->membershipList.end()) { + + if (leaderIP.compare(get<0>(removedVec[i]))==0) { // this is the leader + leaderRemoved = true; + cout << "[ELECTION] leader " << leaderIP << " is removed" << endl; + } + + this->membershipList.erase(iter); + + string message = "["+to_string(this->localTimestamp)+"] node "+get<0>(removedVec[i])+"/"+get<1>(removedVec[i])+"/"+get<2>(removedVec[i])+": REMOVED FROM LOCAL MEMBERSHIP LIST"; + cout << "[REMOVE]" << message.c_str() << endl; + this->logWriter->printTheLog(REMOVE, message); + + //this->debugMembershipList(); + } + } + if (this->membershipList.size()==1 || leaderRemoved) { // Only me or leader failed, restart leader election + if (checkLeaderExist()) { // restart if we have a leader + restartElection(); + } + } + return 0; +} + + +int Node::joinSystem(Member introducer) +{ + string mem_list_to_send = populateMembershipMessage(); + //Messages msg(JOIN, mem_list_to_send); + string msg = populateSDFSFileList(JOIN, mem_list_to_send); + + string message = "["+to_string(this->localTimestamp)+"] sent a request to "+introducer.ip+"/"+introducer.port+", I am "+nodeInformation.ip+"/"+nodeInformation.port; + cout << "[JOIN]" << message.c_str() << endl; + this->logWriter->printTheLog(JOINGROUP, message); + udpServent->sendMessage(introducer.ip, introducer.port, msg); + return 0; +} + +int Node::requestSwitchingMode() +{ + string message = nodeInformation.ip+","+nodeInformation.port; + //Messages msg(SWREQ, message); + string msg = populateSDFSFileList(SWREQ, message); + for(auto& element: this->membershipList) { + tuple<string,string,string> keyTuple = element.first; + //tuple<int, int, int> valueTuple = element.second; + cout << "[SWITCH] sent a request to " << get<0>(keyTuple) << "/" << get<1>(keyTuple) << endl; + udpServent->sendMessage(get<0>(keyTuple), get<1>(keyTuple), msg); + } + return 0; +} + +int Node::SwitchMyMode() +{ + // wait for a while + sleep(T_switch); + // empty all messages + udpServent->qMessages = queue<string>(); + switch (this->runningMode) { + case GOSSIP: { + this->runningMode = ALL2ALL; + cout << "[SWITCH] === from gossip to all-to-all ===" << endl; + break; + } + case ALL2ALL: { + this->runningMode = GOSSIP; + cout << "[SWITCH] === from all-to-all to gossip ===" << endl; + break; + } + default: + break; + } + // finishing up + prepareToSwitch = false; + return 0; +} + +int Node::listenToHeartbeats() +{ + //look in queue for any strings --> if non empty, we have received a message and need to check the membership list + + // 1. deepcopy and handle queue + queue<string> qCopy(udpServent->qMessages); + udpServent->qMessages = queue<string>(); + + int size = qCopy.size(); + //cout << "Got " << size << " messages in the queue" << endl; + //cout << "checking queue size " << nodeOwn->udpServent->qMessages.size() << endl; + + // 2. merge membership list + for (int j = 0; j < size; j++) { + //cout << qCopy.front() << endl; + readMessage(qCopy.front()); + + // Volunteerily leave + if(this->activeRunning == false){ + return 0; + } + // byteReceived += qCopy.front().size(); + qCopy.pop(); + } + + return 0; +} + +void Node::debugMembershipList() +{ + cout << "Membership list [" << this->membershipList.size() << "]:" << endl; + if (isLeader) { + cout << "[T] IP/Port/JoinedTime:Heartbeat/LocalTimestamp/FailFlag" << endl; + } else { + cout << "[T] IP/Port/JoinedTime:Heartbeat/LocalTimestamp/FailFlag" << endl; + } + string message = ""; + + for (auto& element: this->membershipList) { + tuple<string,string,string> keyTuple = element.first; + tuple<int, int, int> valueTuple = element.second; + + if (nodeInformation.ip.compare(get<0>(keyTuple))==0) { // Myself + if (isLeader) { + message += "[L/M] "; + } else { + message += "[M] "; + } + } else if (leaderIP.compare(get<0>(keyTuple))==0) { + message += "[L] "; + } else { + if (isLeader) { + message += " "; + } else { + message += " "; + } + } + + message += get<0>(keyTuple)+"/"+get<1>(keyTuple)+"/"+get<2>(keyTuple); + message += ": "+to_string(get<0>(valueTuple))+"/"+to_string(get<1>(valueTuple))+"/"+to_string(get<2>(valueTuple))+"\n"; + } + cout << message.c_str() << endl; + this->logWriter->printTheLog(MEMBERS, message); +} + +void Node::processHeartbeat(string message) { + bool changed = false; + vector<string> incomingMembershipList = splitString(message, "\n"); + vector<string> membershipListEntry; + for(string list_entry: incomingMembershipList){ +#ifdef LOG_VERBOSE + cout << "handling with " << list_entry << endl; +#endif + if (list_entry.size() == 0) { + continue; + } + membershipListEntry.clear(); + membershipListEntry = splitString(list_entry, ","); + if (membershipListEntry.size() != 5) { + // circumvent craching + continue; + } + + int incomingHeartbeatCounter = stoi(membershipListEntry[3]); + int failFlag = stoi(membershipListEntry[4]); + tuple<string,string,string> mapKey(membershipListEntry[0], membershipListEntry[1], membershipListEntry[2]); + + if ((get<0>(mapKey).compare(nodeInformation.ip) == 0) && (get<1>(mapKey).compare(nodeInformation.port) == 0)) { + // Volunteerily leave if you are marked as failed + if(failFlag == 1){ + this->activeRunning = false; + + string message = "["+to_string(this->localTimestamp)+"] node "+this->nodeInformation.ip+"/"+this->nodeInformation.port+" is left"; + cout << "[VOLUNTARY LEAVE]" << message.c_str() << endl; + this->logWriter->printTheLog(LEAVE, message); + return; + } + + // do not check itself heartbeat +#ifdef LOG_VERBOSE + cout << "skip it" << endl; +#endif + continue; + } + + map<tuple<string,string,string>, tuple<int, int, int>>::iterator it; + it = this->membershipList.find(mapKey); + if (it == this->membershipList.end() && failFlag == 0) { + tuple<int, int, int> valueTuple(incomingHeartbeatCounter, localTimestamp, failFlag); + this->membershipList[mapKey] = valueTuple; + updateHashRing(); + string message = "["+to_string(this->localTimestamp)+"] new node "+get<0>(mapKey)+"/"+get<1>(mapKey)+"/"+get<2>(mapKey)+" is joined"; + cout << "[JOIN]" << message.c_str() << endl; + this->logWriter->printTheLog(JOINGROUP, message); + changed = true; + } else if(it != this->membershipList.end()) { + // update heartbeat count and local timestamp if fail flag of node is not equal to 1. If it equals 1, we ignore it. + if(get<2>(this->membershipList[mapKey]) != 1){ + //if incoming membership list has node with fail flag = 1, but fail flag in local membership list = 0, we have to set fail flag = 1 in local + switch (this->runningMode) { + case GOSSIP: { + if(failFlag == 1){ + get<2>(this->membershipList[mapKey]) = 1; + get<1>(this->membershipList[mapKey]) = localTimestamp; + string message = "["+to_string(this->localTimestamp)+"] node "+get<0>(mapKey)+"/"+get<1>(mapKey)+"/"+get<2>(mapKey)+": Disseminated Failure"; + cout << "[FAIL]" << message.c_str() << endl; + this->logWriter->printTheLog(FAIL, message); + } + else{ + int currentHeartbeatCounter = get<0>(this->membershipList[mapKey]); + if(incomingHeartbeatCounter > currentHeartbeatCounter){ + get<0>(this->membershipList[mapKey]) = incomingHeartbeatCounter; + get<1>(this->membershipList[mapKey]) = localTimestamp; + // get<2>(this->membershipList[mapKey]) = failFlag; + string message = "["+to_string(this->localTimestamp)+"] node "+get<0>(mapKey)+"/"+get<1>(mapKey)+"/"+get<2>(mapKey)+" from "+to_string(currentHeartbeatCounter)+" to "+to_string(incomingHeartbeatCounter); +#ifdef LOG_VERBOSE + cout << "[UPDATE]" << message.c_str() << endl; +#endif + this->logWriter->printTheLog(UPDATE, message); + } + } + break; + } + default: { // ALL2ALL doesn't disseminate + int currentHeartbeatCounter = get<0>(this->membershipList[mapKey]); + if(incomingHeartbeatCounter > currentHeartbeatCounter){ + get<0>(this->membershipList[mapKey]) = incomingHeartbeatCounter; + get<1>(this->membershipList[mapKey]) = localTimestamp; + get<2>(this->membershipList[mapKey]) = failFlag; + string message = "["+to_string(this->localTimestamp)+"] node "+get<0>(mapKey)+"/"+get<1>(mapKey)+"/"+get<2>(mapKey)+" from "+to_string(currentHeartbeatCounter)+" to "+to_string(incomingHeartbeatCounter); +#ifdef LOG_VERBOSE + cout << "[UPDATE]" << message.c_str() << endl; +#endif + this->logWriter->printTheLog(UPDATE, message); + } + break; + } + } + } else { + // do nothing + } + } + } + + // If membership list changed in all-to-all, full membership list will be sent + if(changed && this->runningMode == ALL2ALL){ + string mem_list_to_send = populateIntroducerMembershipMessage(); + vector<tuple<string,string,string>> targetNodes = getRandomNodesToGossipTo(); + + for (uint i=0; i<targetNodes.size(); i++) { + Member destination(get<0>(targetNodes[i]), get<1>(targetNodes[i])); + + string message = "["+to_string(this->localTimestamp)+"] node "+destination.ip+"/"+destination.port+"/"+get<2>(targetNodes[i]); +#ifdef LOG_VERBOSE + cout << "[Gossip]" << message.c_str() << endl; +#endif + this->logWriter->printTheLog(GOSSIPTO, message); + + if (isLeader) { + //Messages msg(LEADERHEARTBEAT, mem_list_to_send); + if (isBlackout) { + string msg = populateSDFSFileList(LEADERPENDING, mem_list_to_send); + udpServent->sendMessage(destination.ip, destination.port, msg); + } else { + string msg = populateSDFSFileList(LEADERHEARTBEAT, mem_list_to_send); + udpServent->sendMessage(destination.ip, destination.port, msg); + } + } else { + //Messages msg(HEARTBEAT, mem_list_to_send); + string msg = populateSDFSFileList(HEARTBEAT, mem_list_to_send); + udpServent->sendMessage(destination.ip, destination.port, msg); + } + + } + } +} + +void Node::setUpLeader(string message, bool pending) +{ + string msg(message); + vector<string> fields = splitString(msg, ","); + if(fields.size() >= 3){ + Member leader(fields[0], fields[1]); + leaderPosition = hashingId(leader, fields[2]); + leaderIP = fields[0]; + leaderPort = fields[1]; + } + leaderCreateHashRing(); // local copy of hashRing on each node + + if (pending != isBlackout) { + if (isBlackout) { + cout << "[BLACKOUT] Leader is ready now" << endl; + } else { + cout << "[BLACKOUT] Leader is busy now" << endl; + } + } + if (pending) { + isBlackout = true; + } else { + isBlackout = false; + } +} + +/** + * given a string message which contains a membership list, we will take the string, split it by returns, and then split it by commas, to then compare the heartbeat counters + * of each IP,PORT,timestamp tuple with the membership list of the receiving Node. + * + * Found help on how to do string processing part of this at https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c + */ +void Node::readMessage(string message){ + + // decapsulate with specific messages + //cout << "readMessage " << message << endl; + string deMeg = decapsulateMessage(message); + bool pending = true; + //cout << "readMessage deMeg " << deMeg << endl; + + Messages msg(deMeg); + switch (msg.type) { + case LEADERHEARTBEAT: // Note: not for Gossip-style, only All-to-All + //cout << "LEADERHEARTBEAT: " << msg.payload << endl; + pending = false; + case LEADERPENDING: + setUpLeader(msg.payload, pending); + case HEARTBEAT: + case JOINRESPONSE:{ + processHeartbeat(msg.payload); + break; + } + case JOIN:{ + // introducer checks collision here + vector<string> fields = splitString(msg.payload, ","); + if(fields.size() >= 3){ + Member member(fields[0], fields[1]); + int checkPosition = hashingId(member, fields[2]); + if (checkHashNodeCollision(checkPosition)) { + //Messages response(JOINREJECT, ""); + string response = populateSDFSFileList(JOINREJECT, ""); + udpServent->sendMessage(fields[0], fields[1], response); + } else { + string introducerMembershipList; + introducerMembershipList = populateIntroducerMembershipMessage(); + //Messages response(JOINRESPONSE, introducerMembershipList); + string response = populateSDFSFileList(JOINRESPONSE, introducerMembershipList); + udpServent->sendMessage(fields[0], fields[1], response); + } + } + break; + } + case SWREQ: { + // got a request, send an ack back + vector<string> fields = splitString(msg.payload, ","); + if (fields.size() == 2) { + cout << "[SWITCH] got a request from "+fields[0]+"/"+fields[1] << endl; + string messageReply = nodeInformation.ip+","+nodeInformation.port; + //Messages msgReply(SWRESP, messageReply); + string msgReply = populateSDFSFileList(SWRESP, messageReply); + udpServent->sendMessage(fields[0], fields[1], msgReply); + + prepareToSwitch = true; + } + break; + } + case SWRESP: { + // got an ack + vector<string> fields = splitString(msg.payload, ","); + if (fields.size() == 2) { + cout << "[SWITCH] got an ack from "+fields[0]+"/"+fields[1] << endl; + } + break; + } + case JOINREJECT: { + // TODO: the node should leave + cout << "[JOINREJECT] There is a collision, and I have to leave..." << endl; + this->activeRunning = false; + pthread_exit(NULL); + break; + } + default: + break; + } + //debugMembershipList(); +} + +vector<string> Node::splitString(string s, string delimiter){ + vector<string> result; + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + string token; + + while ((pos_end = s.find (delimiter, pos_start)) != string::npos) { + token = s.substr (pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + result.push_back (token); + } + + result.push_back (s.substr (pos_start)); + return result; +} + +int Node::hashingId(Member nodeMember, string joinTime) +{ + string toBeHashed = "NODE::" + nodeMember.ip + "::" + nodeMember.port + "::" + joinTime; + int ringPosition = hash<string>{}(toBeHashed) % HASHMODULO; + return ringPosition; +} + +int Node::getPositionOnHashring(){ + hashRingPosition = hashingId(nodeInformation, joinTimestamp); + cout << "[ELECTION] This node is at hash position: " << hashRingPosition << endl; + return 0; +} + +int Node::updateHashRing(){ + bool needToUpdate = true; + for(auto& it: membershipList){ + needToUpdate = true; + string ip = get<0>(it.first); + for(int i: hashRing->nodePositions){ + if(ip.compare(hashRing->getValue(i)) == 0){ + needToUpdate = false; + break; + } + } + if(needToUpdate){ + Member toBeInserted(ip, get<1>(it.first)); + int hashPosition = hashingId(toBeInserted, get<2>(it.first)); + hashRing->addNode(ip, hashPosition); + } + } + return 0; +} + +bool Node::checkLeaderExist() +{ + return leaderPosition != -1; +} + +bool Node::checkHashNodeCollision(int checkPosition) +{ + // if True, the position is full + for (auto& element: this->membershipList) { + tuple<string, string, string> keyTuple = element.first; + Member member(get<0>(keyTuple), get<1>(keyTuple)); + if (nodeInformation.ip.compare(member.ip)==0) { // myself, skip it + continue; + } + int pos = hashingId(member, get<2>(keyTuple)); + if (pos == checkPosition) { + return true; + } + } + return false; +} + +bool Node::findWillBeLeader() +{ + bool beLeader = true; + vector<int> positions; + vector<string> ipAddresses; + if (membershipList.size() > 1) { // only 1 member does not need the leader + for (auto& element: this->membershipList) { + tuple<string, string, string> keyTuple = element.first; + Member member(get<0>(keyTuple), get<1>(keyTuple)); + int pos = hashingId(member, get<2>(keyTuple)); + if (pos < hashRingPosition) { + //cout << get<0>(keyTuple) << " with id " << pos << " is smaller" << endl; + beLeader = false; + } + if (nodeInformation.ip.compare(get<0>(keyTuple))!=0) { + int posNext = (pos + (HASHMODULO-hashRingPosition)) % HASHMODULO; + positions.push_back(posNext); + ipAddresses.push_back(get<0>(keyTuple)); + } + } + } else { + beLeader = false; + } + + if (positions.size() > 0) { + int index = 0; + int possibleSuccessor = positions[index]; + for (uint i=1; i<positions.size(); i++) { + if (positions[i] < possibleSuccessor) { + possibleSuccessor = positions[i]; + index = i; + } + } + //cout << "[ELECTION] My Possible Successor is " << ipAddresses[index] << endl; + possibleSuccessorIP = ipAddresses[index]; + } + + return beLeader; +} + +void Node::restartElection() // haven't tested yet +{ + cout << "[ELECTION] No leader now, restart election..." << endl; + electedTime = localTimestamp; + isLeader = false; + leaderPosition = -1; + leaderIP = ""; + leaderPort = ""; +} + +void Node::leaderCreateHashRing() +{ + // The leader or notes creates hashRing + hashRing->clear(); + for (auto& element: this->membershipList) { // update hashRing + tuple<string, string, string> keyTuple = element.first; + Member member(get<0>(keyTuple), get<1>(keyTuple)); + int pos = hashingId(member, get<2>(keyTuple)); + //hashRing->addNode("NODE::"+get<0>(keyTuple), pos); // since we don't store file, remove NODE + hashRing->addNode(get<0>(keyTuple), pos); + } + //hashRing->debugHashRing(); +} + +void Node::proposeToBeLeader() +{ + // Start election + Messages msg(ELECTION, to_string(hashRingPosition)); + cout << "[ELECTION] Propose to be leader, send to " << possibleSuccessorIP << endl; + tcpServent->sendMessage(possibleSuccessorIP, TCPPORT, msg.toString()); +} + +void Node::processElection(Messages messages) +{ + switch (messages.type) { + case ELECTION: { // check id + int currentId = stoi(messages.payload); + if (hashRingPosition > currentId) { + //incoming is smaller, just forward + cout << "[ELECTION] Got Election, agree on voting: " << messages.payload << endl; + tcpServent->sendMessage(possibleSuccessorIP, TCPPORT, messages.toString()); + } else if (hashRingPosition < currentId) { + //incoming is biger, replace and send it + cout << "[ELECTION] Got Election, against this voting " << messages.payload; + cout << ", and using my id " << hashRingPosition << endl; + Messages msg(ELECTION, to_string(hashRingPosition)); + tcpServent->sendMessage(possibleSuccessorIP, TCPPORT, msg.toString()); + } else { // finish 1st pass + cout << "[ELECTION] Got Election, everyone voted on me and start acking" << endl; + Messages msg(ELECTIONACK, to_string(hashRingPosition)); + tcpServent->sendMessage(possibleSuccessorIP, TCPPORT, msg.toString()); + } + break; + } + case ELECTIONACK: { + int currentId = stoi(messages.payload); + if (hashRingPosition == currentId) { // finish 2 pass + cout << "[ELECTION] I am the leader now" << endl; + isBlackout = false; + leaderPosition = hashRingPosition; + isLeader = true; + leaderIP = nodeInformation.ip; + leaderPort = nodeInformation.port; + leaderCreateHashRing(); + } else { + // Not me, just forward + cout << "[ELECTION] Pass ACK " << messages.payload << endl; + tcpServent->sendMessage(possibleSuccessorIP, TCPPORT, messages.toString()); + } + electedTime = localTimestamp; // update elected time + cout << "[ELECTION] Elected at Local Time " << electedTime << endl; + break; + } + default: + break; + } +} + +void Node::processTcpMessages() +{ + queue<string> qCopy(tcpServent->qMessages); + tcpServent->qMessages = queue<string>(); + + int size = qCopy.size(); + //cout << "Got " << size << " TCP messages" << endl; + + for (int j=0; j<size; j++) { + //cout << qCopy.front() << endl; + Messages msg(qCopy.front()); + //cout << "Has " << msg.type << " with " << msg.payload << endl; + switch (msg.type) { + case ELECTION: + case ELECTIONACK: { + processElection(msg); + break; + } + default: + break; + } + qCopy.pop(); + } +} + +void Node::updateFileList(string sdfsfilename, int nodePosition) +{ + if (isLeader) { + vector<int> positions = fileList[sdfsfilename]; + bool existed = false; + for (uint i=0; i<positions.size(); i++) { + if (positions[i] == nodePosition) { + existed = true; + } + } + if (!existed) { + positions.push_back(nodePosition); + } + vector<int> storedPositionsCopy(positions); + fileList[sdfsfilename] = storedPositionsCopy; + } +} + +//Can only be called when we are the leader node, since we are going to be calling REREPLICATE here, and checking the global file list. +//Called in ProcessRegMessages before we do anything else, since we want to check the global file list consistency before we process the other messages +//In the Queue. +void Node::checkFileListConsistency(){ + if (membershipList.size() < 4) { + cout << "[ERROR] The number of members are too small, we need at least 4" << endl; + return; + } + for (auto& element: fileList) { + if(element.second.size() < 4){ + //Need to rereplicate --> do this one at a time + //First check the closest node, successor and predecessor + int closestNodePostion = hashRing->locateClosestNode(element.first); + int pred = hashRing->getPredecessor(closestNodePostion); + int succ = hashRing->getSuccessor(closestNodePostion); + int randomNode = hashRing->getRandomNode(tuple<int, int, int>(closestNodePostion, pred, succ)); + vector<int> nodesToCheck = {closestNodePostion, pred, succ, randomNode}; + for(unsigned int i = 0; i < nodesToCheck.size(); i++){ + if (!isInVector(element.second, nodesToCheck[i])) + { + string nodeInfo = hashRing->getValue(nodesToCheck[i]); + Messages outMsg(DNSGET, nodeInfo + "::" + to_string(nodesToCheck[i]) + "::" + element.first + "::"); + tuple<int, int, int> request = pendingRequests[element.first]; + if(get<0>(request) == 0 && get<1>(request) == 0 && get<2>(request) == 0){ + pendingRequests[element.first] = tuple<int, int, int>(-1, -1, nodesToCheck[i]); + pendingRequestSent[element.first] = tuple<int, int, int>(true, true, true); + tcpServent->sendMessage(leaderIP, TCPPORT, outMsg.toString()); + break; + } + if(get<0>(request) != -1 || get<1>(request) != -1 || get<2>(request) != -1){ + cout << "on put " << get<0>(request) << "/" << get<1>(request) << "/" << get<2>(request) << endl; + break; + } + pendingRequests[element.first] = tuple<int, int, int>(-1, -1, nodesToCheck[i]); + pendingRequestSent[element.first] = tuple<int, int, int>(true, true, true); + tcpServent->sendMessage(leaderIP, TCPPORT, outMsg.toString()); + break; + } + } + } + } + +} + +bool Node::isInVector(vector<int> v, int i){ + for(int element: v){ + if(element == i){ + return true; + } + } + return false; +} + + +void Node::processRegMessages() +{ + //Before we do anything here, we should have the leader check to see if the file list is consistent or not. + //We do this by: + //1. Checking the files in the filelist, making sure each one has 4 entries. If not, then we need to rereplicate. + // We can initiate a PUT, put pending request, setting as -1, -1, and then last one as target node that we want to replicate to (new node to replace the one that failed) + if(isLeader){ + checkFileListConsistency(); + } + queue<string> qCopy(tcpServent->regMessages); + tcpServent->regMessages = queue<string>(); + + int size = qCopy.size(); + //cout << "Got " << size << " TCP messages" << endl; + + for (int j=0; j<size; j++) { + // cout << qCopy.front() << endl; + vector<string> msgSplit = splitString(qCopy.front(), "::"); + if (msgSplit.size() < 1){ + qCopy.pop(); + continue; + } + string payload = ""; + for(uint k = 1; k < msgSplit.size(); k++){ + if(k == msgSplit.size() - 1){ + payload += msgSplit[k]; + } else { + payload += msgSplit[k] + "::"; + } + } + MessageType msgType = static_cast<MessageType>(stoi(msgSplit[0])); + Messages msg(msgType, payload); + // cout << "Has " << msg.type << " with " << msg.payload << endl; + switch (msg.type) { + case PUTACK: { + vector<string> inMsg = splitString(msg.payload, "::"); + if(inMsg.size() >= 4){ + string inMsgIP = inMsg[0]; + string sdfsfilename = inMsg[1]; + string localfilename = inMsg[2]; + string remoteLocalname = inMsg[3]; + + cout << "[PUTACK] " << "inMsgIP: " << inMsgIP << " sdfsfilename: " << sdfsfilename << " localfilename: " << localfilename << endl; + + localFilelist[sdfsfilename] = localfilename; + Messages outMsg(ACK, to_string(this->hashRingPosition)+"::"+sdfsfilename+"::"+remoteLocalname); + this->tcpServent->sendMessage(inMsgIP, TCPPORT, outMsg.toString()); + } + break; + } + case DELETE: { + if (isLeader) { + vector<string> inMsg = splitString(msg.payload, "::"); + if(inMsg.size() >= 2){ + string inMsgIP = inMsg[0]; + string sdfsfilename = inMsg[1]; + + cout << "[DELETE] " << "inMsgIP: " << inMsgIP << " sdfsfilename: " << sdfsfilename << endl; + localFilelist.erase(sdfsfilename); + fileList.erase(sdfsfilename); + // This is TCP, so we don't need to ACK + } + } + break; + } + case DNSGET: { + if(isLeader){ + // Do replicating to the node + //isBlackout = true; + vector<string> inMsg = splitString(msg.payload, "::"); + cout << "msg.payload " << msg.payload << endl; + if(inMsg.size() >= 4){ + string inMsgIP = inMsg[0]; + int nodePosition = stoi(inMsg[1]); + int selectedNodePosition = nodePosition; + string sdfsfilename = inMsg[2]; + string localfilename = inMsg[3]; + cout << "[DNSGET] Got " << "inMsgIP: " << inMsgIP << ", sdfsfilename: " << sdfsfilename << ", localfilename: " << localfilename << endl; + vector<int> positions = fileList[sdfsfilename]; + if (positions.size() == 0) { + // the file is not available + cout << "[DNSGET] sdfsfilename " << sdfsfilename << " is not available" << endl; + fileList.erase(sdfsfilename); + Messages outMsg(GETNULL, sdfsfilename+": the file is not available::"); + this->tcpServent->sendMessage(inMsgIP, TCPPORT, outMsg.toString()); + //isBlackout = false; + break; + } + cout << "[DNSGET] we have "; + for (uint i=0; i<positions.size(); i++) { // pick any node other than the requested node + cout << positions[i] << " "; + if (positions[i]!=nodePosition) { + selectedNodePosition = positions[i]; + } + } + cout << endl; + cout << "[DNSGET] we picks " << selectedNodePosition << endl; + pendingRequests[sdfsfilename] = tuple<int, int, int>(-1, -1, nodePosition); + pendingRequestSent[sdfsfilename] = tuple<int, int, int>(true, true, true); + string nodeIP = hashRing->getValue(selectedNodePosition); + pendingSenderRequests[sdfsfilename] = tuple<string, string, string>("", "", nodeIP); + Messages outMsg(REREPLICATEGET, to_string(nodePosition) + "::" + sdfsfilename+ "::" +localfilename); + cout << "[DNSGET] Ask node " << nodeIP << " to replicate on pos "; + cout << to_string(nodePosition) << endl; + this->tcpServent->sendMessage(nodeIP, TCPPORT, outMsg.toString()); + } + } + break; + } + case DNS: { + // TODO: finish DNS functionality here, send out DNSANS + if(isLeader){ + // Check hashring, get positions and send out DNS ANS + isBlackout = true; + vector<string> inMsg = splitString(msg.payload, "::"); + if(inMsg.size() >= 4){ + string inMsgIP = inMsg[0]; + int nodePosition = stoi(inMsg[1]); + string sdfsfilename = inMsg[2]; + string localfilename = inMsg[3]; + + cout << "[DNS] Got " << "inMsgIP: " << inMsgIP << ", sdfsfilename: " << sdfsfilename; + cout << ", localfilename: " << localfilename << ", pos: " << nodePosition << endl; + //this->localFilelist[sdfsfilename] = localfilename; + // update fileList, client itself is one of the replicas + updateFileList(sdfsfilename, nodePosition); + hashRing->debugHashRing(); + int closestNode = hashRing->locateClosestNode(sdfsfilename); + int pred = hashRing->getPredecessor(closestNode); + int succ = hashRing->getSuccessor(closestNode); + if (hashRing->getValue(closestNode).compare(inMsgIP)==0) { + closestNode = hashRing->getRandomNode(tuple<int, int, int>(closestNode, pred, succ)); + cout << "[DNS] we need one more node " << closestNode << endl; + } + if (hashRing->getValue(pred).compare(inMsgIP)==0) { + pred = hashRing->getRandomNode(tuple<int, int, int>(closestNode, pred, succ)); + cout << "[DNS] we need one more node " << pred << endl; + } + if (hashRing->getValue(succ).compare(inMsgIP)==0) { + succ = hashRing->getRandomNode(tuple<int, int, int>(closestNode, pred, succ)); + cout << "[DNS] we need one more node " << succ << endl; + } + cout << "[DNS] we have nodes [" << closestNode << " (closestNode), "; + cout << pred << " (pred), " << succ << " (succ)], reply " << closestNode << endl; + pendingRequests[sdfsfilename] = tuple<int, int, int>(closestNode, pred, succ); + pendingRequestSent[sdfsfilename] = tuple<int, int, int>(true, false, false); + pendingSenderRequests[sdfsfilename] = tuple<string, string, string>(inMsgIP, "", ""); + Messages outMsg(DNSANS, to_string(closestNode) + "::" + localfilename + "::" + sdfsfilename); + this->tcpServent->sendMessage(inMsgIP, TCPPORT, outMsg.toString()); + } + } + + break; + } + case DNSANS:{ + // Read the answer and send a PUT msg to dest + vector<string> inMsg = splitString(msg.payload, "::"); + if(inMsg.size() >= 3){ + int nodePosition = stoi(inMsg[0]); + // since we do not keep files in hashRing, the value itself is IPaddress, not NODE:IP_Address + string nodeIP = hashRing->getValue(nodePosition); + //cout << "nodeIP " << nodeIP << endl; + + cout << "[DNSANS] " << "we will put sdfsfilename: " << inMsg[2] << " to nodeIP: " << nodeIP; + cout << " using localfilename: " << inMsg[1] << endl; + + string sendMsg = nodeIP+"::"+inMsg[1]+"::"+inMsg[2]+"::"; + this->tcpServent->pendSendMessages.push(sendMsg); + //this->tcpServent->sendFile(nodeIP, TCPPORT, inMsg[1], inMsg[2], ""); + } + break; + } + case REREPLICATEGET: { + vector<string> inMsg = splitString(msg.payload, "::"); + if (inMsg.size() >= 3) { + int nodePosition = stoi(inMsg[0]); + // since we do not keep files in hashRing, the value itself is IPaddress, not NODE:IP_Address + string nodeIP = hashRing->getValue(nodePosition); + string sdfsfilename = inMsg[1]; + string remoteLocalfilename = inMsg[2]; + string localfilename = this->localFilelist[sdfsfilename]; + cout << "[REREPLICATEGET] Got a request of sdfsfilename " << sdfsfilename << " to nodeIP " << nodeIP << endl; + cout << "[REREPLICATEGET] Put localfilename " << localfilename << " to nodeIP " << nodeIP << endl; + string sendMsg = nodeIP+"::"+localfilename+"::"+sdfsfilename+"::"+remoteLocalfilename; + this->tcpServent->pendSendMessages.push(sendMsg); + //this->tcpServent->sendFile(nodeIP, TCPPORT, localfilename, sdfsfilename, remoteLocalfilename); + } + break; + } + case REREPLICATE:{ + // Read the answer and send a PUT msg to dest + vector<string> inMsg = splitString(msg.payload, "::"); + if (inMsg.size() >= 2) { + int nodePosition = stoi(inMsg[0]); + // since we do not keep files in hashRing, the value itself is IPaddress, not NODE:IP_Address + string nodeIP = hashRing->getValue(nodePosition); + string sdfsfilename = inMsg[1]; + string localfilename = this->localFilelist[sdfsfilename]; + cout << "[REREPLICATE] Got a request of sdfsfilename " << sdfsfilename << " to nodeIP " << nodeIP << endl; + cout << "[REREPLICATE] Put localfilename " << localfilename << " to nodeIP " << nodeIP << endl; + string sendMsg = nodeIP+"::"+localfilename+"::"+sdfsfilename+"::"; + this->tcpServent->pendSendMessages.push(sendMsg); + //this->tcpServent->sendFile(nodeIP, TCPPORT, localfilename, sdfsfilename, ""); + } + break; + } + case GETNULL: { + vector<string> inMsg = splitString(msg.payload, "::"); + if (inMsg.size() >= 1) { + cout << "[GETNULL] " << inMsg[0] << endl; + } + break; + } + + case ACK:{ + vector<string> inMsg = splitString(msg.payload, "::"); + if (inMsg.size() >= 3) { + string nodePosition = inMsg[0]; + string sdfsfilename = inMsg[1]; + string localfilename = inMsg[2]; + localFilelist[sdfsfilename] = localfilename; + + Messages outMsg(LEADERACK, this->nodeInformation.ip + "::" + to_string(this->hashRingPosition) + "::" + msg.payload); + cout << "[ACK] Done replicated sdfsfilename " << sdfsfilename; + cout << " on node " << nodePosition << ", and ACK back to the leader" << endl; + this->tcpServent->sendMessage(leaderIP, TCPPORT, outMsg.toString()); + } + + break; + } + case LEADERACK:{ + if(isLeader){ + //TODO: tick the list off + vector<string> inMsg = splitString(msg.payload, "::"); + if(inMsg.size() >= 4){ + string inMsgIP = inMsg[0]; + int inMsgnodePosition = stoi(inMsg[1]); + int nodePosition = stoi(inMsg[2]); + string sdfsfilename = inMsg[3]; + string replicatedNodeIP = hashRing->getValue(nodePosition); + + cout << "[LEADERACK] Got ACK inMsgIP: " << inMsgIP << " sdfsfilename: " << sdfsfilename << " done on " << replicatedNodeIP << endl; + string closestNodeIP = ""; + + // update fileList + updateFileList(sdfsfilename, inMsgnodePosition); + updateFileList(sdfsfilename, nodePosition); + + vector<int> temp; + cout << "pendingRequests: "; + if (get<0>(pendingRequests[sdfsfilename]) == nodePosition) { + closestNodeIP = hashRing->getValue(get<0>(pendingRequests[sdfsfilename])); + temp.push_back(-1); + } else { + temp.push_back(get<0>(pendingRequests[sdfsfilename])); + } + cout << temp[0] << " (sent: " << get<0>(pendingRequestSent[sdfsfilename]); + cout << ", from " << get<0>(pendingSenderRequests[sdfsfilename]) << "), "; + if (get<1>(pendingRequests[sdfsfilename]) == nodePosition) { + temp.push_back(-1); + } else { + temp.push_back(get<1>(pendingRequests[sdfsfilename])); + } + cout << temp[1] << " (sent: " << get<1>(pendingRequestSent[sdfsfilename]); + cout << ", from " << get<1>(pendingSenderRequests[sdfsfilename]) << "), "; + if (get<2>(pendingRequests[sdfsfilename]) == nodePosition) { + temp.push_back(-1); + } else { + temp.push_back(get<2>(pendingRequests[sdfsfilename])); + } + cout << temp[2] << " (sent:" << get<2>(pendingRequestSent[sdfsfilename]); + cout << ", from " << get<2>(pendingSenderRequests[sdfsfilename]) << ")" << endl; + pendingRequests[sdfsfilename] = tuple<int, int, int>(temp[0], temp[1], temp[2]); + + if(get<1>(pendingRequests[sdfsfilename]) == -1 && get<2>(pendingRequests[sdfsfilename])== -1){ + pendingRequests.erase(sdfsfilename); + pendingRequestSent.erase(sdfsfilename); + pendingSenderRequests.erase(sdfsfilename); + cout << "[LEADERACK] 3 or more Replicated files are done" << endl; + isBlackout = false; + break; + } + if((get<1>(pendingRequests[sdfsfilename])!=-1) && (!get<1>(pendingRequestSent[sdfsfilename]))){ + Messages outMsg(REREPLICATE, to_string(get<1>(pendingRequests[sdfsfilename])) + "::" + sdfsfilename); + // cout << "Sending out rereplicate to " << inMsgIP << "with message " << outMsg.toString() << endl; + cout << "[LEADERACK] Ask node incoming " << inMsgIP << " to replicate on pos "; + cout << to_string(get<1>(pendingRequests[sdfsfilename])) << endl; + this->tcpServent->sendMessage(inMsgIP, TCPPORT, outMsg.toString()); + pendingRequestSent[sdfsfilename] = tuple<int, int, int>(get<0>(pendingRequestSent[sdfsfilename]), true, get<2>(pendingRequestSent[sdfsfilename])); + pendingSenderRequests[sdfsfilename] = tuple<string, string, string>(get<0>(pendingSenderRequests[sdfsfilename]), inMsgIP, get<2>(pendingSenderRequests[sdfsfilename])); + } + if((get<2>(pendingRequests[sdfsfilename]) != -1) && (!get<2>(pendingRequestSent[sdfsfilename]))){ + Messages outMsg(REREPLICATE, to_string(get<2>(pendingRequests[sdfsfilename])) + "::" + sdfsfilename); + // cout << "Sending out rereplicate to " << closestNodeIP << "with message " << outMsg.toString() << endl; + cout << "[LEADERACK] Ask node closest " << closestNodeIP << " to replicate on pos "; + cout << to_string(get<2>(pendingRequests[sdfsfilename])) << endl; + this->tcpServent->sendMessage(closestNodeIP, TCPPORT, outMsg.toString()); + pendingRequestSent[sdfsfilename] = tuple<int, int, int>(get<0>(pendingRequestSent[sdfsfilename]), get<1>(pendingRequestSent[sdfsfilename]), true); + pendingSenderRequests[sdfsfilename] = tuple<string, string, string>(get<0>(pendingSenderRequests[sdfsfilename]), get<1>(pendingSenderRequests[sdfsfilename]), inMsgIP); + } + } + } + break; + } + default: + break; + } + qCopy.pop(); + } +} + +/** + * Store the given filename in your sdfs filename, discard the original name, and + * give it a new name. The hashing will be done based on this sdfs filename. + * + * Can be called by any node, this one will be called by sender + * +*/ +// int Node::putFileSender(string filename, string sdfsfilename){ +// tcpServent->sendFile(leaderIP, leaderPort, filename); +// return 0; + +// } + +// int Node::putFileMaster(string sdfsfilename){ +// return 0; +// } + +// int Node::putFileReeiver(string sdfsfilename){ +// return 0; + +// } + +void Node::listLocalFiles(){ + cout << "sdfsfilename ---> localfilename" << endl; + for (auto& element: localFilelist) { + cout << element.first << " ---> " << element.second << endl; + } +} + +void Node::debugSDFSFileList() { + cout << "sdfsfilename ---> positions,..." << endl; + for (auto& element: fileList) { + cout << element.first << " ---> "; + for (uint i=0; i<element.second.size(); i++) { + cout << element.second[i]; + if (i == element.second.size()-1) { + continue; + } else { + cout << ", "; + } + } + cout << endl; + } +} + +void Node::listSDFSFileList(string sdfsfilename) { + bool found = false; + vector<int> foundPositions; + for (auto& element: fileList) { + if(element.first.compare(sdfsfilename)==0) { // found sdfsfilename + found = true; + foundPositions = element.second; + break; + } + } + if (found) { + if (foundPositions.size() > 0) { + cout << "sdfsfilename " << sdfsfilename << " is stored at..." << endl; + cout << "=========" << endl; + for (uint i=0; i<foundPositions.size(); i++) { + string storedIP = hashRing->getValue(foundPositions[i]); + cout << storedIP << " at " << foundPositions[i] << endl; + } + } else { + cout << "sdfsfilename " << sdfsfilename << " is stored at..." << endl; + cout << "=== Current list is empty ===" << endl; + } + } else { + cout << "sdfsfilename " << sdfsfilename << " is not existed" << endl; + } +} + +string Node::encapsulateFileList() +{ + string enMeg = ""; + if (checkLeaderExist() && isLeader) { + for (auto& element: fileList) { + string positions = ""; + string sdfsfilename = element.first; + + for (uint i=0; i<element.second.size(); i++) { + positions += to_string(element.second[i]); + if (i != element.second.size()-1) { + positions += ","; + } + } + //cout << "sdfsfilename " << sdfsfilename << endl; + //cout << "positions " << positions << endl; + char *cstr = new char[sdfsfilename.length()+positions.length()+7]; + size_t len = sdfsfilename.length()+3; + cstr[0] = len & 0xff; + cstr[1] = (len >> 8) & 0xff; + if (cstr[1] == 0) { // avoid null + cstr[1] = 0xff; + } + //printf("cstr[0] %x, cstr[1] %x\n", cstr[0], cstr[1]); + cstr[2] = FILENAME; + for (uint i=0; i<sdfsfilename.length(); i++) { + cstr[i+3] = sdfsfilename.c_str()[i]; + } + size_t len2 = positions.length()+3; + cstr[sdfsfilename.length()+3] = len2 & 0xff; + cstr[sdfsfilename.length()+4] = (len2 >> 8) & 0xff; + if (cstr[sdfsfilename.length()+4] == 0) { // avoid null + cstr[sdfsfilename.length()+4] = 0xff; + } + //printf("cstr[3] %x, cstr[4] %x\n", cstr[0], cstr[1]); + cstr[sdfsfilename.length()+5] = FILEPOSITIONS; + //printf("cstr[%lu] %d\n", sdfsfilename.length()+2, cstr[sdfsfilename.length()+2]); + for (uint i=0; i<positions.length(); i++) { + cstr[sdfsfilename.length()+6+i] = positions.c_str()[i]; + } + cstr[sdfsfilename.length()+positions.length()+6] = '\0'; + //printf("cstrFile %s\n", cstr); + string enMegFile(cstr); + //cout << "enMegFile " << enMegFile << endl; + enMeg += enMegFile; + } + //cout << "encapsulateFileList " << enMeg << endl; + } + return enMeg; +} + +string Node::encapsulateMessage(map<PayloadType,string> payloads) +{ + string enMeg = ""; + //cout << "payloads.size " << payloads.size() << endl; + for (auto& element: payloads) { + PayloadType type = element.first; + string message = element.second; + //cout << "message " << message << endl; + //cout << "message.length " << message.length() << endl; + //cout << "type " << type << endl; + + char *cstr = new char[message.length()+4]; + size_t len = message.length()+3; + cstr[0] = len & 0xff; + cstr[1] = (len >> 8) & 0xff; + if (cstr[1] == 0) { // avoid null + cstr[1] = 0xff; + } + //printf("cstr[0] %x, cstr[1] %x\n", cstr[0], cstr[1]); + cstr[2] = type; + //printf("cstr[2] %x\n", cstr[2]); + for (uint i=0; i<message.length(); i++) { + cstr[i+3] = message.c_str()[i]; + } + cstr[message.length()+3] = '\0'; + //printf("cstrMsg %s\n", cstr); + string enMegPart(cstr); + //cout << "enMegPart " << enMegPart << endl; + enMeg += enMegPart; + } + //cout << "encapsulateMessage " << enMeg << endl; + return enMeg; +} + +void Node::decapsulateFileList(string payload) +{ + int size = payload.length(); + uint pos = 0; + fileList.clear(); + string lastFilename = ""; + while (size > 0) { + size_t length; + if ((payload.c_str()[1+pos] & 0xff) == 0xff) { + length = 0; + } else { + length = (payload.c_str()[1+pos]) & 0xff; + length = length << 8; + } + length += (payload.c_str()[0+pos]) & 0xff; + PayloadType type = static_cast<PayloadType>(payload.c_str()[2+pos]); + //printf(" len %lu, type %d\n", length, type); + char cstr[length]; + bzero(cstr, sizeof(cstr)); + for (uint i=3; i<length; i++) { + cstr[i-3] = payload.c_str()[pos+i]; + } + string deMegPart(cstr); + switch (type) { + case FILENAME: { + //cout << "FILENAME " << deMegPart << endl; + lastFilename = deMegPart; + break; + } + case FILEPOSITIONS: { + //cout << "FILEPOSITIONS " << deMegPart << endl; + vector<string> temp = splitString(deMegPart, ","); + vector<int> positions; + for (uint i=0; i<temp.size(); i++) { + if (temp[i].compare("")!=0) { + positions.push_back(stoi(temp[i])); + } + } + fileList[lastFilename] = positions; + break; + } + default: + break; + } + size -= length; + pos += length; + } + + // check with local file list + if (!isLeader) { + vector<string> fileToDelete; + for (auto& element: localFilelist) { + //cout << "sdfsfilename " << element.first << endl; + if (fileList[element.first].size() == 0) { + fileToDelete.push_back(element.first); + } + } + for (uint i=0; i<fileToDelete.size(); i++) { + localFilelist.erase(fileToDelete[i]); + cout << "[DELETE] sdfsfilename " << fileToDelete[i] << endl; + } + } +} + +string Node::decapsulateMessage(string payload) +{ + int size = payload.length(); + uint pos = 0; + //cout << "payload " << payload << endl; + //cout << "size " << size << endl; + string deMeg = ""; + while (size > 0) { + size_t length; + if ((payload.c_str()[1+pos] & 0xff) == 0xff) { + length = 0; + } else { + length = (payload.c_str()[1+pos]) & 0xff; + length = length << 8; + } + length += (payload.c_str()[0+pos] & 0xff); + //printf("lengthMeg %x %x %lu\n", payload.c_str()[0+pos], payload.c_str()[1+pos], length); + + PayloadType type = static_cast<PayloadType>(payload.c_str()[2+pos]); + //printf(" len %lu, type %d\n", length, type); + char cstr[length]; + bzero(cstr, sizeof(cstr)); + for (uint i=3; i<length; i++) { + cstr[i-3] = payload.c_str()[pos+i]; + } + //printf("cstr %s\n", cstr); + string deMegPart(cstr); + //cout << "deMegPart " << deMegPart << endl; + if (type == REGULAR) { + deMeg = deMegPart; + } else if (type == FILEPAIR) { + if (checkLeaderExist() && !isLeader) { + //cout << "FILEPAIR " << deMegPart << endl; + decapsulateFileList(deMegPart); + } + } + //cout << "size1 " << size << endl; + size -= length; + pos += length; + //cout << "size2 " << size << endl; + } + //cout << "deMeg " << deMeg << endl; + return deMeg; +} + +// piggyback fileList in heartbeat +string Node::populateSDFSFileList(MessageType type, string mem_list_to_send) +{ + Messages msg(type, mem_list_to_send); + //cout << "populateSDFSFileList " << msg.toString() << endl; + map<PayloadType,string> payloads; + payloads[REGULAR] = msg.toString(); + if (isLeader) { // Only the leader includes the fileList + payloads[FILEPAIR] = encapsulateFileList(); + } + string enMeg = encapsulateMessage(payloads); + return enMeg; +} + +void Node::findNodesWithFile(string sdfsfilename){ + /*tuple<int, int, int> nodes = fileList[sdfsfilename]; + cout << hashRing->getValue(get<0>(nodes)) << endl; + cout << hashRing->getValue(get<1>(nodes)) << endl; + cout << hashRing->getValue(get<2>(nodes)) << endl;*/ +} + diff --git a/src/RandomGenerator.cpp b/src/RandomGenerator.cpp new file mode 100644 index 0000000..b70b6eb --- /dev/null +++ b/src/RandomGenerator.cpp @@ -0,0 +1,69 @@ +#include "../inc/Node.h" + +vector<tuple<string,string, string>> Node::getRandomNodesToGossipTo() +{ + //make an index list, then math.random to get index + // from list (remember to exclude the node itself) + vector<int> indexList; + vector<string> IPList; + vector<string> portList; + vector<string> timestampList; + vector<tuple<string, string, string>> selectedNodesInfo; + //int i = 0; + for(auto& element: this->membershipList){ + tuple<string, string, string> keyPair = element.first; + tuple<int, int, int> valueTuple = element.second; + //cout << "run " << keyPair.first << "/" << keyPair.second << endl; + //add check to make sure we don't add any failed nodes to gossip to + if(get<0>(keyPair).compare(this->nodeInformation.ip) != 0 && get<2>(valueTuple) != 1){ + IPList.push_back(get<0>(keyPair)); + portList.push_back(get<1>(keyPair)); + timestampList.push_back(get<2>(keyPair)); + indexList.push_back(IPList.size()-1); // bug fix + } + //i++; + } + // on one to gossip, return an empty vector + if (IPList.size() == 0) { + return selectedNodesInfo; + } + + /*for (uint j=0; j<indexList.size(); j++) { + cout << indexList[j] << ":" << IPList[j] << "/" << portList[j] << endl; + }*/ + + switch (this->runningMode) { + case GOSSIP: { + srand(time(NULL)); + int nodesSelected = 0; + // N_b is a predefined number + if(IPList.size() <= N_b){ + for (uint j=0; j<indexList.size(); j++) { + //int chosenIndex = indexList[j]; + //cout << "put " << IPList[j] << "/" << portList[j] << endl; + selectedNodesInfo.push_back(make_tuple(IPList[j], portList[j], timestampList[j])); + } + } + else{ + while (nodesSelected < N_b) { + int randomNum = rand() % indexList.size(); + int chosenIndex = indexList[randomNum]; + selectedNodesInfo.push_back(make_tuple(IPList[chosenIndex], portList[chosenIndex], timestampList[chosenIndex])); + indexList.erase(indexList.begin() + randomNum); + nodesSelected++; + } + } + break; + } + default: { + // All2All + for (uint j=0; j<indexList.size(); j++) { + //int chosenIndex = indexList[j]; + //cout << "put " << IPList[j] << "/" << portList[j] << endl; + selectedNodesInfo.push_back(make_tuple(IPList[j], portList[j], timestampList[j])); + } + break; + } + } + return selectedNodesInfo; +} \ No newline at end of file diff --git a/src/TcpSocket.cpp b/src/TcpSocket.cpp new file mode 100644 index 0000000..2860bf1 --- /dev/null +++ b/src/TcpSocket.cpp @@ -0,0 +1,418 @@ +#include "../inc/TcpSocket.h" +#include "../inc/UdpSocket.h" +#include "../inc/Messages.h" +#include "../inc/FileObject.h" + +void sigchld_handler(int s) +{ + while(waitpid(-1, NULL, WNOHANG) > 0); +} + +TcpSocket::TcpSocket() +{ + +} + +void TcpSocket::bindServer(string port) +{ + int sockfd, new_fd; // listen on sock_fd, new connection on new_fd + struct addrinfo hints, *servinfo, *p; + struct sockaddr_storage their_addr; // connector's address information + socklen_t sin_size; + struct sigaction sa; + int yes = 1; + char s[INET6_ADDRSTRLEN]; + int rv; + char buf[DEFAULT_TCP_BLKSIZE]; + int numbytes; + string delimiter = "::"; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP + + if ((rv = getaddrinfo(NULL, port.c_str(), &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return; + } + + // loop through all the results and bind to the first we can + for(p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("server: socket"); + continue; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) == -1) { + perror("setsockopt"); + exit(1); + } + + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + perror("server: bind"); + continue; + } + + break; + } + + if (p == NULL) { + fprintf(stderr, "server: failed to bind\n"); + return; + } + + freeaddrinfo(servinfo); // all done with this structure + + if (listen(sockfd, BACKLOG) == -1) { + perror("listen"); + exit(1); + } + + sa.sa_handler = sigchld_handler; // reap all dead processes + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + perror("sigaction"); + exit(1); + } + + //printf("server: waiting for connections...\n"); + + while(1) { // main accept() loop + sin_size = sizeof their_addr; + new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); + if (new_fd == -1) { + perror("accept"); + continue; + } + + inet_ntop(their_addr.ss_family, + get_in_addr((struct sockaddr *)&their_addr), + s, sizeof s); + //printf("server: got connection from %s\n", s); + + bzero(buf, sizeof(buf)); + MessageType type = JOIN; // for default case here + string payload = ""; + if ((numbytes = recv(new_fd, buf, DEFAULT_TCP_BLKSIZE, 0)) > 0) { + //buf[numbytes] = '\0'; + //printf("Got %s\n", buf); + + string payloadMessage(buf); + Messages msg(payloadMessage); + //printf("message type: %d\n", msg.type); + type = msg.type; + payload = msg.payload; + } + + switch (type) { + case ELECTION: + case ELECTIONACK: { + //cout << "election id is " << payload << endl; + string payloadMessage(buf); + //cout << "payloadMessage " << payloadMessage << endl; + qMessages.push(payloadMessage); + //cout << "qMessages size 1 " << qMessages.size() << endl; + break; + } + case PUT: { + FILE *fp; + int filesize = 0; + int byteReceived = 0; + string sdfsfilename = ""; + string incomingChecksum = ""; + string remoteLocalname = ""; + string overwriteFilename = ""; + // format: size,checksum,sdfsfilename + vector<string> fields = splitString(payload, ","); + if (fields.size() >= 5) { + filesize = stoi(fields[0]); + incomingChecksum = fields[1]; + sdfsfilename = fields[2]; + remoteLocalname = fields[3]; + overwriteFilename = fields[4]; + } + cout << "file is " << sdfsfilename << " with size " << filesize << " and checksum " << incomingChecksum << endl; + + time_t fileTimestamp; + time(&fileTimestamp); + string localfilename = sdfsfilename+"_"+to_string(fileTimestamp); + if (overwriteFilename.compare("") != 0) { + localfilename = overwriteFilename; + cout << "it's GET with filename " << overwriteFilename << endl; + } + cout << "backup filename " << localfilename << endl; + fp = fopen(localfilename.c_str(), "wb"); + if (fp == NULL) { + cout << "file error" << endl; + close(new_fd); + exit(0); + } + + bzero(buf, sizeof(buf)); + while ((numbytes=recv(new_fd, buf, DEFAULT_TCP_BLKSIZE, 0)) > 0) { + //printf("Got %d\n", numbytes); + fwrite(buf, sizeof(char), numbytes, fp); + byteReceived += numbytes; + if (byteReceived >= filesize) { + break; + } + bzero(buf, sizeof(buf)); + } + cout << "we have all the file, finishing this connections" << endl; + fclose(fp); + + FileObject f(localfilename); + if(incomingChecksum.compare(f.checksum) != 0){ + cout << "[ERROR] FILE CORRUPTED" << endl; + // TODO: Handel file corruption here + } else { + string returnIP(s); + Messages putack(PUTACK, returnIP + "::" + sdfsfilename + "::" + localfilename+"::"+remoteLocalname); + regMessages.push(putack.toString()); + } + + break; + } + case DNSANS: + case ACK: + case PUTACK: + case LEADERACK: + case REREPLICATE: + case REREPLICATEGET: + case DNSGET: + case DELETE: + case GETNULL: + case DNS:{ + string payloadMessage(buf); + cout << "Type: " << type << " payloadMessage: " << payloadMessage << endl; + regMessages.push(payloadMessage); + break; + } + default: + break; + } + close(new_fd); + } +} + +string TcpSocket::getFileMetadata(int size, string checksum, + string sdfsfilename, string localfilename, string remoteLocalfilename) +{ + // format: size,checksum,sdfsfilename + string msg = to_string(size) + "," + checksum + "," + sdfsfilename+","+localfilename+","+remoteLocalfilename; + return msg; +} + +void TcpSocket::sendFile(string ip, string port, + string localfilename, string sdfsfilename, string remoteLocalfilename) +{ + int sockfd, numbytes; + char buf[DEFAULT_TCP_BLKSIZE]; + struct addrinfo hints, *servinfo, *p; + int rv; + char s[INET6_ADDRSTRLEN]; + FILE *fp; + int size = 0; + + bzero(buf, sizeof(buf)); + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((rv = getaddrinfo(ip.c_str(), port.c_str(), &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return; + } + + // loop through all the results and connect to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("client: socket"); + continue; + } + + if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + perror("client: connect"); + continue; + } + + break; + } + + if (p == NULL) { + fprintf(stderr, "client: failed to connect\n"); + return; + } + + inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), + s, sizeof s); + printf("client: connecting to %s\n", s); + + freeaddrinfo(servinfo); // all done with this structure + + // read file + fp = fopen(localfilename.c_str(), "rb"); + if (fp == NULL) { + printf("Could not open file to send."); + return; + } + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + // send bytes and filename first + FileObject f(localfilename); + Messages msg(PUT, getFileMetadata(size, f.checksum, sdfsfilename, localfilename, remoteLocalfilename)); + string payload = msg.toString(); + + if (send(sockfd, payload.c_str(), strlen(payload.c_str()), 0) == -1) { + perror("send"); + } + sleep(1); + + while (!feof(fp) && size > 0) { + if (size < DEFAULT_TCP_BLKSIZE) { + bzero(buf, sizeof(buf)); + numbytes = fread(buf, sizeof(char), size, fp); + //printf("11 numbytes %d, size %d\n", numbytes, size); + if (send(sockfd, buf, numbytes, 0) == -1) { + perror("send"); + } + } else { + bzero(buf, sizeof(buf)); + numbytes = fread(buf, sizeof(char), DEFAULT_TCP_BLKSIZE, fp); + //printf("22 numbytes %d, size %d\n", numbytes, size); + size -= numbytes; + //printf("33 numbytes %d, size %d\n", numbytes, size); + if (send(sockfd, buf, numbytes, 0) == -1) { + perror("send"); + } + } + + } + fclose(fp); + close(sockfd); +} + +void TcpSocket::sendMessage(string ip, string port, string message) +{ + int sockfd; + struct addrinfo hints, *servinfo, *p; + int rv; + char s[INET6_ADDRSTRLEN]; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((rv = getaddrinfo(ip.c_str(), port.c_str(), &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return; + } + + // loop through all the results and connect to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("client: socket"); + continue; + } + + if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + perror("client: connect"); + continue; + } + + break; + } + + if (p == NULL) { + fprintf(stderr, "client: failed to connect\n"); + return; + } + + inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), + s, sizeof s); + //printf("client: connecting to %s\n", s); + + freeaddrinfo(servinfo); // all done with this structure + + if (send(sockfd, message.c_str(), strlen(message.c_str()), 0) == -1) { + perror("send"); + } + + close(sockfd); +} + +// copy from Node.cpp +vector<string> TcpSocket::splitString(string s, string delimiter){ + vector<string> result; + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + string token; + + while ((pos_end = s.find (delimiter, pos_start)) != string::npos) { + token = s.substr (pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + result.push_back (token); + } + + result.push_back (s.substr (pos_start)); + return result; +} + +// void *runTcpServer(void *tcpSocket) +// { +// TcpSocket* tcp; +// tcp = (TcpSocket*) tcpSocket; +// tcp->bindServer(TCPPORT); +// pthread_exit(NULL); +// } + +// void *runTcpClient(void *tcpSocket) +// { +// TcpSocket* tcp; +// tcp = (TcpSocket*) tcpSocket; +// sleep(1); +// // testing election +// Messages msg(ELECTION, "my_id"); +// tcp->sendMessage("127.0.0.1", TCPPORT, msg.toString()); + +// // testing to send file +// for (int i=0; i<2; i++) { +// sleep(1); +// tcp->sendFile("127.0.0.1", TCPPORT, "file_example_MP3_700KB.mp3"); +// } +// pthread_exit(NULL); +// } + +/*int main(int argc, char *argv[]) +{ + TcpSocket *tcpSocket = new TcpSocket(); + + pthread_t threads[2]; + + int rc; + + if ((rc = pthread_create(&threads[0], NULL, runTcpServer, (void *)tcpSocket)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + if ((rc = pthread_create(&threads[1], NULL, runTcpClient, (void *)tcpSocket)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + pthread_exit(NULL); + + return 0; +}*/ \ No newline at end of file diff --git a/src/Threads.cpp b/src/Threads.cpp new file mode 100644 index 0000000..c6d0196 --- /dev/null +++ b/src/Threads.cpp @@ -0,0 +1,154 @@ +#include "../inc/Node.h" + +/** + * + * runUdpServer: Enqueue each heartbeat it receives + * + **/ +void *runUdpServer(void *udpSocket) +{ + // acquire UDP object + UdpSocket* udp; + udp = (UdpSocket*) udpSocket; + udp->bindServer(PORT); + pthread_exit(NULL); +} + +/** + * + * runTcpServer: Enqueue each request it receives + * + **/ +void *runTcpServer(void *tcpSocket) +{ + TcpSocket* tcp; + tcp = (TcpSocket*) tcpSocket; + tcp->bindServer(TCPPORT); + pthread_exit(NULL); +} + +vector<string> splitString(string s, string delimiter){ + vector<string> result; + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + string token; + + while ((pos_end = s.find (delimiter, pos_start)) != string::npos) { + token = s.substr (pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + result.push_back (token); + } + + result.push_back (s.substr (pos_start)); + return result; +} + +void *runTcpSender(void *tcpSocket) +{ + TcpSocket* tcp; + tcp = (TcpSocket*) tcpSocket; + while (1) { + while (!tcp->pendSendMessages.empty()) { + vector<string> msgSplit = splitString(tcp->pendSendMessages.front(), "::"); + if (msgSplit.size() >= 4) { + string nodeIP = msgSplit[0]; + string localfilename = msgSplit[1]; + string sdfsfilename = msgSplit[2]; + string remoteLocalfilename = msgSplit[3]; + cout << "[DOSEND] nodeIP " << nodeIP << ", localfilename " << localfilename; + cout << ", sdfsfilename " << sdfsfilename << ", remoteLocalfilename " << remoteLocalfilename << endl; + tcp->sendFile(nodeIP, TCPPORT, localfilename, sdfsfilename, remoteLocalfilename); + } + tcp->pendSendMessages.pop(); + } + } + pthread_exit(NULL); +} + +void testMessages(UdpSocket* udp) +{ + sleep(2); + for (int j = 0; j < 4; j++) { + udp->sendMessage("127.0.0.1", PORT, "test message "+to_string(j)); + } + sleep(1); +} + +/** + * + * runSenderThread: + * 1. handle messages in queue + * 2. merge membership list + * 3. prepare to send heartbeating + * 4. do gossiping + * + **/ +void *runSenderThread(void *node) +{ + // acquire node object + Node *nodeOwn = (Node *) node; + + nodeOwn->activeRunning = true; + + // step: joining to the group -> just heartbeating to introducer + Member introducer(INTRODUCER, PORT); + nodeOwn->joinSystem(introducer); + + while (nodeOwn->activeRunning) { + + // 1. deepcopy and handle queue, and + // 2. merge membership list + nodeOwn->listenToHeartbeats(); + + // Volunteerily leave + if(nodeOwn->activeRunning == false){ + pthread_exit(NULL); + } + + //add failure detection in between listening and sending out heartbeats + nodeOwn->failureDetection(); + + // keep heartbeating + nodeOwn->localTimestamp++; + nodeOwn->heartbeatCounter++; + nodeOwn->updateNodeHeartbeatAndTime(); + + // 3. prepare to send heartbeating, and + // 4. do gossiping + nodeOwn->heartbeatToNode(); + + // 5. check for regular TCP messages + nodeOwn->processRegMessages(); + + // 6. check leader (If hashRing is sent via heartbeat, then we have a leader) + if (!nodeOwn->checkLeaderExist()) { // If no leader + nodeOwn->processTcpMessages(); + if (nodeOwn->findWillBeLeader()) { + //cout << "Try to propose to be leader" << endl; + if (nodeOwn->localTimestamp-nodeOwn->electedTime > T_election) { // when entering to stable state + if (nodeOwn->localTimestamp-nodeOwn->proposedTime > T_election) { + nodeOwn->proposeToBeLeader(); + nodeOwn->proposedTime = nodeOwn->localTimestamp; + } + } + } + } + + // for debugging + //nodeOwn->debugMembershipList(); + time_t endTimestamp; + time(&endTimestamp); + double diff = difftime(endTimestamp, nodeOwn->startTimestamp); + nodeOwn->computeAndPrintBW(diff); +#ifdef LOG_VERBOSE + cout << endl; +#endif + if (nodeOwn->prepareToSwitch) { + cout << "[SWITCH] I am going to swtich my mode in " << T_switch << "s" << endl; + nodeOwn->SwitchMyMode(); + } else { + usleep(T_period); + } + } + + pthread_exit(NULL); +} \ No newline at end of file diff --git a/src/UdpSocket.cpp b/src/UdpSocket.cpp new file mode 100644 index 0000000..5d17e06 --- /dev/null +++ b/src/UdpSocket.cpp @@ -0,0 +1,183 @@ +#include "../inc/UdpSocket.h" + +UdpSocket::UdpSocket(){ + byteSent = 0; + byteReceived = 0; +} + +void *get_in_addr(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +void UdpSocket::bindServer(string port) +{ + int sockfd; + struct addrinfo hints, *servinfo, *p; + int rv; + int numbytes; + struct sockaddr_storage their_addr; + char buf[MAXBUFLEN]; + socklen_t addr_len; + //char s[INET6_ADDRSTRLEN]; + + memset(&hints, 0, sizeof hints); + + hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4 + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; // use my IP + + if ((rv = getaddrinfo(NULL, port.c_str(), &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return; + } + + // loop through all the results and bind to the first we can + for(p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("bindServer: socket"); + continue; + } + + if (bind(sockfd, p->ai_addr, p->ai_addrlen) < 0) { + close(sockfd); + perror("bindServer: bind"); + continue; + } + break; + } + + if (p == NULL) { + fprintf(stderr, "bindServer: failed to bind socket\n"); + return; + } + + freeaddrinfo(servinfo); + + //cout << "bindServer: waiting to recvfrom... " << endl; + + addr_len = sizeof(their_addr); + bzero(buf, sizeof(buf)); + while ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0, + (struct sockaddr *)&their_addr, &addr_len)) > 0) { + this->byteReceived += numbytes; + //cout << "bindServer: from " << inet_ntop(their_addr.ss_family, + // get_in_addr((struct sockaddr *)&their_addr), + // s, sizeof(s)); + //cout << "bindServer: packet is " << numbytes << " bytes long" << endl; + buf[numbytes] = '\0'; + //cout << ": " << buf << endl; + + // put into queue + qMessages.push(buf); + + bzero(buf, sizeof(buf)); + } + + close(sockfd); +} + +void UdpSocket::sendMessage(string ip, string port, string message) +{ + int sockfd; + struct addrinfo hints, *servinfo, *p; + int rv; + int numbytes; + int lucky_number; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + if ((rv = getaddrinfo(ip.c_str(), port.c_str(), &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return; + } + + // loop through all the results and make a socket + for(p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("sendMessage: socket"); + continue; + } + break; + } + + if (p == NULL) { + fprintf(stderr, "sendMessage: failed to bind socket\n"); + return; + } + + // Simulate package loss + srand(time(NULL)); + lucky_number = rand() % 100 + 1; + + this->byteSent += strlen(message.c_str()); + if(lucky_number > LOSS_RATE){ + numbytes = sendto(sockfd, message.c_str(), strlen(message.c_str()), 0, p->ai_addr, p->ai_addrlen); + } + + if (numbytes == -1) { + perror("sendMessage: sendto"); + exit(1); + } + + freeaddrinfo(servinfo); + + //cout << "sendMessage: sent " << numbytes << " bytes to " << ip << endl; + close(sockfd); +} + +// void *runServer(void *udpSocket) +// { +// UdpSocket* udp; +// udp = (UdpSocket*) udpSocket; +// udp->bindServer("4950"); +// pthread_exit(NULL); +// } + +// void *runClient(void *udpSocket) +// { +// UdpSocket* udp; +// udp = (UdpSocket*) udpSocket; +// for (int i = 0; i < 3; i++) { +// sleep(2); +// udp->sendMessage("127.0.0.1", "4950", "test message"); +// } +// pthread_exit(NULL); +// } + +/*int main(int argc, char *argv[]) +{ + UdpSocket *udpSocket = new UdpSocket(); + + if (strcmp(argv[1], "client") == 0) { + udpSocket.send_message("127.0.0.1", "4950", "test message"); + } else { + udpSocket.bind_server("4950"); + } + + pthread_t threads[NUM_THREADS]; + + int rc; + + if ((rc = pthread_create(&threads[0], NULL, runServer, (void *)udpSocket)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + if ((rc = pthread_create(&threads[1], NULL, runClient, (void *)udpSocket)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + pthread_exit(NULL); + + return 0; +}*/ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..3e20e04 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,220 @@ +#include "../inc/Node.h" + +int main(int argc, char *argv[]) +{ + pthread_t threads[4]; + int rc; + Node *node; + + cout << "Mode: " << ALL2ALL << "->All-to-All, "; + cout << GOSSIP << "->Gossip-style" << endl; + if (argc < 2) { + node = new Node(); + } else { + ModeType mode = ALL2ALL; + if (atoi(argv[1]) == 1) { + mode = GOSSIP; + } + node = new Node(mode); + } + cout << "Running mode: " << node->runningMode << endl; + cout << endl; + + char host[100] = {0}; + struct hostent *hp; + + if (gethostname(host, sizeof(host)) < 0) { + cout << "error: gethostname" << endl; + return 0; + } + + if ((hp = gethostbyname(host)) == NULL) { + cout << "error: gethostbyname" << endl; + return 0; + } + + if (hp->h_addr_list[0] == NULL) { + cout << "error: no ip" << endl; + return 0; + } + //cout << "hostname " << hp->h_name << endl; + Member own(inet_ntoa(*(struct in_addr*)hp->h_addr_list[0]), PORT, + node->localTimestamp, node->heartbeatCounter); + node->nodeInformation = own; + cout << "[NEW] Starting Node at " << node->nodeInformation.ip << "/"; + cout << node->nodeInformation.port << "..." << endl; + + int *ret; + string s; + string cmd; + bool joined = false; + + // listening server can run first regardless of running time commands + if ((rc = pthread_create(&threads[0], NULL, runUdpServer, (void *)node->udpServent)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + if ((rc = pthread_create(&threads[2], NULL, runTcpServer, (void *)node->tcpServent)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + if ((rc = pthread_create(&threads[4], NULL, runTcpSender, (void *)node->tcpServent)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + + node->localFilelist.clear(); // for testing + /*node->localFilelist["sdfsfilename1"] = "localfilename1"; + node->localFilelist["sdfsfilename2"] = "localfilename2";*/ + + while(1){ + string s; + getline (cin, s); + vector<string> cmdLineInput; + string delimiter = " "; + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + string token; + while ((pos_end = s.find (delimiter, pos_start)) != string::npos) { + token = s.substr (pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + cmdLineInput.push_back (token); + } + cmdLineInput.push_back (s.substr (pos_start)); + cmd = cmdLineInput[0]; + // Deal with multiple cmd input + if(cmd == "join"){ + node->startActive(); + if ((rc = pthread_create(&threads[1], NULL, runSenderThread, (void *)node)) != 0) { + cout << "Error:unable to create thread," << rc << endl; + exit(-1); + } + joined = true; + } else if(cmd == "leave" && joined){ + node->activeRunning = false; + node->membershipList.clear(); + node->restartElection(); // clean up leader info + pthread_join(threads[1], (void **)&ret); + + string message = "["+to_string(node->localTimestamp)+"] node "+node->nodeInformation.ip+"/"+node->nodeInformation.port+" is left"; + cout << "[LEAVE]" << message.c_str() << endl; + node->logWriter->printTheLog(LEAVE, message); + sleep(2); // wait for logging + + joined = false; + } else if(cmd == "id"){ + cout << "ID: (" << node->nodeInformation.ip << ", " << node->nodeInformation.port << ")" << endl; + } else if(cmd == "member"){ + node->debugMembershipList(); + } else if(cmd == "switch") { + if(joined){ + node->requestSwitchingMode(); + } + } else if(cmd == "mode") { + cout << "In " << node->runningMode << " mode" << endl; + } else if(cmd == "exit"){ + cout << "exiting..." << endl; + break; + } else if (cmd == "put" && joined){ // MP2 op1 + if(cmdLineInput.size() < 3){ + cout << "USAGE: put filename sdfsfilename" << endl; + continue; + } + if (!node->isBlackout) { + string localfilename = cmdLineInput[1]; + string sdfsfilename = cmdLineInput[2]; + Messages outMsg(DNS, node->nodeInformation.ip + "::" + to_string(node->hashRingPosition) + "::" + sdfsfilename + "::" + localfilename); + cout << "[PUT] Got localfilename: " << localfilename << " with sdfsfilename: " << sdfsfilename << endl; + if (access(localfilename.c_str(), F_OK) != -1) { + node->tcpServent->sendMessage(node->leaderIP, TCPPORT, outMsg.toString()); + } else { + cout << "[PUT] The file " << localfilename << " is not existed" << endl; + } + + } else { + cout << "[BLACKOUT] Leader cannot accept the request" << endl; + } + } else if (cmd == "get" && joined){ // MP2 op2 + if(cmdLineInput.size() < 3){ + cout << "USAGE: get sdfsfilename filename" << endl; + continue; + } + string sdfsfilename = cmdLineInput[1]; + string localfilename = cmdLineInput[2]; + if (node->localFilelist.find(sdfsfilename) != node->localFilelist.end()) { + // found + cout << "[GET] You have sdfsfilename " << sdfsfilename << " as " << node->localFilelist[sdfsfilename] << endl; + continue; + } + if (node->fileList.find(sdfsfilename) == node->fileList.end()) { + // not found + cout << "[GET] Get sdfsfilename " << sdfsfilename << " failed" << endl; + continue; + } else { + if (node->fileList[sdfsfilename].size()==1 && node->isBlackout) { + // in 1st pass, the Leader is backup, wait for a minute + cout << "[GET] Get sdfsfilename " << sdfsfilename << " failed" << endl; + continue; + } + } + + Messages outMsg(DNSGET, node->nodeInformation.ip + "::" + to_string(node->hashRingPosition) + "::" + sdfsfilename + "::" + localfilename); + cout << "[GET] Got sdfsfilename: " << sdfsfilename << " with localfilename: " << localfilename << endl; + node->tcpServent->sendMessage(node->leaderIP, TCPPORT, outMsg.toString()); + } else if (cmd == "delete" && joined){ // MP2 op3 + if(cmdLineInput.size() < 2){ + cout << "USAGE: delete sdfsfilename" << endl; + continue; + } + if (!node->isBlackout) { + string sdfsfilename = cmdLineInput[1]; + Messages outMsg(DELETE, node->nodeInformation.ip + "::" + sdfsfilename); + cout << "[DELETE] Got sdfsfilename: " << sdfsfilename << endl; + node->tcpServent->sendMessage(node->leaderIP, TCPPORT, outMsg.toString()); + } else { + cout << "[BLACKOUT] Leader cannot accept the request" << endl; + } + } else if (cmd == "ls" && joined){ // MP2 op4 + if(cmdLineInput.size() < 2){ + cout << "USAGE: ls sdfsfilename" << endl; + continue; + } + if (!node->isBlackout) { + string sdfsfilename = cmdLineInput[1]; + node->listSDFSFileList(sdfsfilename); + } else { + cout << "[BLACKOUT] Leader cannot accept the request" << endl; + } + } else if (cmd == "store"){ // MP2 op5 + node->listLocalFiles(); + } else if (cmd == "lsall"){ + node->debugSDFSFileList(); + } else { + cout << "[join] join to a group via fixed introducer" << endl; + cout << "[leave] leave the group" << endl; + cout << "[id] print id (IP/PORT)" << endl; + cout << "[member] print all membership list" << endl; + cout << "[switch] switch to other mode (All-to-All to Gossip, and vice versa)" << endl; + cout << "[mode] show in 0/1 [All-to-All/Gossip] modes" << endl; + cout << "[exit] terminate process" << endl; + cout << " === New since MP2 === " << endl; + cout << "[put] localfilename sdfsfilename" << endl; + cout << "[get] sdfsfilename localfilename" << endl; + cout << "[delete] sdfsfilename" << endl; + cout << "[ls] list all machine (VM) addresses where this file is currently being stored" << endl; + cout << "[lsall] list all sdfsfilenames with positions" << endl; + cout << "[store] list all files currently being stored at this machine" << endl << endl; + } // More command line interface if wanted + } + + pthread_kill(threads[0], SIGUSR1); + pthread_kill(threads[4], SIGUSR1); + if(joined){ + pthread_kill(threads[1], SIGUSR1); + } + + pthread_exit(NULL); + + return 1; +} \ No newline at end of file -- GitLab