1 package org.kuali.common.deploy;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collections;
8 import java.util.List;
9 import java.util.Properties;
10
11 import org.apache.commons.io.IOUtils;
12 import org.codehaus.plexus.util.StringUtils;
13 import org.kuali.common.util.Assert;
14 import org.kuali.common.util.FormatUtils;
15 import org.kuali.common.util.LocationUtils;
16 import org.kuali.common.util.LoggerLevel;
17 import org.kuali.common.util.LoggerUtils;
18 import org.kuali.common.util.MonitorTextFileResult;
19 import org.kuali.common.util.ThreadUtils;
20 import org.kuali.common.util.UnixCmds;
21 import org.kuali.common.util.UnixProcess;
22 import org.kuali.common.util.property.Constants;
23 import org.kuali.common.util.secure.RemoteFile;
24 import org.kuali.common.util.secure.Result;
25 import org.kuali.common.util.secure.SecureChannel;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28 import org.springframework.util.CollectionUtils;
29 import org.springframework.util.PropertyPlaceholderHelper;
30
31 public class DeployUtils {
32
33 private static final Logger logger = LoggerFactory.getLogger(DeployUtils.class);
34
35 private static final String CMD = "CMD";
36 private static final String TRAVERSE_SYMBOLIC_LINKS = "-L";
37 private static final UnixCmds CMDS = new UnixCmds();
38 private static final PropertyPlaceholderHelper HELPER = Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER;
39
40
41
42
43 public static MonitorTextFileResult monitorTextFile(SecureChannel channel, String path, String token, int intervalMillis, int timeoutMillis, String encoding) {
44
45
46 Assert.notNull(channel, "channel is null");
47 Assert.notNull(path, "path is null");
48 Assert.hasText(token, "token has no text");
49 Assert.hasText(encoding, "encoding has no text");
50 Assert.isTrue(intervalMillis > 0, "interval must be a positive integer");
51 Assert.isTrue(timeoutMillis > 0, "timeout must be a positive integer");
52
53
54 long start = System.currentTimeMillis();
55 long stop = start + timeoutMillis;
56 boolean exists = false;
57 boolean contains = false;
58 boolean timeoutExceeded = false;
59 long now = -1;
60 String content = null;
61
62
63 for (;;) {
64
65
66 if (now != -1) {
67 ThreadUtils.sleep(intervalMillis);
68 }
69
70
71 now = System.currentTimeMillis();
72 if (now > stop) {
73 timeoutExceeded = true;
74 break;
75 }
76
77
78 exists = channel.exists(path);
79 if (!exists) {
80 continue;
81 }
82
83
84 RemoteFile remoteFile = new RemoteFile(path);
85 content = channel.toString(remoteFile);
86 contains = StringUtils.contains(content, token);
87 if (contains) {
88
89 break;
90 }
91 }
92
93
94 long elapsed = now - start;
95
96
97 MonitorTextFileResult mtfr = new MonitorTextFileResult(exists, contains, timeoutExceeded, elapsed);
98 mtfr.setAbsolutePath(path);
99 mtfr.setContent(content);
100 return mtfr;
101 }
102
103 public static void killMatchingProcesses(SecureChannel channel, String user, String cmd, String processLabel) {
104 List<UnixProcess> processes = getUnixProcesses(channel, user);
105
106
107 if (processes.size() == 0) {
108 logger.info(" no running processes for user [{}]", user);
109 return;
110 }
111
112
113 List<UnixProcess> matches = getMatchingProcesses(processes, cmd);
114
115 if (CollectionUtils.isEmpty(matches)) {
116
117 logger.info(" no machine agents detected. total running processes - {}", processes.size());
118 return;
119 } else {
120
121 for (UnixProcess match : matches) {
122 logger.info(" killing {} - [pid:{}]", processLabel, match.getProcessId());
123 kill(channel, match);
124 }
125 }
126 }
127
128
129
130
131 public static String getNohupBackgroundProcess(String user, String cmd) {
132 StringBuilder sb = new StringBuilder();
133 sb.append("su");
134 sb.append(" - ");
135 sb.append(user);
136 sb.append(" ");
137 sb.append("--command");
138 sb.append("=");
139 sb.append("'");
140 sb.append(CMDS.nohup(cmd));
141 sb.append(" ");
142 sb.append("&");
143 sb.append("'");
144 return sb.toString();
145 }
146
147 public static void copyFiles(SecureChannel channel, List<Deployable> deployables, Properties filterProperties) {
148 if (CollectionUtils.isEmpty(deployables)) {
149 return;
150 }
151 for (Deployable deployable : deployables) {
152 RemoteFile destination = new RemoteFile(deployable.getRemote());
153 String location = deployable.getLocal();
154 logger.info(" creating -> [{}]", destination.getAbsolutePath());
155 if (deployable.isFilter()) {
156 long start = System.currentTimeMillis();
157 String originalContent = LocationUtils.toString(location);
158 String resolvedContent = HELPER.replacePlaceholders(originalContent, filterProperties);
159 channel.copyStringToFile(resolvedContent, destination);
160 String elapsed = FormatUtils.getTime(System.currentTimeMillis() - start);
161 Object[] args = { filterProperties.size(), location, destination.getAbsolutePath(), elapsed };
162 logger.debug("Used {} properties to filter [{}] -> [{}] - {}", args);
163 } else {
164 long start = System.currentTimeMillis();
165 channel.copyLocationToFile(location, destination);
166 logCopy(location, destination.getAbsolutePath(), System.currentTimeMillis() - start);
167 }
168 if (deployable.getPermissions() != null) {
169 String path = deployable.getRemote();
170 String perms = deployable.getPermissions();
171 String command = CMDS.chmod(perms, path);
172 executePathCommand(channel, command, path);
173 }
174 }
175 }
176
177 protected static void logCopy(String src, String dst, long elapsed) {
178 String rate = "";
179 String size = "";
180 if (LocationUtils.isExistingFile(src)) {
181 long bytes = new File(src).length();
182 rate = FormatUtils.getRate(elapsed, bytes);
183 size = FormatUtils.getSize(bytes);
184 }
185 Object[] args = { dst, size, FormatUtils.getTime(elapsed), rate };
186 logger.debug("Source -> [{}]", src);
187 logger.debug(" created [{}] - [{} {} {}]", args);
188 }
189
190
191
192
193 public static List<UnixProcess> getMatchingProcesses(List<UnixProcess> processes, String command) {
194 List<UnixProcess> matches = new ArrayList<UnixProcess>();
195 for (UnixProcess process : processes) {
196 if (StringUtils.equals(process.getCommand(), command)) {
197 matches.add(process);
198 }
199 }
200 return matches;
201 }
202
203
204
205
206
207
208
209
210
211
212 public static List<UnixProcess> getUnixProcesses(Result result) {
213
214 List<String> lines = DeployUtils.getOutputLines(result);
215
216
217 if (CollectionUtils.isEmpty(lines)) {
218 throw new IllegalStateException("There should be a header line");
219 }
220
221
222 if (lines.size() == 1 && result.getExitValue() == 1) {
223
224 return Collections.emptyList();
225 }
226
227
228 DeployUtils.validateResult(result);
229
230
231 String header = lines.get(0);
232
233
234 List<UnixProcess> processes = new ArrayList<UnixProcess>();
235
236
237 for (int i = 1; i < lines.size(); i++) {
238
239
240 String line = lines.get(i);
241
242
243 UnixProcess process = getUnixProcess(header, line);
244
245
246 processes.add(process);
247 }
248
249
250 return processes;
251 }
252
253
254
255
256
257
258
259
260
261
262 public static UnixProcess getUnixProcess(String header, String line) {
263
264 String[] tokens = StringUtils.split(line, " ");
265
266 String userId = StringUtils.trim(tokens[0]);
267
268 String processId = StringUtils.trim(tokens[1]);
269
270 int pos = header.indexOf(CMD);
271 if (pos == -1) {
272 throw new IllegalStateException("[" + line + "] does not contain [" + CMD + "]");
273 }
274 String command = StringUtils.trim(StringUtils.substring(line, pos));
275
276
277 UnixProcess process = new UnixProcess();
278 process.setUserId(userId);
279 process.setProcessId(Integer.parseInt(processId));
280 process.setCommand(command);
281 return process;
282 }
283
284 public static Result executeCommand(SecureChannel channel, String command, boolean validateResult) {
285 Result result = channel.executeCommand(command);
286 if (validateResult) {
287 DeployUtils.validateResult(result);
288 }
289 return result;
290 }
291
292 public static void kill(SecureChannel channel, UnixProcess process) {
293 String command = CMDS.kill(process.getProcessId());
294 Result result = channel.executeCommand(command);
295 DeployUtils.logResult(result, logger, LoggerLevel.DEBUG);
296 DeployUtils.validateResult(result);
297 }
298
299 public static List<UnixProcess> getUnixProcesses(SecureChannel channel, String user) {
300 String command = CMDS.psf(user);
301 Result result = channel.executeCommand(command);
302 return getUnixProcesses(result);
303
304 }
305
306 public static Result runscript(SecureChannel channel, String username, String script) {
307 return executeCommand(channel, CMDS.su(username, script), true);
308 }
309
310 public static Result runscript(SecureChannel channel, String username, String script, boolean validateExitValue) {
311 return executeCommand(channel, CMDS.su(username, script), validateExitValue);
312 }
313
314 public static Result delete(SecureChannel channel, List<String> paths) {
315 return executePathCommand(channel, CMDS.rmrf(paths), paths);
316 }
317
318 public static Result mkdirs(SecureChannel channel, List<String> paths) {
319 return executePathCommand(channel, CMDS.mkdirp(paths), paths);
320 }
321
322 public static Result chown(SecureChannel channel, String owner, String group, List<String> paths) {
323 List<String> options = Arrays.asList(TRAVERSE_SYMBOLIC_LINKS);
324 String cmd = CMDS.chownr(options, owner, group, paths);
325 return executePathCommand(channel, cmd, paths);
326 }
327
328 public static void executePathCommand(SecureChannel channel, String command, String path) {
329 executePathCommand(channel, command, Collections.singletonList(path));
330 }
331
332 public static Result executePathCommand(SecureChannel channel, String command, List<String> paths) {
333 Result result = channel.executeCommand(command);
334 validateResult(result);
335 return result;
336 }
337
338 public static List<String> getOutputLines(Result result) {
339 try {
340 return IOUtils.readLines(LocationUtils.getBufferedReaderFromString(result.getStdout()));
341 } catch (IOException e) {
342 throw new IllegalArgumentException("Unexpected IO error", e);
343 }
344 }
345
346 public static void logResult(Result result, Logger logger, LoggerLevel level) {
347 LoggerUtils.logLines("[" + result.getCommand() + "] - " + FormatUtils.getTime(result.getElapsed()), logger, level);
348 LoggerUtils.logLines(result.getStdout(), logger, level);
349 LoggerUtils.logLines(result.getStderr(), logger, LoggerLevel.WARN);
350 if (result.getExitValue() != 0) {
351 logger.warn("Exit value = {}", result.getExitValue());
352 }
353 }
354
355 public static void logResult(Result result, Logger logger) {
356 logResult(result, logger, LoggerLevel.INFO);
357 }
358
359 public static void validateResult(Result result) {
360 validateResult(result, Arrays.asList(0));
361 logger.trace("Result is valid");
362 }
363
364 public static void validateResult(Result result, List<Integer> exitValues) {
365 for (Integer exitValue : exitValues) {
366 if (exitValue.equals(result.getExitValue())) {
367 return;
368 }
369 }
370 throw new IllegalStateException("Exit value " + result.getExitValue() + " is not allowed");
371 }
372
373 }