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.MonitorTextFileResult;
17 import org.kuali.common.util.ThreadUtils;
18 import org.kuali.common.util.UnixCmds;
19 import org.kuali.common.util.UnixProcess;
20 import org.kuali.common.util.log.LoggerLevel;
21 import org.kuali.common.util.log.LoggerUtils;
22 import org.kuali.common.util.property.Constants;
23 import org.kuali.common.util.secure.channel.RemoteFile;
24 import org.kuali.common.util.secure.channel.Result;
25 import org.kuali.common.util.secure.channel.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.Builder(path).build();
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.Builder(deployable.getRemote()).build();
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().isPresent()) {
169 String path = deployable.getRemote();
170 String perms = deployable.getPermissions().get();
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 = getOutputLines(result);
215
216
217 Assert.isFalse(CollectionUtils.isEmpty(lines), "There should be a header line");
218
219
220 if (lines.size() == 1 && result.getExitValue() == 1) {
221
222 return Collections.emptyList();
223 }
224
225
226 validateResult(result);
227
228
229 String header = lines.get(0);
230
231
232 List<UnixProcess> processes = new ArrayList<UnixProcess>();
233
234
235 for (int i = 1; i < lines.size(); i++) {
236
237
238 String line = lines.get(i);
239
240
241 UnixProcess process = getUnixProcess(header, line);
242
243
244 processes.add(process);
245 }
246
247
248 return processes;
249 }
250
251
252
253
254
255
256
257
258
259
260 public static UnixProcess getUnixProcess(String header, String line) {
261
262 String[] tokens = StringUtils.split(line, " ");
263
264 String userId = StringUtils.trim(tokens[0]);
265
266 String processId = StringUtils.trim(tokens[1]);
267
268 int pos = header.indexOf(CMD);
269
270 Assert.isFalse(pos == -1, "[" + line + "] does not contain [" + CMD + "]");
271
272 String command = StringUtils.trim(StringUtils.substring(line, pos));
273
274
275 UnixProcess process = new UnixProcess();
276 process.setUserId(userId);
277 process.setProcessId(Integer.parseInt(processId));
278 process.setCommand(command);
279 return process;
280 }
281
282 public static Result executeCommand(SecureChannel channel, String command, boolean validateResult) {
283 Result result = channel.executeCommand(command);
284 if (validateResult) {
285 validateResult(result);
286 }
287 return result;
288 }
289
290 public static void kill(SecureChannel channel, UnixProcess process) {
291 String command = CMDS.kill(process.getProcessId());
292 Result result = channel.executeCommand(command);
293 logResult(result, logger, LoggerLevel.DEBUG);
294 validateResult(result);
295 }
296
297 public static List<UnixProcess> getUnixProcesses(SecureChannel channel, String user) {
298 String command = CMDS.psf(user);
299 Result result = channel.executeCommand(command);
300 return getUnixProcesses(result);
301
302 }
303
304 public static Result runscript(SecureChannel channel, String username, String script) {
305 return executeCommand(channel, CMDS.su(username, script), true);
306 }
307
308 public static Result runscript(SecureChannel channel, String username, String script, boolean validateExitValue) {
309 return executeCommand(channel, CMDS.su(username, script), validateExitValue);
310 }
311
312 public static Result delete(SecureChannel channel, List<String> paths) {
313 return executePathCommand(channel, CMDS.rmrf(paths), paths);
314 }
315
316 public static Result mkdirs(SecureChannel channel, List<String> paths) {
317 return executePathCommand(channel, CMDS.mkdirp(paths), paths);
318 }
319
320 public static Result chown(SecureChannel channel, String owner, String group, List<String> paths) {
321 List<String> options = Arrays.asList(TRAVERSE_SYMBOLIC_LINKS);
322 String cmd = CMDS.chownr(options, owner, group, paths);
323 return executePathCommand(channel, cmd, paths);
324 }
325
326 public static void executePathCommand(SecureChannel channel, String command, String path) {
327 executePathCommand(channel, command, Collections.singletonList(path));
328 }
329
330 public static Result executePathCommand(SecureChannel channel, String command, List<String> paths) {
331 Result result = channel.executeCommand(command);
332 validateResult(result);
333 return result;
334 }
335
336 public static List<String> getOutputLines(Result result) {
337 try {
338 return IOUtils.readLines(LocationUtils.getBufferedReaderFromString(result.getStdout()));
339 } catch (IOException e) {
340 throw new IllegalArgumentException("Unexpected IO error", e);
341 }
342 }
343
344 public static void logResult(Result result, Logger logger, LoggerLevel level) {
345 LoggerUtils.logLines("[" + result.getCommand() + "] - " + FormatUtils.getTime(result.getElapsed()), logger, level);
346 LoggerUtils.logLines(result.getStdout(), logger, level);
347 LoggerUtils.logLines(result.getStderr(), logger, LoggerLevel.WARN);
348 if (result.getExitValue() != 0) {
349 logger.warn("Exit value = {}", result.getExitValue());
350 }
351 }
352
353 public static void logResult(Result result, Logger logger) {
354 logResult(result, logger, LoggerLevel.INFO);
355 }
356
357 public static void validateResult(Result result) {
358 validateResult(result, Arrays.asList(0));
359 logger.trace("Result is valid");
360 }
361
362 public static void validateResult(Result result, List<Integer> exitValues) {
363 for (Integer exitValue : exitValues) {
364 if (exitValue.equals(result.getExitValue())) {
365 return;
366 }
367 }
368 throw new IllegalStateException("Exit value " + result.getExitValue() + " is not allowed");
369 }
370
371 }