2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.distribution.test;
11 import java.io.FileFilter;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.net.HttpURLConnection;
16 import java.net.Socket;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.HashSet;
21 import java.util.Properties;
23 import java.util.jar.Attributes;
24 import java.util.jar.JarFile;
26 import javax.management.JMX;
27 import javax.management.MBeanServerConnection;
28 import javax.management.Notification;
29 import javax.management.NotificationListener;
30 import javax.management.ObjectName;
31 import javax.management.remote.JMXConnector;
32 import javax.management.remote.JMXConnectorFactory;
33 import javax.management.remote.JMXServiceURL;
35 import org.junit.Assert;
36 import org.junit.BeforeClass;
37 import org.junit.Test;
38 import org.osgi.framework.Bundle;
39 import org.osgi.framework.Constants;
40 import org.ow2.chameleon.management.beans.BundleMXBean;
41 import org.ow2.chameleon.management.beans.OSGiPlatformMXBean;
44 * This integration test assumes a running local controller. The test does the
46 * 1) Wait for HTTP, JMX and OF ports to open
47 * 2) Establishes a JMX connection and registers a bundle notification listener
48 * 3) Waits till all bundles reach expected states or the timeout period elapses
50 public class SanityIT {
51 private static final int OF_PORT = Integer.getInteger("ctrl.of.port", 6633);
52 private static final int HTTP_PORT = Integer.getInteger("ctrl.http.port", 8080);
53 private static final int JMX_PORT = Integer.getInteger("ctrl.jmx.port", 1088);
54 private static final int RMI_PORT = Integer.getInteger("ctrl.rmi.port", 1099);
55 private static final String CTRL_HOST = System.getProperty("ctrl.host", "127.0.0.1");
56 private static final String CTRL_HOME = System.getProperty("ctrl.home");
57 private static final long TIMEOUT_MILLIS =
58 Integer.getInteger("ctrl.start.timeout", 3*60) * 1000L;
59 private static final String JMX_URL =
60 "service:jmx:rmi:///jndi/rmi://" + CTRL_HOST + ":" + JMX_PORT + "/jmxrmi";
62 private static final Set<String> bundles =
63 Collections.synchronizedSet(new HashSet<String>());
64 private static final Set<String> fragments =
65 Collections.synchronizedSet(new HashSet<String>());
68 public static void loadBundles() throws IOException {
69 log(" ctrl.home: " + CTRL_HOME);
70 log(" ctrl.host: " + CTRL_HOST);
71 log("ctrl.start.timeout: " + TIMEOUT_MILLIS);
72 log(" jmx.url: " + JMX_URL);
74 Assert.assertNotNull(CTRL_HOME);
75 File ctrlHome = new File(CTRL_HOME);
76 Assert.assertTrue(ctrlHome.exists() && ctrlHome.isDirectory());
77 File configDir = new File(ctrlHome, "configuration");
78 Assert.assertTrue(configDir.exists() && configDir.isDirectory());
79 File configIni = new File(configDir, "config.ini");
80 Assert.assertTrue(configIni.exists());
81 Properties config = new Properties();
82 config.load(new FileInputStream(configIni));
83 processBundles(configDir, config.getProperty("osgi.bundles"));
84 processBundles(new File(ctrlHome, "plugins"));
85 log("Bundles found in installation: " + bundles.size());
86 log("Fragments found in installation: " + fragments.size());
90 public void sanityTest() throws Exception {
91 // wait for http, jmx & of ports to open
92 long startTime = System.currentTimeMillis();
93 waitForListening(OF_PORT, false, startTime);
94 waitForListening(JMX_PORT, false, startTime);
95 waitForListening(HTTP_PORT, false, startTime);
97 // open jmx connection
98 JMXServiceURL serviceUrl = new JMXServiceURL(JMX_URL);
99 JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
100 final MBeanServerConnection conn = jmxConnector.getMBeanServerConnection();
102 ObjectName fmkName = new ObjectName("org.ow2.chameleon:type=framework");
103 OSGiPlatformMXBean fmkBean= JMX.newMBeanProxy(conn, fmkName,
104 OSGiPlatformMXBean.class, true);
105 conn.addNotificationListener(fmkName, new NotificationListener() {
108 public void handleNotification(Notification n, Object handback) {
110 //log("Notification: source: " + n.getSource());
111 ObjectName bundleName = new ObjectName(
112 "org.ow2.chameleon:type=bundle,id=" + n.getUserData());
113 BundleMXBean bundleBean = JMX.newMXBeanProxy(conn,
114 bundleName, BundleMXBean.class, true);
115 log("Bundle state change: " + bundleBean.getSymbolicName() +
116 " : " + stateToString(bundleBean.getState()));
117 handleBundleEvent(bundleBean);
118 // if its a system bundle, notify the main thread
119 if (bundleBean.getBundleId() == 0 &&
120 bundleBean.getState() == Bundle.ACTIVE)
122 synchronized(SanityIT.this) {
123 SanityIT.this.notify();
126 } catch (Exception e) {
132 if (checkAllBundles(conn) > 0) {
133 log("Waiting for system bundle to start... (times out in: " + TIMEOUT_MILLIS + "ms)");
134 long timeElapsed = System.currentTimeMillis() - startTime;
136 this.wait(TIMEOUT_MILLIS - timeElapsed);
138 log("System bundle started. Revalidating bundle states for all bundles...");
140 // Sometimes, the system bundle starts earlier than other bundles(?). The
141 // following loop will cover that case.
143 Thread.sleep(2000); // 2s seems appropriate given the default timeout
144 if (checkAllBundles(conn) == 0) {
147 } while(System.currentTimeMillis() - startTime < TIMEOUT_MILLIS);
150 jmxConnector.close();
151 } catch (Exception ignore) {
152 // dont want the tests to fail in case we can't close jmx connection
153 ignore.printStackTrace();
155 if (bundles.size() + fragments.size() == 0) {
156 log("All bundles have reached expected state");
158 log("The following bundles did not reach expected state: ");
159 for (String s : bundles) log("Bundle: " + s);
160 for (String s : fragments) log("Fragment: " + s);
163 Assert.assertTrue(bundles.size() == 0 && fragments.size() == 0);
166 private static int checkAllBundles(MBeanServerConnection conn) throws Exception {
167 ObjectName allBundlesName = new ObjectName("org.ow2.chameleon:*");
168 Set<ObjectName> bundleNames = conn.queryNames(allBundlesName, null);
169 for (ObjectName bundleName : bundleNames) {
170 if ("bundle".equals(bundleName.getKeyProperty("type"))) {
171 BundleMXBean bundleBean = JMX.newMBeanProxy(conn, bundleName,
172 BundleMXBean.class, true);
173 handleBundleEvent(bundleBean);
176 int remaining = bundles.size() + fragments.size();
178 log("Bundles not in expected states: " + remaining + " Waiting...");
183 private synchronized static void handleBundleEvent(BundleMXBean bundleBean) {
184 String name = bundleBean.getSymbolicName();
185 int state = bundleBean.getState();
186 // BUG in BundleMXBeanImpl - can't get bundle headers :(
187 // String fragHost = bundleBean.getBundleHeaders().get(Constants.FRAGMENT_HOST);
188 if (bundles.contains(name) && (state == Bundle.RESOLVED || state == Bundle.ACTIVE)) {
189 bundles.remove(name);
190 } else if (fragments.contains(name) && state == Bundle.RESOLVED) {
191 fragments.remove(name);
193 //log("Bundles to be started: " + bundles.size());
197 // reference\:file\:../lib/org.apache.felix.fileinstall-3.1.6.jar@1:start,\
198 private static void processBundles(File dir, String property) {
199 Assert.assertTrue(property == null || property.length() > 0);
200 for(String s : property.split(",")) {
201 int idx = s.indexOf("@");
202 s = s.substring(15, (idx == -1 ? s.length() : idx));
203 if (!s.endsWith(".jar")) s = s + ".jar";
204 processJar(new File(dir, s));
208 public static void processBundles(File dir) throws IOException {
209 if (!dir.exists()) throw new FileNotFoundException("Cannot find dir:" + dir);
210 dir.listFiles(new FileFilter() {
213 public boolean accept(File file) {
215 if (file.getName().endsWith(".jar")) {
216 return processJar(file);
223 public static boolean processJar(File file) {
225 //log("----" + file);
226 JarFile jar = new JarFile(file, false);
227 Attributes jarAttributes = jar.getManifest().getMainAttributes();
228 String bundleName = jarAttributes.getValue(Constants.BUNDLE_SYMBOLICNAME);
229 String fragHost = jarAttributes.getValue(Constants.FRAGMENT_HOST);
230 if (bundleName == null) {
231 log("Found a non bundle file:" + file);
234 int idx = bundleName.indexOf(';');
236 bundleName = bundleName.substring(0, idx);
240 if (fragHost == null) {
241 if (!bundles.add(bundleName)) {
242 throw new IllegalStateException(
243 "Found duplicate bundles with same symbolic name: "
247 // fragments attaching to framework can't be detected
248 if (fragHost.contains("extension:=\"framework\"")) return false;
249 if (!fragments.add(bundleName)) {
250 throw new IllegalStateException(
251 "Found duplicate fragments with same symbolic name: "
255 } catch (IOException e) {
256 throw new IllegalStateException("Error processing jar: " + file , e);
261 public static long waitForListening(int port, boolean isHTTP, long beginTime)
262 throws InterruptedException {
263 long timeElapsedMillis = System.currentTimeMillis() - beginTime;
264 long sleepTimeMillis = 500L; // 0.5 secs
266 while (timeElapsedMillis < TIMEOUT_MILLIS) {
267 long timeRemaining = TIMEOUT_MILLIS - timeElapsedMillis;
268 sleepTimeMillis *= 2; // exponential backoff
269 long toSleep = (sleepTimeMillis > timeRemaining)
270 ? timeRemaining : sleepTimeMillis;
271 Thread.sleep(toSleep);
272 timeElapsedMillis = System.currentTimeMillis() - beginTime;
273 if (isHTTP ? connectHTTP(port) : connectTCP(port)) {
274 log("Port is open: " + port);
275 return timeElapsedMillis;
278 throw new IllegalStateException("Timeout waiting for port: " + port);
281 private static void log(String msg) {
282 System.out.format("[SanityIT] [%s] %s %s", new Date().toString(), msg,
283 System.lineSeparator());
286 public static boolean connectTCP(int port) {
287 String host = CTRL_HOST.length() == 0 ? null : CTRL_HOST;
289 Socket sock = new Socket(host, port);
293 } catch (IOException ignore) {
294 // Can't close socket. Ingore and let downstream validate health
297 } catch (IOException ioe) {
302 public static boolean connectHTTP(int port) {
303 String host = CTRL_HOST.length() == 0 ? "localhost" : CTRL_HOST;
305 URL url = new URL("http", host, port, "/");
306 HttpURLConnection con;
307 con = (HttpURLConnection) url.openConnection();
308 return (con.getResponseCode() > 0);
309 } catch (IOException e) {
314 private String stateToString(int state) {
316 case Bundle.ACTIVE: return "ACTIVE";
317 case Bundle.INSTALLED: return "INSTALLED";
318 case Bundle.RESOLVED: return "RESOLVED";
319 case Bundle.UNINSTALLED: return "UNINSTALLED";
320 case Bundle.STARTING: return "STARTING";
321 case Bundle.STOPPING: return "STOPPING";
322 default: return "UNKNOWN: " + state;