Merge "Improve logging of persister and netconf client."
[controller.git] / opendaylight / distribution / sanitytest / src / test / java / org / opendaylight / controller / distribution / test / SanityIT.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.distribution.test;
9
10 import java.io.File;
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;
17 import java.net.URL;
18 import java.util.Collections;
19 import java.util.Date;
20 import java.util.HashSet;
21 import java.util.Properties;
22 import java.util.Set;
23 import java.util.jar.Attributes;
24 import java.util.jar.JarFile;
25
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;
34
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;
42
43 /**
44  * This integration test assumes a running local controller. The test does the
45  * following:
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
49  */
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";
61
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>());
66
67   @BeforeClass
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);
73
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());
87   }
88
89 @Test
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);
96
97     // open jmx connection
98     JMXServiceURL serviceUrl = new JMXServiceURL(JMX_URL);
99     JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
100     final MBeanServerConnection conn = jmxConnector.getMBeanServerConnection();
101
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() {
106
107         @Override
108         public void handleNotification(Notification n, Object handback) {
109             try {
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)
121                 {
122                     synchronized(SanityIT.this) {
123                         SanityIT.this.notify();
124                     }
125                 }
126             } catch (Exception e) {
127                 e.printStackTrace();
128             }
129         }
130     }, null, null);
131
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;
135         synchronized(this) {
136             this.wait(TIMEOUT_MILLIS - timeElapsed);
137         }
138         log("System bundle started. Revalidating bundle states for all bundles...");
139
140         // Sometimes, the system bundle starts earlier than other bundles(?). The
141         // following loop will cover that case.
142         do {
143             Thread.sleep(2000); // 2s seems appropriate given the default timeout
144             if (checkAllBundles(conn) == 0) {
145                 break;
146             }
147         } while(System.currentTimeMillis() - startTime < TIMEOUT_MILLIS);
148     }
149     try {
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();
154     }
155     if (bundles.size() + fragments.size() == 0) {
156         log("All bundles have reached expected state");
157     } else {
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);
161
162     }
163     Assert.assertTrue(bundles.size() == 0 && fragments.size() == 0);
164   }
165
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);
174           }
175       }
176       int remaining = bundles.size() + fragments.size();
177       if (remaining > 0) {
178         log("Bundles not in expected states: " + remaining + " Waiting...");
179       }
180       return remaining;
181   }
182
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);
192       }
193       //log("Bundles to be started: " + bundles.size());
194   }
195
196
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));
205       }
206   }
207
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() {
211
212         @Override
213         public boolean accept(File file) {
214             //p("---- " + file);
215             if (file.getName().endsWith(".jar")) {
216                 return processJar(file);
217             }
218             return false;
219         }
220       });
221   }
222
223   public static boolean processJar(File file) {
224       try {
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);
232               return false;
233           } else {
234               int idx = bundleName.indexOf(';');
235               if (idx > -1) {
236                   bundleName = bundleName.substring(0, idx);
237               }
238           }
239
240           if (fragHost == null) {
241               if (!bundles.add(bundleName)) {
242                   throw new IllegalStateException(
243                           "Found duplicate bundles with same symbolic name: "
244                                   + bundleName);
245               }
246           } else {
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: "
252                                   + bundleName);
253               }
254           }
255       } catch (IOException e) {
256           throw new IllegalStateException("Error processing jar: " + file , e);
257       }
258       return true;
259   }
260
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
265
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;
276             }
277         }
278         throw new IllegalStateException("Timeout waiting for port: " + port);
279     }
280
281     private static void log(String msg) {
282         System.out.format("[SanityIT] [%s] %s %s", new Date().toString(), msg,
283                 System.lineSeparator());
284     }
285
286     public static boolean connectTCP(int port) {
287       String host = CTRL_HOST.length() == 0 ? null : CTRL_HOST;
288       try {
289         Socket sock = new Socket(host, port);
290         sock.getPort();
291         try {
292           sock.close();
293         } catch (IOException ignore) {
294           // Can't close socket. Ingore and let downstream validate health
295         }
296         return true;
297       } catch (IOException ioe) {
298         return false;
299       }
300     }
301
302     public static boolean connectHTTP(int port) {
303         String host = CTRL_HOST.length() == 0 ? "localhost" : CTRL_HOST;
304         try {
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) {
310             return false;
311         }
312     }
313
314     private String stateToString(int state) {
315         switch (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;
323         }
324     }
325
326
327
328
329 }