package org.corfudb.util.quickcheck; import com.google.common.io.ByteStreams; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import lombok.extern.slf4j.Slf4j; import org.codehaus.plexus.util.ExceptionUtils; import org.corfudb.infrastructure.CorfuServer; import org.corfudb.infrastructure.LayoutServer; import org.corfudb.protocols.wireprotocol.LayoutPrepareResponse; import org.corfudb.runtime.CorfuRuntime; import org.corfudb.runtime.clients.BaseClient; import org.corfudb.runtime.clients.LayoutClient; import org.corfudb.runtime.clients.ManagementClient; import org.corfudb.runtime.clients.NettyClientRouter; import org.corfudb.runtime.exceptions.OutrankedException; import org.corfudb.runtime.exceptions.WrongEpochException; import org.corfudb.runtime.view.Layout; import org.corfudb.util.GitRepositoryState; import org.docopt.Docopt; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import static org.corfudb.util.quickcheck.QCUtil.replyErr; import static org.corfudb.util.quickcheck.QCUtil.replyOk; @Slf4j public class QCLayout { private static Map<String, NettyClientRouter> routers = new ConcurrentHashMap<>(); private static Map<String, CorfuRuntime> setEpochRTs = new ConcurrentHashMap<>(); private static final String USAGE = "quickcheck interface legacy code.\n" + "\n" + "Usage:\n" + "\tcorfu_layout query <address>:<port> [-d <level>] [-e epoch] [-p <qapp>]\n" + "\tcorfu_layout bootstrap <address>:<port> [-l <layout>|-s] [-d <level>] [-e epoch] [-p <qapp>]\n" + "\tcorfu_layout set_epoch <address>:<port> -e epoch [-d <level>] [-p <qapp>]\n" + "\tcorfu_layout prepare <address>:<port> -r <rank> [-d <level>] [-e epoch] [-p <qapp>]\n" + "\tcorfu_layout propose <address>:<port> -r <rank> [-l <layout>|-s] [-d <level>] [-e epoch] [-p <qapp>]\n" + "\tcorfu_layout committed <address>:<port> -r <rank> [-l <layout>] [-d <level>] [-e epoch] [-p <qapp>]\n" + "\tcorfu_layout getClientID <address>:<port> [-d <level>] [-e epoch] [-p <qapp>]\n" + "\n" + "Options:\n" + " -l <layout>, --layout-file=<layout> Path to a JSON file describing the \n" + " desired layout. If not specified and\n" + " --single not specified, takes input from stdin.\n" + " -s, --single Generate a single node layout, with the node \n" + " itself serving all roles. \n" + " -r <rank>, --rank=<rank> The rank to use for a Paxos operation. \n" + " -d <level>, --log-level=<level> Set the logging level, valid levels are: \n" + " ERROR,WARN,INFO,DEBUG,TRACE [default: INFO].\n" + " -e <epoch>, --epoch=<epoch> Set the epoch for the client request PDU." + " -p <qapp>, --quickcheck-ap-prefix=<qapp> Set QuickCheck addressportPrefix." + " -h, --help Show this screen\n" + " --version Show version\n"; public static String[] main(String[] args) { if (args != null && args.length > 0 && args[0].contentEquals("reboot")) { LayoutServer ls = CorfuServer.getLayoutServer(); if (ls != null) { ls.shutdown(); CorfuServer.addLayoutServer(); return replyOk(); } else { return replyErr("No active layout server"); } } // Parse the options given, using docopt. Map<String, Object> opts = new Docopt(USAGE).withVersion(GitRepositoryState.getRepositoryState().describe).parse(args); // Configure base options // configureBase(opts); // Parse host address and port String addressport = (String) opts.get("<address>:<port>"); String host = addressport.split(":")[0]; Integer port = Integer.parseInt(addressport.split(":")[1]); String qapp = (String) opts.get("<qapp>"); String addressportPrefix = ""; if (qapp != null) { addressportPrefix = qapp; } NettyClientRouter router; if ((router = routers.get(addressportPrefix + addressport)) == null) { // Create a client router and get layout. log.trace("Creating router for {} ++ {}:{}", addressportPrefix, port); router = new NettyClientRouter(host, port); router.addClient(new BaseClient()) .addClient(new LayoutClient()) .start(); routers.putIfAbsent(addressportPrefix + addressport, router); } router = routers.get(addressportPrefix + addressport); Long epoch = 0L; if (opts.get("--epoch") != null) { epoch = Long.parseLong((String) opts.get("--epoch")); log.trace("Specify router's epoch as " + epoch); router.setEpoch(epoch); } else { try { Layout l = router.getClient(LayoutClient.class).getLayout().get(); if (l != null) { log.trace("Set router's epoch to " + l.getEpoch()); router.setEpoch(l.getEpoch()); } else { log.trace("Cannot set router's epoch"); } } catch (Exception e) { return replyErr("ERROR Exception getting initial epoch " + e.getCause()); } } if ((Boolean) opts.get("getClientID")) { String clientID = router.getClientID().toString(); return replyOk(clientID); } else if ((Boolean) opts.get("query")) { try { Layout l = router.getClient(LayoutClient.class).getLayout().get(); Gson gs = new GsonBuilder().setPrettyPrinting().create(); return replyOk("layout: " + gs.toJson(l)); } catch (ExecutionException ex) { if (ex.getCause().getClass() == WrongEpochException.class) { WrongEpochException we = (WrongEpochException) ex.getCause(); return replyErr("Exception during query", ex.getCause().toString(), "correctEpoch: " + we.getCorrectEpoch(), "stack: " + ExceptionUtils.getStackTrace(ex)); } else { return replyErr("Exception during query", ex.getCause().toString(), "stack: " + ExceptionUtils.getStackTrace(ex)); } } catch (Exception e) { return replyErr("ERROR Exception getting layout" + e); } } else if ((Boolean) opts.get("bootstrap")) { Layout l = getLayout(opts); log.debug("Bootstrapping with layout {}", l); try { if (router.getClient(LayoutClient.class).bootstrapLayout(l).get()) { router.getClient(ManagementClient.class).bootstrapManagement(l).get(); return replyOk(); } else { return replyErr("NACK"); } } catch (ExecutionException ex) { return replyErr("Exception bootstrapping layout", ex.getCause().toString()); } catch (Exception e) { return replyErr("Exception bootstrapping layout", e.toString()); } } else if ((Boolean) opts.get("set_epoch")) { log.debug("Set epoch with new epoch={}", epoch); try { CorfuRuntime rt; if ((rt = setEpochRTs.get(addressport)) == null) { log.trace("Creating CorfuRuntime for set_epoch for {} ", addressport); rt = new CorfuRuntime().addLayoutServer(addressport); setEpochRTs.putIfAbsent(addressport, rt); } rt = setEpochRTs.get(addressport); // Construct a layout that contains just enough to allow .moveServersToEpoch() // to send SET_EPOCH to our desired endpoint. List<String> ls = new ArrayList(1); ls.add(addressport); List<String> none1 = new ArrayList(0); List<Layout.LayoutSegment> none2 = new ArrayList(0); Layout tmpLayout = new Layout(ls, none1, none2, epoch); tmpLayout.setRuntime(rt); tmpLayout.moveServersToEpoch(); return replyOk(); } catch (WrongEpochException we) { return replyErr("Exception during set_epoch", we.getCause() == null ? "WrongEpochException" : we.getCause().toString(), "correctEpoch: " + we.getCorrectEpoch(), "stack: " + ExceptionUtils.getStackTrace(we)); } catch (Exception e) { return replyErr("Exception during set_epoch", e.toString(), ExceptionUtils.getStackTrace(e)); } } else if ((Boolean) opts.get("prepare")) { long rank = Long.parseLong((String) opts.get("--rank")); log.debug("Prepare with new rank={}", rank); try { LayoutPrepareResponse r = router.getClient(LayoutClient.class).prepare(epoch, rank).get(); Layout r_layout = r.getLayout(); if (r_layout == null) { return replyOk("ignored: ignored"); } else { return replyOk("layout: " + r_layout.asJSONString()); } } catch (ExecutionException ex) { if (ex.getCause().getClass() == OutrankedException.class) { OutrankedException oe = (OutrankedException) ex.getCause(); return replyErr("Exception during prepare", ex.getCause().toString(), "newRank: " + Long.toString(oe.getNewRank()), "layout: " + (oe.getLayout() == null ? "" : oe.getLayout().asJSONString())); } else if (ex.getCause().getClass() == WrongEpochException.class) { WrongEpochException we = (WrongEpochException) ex.getCause(); return replyErr("Exception during prepare", ex.getCause().toString(), "correctEpoch: " + we.getCorrectEpoch(), "stack: " + ExceptionUtils.getStackTrace(ex)); } else { return replyErr("Exception during prepare", ex.getCause().toString(), "stack: " + ExceptionUtils.getStackTrace(ex)); } } catch (Exception e) { return replyErr("Exception during prepare", e.toString(), ExceptionUtils.getStackTrace(e)); } } else if ((Boolean) opts.get("propose")) { long rank = Long.parseLong((String) opts.get("--rank")); Layout l = getLayout(opts); log.debug("Propose with new rank={}, layout={}", rank, l); try { if (router.getClient(LayoutClient.class).propose(l.getEpoch(), rank, l).get()) { return replyOk(); } else { return replyErr("NACK"); } } catch (ExecutionException ex) { if (ex.getCause().getClass() == OutrankedException.class) { OutrankedException oe = (OutrankedException) ex.getCause(); return replyErr("Exception during propose", ex.getCause().toString(), "newRank: " + Long.toString(oe.getNewRank()), "stack: " + ExceptionUtils.getStackTrace(ex)); } else if (ex.getCause().getClass() == WrongEpochException.class) { WrongEpochException we = (WrongEpochException) ex.getCause(); return replyErr("Exception during propose", ex.getCause().toString(), "correctEpoch: " + we.getCorrectEpoch(), "stack: " + ExceptionUtils.getStackTrace(ex)); } else { return replyErr("Exception during propose", ex.getCause().toString(), "stack: " + ExceptionUtils.getStackTrace(ex)); } } catch (Exception e) { return replyErr("Exception during propose", e.toString(), "stack: " + ExceptionUtils.getStackTrace(e)); } } else if ((Boolean) opts.get("committed")) { long rank = Long.parseLong((String) opts.get("--rank")); Layout l = getLayout(opts); log.debug("Propose with new rank={}", rank); try { if (router.getClient(LayoutClient.class).committed(l.getEpoch(), l).get()) { return replyOk(); } else { return replyErr("NACK"); } } catch (ExecutionException ex) { if (ex.getCause().getClass() == WrongEpochException.class) { WrongEpochException we = (WrongEpochException) ex.getCause(); return replyErr("Exception during commit", ex.getCause().toString(), "correctEpoch: " + we.getCorrectEpoch(), "stack: " + ExceptionUtils.getStackTrace(ex)); } else { return replyErr("Exception during commit", ex.getCause().toString(), "stack: " + ExceptionUtils.getStackTrace(ex)); } } catch (Exception e) { return replyErr("Exception during commit", e.toString(), "stack: " + ExceptionUtils.getStackTrace(e)); } } return replyErr("Hush, compiler."); } private static Layout getLayout(Map<String, Object> opts) { Layout oLayout = null; if ((Boolean) opts.getOrDefault("--single", false)) { String localAddress = (String) opts.get("<address>:<port>"); log.info("Single-node mode requested, initializing layout with single log unit and sequencer at {}.", localAddress); oLayout = new Layout( Collections.singletonList(localAddress), Collections.singletonList(localAddress), Collections.singletonList(new Layout.LayoutSegment( Layout.ReplicationMode.CHAIN_REPLICATION, 0L, -1L, Collections.singletonList( new Layout.LayoutStripe( Collections.singletonList(localAddress) ) ) )), 0L ); } else if (opts.get("--layout-file") != null) { try { String f = (String) opts.get("--layout-file"); String layoutJson = new String(Files.readAllBytes(Paths.get ((String) opts.get("--layout-file")))); Gson g = new GsonBuilder().create(); oLayout = g.fromJson(layoutJson, Layout.class); } catch (Exception e) { throw new RuntimeException("Failed to read from file (not a JSON file?)", e); } } if (oLayout == null) { log.trace("Reading layout from stdin."); try { String s = new String(ByteStreams.toByteArray(System.in)); Gson g = new GsonBuilder().create(); oLayout = g.fromJson(s, Layout.class); } catch (Exception e) { throw new RuntimeException("Failed to read from stdin (not valid JSON?)", e); } } log.debug("Parsed layout to {}", oLayout); return oLayout; } }