package org.curriki.tools.loadtest; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.PosixParser; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.impl.Log4JLogger; import org.apache.http.HttpResponse; import org.apache.log4j.Level; import org.apache.log4j.helpers.ISO8601DateFormat; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; import org.jdom2.filter.ElementFilter; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.List; /** */ public class TestClusteringWorksOnTitles { private static Log LOG = LogFactory.getLog(TestClusteringWorksOnTitles.class); public static void main(String[] args) throws Exception { Options options = new Options(); options.addOption("q", "quiet", false, "Prevents output except in error cases."); options.addOption("h", "help", false, "Displays this help message."); options.addOption("s", "sleep", true, "Sleep n milliseconds between calls."); options.addOption("d", "delete", false, "Delete (and empties trash) before testing (sounds buggy)."); options.addOption("u", "user", true, "User-name to post to (required)."); options.addOption("p", "password", true, "Password for that username (required)."); options.addOption("d", "debug", false, "Detail all communications."); options.addOption("b", "backEnd1", true, "Back-end URL 1 (optional)."); options.addOption("c", "backEnd2", true, "Back-end URL 2 (optional)."); options.addOption("w", "witness", true, "Witness file (outputs json, optional)."); CommandLine cli = new PosixParser().parse(options, args); List argsList = cli.getArgList(); if(argsList.size()!=4 || !cli.hasOption("user") || !cli.hasOption("password")) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("TestClusteringWorksOnTitles [OPTIONS] <baseURL> <Space/PageName> <route1> <route2>", "A tool to test XWiki's clustering between two cluster routes which are on the back of " + "a load-balancer which follows the convention to append the route-name at end" + "of the JSESSIONID cookie.", options, "(example: TestClusteringWorksOnTitles --user u --password p http://www.curriki.org/ Sandbox/TestClusteringPage prod-app failover-app)", true); System.exit(1); } TestClusteringWorksOnTitles t = new TestClusteringWorksOnTitles(); try { t.baseURL = (String) argsList.get(0); t.backEndUrl1 = cli.getOptionValue("backEnd1"); t.backEndUrl2 = cli.getOptionValue("backEnd2"); t.userName = cli.getOptionValue("user"); t.password = cli.getOptionValue("password"); t.doDeletions = cli.hasOption("delete"); t.doSleep = cli.hasOption("sleep"); t.quiet = cli.hasOption("quiet"); t.witnessFile = cli.getOptionValue("witness"); if(t.quiet) ((Log4JLogger) LOG).getLogger().getParent().setLevel(Level.ERROR); if(cli.hasOption("debug")) ((Log4JLogger) LOG).getLogger().getParent().setLevel(Level.DEBUG); t.page = (String) argsList.get(1); t.node1 = (String) argsList.get(2); t.node2 = (String) argsList.get(3); t.init(); t.run(); t.notifyOk(true); System.exit(0); } catch (Throwable e) { LOG.error("TestClusteringWorksOnTitles failed.", e); if(t!=null) LOG.info("Traces would be visible on " + t.baseURL + "xwiki/bin/view/" + t.page); t.notifyOk(false); System.exit(1); } } private String baseURL, backEndUrl1 = null, backEndUrl2 = null; private String userName, password; private String node1, node2; private String witnessFile; private String page = "Sandbox/TempForTestingClustering"; private XWikiHttpClient frontEndClient, backEndClient1 = null, backEndClient2 = null; private boolean doDeletions = false, quiet = false, doSleep = false; private void init() throws IOException { this.frontEndClient = new XWikiHttpClient(baseURL, userName, password); if(backEndUrl1!=null) this.backEndClient1 = new XWikiHttpClient(backEndUrl1, userName, password); if(backEndUrl2!=null) this.backEndClient2 = new XWikiHttpClient(backEndUrl2, userName, password); } private void run() throws Exception { HttpResponse response; Document doc; frontEndClient.tryLogin(); if(backEndClient1!=null) backEndClient1.tryLogin(); if(backEndClient2!=null) backEndClient2.tryLogin(); LOG.info("--- Now on " + frontEndClient.getClusterNodeOfSession()); // get first to verify if there if(doDeletions) { response = frontEndClient.getPage("/xwiki/bin/view/" + page + "?language=en"); if(response.getStatusLine().getStatusCode()==200) { // delete first LOG.info("--- deleting earlier versions."); response = frontEndClient.getPage("/xwiki/bin/delete/"+page+"?confirm=1&language=en"); doc = Checker.parseResponse(response); Checker.checkDocumentHas("LEGEND", null, doc, "Delete"); for(Element elt: doc.getRootElement().getDescendants(new ElementFilter("A",Checker.HTMLNS))) { String href = elt.getAttributeValue("href"); if(href!=null && href.startsWith("/xwiki/bin/delete/" + page) && href.contains("id=")) { LOG.info("---- deleting: " + href); frontEndClient.getPage(href.substring(1)); } } } else { LOG.info("--- earlier version not found."); } } if(doSleep) Thread.sleep(10000); String newContent = "Title Edited On " + frontEndClient.getClusterNodeOfSession() + " " + ((int) (Math.random()*10000)); uploadContent(frontEndClient, newContent, newContent); if(doSleep) Thread.sleep(10000); checkItArrivedOn(newContent, frontEndClient, backEndClient2, node1, node2); for(int i=0; i<5; i++) { LOG.info(""); LOG.info("- STEP: " + i); if(doSleep) Thread.sleep(10000); newContent = "Title Edited On " + frontEndClient.getClusterNodeOfSession() + " " + ((int) (Math.random()*10000)); if(i % 2 == 1) { uploadContent(frontEndClient, newContent, newContent); if(doSleep) Thread.sleep(10000); checkItArrivedOn(newContent, frontEndClient, backEndClient2, node1, node2); } else { uploadContent(frontEndClient, newContent, newContent); if(doSleep) Thread.sleep(10000); checkItArrivedOn(newContent, frontEndClient, backEndClient1, node2, node1); } } frontEndClient.getPage("/xwiki/bin/cancel/" + page + "?language=en"); LOG.info("-- Cleaned up."); LOG.info("Traces would be visible on " + baseURL + "xwiki/bin/view/" + page); } private void checkItArrivedOn(String newContent, XWikiHttpClient frontEndClient, XWikiHttpClient backEndClient, String oldNode, String targetNode) throws IOException, JDOMException { LOG.info("-- Checking it arrived on other cluster node ("+targetNode+")."); frontEndClient.changeClusterNode(oldNode, targetNode); HttpResponse response; Document doc; response = frontEndClient.getPage("/xwiki/bin/edit/" + page); LOG.info("--- Now on " + frontEndClient.getClusterNodeOfSession()); doc = Checker.parseResponse(response); Checker.checkDocumentHas("TEXTAREA", "content", doc, newContent); // [@name='content'] String comment = Checker.findCommentStartingWith(doc, "generated on "); if(comment==null || !comment.toLowerCase().startsWith("generated on " + targetNode)) LOG.error("--- wrong node delivered it: " + comment); else LOG.info("--- delivered from correct node: " + targetNode); LOG.info("--- It did arrive (front "+ frontEndClient+")."); if(backEndClient!=null) { response = backEndClient.getPage("/xwiki/bin/edit/" + page); doc = Checker.parseResponse(response); Checker.checkDocumentHas("TEXTAREA", "content", doc, newContent); // [@name='content'] LOG.info("--- It did arrive (back, " + backEndClient + ")."); } } private void uploadContent(XWikiHttpClient client, String title, String newContent) throws Exception { HttpResponse response; LOG.info("-- Uploading \"" + newContent + "\" to "+ page +"."); response = client.postForm(baseURL + "xwiki/bin/save/" + page + "?language=en", "title:" + newContent, "content:" + newContent, "comment:edited"); Checker.assertResponseIsRedirect(response, baseURL + "xwiki/bin/view/" + page); LOG.info("--- redirected properly."); } private void notifyOk(boolean result) { if(witnessFile!=null && witnessFile.length()>0) { File file = new File(witnessFile); try { org.apache.commons.io.FileUtils.writeStringToFile(file, "window.clusteringCheckResult = [" + "date: " + new ISO8601DateFormat().format(new Date()) + ", " + "success: " + result + "]"); } catch (IOException e) { LOG.error("Can't write to \"" + file + "\". No witness.", e); } } } }