blob: a2539950be700b61b4ccdd0a8ec1c0e3976247ca [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.buckdaemon;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import org.onosproject.checkstyle.CheckstyleRunner;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.WRITE;
/**
* Buck daemon process.
*/
public final class BuckDaemon {
private static final long POLLING_INTERVAL = 1000; //ms
private final Map<String, BuckTask> tasks = new HashMap<>();
private final String portLock;
private final String buckPid;
// Public construction forbidden
private BuckDaemon(String[] args) {
portLock = args[0];
buckPid = args[1];
}
/**
* Main entry point for the daemon.
*
* @param args command-line arguments
* @throws CheckstyleException on checkstyle error
* @throws IOException on I/O error
*/
public static void main(String[] args)
throws CheckstyleException, IOException {
BuckDaemon daemon = new BuckDaemon(args);
daemon.registerTasks();
daemon.startServer();
}
/**
* Registers re-entrant tasks by their task name.
*/
private void registerTasks() {
tasks.put("checkstyle", new CheckstyleRunner(System.getProperty("checkstyle.config"),
System.getProperty("checkstyle.suppressions")));
// tasks.put("swagger", new SwaggerGenerator());
}
/**
* Monitors another PID and exit when that process exits.
*/
private void watchProcess(String pid) {
if (pid == null || pid.equals("0")) {
return;
}
Timer timer = new Timer(true); // start as a daemon, so we don't hang shutdown
timer.scheduleAtFixedRate(new TimerTask() {
private String cmd = "kill -s 0 " + pid;
@Override
public void run() {
try {
Process p = Runtime.getRuntime().exec(cmd);
p.waitFor();
if (p.exitValue() != 0) {
debug("shutting down...");
System.exit(0);
}
} catch (IOException | InterruptedException e) {
//no-op
e.printStackTrace();
}
}
}, POLLING_INTERVAL, POLLING_INTERVAL);
}
/**
* Initiates a server.
*/
private void startServer() throws IOException, CheckstyleException {
// Use a file lock to ensure only one copy of the daemon runs
Path portLockPath = Paths.get(portLock);
FileChannel channel = FileChannel.open(portLockPath, WRITE, CREATE);
FileLock lock = channel.tryLock();
if (lock == null) {
debug("Server is already running");
System.exit(1);
} //else, hold the lock until the JVM exits
// Start the server and bind it to a random port
ServerSocket server = new ServerSocket(0);
// Monitor the parent buck process
watchProcess(buckPid);
// Set up hook to clean up after ourselves
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
channel.truncate(0);
channel.close();
debug("tear down...");
Files.delete(portLockPath);
} catch (IOException e) {
//no-op: shutting down
e.printStackTrace();
}
}));
// Write the bound port to the port file
int port = server.getLocalPort();
channel.truncate(0);
channel.write(ByteBuffer.wrap(Integer.toString(port).getBytes()));
channel.force(false); // flush the port number to disk
// Instantiate a Checkstyle runner and executor; serve until exit...
ExecutorService executor = Executors.newCachedThreadPool();
while (true) {
try {
executor.submit(new BuckTaskRunner(server.accept()));
} catch (Exception e) {
e.printStackTrace();
//no-op
}
}
}
/**
* Runnable capable of invoking the appropriate Buck task with input
* consumed form the specified socket and output produced back to that
* socket.
*/
private class BuckTaskRunner implements Runnable {
private final Socket socket;
public BuckTaskRunner(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
try {
socket.setSoTimeout(1_000); //reads should time out after 1 second
BuckTaskContext context = BuckTaskContext.createBuckTaskContext(socket.getInputStream());
if (context == null) {
socket.close();
return;
}
String taskName = context.taskName();
BuckTask task = tasks.get(taskName);
if (task != null) {
debug(String.format("Executing task '%s'", taskName));
try {
task.execute(context);
for (String line : context.output()) {
send(socket, line);
}
// TODO should we catch Exception, RuntimeException, or something specific?
} catch (Throwable e) {
e.printStackTrace(new PrintStream(socket.getOutputStream()));
}
} else {
String message = String.format("No task named '%s'", taskName);
debug(message);
send(socket, message);
}
} catch (Throwable e) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
String stacktrace = writer.toString();
debug(stacktrace);
send(socket, stacktrace);
}
socket.getOutputStream().flush();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void send(Socket socket, String line) throws IOException {
socket.getOutputStream().write((line + "\n").getBytes());
}
private static void debug(String message) {
// no-op; print to System.out if needed
}
}