blob: 5b37609c6a89ce73f8a68332b7c219f6caf5fa89 [file] [log] [blame]
Thomas Vachuska275d2e82016-07-14 17:41:34 -07001/*
Brian O'Connor0a4e6742016-09-15 23:03:10 -07002 * Copyright 2016-present Open Networking Laboratory
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
19import com.google.common.io.ByteStreams;
20import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
21import org.onosproject.checkstyle.CheckstyleRunner;
22
23import java.io.IOException;
Ray Milkey8df94b82016-11-16 11:03:32 -080024import java.io.PrintStream;
Thomas Vachuska275d2e82016-07-14 17:41:34 -070025import java.net.ServerSocket;
26import java.net.Socket;
27import java.nio.ByteBuffer;
28import java.nio.channels.FileChannel;
29import java.nio.channels.FileLock;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.util.HashMap;
34import java.util.Map;
35import java.util.Timer;
36import java.util.TimerTask;
37import java.util.concurrent.Callable;
38import java.util.concurrent.ExecutorService;
39import java.util.concurrent.Executors;
40
41import static java.nio.file.StandardOpenOption.*;
42
43/**
44 * Buck daemon process.
45 */
46public final class BuckDaemon {
47
48 private static long POLLING_INTERVAL = 1000; //ms
49
50 private final Map<String, BuckTask> tasks = new HashMap<>();
51 private final String portLock;
52 private final String buckPid;
53
54 // Public construction forbidden
55 private BuckDaemon(String[] args) {
56 portLock = args[0];
57 buckPid = args[1];
58 }
59
60 /**
61 * Main entry point for the daemon.
62 *
63 * @param args command-line arguments
64 */
65 public static void main(String[] args)
66 throws CheckstyleException, IOException {
67 BuckDaemon daemon = new BuckDaemon(args);
68 daemon.registerTasks();
69 daemon.startServer();
70 }
71
72 /**
73 * Registers re-entrant tasks by their task name.
74 */
75 private void registerTasks() {
76 tasks.put("checkstyle", new CheckstyleRunner(System.getProperty("checkstyle.config"),
77 System.getProperty("checkstyle.suppressions")));
78 // tasks.put("swagger", new SwaggerGenerator());
79 }
80
81 /**
82 * Monitors another PID and exit when that process exits.
83 */
84 private void watchProcess(String pid) {
85 if (pid == null || pid.equals("0")) {
86 return;
87 }
88 Timer timer = new Timer(true); // start as a daemon, so we don't hang shutdown
89 timer.scheduleAtFixedRate(new TimerTask() {
90 private String cmd = "kill -s 0 " + pid;
91
92 @Override
93 public void run() {
94 try {
95 Process p = Runtime.getRuntime().exec(cmd);
96 p.waitFor();
97 if (p.exitValue() != 0) {
98 System.err.println("shutting down...");
99 System.exit(0);
100 }
101 } catch (IOException | InterruptedException e) {
102 //no-op
103 e.printStackTrace();
104 }
105 }
106 }, POLLING_INTERVAL, POLLING_INTERVAL);
107 }
108
109 /**
110 * Initiates a server.
111 */
112 private void startServer() throws IOException, CheckstyleException {
113 // Use a file lock to ensure only one copy of the daemon runs
114 Path portLockPath = Paths.get(portLock);
115 FileChannel channel = FileChannel.open(portLockPath, WRITE, CREATE);
116 FileLock lock = channel.tryLock();
117 if (lock == null) {
118 System.out.println("Server is already running");
119 System.exit(1);
120 } //else, hold the lock until the JVM exits
121
122 // Start the server and bind it to a random port
123 ServerSocket server = new ServerSocket(0);
124
125 // Monitor the parent buck process
126 watchProcess(buckPid);
127
128 // Set up hook to clean up after ourselves
129 Runtime.getRuntime().addShutdownHook(new Thread(() -> {
130 try {
131 channel.truncate(0);
132 channel.close();
133 System.err.println("tear down...");
134 Files.delete(portLockPath);
135 } catch (IOException e) {
136 //no-op: shutting down
137 e.printStackTrace();
138 }
139 }));
140
141 // Write the bound port to the port file
142 int port = server.getLocalPort();
143 channel.truncate(0);
144 channel.write(ByteBuffer.wrap(Integer.toString(port).getBytes()));
145
146 // Instantiate a Checkstyle runner and executor; serve until exit...
147 ExecutorService executor = Executors.newCachedThreadPool();
148 while (true) {
149 try {
150 executor.submit(new BuckTaskRunner(server.accept()));
151 } catch (Exception e) {
152 e.printStackTrace();
153 //no-op
154 }
155 }
156 }
157
158 /**
159 * Runnable capable of invoking the appropriate Buck task with input
160 * consumed form the specified socket and output produced back to that
161 * socket.
162 */
163 private class BuckTaskRunner implements Runnable {
164
165 private final Socket socket;
166
167 public BuckTaskRunner(Socket socket) {
168 this.socket = socket;
169 }
170
171 @Override
172 public void run() {
173 try {
174 BuckTaskContext context = new BuckTaskContext(socket.getInputStream());
175 String taskName = context.taskName();
176 if (!taskName.isEmpty()) {
177 BuckTask task = tasks.get(taskName);
178 if (task != null) {
179 System.out.println(String.format("Executing task '%s'", taskName));
Ray Milkey8df94b82016-11-16 11:03:32 -0800180 try {
181 task.execute(context);
182 for (String line : context.output()) {
183 output(socket, line);
184 }
185 // TODO should we catch Exception, RuntimeException, or something specific?
186 } catch (Throwable e) {
187 e.printStackTrace(new PrintStream(socket.getOutputStream()));
Thomas Vachuska275d2e82016-07-14 17:41:34 -0700188 }
189 } else {
190 String message = String.format("No task named '%s'", taskName);
191 System.out.print(message);
192 output(socket, message);
193 }
194 }
195 socket.getOutputStream().flush();
196 socket.close();
197 } catch (IOException e) {
198 e.printStackTrace();
199 }
200 }
201
202 private void output(Socket socket, String line) throws IOException {
203 socket.getOutputStream().write((line + "\n").getBytes());
204 }
205 }
206}