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}