blob: ce8a78cf3a570111a32e9a02926b540fdc34ffa4 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* 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.google.common.io.ByteStreams;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import org.onosproject.checkstyle.CheckstyleRunner;
import java.io.IOException;
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.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static java.nio.file.StandardOpenOption.*;
/**
* Buck daemon process.
*/
public final class BuckDaemon {
private static 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
*/
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) {
System.err.println("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) {
System.out.println("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();
System.err.println("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()));
// 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 {
BuckTaskContext context = new BuckTaskContext(socket.getInputStream());
String taskName = context.taskName();
if (!taskName.isEmpty()) {
BuckTask task = tasks.get(taskName);
if (task != null) {
System.out.println(String.format("Executing task '%s'", taskName));
task.execute(context);
for (String line : context.output()) {
output(socket, line);
}
} else {
String message = String.format("No task named '%s'", taskName);
System.out.print(message);
output(socket, message);
}
}
socket.getOutputStream().flush();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void output(Socket socket, String line) throws IOException {
socket.getOutputStream().write((line + "\n").getBytes());
}
}
}