From: Giovanni Meo Date: Wed, 11 Dec 2013 14:39:56 +0000 (+0000) Subject: Merge "Improve logging of persister and netconf client." X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~207 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=aea39142ff1a2a5fc6183cf11f99fe2199259be8;hp=9e85156750c913457315e6593adfa362e1a443cf Merge "Improve logging of persister and netconf client." --- diff --git a/opendaylight/commons/controller-maven-plugin/pom.xml b/opendaylight/commons/controller-maven-plugin/pom.xml new file mode 100644 index 0000000000..1ab4e7067b --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + + + org.opendaylight.controller + commons.opendaylight + 1.4.1-SNAPSHOT + ../../commons/opendaylight + + + controller-maven-plugin + 0.1.0-SNAPSHOT + maven-plugin + + controller-maven-plugin + Maven Plugin for controlling the launch of the controller. + http://maven.apache.org + + + UTF-8 + + + + + + org.apache.maven + maven-plugin-api + 2.0 + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.2 + provided + + + + com.sun + tools + system + 7 + ${java.home}/../lib/tools.jar + + + junit + junit + 4.8.1 + test + + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.2 + + controller-maven-plugin + true + + + + mojo-descriptor + + descriptor + + + + help-goal + + helpmojo + + + + + + + + diff --git a/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/AbstractControllerMojo.java b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/AbstractControllerMojo.java new file mode 100644 index 0000000000..39ca71c62c --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/AbstractControllerMojo.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.maven.plugin; + +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Parameter; + +import org.opendaylight.controller.maven.plugin.util.JavaProcess; +import org.opendaylight.controller.maven.plugin.util.ProcessMonitor; + +/** + * Base controller mojo which handles common operations + */ +public abstract class AbstractControllerMojo extends AbstractMojo { + public static final String OS_NAME = System.getProperty("os.name"); + public static final boolean WIN = OS_NAME.toUpperCase().contains("WINDOWS"); + public static final String JAVA_HOME = "JAVA_HOME"; + public static final boolean skip = Boolean.getBoolean("controller.startup.skip"); + public static final String MAIN_CLASS = "org.eclipse.equinox.launcher.Main"; + public static final String CTRL_PROP = "opendaylight.controller"; + + /** + * The home directory where controller is installed + */ + @Parameter( required = false ) + protected File controllerHome; + + /** + * The address on which controller is listening + */ + @Parameter( defaultValue = "localhost") + protected String controllerHost; + + /** + * The admin web port + */ + @Parameter( defaultValue = "8080") + protected int controllerWebPort; + + /** + * The openflow port + */ + @Parameter( defaultValue = "6633") + protected int controllerOFPort; + + /** + * Additional environment variables passed when starting the controller + * process. + */ + @Parameter(required = false) + protected Properties controllerShellVariables; + + /** + * The script name to invoke + */ + @Parameter(required = false) + protected String controllerStartScriptName; + + /** + * The username + */ + @Parameter(required = false) + protected String controllerUsername; + + /** + * The password + */ + @Parameter(required = false) + protected String controllerPassword; + + /** + * pidFile location + */ + @Parameter(required = false) + protected File pidFile; + + protected final ProcessMonitor procMon = ProcessMonitor.load(); + + public abstract void start() throws MojoExecutionException, MojoFailureException; + + public void execute() throws MojoExecutionException, MojoFailureException { + if (skip) return; + validateArgs(); + start(); + } + + protected URL getWebUrl() { + try { + return new URL("http", controllerHost, controllerWebPort, "/"); + } catch (MalformedURLException e) { + throw new IllegalArgumentException( + "controller host:port is Malformed: " + controllerHost + " " + controllerWebPort, e); + } + + } + + protected void validateArgs() throws IllegalArgumentException { + // System property and environment variable override the default setting + String odlHome = System.getProperty("controllerHome"); + if (odlHome != null) { + controllerHome = new File(odlHome); + } + if (controllerHome == null) { + getLog().error("controllerHome cannot be determined from controllerHome " + + "property or ONE_HOME env variable"); + throw new IllegalArgumentException("controllerHome cannot be determined."); + } + if (!controllerHome.exists()) { + throw new IllegalArgumentException( + "controllerHome does not exist: " + controllerHome); + } + if (controllerUsername == null) { + controllerUsername = System.getProperty("controllerUsername"); + } + if (controllerPassword == null) { + controllerPassword= System.getProperty("controllerPassword"); + } + URL u = getWebUrl(); + getLog().info("Controller Home : " + controllerHome); + getLog().info("Controller Url : " + u); + getLog().info("Controller credentials: " + controllerUsername + + "/" + controllerPassword); + } + + protected Process invokeScript(List args, String log) + throws MojoFailureException, MojoExecutionException + { + ProcessBuilder pb = new ProcessBuilder(); + List cmd = new ArrayList(); + cmd.add(getScript()); + if (args != null) { + for (String s : args) { + // on windows args containing equals symbols need to be quoted + if (WIN && s.contains("=") && !s.startsWith("\"")) { + cmd.add("\"" + s + "\""); + } else { + cmd.add(s); + } + } + } + pb.command(cmd); + pb.directory(controllerHome); + pb.redirectErrorStream(true); + pb.inheritIO(); + Map env = pb.environment(); + if (controllerShellVariables != null) { + for (Enumeration e = controllerShellVariables.propertyNames(); e.hasMoreElements();) { + String n = (String) e.nextElement(); + env.put(n, controllerShellVariables.getProperty(n)); + } + } + String jh = env.get(JAVA_HOME); + if (jh == null) env.put(JAVA_HOME, System.getProperty("java.home")); + try { + getLog().info("Invoking process " + pb.command()); + return pb.start(); + } catch (IOException e) { + throw new MojoExecutionException(e.getMessage()); + } + } + + private String getScript() throws MojoFailureException { + File script = null; + if (controllerStartScriptName != null && !"".equals(controllerStartScriptName) ) { + script = new File(controllerStartScriptName); + if (!script.exists()) { + // try relative path + script = new File(controllerHome, controllerStartScriptName); + } + if (script.exists()) return script.getAbsolutePath(); + throw new MojoFailureException("Script not found: " + controllerStartScriptName); + } + // try default + script = new File(controllerHome, "run." + (WIN ? "bat" : "sh") ); + if (script.exists()) return script.getAbsolutePath(); + throw new MojoFailureException("Cannot find a default script to launch."); + } + + protected boolean canConnect() { + try { + URL url = getWebUrl(); + HttpURLConnection con; + con = (HttpURLConnection) url.openConnection(); + return (con.getResponseCode() > 0); + } catch (IOException e) { + return false; + } + } + + public void killControllers() { + getLog().info("Checking environment for stray processes."); + List jvms = procMon.getProcesses(MAIN_CLASS, CTRL_PROP); + for (JavaProcess j : jvms) { + getLog().info("Killing running process: " + j); + ProcessMonitor.kill(j.getPid()); + } + // cleanup pid files + getLog().info("Checking left over pid file: " + pidFile); + if (pidFile != null && pidFile.exists()) { + getLog().info("Cleaning up pid file : " + pidFile); + pidFile.delete(); + } + } + + public boolean isControllerRunning() { + return !procMon.getProcesses(MAIN_CLASS, CTRL_PROP).isEmpty(); + } + +} diff --git a/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/StartControllerMojo.java b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/StartControllerMojo.java new file mode 100644 index 0000000000..0a3bee42f4 --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/StartControllerMojo.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.maven.plugin; + +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + + +/** + * Starts the controller + */ +@Mojo( name = "run", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST ) +public class StartControllerMojo extends AbstractControllerMojo { + public static final String REDIRECT_LOG = "controller.out"; + + /** + * The timeout value for starting the controller. Defaults to 60 secs + */ + @Parameter(defaultValue = "60") + public int timeoutSecs = 60; + + /** + * The startArgs for starting the controller + */ + @Parameter(required = false) + protected List startArgs = new ArrayList(); + + /** + * The time to wait after successfully connecting to the controller and + * before returning from execution. + */ + @Parameter(required = false) + protected int warmupTimeSecs = 10; + + + @Override + public void start() throws MojoExecutionException, MojoFailureException { + killControllers(); + // if we can still connect to a controller, bail out + if (canConnect()) { + getLog().error("A controller is already running. Shutdown and retry."); + throw new MojoFailureException("Controller is already running."); + } + startArgs.add("-D" + CTRL_PROP); + Process process = invokeScript(startArgs, REDIRECT_LOG); + getLog().info("Controller starting... (waiting for open ports)"); + try { + waitForListening(process); + getLog().info("Controller port open. Waiting for warmup: " + + warmupTimeSecs); + Thread.sleep(warmupTimeSecs*1000); + } catch (Exception e) { + throw new MojoExecutionException(e.getMessage()); + } + getLog().info("Controller started successfully."); + } + + protected boolean waitForListening(Process process) + throws MalformedURLException, InterruptedException, MojoExecutionException + { + long timeElapsedMillis = 0L; + long sleepTimeMillis = 2000L; // 2 secs + long timeoutMillis = timeoutSecs * 1000; + + while (timeElapsedMillis < timeoutMillis) { + long timeRemaining = timeoutMillis - timeElapsedMillis; + sleepTimeMillis *= 2; + long toSleep = (sleepTimeMillis > timeRemaining) + ? timeRemaining : sleepTimeMillis; + Thread.sleep(toSleep); + timeElapsedMillis += toSleep; + if (canConnect()) { + return true; + } + if (!isControllerRunning()) { + throw new MojoExecutionException("Process seems to have exited prematurely."); + } + } + return false; + } +} diff --git a/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/StopControllerMojo.java b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/StopControllerMojo.java new file mode 100644 index 0000000000..579778d150 --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/StopControllerMojo.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.maven.plugin; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; + +/** + * Stop controller + */ +@Mojo( name = "stop", defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST ) +public class StopControllerMojo extends AbstractControllerMojo { + private static final boolean SKIP_STOP = Boolean.getBoolean("skipControllerStop"); + + @Override + public void start() throws MojoExecutionException, MojoFailureException { + if (SKIP_STOP) { + getLog().info("Controller STOP is skipped per configuration " + + "(-DskipControllerStop=true)."); + return; + } + if (canConnect()) { + List args = new ArrayList(); + args.add("-stop"); + Process proc = invokeScript(args, null); + try { + int status = proc.waitFor(); + if (status == 0) { + getLog().info("Controller stopped."); + } else { + getLog().error("Error stopping controller. See stdout log for details."); + } + } catch (InterruptedException ie) { + throw new MojoExecutionException("Error stopping controller : " + ie.getMessage()); + } + } else { + getLog().info("Controller not running."); + } + // cleanup for any hung processes + killControllers(); + } + +} diff --git a/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/JavaProcess.java b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/JavaProcess.java new file mode 100644 index 0000000000..95da34ffad --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/JavaProcess.java @@ -0,0 +1,57 @@ + +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.maven.plugin.util; + +import java.util.Properties; + +public class JavaProcess { + private final int pid; + private final String mainClass; + private final Properties systemProperties = new Properties(); + + public JavaProcess(int id, String cls) { + this.pid = id; + this.mainClass = cls; + } + + public void setSystemProperties(String line) { + if (line == null || line.length() == 0) return; + String[] tokens = line.split("\\s"); + for (String t : tokens) setSystemProperty(t); + } + + public void setSystemProperty(String line) { + if (line.startsWith("-D")) { + int x = line.indexOf('='); + if (x > -1) { + systemProperties.put(line.substring(2, x), line.substring(x+1)); + } else { + systemProperties.put(line.substring(2), ""); + } + } + } + + public int getPid() { + return pid; + } + + public String getMainClass() { + return mainClass; + } + + public Properties getSystemProperties() { + return systemProperties; + } + + @Override + public String toString() { + return "pid:" + pid + " class:" + mainClass + + " system-properties:" + systemProperties.toString(); + } +} diff --git a/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/JpsProcessMonitor.java b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/JpsProcessMonitor.java new file mode 100644 index 0000000000..8474266df2 --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/JpsProcessMonitor.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.maven.plugin.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Uses JPS tool to monitor java local processes + */ +public class JpsProcessMonitor extends ProcessMonitor { + private final String jpsTool; + + public JpsProcessMonitor() { + String jh = System.getProperty("java.home"); + File jps = new File(jh + SEP + "bin" + SEP + "jps"); + if (!jps.exists()) { + // try one dir above + jps = new File(jh + SEP + ".." + SEP + "bin" + SEP + "jps"); + if (!jps.exists()) { + throw new IllegalStateException("jps tool cannot be located."); + } + } + jpsTool = jps.getAbsolutePath(); + } + + @Override + public List getProcesses() { + if (jpsTool == null) return super.getProcesses(); + List jvms = new ArrayList(); + try { + ProcessBuilder pb = new ProcessBuilder(); + pb.command(new String[] { jpsTool, "-mlvV"} ); + pb.redirectErrorStream(true); + Process process = pb.start(); + BufferedReader br = new BufferedReader( + new InputStreamReader(process.getInputStream())); + String line = null; + while ( (line = br.readLine()) != null) { + JavaProcess j = parseLine(line); + if (j != null) jvms.add(j); + } + } catch (Exception e) { + e.printStackTrace(); + } + return jvms; + } + + public static JavaProcess parseLine(String line) { + String[] tokens = line.split("\\s"); + if (tokens.length < 2) { + System.out.println("Unable to parse line: " + line); + return null; + } + int idx = 0; + int pid = Integer.parseInt(tokens[idx++]); + String mainClass = ""; + if (!tokens[idx].startsWith("-")) { + mainClass = tokens[idx++]; + } + JavaProcess proc = new JavaProcess(pid, mainClass); + for (int i=idx; i -1; + + + + public void log(String msg) { + System.out.println("" + msg); + } + + public List getProcesses() { + return Collections.emptyList(); + } + + public List getProcesses(String mainClass, String systemPropertyKey) { + List result = new ArrayList(); + for (JavaProcess info : getProcesses()) { + if (info.getMainClass().equals(mainClass)) { + if (info.getSystemProperties().containsKey(systemPropertyKey)) { + result.add(info); + } + } + } + return result; + } + + public int kill(String mainClass) { + for (JavaProcess info : getProcesses()) { + if (info.getMainClass().equals(mainClass)) { + log("Killing process matching class: " + mainClass); + return kill(info.getPid()); + } + } + return -1; + } + + public static int kill(int pid) { + String cmd = WIN ? "TASKKILL /F /PID " + pid : "kill -SIGTERM " + pid; + try { + Process p = Runtime.getRuntime().exec(cmd); + p.waitFor(); + return p.exitValue(); + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + public static ProcessMonitor load() { + // load the providers dynamically to allow error handling + ProcessMonitor pm = load("org.opendaylight.controller.maven.plugin.util.VMProcessMonitor"); + if (pm == null) { + pm = load("org.opendaylight.controller.maven.plugin.util.JpsProcessMonitor"); + } + return (pm == null ? new ProcessMonitor() : pm); + } + + private static ProcessMonitor load(String clz) { + try { + Class c = Class.forName(clz); + return (ProcessMonitor) c.newInstance(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + // simple driver for basic manual testing + public static void main(String[] args) throws Exception { + ProcessMonitor pm = ProcessMonitor.load(); + System.out.println("==== " + pm); + for (JavaProcess info : pm.getProcesses()) { + System.out.println(info.toString()); + } + pm.kill("Foo"); + System.out.println("==== controller processses "); + for (JavaProcess info : pm.getProcesses( + "org.eclipse.equinox.launcher.Main", "opendaylight.controller")) + { + System.out.println(info.toString()); + pm.kill(info.getPid()); + } + } + +} diff --git a/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/VMProcessMonitor.java b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/VMProcessMonitor.java new file mode 100644 index 0000000000..fdf232a63c --- /dev/null +++ b/opendaylight/commons/controller-maven-plugin/src/main/java/org/opendaylight/controller/maven/plugin/util/VMProcessMonitor.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.maven.plugin.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import sun.jvmstat.monitor.HostIdentifier; +import sun.jvmstat.monitor.MonitoredHost; +import sun.jvmstat.monitor.MonitoredVm; +import sun.jvmstat.monitor.MonitoredVmUtil; +import sun.jvmstat.monitor.VmIdentifier; + +public class VMProcessMonitor extends ProcessMonitor { + + @Override + public List getProcesses() { + Set activeVmPids = null; + List result = new ArrayList(); + MonitoredHost monitoredHost = null; + MonitoredVm mvm = null; + try { + monitoredHost = MonitoredHost.getMonitoredHost( + new HostIdentifier((String) null)); + activeVmPids = monitoredHost.activeVms(); + } catch (Exception e) { + throw new IllegalStateException("Error accessing VM", e); + } + for (Integer vmPid : activeVmPids) { + try { + mvm = monitoredHost.getMonitoredVm( + new VmIdentifier(vmPid.toString())); + JavaProcess proc = new JavaProcess(vmPid, + MonitoredVmUtil.mainClass(mvm, true)); + proc.setSystemProperties(MonitoredVmUtil.jvmArgs(mvm)); + proc.setSystemProperties(MonitoredVmUtil.jvmFlags(mvm)); + result.add(proc); + } catch(Exception e2) { + log("Error connecting to pid: " + vmPid + " reason:" + + e2.getMessage()); + e2.printStackTrace(); + } finally { + if (mvm != null) { + mvm.detach(); + } + } + } + return result; + } + + +} diff --git a/opendaylight/commons/opendaylight/pom.xml b/opendaylight/commons/opendaylight/pom.xml index dd73815b34..07c89166f9 100644 --- a/opendaylight/commons/opendaylight/pom.xml +++ b/opendaylight/commons/opendaylight/pom.xml @@ -70,6 +70,8 @@ 0.5.3.201107060350 1.3.1 2.3.7 + 2.5 + 1.3.1 4.8.1 0.3.0-SNAPSHOT 0.5.9-SNAPSHOT diff --git a/opendaylight/distribution/opendaylight/pom.xml b/opendaylight/distribution/opendaylight/pom.xml index 4c0b81f7d7..033d0573f9 100644 --- a/opendaylight/distribution/opendaylight/pom.xml +++ b/opendaylight/distribution/opendaylight/pom.xml @@ -461,6 +461,7 @@ + integrationtests false @@ -469,57 +470,41 @@ org.apache.maven.plugins - maven-dependency-plugin - 2.8 - - - copy - package - - copy - - - + maven-invoker-plugin + 1.5 - - - org.opendaylight.controller - sanitytest - ${controller.version} - jar - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - - sanity-test - package - - exec - - - - - ${java.home}/bin/java - - -cp - ./target/dependency/* - org.opendaylight.controller.distribution.Sanity - - - - ${java.home} - - - - + false + ../sanitytest + + pom.xml + + true + true + + clean + verify + + + + + integration-test + + install + run + + + + + + + + org.opendaylight.controller + controller-maven-plugin + 0.1.0-SNAPSHOT + + @@ -1347,13 +1332,6 @@ ${commons.httpclient.version} - - org.opendaylight.controller - sanitytest - ${controller.version} - - - @@ -1396,6 +1374,8 @@ + + diff --git a/opendaylight/distribution/opendaylight/runsanity.bat b/opendaylight/distribution/opendaylight/runsanity.bat deleted file mode 100644 index f219828bad..0000000000 --- a/opendaylight/distribution/opendaylight/runsanity.bat +++ /dev/null @@ -1,23 +0,0 @@ -rem Inject the sanitytest jar as a controller plugin -copy .\target\dependency\sanitytest*.jar .\target\distribution.opendaylight-osgipackage\opendaylight\plugins - -rem Store the current working directory in a variable so that we can get back to it later -set cwd=%cd% - -rem Switch to the distribution folder -cd .\target\distribution.opendaylight-osgipackage\opendaylight - -rem Run the controller -cmd.exe /c run.bat - -rem Store the exit value of the controller in a variable -set success=%ERRORLEVEL% - -rem Switch back to the directory from which this script was invoked -cd %cwd% - -rem Remove the sanitytest jar from the plugins directory -del .\target\distribution.opendaylight-osgipackage\opendaylight\plugins\sanitytest*.jar - -rem Exit using the exit code that we had captured earlier after running the controller -exit /b %SUCCESS% \ No newline at end of file diff --git a/opendaylight/distribution/opendaylight/runsanity.sh b/opendaylight/distribution/opendaylight/runsanity.sh deleted file mode 100755 index 4ee9555b97..0000000000 --- a/opendaylight/distribution/opendaylight/runsanity.sh +++ /dev/null @@ -1,24 +0,0 @@ -# Inject the sanitytest jar as a controller plugin -cp ./target/dependency/sanitytest*.jar ./target/distribution.opendaylight-osgipackage/opendaylight/plugins - -# Store the current working directory in a variable so that we can get back to it later -cwd=`pwd` - -# Switch to the distribution folder -cd ./target/distribution.opendaylight-osgipackage/opendaylight/ - -# Run the controller -./run.sh - -# Store the exit value of the controller in a variable -success=`echo $?` - -# Switch back to the directory from which this script was invoked -cd $cwd - -# Remove the sanitytest jar from the plugins directory -rm ./target/distribution.opendaylight-osgipackage/opendaylight/plugins/sanitytest*.jar - -# Exit using the exit code that we had captured earlier after running the controller -exit $success - diff --git a/opendaylight/distribution/opendaylight/src/assemble/bin.xml b/opendaylight/distribution/opendaylight/src/assemble/bin.xml index 8fea175614..1e8f34e5b7 100644 --- a/opendaylight/distribution/opendaylight/src/assemble/bin.xml +++ b/opendaylight/distribution/opendaylight/src/assemble/bin.xml @@ -27,7 +27,6 @@ com.sun.jersey:jersey-json com.sun.jersey:jersey-server org.opendaylight.controller:logging.bridge - org.opendaylight.controller:sanitytest ${artifact.groupId}.${artifact.artifactId}-${artifact.version}${dashClassifier?}.${artifact.extension} diff --git a/opendaylight/distribution/opendaylight/src/main/resources/run.bat b/opendaylight/distribution/opendaylight/src/main/resources/run.bat index 9d6ac8d1de..a8e33d2632 100644 --- a/opendaylight/distribution/opendaylight/src/main/resources/run.bat +++ b/opendaylight/distribution/opendaylight/src/main/resources/run.bat @@ -140,7 +140,7 @@ SET RUN_CMD="%JAVA_HOME%\bin\java.exe" -Dopendaylight.controller %extraJVMOpts% ECHO %RUN_CMD% if "%startEnabled%" NEQ "" ( - START /B cmd /C CALL %RUN_CMD% > %basedir%\logs\controller.out 2>&1 + START /B cmd /C CALL %RUN_CMD% ECHO Running controller in the background. ) else ( %RUN_CMD% diff --git a/opendaylight/distribution/sanitytest/pom.xml b/opendaylight/distribution/sanitytest/pom.xml index 9d5ba5cc95..ce710d120e 100644 --- a/opendaylight/distribution/sanitytest/pom.xml +++ b/opendaylight/distribution/sanitytest/pom.xml @@ -16,43 +16,153 @@ sanitytest 0.4.1-SNAPSHOT - bundle + jar + + + ${project.basedir}/../opendaylight/target/distribution.opendaylight-osgipackage/opendaylight + ${distro.dir}/run.bat + + 127.0.0.1 + 300 + + + + + non-windows + + + !windows + + + + ${distro.dir}/run.sh + /tmp/opendaylight.PID + + + + org.osgi org.osgi.core provided + + + junit + junit + test + + + + org.ow2.chameleon.management + chameleon-mbeans + test + + + - org.apache.felix - maven-bundle-plugin - ${bundle.plugin.version} - true + + org.apache.maven.plugins + maven-install-plugin + ${install.plugin.version} - - - org.opendaylight.controller.sanitytest - - - javax.xml.bind.annotation, - org.osgi.service.component, - org.slf4j, - org.eclipse.osgi.framework.console, - org.osgi.framework, - org.eclipse.osgi.baseadaptor, - org.eclipse.osgi.framework.adaptor, - org.osgi.framework.wiring - - - org.opendaylight.controller.sanitytest.internal.Activator - - - ${project.basedir}/META-INF + true + + + default-install + none + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${enforcer.plugin.version} + + + enforce-files-exist + pre-integration-test + + enforce + + + + + + ${distro.script} + + + + true + + + + + + + org.opendaylight.controller + controller-maven-plugin + 0.1.0-SNAPSHOT + + ${distro.dir} + localhost + 8080 + admin + admin + ${distro.script} + ${distro.pid} + + + + + start-controller + pre-integration-test + + + -start + -jmx + -Djava.rmi.server.hostname=${sanitytest.bind.address} + + 60 + + + run + + + + + stop-controller + post-integration-test + + stop + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${failsafe.version} + + + ${distro.dir} + ${sanitytest.bind.address} + ${sanitytest.timeout} + + + + + + diff --git a/opendaylight/distribution/sanitytest/src/main/java/org/opendaylight/controller/distribution/Sanity.java b/opendaylight/distribution/sanitytest/src/main/java/org/opendaylight/controller/distribution/Sanity.java deleted file mode 100644 index 7fc25e2b81..0000000000 --- a/opendaylight/distribution/sanitytest/src/main/java/org/opendaylight/controller/distribution/Sanity.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.opendaylight.controller.distribution; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import java.util.ArrayList; -import java.util.List; - -public class Sanity { - - static void copy(InputStream in, OutputStream out) throws IOException { - while (true) { - int c = in.read(); - if (c == -1) break; - out.write((char)c); - } - } - - public static void main(String[] args) throws IOException, InterruptedException { - String cwd = System.getProperty("user.dir"); - - System.out.println("Current working directory = " + cwd); - - String os = System.getProperty("os.name").toLowerCase(); - List script = new ArrayList(); - - if(os.contains("windows")){ - script.add("cmd.exe"); - script.add("/c"); - script.add("runsanity.bat"); - } else { - script.add("./runsanity.sh"); - } - - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.inheritIO().command(script); - Process p = processBuilder.start(); - - copy(p.getInputStream(), System.out); - - p.waitFor(); - - System.out.println("Test exited with exitValue = " + p.exitValue()); - - System.exit(p.exitValue()); - } -} diff --git a/opendaylight/distribution/sanitytest/src/main/java/org/opendaylight/controller/sanitytest/internal/Activator.java b/opendaylight/distribution/sanitytest/src/main/java/org/opendaylight/controller/sanitytest/internal/Activator.java deleted file mode 100644 index 08f0700168..0000000000 --- a/opendaylight/distribution/sanitytest/src/main/java/org/opendaylight/controller/sanitytest/internal/Activator.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.opendaylight.controller.sanitytest.internal; - -import java.util.Timer; -import java.util.TimerTask; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.wiring.BundleRevision; - -public class Activator implements BundleActivator { - //10 Second initial, 1 second subsequent - private static final int INITIAL_DELAY = 10000; - private static final int SUBSEQUENT_DELAY = 1000; - private static final int MAX_ATTEMPTS = 120; - - - private String stateToString(int state) { - switch (state) { - case Bundle.ACTIVE: - return "ACTIVE"; - case Bundle.INSTALLED: - return "INSTALLED"; - case Bundle.RESOLVED: - return "RESOLVED"; - case Bundle.UNINSTALLED: - return "UNINSTALLED"; - case Bundle.STARTING: - return "STARTING"; - default: - return "Not CONVERTED: state value is " + state; - } - } - - public void start(final BundleContext bundleContext) throws Exception { - Timer monitorTimer = new Timer("monitor timer", true); - monitorTimer.schedule(new TimerTask() { - @Override - public void run() { - int countup = 0; - boolean failed = false; - boolean resolved = false; - while (!resolved) { - resolved = true; - failed = false; - for(Bundle bundle : bundleContext.getBundles()){ - /* - * A bundle should be ACTIVE, unless it a fragment, in which case it should be RESOLVED - */ - int state = bundle.getState(); - if ((bundle.adapt(BundleRevision.class).getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { - //fragment - if (state != Bundle.RESOLVED) { - System.out.println("------ Failed to activate/resolve fragment = " + bundle.getSymbolicName() + " state = " + stateToString(bundle.getState())); - failed = true; - if (state == Bundle.STARTING) - resolved = false; - } - } else { - if(state != Bundle.ACTIVE) { - System.out.println("------ Failed to activate/resolve bundle = " + bundle.getSymbolicName() + " state = " + stateToString(bundle.getState())); - failed = true; - if (state == Bundle.STARTING) - resolved = false; - } - } - } - if (!resolved) { - countup++; - if (countup < MAX_ATTEMPTS) { - System.out.println("all bundles haven't finished starting, will repeat"); - try { - Thread.sleep(SUBSEQUENT_DELAY); - } catch (Exception e) { - System.out.println("Thread.sleep interuptted."); - break; - } - } else - resolved = true; - } - } - - if(failed){ - System.out.flush(); - System.out.println("exiting with 1 as failed"); - System.out.close(); - Runtime.getRuntime().exit(1); - } else { - System.out.flush(); - System.out.println("exiting with 0 as succeeded"); - System.out.close(); - Runtime.getRuntime().exit(0); - } - } - }, INITIAL_DELAY); - } - - public void stop(BundleContext bundleContext) throws Exception { - - } -} diff --git a/opendaylight/distribution/sanitytest/src/test/java/org/opendaylight/controller/distribution/test/SanityIT.java b/opendaylight/distribution/sanitytest/src/test/java/org/opendaylight/controller/distribution/test/SanityIT.java new file mode 100644 index 0000000000..931e88fc4f --- /dev/null +++ b/opendaylight/distribution/sanitytest/src/test/java/org/opendaylight/controller/distribution/test/SanityIT.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.distribution.test; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Socket; +import java.net.URL; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarFile; + +import javax.management.JMX; +import javax.management.MBeanServerConnection; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.ow2.chameleon.management.beans.BundleMXBean; +import org.ow2.chameleon.management.beans.OSGiPlatformMXBean; + +/** + * This integration test assumes a running local controller. The test does the + * following: + * 1) Wait for HTTP, JMX and OF ports to open + * 2) Establishes a JMX connection and registers a bundle notification listener + * 3) Waits till all bundles reach expected states or the timeout period elapses + */ +public class SanityIT { + private static final int OF_PORT = Integer.getInteger("ctrl.of.port", 6633); + private static final int HTTP_PORT = Integer.getInteger("ctrl.http.port", 8080); + private static final int JMX_PORT = Integer.getInteger("ctrl.jmx.port", 1088); + private static final int RMI_PORT = Integer.getInteger("ctrl.rmi.port", 1099); + private static final String CTRL_HOST = System.getProperty("ctrl.host", "127.0.0.1"); + private static final String CTRL_HOME = System.getProperty("ctrl.home"); + private static final long TIMEOUT_MILLIS = + Integer.getInteger("ctrl.start.timeout", 3*60) * 1000L; + private static final String JMX_URL = + "service:jmx:rmi:///jndi/rmi://" + CTRL_HOST + ":" + JMX_PORT + "/jmxrmi"; + + private static final Set bundles = + Collections.synchronizedSet(new HashSet()); + private static final Set fragments = + Collections.synchronizedSet(new HashSet()); + + @BeforeClass + public static void loadBundles() throws IOException { + log(" ctrl.home: " + CTRL_HOME); + log(" ctrl.host: " + CTRL_HOST); + log("ctrl.start.timeout: " + TIMEOUT_MILLIS); + log(" jmx.url: " + JMX_URL); + + Assert.assertNotNull(CTRL_HOME); + File ctrlHome = new File(CTRL_HOME); + Assert.assertTrue(ctrlHome.exists() && ctrlHome.isDirectory()); + File configDir = new File(ctrlHome, "configuration"); + Assert.assertTrue(configDir.exists() && configDir.isDirectory()); + File configIni = new File(configDir, "config.ini"); + Assert.assertTrue(configIni.exists()); + Properties config = new Properties(); + config.load(new FileInputStream(configIni)); + processBundles(configDir, config.getProperty("osgi.bundles")); + processBundles(new File(ctrlHome, "plugins")); + log("Bundles found in installation: " + bundles.size()); + log("Fragments found in installation: " + fragments.size()); + } + +@Test + public void sanityTest() throws Exception { + // wait for http, jmx & of ports to open + long startTime = System.currentTimeMillis(); + waitForListening(OF_PORT, false, startTime); + waitForListening(JMX_PORT, false, startTime); + waitForListening(HTTP_PORT, false, startTime); + + // open jmx connection + JMXServiceURL serviceUrl = new JMXServiceURL(JMX_URL); + JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null); + final MBeanServerConnection conn = jmxConnector.getMBeanServerConnection(); + + ObjectName fmkName = new ObjectName("org.ow2.chameleon:type=framework"); + OSGiPlatformMXBean fmkBean= JMX.newMBeanProxy(conn, fmkName, + OSGiPlatformMXBean.class, true); + conn.addNotificationListener(fmkName, new NotificationListener() { + + @Override + public void handleNotification(Notification n, Object handback) { + try { + //log("Notification: source: " + n.getSource()); + ObjectName bundleName = new ObjectName( + "org.ow2.chameleon:type=bundle,id=" + n.getUserData()); + BundleMXBean bundleBean = JMX.newMXBeanProxy(conn, + bundleName, BundleMXBean.class, true); + log("Bundle state change: " + bundleBean.getSymbolicName() + + " : " + stateToString(bundleBean.getState())); + handleBundleEvent(bundleBean); + // if its a system bundle, notify the main thread + if (bundleBean.getBundleId() == 0 && + bundleBean.getState() == Bundle.ACTIVE) + { + synchronized(SanityIT.this) { + SanityIT.this.notify(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }, null, null); + + if (checkAllBundles(conn) > 0) { + log("Waiting for system bundle to start... (times out in: " + TIMEOUT_MILLIS + "ms)"); + long timeElapsed = System.currentTimeMillis() - startTime; + synchronized(this) { + this.wait(TIMEOUT_MILLIS - timeElapsed); + } + log("System bundle started. Revalidating bundle states for all bundles..."); + + // Sometimes, the system bundle starts earlier than other bundles(?). The + // following loop will cover that case. + do { + Thread.sleep(2000); // 2s seems appropriate given the default timeout + if (checkAllBundles(conn) == 0) { + break; + } + } while(System.currentTimeMillis() - startTime < TIMEOUT_MILLIS); + } + try { + jmxConnector.close(); + } catch (Exception ignore) { + // dont want the tests to fail in case we can't close jmx connection + ignore.printStackTrace(); + } + if (bundles.size() + fragments.size() == 0) { + log("All bundles have reached expected state"); + } else { + log("The following bundles did not reach expected state: "); + for (String s : bundles) log("Bundle: " + s); + for (String s : fragments) log("Fragment: " + s); + + } + Assert.assertTrue(bundles.size() == 0 && fragments.size() == 0); + } + + private static int checkAllBundles(MBeanServerConnection conn) throws Exception { + ObjectName allBundlesName = new ObjectName("org.ow2.chameleon:*"); + Set bundleNames = conn.queryNames(allBundlesName, null); + for (ObjectName bundleName : bundleNames) { + if ("bundle".equals(bundleName.getKeyProperty("type"))) { + BundleMXBean bundleBean = JMX.newMBeanProxy(conn, bundleName, + BundleMXBean.class, true); + handleBundleEvent(bundleBean); + } + } + int remaining = bundles.size() + fragments.size(); + if (remaining > 0) { + log("Bundles not in expected states: " + remaining + " Waiting..."); + } + return remaining; + } + + private synchronized static void handleBundleEvent(BundleMXBean bundleBean) { + String name = bundleBean.getSymbolicName(); + int state = bundleBean.getState(); + // BUG in BundleMXBeanImpl - can't get bundle headers :( + // String fragHost = bundleBean.getBundleHeaders().get(Constants.FRAGMENT_HOST); + if (bundles.contains(name) && (state == Bundle.RESOLVED || state == Bundle.ACTIVE)) { + bundles.remove(name); + } else if (fragments.contains(name) && state == Bundle.RESOLVED) { + fragments.remove(name); + } + //log("Bundles to be started: " + bundles.size()); + } + + + // reference\:file\:../lib/org.apache.felix.fileinstall-3.1.6.jar@1:start,\ + private static void processBundles(File dir, String property) { + Assert.assertTrue(property == null || property.length() > 0); + for(String s : property.split(",")) { + int idx = s.indexOf("@"); + s = s.substring(15, (idx == -1 ? s.length() : idx)); + if (!s.endsWith(".jar")) s = s + ".jar"; + processJar(new File(dir, s)); + } + } + + public static void processBundles(File dir) throws IOException { + if (!dir.exists()) throw new FileNotFoundException("Cannot find dir:" + dir); + dir.listFiles(new FileFilter() { + + @Override + public boolean accept(File file) { + //p("---- " + file); + if (file.getName().endsWith(".jar")) { + return processJar(file); + } + return false; + } + }); + } + + public static boolean processJar(File file) { + try { + //log("----" + file); + JarFile jar = new JarFile(file, false); + Attributes jarAttributes = jar.getManifest().getMainAttributes(); + String bundleName = jarAttributes.getValue(Constants.BUNDLE_SYMBOLICNAME); + String fragHost = jarAttributes.getValue(Constants.FRAGMENT_HOST); + if (bundleName == null) { + log("Found a non bundle file:" + file); + return false; + } else { + int idx = bundleName.indexOf(';'); + if (idx > -1) { + bundleName = bundleName.substring(0, idx); + } + } + + if (fragHost == null) { + if (!bundles.add(bundleName)) { + throw new IllegalStateException( + "Found duplicate bundles with same symbolic name: " + + bundleName); + } + } else { + // fragments attaching to framework can't be detected + if (fragHost.contains("extension:=\"framework\"")) return false; + if (!fragments.add(bundleName)) { + throw new IllegalStateException( + "Found duplicate fragments with same symbolic name: " + + bundleName); + } + } + } catch (IOException e) { + throw new IllegalStateException("Error processing jar: " + file , e); + } + return true; + } + + public static long waitForListening(int port, boolean isHTTP, long beginTime) + throws InterruptedException { + long timeElapsedMillis = System.currentTimeMillis() - beginTime; + long sleepTimeMillis = 500L; // 0.5 secs + + while (timeElapsedMillis < TIMEOUT_MILLIS) { + long timeRemaining = TIMEOUT_MILLIS - timeElapsedMillis; + sleepTimeMillis *= 2; // exponential backoff + long toSleep = (sleepTimeMillis > timeRemaining) + ? timeRemaining : sleepTimeMillis; + Thread.sleep(toSleep); + timeElapsedMillis = System.currentTimeMillis() - beginTime; + if (isHTTP ? connectHTTP(port) : connectTCP(port)) { + log("Port is open: " + port); + return timeElapsedMillis; + } + } + throw new IllegalStateException("Timeout waiting for port: " + port); + } + + private static void log(String msg) { + System.out.format("[SanityIT] [%s] %s %s", new Date().toString(), msg, + System.lineSeparator()); + } + + public static boolean connectTCP(int port) { + String host = CTRL_HOST.length() == 0 ? null : CTRL_HOST; + try { + Socket sock = new Socket(host, port); + sock.getPort(); + try { + sock.close(); + } catch (IOException ignore) { + // Can't close socket. Ingore and let downstream validate health + } + return true; + } catch (IOException ioe) { + return false; + } + } + + public static boolean connectHTTP(int port) { + String host = CTRL_HOST.length() == 0 ? "localhost" : CTRL_HOST; + try { + URL url = new URL("http", host, port, "/"); + HttpURLConnection con; + con = (HttpURLConnection) url.openConnection(); + return (con.getResponseCode() > 0); + } catch (IOException e) { + return false; + } + } + + private String stateToString(int state) { + switch (state) { + case Bundle.ACTIVE: return "ACTIVE"; + case Bundle.INSTALLED: return "INSTALLED"; + case Bundle.RESOLVED: return "RESOLVED"; + case Bundle.UNINSTALLED: return "UNINSTALLED"; + case Bundle.STARTING: return "STARTING"; + case Bundle.STOPPING: return "STOPPING"; + default: return "UNKNOWN: " + state; + } + } + + + + +} diff --git a/pom.xml b/pom.xml index f7f9bc2256..6ece420d5a 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,7 @@ opendaylight/commons/opendaylight opendaylight/commons/parent opendaylight/commons/logback_settings + opendaylight/commons/controller-maven-plugin @@ -149,7 +150,6 @@ opendaylight/statisticsmanager/integrationtest opendaylight/commons/integrationtest opendaylight/containermanager/it.implementation - opendaylight/distribution/sanitytest/