Merge branch 'betterScalingJobControll' into 'master'
Better scaling job control See merge request whom/jobserv!3
This commit is contained in:
commit
8c03a32fc4
19 changed files with 2286 additions and 3 deletions
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Ignore Gradle project-specific cache directory
|
||||||
|
.gradle
|
||||||
|
|
||||||
|
# Ignore Gradle build output directory
|
||||||
|
build
|
||||||
|
|
||||||
|
# Ignore emacs swapfiles
|
||||||
|
\#*
|
||||||
|
.\#*
|
||||||
|
|
||||||
|
# Ignore vim swapfiles
|
||||||
|
*\~
|
||||||
|
|
||||||
|
# Dont commit certs
|
||||||
|
resources/*
|
||||||
|
|
||||||
|
# Dont commit certs or compiled software
|
||||||
|
staging/*
|
||||||
|
|
||||||
|
# Test Logs
|
||||||
|
JobServ-Server-*
|
||||||
45
README.md
45
README.md
|
|
@ -1,10 +1,49 @@
|
||||||
# JobServ
|
# JobServ
|
||||||
Remote Procedure Calls over the protobuf API
|
Remote Procedure Calls over the protobuf API
|
||||||
|
|
||||||
# Dependancies
|
# Requirements
|
||||||
|
- openssl
|
||||||
|
- tar
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
Gradle will manage dependencies, generate code, compile the java, and package the code.
|
||||||
# Testing
|
Simply run the folllowing command:
|
||||||
|
```shell
|
||||||
|
$ ./buildwrapper.sh
|
||||||
|
```
|
||||||
|
Buildwrapper will ask you for details about the client and server. If you are testing this software both CNs can be set to localhost.
|
||||||
|
Buildwrapper will then generate CAs for and signed certs for the Client and Server. In addition a seperate, third CA and cert will be generated for testing purposes.
|
||||||
|
Gradle will then generate protobuf source and compile it with the java source for the client and server.
|
||||||
|
After gradle is finished compiling and running the junit tests, buildwrapper will organize the sources with their respective certs in the staging folder.
|
||||||
|
In addition to a server folder and a client folder, there will be a test folder which has a copy of all certs and both server and client functionality.
|
||||||
|
The test CA is not trusted by the server or the client by default. As such, the test cert can be used to induce a mutual tls authentication failure.
|
||||||
|
|
||||||
# Running
|
# Running
|
||||||
|
After build, the programs can be found in the staging folder.
|
||||||
|
After changing directory to the 'staging/client' folder or the 'staging/server' folder, either program can be run as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ./server (port)
|
||||||
|
$ ./client (hostname) (port) (command) (arguments)
|
||||||
|
```
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
$ ./buildwrapper.sh
|
||||||
|
.....
|
||||||
|
$ cd staging/server
|
||||||
|
$ ./server 8448 &
|
||||||
|
$ cd ../client
|
||||||
|
$ client localhost 8448 new ping archive.org
|
||||||
|
```
|
||||||
|
alternatively, for guidance:
|
||||||
|
```
|
||||||
|
$ ./server
|
||||||
|
$ ./client help
|
||||||
|
```
|
||||||
|
|
||||||
|
# Distribution
|
||||||
|
At this point you can copy the staging/client or staging/server folders to any environment in which their Certificate CN's are valid.
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
Running the gradle test task, or the buildwrapper will run all junit tests.
|
||||||
|
Currently that includes a test of certificate based authentication (Mutual TLS), tests for the thread safe process control module, and tests ensuring that only one connection can access a processes information at a time.
|
||||||
|
|
|
||||||
111
build.gradle
Normal file
111
build.gradle
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* This file was generated by the Gradle 'init' task.
|
||||||
|
*
|
||||||
|
* This generated file contains a sample Java project to get you started.
|
||||||
|
* For more details take a look at the Java Quickstart chapter in the Gradle
|
||||||
|
* User Manual available at https://docs.gradle.org/5.2.1/userguide/tutorial_java_projects.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'com.google.protobuf' version '0.8.8'
|
||||||
|
id 'application'
|
||||||
|
id 'com.adarshr.test-logger' version '1.6.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
def grpcVersion = '1.20.0'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
//maven{ url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
|
||||||
|
//mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// This dependency is found on compile classpath of this component and consumers.
|
||||||
|
implementation 'com.google.guava:guava:27.0.1-jre'
|
||||||
|
|
||||||
|
// Use JUnit test framework
|
||||||
|
testImplementation "io.grpc:grpc-testing:${grpcVersion}"
|
||||||
|
testImplementation "junit:junit:4.12"
|
||||||
|
testImplementation "org.mockito:mockito-core:2.25.1"
|
||||||
|
|
||||||
|
// Used by GRPC generated code
|
||||||
|
compile 'org.glassfish:javax.annotation:10.0-b28'
|
||||||
|
|
||||||
|
// grpc stuff
|
||||||
|
compile "io.grpc:grpc-netty:${grpcVersion}"
|
||||||
|
compile "io.grpc:grpc-protobuf:${grpcVersion}"
|
||||||
|
compile "io.grpc:grpc-stub:${grpcVersion}"
|
||||||
|
compile 'io.netty:netty-tcnative-boringssl-static:2.0.22.Final'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
testLogging.showStandardStreams = true
|
||||||
|
testLogging.exceptionFormat = 'full'
|
||||||
|
}
|
||||||
|
|
||||||
|
testlogger {
|
||||||
|
theme 'standard'
|
||||||
|
showExceptions true
|
||||||
|
slowThreshold 2000
|
||||||
|
showSummary true
|
||||||
|
showPassed true
|
||||||
|
showSkipped true
|
||||||
|
showFailed true
|
||||||
|
showStandardStreams false
|
||||||
|
showPassedStandardStreams true
|
||||||
|
showSkippedStandardStreams true
|
||||||
|
showFailedStandardStreams true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the main class for the application
|
||||||
|
mainClassName = 'JobServ.JobServClient'
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = "com.google.protobuf:protoc:3.7.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
grpc {
|
||||||
|
artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateProtoTasks {
|
||||||
|
all()*.plugins {
|
||||||
|
grpc {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task Server(type: CreateStartScripts) {
|
||||||
|
mainClassName = 'JobServ.JobServServer'
|
||||||
|
applicationName = 'jobserv-server'
|
||||||
|
outputDir = new File(project.buildDir, 'tmp')
|
||||||
|
classpath = startScripts.classpath
|
||||||
|
}
|
||||||
|
|
||||||
|
task Client(type: CreateStartScripts) {
|
||||||
|
mainClassName = 'JobServ.JobServClient'
|
||||||
|
applicationName = 'jobserv-client'
|
||||||
|
outputDir = new File(project.buildDir, 'tmp')
|
||||||
|
classpath = startScripts.classpath
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationDistribution.into('bin') {
|
||||||
|
from(Server)
|
||||||
|
from(Client)
|
||||||
|
fileMode = 0755
|
||||||
|
}
|
||||||
126
buildwrapper.sh
Executable file
126
buildwrapper.sh
Executable file
|
|
@ -0,0 +1,126 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
read -p "Enter Server CN (localhost or address): " SRVNAME
|
||||||
|
read -p "Enter Client CN (localhost or address): " CLTNAME
|
||||||
|
|
||||||
|
SERVER_CA_CN=jobserv-server-ca
|
||||||
|
SERVER_PATH=resources/server
|
||||||
|
CLIENT_CA_CN=jobserv-client-ca
|
||||||
|
CLIENT_PATH=resources/client
|
||||||
|
TEST_CA_CN=jobserv-bad-cert-ca
|
||||||
|
TEST_CN=localhost
|
||||||
|
TEST_PATH=resources/test
|
||||||
|
|
||||||
|
# refactor this to test for directory existanc
|
||||||
|
rm -rf resources
|
||||||
|
mkdir resources/
|
||||||
|
mkdir resources/client
|
||||||
|
mkdir resources/server
|
||||||
|
mkdir resources/test
|
||||||
|
rm -rf staging
|
||||||
|
|
||||||
|
|
||||||
|
# Get passwords for CAs
|
||||||
|
read -p "Enter Server CA Passphrase: " SRVCAPASS
|
||||||
|
read -p "Enter Client CA Passphrase: " CLTCAPASS
|
||||||
|
|
||||||
|
# Generate CA Keys
|
||||||
|
echo "[+] Generating Server CA Key"
|
||||||
|
openssl genrsa -passout pass:$SRVCAPASS -aes256 -out $SERVER_PATH/ca.key 4096
|
||||||
|
echo "[+] Generating Client CA Key"
|
||||||
|
openssl genrsa -passout pass:$CLTCAPASS -aes256 -out $CLIENT_PATH/ca.key 4096
|
||||||
|
echo "[+] Generating test CA Key"
|
||||||
|
openssl genrsa -passout pass:dontusethiskey -aes256 -out $TEST_PATH/ca.key 4096
|
||||||
|
|
||||||
|
# Generate CA Certs
|
||||||
|
echo "[+] Generating Server CA Cert"
|
||||||
|
openssl req -passin pass:$SRVCAPASS -new -x509 -days 365 -key $SERVER_PATH/ca.key -out $SERVER_PATH/ca.crt -subj "/CN=${SERVER_CA_CN}"
|
||||||
|
echo "[+] Generating Client CA Cert"
|
||||||
|
openssl req -passin pass:$CLTCAPASS -new -x509 -days 365 -key $CLIENT_PATH/ca.key -out $CLIENT_PATH/ca.crt -subj "/CN=${CLIENT_CA_CN}"
|
||||||
|
echo "[+] Generating test CA Key"
|
||||||
|
openssl req -passin pass:dontusethiskey -new -x509 -days 365 -key $TEST_PATH/ca.key -out $TEST_PATH/ca.crt -subj "/CN=${TEST_CA_CN}"
|
||||||
|
|
||||||
|
|
||||||
|
# Generate Server Key, Signing request, cert
|
||||||
|
echo "[+] Generating Server key"
|
||||||
|
openssl genrsa -passout pass:${SRVCAPASS} -aes256 -out $SERVER_PATH/private.key 4096
|
||||||
|
echo "[+] Generating Server signing request"
|
||||||
|
openssl req -passin pass:${SRVCAPASS} -new -key $SERVER_PATH/private.key -out $SERVER_PATH/request.csr -subj "/CN=${SRVNAME}"
|
||||||
|
echo "[+] Generating Server certificate "
|
||||||
|
openssl x509 -req -passin pass:${SRVCAPASS} -days 365 -in $SERVER_PATH/request.csr -CA $SERVER_PATH/ca.crt -CAkey $SERVER_PATH/ca.key -set_serial 01 -out $SERVER_PATH/server.crt
|
||||||
|
echo "[+] Removing passphrase from server key"
|
||||||
|
openssl rsa -passin pass:${SRVCAPASS} -in $SERVER_PATH/private.key -out $SERVER_PATH/private.key
|
||||||
|
|
||||||
|
# Generate Client Key, Signing request, cert
|
||||||
|
echo "[+] Generating Client key"
|
||||||
|
openssl genrsa -passout pass:${CLTCAPASS} -aes256 -out $CLIENT_PATH/private.key 4096
|
||||||
|
echo "[+] Generating Client signing request"
|
||||||
|
openssl req -passin pass:${CLTCAPASS} -new -key $CLIENT_PATH/private.key -out $CLIENT_PATH/request.csr -subj "/CN=${CLTNAME}"
|
||||||
|
echo "[+] Generating Client certificate "
|
||||||
|
openssl x509 -req -passin pass:${CLTCAPASS} -days 365 -in $CLIENT_PATH/request.csr -CA $CLIENT_PATH/ca.crt -CAkey $CLIENT_PATH/ca.key -set_serial 01 -out $CLIENT_PATH/client.crt
|
||||||
|
echo "[+] Removing passphrase from client key"
|
||||||
|
openssl rsa -passin pass:${CLTCAPASS} -in $CLIENT_PATH/private.key -out $CLIENT_PATH/private.key
|
||||||
|
|
||||||
|
# Generate Test Key, Signing request, cert
|
||||||
|
echo "[+] Generating test key"
|
||||||
|
openssl genrsa -passout pass:dontusethiskey -aes256 -out $TEST_PATH/private.key 4096
|
||||||
|
echo "[+] Generating test signing request"
|
||||||
|
openssl req -passin pass:dontusethiskey -new -key $TEST_PATH/private.key -out $TEST_PATH/request.csr -subj "/CN=${TEST_CN}"
|
||||||
|
echo "[+] Generating test certificate "
|
||||||
|
openssl x509 -req -passin pass:dontusethiskey -days 365 -in $TEST_PATH/request.csr -CA $TEST_PATH/ca.crt -CAkey $TEST_PATH/ca.key -set_serial 01 -out $TEST_PATH/test.crt
|
||||||
|
echo "[+] Removing passphrase from test key"
|
||||||
|
openssl rsa -passin pass:dontusethiskey -in $TEST_PATH/private.key -out $TEST_PATH/private.key
|
||||||
|
|
||||||
|
|
||||||
|
echo "[+] Converting private keys to X.509"
|
||||||
|
openssl pkcs8 -topk8 -nocrypt -in $CLIENT_PATH/private.key -out $CLIENT_PATH/private.pem
|
||||||
|
openssl pkcs8 -topk8 -nocrypt -in $SERVER_PATH/private.key -out $SERVER_PATH/private.pem
|
||||||
|
openssl pkcs8 -topk8 -nocrypt -in $TEST_PATH/private.key -out $TEST_PATH/private.pem
|
||||||
|
|
||||||
|
echo "[+] initiating gradle build"
|
||||||
|
./gradlew clean build
|
||||||
|
|
||||||
|
# Ideally this next section would be done with gradle
|
||||||
|
# Unfortunately gradle's protobuf distribution plugin does not seem to have facilities to manually include certs
|
||||||
|
# Or to specify seperate client and server tarballs for that matter
|
||||||
|
# Definitely more research on gradle should be done, but after JobServ hits MVP
|
||||||
|
echo "[+] extracting built code"
|
||||||
|
mkdir staging
|
||||||
|
mkdir staging/client
|
||||||
|
mkdir staging/server
|
||||||
|
mkdir staging/test
|
||||||
|
tar -xvf build/distributions/JobServ.tar -C staging/client
|
||||||
|
tar -xvf build/distributions/JobServ.tar -C staging/server
|
||||||
|
tar -xvf build/distributions/JobServ.tar -C staging/test
|
||||||
|
|
||||||
|
echo "[+] removing server capabilities from client"
|
||||||
|
rm staging/client/JobServ/bin/jobserv-server staging/client/JobServ/bin/jobserv-server.bat
|
||||||
|
|
||||||
|
echo "[+] removing client capabilities from server"
|
||||||
|
rm staging/server/JobServ/bin/jobserv-client staging/server/JobServ/bin/jobserv-client.bat
|
||||||
|
|
||||||
|
echo "[+] populating certificates"
|
||||||
|
cp resources/server/server.crt staging/server/
|
||||||
|
cp resources/server/private.pem staging/server/
|
||||||
|
cp resources/client/ca.crt staging/server/
|
||||||
|
cp resources/client/client.crt staging/client/
|
||||||
|
cp resources/client/private.pem staging/client/
|
||||||
|
cp resources/server/ca.crt staging/client/
|
||||||
|
cp -r resources/* staging/test/
|
||||||
|
|
||||||
|
echo "[+] Adding wrapper script for client"
|
||||||
|
# This could also be a .desktop file without much more work.
|
||||||
|
cat << EOF > staging/client/client
|
||||||
|
./JobServ/bin/jobserv-client private.pem client.crt ca.crt \$@
|
||||||
|
EOF
|
||||||
|
chmod +x staging/client/client
|
||||||
|
|
||||||
|
echo "[+] Adding wrapper script for server"
|
||||||
|
# This could also be a .desktop file without much more work.
|
||||||
|
cat << EOF > staging/server/server
|
||||||
|
./JobServ/bin/jobserv-server \$1 server.crt private.pem ca.crt
|
||||||
|
EOF
|
||||||
|
chmod +x staging/server/server
|
||||||
|
|
||||||
|
echo "[+] removing test logs"
|
||||||
|
rm JobServ-Server-*
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
172
gradlew
vendored
Executable file
172
gradlew
vendored
Executable file
|
|
@ -0,0 +1,172 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
84
gradlew.bat
vendored
Normal file
84
gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
296
src/main/java/JobServ/JobServClient.java
Normal file
296
src/main/java/JobServ/JobServClient.java
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
/*
|
||||||
|
* JobServClient
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 18, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import io.grpc.netty.GrpcSslContexts;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import java.util.InputMismatchException;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import io.grpc.netty.NettyChannelBuilder;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The JobServClient class extends the gRPC stub code
|
||||||
|
* Additionally, it plugs a command line interface into the API code.
|
||||||
|
*/
|
||||||
|
public class JobServClient {
|
||||||
|
private final String serversideTimeoutErrorMessage = "Timeout locking process control on server\n"+
|
||||||
|
"Server could be under heavy load\nConsider trying again.";
|
||||||
|
|
||||||
|
private JobServClientAPIConnector api;
|
||||||
|
private String[] programArgs;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
* takes program arguments and an api connector object
|
||||||
|
*/
|
||||||
|
public JobServClient(String[] args, JobServClientAPIConnector api) {
|
||||||
|
this.programArgs = args;
|
||||||
|
this.api = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getPidArg()
|
||||||
|
* reentrant code was found in all commands except newjob
|
||||||
|
* this function pulls the pid argument and wraps around the integer cast
|
||||||
|
* returns -1 (an invalid PID) if bad index or unparsable int
|
||||||
|
*/
|
||||||
|
private int getPidArg(int index) {
|
||||||
|
if (this.programArgs.length < index) {
|
||||||
|
System.out.println("Improper formatting, try client --help");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(this.programArgs[index]);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println(this.programArgs[index] + " is not a valid integer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* outputHelp()
|
||||||
|
* writes help information about all commands in the shell to screen
|
||||||
|
*/
|
||||||
|
public static void outputHelp() {
|
||||||
|
System.out.println("... new (command)\n"+
|
||||||
|
"Starts a new process on the server\n"+
|
||||||
|
"example: ./client key.pem cert.crt ca.crt localhost 8448 new echo hello world!\n\n"+
|
||||||
|
"... output (pid) (lines)\n"+
|
||||||
|
"Garners (lines) lines of output from process (pid) on server\n"+
|
||||||
|
"example: ./client key.pem cert.crt ca.crt localhost 8448 output 0 5\n\n"+
|
||||||
|
"... status (pid)\n"+
|
||||||
|
"Returns whether process on server is running\n"+
|
||||||
|
"example: ./client key.pem cert.crt ca.crt localhost 8448 status 0\n\n"+
|
||||||
|
"... return (pid)\n"+
|
||||||
|
"Collects return code from remote process\n"+
|
||||||
|
"example: ./client key.pem cert.crt ca.crt localhost 8448 return 0\n\n"+
|
||||||
|
"... kill (pid)\n"+
|
||||||
|
"Immediately destroys remote process\n"+
|
||||||
|
"example: ./client key.pem cert.crt ca.crt localhost 8448 kill 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* makeNewProcess
|
||||||
|
* makes a new process
|
||||||
|
*/
|
||||||
|
public void makeNewProcess() {
|
||||||
|
String command = "";
|
||||||
|
for (int token = 6; token < this.programArgs.length; token++) {
|
||||||
|
command += " " + this.programArgs[token];
|
||||||
|
}
|
||||||
|
|
||||||
|
int newProcess = this.api.sendNewJobMessage(command);
|
||||||
|
switch(newProcess) {
|
||||||
|
case -1:
|
||||||
|
System.out.println("Server failed to spawn process. Bad command.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case -2:
|
||||||
|
// error logged by API Connector
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.printf("Process started, assigned pid is %d\n", newProcess);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getOutput
|
||||||
|
* gets output from a process
|
||||||
|
*/
|
||||||
|
public void getOutput() {
|
||||||
|
if (this.programArgs.length < 8) {
|
||||||
|
System.out.println("Improper formatting, need a lines and a pid argument.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int candidatePid = this.getPidArg(6);
|
||||||
|
int lines = this.getPidArg(7);
|
||||||
|
if (candidatePid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String processOutput = this.api.getProcessOutput(candidatePid, lines);
|
||||||
|
System.out.println(processOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStatus
|
||||||
|
* gets the running status of a process
|
||||||
|
*/
|
||||||
|
public void getStatus() {
|
||||||
|
int candidatePid = this.getPidArg(6);
|
||||||
|
if (candidatePid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int processStatus = this.api.getProcessStatus(candidatePid);
|
||||||
|
switch(processStatus) {
|
||||||
|
case 0:
|
||||||
|
System.out.println("Process is running");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
System.out.println("Process is not running");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
System.out.println("A client killed the process already");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
System.out.println("Process does not exist");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
System.out.println(this.serversideTimeoutErrorMessage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* killProcess
|
||||||
|
* kills a process
|
||||||
|
*/
|
||||||
|
public void killProcess() {
|
||||||
|
int candidatePid = this.getPidArg(6);
|
||||||
|
if (candidatePid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int finalStatus = this.api.killProcess(candidatePid);
|
||||||
|
switch(finalStatus) {
|
||||||
|
case 0:
|
||||||
|
System.out.println("Process is still running");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
System.out.println("Process was killed");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
System.out.println("Process does not exist");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
System.out.println(this.serversideTimeoutErrorMessage);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
// error logged in API Connector
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getReturn
|
||||||
|
* gets return code from a process
|
||||||
|
*/
|
||||||
|
public void getReturn() {
|
||||||
|
int candidatePid = this.getPidArg(6);
|
||||||
|
if (candidatePid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int returnCode = this.api.getProcessReturn(candidatePid);
|
||||||
|
|
||||||
|
switch(returnCode){
|
||||||
|
case 256:
|
||||||
|
System.out.println("Process is still running");
|
||||||
|
break;
|
||||||
|
case 257:
|
||||||
|
System.out.println("Process was killed manually by a client");
|
||||||
|
break;
|
||||||
|
case 258:
|
||||||
|
System.out.println("Process does not exist");
|
||||||
|
break;
|
||||||
|
case 259:
|
||||||
|
System.out.println(this.serversideTimeoutErrorMessage);
|
||||||
|
break;
|
||||||
|
case 260:
|
||||||
|
// error logged in getProcesReturn
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println("Process Exit Code: " + Integer.toString(returnCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* main()
|
||||||
|
* Client entrypoint
|
||||||
|
* Parses arguments, initializes client, and calls the correct functions
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
// check args
|
||||||
|
if (args.length < 7) {
|
||||||
|
System.out.println("Usage: $ ./jobserv-client privatekey, cert, truststore, host, port, command, args");
|
||||||
|
System.out.println("Or try $ ./jobserv-client help");
|
||||||
|
outputHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobServClientAPIConnector api;
|
||||||
|
try {
|
||||||
|
SslContextBuilder builder = GrpcSslContexts.forClient();
|
||||||
|
builder.trustManager(new File(args[2]));
|
||||||
|
builder.keyManager(new File(args[1]), new File(args[0]));
|
||||||
|
|
||||||
|
ManagedChannel channel = NettyChannelBuilder.forAddress(args[3], Integer.parseInt(args[4]))
|
||||||
|
.sslContext(builder.build())
|
||||||
|
.build();
|
||||||
|
api = new JobServClientAPIConnector(channel);
|
||||||
|
|
||||||
|
// Likely bad port
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("Invalid Port");
|
||||||
|
return;
|
||||||
|
|
||||||
|
// bad cert or key format
|
||||||
|
} catch (SSLException e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobServClient client = new JobServClient(args, api);
|
||||||
|
|
||||||
|
// parse remaining args
|
||||||
|
switch (args[5]) {
|
||||||
|
case "new":
|
||||||
|
client.makeNewProcess();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "output":
|
||||||
|
client.getOutput();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "status":
|
||||||
|
client.getStatus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "kill":
|
||||||
|
client.killProcess();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "return":
|
||||||
|
client.getReturn();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println("Improper command, try 'help'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
200
src/main/java/JobServ/JobServClientAPIConnector.java
Normal file
200
src/main/java/JobServ/JobServClientAPIConnector.java
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
/*
|
||||||
|
* JobServClientAPIConnector
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 23, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.StatusRuntimeException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JobServClientAPIConnector
|
||||||
|
* Starts a connection to the API Connector
|
||||||
|
* implements functions that send and recieve frm the API
|
||||||
|
* Refactored into its own module to make the Client interface nicer
|
||||||
|
* and to allow for a veriety of interfaces to be created
|
||||||
|
*/
|
||||||
|
class JobServClientAPIConnector {
|
||||||
|
private final String apiFailureMessage = "Failed while trying to connect to server.";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The client should not use the same logging module as the server.
|
||||||
|
* In a more robust product the server logging module will take advantage of system level
|
||||||
|
* log aggregators such as journalctl, which the client should not be writing to on the users system
|
||||||
|
*/
|
||||||
|
private static final Logger logger = Logger.getLogger(JobServClient.class.getName());
|
||||||
|
|
||||||
|
private final ManagedChannel channel;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* blockingStub is used when the client needs to block until the server responds
|
||||||
|
* the client doesnt nessesarily need to support asynchronously firing off commands
|
||||||
|
* in this shell-like interface it would be disconcerting to get multiple returns out of order
|
||||||
|
*/
|
||||||
|
private final ShellServerGrpc.ShellServerBlockingStub blockingStub;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
* Spawns a new blockingStub for network operations with the server
|
||||||
|
*/
|
||||||
|
public JobServClientAPIConnector(ManagedChannel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
blockingStub = ShellServerGrpc.newBlockingStub(this.channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* shutdown()
|
||||||
|
* Gets called when you press cntrl+c
|
||||||
|
* takes at most 5 seconds to close its connection
|
||||||
|
*/
|
||||||
|
public void shutdown() throws InterruptedException {
|
||||||
|
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getProcessOutput()
|
||||||
|
* sends the server a request for output from the process identified by 'pid'
|
||||||
|
* returns process output as string
|
||||||
|
*/
|
||||||
|
public String getProcessOutput(int pid, int lines) {
|
||||||
|
logger.info("[+] requesting output");
|
||||||
|
|
||||||
|
OutputRequestMessage request = OutputRequestMessage.newBuilder()
|
||||||
|
.setPid(pid)
|
||||||
|
.setLines(lines)
|
||||||
|
.build();
|
||||||
|
OutputMessage response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// blocking network operation
|
||||||
|
response = blockingStub.getOutput(request);
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
logger.log(Level.WARNING, this.apiFailureMessage + ": " + e.getStatus());
|
||||||
|
return "<Error connecting to API>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sendNewJobMessage()
|
||||||
|
* sends a shell command to the api server
|
||||||
|
* returns new pid of job
|
||||||
|
* or -1 if server failed to create job
|
||||||
|
* or -2 if client fails to connect
|
||||||
|
*/
|
||||||
|
public int sendNewJobMessage(String command) {
|
||||||
|
// thought of escaping this, but the vulnerability is only client side, from client user input.
|
||||||
|
logger.info("[+] Sending command to server");
|
||||||
|
|
||||||
|
NewJobMessage request = NewJobMessage.newBuilder()
|
||||||
|
.setCommand(command)
|
||||||
|
.build();
|
||||||
|
PIDMessage response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// blocking network operation
|
||||||
|
response = blockingStub.newJob(request);
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
logger.log(Level.WARNING, this.apiFailureMessage + ": " + e.getStatus());
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getPid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getProcessStatus()
|
||||||
|
* requests running status of process pid
|
||||||
|
* 0: running
|
||||||
|
* 1: not running
|
||||||
|
* 2: killed manually by a client
|
||||||
|
* 3: doesnt exist
|
||||||
|
* 4: couldnt grab lock
|
||||||
|
*/
|
||||||
|
public int getProcessStatus(int pid) {
|
||||||
|
logger.info("[+] Requesting status of a job");
|
||||||
|
|
||||||
|
PIDMessage request = PIDMessage.newBuilder()
|
||||||
|
.setPid(pid)
|
||||||
|
.build();
|
||||||
|
StatusMessage response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// blocking network operation
|
||||||
|
response = blockingStub.getStatus(request);
|
||||||
|
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
logger.log(Level.WARNING, this.apiFailureMessage + ": " + e.getStatus());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getProcessStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* sends PID to server
|
||||||
|
* returns process exit code
|
||||||
|
* 0-255: process exit code
|
||||||
|
* 256: process still running
|
||||||
|
* 257: process was killed by a client
|
||||||
|
* 258: process doesnt exist
|
||||||
|
* 259: couldnt grab lock in time
|
||||||
|
* 260: couldnt connect to API
|
||||||
|
*/
|
||||||
|
public int getProcessReturn(int pid) {
|
||||||
|
logger.info("[+] Requesting return code of a job");
|
||||||
|
|
||||||
|
PIDMessage request = PIDMessage.newBuilder()
|
||||||
|
.setPid(pid)
|
||||||
|
.build();
|
||||||
|
ReturnMessage response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// blocking network operation
|
||||||
|
response = blockingStub.getReturn(request);
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
logger.log(Level.WARNING, this.apiFailureMessage + ": " + e.getStatus());
|
||||||
|
return 260;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getProcessReturnCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* killProcess()
|
||||||
|
* send a PID to be killed, function returns process status after kill operation
|
||||||
|
* returns 0 if still running
|
||||||
|
* returns 1 if process was killed
|
||||||
|
* returns 2 if process not found
|
||||||
|
* returns 3 if couldnt grab lock
|
||||||
|
* returns 4 on API failure
|
||||||
|
*/
|
||||||
|
public int killProcess(int pid) {
|
||||||
|
logger.info("[+] Killing a job");
|
||||||
|
|
||||||
|
PIDMessage request = PIDMessage.newBuilder()
|
||||||
|
.setPid(pid)
|
||||||
|
.build();
|
||||||
|
StatusMessage response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// blocking network operation
|
||||||
|
response = blockingStub.killJob(request);
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
logger.log(Level.WARNING, this.apiFailureMessage + ": " + e.getStatus());
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.getProcessStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/main/java/JobServ/JobServServer.java
Normal file
132
src/main/java/JobServ/JobServServer.java
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
/*
|
||||||
|
* JobServServer
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 18, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import io.grpc.Server;
|
||||||
|
import io.grpc.ServerBuilder;
|
||||||
|
import io.grpc.netty.GrpcSslContexts;
|
||||||
|
import io.grpc.netty.NettyServerBuilder;
|
||||||
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import io.netty.handler.ssl.SslProvider;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import java.util.InputMismatchException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The JobServServer class implements the JobServ protobuf API
|
||||||
|
* It does this by extending the gRPC stub code.
|
||||||
|
* Additionally, JobServServer starts and manages a daemon
|
||||||
|
* Which accepts incoming connections from client.
|
||||||
|
*/
|
||||||
|
public class JobServServer {
|
||||||
|
public static SimpleLogger logger = new SimpleLogger("JobServ-Server-");
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private ProcessManager manager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
* builds server object
|
||||||
|
*/
|
||||||
|
public JobServServer(SslContext ssl, int port) throws IOException {
|
||||||
|
this.manager = new ProcessManager();
|
||||||
|
this.server = NettyServerBuilder.forPort(port)
|
||||||
|
.addService(new ShellServerService(manager))
|
||||||
|
.sslContext(ssl)
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* start()
|
||||||
|
* this initializes the server
|
||||||
|
*/
|
||||||
|
private void start() throws IOException {
|
||||||
|
// TODO: this should be passed in from a configuration manager
|
||||||
|
server.start();
|
||||||
|
logger.write("Server initialized!");
|
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
logger.write("Shutting down server");
|
||||||
|
logger.shutdown();
|
||||||
|
manager.shutdown();
|
||||||
|
JobServServer.this.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* stop()
|
||||||
|
* This is called when ctrl+c is pressed
|
||||||
|
*/
|
||||||
|
private void stop() {
|
||||||
|
if (server != null) {
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* blockUntilShutdown()
|
||||||
|
* This is more or less the main loop of the server.
|
||||||
|
* It spins until shutdown is called.
|
||||||
|
*/
|
||||||
|
private void blockUntilShutdown() throws InterruptedException {
|
||||||
|
if (server != null) {
|
||||||
|
server.awaitTermination();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* main()
|
||||||
|
* Entrypoint of hte server
|
||||||
|
* parses args and initializes a server object.
|
||||||
|
* calls server main loop.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
// TODO: port and key/cert files should be handled by a config manager
|
||||||
|
if(args.length < 4) {
|
||||||
|
System.out.println("Usage: ./jobserv-server port cert privatekey truststore");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobServServer server;
|
||||||
|
|
||||||
|
try {
|
||||||
|
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(new File(args[1]), new File(args[2]));
|
||||||
|
|
||||||
|
// Mutual TLS trust store and require client auth
|
||||||
|
sslContextBuilder.trustManager(new File(args[3]));
|
||||||
|
sslContextBuilder.clientAuth(ClientAuth.REQUIRE);
|
||||||
|
|
||||||
|
server = new JobServServer(GrpcSslContexts.configure(sslContextBuilder).build(),
|
||||||
|
Integer.parseInt(args[0]));
|
||||||
|
|
||||||
|
} catch (InputMismatchException e) {
|
||||||
|
System.out.println("Invalid port!");
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (SSLException e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JobServServer.logger.write("Initialized JobServ Server");
|
||||||
|
server.blockUntilShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/main/java/JobServ/ProcessController.java
Normal file
152
src/main/java/JobServ/ProcessController.java
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* ProcessController
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 22, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ProcessController
|
||||||
|
* This class wraps a java Process object with metadata
|
||||||
|
* such as translated PID that exist for this specific API
|
||||||
|
* as well as general metadata like IO streams.
|
||||||
|
*/
|
||||||
|
class ProcessController {
|
||||||
|
// incremented in constructor
|
||||||
|
private static int nextPid = 0;
|
||||||
|
private int pid;
|
||||||
|
|
||||||
|
// TODO: add an api endpoint for streaming client input into
|
||||||
|
// interactive processes (out of scope for initial API)
|
||||||
|
private OutputStream output;
|
||||||
|
private InputStream input;
|
||||||
|
private InputStreamReader inputIntermediateStream;
|
||||||
|
private BufferedReader reader;
|
||||||
|
|
||||||
|
private Process process;
|
||||||
|
|
||||||
|
private Boolean killedManually = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
* Takes a command and spawns it in a new process
|
||||||
|
* Redirects IO streams and assigns a fake PID
|
||||||
|
*/
|
||||||
|
public ProcessController(String command) throws IOException {
|
||||||
|
this.pid = ProcessController.nextPid;
|
||||||
|
ProcessController.nextPid += 1;
|
||||||
|
|
||||||
|
this.process = Runtime.getRuntime().exec(command);
|
||||||
|
this.output = this.process.getOutputStream();
|
||||||
|
this.input = this.process.getInputStream();
|
||||||
|
this.inputIntermediateStream = new InputStreamReader(this.input);
|
||||||
|
this.reader = new BufferedReader(this.inputIntermediateStream);
|
||||||
|
|
||||||
|
JobServServer.logger.write("Job " + String.valueOf(this.pid) + ": " + command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getPid()
|
||||||
|
* returns translated pid of this process
|
||||||
|
*/
|
||||||
|
public int getPid() {
|
||||||
|
return this.pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStatus()
|
||||||
|
* returns whether or not the process is running
|
||||||
|
*
|
||||||
|
* TODO: (for future release) return thread state
|
||||||
|
*/
|
||||||
|
public int getStatus() {
|
||||||
|
if (this.killedManually) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
process.exitValue();
|
||||||
|
return 1;
|
||||||
|
} catch (IllegalThreadStateException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getReturn()
|
||||||
|
* returns the exit code of the process
|
||||||
|
* 256 if process is still running
|
||||||
|
* 257 if process was killed manually and no longer exists
|
||||||
|
* (unix/posix defines an exit code as a uint8, so 256+ is fair game)
|
||||||
|
*/
|
||||||
|
public int getReturn() {
|
||||||
|
if (this.killedManually) {
|
||||||
|
return 257;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return process.exitValue();
|
||||||
|
} catch (IllegalThreadStateException e) {
|
||||||
|
return 256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getOutput()
|
||||||
|
* gets output from process
|
||||||
|
*/
|
||||||
|
public String getOutput(int lines) {
|
||||||
|
if(this.killedManually) {
|
||||||
|
return "[-] SERVER: Process has already been killed by a JobServ client!";
|
||||||
|
}
|
||||||
|
|
||||||
|
String output = "";
|
||||||
|
for (int i = 0; i < lines; i++) {
|
||||||
|
String newLine = null;
|
||||||
|
try {
|
||||||
|
newLine = reader.readLine();
|
||||||
|
} catch (IOException e) {
|
||||||
|
newLine = "[-] SERVER: error reading process output: " + e.getMessage();
|
||||||
|
} finally {
|
||||||
|
if (newLine != null) {
|
||||||
|
output += newLine + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* kill()
|
||||||
|
* Cleans up resources and destroys process
|
||||||
|
*/
|
||||||
|
public void kill() {
|
||||||
|
if (this.killedManually) {
|
||||||
|
JobServServer.logger.write("Tried to kill already killed process");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.input.close();
|
||||||
|
this.output.close();
|
||||||
|
this.inputIntermediateStream.close();
|
||||||
|
this.reader.close();
|
||||||
|
this.process.destroy();
|
||||||
|
this.killedManually = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
JobServServer.logger.write("Killing process " +
|
||||||
|
String.valueOf(this.pid) + " failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/main/java/JobServ/ProcessManager.java
Normal file
274
src/main/java/JobServ/ProcessManager.java
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
/*
|
||||||
|
* ProcessManager
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 22, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
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.ConcurrentHashMap;
|
||||||
|
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
|
||||||
|
* Mutex Timeout is declared here as well.
|
||||||
|
*/
|
||||||
|
class ProcessManager {
|
||||||
|
// TODO: LOCK_TIMEOUT should be defined in a configuration management system
|
||||||
|
private final int LOCK_TIMEOUT = 2; // seconds
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The significance of the concurrent hash map is that an in process
|
||||||
|
* update will not leave it in an unusable state like it will a normal
|
||||||
|
* HashMap. It is still up to the programmer in this instance to make
|
||||||
|
* sure that there are no concurrent operations done to the ProcessControllers
|
||||||
|
* Themselves. The last thing we want is to throw NPEs or whatnot when
|
||||||
|
* accessing a process destroyed mid read by another thread.
|
||||||
|
* Hence getLock(...) and lockMap controlling access to individual entries in
|
||||||
|
* processMap
|
||||||
|
*/
|
||||||
|
protected ConcurrentHashMap<Integer, ProcessController> processMap;
|
||||||
|
protected ConcurrentHashMap<Integer, Boolean> lockMap;
|
||||||
|
private ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
* initializes process queue and start the background process checking daemon
|
||||||
|
*/
|
||||||
|
public ProcessManager() {
|
||||||
|
processMap = new ConcurrentHashMap<Integer, ProcessController>();
|
||||||
|
lockMap = new ConcurrentHashMap<Integer, Boolean>();
|
||||||
|
/* TODO: In a long running server over a large period of time
|
||||||
|
* It is possible that the streams used to redirect IO in the
|
||||||
|
* Processes may become a significant use of resources.
|
||||||
|
* In this case a background thread should be called to periodically
|
||||||
|
* remove dead ProcessControllers after calling kill() on them.
|
||||||
|
*
|
||||||
|
* (grab lock, iterate over map, remove finished processes, store exit codes, release lock, sleep, repeat)
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* newProcess()
|
||||||
|
* Takes a command and returns the translated pid of a new process
|
||||||
|
* Returns -1 if controller throws an IOException
|
||||||
|
*/
|
||||||
|
public int newProcess(String command) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
ProcessController newProc = new ProcessController(command);
|
||||||
|
// we dont need to lock the map yet
|
||||||
|
this.lockMap.put(newProc.getPid(), true);
|
||||||
|
this.processMap.put(newProc.getPid(), newProc);
|
||||||
|
|
||||||
|
this.releaseLock(newProc.getPid());
|
||||||
|
return newProc.getPid();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
JobServServer.logger.write("Couldnt Spawn New Command: (" +
|
||||||
|
command + "): " + e.getMessage());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getProcessStatus()
|
||||||
|
* returns whether or not a process is running.
|
||||||
|
* 0: running
|
||||||
|
* 1: not running
|
||||||
|
* 2: killed manually by a client
|
||||||
|
* 3: doesnt exist
|
||||||
|
* 4: couldnt grab lock
|
||||||
|
*/
|
||||||
|
public int getProcessStatus(int pid) {
|
||||||
|
try {
|
||||||
|
if(!this.getLock(pid)) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
// lock could not be grabbed before timeout
|
||||||
|
JobServServer.logger.write("Timeout getting process " +
|
||||||
|
String.valueOf(pid) + " status: " + e.getMessage());
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessController candidate = this.processMap.get(pid);
|
||||||
|
int status = candidate.getStatus();
|
||||||
|
this.releaseLock(pid);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getProcessReturn()
|
||||||
|
* returns:
|
||||||
|
* 0-255: process exit code
|
||||||
|
* 256: process still running
|
||||||
|
* 257: process was killed by a client (TODO: list which client connection killed a process)
|
||||||
|
* 258: process doesnt exist
|
||||||
|
* 259: couldnt grab lock in time
|
||||||
|
*/
|
||||||
|
public int getProcessReturn(int pid) {
|
||||||
|
try {
|
||||||
|
if(!this.getLock(pid)) {
|
||||||
|
return 258;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
JobServServer.logger.write("Timeout getting process " +
|
||||||
|
String.valueOf(pid) + " return: " + e.getMessage());
|
||||||
|
return 259;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessController candidate = this.processMap.get(pid);
|
||||||
|
int ret = candidate.getReturn();
|
||||||
|
this.releaseLock(pid);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getProcessOutput()
|
||||||
|
* returns output of process 'pid'
|
||||||
|
* or returns description of error
|
||||||
|
*/
|
||||||
|
public String getProcessOutput(int pid, int lines) {
|
||||||
|
try {
|
||||||
|
if(!this.getLock(pid)) {
|
||||||
|
return "[-] SERVER: Process not found";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
JobServServer.logger.write("Timeout getting process " +
|
||||||
|
String.valueOf(pid) + " output: " + e.getMessage());
|
||||||
|
return "[-] SERVER: Timeout grabbing lock to access process information";
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessController candidate = this.processMap.get(pid);
|
||||||
|
String output = candidate.getOutput(lines);
|
||||||
|
this.releaseLock(pid);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* killProcess()
|
||||||
|
* returns mirror processStatus
|
||||||
|
* returns 0 if still running
|
||||||
|
* returns 1 if process was killed
|
||||||
|
* returns 2 if process not found
|
||||||
|
* returns 3 if couldnt grab lock
|
||||||
|
*/
|
||||||
|
public int killProcess(int pid) {
|
||||||
|
try {
|
||||||
|
if(!this.getLock(pid)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
JobServServer.logger.write("Timeout killing process " +
|
||||||
|
String.valueOf(pid) + ": " + e.getMessage());
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessController candidate = this.processMap.get(pid);
|
||||||
|
candidate.kill();
|
||||||
|
this.releaseLock(pid);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getLock()
|
||||||
|
* Locks access to this.processQueue
|
||||||
|
* Waits for a predefined timeout period for mutex to be avail.
|
||||||
|
* Synchronized so two things cannot grab lock at once.
|
||||||
|
* Throws TimeoutException when it fails to get the lock.
|
||||||
|
* Alternatively, throws false if lock doesnt exist for PID
|
||||||
|
* Function is synchronized to prevent multiple threads accessing the same lock at once
|
||||||
|
* (ConcurrentHashMap will report whatever lock value was last to successfully update)
|
||||||
|
*/
|
||||||
|
protected synchronized Boolean getLock(int pid) throws TimeoutException {
|
||||||
|
if (!lockMap.containsKey(pid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Object> future = this.threadPool.submit(
|
||||||
|
new Callable<Object>() {
|
||||||
|
public Object call() {
|
||||||
|
while(lockMap.get(pid)) {
|
||||||
|
continue; // spin!
|
||||||
|
}
|
||||||
|
|
||||||
|
lockMap.replace(pid, true);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
future.get(this.LOCK_TIMEOUT, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
JobServServer.logger.write("[!] Couldnt get lock " +
|
||||||
|
String.valueOf(pid) + ": "+ e.getMessage());
|
||||||
|
future.cancel(true);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
JobServServer.logger.write("[!] Couldnt get lock " +
|
||||||
|
String.valueOf(pid) + ": "+ e.getMessage());
|
||||||
|
future.cancel(true);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// cancel the attempt to grab the lock
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: touch of tech debt here
|
||||||
|
* There should honestly be an
|
||||||
|
* operation retry queue for ops
|
||||||
|
* That dont get the lock in time.
|
||||||
|
*
|
||||||
|
* This would require a scheduler
|
||||||
|
* that manages a queue of callbacks
|
||||||
|
* This scheduler would also likely
|
||||||
|
* mediate access to the ProcessManager
|
||||||
|
* object for fresh calls as well.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* releaseLock()
|
||||||
|
* releases mutex so other threads can operate on processqueue
|
||||||
|
*/
|
||||||
|
protected void releaseLock(int pid) {
|
||||||
|
this.lockMap.put(pid, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* shutdown()
|
||||||
|
* called (eventually) by the grpc shutdown hook
|
||||||
|
* (AKA when user hits control c in the shell)
|
||||||
|
* releases resources held in the processController objects
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/main/java/JobServ/ShellServerService.java
Normal file
119
src/main/java/JobServ/ShellServerService.java
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* ShellServerService
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 18, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ShellServerService wraps around the protobuf API
|
||||||
|
* Implements API endpoints
|
||||||
|
*/
|
||||||
|
class ShellServerService extends ShellServerGrpc.ShellServerImplBase {
|
||||||
|
|
||||||
|
private ProcessManager manager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* constructor
|
||||||
|
* initialized ProcessManager
|
||||||
|
*/
|
||||||
|
public ShellServerService(ProcessManager manager) {
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStatus
|
||||||
|
* implements api endpoint as defined in jobserv.proto
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void getStatus(PIDMessage request,
|
||||||
|
StreamObserver<StatusMessage> responder) {
|
||||||
|
|
||||||
|
JobServServer.logger.write("New status request for pid: " + String.valueOf(request.getPid()));
|
||||||
|
int status = manager.getProcessStatus(request.getPid());
|
||||||
|
|
||||||
|
StatusMessage reply = StatusMessage.newBuilder()
|
||||||
|
.setProcessStatus(status)
|
||||||
|
.build();
|
||||||
|
responder.onNext(reply);
|
||||||
|
responder.onCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getOutput
|
||||||
|
* implements api endpoint as defined in jobserv.proto
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void getOutput(OutputRequestMessage request,
|
||||||
|
StreamObserver<OutputMessage> responder) {
|
||||||
|
|
||||||
|
JobServServer.logger.write("New Output request for pid: " + String.valueOf(request.getPid()));
|
||||||
|
String output = manager.getProcessOutput(request.getPid(),
|
||||||
|
request.getLines());
|
||||||
|
|
||||||
|
OutputMessage reply = OutputMessage.newBuilder()
|
||||||
|
.setOutput(output)
|
||||||
|
.build();
|
||||||
|
responder.onNext(reply);
|
||||||
|
responder.onCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* newJob
|
||||||
|
* implements api endpoint as defined in jobserv.proto
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void newJob(NewJobMessage request,
|
||||||
|
StreamObserver<PIDMessage> responder) {
|
||||||
|
|
||||||
|
String command = request.getCommand();
|
||||||
|
JobServServer.logger.write("New job request: " + command);
|
||||||
|
int newPid = manager.newProcess(command);
|
||||||
|
|
||||||
|
PIDMessage reply = PIDMessage.newBuilder()
|
||||||
|
.setPid(newPid)
|
||||||
|
.build();
|
||||||
|
responder.onNext(reply);
|
||||||
|
responder.onCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getReturn
|
||||||
|
* implements api endpoint as defined in jobserv.proto
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void getReturn(PIDMessage request,
|
||||||
|
StreamObserver<ReturnMessage> responder) {
|
||||||
|
|
||||||
|
JobServServer.logger.write("New request for return from job: " + String.valueOf(request.getPid()));
|
||||||
|
int retVal = manager.getProcessReturn(request.getPid());
|
||||||
|
|
||||||
|
ReturnMessage reply = ReturnMessage.newBuilder()
|
||||||
|
.setProcessReturnCode(retVal)
|
||||||
|
.build();
|
||||||
|
responder.onNext(reply);
|
||||||
|
responder.onCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* killJob
|
||||||
|
* implements api endpoint as defined in jobserv.proto
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void killJob(PIDMessage request,
|
||||||
|
StreamObserver<StatusMessage> responder) {
|
||||||
|
|
||||||
|
JobServServer.logger.write("New Request to kill job: " + String.valueOf(request.getPid()));
|
||||||
|
int status = manager.killProcess(request.getPid());
|
||||||
|
|
||||||
|
StatusMessage reply = StatusMessage.newBuilder()
|
||||||
|
.setProcessStatus(status)
|
||||||
|
.build();
|
||||||
|
responder.onNext(reply);
|
||||||
|
responder.onCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/JobServ/SimpleLogger.java
Normal file
85
src/main/java/JobServ/SimpleLogger.java
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* SimpleLogger
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 26, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SimpleLogger
|
||||||
|
* Automatically manages the creation of and output to a log file
|
||||||
|
* TODO: Log Levels, decorations for entries of different severity
|
||||||
|
*/
|
||||||
|
class SimpleLogger {
|
||||||
|
private static final SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss");
|
||||||
|
private Timestamp programStart;
|
||||||
|
private FileWriter logWriter;
|
||||||
|
private Boolean writable = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
* Initializes timestamp and opens new file for logging
|
||||||
|
*/
|
||||||
|
public SimpleLogger(String filePrefix) {
|
||||||
|
this.programStart = new Timestamp(System.currentTimeMillis());
|
||||||
|
File currentLog = new File(filePrefix + this.dateTimeFormat.format(this.programStart));
|
||||||
|
|
||||||
|
try{
|
||||||
|
this.logWriter = new FileWriter(currentLog, true);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println("Error creating LogWriter!");
|
||||||
|
this.writable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.write(this.programStart.toString() + ": JobServ Logging Started");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* write
|
||||||
|
* appends a line of information to the log
|
||||||
|
*/
|
||||||
|
public void write(String message) {
|
||||||
|
Timestamp currentTime = new Timestamp(System.currentTimeMillis());
|
||||||
|
message = currentTime.toString() + "> " + message;
|
||||||
|
|
||||||
|
if (this.writable) {
|
||||||
|
try {
|
||||||
|
this.logWriter.write(message + "\n");
|
||||||
|
this.logWriter.flush();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
this.writable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* shutdown()
|
||||||
|
* called on server exit, closes the FileWriter and frees its resources
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
Timestamp exitTime = new Timestamp(System.currentTimeMillis());
|
||||||
|
this.write(exitTime.toString() + ": JobServ Logging Stopped");
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.logWriter.close();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
// not sure what would be appropriate to do here
|
||||||
|
System.out.println(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/main/proto/jobserv.proto
Normal file
41
src/main/proto/jobserv.proto
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "JobServ";
|
||||||
|
option java_outer_classname = "JobServGrpc";
|
||||||
|
option objc_class_prefix = "JSV";
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
service ShellServer {
|
||||||
|
rpc getStatus (PIDMessage) returns (StatusMessage) {}
|
||||||
|
rpc getReturn (PIDMessage) returns (ReturnMessage) {}
|
||||||
|
rpc getOutput (OutputRequestMessage) returns (OutputMessage) {}
|
||||||
|
rpc killJob (PIDMessage) returns (StatusMessage) {}
|
||||||
|
rpc newJob (NewJobMessage) returns (PIDMessage) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatusMessage {
|
||||||
|
int32 ProcessStatus = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReturnMessage {
|
||||||
|
int32 ProcessReturnCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OutputRequestMessage {
|
||||||
|
int32 Pid = 1;
|
||||||
|
int32 Lines = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OutputMessage {
|
||||||
|
string Output = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NewJobMessage {
|
||||||
|
string Command = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PIDMessage {
|
||||||
|
int32 Pid = 1;
|
||||||
|
}
|
||||||
154
src/test/java/JobServ/JobServerAuthenticationTest.java
Normal file
154
src/test/java/JobServ/JobServerAuthenticationTest.java
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* JobServerAuthenticationTest
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 21, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.AdditionalAnswers.delegatesTo;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.netty.NettyChannelBuilder;
|
||||||
|
import io.grpc.StatusRuntimeException;
|
||||||
|
import io.grpc.ManagedChannelBuilder;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
import io.grpc.testing.GrpcCleanupRule;
|
||||||
|
import io.grpc.netty.GrpcSslContexts;
|
||||||
|
|
||||||
|
import io.netty.handler.ssl.ClientAuth;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import io.netty.handler.ssl.SslProvider;
|
||||||
|
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.ArgumentMatchers;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JobServerAuthenticationTest
|
||||||
|
* Creates a client using authorized certs and another one using unauthorized certs
|
||||||
|
* Ensures only the client with authorized certs can connect to the server.
|
||||||
|
* For more information on the hardcoded paths check buildwrapper.sh
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class JobServerAuthenticationTest {
|
||||||
|
|
||||||
|
private final String projectRoot = "";
|
||||||
|
|
||||||
|
// Authorized client key/cert/ca
|
||||||
|
private final String clientCa = projectRoot + "resources/client/ca.crt";
|
||||||
|
private final String clientKey = projectRoot + "resources/client/private.pem";
|
||||||
|
private final String clientCert = projectRoot + "resources/client/client.crt";
|
||||||
|
|
||||||
|
// Authorized server key/cert/ca
|
||||||
|
private final String serverCa = projectRoot + "resources/server/ca.crt";
|
||||||
|
private final String serverKey = projectRoot + "resources/server/private.pem";
|
||||||
|
private final String serverCert = projectRoot + "resources/server/server.crt";
|
||||||
|
|
||||||
|
// controlled failure key/cert/ca
|
||||||
|
private final String badCa = projectRoot + "resources/test/ca.crt";
|
||||||
|
private final String badKey = projectRoot + "resources/test/private.pem";
|
||||||
|
private final String badCert = projectRoot + "resources/test/test.crt";
|
||||||
|
|
||||||
|
// badClient uses unauthorized certs
|
||||||
|
private JobServClientAPIConnector goodClient;
|
||||||
|
private JobServClientAPIConnector badClient;
|
||||||
|
private JobServServer server;
|
||||||
|
|
||||||
|
// was setUp able to use SSL Certs
|
||||||
|
private Boolean serverSslInitialized = true;
|
||||||
|
private Boolean clientSslInitialized = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* test constructor
|
||||||
|
* generates both clients and the server
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
SslContextBuilder goodClientBuilder = GrpcSslContexts.forClient();
|
||||||
|
goodClientBuilder.trustManager(new File(serverCa));
|
||||||
|
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();
|
||||||
|
|
||||||
|
ManagedChannel badChannel = NettyChannelBuilder.forAddress("localhost", 8448)
|
||||||
|
.sslContext(badClientBuilder.build())
|
||||||
|
.directExecutor()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
goodClient = new JobServClientAPIConnector(goodChannel);
|
||||||
|
badClient = new JobServClientAPIConnector(badChannel);
|
||||||
|
this.clientSslInitialized = true;
|
||||||
|
|
||||||
|
} catch (SSLException e) {
|
||||||
|
this.clientSslInitialized = false;
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.clientSslInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TLS Cert Auth Test
|
||||||
|
* this needed to be one test because running multiple tests at the same time
|
||||||
|
* fails as the server tries to rebind to the same port.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void certificateAuthenticationTest() {
|
||||||
|
assertEquals(true, serverSslInitialized);
|
||||||
|
assertEquals(true, clientSslInitialized);
|
||||||
|
|
||||||
|
int result = badClient.sendNewJobMessage("test command");
|
||||||
|
assertEquals(-3, result);
|
||||||
|
|
||||||
|
result = goodClient.sendNewJobMessage("test command");
|
||||||
|
Boolean assertCondition = result == -3;
|
||||||
|
assertEquals(assertCondition, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
228
src/test/java/JobServ/ProcessManagerTest.java
Normal file
228
src/test/java/JobServ/ProcessManagerTest.java
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* 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.assertTrue;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
private ProcessManagerTestImplementation manager = new ProcessManagerTestImplementation();
|
||||||
|
private ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
private int asyncTestPid;
|
||||||
|
|
||||||
|
// calls a test function that simulates load by holding the lock for a long time
|
||||||
|
private Callable<Object> holdLockFourSeconds = new Callable<Object>() {
|
||||||
|
public Object call() {
|
||||||
|
manager.longCallHoldsLock(asyncTestPid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* addProcessTest()
|
||||||
|
* positive unit test for newProcess
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void addProcessesTest() {
|
||||||
|
int pid = manager.newProcess("sleep 1");
|
||||||
|
assertNotEquals(-1, pid);
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getStatusTest
|
||||||
|
* unit test for getStatus
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getStatusTest() {
|
||||||
|
int pid = manager.newProcess("sleep 1");
|
||||||
|
int status = manager.getProcessStatus(pid);
|
||||||
|
assertEquals(0, status);
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getOldStatusTest
|
||||||
|
* do finished processes return 1
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getOldStatusTest() {
|
||||||
|
int pid = manager.newProcess("echo 'test'");
|
||||||
|
|
||||||
|
try{
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = manager.getProcessStatus(pid);
|
||||||
|
assertEquals(1, status);
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getUnknownStatusTest()
|
||||||
|
* ensures 2 is returned when a status is not known
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getUnknownStatusTest() {
|
||||||
|
int status = manager.getProcessStatus(400);
|
||||||
|
assertEquals(3, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getReturnTest()
|
||||||
|
* test of process returns
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getReturnTest() {
|
||||||
|
int pid = manager.newProcess("sleep .5");
|
||||||
|
int ret = manager.getProcessReturn(pid);
|
||||||
|
assertEquals(256, ret);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(550);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = manager.getProcessReturn(pid);
|
||||||
|
assertNotEquals(ret, 256);
|
||||||
|
assertNotEquals(ret, 257);
|
||||||
|
assertNotEquals(ret, 258);
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getUNknownProcessReturn
|
||||||
|
* tests process return for unknown processes
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getUnknownProcessReturnTest() {
|
||||||
|
int ret = manager.getProcessReturn(502);
|
||||||
|
assertEquals(258, ret);
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getProcessOutputTest()
|
||||||
|
* verifies output is grabbed correctly from processes
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getProcessOutputTest() {
|
||||||
|
int pid = manager.newProcess("echo test");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
String out = manager.getProcessOutput(pid, 2);
|
||||||
|
assertEquals("test\n", out); // calls string.equals()
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getUnknownOutputTest()
|
||||||
|
* verifies correct information is returned when
|
||||||
|
* output is requested of an unknown process
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getUnknownOutputTest() {
|
||||||
|
String out = manager.getProcessOutput(532, 10);
|
||||||
|
assertEquals("[-] SERVER: Process not found", out);
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* killProcessTest()
|
||||||
|
* ensures killing a process works
|
||||||
|
* also tests if getProcessStatus returns 2
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void killProcessTest() {
|
||||||
|
int pid = manager.newProcess("sleep 10");
|
||||||
|
int ret = manager.killProcess(pid);
|
||||||
|
|
||||||
|
assertEquals(1, ret);
|
||||||
|
|
||||||
|
int status = manager.getProcessStatus(pid);
|
||||||
|
|
||||||
|
assertEquals(2, status);
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* asyncLockTimeoutTest
|
||||||
|
* ensures that two things cannot grab the lock at the same time
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void asyncLockTimeoutTest() {
|
||||||
|
// start new process that will last the whole test
|
||||||
|
asyncTestPid = this.manager.newProcess("sleep 7");
|
||||||
|
int secondProcess = this.manager.newProcess("sleep 10");
|
||||||
|
|
||||||
|
// grab that processes lock for 4 seconds
|
||||||
|
Future<Object> future = this.threadPool.submit(this.holdLockFourSeconds);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println("[!!] Thread for async test interrupted!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to grab a held lock
|
||||||
|
System.err.println("[2] attempting to grab (held) lock");
|
||||||
|
int status = this.manager.getProcessStatus(this.asyncTestPid);
|
||||||
|
assertEquals(4, status); // should time out after 2 secs
|
||||||
|
|
||||||
|
// try to grab unrelated lock (not nessesary, but important it works)
|
||||||
|
int statusTertiary = this.manager.getProcessStatus(secondProcess);
|
||||||
|
assertNotEquals(4, statusTertiary);
|
||||||
|
|
||||||
|
// give lockMap small time to update
|
||||||
|
try {
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println("[!!] Thread for async test interrupted!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// should be grabbable now
|
||||||
|
int statusSecondTry = this.manager.getProcessStatus(this.asyncTestPid);
|
||||||
|
assertNotEquals(4, statusSecondTry);
|
||||||
|
|
||||||
|
manager.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
44
src/test/java/JobServ/ProcessManagerTestImplementation.java
Normal file
44
src/test/java/JobServ/ProcessManagerTestImplementation.java
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* ProcessManagerTestImplementation
|
||||||
|
*
|
||||||
|
* v1.0
|
||||||
|
*
|
||||||
|
* May 23, 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package JobServ;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ProcessManagerTestImplementation
|
||||||
|
* inherits ProcessManager and adds useful functions for testing
|
||||||
|
*/
|
||||||
|
class ProcessManagerTestImplementation extends ProcessManager {
|
||||||
|
|
||||||
|
public void longCallHoldsLock(int pid) {
|
||||||
|
try {
|
||||||
|
super.getLock(pid);
|
||||||
|
System.err.println("[1] Long Call Has Lock");
|
||||||
|
|
||||||
|
// hold lock for 3.5 seconds, more than double normal timeout.
|
||||||
|
Thread.sleep(3500);
|
||||||
|
|
||||||
|
super.releaseLock(pid);
|
||||||
|
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
System.err.println("[!!] Long Call wasnt able to grab lock!");
|
||||||
|
return;
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
super.releaseLock(pid); // this doesnt happen, dont cancel this task
|
||||||
|
System.err.println("[3] Released lock: interrupted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean reportLockState(int pid) {
|
||||||
|
return super.lockMap.get(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue