|
1 /* |
|
2 * Copyright 2008 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 JMXNamespaceViewTest.java |
|
26 * @summary Test the JMXNamespaceView class. |
|
27 * @author Daniel Fuchs |
|
28 * @run clean JMXNamespaceViewTest Wombat WombatMBean |
|
29 * @run build JMXNamespaceViewTest Wombat WombatMBean |
|
30 * @run main JMXNamespaceViewTest |
|
31 */ |
|
32 |
|
33 |
|
34 import java.lang.management.ManagementFactory; |
|
35 import java.net.ServerSocket; |
|
36 import java.rmi.registry.LocateRegistry; |
|
37 import java.util.ArrayList; |
|
38 import java.util.Arrays; |
|
39 import java.util.HashMap; |
|
40 import java.util.HashSet; |
|
41 import java.util.List; |
|
42 import java.util.Map; |
|
43 import java.util.Set; |
|
44 import javax.management.JMException; |
|
45 import javax.management.MBeanRegistration; |
|
46 import javax.management.MBeanServer; |
|
47 import javax.management.MBeanServerFactory; |
|
48 import javax.management.ObjectInstance; |
|
49 import javax.management.ObjectName; |
|
50 import javax.management.namespace.JMXNamespace; |
|
51 import javax.management.namespace.JMXNamespaceView; |
|
52 import javax.management.namespace.JMXNamespaces; |
|
53 import javax.management.namespace.JMXRemoteNamespace; |
|
54 import javax.management.remote.JMXConnectorServer; |
|
55 import javax.management.remote.JMXConnectorServerFactory; |
|
56 import javax.management.remote.JMXServiceURL; |
|
57 |
|
58 /** |
|
59 * A simple test to test the JMXNamespaceViewTest... |
|
60 * @author dfuchs |
|
61 */ |
|
62 public class JMXNamespaceViewTest { |
|
63 |
|
64 // TODO: Remove this when contexts are added. |
|
65 public static class ClientContext { |
|
66 public final static String NAMESPACE = "jmx.context"; |
|
67 } |
|
68 |
|
69 /** |
|
70 * Describe the configuration of a namespace |
|
71 */ |
|
72 public static class NamespaceConfig { |
|
73 /** name of the namespace - no // allowed **/ |
|
74 public String name; |
|
75 /** |
|
76 * JMXServiceURL through which the namespace is exported, if it |
|
77 * is a remote namespace. {@code null} if the namespace is local. |
|
78 * This is an inpur URL - eg: new JMXServiceURL("rmi",null,0).toString() |
|
79 * is acceptable here. |
|
80 */ |
|
81 public String jmxurl; |
|
82 /** |
|
83 * Values of the name= key for each WombatMBean contained in the |
|
84 * namespace. |
|
85 */ |
|
86 public String[] wombats; |
|
87 /** list of child namespace **/ |
|
88 public NamespaceConfig[] children; |
|
89 } |
|
90 |
|
91 /** |
|
92 * Creates a NamespaceConfig record for a local namespace. |
|
93 * @param name name of the namespace |
|
94 * @param wombats names of WombatMBean it should contain. |
|
95 * @return a NamespaceConfig. |
|
96 */ |
|
97 public static NamespaceConfig config(String name, String[] wombats) { |
|
98 return config(name,null,wombats); |
|
99 } |
|
100 |
|
101 /** |
|
102 * Creates a NamespaceConfig record for a remote namespace. |
|
103 * @param name name of the namespace |
|
104 * @param jmxurl input JMXServiceURL for creating the JMXConnectorServer |
|
105 * @param wombats names of WombatMBean it should contain. |
|
106 * @return a NamespaceConfig. |
|
107 */ |
|
108 public static NamespaceConfig config(String name, String jmxurl, |
|
109 String[] wombats) { |
|
110 return config(name,jmxurl,wombats,(NamespaceConfig[])null); |
|
111 } |
|
112 |
|
113 /** |
|
114 * Creates a NamespaceConfig record for a local namespace. |
|
115 * @param name name of the namespace |
|
116 * @param wombats names of WombatMBean it should contain. |
|
117 * @param children list of sub namespaces. |
|
118 * @return a NamespaceConfig. |
|
119 */ |
|
120 public static NamespaceConfig config(String name, String[] wombats, |
|
121 NamespaceConfig... children) { |
|
122 return config(name,null,wombats,children); |
|
123 } |
|
124 |
|
125 /** |
|
126 * Creates a NamespaceConfig record for a remote namespace. |
|
127 * @param name name of the namespace |
|
128 * @param jmxurl input JMXServiceURL for creating the JMXConnectorServer |
|
129 * @param wombats names of WombatMBean it should contain. |
|
130 * @param children list of sub namespaces. |
|
131 * @return a NamespaceConfig. |
|
132 */ |
|
133 static NamespaceConfig config(String name, String jmxurl, String[] wombats, |
|
134 NamespaceConfig... children) { |
|
135 final NamespaceConfig cfg = new NamespaceConfig(); |
|
136 cfg.name=name; cfg.jmxurl=jmxurl; cfg.wombats=wombats; |
|
137 cfg.children=children; |
|
138 return cfg; |
|
139 } |
|
140 |
|
141 /** |
|
142 * Returns the given names. This is a utility method to ease code |
|
143 * reading. |
|
144 * @param names names of Wombat MBeans. |
|
145 * @return the given names. |
|
146 */ |
|
147 static String[] wombats(String... names) { |
|
148 return names; |
|
149 } |
|
150 |
|
151 /** |
|
152 * Creates a JMXServiceURL string for the given protocol. |
|
153 * This is also a utility method to ease code reading. |
|
154 * @param protocol The protocol name (e.g. "rmi") |
|
155 * @return A JMXServiceURL string. |
|
156 * @throws Exception if creation of the JMXServiceURL fails. |
|
157 */ |
|
158 static String url(String protocol) throws Exception { |
|
159 return new JMXServiceURL(protocol,null,0).toString(); |
|
160 } |
|
161 |
|
162 /** |
|
163 * Creates a config for a hierarchy of namespaces, mixing local namespaces |
|
164 * and remote namespaces using the given protocol. |
|
165 * @param protocol The protocol that should be used for remote namespaces. |
|
166 * @return A namespace config hierarchy. |
|
167 * @throws java.lang.Exception |
|
168 */ |
|
169 public static NamespaceConfig[] makeConfig(String protocol) |
|
170 throws Exception { |
|
171 final NamespaceConfig[] config = { |
|
172 // Top level namespace "top1" (local) |
|
173 config("top1",wombats("wchief","w1","w2","w3"), |
|
174 // top1//local1 |
|
175 config("local1",wombats("wchief","ww1","ww2")), |
|
176 // top1//local2 |
|
177 config("local2",wombats("wchief","ww4","ww5","ww6"), |
|
178 // top1//local2//local3 |
|
179 config("local3",wombats("wchief","www1","www2")), |
|
180 // top1//local2//rmi1 |
|
181 config("rmi1",url(protocol),wombats("wchief","www3","www4","www5"))), |
|
182 // top1//rmi2 |
|
183 config("rmi2",url(protocol),wombats("wchief","ww7","ww8","ww9"), |
|
184 // top1//rmi2//local4 |
|
185 config("local4",wombats("wchief","www6","www7")), |
|
186 // top1//rmi2//rmi3 |
|
187 config("rmi3",url(protocol),wombats("wchief","www3","www4","www5"), |
|
188 // top1//rmi2//rmi3//local5 |
|
189 config("local5",wombats("wchief","wwww1"))))), |
|
190 // Top level namespace "top2" (local) |
|
191 config("top2",wombats("wchief","w21","w22","w23"), |
|
192 // top2//local21 |
|
193 config("local21",wombats("wchief","ww21","ww22")), |
|
194 // top2//rmi22 |
|
195 config("rmi22",url(protocol),wombats("wchief","ww27","ww28","ww29"), |
|
196 // top2//rmi22//local24 |
|
197 config("local24",wombats("wchief","www26","www27")), |
|
198 // top2//rmi22//rmi23 |
|
199 config("rmi23",url(protocol),wombats("wchief","www23","www24","www25"), |
|
200 // top2//rmi22//rmi23//local25 |
|
201 config("local25",wombats("wchief","wwww21"))))), |
|
202 // Top level namespace "top3" (remote) |
|
203 config("top3",url(protocol),wombats("wchief","w31","w32","w33"), |
|
204 // top3//local31 |
|
205 config("local31",wombats("wchief","ww31","ww32")), |
|
206 // top3//rmi32 |
|
207 config("rmi32",url(protocol),wombats("wchief","ww37","ww38","ww39"), |
|
208 // top3//rmi32//local34 |
|
209 config("local34",wombats("wchief","www36","www37")), |
|
210 // top3//rmi32//rmi33 |
|
211 config("rmi33",url(protocol),wombats("wchief","www33","www34","www35"), |
|
212 // top3//rmi32//local35 |
|
213 config("local35",wombats("wchief","wwww31"))))), |
|
214 }; |
|
215 return config; |
|
216 } |
|
217 |
|
218 /** |
|
219 * Close all connector servers in the list. |
|
220 * @param cslist List of connector servers to close. |
|
221 */ |
|
222 public static void closeAll(List<JMXConnectorServer> cslist) { |
|
223 for (JMXConnectorServer cs : cslist) { |
|
224 try { |
|
225 cs.stop(); |
|
226 } catch (Exception xx) { |
|
227 System.err.println("Failed to stop connector: " + xx); |
|
228 } |
|
229 } |
|
230 } |
|
231 |
|
232 public static class MBeanServerConfigCreator { |
|
233 public MBeanServer createMBeanServerFor(NamespaceConfig config) { |
|
234 return MBeanServerFactory.newMBeanServer(); |
|
235 } |
|
236 } |
|
237 |
|
238 /** |
|
239 * Load the given namespace configuration inside the given MBeanServer. |
|
240 * Return a list of connector servers created in the process. |
|
241 * @param server The MBeanServer in which the namespaces must |
|
242 * be created. |
|
243 * @param namespaces The list of namespaces to create. |
|
244 * @return a list of started connector servers. |
|
245 * @throws java.lang.Exception failed to create the specified namespaces. |
|
246 */ |
|
247 public static List<JMXConnectorServer> load(MBeanServer server, |
|
248 MBeanServerConfigCreator factory, |
|
249 NamespaceConfig... namespaces) throws Exception { |
|
250 final List<JMXConnectorServer> cslist = |
|
251 new ArrayList<JMXConnectorServer>(); |
|
252 try { |
|
253 final ObjectName creator = |
|
254 new ObjectName("jmx.creator:type=JMXNamespaceCreator"); |
|
255 if (System.getProperty("jmx.wait")!=null |
|
256 && !server.isRegistered(creator)) { |
|
257 server.registerMBean(new JMXNamespaceCreator(),creator); |
|
258 } |
|
259 for (NamespaceConfig cfg : namespaces) { |
|
260 final MBeanServer srv = factory.createMBeanServerFor(cfg); |
|
261 if (System.getProperty("jmx.wait")!=null |
|
262 && !srv.isRegistered(creator)) { |
|
263 srv.registerMBean(new JMXNamespaceCreator(),creator); |
|
264 } |
|
265 if (cfg.wombats != null) { |
|
266 for (String w : cfg.wombats) { |
|
267 final ObjectName n = |
|
268 new ObjectName("wombat:type=Wombat,name=" + w); |
|
269 final WombatMBean ww = new Wombat(); |
|
270 srv.registerMBean(ww, n); |
|
271 } |
|
272 } |
|
273 if (cfg.children != null) { |
|
274 cslist.addAll(load(srv, factory, cfg.children)); |
|
275 } |
|
276 JMXNamespace nm; |
|
277 if (cfg.jmxurl == null) { |
|
278 nm = new JMXNamespace(srv); |
|
279 } else { |
|
280 JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(new JMXServiceURL(cfg.jmxurl), |
|
281 null, srv); |
|
282 srv.registerMBean(cs, |
|
283 new ObjectName("jmx.remote:type=JMXConnectorServer")); |
|
284 cs.start(); |
|
285 cslist.add(cs); |
|
286 nm = JMXRemoteNamespace. |
|
287 newJMXRemoteNamespace(cs.getAddress(), |
|
288 null); |
|
289 } |
|
290 server.registerMBean(nm, |
|
291 JMXNamespaces.getNamespaceObjectName(cfg.name)); |
|
292 if (nm instanceof JMXRemoteNamespace) { |
|
293 server.invoke( |
|
294 JMXNamespaces.getNamespaceObjectName(cfg.name), |
|
295 "connect", null, null); |
|
296 } |
|
297 } |
|
298 } catch (Exception x) { |
|
299 closeAll(cslist); |
|
300 throw x; |
|
301 } |
|
302 return cslist; |
|
303 } |
|
304 |
|
305 /** |
|
306 * Add an entry {@code <path,NamespaceConfig>} in the map for the given |
|
307 * namespace and its subnamespaces. |
|
308 * @param map A {@code Map<path,NamespaceConfig>}. |
|
309 * @param parent The path of the parent workspace. |
|
310 * @param cfg The NamespaceConfig hierarchy to index in the map. |
|
311 */ |
|
312 public static void fillMap(Map<String,NamespaceConfig> map, String parent, |
|
313 NamespaceConfig cfg) { |
|
314 |
|
315 final String where; |
|
316 if (parent == null || parent.equals("")) |
|
317 where=cfg.name; |
|
318 else |
|
319 where=parent+JMXNamespaces.NAMESPACE_SEPARATOR+cfg.name; |
|
320 map.put(where,cfg); |
|
321 if (cfg.children==null) return; |
|
322 for(NamespaceConfig child:cfg.children) { |
|
323 fillMap(map,where,child); |
|
324 } |
|
325 } |
|
326 |
|
327 /** |
|
328 * Compare a list of namespace names obtained from JMXNamespaceView.list() |
|
329 * with the expected clildren list of the corresponding NamespaceConfig. |
|
330 * @param list A list of namespace names |
|
331 * @param children A list of NamespaceConfig correspondng to expected |
|
332 * namespace. |
|
333 * @param fail If true and the comparison yields false, throws an |
|
334 * exception instead of simply returning false. |
|
335 * @return true if OK, false if NOK. |
|
336 */ |
|
337 private static boolean compare(String[] list, NamespaceConfig[] children, |
|
338 boolean fail) { |
|
339 final List<String> found = new ArrayList<String>(Arrays.asList(list)); |
|
340 if (found.contains(ClientContext.NAMESPACE)) |
|
341 found.remove(ClientContext.NAMESPACE); |
|
342 |
|
343 if (children == null && found.size()==0) return true; |
|
344 if (children == null && fail == false) return false; |
|
345 if (children == null) throw new RuntimeException( |
|
346 "No child expected. Found "+Arrays.toString(list)); |
|
347 final Set<String> names = new HashSet<String>(); |
|
348 for (NamespaceConfig cfg : children) { |
|
349 names.add(cfg.name); |
|
350 if (found.contains(cfg.name)) continue; |
|
351 if (!fail) return false; |
|
352 throw new RuntimeException(cfg.name+" not found in "+ |
|
353 found); |
|
354 } |
|
355 found.removeAll(names); |
|
356 if (found.size()==0) return true; |
|
357 if (fail==false) return false; |
|
358 throw new RuntimeException("found additional namespaces: "+ |
|
359 found); |
|
360 } |
|
361 |
|
362 /** |
|
363 * Compares the result of queryNames(null,null) with a set of expected |
|
364 * wombats. |
|
365 * @param where The path of the namespace that was queried. |
|
366 * @param list The set of ObjectNames found. |
|
367 * @param wombats The expected list of wombats. |
|
368 * @param fail If true and the comparison yields false, throws an |
|
369 * exception instead of simply returning false. |
|
370 * @return true if OK, false if NOK. |
|
371 * @throws java.lang.Exception something went wrong. |
|
372 */ |
|
373 private static boolean compare(String where, |
|
374 Set<ObjectName>list, String[] wombats, |
|
375 boolean fail) throws Exception { |
|
376 final Set<ObjectName> found = new HashSet<ObjectName>(); |
|
377 final Set<ObjectName> expected = new HashSet<ObjectName>(); |
|
378 for (ObjectName n : list) { |
|
379 if ("Wombat".equals(n.getKeyProperty("type"))) |
|
380 found.add(n); |
|
381 } |
|
382 for(String w : wombats) { |
|
383 final ObjectName n = |
|
384 new ObjectName("wombat:type=Wombat,name=" + w); |
|
385 expected.add(n); |
|
386 if (found.contains(n)) continue; |
|
387 if (fail == false) return false; |
|
388 throw new RuntimeException(where+ |
|
389 ": Wombat "+w+" not found in "+found); |
|
390 } |
|
391 found.removeAll(expected); |
|
392 if (found.size()==0) { |
|
393 System.out.println(where+": found all expected: "+expected); |
|
394 return true; |
|
395 } |
|
396 if (fail==false) return false; |
|
397 throw new RuntimeException(where+": found additional MBeans: "+ |
|
398 found); |
|
399 } |
|
400 |
|
401 /** |
|
402 * A generic test to test JMXNamespaceView over a namespace configuration. |
|
403 * @param server The MBeanServer in which to load the namespace |
|
404 * config. |
|
405 * @param namespaces The namespace config to run the test over... |
|
406 * @throws java.lang.Exception |
|
407 */ |
|
408 public static void doTest(MBeanServer server, NamespaceConfig... namespaces) |
|
409 throws Exception { |
|
410 List<JMXConnectorServer> cslist = load(server, |
|
411 new MBeanServerConfigCreator(), namespaces); |
|
412 Map<String,NamespaceConfig> inputMap = |
|
413 new HashMap<String,NamespaceConfig>(); |
|
414 |
|
415 for (NamespaceConfig cfg : namespaces) { |
|
416 fillMap(inputMap,"",cfg); |
|
417 } |
|
418 try { |
|
419 final JMXNamespaceView root = new JMXNamespaceView(server); |
|
420 List<JMXNamespaceView> vlist = new ArrayList<JMXNamespaceView>(); |
|
421 vlist.add(root); |
|
422 |
|
423 while (!vlist.isEmpty()) { |
|
424 JMXNamespaceView v = vlist.remove(0); |
|
425 final String where = v.isRoot()?"root":v.where(); |
|
426 System.out.println(where+": "+ |
|
427 v.getMBeanServerConnection().queryNames(null,null)); |
|
428 for (String ns : v.list()) { |
|
429 final JMXNamespaceView down = v.down(ns); |
|
430 vlist.add(down); |
|
431 if (!down.where().equals(v.isRoot()?ns:where+ |
|
432 JMXNamespaces.NAMESPACE_SEPARATOR+ns)) { |
|
433 throw new RuntimeException("path of "+down.where()+ |
|
434 " should be "+(v.isRoot()?ns:where+ |
|
435 JMXNamespaces.NAMESPACE_SEPARATOR+ns)); |
|
436 } |
|
437 if (down.up().equals(v)) continue; |
|
438 throw new RuntimeException("parent of "+down.where()+ |
|
439 " should be "+where); |
|
440 } |
|
441 final NamespaceConfig[] children; |
|
442 final NamespaceConfig cfg; |
|
443 if (v.isRoot()) { |
|
444 children=namespaces; |
|
445 cfg = null; |
|
446 } else { |
|
447 cfg = inputMap.get(where); |
|
448 children = cfg==null?null:cfg.children; |
|
449 } |
|
450 compare(v.list(),children,true); |
|
451 if (!v.isRoot()) { |
|
452 if (where.endsWith(ClientContext.NAMESPACE)) { |
|
453 System.out.println(where+": skipping queryNames analysis"); |
|
454 continue; |
|
455 } |
|
456 //System.out.println(where+": cfg is: "+cfg); |
|
457 compare(where,v.getMBeanServerConnection(). |
|
458 queryNames(null, null),cfg.wombats,true); |
|
459 } |
|
460 } |
|
461 |
|
462 exportAndWaitIfNeeded(server); |
|
463 } finally { |
|
464 closeAll(cslist); |
|
465 } |
|
466 } |
|
467 |
|
468 public static interface JMXNamespaceCreatorMBean { |
|
469 public ObjectInstance createLocalNamespace(String namespace) |
|
470 throws JMException ; |
|
471 public void removeLocalNamespace(String namespace) |
|
472 throws JMException; |
|
473 } |
|
474 |
|
475 public static class JMXNamespaceCreator |
|
476 implements MBeanRegistration, |
|
477 JMXNamespaceCreatorMBean { |
|
478 |
|
479 private volatile MBeanServer mbeanServer; |
|
480 |
|
481 public ObjectInstance createLocalNamespace(String namespace) |
|
482 throws JMException { |
|
483 return mbeanServer.registerMBean( |
|
484 new JMXNamespace(MBeanServerFactory.newMBeanServer()), |
|
485 JMXNamespaces.getNamespaceObjectName(namespace)); |
|
486 } |
|
487 |
|
488 public void removeLocalNamespace(String namespace) |
|
489 throws JMException { |
|
490 mbeanServer.unregisterMBean( |
|
491 JMXNamespaces.getNamespaceObjectName(namespace)); |
|
492 } |
|
493 |
|
494 public ObjectName preRegister(MBeanServer server, ObjectName name) |
|
495 throws Exception { |
|
496 mbeanServer = server; |
|
497 return name; |
|
498 } |
|
499 |
|
500 public void postRegister(Boolean registrationDone) { |
|
501 } |
|
502 |
|
503 public void preDeregister() throws Exception { |
|
504 } |
|
505 |
|
506 public void postDeregister() { |
|
507 } |
|
508 |
|
509 } |
|
510 |
|
511 public static void exportAndWaitIfNeeded(MBeanServer server) |
|
512 throws Exception { |
|
513 if (System.getProperty("jmx.wait")!=null) { |
|
514 final int port = getPortFor("rmi"); |
|
515 LocateRegistry.createRegistry(port); |
|
516 final JMXServiceURL url = |
|
517 new JMXServiceURL("rmi",null,port, |
|
518 "/jndi/rmi://localhost:"+port+"/jmxrmi"); |
|
519 final JMXConnectorServer cs = |
|
520 JMXConnectorServerFactory. |
|
521 newJMXConnectorServer(url, null, server); |
|
522 cs.start(); |
|
523 try { |
|
524 System.out.println("RMI Server waiting at: "+cs.getAddress()); |
|
525 System.in.read(); |
|
526 } finally { |
|
527 cs.stop(); |
|
528 } |
|
529 } |
|
530 } |
|
531 |
|
532 public static int getPortFor(String protocol) throws Exception { |
|
533 final int aport = |
|
534 Integer.valueOf(System.getProperty("jmx."+protocol+".port","0")); |
|
535 if (aport > 0) return aport; |
|
536 final ServerSocket s = new ServerSocket(0); |
|
537 try { |
|
538 final int port = s.getLocalPort(); |
|
539 return port; |
|
540 } finally { |
|
541 s.close(); |
|
542 } |
|
543 } |
|
544 |
|
545 public static void main(String[] args) throws Exception { |
|
546 doTest(ManagementFactory.getPlatformMBeanServer(),makeConfig("rmi")); |
|
547 } |
|
548 |
|
549 } |