J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2003 Sun Microsystems, Inc. All Rights Reserved. |
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. |
| 8 | * |
| 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | * version 2 for more details (a copy is included in the LICENSE file that |
| 13 | * accompanied this code). |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License version |
| 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | * |
| 19 | * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 20 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 21 | * have any questions. |
| 22 | */ |
| 23 | |
| 24 | /* |
| 25 | * @test |
| 26 | * @bug 4865397 |
| 27 | * @summary Tests remote JMX connections |
| 28 | * @author Eamonn McManus |
| 29 | * @run clean ConnectionTest |
| 30 | * @run build ConnectionTest |
| 31 | * @run main ConnectionTest |
| 32 | */ |
| 33 | |
| 34 | import java.io.IOException; |
| 35 | import java.net.MalformedURLException; |
| 36 | import java.util.Collections; |
| 37 | import java.util.HashMap; |
| 38 | import java.util.HashSet; |
| 39 | import java.util.Iterator; |
| 40 | import java.util.LinkedList; |
| 41 | import java.util.List; |
| 42 | import java.util.Map; |
| 43 | import java.util.Set; |
| 44 | import java.util.StringTokenizer; |
| 45 | |
| 46 | import java.security.Principal; |
| 47 | import javax.security.auth.Subject; |
| 48 | |
| 49 | import javax.management.MBeanServer; |
| 50 | import javax.management.MBeanServerConnection; |
| 51 | import javax.management.MBeanServerFactory; |
| 52 | import javax.management.Notification; |
| 53 | import javax.management.NotificationListener; |
| 54 | import javax.management.ObjectName; |
| 55 | |
| 56 | import javax.management.remote.JMXAuthenticator; |
| 57 | import javax.management.remote.JMXConnectionNotification; |
| 58 | import javax.management.remote.JMXConnector; |
| 59 | import javax.management.remote.JMXConnectorFactory; |
| 60 | import javax.management.remote.JMXConnectorServer; |
| 61 | import javax.management.remote.JMXConnectorServerFactory; |
| 62 | import javax.management.remote.JMXPrincipal; |
| 63 | import javax.management.remote.JMXServiceURL; |
| 64 | |
| 65 | public class ConnectionTest { |
| 66 | |
| 67 | public static void main(String[] args) { |
| 68 | // System.setProperty("java.util.logging.config.file", |
| 69 | // "../../../../logging.properties"); |
| 70 | // // we are in <workspace>/build/test/JTwork/scratch |
| 71 | // java.util.logging.LogManager.getLogManager().readConfiguration(); |
| 72 | boolean ok = true; |
| 73 | String[] protocols = {"rmi", "iiop", "jmxmp"}; |
| 74 | if (args.length > 0) |
| 75 | protocols = args; |
| 76 | for (int i = 0; i < protocols.length; i++) { |
| 77 | final String proto = protocols[i]; |
| 78 | System.out.println("Testing for protocol " + proto); |
| 79 | try { |
| 80 | ok &= test(proto); |
| 81 | } catch (Exception e) { |
| 82 | System.err.println("Unexpected exception: " + e); |
| 83 | e.printStackTrace(); |
| 84 | ok = false; |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | if (ok) |
| 89 | System.out.println("Test passed"); |
| 90 | else { |
| 91 | System.out.println("TEST FAILED"); |
| 92 | System.exit(1); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | private static boolean test(String proto) throws Exception { |
| 97 | ObjectName serverName = ObjectName.getInstance("d:type=server"); |
| 98 | MBeanServer mbs = MBeanServerFactory.newMBeanServer(); |
| 99 | JMXAuthenticator authenticator = new BogusAuthenticator(); |
| 100 | Map env = Collections.singletonMap("jmx.remote.authenticator", |
| 101 | authenticator); |
| 102 | JMXServiceURL url = new JMXServiceURL("service:jmx:" + proto + "://"); |
| 103 | JMXConnectorServer server; |
| 104 | try { |
| 105 | server = |
| 106 | JMXConnectorServerFactory.newJMXConnectorServer(url, env, |
| 107 | null); |
| 108 | } catch (MalformedURLException e) { |
| 109 | System.out.println("Protocol " + proto + |
| 110 | " not supported, ignoring"); |
| 111 | return true; |
| 112 | } |
| 113 | System.out.println("Created connector server"); |
| 114 | mbs.registerMBean(server, serverName); |
| 115 | System.out.println("Registered connector server in MBean server"); |
| 116 | mbs.addNotificationListener(serverName, logListener, null, null); |
| 117 | mbs.invoke(serverName, "start", null, null); |
| 118 | System.out.println("Started connector server"); |
| 119 | JMXServiceURL address = |
| 120 | (JMXServiceURL) mbs.getAttribute(serverName, "Address"); |
| 121 | System.out.println("Retrieved address: " + address); |
| 122 | |
| 123 | if (address.getHost().length() == 0) { |
| 124 | System.out.println("Generated address has empty hostname"); |
| 125 | return false; |
| 126 | } |
| 127 | |
| 128 | JMXConnector client = JMXConnectorFactory.connect(address); |
| 129 | System.out.println("Client connected"); |
| 130 | |
| 131 | String clientConnId = client.getConnectionId(); |
| 132 | System.out.println("Got connection ID on client: " + clientConnId); |
| 133 | boolean ok = checkConnectionId(proto, clientConnId); |
| 134 | if (!ok) |
| 135 | return false; |
| 136 | System.out.println("Connection ID is OK"); |
| 137 | |
| 138 | // 4901826: connection ids need some time to be updated using jmxmp |
| 139 | // we don't get the notif immediately either |
| 140 | // this was originally timeout 1ms, which was not enough |
| 141 | Notification notif = waitForNotification(1000); |
| 142 | System.out.println("Server got notification: " + notif); |
| 143 | |
| 144 | ok = mustBeConnectionNotification(notif, clientConnId, |
| 145 | JMXConnectionNotification.OPENED); |
| 146 | if (!ok) |
| 147 | return false; |
| 148 | |
| 149 | client.close(); |
| 150 | System.out.println("Closed client"); |
| 151 | |
| 152 | notif = waitForNotification(1000); |
| 153 | System.out.println("Got notification: " + notif); |
| 154 | |
| 155 | ok = mustBeConnectionNotification(notif, clientConnId, |
| 156 | JMXConnectionNotification.CLOSED); |
| 157 | if (!ok) |
| 158 | return false; |
| 159 | |
| 160 | client = JMXConnectorFactory.connect(address); |
| 161 | System.out.println("Second client connected"); |
| 162 | |
| 163 | String clientConnId2 = client.getConnectionId(); |
| 164 | if (clientConnId.equals(clientConnId2)) { |
| 165 | System.out.println("Same connection ID for two connections: " + |
| 166 | clientConnId2); |
| 167 | return false; |
| 168 | } |
| 169 | System.out.println("Second client connection ID is different"); |
| 170 | |
| 171 | notif = waitForNotification(1); |
| 172 | ok = mustBeConnectionNotification(notif, clientConnId2, |
| 173 | JMXConnectionNotification.OPENED); |
| 174 | if (!ok) |
| 175 | return false; |
| 176 | |
| 177 | MBeanServerConnection mbsc = client.getMBeanServerConnection(); |
| 178 | Map attrs = (Map) mbsc.getAttribute(serverName, "Attributes"); |
| 179 | System.out.println("Server attributes received by client: " + attrs); |
| 180 | |
| 181 | server.stop(); |
| 182 | System.out.println("Server stopped"); |
| 183 | |
| 184 | notif = waitForNotification(1000); |
| 185 | System.out.println("Server got connection-closed notification: " + |
| 186 | notif); |
| 187 | |
| 188 | ok = mustBeConnectionNotification(notif, clientConnId2, |
| 189 | JMXConnectionNotification.CLOSED); |
| 190 | if (!ok) |
| 191 | return false; |
| 192 | |
| 193 | try { |
| 194 | mbsc.getDefaultDomain(); |
| 195 | System.out.println("Connection still working but should not be"); |
| 196 | return false; |
| 197 | } catch (IOException e) { |
| 198 | System.out.println("Connection correctly got exception: " + e); |
| 199 | } |
| 200 | |
| 201 | try { |
| 202 | client = JMXConnectorFactory.connect(address); |
| 203 | System.out.println("Connector server still working but should " + |
| 204 | "not be"); |
| 205 | return false; |
| 206 | } catch (IOException e) { |
| 207 | System.out.println("New connection correctly got exception: " + e); |
| 208 | } |
| 209 | |
| 210 | return true; |
| 211 | } |
| 212 | |
| 213 | private static boolean |
| 214 | mustBeConnectionNotification(Notification notif, |
| 215 | String requiredConnId, |
| 216 | String requiredType) { |
| 217 | |
| 218 | if (!(notif instanceof JMXConnectionNotification)) { |
| 219 | System.out.println("Should have been a " + |
| 220 | "JMXConnectionNotification: " + |
| 221 | notif.getClass()); |
| 222 | return false; |
| 223 | } |
| 224 | |
| 225 | JMXConnectionNotification cnotif = (JMXConnectionNotification) notif; |
| 226 | if (!cnotif.getType().equals(requiredType)) { |
| 227 | System.out.println("Wrong type notif: is \"" + cnotif.getType() + |
| 228 | "\", should be \"" + requiredType + "\""); |
| 229 | return false; |
| 230 | } |
| 231 | |
| 232 | if (!cnotif.getConnectionId().equals(requiredConnId)) { |
| 233 | System.out.println("Wrong connection id: is \"" + |
| 234 | cnotif.getConnectionId() + "\", should be \"" + |
| 235 | requiredConnId); |
| 236 | return false; |
| 237 | } |
| 238 | |
| 239 | return true; |
| 240 | } |
| 241 | |
| 242 | private static boolean checkConnectionId(String proto, String clientConnId) |
| 243 | throws Exception { |
| 244 | StringTokenizer tok = new StringTokenizer(clientConnId, " ", true); |
| 245 | String s; |
| 246 | s = tok.nextToken(); |
| 247 | if (!s.startsWith(proto + ":")) { |
| 248 | System.out.println("Expected \"" + proto + ":\", found \"" + s + |
| 249 | "\""); |
| 250 | return false; |
| 251 | } |
| 252 | s = tok.nextToken(); |
| 253 | if (!s.equals(" ")) { |
| 254 | System.out.println("Expected \" \", found \"" + s + "\""); |
| 255 | return false; |
| 256 | } |
| 257 | s = tok.nextToken(); |
| 258 | StringTokenizer tok2 = new StringTokenizer(s, ";", true); |
| 259 | Set principalNames = new HashSet(); |
| 260 | String s2; |
| 261 | s2 = tok2.nextToken(); |
| 262 | if (s2.equals(";")) { |
| 263 | System.out.println("In identity \"" + s + |
| 264 | "\", expected name, found \";\""); |
| 265 | return false; |
| 266 | } |
| 267 | principalNames.add(s2); |
| 268 | s2 = tok2.nextToken(); |
| 269 | if (!s2.equals(";")) |
| 270 | throw new Exception("Can't happen"); |
| 271 | s2 = tok2.nextToken(); |
| 272 | if (s2.equals(";")) { |
| 273 | System.out.println("In identity \"" + s + |
| 274 | "\", expected name, found \";\""); |
| 275 | return false; |
| 276 | } |
| 277 | principalNames.add(s2); |
| 278 | if (tok2.hasMoreTokens()) { |
| 279 | System.out.println("In identity \"" + s + "\", too many tokens"); |
| 280 | return false; |
| 281 | } |
| 282 | if (principalNames.size() != bogusPrincipals.size()) { |
| 283 | System.out.println("Wrong number of principal names: " + |
| 284 | principalNames.size() + " != " + |
| 285 | bogusPrincipals.size()); |
| 286 | return false; |
| 287 | } |
| 288 | for (Iterator it = bogusPrincipals.iterator(); it.hasNext(); ) { |
| 289 | Principal p = (Principal) it.next(); |
| 290 | if (!principalNames.contains(p.getName())) { |
| 291 | System.out.println("Principal names don't contain \"" + |
| 292 | p.getName() + "\""); |
| 293 | return false; |
| 294 | } |
| 295 | } |
| 296 | s = tok.nextToken(); |
| 297 | if (!s.equals(" ")) { |
| 298 | System.out.println("Expected \" \", found \"" + s + "\""); |
| 299 | return false; |
| 300 | } |
| 301 | return true; |
| 302 | } |
| 303 | |
| 304 | private static Notification waitForNotification(long timeout) |
| 305 | throws InterruptedException { |
| 306 | synchronized (log) { |
| 307 | if (log.isEmpty()) { |
| 308 | long remainingTime = timeout; |
| 309 | final long startTime = System.currentTimeMillis(); |
| 310 | |
| 311 | while (log.isEmpty() && remainingTime >0) { |
| 312 | log.wait(remainingTime); |
| 313 | remainingTime = timeout - (System.currentTimeMillis() - startTime); |
| 314 | } |
| 315 | |
| 316 | if (log.isEmpty()) { |
| 317 | throw new InterruptedException("Timed out waiting for " + |
| 318 | "notification!"); |
| 319 | } |
| 320 | } |
| 321 | return (Notification) log.remove(0); |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | private static class LogListener implements NotificationListener { |
| 326 | LogListener(List log) { |
| 327 | this.log = log; |
| 328 | } |
| 329 | |
| 330 | public void handleNotification(Notification n, Object h) { |
| 331 | synchronized (log) { |
| 332 | log.add(n); |
| 333 | log.notifyAll(); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | private final List log; |
| 338 | } |
| 339 | |
| 340 | private static List log = new LinkedList(); |
| 341 | private static NotificationListener logListener = new LogListener(log); |
| 342 | |
| 343 | private static class BogusAuthenticator implements JMXAuthenticator { |
| 344 | public Subject authenticate(Object credentials) { |
| 345 | Subject subject = |
| 346 | new Subject(true, bogusPrincipals, |
| 347 | Collections.EMPTY_SET, Collections.EMPTY_SET); |
| 348 | System.out.println("Authenticator returns: " + subject); |
| 349 | return subject; |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | private static final Set bogusPrincipals = new HashSet(); |
| 354 | static { |
| 355 | bogusPrincipals.add(new JMXPrincipal("foo")); |
| 356 | bogusPrincipals.add(new JMXPrincipal("bar")); |
| 357 | } |
| 358 | } |