package se.chalmers.gdcn.network; import net.tomp2p.p2p.Peer; import net.tomp2p.peers.Number160; import net.tomp2p.peers.PeerAddress; import net.tomp2p.storage.Data; import se.chalmers.gdcn.communicationToUI.CommandWord; import se.chalmers.gdcn.communicationToUI.NetworkInterface; import se.chalmers.gdcn.communicationToUI.Operation; import se.chalmers.gdcn.communicationToUI.OperationFinishedListener; import se.chalmers.gdcn.control.TaskManager; import se.chalmers.gdcn.control.ThreadService; import se.chalmers.gdcn.control.WorkerReputationManager; import se.chalmers.gdcn.demo.WorkerNames; import se.chalmers.gdcn.files.DataFilesManager; import se.chalmers.gdcn.files.FileManagementUtils; import se.chalmers.gdcn.hashcash.Challenge; import se.chalmers.gdcn.hashcash.HashCash; import se.chalmers.gdcn.hashcash.Solution; import se.chalmers.gdcn.hashcash.WorkerChallengesManager; import se.chalmers.gdcn.replica.ReplicaBox; import se.chalmers.gdcn.replica.ReplicaManager; import se.chalmers.gdcn.replica.ReplicaManager.ReplicaID; import se.chalmers.gdcn.replica.ReplicaManagerBuilder; import se.chalmers.gdcn.taskbuilder.communicationToClient.TaskListener; import se.chalmers.gdcn.utils.Time; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Timer; import java.util.TimerTask; /** * Created by Leif on 2014-03-29. * * Only ONE Passer object may be created for each peer! Handles all messages to and from this Peer. */ public class TaskPasser extends Passer { private final WorkerReputationManager workerReputationManager; private final WorkerChallengesManager workerChallengesManager; private final ReplicaManager replicaManager; private final TaskManager taskManager; private final NetworkInterface client; private DataFilesManager dataFilesManager; private Timer timer; private SecretKey secretKey = null; private HashCash hashCash = null; private final WorkerID myWorkerID; /** * Message passer for sending messages regarding tasks. OBS! Only ONE Passer may be present for a Peer. * * @param peer This peer * @param taskManager Manager to run a task (replica) that was received * @param client Client to put and get results * @param dm Manager for data files */ public TaskPasser(Peer peer, TaskManager taskManager, NetworkInterface client, DataFilesManager dm) { super(peer); this.taskManager = taskManager; this.myWorkerID = new WorkerID(peer.getPeerBean().getKeyPair().getPublic()); this.client = client; this.dataFilesManager = dm; WorkerNames.getInstance().setLocalID(myWorkerID); secretKey = dataFilesManager.getSecretKey(); if(secretKey == null) { try { secretKey = KeyGenerator.getInstance("HmacSHA256").generateKey(); dataFilesManager.saveSecretKey(secretKey); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } WorkerChallengesManager wcm = dataFilesManager.getWorkerChallengesManager(); workerChallengesManager = wcm == null ? new WorkerChallengesManager() : wcm; try { hashCash = new HashCash(secretKey); } catch (InvalidKeyException e) { e.printStackTrace(); } ReplicaManager replicaManager1 = dataFilesManager.getReplicaManager(); if (replicaManager1 == null) { ReplicaManagerBuilder replicaManagerBuilder = new ReplicaManagerBuilder(myWorkerID, taskManager); replicaManagerBuilder.setTimerUpdateInterval(1, Time.MINUTE); replicaManager = replicaManagerBuilder.create(); } else { replicaManager = replicaManager1; replicaManager.setTaskManager(taskManager); } workerReputationManager = replicaManager.getWorkerReputationManager(); timer = new Timer(true); timer.schedule(new TimerTask() { @Override public void run() { // dataFilesManager.saveWorkerNodeManager(workerNodeManager); dataFilesManager.saveReplicaManager(replicaManager); } }, 1000 * 120, 1000 * 120); } /** * Stop timer and save state to file */ public void stopTimer() { timer.cancel(); dataFilesManager.saveReplicaManager(replicaManager); dataFilesManager.saveWorkerChallengesManager(workerChallengesManager); } /** * Debug message. Just sends a request that is answered. * @param otherPeer peer * @param hello String of words */ public void sendHello(PeerAddress otherPeer, String hello){ sendRequest(otherPeer, new TaskMessage(TaskMessageType.HELLO, myWorkerID, hello), new OnReplyCommand() { @Override public void execute(Object replyMessageContent) { TaskMessage taskMessage = TaskMessage.check(replyMessageContent); System.out.println(taskMessage.getActualContent()); } }); } /** * Starts task process working for this peer * @param jobOwner Peer to work for * @param autoWork True -> Will request a new task after successfully finishing a previous task. */ public void requestWork(final PeerAddress jobOwner, final boolean autoWork){ requestWork(jobOwner, autoWork, null); } /** * Used for modified attempts * * @param jobOwner Peer to work for * @param autoWork True -> Will request a new task after successfully finishing a previous task. * @param workMethod How the work is done */ public void requestWork(final PeerAddress jobOwner, final boolean autoWork, final WorkMethod workMethod){ System.out.println("Request work from " + Passer.print(jobOwner)); sendRequest(jobOwner, new TaskMessage(TaskMessageType.REQUEST_CHALLENGE, myWorkerID, ""), new OnReplyCommand() { @Override public void execute(Object replyMessageContent) { TaskMessage taskMessage = TaskMessage.check(replyMessageContent); if (taskMessage.getType() != TaskMessageType.CHALLENGE) { throw new IllegalStateException("Should be a Challenge response here!"); } final Challenge challenge = (Challenge) taskMessage.getActualContent(); System.out.println("Challenge received: " + challenge.toString()); ThreadService.submit(new Runnable() { @Override public void run() { Solution challengeSolution = challenge.solve(); System.out.println("Challenge solved"); sendRequest(jobOwner, new TaskMessage(TaskMessageType.REQUEST_TASK, myWorkerID, challengeSolution), new OnReplyCommand() { @Override public void execute(Object replyMessageContent2) { TaskMessage taskMessage2 = TaskMessage.check(replyMessageContent2); switch (taskMessage2.getType()) { case TASK: ReplicaBox replicaBox = (ReplicaBox) taskMessage2.getActualContent(); System.out.println("Start processing task, \n\tResultKey: " + replicaBox.getResultKey()); if(workMethod == null){ workOnTask(jobOwner, replicaBox, autoWork); } else { workMethod.work(jobOwner, replicaBox, autoWork); } System.out.println("Some Task was received from " + Passer.print(jobOwner)); break; case NO_TASK_AVAILABLE: System.out.println("No Task available at " + Passer.print(jobOwner)); break; case CHALLENGE_FAIL: throw new IllegalStateException("Solution failed: " + taskMessage2.getActualContent()); default: throw new IllegalStateException("Should be a Challenge response here! " + taskMessage2.getType().name()); } } }); } }); } }); } public static interface WorkMethod { void work(final PeerAddress jobOwner, final ReplicaBox replicaBox, final boolean autoWork); } /** * Works on this task until finished. Calls job owner when done or when failed. * @param jobOwner Peer to send result to * @param replicaBox Task (replica) to work on * @param autoWork True -> Will request a new task after successfully finishing a previous task. */ private void workOnTask(final PeerAddress jobOwner, final ReplicaBox replicaBox, final boolean autoWork){ final StringHolder stringHolder = new StringHolder(); taskManager.startTask(jobOwner.getID().toString(), replicaBox.getTaskMeta(), stringHolder, jobOwner,new TaskListener() { @Override public void taskFinished(final String taskName) { final Number160 resultKey = replicaBox.getResultKey(); System.out.println("Task " + taskName + " finished. Attempt to upload and notify job owner."); client.addListener(new OperationFinishedListener(client, resultKey, CommandWord.PUT) { @Override protected void operationFinished(Operation operation) { if(operation.isSuccess()){ System.out.println("Task "+taskName+" finished. Job owner notified if still online."); sendNoReplyMessage(jobOwner, new TaskMessage(TaskMessageType.RESULT_UPLOADED, myWorkerID, replicaBox.getReplicaID())); if(autoWork){ requestWork(jobOwner, true); } } else { taskFailed(taskName, "Couldn't upload result to DHT"); } } }); //TODO sign result with private key... Might want to use the class 'Box' or similar for signing byte[] result = null; try { result = FileManagementUtils.fromFile(new File(stringHolder.getString())); } catch (IOException e) { e.printStackTrace(); taskFailed(taskName, e.getMessage()); } if(result != null){ System.out.println("\nResult holds "+result.length+" bytes."); client.put(resultKey, jobOwner.getID(), new Data(result)); } } @Override public void taskFailed(String taskName, String reason) { System.out.println("Task "+taskName+" failed. Job owner notified if still online. Reason: "+reason); sendNoReplyMessage(jobOwner, new TaskMessage(TaskMessageType.TASK_FAIL, myWorkerID, new FailMessage(reason, replicaBox.getReplicaID()))); } }); } /** * {@inheritDoc} */ @Override synchronized protected Serializable handleRequest(PeerAddress sender, Object messageContent) { TaskMessage taskMessage = TaskMessage.check(messageContent); WorkerID workerID = taskMessage.getSenderID(); final WorkerNames names = WorkerNames.getInstance(); names.registerName(workerID); switch(taskMessage.getType()){ case REQUEST_CHALLENGE: // System.out.println("Received request for a Challenge from "+print(sender)); System.out.println(names.getName(workerID)+" requests a Challenge."); int score = workerChallengesManager.getCurrentScore(workerID); Challenge challenge = workerReputationManager.hasWorkerReputation(workerID)? hashCash.generateAuthenticationChallenge(myWorkerID, workerID, score) : hashCash.generateRegistrationChallenge(myWorkerID, workerID, score); return new TaskMessage(TaskMessageType.CHALLENGE, myWorkerID, challenge); case REQUEST_TASK: // System.out.println("Received request for a Task from "+print(sender)); System.out.println(names.getName(workerID)+" requests a Task."); Solution solution = (Solution) taskMessage.getActualContent(); score = workerChallengesManager.getCurrentScore(workerID); try { if(hashCash.validateSolution(solution, myWorkerID, workerID, score)) { workerChallengesManager.solvedChallenge(workerID,solution); if(solution.getPurpose() == HashCash.Purpose.REG) { workerReputationManager.registerWorker(workerID); } ReplicaBox replicaBox = replicaManager.giveReplicaToWorker(workerID); if(replicaBox==null){ return new TaskMessage(TaskMessageType.NO_TASK_AVAILABLE, myWorkerID, ""); } System.out.println("Gave replica "+replicaBox.getReplicaID()+"\n\tResultKey: "+replicaBox.getResultKey()); return new TaskMessage(TaskMessageType.TASK, myWorkerID, replicaBox); } else { workerReputationManager.reportWorker(workerID); return new TaskMessage(TaskMessageType.CHALLENGE_FAIL, myWorkerID, "Provided solution was FALSE!"); } } catch (InvalidKeyException e) { e.printStackTrace(); } case HELLO: System.out.println("Received Hello: "+taskMessage.getActualContent().toString()); return new TaskMessage(TaskMessageType.HELLO, myWorkerID, "Hi, I heard you said "+taskMessage.getActualContent()); default: throw new UnsupportedOperationException("Unsupported request: "+taskMessage.getType()); } } /** * {@inheritDoc} */ @Override synchronized protected void handleNoReply(PeerAddress sender, Object messageContent) { TaskMessage taskMessage = TaskMessage.check(messageContent); switch (taskMessage.getType()){ case RESULT_UPLOADED: resultUploaded((ReplicaID) taskMessage.getActualContent()); break; case TASK_FAIL: FailMessage failMessage = (FailMessage) taskMessage.getActualContent(); WorkerID worker = taskMessage.getSenderID(); //TODO check reputation as well? //TODO handle in ReplicaManager instead if(replicaManager.isWorkerAssignedReplica(worker, failMessage.getReplicaID())){ System.out.println("My task failed! Reason: "+failMessage.getReason()); replicaManager.replicaFailed(failMessage.getReplicaID()); } else { System.out.println("Warning! A worker node reported a failure in a task it was not participating in..."); workerReputationManager.reportWorker(worker); } break; default: throw new UnsupportedOperationException("Unsupported request: "+taskMessage.getType()); } } /** * Called when the job owner has been notified that a certain result has been uploaded. * @param replicaID ID of the replica who's result was uploaded */ private void resultUploaded(final ReplicaID replicaID){ System.out.println("Replica was completed: "+replicaID); final Number160 resultKey = replicaManager.getReplicaResultKey(replicaID); // System.out.println("\tResultKey: "+resultKey); client.addListener(new OperationFinishedListener(client, resultKey, CommandWord.GET) { @Override protected void operationFinished(Operation operation) { if (operation.isSuccess()) { // System.out.println("RESULT RAW: "+operation.getResult().toString()); Data resultData = (Data) operation.getResult(); byte[] resultArray = resultData.getData(); System.out.println("Result downloaded successfully, \n\tresult holds "+resultArray.length+" bytes."); replicaManager.replicaFinished(replicaID, resultArray); } else { System.out.println("DownloadOperation failed! " + operation.getErrorCode() + "\n\t" + operation.getReason()); } } }); client.get(resultKey, client.getID()); } public ReplicaManager getReplicaManager() { return replicaManager; } }