Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/javax/management/remote/mandatory/loading/MissingClassTest.java
38867 views
/*1* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 4915825 4921009 4934965 4977469 801958426* @summary Tests behavior when client or server gets object of unknown class27* @author Eamonn McManus28* @run clean MissingClassTest SingleClassLoader29* @run build MissingClassTest SingleClassLoader30* @run main MissingClassTest31* @key randomness32*/3334/*35Tests that clients and servers react correctly when they receive36objects of unknown classes. This can happen easily due to version37skew or missing jar files on one end or the other. The default38behaviour of causing a connection to die because of the resultant39IOException is not acceptable! We try sending attributes and invoke40parameters to the server of classes it doesn't know, and we try41sending attributes, exceptions and notifications to the client of42classes it doesn't know.4344We also test objects that are of known class but not serializable.45The test cases are similar.46*/4748import java.io.ByteArrayOutputStream;49import java.io.IOException;50import java.io.NotSerializableException;51import java.io.ObjectOutputStream;52import java.net.MalformedURLException;53import java.util.HashMap;54import java.util.Map;55import java.util.Random;56import java.util.Set;57import javax.management.Attribute;58import javax.management.MBeanServer;59import javax.management.MBeanServerConnection;60import javax.management.MBeanServerFactory;61import javax.management.Notification;62import javax.management.NotificationBroadcasterSupport;63import javax.management.NotificationFilter;64import javax.management.NotificationListener;65import javax.management.ObjectName;66import javax.management.remote.JMXConnectionNotification;67import javax.management.remote.JMXConnector;68import javax.management.remote.JMXConnectorFactory;69import javax.management.remote.JMXConnectorServer;70import javax.management.remote.JMXConnectorServerFactory;71import javax.management.remote.JMXServiceURL;72import javax.management.remote.rmi.RMIConnectorServer;7374public class MissingClassTest {75private static final int NNOTIFS = 50;7677private static ClassLoader clientLoader, serverLoader;78private static Object serverUnknown;79private static Exception clientUnknown;80private static ObjectName on;81private static final Object[] NO_OBJECTS = new Object[0];82private static final String[] NO_STRINGS = new String[0];8384private static final Object unserializableObject = Thread.currentThread();8586private static boolean isInstance(Object o, String cn) {87try {88Class<?> c = Class.forName(cn);89return c.isInstance(o);90} catch (ClassNotFoundException x) {91return false;92}93}9495public static void main(String[] args) throws Exception {96System.out.println("Test that the client or server end of a " +97"connection does not fail if sent an object " +98"it cannot deserialize");99100on = new ObjectName("test:type=Test");101102ClassLoader testLoader = MissingClassTest.class.getClassLoader();103clientLoader =104new SingleClassLoader("$ServerUnknown$", HashMap.class,105testLoader);106serverLoader =107new SingleClassLoader("$ClientUnknown$", Exception.class,108testLoader);109serverUnknown =110clientLoader.loadClass("$ServerUnknown$").newInstance();111clientUnknown = (Exception)112serverLoader.loadClass("$ClientUnknown$").newInstance();113114final String[] protos = {"rmi", /*"iiop",*/ "jmxmp"};115boolean ok = true;116for (int i = 0; i < protos.length; i++) {117try {118ok &= test(protos[i]);119} catch (Exception e) {120System.out.println("TEST FAILED WITH EXCEPTION:");121e.printStackTrace(System.out);122ok = false;123}124}125126if (ok)127System.out.println("Test passed");128else {129throw new RuntimeException("TEST FAILED");130}131}132133private static boolean test(String proto) throws Exception {134System.out.println("Testing for proto " + proto);135136boolean ok = true;137138MBeanServer mbs = MBeanServerFactory.newMBeanServer();139mbs.createMBean(Test.class.getName(), on);140141JMXConnectorServer cs;142JMXServiceURL url = new JMXServiceURL(proto, null, 0);143Map<String,Object> serverMap = new HashMap<>();144serverMap.put(JMXConnectorServerFactory.DEFAULT_CLASS_LOADER,145serverLoader);146147// make sure no auto-close at server side148serverMap.put("jmx.remote.x.server.connection.timeout", "888888888");149150try {151cs = JMXConnectorServerFactory.newJMXConnectorServer(url,152serverMap,153mbs);154} catch (MalformedURLException e) {155System.out.println("System does not recognize URL: " + url +156"; ignoring");157return true;158}159cs.start();160JMXServiceURL addr = cs.getAddress();161Map<String,Object> clientMap = new HashMap<>();162clientMap.put(JMXConnectorFactory.DEFAULT_CLASS_LOADER,163clientLoader);164165System.out.println("Connecting for client-unknown test");166167JMXConnector client = JMXConnectorFactory.connect(addr, clientMap);168169// add a listener to verify no failed notif170CNListener cnListener = new CNListener();171client.addConnectionNotificationListener(cnListener, null, null);172173MBeanServerConnection mbsc = client.getMBeanServerConnection();174175System.out.println("Getting attribute with class unknown to client");176try {177Object result = mbsc.getAttribute(on, "ClientUnknown");178System.out.println("TEST FAILS: getAttribute for class " +179"unknown to client should fail, returned: " +180result);181ok = false;182} catch (IOException e) {183Throwable cause = e.getCause();184if (isInstance(cause, "org.omg.CORBA.MARSHAL")) // see CR 4935098185cause = cause.getCause();186if (cause instanceof ClassNotFoundException) {187System.out.println("Success: got an IOException wrapping " +188"a ClassNotFoundException");189} else {190System.out.println("TEST FAILS: Caught IOException (" + e +191") but cause should be " +192"ClassNotFoundException: " + cause);193ok = false;194}195}196197System.out.println("Doing queryNames to ensure connection alive");198Set<ObjectName> names = mbsc.queryNames(null, null);199System.out.println("queryNames returned " + names);200201System.out.println("Provoke exception of unknown class");202try {203mbsc.invoke(on, "throwClientUnknown", NO_OBJECTS, NO_STRINGS);204System.out.println("TEST FAILS: did not get exception");205ok = false;206} catch (IOException e) {207Throwable wrapped = e.getCause();208if (isInstance(wrapped, "org.omg.CORBA.MARSHAL")) // see CR 4935098209wrapped = wrapped.getCause();210if (wrapped instanceof ClassNotFoundException) {211System.out.println("Success: got an IOException wrapping " +212"a ClassNotFoundException: " +213wrapped);214} else {215System.out.println("TEST FAILS: Got IOException but cause " +216"should be ClassNotFoundException: ");217if (wrapped == null)218System.out.println("(null)");219else220wrapped.printStackTrace(System.out);221ok = false;222}223} catch (Exception e) {224System.out.println("TEST FAILS: Got wrong exception: " +225"should be IOException with cause " +226"ClassNotFoundException:");227e.printStackTrace(System.out);228ok = false;229}230231System.out.println("Doing queryNames to ensure connection alive");232names = mbsc.queryNames(null, null);233System.out.println("queryNames returned " + names);234235ok &= notifyTest(client, mbsc);236237System.out.println("Doing queryNames to ensure connection alive");238names = mbsc.queryNames(null, null);239System.out.println("queryNames returned " + names);240241for (int i = 0; i < 2; i++) {242boolean setAttribute = (i == 0); // else invoke243String what = setAttribute ? "setAttribute" : "invoke";244System.out.println("Trying " + what +245" with class unknown to server");246try {247if (setAttribute) {248mbsc.setAttribute(on, new Attribute("ServerUnknown",249serverUnknown));250} else {251mbsc.invoke(on, "useServerUnknown",252new Object[] {serverUnknown},253new String[] {"java.lang.Object"});254}255System.out.println("TEST FAILS: " + what + " with " +256"class unknown to server should fail " +257"but did not");258ok = false;259} catch (IOException e) {260Throwable cause = e.getCause();261if (isInstance(cause, "org.omg.CORBA.MARSHAL")) // see CR 4935098262cause = cause.getCause();263if (cause instanceof ClassNotFoundException) {264System.out.println("Success: got an IOException " +265"wrapping a ClassNotFoundException");266} else {267System.out.println("TEST FAILS: Caught IOException (" + e +268") but cause should be " +269"ClassNotFoundException: " + cause);270e.printStackTrace(System.out); // XXX271ok = false;272}273}274}275276System.out.println("Doing queryNames to ensure connection alive");277names = mbsc.queryNames(null, null);278System.out.println("queryNames returned " + names);279280System.out.println("Trying to get unserializable attribute");281try {282mbsc.getAttribute(on, "Unserializable");283System.out.println("TEST FAILS: get unserializable worked " +284"but should not");285ok = false;286} catch (IOException e) {287System.out.println("Success: got an IOException: " + e +288" (cause: " + e.getCause() + ")");289}290291System.out.println("Doing queryNames to ensure connection alive");292names = mbsc.queryNames(null, null);293System.out.println("queryNames returned " + names);294295System.out.println("Trying to set unserializable attribute");296try {297Attribute attr =298new Attribute("Unserializable", unserializableObject);299mbsc.setAttribute(on, attr);300System.out.println("TEST FAILS: set unserializable worked " +301"but should not");302ok = false;303} catch (IOException e) {304System.out.println("Success: got an IOException: " + e +305" (cause: " + e.getCause() + ")");306}307308System.out.println("Doing queryNames to ensure connection alive");309names = mbsc.queryNames(null, null);310System.out.println("queryNames returned " + names);311312System.out.println("Trying to throw unserializable exception");313try {314mbsc.invoke(on, "throwUnserializable", NO_OBJECTS, NO_STRINGS);315System.out.println("TEST FAILS: throw unserializable worked " +316"but should not");317ok = false;318} catch (IOException e) {319System.out.println("Success: got an IOException: " + e +320" (cause: " + e.getCause() + ")");321}322323client.removeConnectionNotificationListener(cnListener);324ok = ok && !cnListener.failed;325326client.close();327cs.stop();328329if (ok)330System.out.println("Test passed for protocol " + proto);331332System.out.println();333return ok;334}335336private static class TestListener implements NotificationListener {337TestListener(LostListener ll) {338this.lostListener = ll;339}340341public void handleNotification(Notification n, Object h) {342/* Connectors can handle unserializable notifications in343one of two ways. Either they can arrange for the344client to get a NotSerializableException from its345fetchNotifications call (RMI connector), or they can346replace the unserializable notification by a347JMXConnectionNotification.NOTIFS_LOST (JMXMP348connector). The former case is handled by code within349the connector client which will end up sending a350NOTIFS_LOST to our LostListener. The logic here351handles the latter case by converting it into the352former.353*/354if (n instanceof JMXConnectionNotification355&& n.getType().equals(JMXConnectionNotification.NOTIFS_LOST)) {356lostListener.handleNotification(n, h);357return;358}359360synchronized (result) {361if (!n.getType().equals("interesting")362|| !n.getUserData().equals("known")) {363System.out.println("TestListener received strange notif: "364+ notificationString(n));365result.failed = true;366result.notifyAll();367} else {368result.knownCount++;369if (result.knownCount == NNOTIFS)370result.notifyAll();371}372}373}374375private LostListener lostListener;376}377378private static class LostListener implements NotificationListener {379public void handleNotification(Notification n, Object h) {380synchronized (result) {381handle(n, h);382}383}384385private void handle(Notification n, Object h) {386if (!(n instanceof JMXConnectionNotification)) {387System.out.println("LostListener received strange notif: " +388notificationString(n));389result.failed = true;390result.notifyAll();391return;392}393394JMXConnectionNotification jn = (JMXConnectionNotification) n;395if (!jn.getType().equals(jn.NOTIFS_LOST)) {396System.out.println("Ignoring JMXConnectionNotification: " +397notificationString(jn));398return;399}400final String msg = jn.getMessage();401if ((!msg.startsWith("Dropped ")402|| !msg.endsWith("classes were missing locally"))403&& (!msg.startsWith("Not serializable: "))) {404System.out.println("Surprising NOTIFS_LOST getMessage: " +405msg);406}407if (!(jn.getUserData() instanceof Long)) {408System.out.println("JMXConnectionNotification userData " +409"not a Long: " + jn.getUserData());410result.failed = true;411} else {412int lost = ((Long) jn.getUserData()).intValue();413result.lostCount += lost;414if (result.lostCount == NNOTIFS*2)415result.notifyAll();416}417}418}419420private static class TestFilter implements NotificationFilter {421public boolean isNotificationEnabled(Notification n) {422return (n.getType().equals("interesting"));423}424}425426private static class Result {427int knownCount, lostCount;428boolean failed;429}430private static Result result;431432/* Send a bunch of notifications to exercise the logic to recover433from unknown notification classes. We have four kinds of434notifications: "known" ones are of a class known to the client435and which match its filters; "unknown" ones are of a class that436match the client's filters but that the client can't load;437"tricky" ones are unserializable; and "boring" notifications438are of a class that the client knows but that doesn't match its439filters. We emit NNOTIFS notifications of each kind. We do a440random shuffle on these 4*NNOTIFS notifications so it is likely441that we will cover the various different cases in the logic.442443Specifically, what we are testing here is the logic that444recovers from a fetchNotifications request that gets a445ClassNotFoundException. Because the request can contain446several notifications, the client doesn't know which of them447generated the exception. So it redoes a request that asks for448just one notification. We need to be sure that this works when449that one notification is of an unknown class and when it is of450a known class, and in both cases when there are filtered451notifications that are skipped.452453We are also testing the behaviour in the server when it tries454to include an unserializable notification in the response to a455fetchNotifications, and in the client when that happens.456457If the test succeeds, the listener should receive the NNOTIFS458"known" notifications, and the connection listener should459receive an indication of NNOTIFS lost notifications460representing the "unknown" notifications.461462We depend on some implementation-specific features here:4634641. The buffer size is sufficient to contain the 4*NNOTIFS465notifications which are all sent at once, before the client466gets a chance to start receiving them.4674682. When one or more notifications are dropped because they are469of unknown classes, the NOTIFS_LOST notification contains a470userData that is a Long with a count of the number dropped.4714723. If a notification is not serializable on the server, the473client gets told about it somehow, rather than having it just474dropped on the floor. The somehow might be through an RMI475exception, or it might be by the server replacing the476unserializable notif by a JMXConnectionNotification.NOTIFS_LOST.477*/478private static boolean notifyTest(JMXConnector client,479MBeanServerConnection mbsc)480throws Exception {481System.out.println("Send notifications including unknown ones");482result = new Result();483LostListener ll = new LostListener();484client.addConnectionNotificationListener(ll, null, null);485TestListener nl = new TestListener(ll);486mbsc.addNotificationListener(on, nl, new TestFilter(), null);487mbsc.invoke(on, "sendNotifs", NO_OBJECTS, NO_STRINGS);488489// wait for the listeners to receive all their notifs490// or to fail491long deadline = System.currentTimeMillis() + 60000;492long remain;493while ((remain = deadline - System.currentTimeMillis()) >= 0) {494synchronized (result) {495if (result.failed496|| (result.knownCount >= NNOTIFS497&& result.lostCount >= NNOTIFS*2))498break;499result.wait(remain);500}501}502Thread.sleep(2); // allow any spurious extra notifs to arrive503if (result.failed) {504System.out.println("TEST FAILS: Notification strangeness");505return false;506} else if (result.knownCount == NNOTIFS507&& result.lostCount == NNOTIFS*2) {508System.out.println("Success: received known notifications and " +509"got NOTIFS_LOST for unknown and " +510"unserializable ones");511return true;512} else if (result.knownCount >= NNOTIFS513|| result.lostCount >= NNOTIFS*2) {514System.out.println("TEST FAILS: Received too many notifs: " +515"known=" + result.knownCount + "; lost=" + result.lostCount);516return false;517} else {518System.out.println("TEST FAILS: Timed out without receiving " +519"all notifs: known=" + result.knownCount +520"; lost=" + result.lostCount);521return false;522}523}524525public static interface TestMBean {526public Object getClientUnknown() throws Exception;527public void throwClientUnknown() throws Exception;528public void setServerUnknown(Object o) throws Exception;529public void useServerUnknown(Object o) throws Exception;530public Object getUnserializable() throws Exception;531public void setUnserializable(Object un) throws Exception;532public void throwUnserializable() throws Exception;533public void sendNotifs() throws Exception;534}535536public static class Test extends NotificationBroadcasterSupport537implements TestMBean {538539public Object getClientUnknown() {540return clientUnknown;541}542543public void throwClientUnknown() throws Exception {544throw clientUnknown;545}546547public void setServerUnknown(Object o) {548throw new IllegalArgumentException("setServerUnknown succeeded "+549"but should not have");550}551552public void useServerUnknown(Object o) {553throw new IllegalArgumentException("useServerUnknown succeeded "+554"but should not have");555}556557public Object getUnserializable() {558return unserializableObject;559}560561public void setUnserializable(Object un) {562throw new IllegalArgumentException("setUnserializable succeeded " +563"but should not have");564}565566public void throwUnserializable() throws Exception {567throw new Exception() {568private Object unserializable = unserializableObject;569};570}571572public void sendNotifs() {573/* We actually send the same four notification objects574NNOTIFS times each. This doesn't particularly matter,575but note that the MBeanServer will replace "this" by576the sender's ObjectName the first time. Since that's577always the same, no problem. */578Notification known =579new Notification("interesting", this, 1L, 1L, "known");580known.setUserData("known");581Notification unknown =582new Notification("interesting", this, 1L, 1L, "unknown");583unknown.setUserData(clientUnknown);584Notification boring =585new Notification("boring", this, 1L, 1L, "boring");586Notification tricky =587new Notification("interesting", this, 1L, 1L, "tricky");588tricky.setUserData(unserializableObject);589590// check that the tricky notif is indeed unserializable591try {592new ObjectOutputStream(new ByteArrayOutputStream())593.writeObject(tricky);594throw new RuntimeException("TEST INCORRECT: tricky notif is " +595"serializable");596} catch (NotSerializableException e) {597// OK: tricky notif is not serializable598} catch (IOException e) {599throw new RuntimeException("TEST INCORRECT: tricky notif " +600"serialization check failed");601}602603/* Now shuffle an imaginary deck of cards where K, U, T, and604B (known, unknown, tricky, boring) each appear NNOTIFS times.605We explicitly seed the random number generator so we606can reproduce rare test failures if necessary. We only607use a StringBuffer so we can print the shuffled deck --608otherwise we could just emit the notifications as the609cards are placed. */610long seed = System.currentTimeMillis();611System.out.println("Random number seed is " + seed);612Random r = new Random(seed);613int knownCount = NNOTIFS; // remaining K cards614int unknownCount = NNOTIFS; // remaining U cards615int trickyCount = NNOTIFS; // remaining T cards616int boringCount = NNOTIFS; // remaining B cards617StringBuffer notifList = new StringBuffer();618for (int i = NNOTIFS * 4; i > 0; i--) {619int rand = r.nextInt(i);620// use rand to pick a card from the remaining ones621if ((rand -= knownCount) < 0) {622notifList.append('k');623knownCount--;624} else if ((rand -= unknownCount) < 0) {625notifList.append('u');626unknownCount--;627} else if ((rand -= trickyCount) < 0) {628notifList.append('t');629trickyCount--;630} else {631notifList.append('b');632boringCount--;633}634}635if (knownCount != 0 || unknownCount != 0636|| trickyCount != 0 || boringCount != 0) {637throw new RuntimeException("TEST INCORRECT: Shuffle failed: " +638"known=" + knownCount +" unknown=" +639unknownCount + " tricky=" + trickyCount +640" boring=" + boringCount +641" deal=" + notifList);642}643String notifs = notifList.toString();644System.out.println("Shuffle: " + notifs);645for (int i = 0; i < NNOTIFS * 4; i++) {646Notification n;647switch (notifs.charAt(i)) {648case 'k': n = known; break;649case 'u': n = unknown; break;650case 't': n = tricky; break;651case 'b': n = boring; break;652default:653throw new RuntimeException("TEST INCORRECT: Bad shuffle char: " +654notifs.charAt(i));655}656sendNotification(n);657}658}659}660661private static String notificationString(Notification n) {662return n.getClass().getName() + "/" + n.getType() + " \"" +663n.getMessage() + "\" <" + n.getUserData() + ">";664}665666//667private static class CNListener implements NotificationListener {668public void handleNotification(Notification n, Object o) {669if (n instanceof JMXConnectionNotification) {670JMXConnectionNotification jn = (JMXConnectionNotification)n;671if (JMXConnectionNotification.FAILED.equals(jn.getType())) {672failed = true;673}674}675}676677public boolean failed = false;678}679}680681682