blob: a2539950be700b61b4ccdd0a8ec1c0e3976247ca [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
Yuta HIGUCHI42cc1402018-05-21 12:08:03 -070049 private static final long POLLING_INTERVAL = 1000; //ms
Thomas Vachuska275d2e82016-07-14 17:41:34 -070050
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
Yuta HIGUCHI42cc1402018-05-21 12:08:03 -070065 * @throws CheckstyleException on checkstyle error
66 * @throws IOException on I/O error
Thomas Vachuska275d2e82016-07-14 17:41:34 -070067 */
68 public static void main(String[] args)
69 throws CheckstyleException, IOException {
70 BuckDaemon daemon = new BuckDaemon(args);
71 daemon.registerTasks();
72 daemon.startServer();
73 }
74
75 /**
76 * Registers re-entrant tasks by their task name.
77 */
78 private void registerTasks() {
79 tasks.put("checkstyle", new CheckstyleRunner(System.getProperty("checkstyle.config"),
80 System.getProperty("checkstyle.suppressions")));
81 // tasks.put("swagger", new SwaggerGenerator());
82 }
83
84 /**
85 * Monitors another PID and exit when that process exits.
86 */
87 private void watchProcess(String pid) {
88 if (pid == null || pid.equals("0")) {
89 return;
90 }
91 Timer timer = new Timer(true); // start as a daemon, so we don't hang shutdown
92 timer.scheduleAtFixedRate(new TimerTask() {
93 private String cmd = "kill -s 0 " + pid;
94
95 @Override
96 public void run() {
97 try {
98 Process p = Runtime.getRuntime().exec(cmd);
99 p.waitFor();
100 if (p.exitValue() != 0) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800101 debug("shutting down...");
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700102 System.exit(0);
103 }
104 } catch (IOException | InterruptedException e) {
105 //no-op
106 e.printStackTrace();
107 }
108 }
109 }, POLLING_INTERVAL, POLLING_INTERVAL);
110 }
111
112 /**
113 * Initiates a server.
114 */
115 private void startServer() throws IOException, CheckstyleException {
116 // Use a file lock to ensure only one copy of the daemon runs
117 Path portLockPath = Paths.get(portLock);
118 FileChannel channel = FileChannel.open(portLockPath, WRITE, CREATE);
119 FileLock lock = channel.tryLock();
120 if (lock == null) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800121 debug("Server is already running");
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700122 System.exit(1);
123 } //else, hold the lock until the JVM exits
124
125 // Start the server and bind it to a random port
126 ServerSocket server = new ServerSocket(0);
127
128 // Monitor the parent buck process
129 watchProcess(buckPid);
130
131 // Set up hook to clean up after ourselves
132 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
133 try {
134 channel.truncate(0);
135 channel.close();
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800136 debug("tear down...");
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700137 Files.delete(portLockPath);
138 } catch (IOException e) {
139 //no-op: shutting down
140 e.printStackTrace();
141 }
142 }));
143
144 // Write the bound port to the port file
145 int port = server.getLocalPort();
146 channel.truncate(0);
147 channel.write(ByteBuffer.wrap(Integer.toString(port).getBytes()));
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800148 channel.force(false); // flush the port number to disk
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700149
150 // Instantiate a Checkstyle runner and executor; serve until exit...
151 ExecutorService executor = Executors.newCachedThreadPool();
152 while (true) {
153 try {
154 executor.submit(new BuckTaskRunner(server.accept()));
155 } catch (Exception e) {
156 e.printStackTrace();
157 //no-op
158 }
159 }
160 }
161
162 /**
163 * Runnable capable of invoking the appropriate Buck task with input
164 * consumed form the specified socket and output produced back to that
165 * socket.
166 */
167 private class BuckTaskRunner implements Runnable {
168
169 private final Socket socket;
170
171 public BuckTaskRunner(Socket socket) {
172 this.socket = socket;
173 }
174
175 @Override
176 public void run() {
177 try {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800178 try {
179 socket.setSoTimeout(1_000); //reads should time out after 1 second
Ray Milkey0e440122018-01-16 15:00:50 -0800180 BuckTaskContext context = BuckTaskContext.createBuckTaskContext(socket.getInputStream());
181 if (context == null) {
182 socket.close();
183 return;
184 }
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800185
186 String taskName = context.taskName();
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700187 BuckTask task = tasks.get(taskName);
188 if (task != null) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800189 debug(String.format("Executing task '%s'", taskName));
Ray Milkey8df94b82016-11-16 11:03:32 -0800190 try {
191 task.execute(context);
192 for (String line : context.output()) {
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800193 send(socket, line);
Ray Milkey8df94b82016-11-16 11:03:32 -0800194 }
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800195 // TODO should we catch Exception, RuntimeException, or something specific?
Ray Milkey8df94b82016-11-16 11:03:32 -0800196 } catch (Throwable e) {
197 e.printStackTrace(new PrintStream(socket.getOutputStream()));
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700198 }
199 } else {
200 String message = String.format("No task named '%s'", taskName);
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800201 debug(message);
202 send(socket, message);
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700203 }
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800204 } catch (Throwable e) {
205 StringWriter writer = new StringWriter();
206 e.printStackTrace(new PrintWriter(writer));
207 String stacktrace = writer.toString();
208 debug(stacktrace);
209 send(socket, stacktrace);
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700210 }
211 socket.getOutputStream().flush();
212 socket.close();
213 } catch (IOException e) {
214 e.printStackTrace();
215 }
216 }
217
Brian O'Connorbd0e7202017-11-27 18:40:08 -0800218 }
219
220 private static void send(Socket socket, String line) throws IOException {
221 socket.getOutputStream().write((line + "\n").getBytes());
222 }
223
224 private static void debug(String message) {
225 // no-op; print to System.out if needed
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700226 }
227}