/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.server.test;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ANY_ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.jboss.as.controller.AbstractControllerService;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.ControlledProcessState;
import org.jboss.as.controller.DelegatingResourceDefinition;
import org.jboss.as.controller.ExpressionResolver;
import org.jboss.as.controller.ManagementModel;
import org.jboss.as.controller.ModelController;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.ResourceDefinition;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.RunningModeControl;
import org.jboss.as.controller.access.management.DelegatingConfigurableAuthorizer;
import org.jboss.as.controller.access.management.ManagementSecurityIdentitySupplier;
import org.jboss.as.controller.audit.AuditLogger;
import org.jboss.as.controller.CapabilityRegistry;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.extension.ExtensionRegistry;
import org.jboss.as.controller.extension.RuntimeHostControllerInfoAccessor;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.persistence.AbstractConfigurationPersister;
import org.jboss.as.controller.persistence.ConfigurationPersistenceException;
import org.jboss.as.controller.persistence.ModelMarshallingContext;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.resource.InterfaceDefinition;
import org.jboss.as.controller.services.path.PathManagerService;
import org.jboss.as.repository.ContentReference;
import org.jboss.as.repository.ContentRepository;
import org.jboss.as.repository.DeploymentFileRepository;
import org.jboss.as.server.ServerEnvironment;
import org.jboss.as.server.Services;
import org.jboss.as.server.controller.resources.ServerRootResourceDefinition;
import org.jboss.as.server.parsing.StandaloneXml;
import org.jboss.as.server.services.net.NetworkInterfaceService;
import org.jboss.as.version.ProductConfig;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.modules.Module;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceContainer;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.vfs.VirtualFile;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Basic server controller unit test.
*
* @author Emanuel Muckenhuber
*/
public class InterfaceManagementUnitTestCase {
private final ServiceContainer container = ServiceContainer.Factory.create();
private ModelController controller;
@Before
public void before() throws Exception {
final ServiceTarget target = container.subTarget();
final ExtensionRegistry extensionRegistry =
new ExtensionRegistry(ProcessType.STANDALONE_SERVER, new RunningModeControl(RunningMode.NORMAL), null, null, null, RuntimeHostControllerInfoAccessor.SERVER);
final StringConfigurationPersister persister = new StringConfigurationPersister(Collections.<ModelNode>emptyList(), new StandaloneXml(null, null, extensionRegistry));
extensionRegistry.setWriterRegistry(persister);
final ControlledProcessState processState = new ControlledProcessState(true);
final ModelControllerService svc = new ModelControllerService(processState, persister, new ServerDelegatingResourceDefinition());
final ServiceBuilder<ModelController> builder = target.addService(Services.JBOSS_SERVER_CONTROLLER, svc);
builder.install();
// Create demand for the ON_DEMAND interface service we'll be adding so we can validate what happens in start()
Service<Void> dependentService = new Service<Void>() {
@Override
public void start(StartContext context) throws StartException {
}
@Override
public void stop(StopContext context) {
}
@Override
public Void getValue() throws IllegalStateException, IllegalArgumentException {
return null;
}
};
target.addService(ServiceName.JBOSS.append("interface", "management", "test", "case", "dependent"), dependentService)
.addDependency(NetworkInterfaceService.JBOSS_NETWORK_INTERFACE.append("test"))
.install();
svc.latch.await(20, TimeUnit.SECONDS);
this.controller = svc.getValue();
}
@After
public void after() {
container.shutdown();
}
@Test
public void testInterfacesAlternatives() throws IOException {
final ModelControllerClient client = controller.createClient(Executors.newCachedThreadPool());
final ModelNode base = new ModelNode();
base.get(ModelDescriptionConstants.OP).set("add");
base.get(ModelDescriptionConstants.OP_ADDR).add("interface", "test");
{
// any-address is not valid with the normal criteria
final ModelNode operation = base.clone();
operation.get(ANY_ADDRESS).set(true);
populateCritieria(operation, Nesting.TOP);
executeForNonServiceFailure(client, operation);
}
// Disabled. See https://github.com/wildfly/wildfly-core/commit/ae0ca95c42b481ef519246b9a6eab2b50c48472e
// {
// // AS7-2685 had a notion of disallowing LOOPBACK and LINK_LOCAL_ADDRESS
// final ModelNode operation = base.clone();
// populateCritieria(operation, Nesting.TOP, InterfaceDefinition.LOOPBACK, InterfaceDefinition.LINK_LOCAL_ADDRESS);
// executeForNonServiceFailure(client, operation);
// }
{
// The full set of normal criteria is ok, although it won't resolve properly
final ModelNode operation = base.clone();
populateCritieria(operation, Nesting.TOP);
executeForServiceFailure(client, operation);
}
}
@Test
public void testUpdateInterface() throws IOException {
final ModelControllerClient client = controller.createClient(Executors.newCachedThreadPool());
final ModelNode address = new ModelNode();
address.add("interface", "test");
{
final ModelNode operation = new ModelNode();
operation.get(ModelDescriptionConstants.OP).set("add");
operation.get(ModelDescriptionConstants.OP_ADDR).set(address);
operation.get(ANY_ADDRESS).set(true);
executeForResult(client, operation);
final ModelNode resource = readResource(client, operation.get(ModelDescriptionConstants.OP_ADDR));
Assert.assertTrue(resource.get(ANY_ADDRESS).asBoolean());
}
{
final ModelNode composite = new ModelNode();
composite.get(ModelDescriptionConstants.OP).set("composite");
composite.get(ModelDescriptionConstants.OP_ADDR).setEmptyList();
final ModelNode one = composite.get(ModelDescriptionConstants.STEPS).add();
one.get(ModelDescriptionConstants.OP).set("write-attribute");
one.get(ModelDescriptionConstants.OP_ADDR).set(address);
one.get(ModelDescriptionConstants.NAME).set(ANY_ADDRESS);
one.get(ModelDescriptionConstants.VALUE);
final ModelNode two = composite.get(ModelDescriptionConstants.STEPS).add();
two.get(ModelDescriptionConstants.OP).set("write-attribute");
two.get(ModelDescriptionConstants.OP_ADDR).set(address);
two.get(ModelDescriptionConstants.NAME).set("inet-address");
two.get(ModelDescriptionConstants.VALUE).set("127.0.0.1");
executeForResult(client, composite);
final ModelNode resource = readResource(client, address);
Assert.assertFalse(resource.hasDefined(ANY_ADDRESS));
Assert.assertEquals("127.0.0.1", resource.get("inet-address").asString());
}
}
@Test
public void testComplexInterface() throws IOException {
final ModelNode operation = new ModelNode();
operation.get(ModelDescriptionConstants.OP).set("add");
operation.get(ModelDescriptionConstants.OP_ADDR).add("interface", "test");
// This won't be resolvable with the runtime layer enabled
populateCritieria(operation, Nesting.TOP,
InterfaceDefinition.LOOPBACK);
populateCritieria(operation.get("not"), Nesting.NOT,
InterfaceDefinition.PUBLIC_ADDRESS,
InterfaceDefinition.LINK_LOCAL_ADDRESS,
InterfaceDefinition.SITE_LOCAL_ADDRESS,
InterfaceDefinition.VIRTUAL,
InterfaceDefinition.UP,
InterfaceDefinition.MULTICAST,
InterfaceDefinition.LOOPBACK_ADDRESS,
InterfaceDefinition.POINT_TO_POINT);
populateCritieria(operation.get("any"), Nesting.ANY);
final ModelControllerClient client = controller.createClient(Executors.newCachedThreadPool());
executeForServiceFailure(client, operation);
}
protected void populateCritieria(final ModelNode model, final Nesting nesting, final AttributeDefinition...excluded) {
Set<AttributeDefinition> excludedCriteria = new HashSet<AttributeDefinition>(Arrays.asList(excluded));
for(final AttributeDefinition def : InterfaceDefinition.NESTED_ATTRIBUTES) {
if (excludedCriteria.contains(def)) {
continue;
}
final ModelNode node = model.get(def.getName());
if(def.getType() == ModelType.BOOLEAN) {
node.set(true);
} else if (def == InterfaceDefinition.INET_ADDRESS || def == InterfaceDefinition.LOOPBACK_ADDRESS) {
if (nesting == Nesting.ANY && def == InterfaceDefinition.INET_ADDRESS) {
node.add("127.0.0.1");
} else if (nesting == Nesting.NOT && def == InterfaceDefinition.INET_ADDRESS) {
node.add("10.0.0.1");
} else {
node.set("127.0.0.1");
}
} else if (def == InterfaceDefinition.NIC || def == InterfaceDefinition.NIC_MATCH) {
if (nesting == Nesting.ANY) {
node.add("lo");
} else if (nesting == Nesting.NOT) {
node.add("en3");
} else {
node.set("lo");
}
} else if (def == InterfaceDefinition.SUBNET_MATCH) {
if (nesting == Nesting.ANY) {
node.add("127.0.0.1/24");
} else if (nesting == Nesting.NOT) {
node.add("10.0.0.1/24");
} else {
node.set("127.0.0.0/24");
}
}
}
}
private static class ModelControllerService extends AbstractControllerService {
final CountDownLatch latch = new CountDownLatch(1);
final StringConfigurationPersister persister;
final ControlledProcessState processState;
final ServerDelegatingResourceDefinition rootResourceDefinition;
final ServerEnvironment environment;
final ExtensionRegistry extensionRegistry;
final CapabilityRegistry capabilityRegistry;
volatile ManagementResourceRegistration rootRegistration;
volatile Exception error;
ModelControllerService(final ControlledProcessState processState, final StringConfigurationPersister persister, final ServerDelegatingResourceDefinition rootResourceDefinition) {
super(ProcessType.EMBEDDED_SERVER, new RunningModeControl(RunningMode.ADMIN_ONLY), persister, processState, rootResourceDefinition, null, ExpressionResolver.TEST_RESOLVER,
AuditLogger.NO_OP_LOGGER, new DelegatingConfigurableAuthorizer(), new ManagementSecurityIdentitySupplier(), new CapabilityRegistry(true));
this.persister = persister;
this.processState = processState;
this.rootResourceDefinition = rootResourceDefinition;
Properties properties = new Properties();
properties.put("jboss.home.dir", System.getProperty("basedir", ".") + File.separatorChar + "target");
final String hostControllerName = "hostControllerName"; // Host Controller name may not be null when in a managed domain
environment = new ServerEnvironment(hostControllerName, properties, new HashMap<String, String>(), null, null,
ServerEnvironment.LaunchType.DOMAIN, null, ProductConfig.fromFilesystemSlot(Module.getBootModuleLoader(), ".", properties), false);
extensionRegistry =
new ExtensionRegistry(ProcessType.STANDALONE_SERVER, new RunningModeControl(RunningMode.NORMAL), null, null, null, RuntimeHostControllerInfoAccessor.SERVER);
capabilityRegistry = new CapabilityRegistry(processType.isServer());
}
@Override
protected void initModel(ManagementModel managementModel, Resource modelControllerResource) {
this.rootRegistration = managementModel.getRootResourceRegistration();
}
@Override
protected boolean boot(List<ModelNode> bootOperations, boolean rollbackOnRuntimeFailure) throws ConfigurationPersistenceException {
try {
return super.boot(persister.bootOperations, rollbackOnRuntimeFailure);
} catch (Exception e) {
error = e;
} catch (Throwable t) {
error = new Exception(t);
} finally {
latch.countDown();
}
return false;
}
@Override
public void start(StartContext context) throws StartException {
rootResourceDefinition.setDelegate(new ServerRootResourceDefinition(MockRepository.INSTANCE,
persister, environment, processState, null, null, extensionRegistry, false, MOCK_PATH_MANAGER, null,
authorizer, securityIdentitySupplier, AuditLogger.NO_OP_LOGGER, getMutableRootResourceRegistrationProvider(), getBootErrorCollector(), capabilityRegistry));
super.start(context);
}
}
static final class ServerDelegatingResourceDefinition extends DelegatingResourceDefinition {
@Override
public void setDelegate(ResourceDefinition delegate) {
super.setDelegate(delegate);
}
}
static class StringConfigurationPersister extends AbstractConfigurationPersister {
private final List<ModelNode> bootOperations;
volatile String marshalled;
public StringConfigurationPersister(List<ModelNode> bootOperations, XMLElementWriter<ModelMarshallingContext> rootDeparser) {
super(rootDeparser);
this.bootOperations = bootOperations;
}
@Override
public PersistenceResource store(ModelNode model, Set<PathAddress> affectedAddresses)
throws ConfigurationPersistenceException {
return new StringPersistenceResource(model, this);
}
@Override
public List<ModelNode> load() throws ConfigurationPersistenceException {
return bootOperations;
}
private class StringPersistenceResource implements PersistenceResource {
private byte[] bytes;
private final AbstractConfigurationPersister persister;
StringPersistenceResource(final ModelNode model, final AbstractConfigurationPersister persister) throws ConfigurationPersistenceException {
this.persister = persister;
ByteArrayOutputStream output = new ByteArrayOutputStream(1024 * 8);
try {
try {
persister.marshallAsXml(model, output);
} finally {
try {
output.close();
} catch (Exception ignore) {
}
bytes = output.toByteArray();
}
} catch (Exception e) {
throw new ConfigurationPersistenceException("Failed to marshal configuration", e);
}
}
@Override
public void commit() {
StringConfigurationPersister.this.marshalled = new String(bytes, StandardCharsets.UTF_8);
}
@Override
public void rollback() {
marshalled = null;
}
}
}
static ModelNode readResource(final ModelControllerClient client, final ModelNode address) {
final ModelNode operation = new ModelNode();
operation.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.READ_RESOURCE_OPERATION);
operation.get(ModelDescriptionConstants.OP_ADDR).set(address);
return executeForResult(client, operation);
}
/**
* Assert that the operation failed, but not with the failure message that indicates a service start problem.
* Use this to check that problems that should be detected in the OSH and not in the service are properly
* detected.
*
* @param client the client to use to execute the operation
* @param operation the operation to execute
*/
private static void executeForNonServiceFailure(final ModelControllerClient client, final ModelNode operation) {
try {
final ModelNode result = client.execute(operation);
if (! result.hasDefined("outcome") && ! ModelDescriptionConstants.FAILED.equals(result.get("outcome").asString())) {
Assert.fail("Operation outcome is " + result.get("outcome").asString());
}
System.out.println("Failure for " + operation + "\n is:\n" + result);
Assert.assertFalse(result.toString(), result.get(ModelDescriptionConstants.FAILURE_DESCRIPTION).toString().contains(ControllerLogger.MGMT_OP_LOGGER.failedServices()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Assert that the operation failed, but only with the failure message that indicates a service start problem.
* Use this for instead of executeFoResult in tests that use criteria that may not be resolvable on a real machine,
* but which are conceptually valid. The inability to resolve a matching interface will lead to the service start problem.
*
* @param client the client to use to execute the operation
* @param operation the operation to execute
*/
private static void executeForServiceFailure(final ModelControllerClient client, final ModelNode operation) {
try {
final ModelNode result = client.execute(operation);
if (! result.hasDefined(OUTCOME) && ! ModelDescriptionConstants.FAILED.equals(result.get(OUTCOME).asString())) {
Assert.fail("Operation outcome is " + result.get(OUTCOME).asString());
}
System.out.println("Failure for " + operation + "\n is:\n" + result);
Assert.assertTrue(result.toString(), result.get(ModelDescriptionConstants.FAILURE_DESCRIPTION).toString().contains(ControllerLogger.MGMT_OP_LOGGER.failedServices()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static ModelNode executeForResult(final ModelControllerClient client, final ModelNode operation) {
try {
final ModelNode result = client.execute(operation);
if (result.hasDefined("outcome") && "success".equals(result.get("outcome").asString())) {
return result.get("result");
} else {
Assert.fail("Operation outcome is " + result.get("outcome").asString() + " " + result.get("failure-description"));
throw new RuntimeException(); // not reached
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static final class MockRepository implements ContentRepository, DeploymentFileRepository {
static MockRepository INSTANCE = new MockRepository();
@Override
public File[] getDeploymentFiles(ContentReference reference) {
return null;
}
@Override
public File getDeploymentRoot(ContentReference reference) {
return null;
}
@Override
public void deleteDeployment(ContentReference reference) {
}
@Override
public byte[] addContent(InputStream stream) throws IOException {
return null;
}
@Override
public boolean syncContent(ContentReference reference) {
return hasContent(reference.getHash());
}
@Override
public VirtualFile getContent(byte[] hash) {
return null;
}
@Override
public boolean hasContent(byte[] hash) {
return false;
}
@Override
public void removeContent(ContentReference reference) {
}
@Override
public void addContentReference(ContentReference reference) {
}
@Override
public Map<String, Set<String>> cleanObsoleteContent() {
return null;
}
}
private static PathManagerService MOCK_PATH_MANAGER = new PathManagerService() {
};
private enum Nesting {
TOP,
ANY,
NOT
}
}