blob: 56deadd201a0b892e458792a1b7bdc1ae29248c0 [file] [log] [blame]
Thomas Vachuska275d2e82016-07-14 17:41:34 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Thomas Vachuska275d2e82016-07-14 17:41:34 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.buckdaemon;
18
Thomas Vachuska275d2e82016-07-14 17:41:34 -070019import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
20import org.onosproject.checkstyle.CheckstyleRunner;
21
22import java.io.IOException;
Ray Milkey8df94b82016-11-16 11:03:32 -080023import java.io.PrintStream;
Brian O'Connorbd0e7202017-11-27 18:40:08 -080024import java.io.PrintWriter;
25import java.io.StringWriter;
Thomas Vachuska275d2e82016-07-14 17:41:34 -070026import java.net.ServerSocket;
27import java.net.Socket;
28import java.nio.ByteBuffer;
29import java.nio.channels.FileChannel;
30import java.nio.channels.FileLock;
31import java.nio.file.Files;
32import java.nio.file.Path;
33import java.nio.file.Paths;
34import java.util.HashMap;
35import java.util.Map;
36import java.util.Timer;
37import java.util.TimerTask;
Thomas Vachuska275d2e82016-07-14 17:41:34 -070038import java.util.concurrent.ExecutorService;
39import java.util.concurrent.Executors;
40
Brian O'Connorbd0e7202017-11-27 18:40:08 -080041import static java.nio.file.StandardOpenOption.CREATE;
42import static java.nio.file.StandardOpenOption.WRITE;
Thomas Vachuska275d2e82016-07-14 17:41:34 -070043
44/**
45 * Buck daemon process.
46 */
47public final class BuckDaemon {
48
49 private static long POLLING_INTERVAL = 1000; //ms
50
51 private final Map<String, BuckTask> tasks = new HashMap<>();
52 private final String portLock;
53 private final String buckPid;
54
55 // Public construction forbidden
56 private BuckDaemon(String[] args) {
57 portLock = args[0];
58 buckPid = args[1];
59 }
60
61 /**
62 * Main entry point for the daemon.
63 *
64 * @param args command-line arguments
65 */
66 public static void main(String[] args)
67 throws CheckstyleException, IOException {
68 BuckDaemon daemon = new BuckDaemon(args);
69 daemon.registerTasks();
70 daemon.startServer();
71 }
72
73 /**
74 * Registers re-entrant tasks by their task name.
75 */
76 private void registerTasks() {
77 tasks.put("checkstyle", new CheckstyleRunner(System.getProperty("checkstyle.config"),
78 System.getProperty("checkstyle.suppressions")));
79 // tasks.put("swagger", new SwaggerGenerator());
80 }
81
82 /**
83 * Monitors another PID and exit when that process exits.
84 */
85 private void watchProcess(String pid) {
86 if (pid == null || pid.equals("0")) {
87 return;
88 }
89 Timer timer = new Timer(true); // start as a daemon, so we don't hang shutdown
90 timer.scheduleAtFixedRate(new TimerTask() {
91 private String cmd = "kill -s 0 " + pid;
92
93 @Override
94 public void run() {
95 try {
96 Process p = Runtime.getRuntime().exec(cmd);
97 p.waitFor();
98 if (p.exitValue() != 0) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -080099 debug("shutting down...");
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700100 System.exit(0);
101 }
102 } catch (IOException | InterruptedException e) {
103 //no-op
104 e.printStackTrace();
105 }
106 }
107 }, POLLING_INTERVAL, POLLING_INTERVAL);
108 }
109
110 /**
111 * Initiates a server.
112 */
113 private void startServer() throws IOException, CheckstyleException {
114 // Use a file lock to ensure only one copy of the daemon runs
115 Path portLockPath = Paths.get(portLock);
116 FileChannel channel = FileChannel.open(portLockPath, WRITE, CREATE);
117 FileLock lock = channel.tryLock();
118 if (lock == null) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800119 debug("Server is already running");
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700120 System.exit(1);
121 } //else, hold the lock until the JVM exits
122
123 // Start the server and bind it to a random port
124 ServerSocket server = new ServerSocket(0);
125
126 // Monitor the parent buck process
127 watchProcess(buckPid);
128
129 // Set up hook to clean up after ourselves
130 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
131 try {
132 channel.truncate(0);
133 channel.close();
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800134 debug("tear down...");
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700135 Files.delete(portLockPath);
136 } catch (IOException e) {
137 //no-op: shutting down
138 e.printStackTrace();
139 }
140 }));
141
142 // Write the bound port to the port file
143 int port = server.getLocalPort();
144 channel.truncate(0);
145 channel.write(ByteBuffer.wrap(Integer.toString(port).getBytes()));
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800146 channel.force(false); // flush the port number to disk
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700147
148 // Instantiate a Checkstyle runner and executor; serve until exit...
149 ExecutorService executor = Executors.newCachedThreadPool();
150 while (true) {
151 try {
152 executor.submit(new BuckTaskRunner(server.accept()));
153 } catch (Exception e) {
154 e.printStackTrace();
155 //no-op
156 }
157 }
158 }
159
160 /**
161 * Runnable capable of invoking the appropriate Buck task with input
162 * consumed form the specified socket and output produced back to that
163 * socket.
164 */
165 private class BuckTaskRunner implements Runnable {
166
167 private final Socket socket;
168
169 public BuckTaskRunner(Socket socket) {
170 this.socket = socket;
171 }
172
173 @Override
174 public void run() {
175 try {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800176 try {
177 socket.setSoTimeout(1_000); //reads should time out after 1 second
178 BuckTaskContext context = new BuckTaskContext(socket.getInputStream());
179
180 String taskName = context.taskName();
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700181 BuckTask task = tasks.get(taskName);
182 if (task != null) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800183 debug(String.format("Executing task '%s'", taskName));
Ray Milkey8df94b82016-11-16 11:03:32 -0800184 try {
185 task.execute(context);
186 for (String line : context.output()) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800187 send(socket, line);
Ray Milkey8df94b82016-11-16 11:03:32 -0800188 }
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800189 // TODO should we catch Exception, RuntimeException, or something specific?
Ray Milkey8df94b82016-11-16 11:03:32 -0800190 } catch (Throwable e) {
191 e.printStackTrace(new PrintStream(socket.getOutputStream()));
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700192 }
193 } else {
194 String message = String.format("No task named '%s'", taskName);
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800195 debug(message);
196 send(socket, message);
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700197 }
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800198 } catch (Throwable e) {
199 StringWriter writer = new StringWriter();
200 e.printStackTrace(new PrintWriter(writer));
201 String stacktrace = writer.toString();
202 debug(stacktrace);
203 send(socket, stacktrace);
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700204 }
205 socket.getOutputStream().flush();
206 socket.close();
207 } catch (IOException e) {
208 e.printStackTrace();
209 }
210 }
211
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800212 }
213
214 private static void send(Socket socket, String line) throws IOException {
215 socket.getOutputStream().write((line + "\n").getBytes());
216 }
217
218 private static void debug(String message) {
219 // no-op; print to System.out if needed
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700220 }
221}