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