/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.plugins.platform.content.yum;
import java.io.File;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Provides a bare-bones http server for yum requests. It is designed to be a non-concurrent server to ensure that
* requests are handled <i>one at a tiem</i>. This is neccessary because the server caches the local metadata files. A
* concurrent server would either duplicate the work required (separate files per thread) to construct the <i>local</i>
* metadata or each thread would step-on the shared metadata files. Either way, a concurrent server should be sufficient
* for a single client.
*
* @author jortel
*/
public class YumServer {
/**
* The server socket used to listen for requests.
*/
private ServerSocket socket = null;
/**
* The server thread.
*/
private Thread thread = null;
/**
* The server thread run flag.
*/
private boolean run = true;
/**
* The server's context.
*/
YumContext context;
/**
* The yum configuration file constant
*/
private static final String yumconf = "/etc/yum.repos.d/rhq.repo";
private final Log log = LogFactory.getLog(YumServer.class);
/**
* Starts the server. This will initialize and start a server thread. If the server is already running, this request
* is treated as a restart. The server thread is stopped (run=false) and a new thread is initialized are started.
*
* @param context The server's context.
*
* @throws Exception On all errors.
*/
public void start(YumContext context) throws Exception {
this.context = context;
if (thread != null) {
log.warn("Already started, restarting ...");
halt();
}
setupYumConfiguration();
int port = context.baseurl().getPort();
InetAddress host = bindAddress();
log.info("Binding: " + host + ":" + port);
socket = new ServerSocket(port, 20, host);
socket.setSoTimeout(2000);
start(port);
}
/**
* Stop (halt/run=false) the server thread. The request is ignored if the server thread isn't running.
*/
public void halt() {
if (thread == null) {
log.info("Stop ignored: not running");
return;
}
try {
run = false;
thread.join();
thread = null;
socket.close();
log.info("Stopped");
} catch (Exception e) {
log.error("halt falied", e);
} finally {
removeYumConfiguration();
}
}
public boolean isStarted() {
return this.thread != null;
}
/**
* Clean the cached metadata.
*/
public void cleanMetadata() {
try {
Request request = new Request(this, null);
request.cleanMetadata();
} catch (Exception e) {
log.error("Clean metadata failed", e);
}
}
/**
* Start a server thread listening on the <i>loopback</i> ethernet interface on the specifed port. The thread
* listens for yum/http requests uisng a finite timeout so changes in the <i>run</i> flag can be detected.
*
* @param port The tcp port to listen on.
*/
private void start(int port) {
run = true;
thread = new Thread("yum:" + port) {
@Override
public void run() {
log.info("listening ...");
while (run) {
listen();
}
}
};
thread.start();
log.info("Started, listening on port: " + port);
}
/**
* Listen for and process requests.
*/
private void listen() {
try {
Socket client = socket.accept();
client.setTcpNoDelay(true);
client.setSoLinger(false, 0);
Request request = new Request(this, client);
request.process();
} catch (SocketTimeoutException te) {
// expected
} catch (Exception e) {
log.warn("listen failed", e);
run = false;
}
}
/**
* Setup the /etc/yum.repos.d configuration for this server. The file is over-written to ensure proper content.
*/
private void setupYumConfiguration() {
File file = new File(yumconf);
try {
PrintWriter writer = new PrintWriter(file);
try {
writer.println("[rhq]");
writer.println("name=RHQ");
writer.printf("baseurl=%s\n", context.baseurl());
writer.printf("metadata_expire=%d\n", context.getMetadataCacheTimeout());
writer.println("enabled=1");
writer.println("gpgcheck=0");
writer.println("keepalive=0");
writer.println("timeout=90");
} finally {
writer.close();
}
} catch (Exception e) {
String msg = "The yum repo configuration file '" + file + "' could not be created/updated!";
log.error(msg, e);
}
}
/**
* Remove the etc/yum.repos.d/ackbar.conf configuration file.
*/
public void removeYumConfiguration() {
File file = new File(yumconf);
try {
file.delete();
} catch (Exception e) {
String msg = "The yum repo configuration file '" + file + "' could not be removed!";
log.error(msg, e);
}
}
/**
* Get the bind IP address.
*
* @return An address to bind.
*
* @throws UnknownHostException
*/
private InetAddress bindAddress() throws UnknownHostException {
byte[] host = { 127, 0, 0, 1 };
return InetAddress.getByAddress(host);
}
}