fix typoes, uncaught exceptions, started unit tests for ProcessManager
This commit is contained in:
parent
7d90f1c87f
commit
9754f23fd8
4 changed files with 178 additions and 98 deletions
|
|
@ -8,6 +8,11 @@
|
||||||
|
|
||||||
package JobServ;
|
package JobServ;
|
||||||
|
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ProcessController
|
* ProcessController
|
||||||
* This class wraps a java Process object with metadata
|
* This class wraps a java Process object with metadata
|
||||||
|
|
@ -36,11 +41,11 @@ class ProcessController {
|
||||||
this.pid = ProcessController.nextPid;
|
this.pid = ProcessController.nextPid;
|
||||||
ProcessController.nextPid += 1;
|
ProcessController.nextPid += 1;
|
||||||
|
|
||||||
this.process = Runtime.exec(command);
|
this.process = Runtime.getRuntime().exec(command);
|
||||||
this.output = this.process.getOutputStream();
|
this.output = this.process.getOutputStream();
|
||||||
this.input = this.process.getInputStream();
|
this.input = this.process.getInputStream();
|
||||||
this.outputScanner = new Scanner(this.input);
|
this.outputScanner = new Scanner(this.input);
|
||||||
this.outputScanner.useDelimieter("\\A");
|
this.outputScanner.useDelimiter("\\A");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -60,12 +65,12 @@ class ProcessController {
|
||||||
*
|
*
|
||||||
* TODO: (for future release) return thread state
|
* TODO: (for future release) return thread state
|
||||||
*/
|
*/
|
||||||
public Boolean getStatus() {
|
public int getStatus() {
|
||||||
try {
|
try {
|
||||||
process.exitValue();
|
process.exitValue();
|
||||||
return true;
|
return 1;
|
||||||
} catch (IllegalThreadStateException e) {
|
} catch (IllegalThreadStateException e) {
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,8 +94,8 @@ class ProcessController {
|
||||||
*/
|
*/
|
||||||
public String getOutput() {
|
public String getOutput() {
|
||||||
String out = "";
|
String out = "";
|
||||||
while(scanner.hasNext()) {
|
while(outputScanner.hasNext()) {
|
||||||
out += scanner.next();
|
out += outputScanner.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|
@ -101,8 +106,13 @@ class ProcessController {
|
||||||
* Cleans up resources and destroys process
|
* Cleans up resources and destroys process
|
||||||
*/
|
*/
|
||||||
public void kill() {
|
public void kill() {
|
||||||
this.input.close();
|
try {
|
||||||
this.output.close();
|
this.input.close();
|
||||||
|
this.output.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// streams already closed
|
||||||
|
}
|
||||||
|
|
||||||
process.destroy();
|
process.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,41 +9,52 @@
|
||||||
package JobServ;
|
package JobServ;
|
||||||
|
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
* ProcessManager
|
||||||
* Holds a list of ProcessControllers and controls access to them via mutex
|
* Holds a list of ProcessControllers and controls access to them via mutex
|
||||||
* Additionally, starts and manages a background thread that clears finished processes from the arraylist
|
* Mutex Timeout is declared here as well.
|
||||||
*/
|
*/
|
||||||
class ProcessManager {
|
class ProcessManager {
|
||||||
// TODO: LOCK_TIMEOUT should be defined in a configuration management system
|
// TODO: LOCK_TIMEOUT should be defined in a configuration management system
|
||||||
private final int LOCK_TIMEOUT = 5; // seconds
|
private final int LOCK_TIMEOUT = 5; // seconds
|
||||||
private HashTable<int, ProcessController> processQueue;
|
private HashMap<Integer, ProcessController> processMap;
|
||||||
private Boolean processQueueMutex = false;
|
private Boolean processQueueMutex = false;
|
||||||
private ExecutorService threadPool = Executors.newCachedThreadPool();
|
private ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
private Callable<void> getLockCallable = new Callable<void>() {
|
private Callable<Object> lockCallable = new Callable<Object>() {
|
||||||
public void Object call() {
|
public Object call() {
|
||||||
while(processQueueMutex){
|
while(processQueueMutex){
|
||||||
continue; // spin!
|
continue; // spin!
|
||||||
}
|
}
|
||||||
|
|
||||||
processQueueMutex = true;
|
processQueueMutex = true;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Constructor
|
* Constructor
|
||||||
* initializes process queue and start the background process checking daemon
|
* initializes process queue and start the background process checking daemon
|
||||||
*/
|
*/
|
||||||
public ProcessManager() {
|
public ProcessManager() {
|
||||||
processQueue = new HashTable<int, ProcessController>();
|
processMap = new HashMap<Integer, ProcessController>();
|
||||||
/* TODO: In a long running server over a large period of time
|
/* TODO: In a long running server over a large period of time
|
||||||
* It is possible that the streams used to redirect IO in the
|
* It is possible that the streams used to redirect IO in the
|
||||||
* Processes may become a significant use of resources.
|
* Processes may become a significant use of resources.
|
||||||
* In this case a background thread should be called to periodically
|
* In this case a background thread should be called to periodically
|
||||||
* remove dead ProcessControllers after calling kill() on them.
|
* remove dead ProcessControllers after calling kill() on them.
|
||||||
*
|
*
|
||||||
* (grab lock, iterate over map, remove processes that are done executing, store exit codes, release lock, sleep, repeat)
|
* (grab lock, iterate over map, remove finished processes, store exit codes, release lock, sleep, repeat)
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +62,7 @@ class ProcessManager {
|
||||||
* newProcess()
|
* newProcess()
|
||||||
* Takes a command and returns the translated pid of a new process
|
* Takes a command and returns the translated pid of a new process
|
||||||
* Returns -1 if getLock fails
|
* Returns -1 if getLock fails
|
||||||
|
* Returns -2 if controller throws an IOException
|
||||||
*/
|
*/
|
||||||
public int newProcess(String command) {
|
public int newProcess(String command) {
|
||||||
/*
|
/*
|
||||||
|
|
@ -61,23 +73,29 @@ class ProcessManager {
|
||||||
* which likely changed system state before it was killed.
|
* which likely changed system state before it was killed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Enter critical section
|
|
||||||
try {
|
try {
|
||||||
|
// Enter critical section
|
||||||
this.getLock();
|
this.getLock();
|
||||||
|
|
||||||
|
ProcessController newProc = new ProcessController(command);
|
||||||
|
this.processMap.put(newProc.getPid(), newProc);
|
||||||
|
|
||||||
|
// Exit critical section
|
||||||
|
this.releaseLock();
|
||||||
|
|
||||||
|
return newProc.getPid();
|
||||||
|
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// (lock was not grabbed)
|
// (lock was not grabbed)
|
||||||
System.err.println("Timeout starting new job '%s': " + e.getMessage, command);
|
System.err.println("Timeout starting new job: " + e.getMessage());
|
||||||
return -1
|
return -1;
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// (lock was grabbed)
|
||||||
|
this.releaseLock();
|
||||||
|
System.err.println("ProcessController couldnt start process: " + e.getMessage());
|
||||||
|
return -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessController newProc = ProcessController(command);
|
|
||||||
this.processes.map(newProc.getPid(), newProc);
|
|
||||||
|
|
||||||
// Exit critical section
|
|
||||||
this.releaseLock();
|
|
||||||
|
|
||||||
return newProc.getPid();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -97,8 +115,7 @@ class ProcessManager {
|
||||||
|
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
// lock could not be grabbed before timeout
|
// lock could not be grabbed before timeout
|
||||||
System.err.println("Timeout getting process status for %s: " + e.getMessage(),
|
System.err.println("Timeout getting process status: " + e.getMessage());
|
||||||
Integer.toString(pid));
|
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,8 +145,7 @@ class ProcessManager {
|
||||||
this.getLock();
|
this.getLock();
|
||||||
|
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
System.err.println("Timeout getting process return for %s: " + e.getMessage(),
|
System.err.println("Timeout getting process return: " + e.getMessage());
|
||||||
Integer.toString(pid));
|
|
||||||
return 257;
|
return 257;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,20 +172,19 @@ class ProcessManager {
|
||||||
this.getLock();
|
this.getLock();
|
||||||
|
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
System.err.println("Timeout getting process output for %s: " + e.getMessage(),
|
System.err.println("Timeout getting process output: " + e.getMessage());
|
||||||
Integer.toString());
|
|
||||||
return "[-] ERROR: Timeout grabbing lock to access process information";
|
return "[-] ERROR: Timeout grabbing lock to access process information";
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessController candidate = this.processMap.get(pid);
|
ProcessController candidate = this.processMap.get(pid);
|
||||||
if (candidate != null) {
|
if (candidate != null) {
|
||||||
output = iter.getOutput();
|
output = candidate.getOutput();
|
||||||
this.releaseLock();
|
this.releaseLock();
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.releaseLock();
|
this.releaseLock();
|
||||||
return "[-] ERROR: Process not found"
|
return "[-] ERROR: Process not found";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -182,7 +197,7 @@ class ProcessManager {
|
||||||
this.getLock();
|
this.getLock();
|
||||||
|
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
System.err.println("Timeout killing process: " + e.getMessage);
|
System.err.println("Timeout killing process: " + e.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,9 +217,10 @@ class ProcessManager {
|
||||||
* Throws TimeoutException when it fails to get the lock.
|
* Throws TimeoutException when it fails to get the lock.
|
||||||
*/
|
*/
|
||||||
private synchronized void getLock() throws TimeoutException {
|
private synchronized void getLock() throws TimeoutException {
|
||||||
|
Future<Object> future = this.threadPool.submit(this.lockCallable);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Future<void> future = executor.submit(task);
|
future.get(this.LOCK_TIMEOUT, TimeUnit.SECONDS);
|
||||||
void result = future.get(this.LOCK_TIMEOUT, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
System.err.println("[!] ERROR: " + e.getMessage());
|
System.err.println("[!] ERROR: " + e.getMessage());
|
||||||
|
|
@ -249,9 +265,14 @@ class ProcessManager {
|
||||||
* releases resources held in the processController objects
|
* releases resources held in the processController objects
|
||||||
*/
|
*/
|
||||||
private void shutdown() {
|
private void shutdown() {
|
||||||
this.getLock();
|
this.processQueueMutex = true;
|
||||||
for (ProcessController p : this.processQueue) {
|
|
||||||
p.kill(); // exit threads, release IO streams, etc.
|
Iterator<HashMap.Entry<Integer, ProcessController>> iterator = this.processMap.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
HashMap.Entry<Integer, ProcessController> entry = iterator.next();
|
||||||
|
|
||||||
|
entry.getValue().kill();
|
||||||
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ import org.mockito.ArgumentMatchers;
|
||||||
public class JobServerAuthenticationTest {
|
public class JobServerAuthenticationTest {
|
||||||
|
|
||||||
private final String projectRoot = "";
|
private final String projectRoot = "";
|
||||||
|
|
||||||
// Authorized client key/cert/ca
|
// Authorized client key/cert/ca
|
||||||
private final String clientCa = projectRoot + "resources/client/ca.crt";
|
private final String clientCa = projectRoot + "resources/client/ca.crt";
|
||||||
private final String clientKey = projectRoot + "resources/client/private.pem";
|
private final String clientKey = projectRoot + "resources/client/private.pem";
|
||||||
|
|
@ -73,65 +73,65 @@ public class JobServerAuthenticationTest {
|
||||||
// was setUp able to use SSL Certs
|
// was setUp able to use SSL Certs
|
||||||
private Boolean serverSslInitialized = true;
|
private Boolean serverSslInitialized = true;
|
||||||
private Boolean clientSslInitialized = true;
|
private Boolean clientSslInitialized = true;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* test constructor
|
* test constructor
|
||||||
* generates both clients and the server
|
* generates both clients and the server
|
||||||
*/
|
*/
|
||||||
public JobServerAuthenticationTest() throws Exception {
|
public JobServerAuthenticationTest() throws Exception {
|
||||||
|
|
||||||
try {
|
|
||||||
// generate SSL contexts
|
|
||||||
SslContextBuilder serverContextBuilder = SslContextBuilder.forServer(new File(serverCert),
|
|
||||||
new File(serverKey));
|
|
||||||
serverContextBuilder.trustManager(new File(clientCa));
|
|
||||||
serverContextBuilder.clientAuth(ClientAuth.REQUIRE);
|
|
||||||
|
|
||||||
this.server = new JobServServer(GrpcSslContexts.configure(serverContextBuilder).build(), 8448);
|
try {
|
||||||
this.serverSslInitialized = true;
|
// generate SSL contexts
|
||||||
|
SslContextBuilder serverContextBuilder = SslContextBuilder.forServer(new File(serverCert),
|
||||||
} catch (SSLException e) {
|
new File(serverKey));
|
||||||
this.serverSslInitialized = false;
|
serverContextBuilder.trustManager(new File(clientCa));
|
||||||
System.err.println(e.getMessage());
|
serverContextBuilder.clientAuth(ClientAuth.REQUIRE);
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
this.serverSslInitialized = false;
|
|
||||||
System.err.println(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate ssl for clients
|
this.server = new JobServServer(GrpcSslContexts.configure(serverContextBuilder).build(), 8448);
|
||||||
|
this.serverSslInitialized = true;
|
||||||
|
|
||||||
|
} catch (SSLException e) {
|
||||||
|
this.serverSslInitialized = false;
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
this.serverSslInitialized = false;
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate ssl for clients
|
||||||
if (this.serverSslInitialized) {
|
if (this.serverSslInitialized) {
|
||||||
try {
|
try {
|
||||||
SslContextBuilder goodClientBuilder = GrpcSslContexts.forClient();
|
SslContextBuilder goodClientBuilder = GrpcSslContexts.forClient();
|
||||||
goodClientBuilder.trustManager(new File(serverCa));
|
goodClientBuilder.trustManager(new File(serverCa));
|
||||||
goodClientBuilder.keyManager(new File(clientCert), new File(clientKey));
|
goodClientBuilder.keyManager(new File(clientCert), new File(clientKey));
|
||||||
|
|
||||||
|
SslContextBuilder badClientBuilder = GrpcSslContexts.forClient();
|
||||||
|
badClientBuilder.trustManager(new File(serverCa));
|
||||||
|
badClientBuilder.keyManager(new File(badCert), new File(badKey));
|
||||||
|
|
||||||
|
ManagedChannel goodChannel = NettyChannelBuilder.forAddress("localhost", 8448)
|
||||||
|
.sslContext(goodClientBuilder.build())
|
||||||
|
.directExecutor()
|
||||||
|
.build();
|
||||||
|
|
||||||
SslContextBuilder badClientBuilder = GrpcSslContexts.forClient();
|
ManagedChannel badChannel = NettyChannelBuilder.forAddress("localhost", 8448)
|
||||||
badClientBuilder.trustManager(new File(serverCa));
|
.sslContext(badClientBuilder.build())
|
||||||
badClientBuilder.keyManager(new File(badCert), new File(badKey));
|
.directExecutor()
|
||||||
|
.build();
|
||||||
ManagedChannel goodChannel = NettyChannelBuilder.forAddress("localhost", 8448)
|
|
||||||
.sslContext(goodClientBuilder.build())
|
goodClient = new JobServClient(goodChannel);
|
||||||
.directExecutor()
|
badClient = new JobServClient(badChannel);
|
||||||
.build();
|
this.clientSslInitialized = true;
|
||||||
|
|
||||||
ManagedChannel badChannel = NettyChannelBuilder.forAddress("localhost", 8448)
|
} catch (SSLException e) {
|
||||||
.sslContext(badClientBuilder.build())
|
this.clientSslInitialized = false;
|
||||||
.directExecutor()
|
System.err.println(e.getMessage());
|
||||||
.build();
|
}
|
||||||
|
|
||||||
goodClient = new JobServClient(goodChannel);
|
|
||||||
badClient = new JobServClient(badChannel);
|
|
||||||
this.clientSslInitialized = true;
|
|
||||||
|
|
||||||
} catch (SSLException e) {
|
|
||||||
this.clientSslInitialized = false;
|
|
||||||
System.err.println(e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.clientSslInitialized = false;
|
this.clientSslInitialized = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -141,14 +141,14 @@ public class JobServerAuthenticationTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void certificateAuthenticationTest() {
|
public void certificateAuthenticationTest() {
|
||||||
assertEquals(true, serverSslInitialized);
|
assertEquals(true, serverSslInitialized);
|
||||||
assertEquals(true, clientSslInitialized);
|
assertEquals(true, clientSslInitialized);
|
||||||
|
|
||||||
int result = badClient.sendNewJobMessage("test command");
|
int result = badClient.sendNewJobMessage("test command");
|
||||||
assertEquals(-2, result);
|
assertEquals(-2, result);
|
||||||
|
|
||||||
result = goodClient.sendNewJobMessage("test command");
|
result = goodClient.sendNewJobMessage("test command");
|
||||||
Boolean assertCondition = result == -2;
|
Boolean assertCondition = result == -2;
|
||||||
assertEquals(assertCondition, false);
|
assertEquals(assertCondition, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
src/test/java/JobServ/ProcessManagerTest.java
Normal file
49
src/test/java/JobServ/ProcessManagerTest.java
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* ProcessManagerTest
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 22, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ProcessManagerTest
|
||||||
|
* Class that performs positive and negative unit tests
|
||||||
|
* of every public method in ProcessManager. This not
|
||||||
|
* only unit tests ProcessManager but also integration
|
||||||
|
* tests it with ProcessController.
|
||||||
|
*/
|
||||||
|
public class ProcessManagerTest {
|
||||||
|
ProcessManager manager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ProcessManagerTest constructor
|
||||||
|
* initializes the process manager
|
||||||
|
*/
|
||||||
|
public ProcessManagerTest() {
|
||||||
|
manager = new ProcessManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* addProcessTest()
|
||||||
|
* positive unit test for newProcess
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void addProcessesTest() {
|
||||||
|
int pid1 = manager.newProcess("ping google");
|
||||||
|
assertEquals(0, pid1);
|
||||||
|
|
||||||
|
int pid2 = manager.newProcess("ping google");
|
||||||
|
assertEquals(1, pid2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue