001/* 002 * To change this template, choose Tools | Templates 003 * and open the template in the editor. 004 */ 005package org.anarres.qemu.manager; 006 007import com.google.common.base.MoreObjects; 008import java.io.IOException; 009import java.net.ConnectException; 010import java.net.InetSocketAddress; 011import java.net.NoRouteToHostException; 012import java.net.UnknownServiceException; 013import java.util.concurrent.TimeUnit; 014import javax.annotation.CheckForNull; 015import javax.annotation.Nonnull; 016import org.anarres.qemu.exec.QEmuCustomOption; 017import org.anarres.qemu.exec.QEmuMonitorOption; 018import org.anarres.qemu.exec.recipe.QEmuMonitorRecipe; 019import org.anarres.qemu.qapi.api.QuitCommand; 020import org.anarres.qemu.qapi.common.QApiConnection; 021import org.anarres.qemu.qapi.common.QApiException; 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024 025/** 026 * 027 * @author shevek 028 */ 029public class QEmuProcess { 030 031 private static final Logger LOG = LoggerFactory.getLogger(QEmuProcess.class); 032 private final Process process; 033 private final InetSocketAddress monitor; 034 private QApiConnection connection; 035 private final Object lock = new Object(); 036 // private final IOBuffer stdout = new IOBuffer(); 037 // private final IOBuffer stderr = new IOBuffer(); 038 039 public QEmuProcess(@Nonnull Process process, @CheckForNull InetSocketAddress monitor) { 040 this.process = process; 041 this.monitor = monitor; 042 043 // new IOThread(process.getInputStream(), stdout).start(); 044 // new IOThread(process.getErrorStream(), stderr).start(); 045 } 046 047 /** 048 * Returns the {@link Process} representing the underlying QEmu process. 049 * 050 * @return the {@link Process} representing the underlying QEmu process. 051 */ 052 @Nonnull 053 public Process getProcess() { 054 return process; 055 } 056 057 /** 058 * Returns the address of the QEmu monitor socket, if one exists. 059 * 060 * If you connect to this address, you will have side-effects on the QEmu 061 * virtual machine. 062 * 063 * If the QEmuProcess was started without an appropriate 064 * {@link QEmuMonitorRecipe}, {@link QEmuMonitorOption} or 065 * {@link QEmuCustomOption} then this will return null. 066 * 067 * @return the address of the QEmu monitor socket, if one exists. 068 */ 069 @CheckForNull 070 public InetSocketAddress getMonitor() { 071 return monitor; 072 } 073 074 /** 075 * @throws NoRouteToHostException if the process is terminated. 076 * @throws UnknownServiceException if the process has no known monitor address. 077 */ 078 @Nonnull 079 public QApiConnection getConnection() throws IOException { 080 if (monitor == null) 081 throw new UnknownServiceException("No monitor address known."); 082 083 try { 084 // If this succeeds, then we have exited. 085 int exitValue = process.exitValue(); 086 connection = null; 087 throw new NoRouteToHostException("Process terminated with exit code " + exitValue); 088 } catch (IllegalThreadStateException e) { 089 } 090 091 synchronized (lock) { 092 if (connection != null) 093 return connection; 094 connection = new QApiConnection(monitor); 095 return connection; 096 } 097 } 098 099 @Nonnull 100 public QApiConnection getConnection(long timeout, @Nonnull TimeUnit unit) throws IOException, InterruptedException { 101 long end = System.currentTimeMillis() + unit.toMillis(timeout); 102 while (end > System.currentTimeMillis()) { 103 try { 104 return getConnection(); 105 } catch (ConnectException e) { 106 LOG.warn("Failed to connect to " + this + ": " + e); 107 } 108 109 long delay = Math.min(500, end - System.currentTimeMillis()); 110 Thread.sleep(delay); 111 } 112 113 return getConnection(); // This last call blows our timing out of the window. :-( 114 } 115 116 public void destroy() throws IOException, QApiException { 117 try { 118 QApiConnection c = getConnection(); 119 if (c != null) { 120 c.call(new QuitCommand()); 121 c.close(); 122 } 123 } catch (IllegalStateException e) { 124 LOG.warn("Cannot destroy " + this, e); 125 } catch (IOException e) { 126 LOG.warn("Cannot destroy " + this, e); 127 } 128 process.destroy(); 129 } 130 131 @Override 132 public String toString() { 133 return MoreObjects.toStringHelper(this) 134 .add("process", process) 135 // .add("stdout", stdout) 136 // .add("stderr", stderr) 137 .toString(); 138 } 139}