/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package functionaltests.jmx;
import static org.junit.Assert.*;
import java.io.IOException;
import java.security.PublicKey;
import java.util.HashMap;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.junit.Test;
import org.objectweb.proactive.core.node.Node;
import org.ow2.proactive.authentication.crypto.CredData;
import org.ow2.proactive.authentication.crypto.Credentials;
import org.ow2.proactive.jmx.JMXClientHelper;
import org.ow2.proactive.jmx.naming.JMXTransportProtocol;
import org.ow2.proactive.jmx.provider.JMXProviderUtils;
import org.ow2.proactive.resourcemanager.authentication.RMAuthentication;
import org.ow2.proactive.resourcemanager.common.event.RMEventType;
import org.ow2.proactive.resourcemanager.core.jmx.RMJMXBeans;
import org.ow2.proactive.resourcemanager.core.properties.PAResourceManagerProperties;
import functionaltests.utils.RMFunctionalTest;
import functionaltests.utils.RMTHelper;
import functionaltests.utils.TestUsers;
/**
* Test the JMX infrastructure of the Resource Manager. This test supposes the
* {@link Users#TEST_USERNAME} user has all permissions and the demo user is well
* defined and have user-level permissions.
*
* @author ProActive team
*/
public final class ResourceManagerJMXTest extends RMFunctionalTest {
@Test
public void action() throws Exception {
final RMAuthentication auth = rmHelper.getRMAuth();
final PublicKey pubKey = auth.getPublicKey();
final Credentials adminCreds = Credentials.createCredentials(new CredData(TestUsers.TEST.username,
TestUsers.TEST.password),
pubKey);
final JMXServiceURL jmxRmiServiceURL = new JMXServiceURL(auth.getJMXConnectorURL(JMXTransportProtocol.RMI));
final JMXServiceURL jmxRoServiceURL = new JMXServiceURL(auth.getJMXConnectorURL(JMXTransportProtocol.RO));
final ObjectName allAccountsMBeanName = new ObjectName(RMJMXBeans.ALLACCOUNTS_MBEAN_NAME);
final ObjectName myAccountMBeanName = new ObjectName(RMJMXBeans.MYACCOUNT_MBEAN_NAME);
final ObjectName runtimeDataMBeanName = new ObjectName(RMJMXBeans.RUNTIMEDATA_MBEAN_NAME);
final ObjectName managementMBeanName = new ObjectName(RMJMXBeans.MANAGEMENT_MBEAN_NAME);
final String suffix = "/" + PAResourceManagerProperties.RM_JMX_CONNECTOR_NAME.getValueAsString();
jmxURLsAreWellFormed(jmxRmiServiceURL, jmxRoServiceURL, suffix);
jmxAuthInvalidCreds(jmxRmiServiceURL);
jmxAuthNullLoginPassword(jmxRmiServiceURL);
jmxAuthInvalidLoginPassword(jmxRmiServiceURL);
jmxRMIAsUser(jmxRmiServiceURL, allAccountsMBeanName, myAccountMBeanName, runtimeDataMBeanName);
jmxRemoteObjectAsAdmin(adminCreds, jmxRoServiceURL, allAccountsMBeanName, managementMBeanName);
simultaneousRMIAndROConnections(adminCreds, jmxRmiServiceURL, jmxRoServiceURL);
jmxClientHelper(auth, adminCreds);
}
private void jmxAuthInvalidLoginPassword(JMXServiceURL jmxRmiServiceURL) {
RMTHelper.log("Test invalid JMX auth with bad login/password creds");
// Create the environment
final HashMap<String, Object> env = new HashMap<>(1);
env.put(JMXConnector.CREDENTIALS, new Object[] { "abra", "cadabra" });
try {
JMXConnectorFactory.connect(jmxRmiServiceURL, env);
} catch (Exception e) {
assertTrue("JMX auth must throw SecurityException if a client tries to connect with bad " +
"login/password credentials the env", e instanceof SecurityException);
}
}
private void jmxRemoteObjectAsAdmin(Credentials adminCreds, JMXServiceURL jmxRoServiceURL,
ObjectName allAccountsMBeanName, ObjectName managementMBeanName)
throws IOException, InstanceNotFoundException, IntrospectionException, ReflectionException {
// Test as admin over RO
RMTHelper.log("Test as admin 1, auth with login/creds over RO and check connection");
// Create the environment
final HashMap<String, Object> env = new HashMap<>(1);
env.put(JMXConnector.CREDENTIALS, new Object[] { TestUsers.TEST.username, adminCreds });
env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, JMXProviderUtils.RO_PROVIDER_PKGS);
// Connect to the JMX RO Connector Server
final JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxRoServiceURL, env);
final MBeanServerConnection conn = jmxConnector.getMBeanServerConnection();
// Check that the MBean Server connection is not null
assertNotNull("Unable to obtain the MBean server connection over RO", conn);
RMTHelper.log("Test as admin 2 - Check ManagementMBean is registered in the MBean server");
assertTrue("ManagementMBean is not registered", conn.isRegistered(managementMBeanName));
RMTHelper.log("Test as admin 3 - Check ManagementMBean attributes do not throw exception");
final MBeanInfo mInfo = conn.getMBeanInfo(managementMBeanName);
for (final MBeanAttributeInfo att : mInfo.getAttributes()) {
final String attName = att.getName();
try {
conn.getAttribute(managementMBeanName, attName);
} catch (Exception e) {
fail("The attribute " + attName + " of ManagementMBean must not throw " + e);
}
}
RMTHelper.log("Test as admin 4 - Check AllAccountsMBean Username attribute");
final String username = "Username";
try {
conn.setAttribute(allAccountsMBeanName, new Attribute(username, TestUsers.TEST.username));
} catch (Exception e) {
fail("Setting Username attribute of the AllAccountsMBean must not throw " + e);
}
String res = "";
try {
res = (String) conn.getAttribute(allAccountsMBeanName, username);
} catch (Exception e) {
fail("The attribute " + username + " of AllAccountsMBean must not throw " + e);
}
assertTrue("The attribute " + username + " of returns incorrect value", res.equals(TestUsers.TEST.username));
jmxConnector.close();
}
private void jmxURLsAreWellFormed(JMXServiceURL jmxRmiServiceURL, JMXServiceURL jmxRoServiceURL, String suffix) {
RMTHelper.log("Test jmxRmiServiceURL is well formed");
assertTrue("The jmxRmiServiceURL protocol must be rmi", jmxRmiServiceURL.getProtocol().equals("rmi"));
assertTrue("The jmxRmiServiceURL URLPath must end with " + suffix,
jmxRmiServiceURL.getURLPath().endsWith(suffix));
RMTHelper.log("Test jmxRoServiceURL is well formed");
assertTrue("The jmxRoServiceURL protocol must be ro", jmxRoServiceURL.getProtocol().equals("ro"));
assertTrue("The jmxRoServiceURL URLPath must end with " + suffix,
jmxRoServiceURL.getURLPath().endsWith(suffix));
RMTHelper.log("Test jmxRmiServiceURL and jmxRoServiceURL are not equal");
assertFalse("The jmxRmiServiceURL and jmxRoServiceURL must not be equal",
jmxRmiServiceURL.equals(jmxRoServiceURL));
}
private void jmxAuthInvalidCreds(JMXServiceURL jmxRmiServiceURL) {
RMTHelper.log("Test invalid JMX auth without creds (expect SecurityException)");
try {
JMXConnectorFactory.connect(jmxRmiServiceURL, new HashMap<String, Object>(0));
} catch (Exception e) {
assertTrue("JMX auth must throw SecurityException if a client tries to connect without creds in the " +
"env", e instanceof SecurityException);
}
}
private void jmxAuthNullLoginPassword(JMXServiceURL jmxRmiServiceURL) {
RMTHelper.log("Test invalid JMX auth with null login/password creds (expect SecurityException)");
// Create the environment
final HashMap<String, Object> env = new HashMap<>(1);
env.put(JMXConnector.CREDENTIALS, new Object[] { null, null });
try {
JMXConnectorFactory.connect(jmxRmiServiceURL, env);
} catch (Exception e) {
assertTrue("JMX auth must throw SecurityException if a client tries to connect with null credentials" +
" the env", e instanceof SecurityException);
}
}
private void jmxRMIAsUser(JMXServiceURL jmxRmiServiceURL, ObjectName allAccountsMBeanName,
ObjectName myAccountMBeanName, ObjectName runtimeDataMBeanName) throws Exception {
// Tests as user over RMI
RMTHelper.log("Test as user 1 - Auth with login/pass over RMI and check connection");
// Create the environment
final HashMap<String, Object> env = new HashMap<>(1);
env.put(JMXConnector.CREDENTIALS, new Object[] { TestUsers.DEMO.username, TestUsers.DEMO.password });
// Connect to the JMX RMI Connector Server
final JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxRmiServiceURL, env);
final MBeanServerConnection conn = jmxConnector.getMBeanServerConnection();
// Check that the MBean Server connection is not null
assertNotNull("Unable to obtain the MBean server connection over RMI", conn);
RMTHelper.log("Test as user 2 - Check MyAccountMBean is registered in the MBean server");
assertTrue("MyAccountMBean must be registered in the MBean server", conn.isRegistered(myAccountMBeanName));
RMTHelper.log("Test as user 3 - Check MyAccountMBean attributes do not throw exceptions");
final MBeanInfo info = conn.getMBeanInfo(myAccountMBeanName);
for (final MBeanAttributeInfo att : info.getAttributes()) {
final String attName = att.getName();
try {
conn.getAttribute(myAccountMBeanName, attName);
} catch (Exception e) {
fail("The attribute " + attName + " of MyAccountMBean must not throw " + e);
}
}
RMTHelper.log("Test as user 3 - Check RuntimeDataMBean is registered in the MBean server");
assertTrue("RuntimeDataMBean must be registered in the MBean server", conn.isRegistered(runtimeDataMBeanName));
RMTHelper.log("Test as user 4 - Check RuntimeDataMBean attributes are correct");
// Start a new node and add it to the rmHelper
testNode = rmHelper.createNode("test");
final Node node = testNode.getNode();
final String nodeURL = node.getNodeInformation().getURL();
rmHelper.getResourceManager().addNode(nodeURL).getBooleanValue(); // force sync, now the node is in configuring state
rmHelper.waitForAnyNodeEvent(RMEventType.NODE_ADDED);
// We eat configuring to free events
rmHelper.waitForAnyNodeEvent(RMEventType.NODE_STATE_CHANGED);
// Get all attributes to test
AttributeList list = conn.getAttributes(runtimeDataMBeanName,
new String[] { "Status", "AvailableNodesCount", "FreeNodesCount" });
// Check RMStatus
Attribute attribute = (Attribute) list.get(0);
assertEquals("Incorrect value of " + attribute.getName() + " attribute", "STARTED", attribute.getValue());
// Check AvailableNodesCount
attribute = (Attribute) list.get(1);
assertEquals("Incorrect value of " + attribute.getName() + " attribute", 1, attribute.getValue());
// Check FreeNodesCount
attribute = (Attribute) list.get(2);
assertEquals("Incorrect value of " + attribute.getName() + " attribute", 1, attribute.getValue());
rmHelper.getResourceManager().removeNode(nodeURL, false);
rmHelper.waitForNodeEvent(RMEventType.NODE_REMOVED, nodeURL);
// Get all attributes to test
list = conn.getAttributes(runtimeDataMBeanName, new String[] { "AvailableNodesCount", "FreeNodesCount" });
// Check AvailableNodesCount
attribute = (Attribute) list.get(0);
assertEquals("Incorrect value of " + attribute.getName() + " attribute", 0, attribute.getValue());
// Check FreeNodesCount
attribute = (Attribute) list.get(1);
assertEquals("Incorrect value of " + attribute.getName() + " attribute", 0, attribute.getValue());
RMTHelper.log("Test as user 5 - Check AllAccountsMBean attributes are not accessible");
final MBeanInfo mBeanInfo = conn.getMBeanInfo(allAccountsMBeanName);
for (final MBeanAttributeInfo att : mBeanInfo.getAttributes()) {
final String attName = att.getName();
try {
conn.getAttribute(allAccountsMBeanName, attName);
} catch (Exception e) {
assertTrue("The attribute " + attName + " must not be accessible with user-level permissions",
e instanceof RuntimeException);
}
}
jmxConnector.close();
}
private void simultaneousRMIAndROConnections(Credentials adminCreds, JMXServiceURL jmxRmiServiceURL,
JMXServiceURL jmxRoServiceURL) throws IOException {
// Test simultaneous RMI and RO connections
RMTHelper.log("Test simultaneous JMX-RMI and JMX-RO connections as admin");
final HashMap<String, Object> env = new HashMap<>(1);
env.put(JMXConnector.CREDENTIALS, new Object[] { TestUsers.TEST.username, adminCreds });
// Connect to the JMX-RMI Connector Server
final JMXConnector jmxRmiConnector = JMXConnectorFactory.connect(jmxRmiServiceURL, env);
final MBeanServerConnection conRmi = jmxRmiConnector.getMBeanServerConnection();
// Connect to the JMX-RO Connector Server
env.put(JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, JMXProviderUtils.RO_PROVIDER_PKGS);
final JMXConnector jmxRoConnector1 = JMXConnectorFactory.connect(jmxRoServiceURL, env);
final MBeanServerConnection conRo = jmxRoConnector1.getMBeanServerConnection();
assertFalse("In case of simultaneous RMI and RO JMX connections they must not be equal", conRmi.equals(conRo));
assertFalse("In case of simultaneous RMI and RO JMX connections the connectors must not provide the same " +
"connection ids", jmxRmiConnector.getConnectionId().equals(jmxRoConnector1.getConnectionId()));
RMTHelper.log("Test JMX-RO connection unicity (two connections over RO must not have the same " + "id)");
final JMXConnector jmxRoConnector2 = JMXConnectorFactory.connect(jmxRoServiceURL, env);
assertFalse("In case of multiple RO JMX connections the connectors must not provide the same connection " +
"ids", jmxRoConnector1.getConnectionId().equals(jmxRoConnector2.getConnectionId()));
// Close all connectors
jmxRoConnector2.close();
jmxRoConnector1.close();
jmxRmiConnector.close();
}
private void jmxClientHelper(RMAuthentication auth, Credentials adminCreds) throws IOException {
// Test Helper class
RMTHelper.log("Test JMXClientHelper as admin over RMI with connect() method");
final JMXClientHelper client = new JMXClientHelper(auth, new Object[] { TestUsers.TEST.username, adminCreds });
final boolean isConnected1 = client.connect(); // default is over
// RMI
assertTrue("Unable to connect, exception is " + client.getLastException(), isConnected1);
assertTrue("Incorrect default behavior of connect() method it must use RMI protocol",
client.getConnector().getConnectionId().startsWith("rmi"));
client.disconnect();
assertFalse("The helper disconnect() must set the helper as disconnected", client.isConnected());
final boolean isConnected2 = client.connect(JMXTransportProtocol.RO);
assertTrue("Unable to connect, exception is " + client.getLastException(), isConnected2);
assertTrue("The helper connect(JMXTransportProtocol.RO) method must use RO protocol",
client.getConnector().getConnectionId().startsWith("ro"));
client.disconnect();
assertFalse("The helper disconnect() must set the helper as disconnected", client.isConnected());
}
}