001 /*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
005 *
006 * The contents of this file are subject to the terms of either the GNU
007 * General Public License Version 2 only ("GPL") or the Common Development
008 * and Distribution License("CDDL") (collectively, the "License"). You
009 * may not use this file except in compliance with the License. You can
010 * obtain a copy of the License at
011 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012 * or packager/legal/LICENSE.txt. See the License for the specific
013 * language governing permissions and limitations under the License.
014 *
015 * When distributing the software, include this License Header Notice in each
016 * file and include the License file at packager/legal/LICENSE.txt.
017 *
018 * GPL Classpath Exception:
019 * Oracle designates this particular file as subject to the "Classpath"
020 * exception as provided by Oracle in the GPL Version 2 section of the License
021 * file that accompanied this code.
022 *
023 * Modifications:
024 * If applicable, add the following below the License Header, with the fields
025 * enclosed by brackets [] replaced by your own identifying information:
026 * "Portions Copyright [year] [name of copyright owner]"
027 *
028 * Contributor(s):
029 * If you wish your version of this file to be governed by only the CDDL or
030 * only the GPL Version 2, indicate your decision by adding "[Contributor]
031 * elects to include this software in this distribution under the [CDDL or GPL
032 * Version 2] license." If you don't indicate a single choice of license, a
033 * recipient has the option to distribute your version of this file under
034 * either the CDDL, the GPL Version 2 or to extend the choice of license to
035 * its licensees as provided above. However, if you add GPL Version 2 code
036 * and therefore, elected the GPL Version 2 license, then the option applies
037 * only if the new code is made subject to such option by the copyright
038 * holder.
039 */
040 package com.sun.enterprise.admin.cli;
041
042 import com.sun.enterprise.util.OS;
043 import java.io.File;
044 import java.io.IOException;
045 import java.util.*;
046 import java.util.logging.Level;
047 import java.util.logging.Logger;
048 import com.sun.enterprise.admin.launcher.GFLauncher;
049 import com.sun.enterprise.admin.launcher.GFLauncherException;
050 import com.sun.enterprise.admin.launcher.GFLauncherInfo;
051 import com.sun.enterprise.universal.i18n.LocalStringsImpl;
052 import com.sun.enterprise.universal.process.ProcessStreamDrainer;
053 import com.sun.enterprise.universal.process.ProcessUtils;
054 import com.sun.enterprise.util.HostAndPort;
055 import com.sun.enterprise.util.io.ServerDirs;
056 import com.sun.enterprise.util.net.NetUtils;
057 import org.glassfish.api.admin.CommandException;
058 import static com.sun.enterprise.util.StringUtils.ok;
059 import static com.sun.enterprise.admin.cli.CLIConstants.WAIT_FOR_DAS_TIME_MS;
060
061 /**
062 * Java does not allow multiple inheritance. Both StartDomainCommand and
063 * StartInstanceCommand have common code but they are already in a different
064 * hierarchy of classes. The first common baseclass is too far away -- e.g.
065 * no "launcher" variable, etc.
066 *
067 * Instead -- put common code in here and call it as common utilities
068 * This class is designed to be thread-safe and IMMUTABLE
069 * @author bnevins
070 */
071 public class StartServerHelper {
072 public StartServerHelper(Logger logger0, boolean terse0,
073 ServerDirs serverDirs0, GFLauncher launcher0,
074 String masterPassword0, boolean debug0) {
075 logger = logger0;
076 terse = terse0;
077 launcher = launcher0;
078 info = launcher.getInfo();
079
080 if (info.isDomain())
081 serverOrDomainName = info.getDomainName();
082 else
083 serverOrDomainName = info.getInstanceName();
084
085 addresses = info.getAdminAddresses();
086 serverDirs = serverDirs0;
087 pidFile = serverDirs.getPidFile();
088 masterPassword = masterPassword0;
089 debug = debug0;
090 // it will be < 0 if both --debug is false and debug-enabled=false in jvm-config
091 debugPort = launcher.getDebugPort();
092 isDebugSuspend = launcher.isDebugSuspend();
093
094 if (isDebugSuspend && debugPort >= 0) {
095 logger.info(strings.get("ServerStart.DebuggerSuspendedMessage", "" + debugPort));
096 }
097 }
098
099 // TODO check the i18n messages
100 public void waitForServer() throws CommandException {
101 long startWait = System.currentTimeMillis();
102 if (!terse) {
103 // use stdout because logger always appends a newline
104 System.out.print(strings.get("WaitServer", serverOrDomainName) + " ");
105 }
106
107 boolean alive = false;
108 int count = 0;
109
110 pinged:
111 while (!timedOut(startWait)) {
112 if (pidFile != null) {
113 logger.finer("Check for pid file: " + pidFile);
114 if (pidFile.exists()) {
115 alive = true;
116 break pinged;
117 }
118 }
119 else {
120 // first, see if the admin port is responding
121 // if it is, the DAS is up
122 for (HostAndPort addr : addresses) {
123 if (NetUtils.isRunning(addr.getHost(), addr.getPort())) {
124 alive = true;
125 break pinged;
126 }
127 }
128 }
129
130 // check to make sure the DAS process is still running
131 // if it isn't, startup failed
132 try {
133 Process p = launcher.getProcess();
134 int exitCode = p.exitValue();
135 // uh oh, DAS died
136 String sname;
137
138 if (info.isDomain())
139 sname = "domain " + info.getDomainName();
140 else
141 sname = "instance " + info.getInstanceName();
142
143 ProcessStreamDrainer psd = launcher.getProcessStreamDrainer();
144 String output = psd.getOutErrString();
145 if (ok(output))
146 throw new CommandException(strings.get("serverDiedOutput",
147 sname, exitCode, output));
148 else
149 throw new CommandException(strings.get("serverDied",
150 sname, exitCode));
151 }
152 catch (GFLauncherException ex) {
153 // should never happen
154 }
155 catch (IllegalThreadStateException ex) {
156 // process is still alive
157 }
158
159 // wait before checking again
160 try {
161 Thread.sleep(100);
162 if (!terse && count++ % 10 == 0)
163 System.out.print(".");
164 }
165 catch (InterruptedException ex) {
166 // don't care
167 }
168 }
169
170 if (!terse)
171 System.out.println();
172
173 if (!alive) {
174 String msg;
175 String time = "" + (WAIT_FOR_DAS_TIME_MS / 1000);
176 if (info.isDomain())
177 msg = strings.get("serverNoStart", strings.get("DAS"),
178 info.getDomainName(), time);
179 else
180 msg = strings.get("serverNoStart", strings.get("INSTANCE"),
181 info.getInstanceName(), time);
182
183 throw new CommandException(msg);
184 }
185 }
186
187 /**
188 * Run a series of commands to prepare for a launch.
189 * @return false if there was a problem.
190 */
191 public boolean prepareForLaunch() throws CommandException {
192
193 waitForParentToDie();
194 setSecurity();
195
196 if (checkPorts() == false)
197 return false;
198
199 deletePidFile();
200
201 return true;
202 }
203
204 public void report() {
205 String logfile;
206
207 try {
208 logfile = launcher.getLogFilename();
209 }
210 catch (GFLauncherException ex) {
211 logfile = "UNKNOWN"; // should never happen
212 }
213
214 int adminPort = -1;
215 String adminPortString = "-1";
216
217 try {
218 if (addresses != null && addresses.size() > 0)
219 adminPort = addresses.get(0).getPort();
220 // To avoid having the logger do this: port = 4,848
221 // so we do the conversion to a string ourselves
222 adminPortString = "" + adminPort;
223 }
224 catch (Exception e) {
225 //ignore
226 }
227
228 logger.info(strings.get(
229 "ServerStart.SuccessMessage",
230 info.isDomain() ? "domain " : "instance",
231 serverDirs.getServerName(),
232 serverDirs.getServerDir(),
233 logfile,
234 adminPortString));
235
236 if (debugPort >= 0) {
237 logger.info(strings.get("ServerStart.DebuggerMessage", "" + debugPort));
238 }
239 }
240
241 /**
242 * If the parent is a GF server -- then wait for it to die. This is part
243 * of the Client-Server Restart Dance!
244 * THe dying server called us with the system property AS_RESTART set to its pid
245 * @throws CommandException if we timeout waiting for the parent to die or
246 * if the admin ports never free up
247 */
248 private void waitForParentToDie() throws CommandException {
249 // we also come here with just a regular start in which case there is
250 // no parent, and the System Property is NOT set to anything...
251 String pids = System.getProperty(CLIConstants.RESTART_FLAG_PARENT_PID);
252
253 if (!ok(pids))
254 return;
255
256 int pid = -1;
257
258 try {
259 pid = Integer.parseInt(pids);
260 }
261 catch (Exception e) {
262 pid = -1;
263 }
264 waitForParentDeath(pid);
265 }
266
267 private boolean checkPorts() {
268 String err = adminPortInUse();
269
270 if (err != null) {
271 logger.warning(err);
272 return false;
273 }
274
275 return true;
276 }
277
278 private void deletePidFile() {
279 String msg = serverDirs.deletePidFile();
280
281 if (msg != null)
282 logger.finer(msg);
283 }
284
285 private void setSecurity() {
286 info.addSecurityToken(CLIConstants.MASTER_PASSWORD, masterPassword);
287 }
288
289 private String adminPortInUse() {
290 return adminPortInUse(info.getAdminAddresses());
291 }
292
293 private String adminPortInUse(List<HostAndPort> adminAddresses) {
294 // it returns a String for logging --- if desired
295 for (HostAndPort addr : adminAddresses) {
296 if (!NetUtils.isPortFree(addr.getHost(), addr.getPort()))
297 return strings.get("ServerRunning",
298 Integer.toString(addr.getPort()));
299 }
300
301 return null;
302 }
303
304 // use the pid we received from the parent server and platform specific tools
305 // to see FOR SURE when the entire JVM process is gone. This solves
306 // potential niggling bugs.
307 private void waitForParentDeath(int pid) throws CommandException {
308 if (pid < 0) {
309 // can not happen. (Famous Last Words!)
310 new ParentDeathWaiterPureJava();
311 return;
312 }
313
314 long start = System.currentTimeMillis();
315 try {
316 do {
317 Boolean b = ProcessUtils.isProcessRunning(pid);
318 if (b == null) {
319 // this means we were unable to find out from the OS if the process
320 // is running or not
321 debugMessage("ProcessUtils.isProcessRunning(" + pid + ") "
322 + "returned null which means we can't get process "
323 + "info on this platform.");
324
325 new ParentDeathWaiterPureJava();
326 return;
327 }
328 if (b.booleanValue() == false) {
329 debugMessage("Parent process (" + pid + ") is dead.");
330 return;
331 }
332 // else parent is still breathing...
333 debugMessage("Wait one more second for parent to die...");
334 Thread.sleep(1000);
335 }while (!timedOut(start, CLIConstants.DEATH_TIMEOUT_MS));
336
337 }
338 catch (Exception e) {
339 // fall through. Normal returns are in the block above
340 }
341
342 // abnormal return path
343 throw new CommandException(
344 strings.get("deathwait_timeout", CLIConstants.DEATH_TIMEOUT_MS));
345 }
346
347 private static boolean timedOut(long startTime) {
348 return timedOut(startTime, WAIT_FOR_DAS_TIME_MS);
349 }
350
351 private static boolean timedOut(long startTime, long span) {
352 return (System.currentTimeMillis() - startTime) > span;
353 }
354
355 private static void debugMessage(String s) {
356 // very difficult to see output from this process when part of restart-domain.
357 // Normally there is no console.
358 // There are **three** JVMs in a restart -- old server, new server, cli
359 // we will not even see AS_DEBUG!
360 if (DEBUG_MESSAGES_ON)
361 CLIUtil.writeCommandToDebugLog(new String[]{"DEBUG MESSAGE FROM RESTART JVM", s}, 99999);
362 }
363 private final boolean terse;
364 private final GFLauncher launcher;
365 private final Logger logger;
366 private final File pidFile;
367 private final GFLauncherInfo info;
368 private final List<HostAndPort> addresses;
369 private final ServerDirs serverDirs;
370 private final String masterPassword;
371 private final String serverOrDomainName;
372 private final boolean debug;
373 private final int debugPort;
374 private final boolean isDebugSuspend;
375 // only set when actively trouble-shooting or investigating...
376 private final static boolean DEBUG_MESSAGES_ON = false;
377 private static final LocalStringsImpl strings =
378 new LocalStringsImpl(StartServerHelper.class);
379
380 /**
381 * bnevins
382 * the restart flag is set by the RestartDomain command in the local
383 * server. The dying server has started a new JVM process and is
384 * running this code. Our official parent process is the dying server.
385 * The ParentDeathWaiterPureJava waits for the parent process to disappear.
386 * see RestartDomainCommand in core/kernel for more details
387 */
388 private class ParentDeathWaiterPureJava implements Runnable {
389 @Override
390 @SuppressWarnings("empty-statement")
391 public void run() {
392 try {
393 // When parent process is almost dead, in.read returns -1 (EOF)
394 // as the pipe breaks.
395
396 while (System.in.read() >= 0);
397 }
398 catch (IOException ex) {
399 // ignore
400 }
401
402 // The port may take some time to become free after the pipe breaks
403 while (adminPortInUse(addresses) != null)
404 ;
405 success = true;
406 }
407
408 private ParentDeathWaiterPureJava() throws CommandException {
409 try {
410 Thread t = new Thread(this);
411 t.start();
412 t.join(CLIConstants.DEATH_TIMEOUT_MS);
413 }
414 catch (Exception e) {
415 // ignore!
416 }
417
418 if (!success)
419 throw new CommandException(
420 strings.get("deathwait_timeout", CLIConstants.DEATH_TIMEOUT_MS));
421 }
422 boolean success = false;
423 }
424 }