package ch.usi.da.smr.recovery;
/*
* Copyright (c) 2014 Università della Svizzera italiana (USI)
*
* This file is part of URingPaxos.
*
* URingPaxos is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* URingPaxos 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with URingPaxos. If not, see <http://www.gnu.org/licenses/>.
*/
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import ch.usi.da.smr.PartitionManager;
import ch.usi.da.smr.Replica;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
/**
* Name: HttpRecovery<br>
* Description: <br>
*
* Creation date: Nov 6, 2014<br>
* $Id$
*
* @author Samuel Benz benz@geoid.ch
*/
public class HttpRecovery implements RecoveryInterface {
private final static Logger logger = Logger.getLogger(HttpRecovery.class);
private final PartitionManager partitions;
private final int max_ring = 20;
private HttpServer httpd;
public static final String snapshot_file = "/tmp/snapshot.ser";
public static final String state_file = "/tmp/snapshot.state"; // only used to quickly decide if remote snapshot is newer
private boolean download_active = false;
public HttpRecovery(PartitionManager partitions){
this.partitions = partitions;
// remote snapshot transfer server
try {
httpd = HttpServer.create(new InetSocketAddress(8080), 0);
httpd.createContext("/state", new SendFile(state_file));
httpd.createContext("/snapshot", new SendFile(snapshot_file));
httpd.setExecutor(null); // creates a default executor
httpd.start();
}catch(Exception e){
logger.error("Replica could not start http server: " + e);
}
}
public boolean storeState(Map<Integer,Long> instances, Map<String,byte[]> db){
try {
synchronized(db){
logger.info("Replica start storing state ... ");
for(Entry<Integer,Long> e : instances.entrySet()){
db.put("r:" + e.getKey(),e.getValue().toString().getBytes());
}
FileOutputStream fs = new FileOutputStream(snapshot_file);
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(db);
os.flush();
fs.getChannel().force(false); // fsync
os.close();
fs = new FileOutputStream(state_file);
for(Entry<Integer, Long> e : instances.entrySet()){
fs.write((e.getKey() + "=" + e.getValue() + "\n").getBytes());
}
fs.getChannel().force(false);
os.close();
logger.info("... state stored up to instance: " + instances);
}
return true;
} catch (IOException e) {
logger.error(e);
}
return false;
}
public Map<Integer,Long> installState(String token,Map<String,byte[]> db) throws Exception {
Map<Integer,Long> instances = new HashMap<Integer,Long>();
if(download_active){
logger.info("Install state surpressed since download active.");
return instances;
}
String host = null;
InputStreamReader isr;
String line;
Map<Integer, Long> state = new HashMap<Integer, Long>();
// local
try{
isr = new InputStreamReader(new FileInputStream(state_file));
BufferedReader bin = new BufferedReader(isr);
while ((line = bin.readLine()) != null){
String[] s = line.split("=");
state.put(Integer.parseInt(s[0]),Long.parseLong(s[1]));
}
logger.info("Replica found local snapshot: " + state);
bin.close();
}catch (FileNotFoundException e){
logger.info("No local snapshot present.");
}
// remote TODO: must ask min. GSQ replicas
for(String h : partitions.getReplicas(token)){
if(h.contains(":")){
h = "[" + h + "]";
}
try{
URL url = new URL("http://" + h + ":8080/state");
HttpURLConnection con = (HttpURLConnection)url.openConnection();
isr = new InputStreamReader(con.getInputStream());
BufferedReader in = new BufferedReader(isr);
Map<Integer, Long> nstate = new HashMap<Integer, Long>();
while ((line = in.readLine()) != null){
String[] s = line.split("=");
nstate.put(Integer.parseInt(s[0]),Long.parseLong(s[1]));
}
if(state.isEmpty()){
state = nstate;
host = h;
}else if(Replica.newerState(nstate,state)){
state = nstate;
host = h;
}
logger.info("Replica found remote snapshot: " + nstate + " (" + h + ")");
in.close();
}catch(Exception e){
logger.error("Error getting state from " + h,e);
}
}
InputStream in;
if(host != null){
logger.info("Use remote snapshot from host " + host);
download_active = true;
URL url = new URL("http://" + host + ":8080/snapshot");
HttpURLConnection con = (HttpURLConnection)url.openConnection();
logger.info("Open remote host " + host + " size:" + con.getContentLengthLong() + " timeout:" + con.getReadTimeout());
in = con.getInputStream();
}else{
in = new FileInputStream(new File(snapshot_file));
}
ObjectInputStream ois = new ObjectInputStream(in);
logger.info("Start reading remote snapshot ... ");
@SuppressWarnings("unchecked")
Map<String,byte[]> m = (Map<String,byte[]>) ois.readObject();
logger.info(" ... read " + m.size() + " entries.");
ois.close();
in.close();
synchronized(db){
db.clear();
db.putAll(m);
byte[] b = null;
for(int i = 1;i<max_ring+1;i++){
if((b = db.get("r:" + i)) != null){
instances.put(i,Long.valueOf(new String(b)));
}
}
}
logger.info("Replica installed snapshot instance: " + instances);
download_active = false;
return instances;
}
public void close(){
if(httpd != null){
httpd.stop(1);
}
}
public static class SendFile implements HttpHandler {
private final File file;
public SendFile(String file){
this.file = new File(file);
}
public void handle(HttpExchange t) throws IOException {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
byte[] b = new byte[(int)file.length()];
bis.read(b, 0, b.length); //TODO: FIXME: keeps file in mem!
bis.close();
fis.close();
t.sendResponseHeaders(200, b.length);
OutputStream os = t.getResponseBody();
os.write(b);
os.close();
}
}
}