Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Running Tests in Apache Artemis

This document explains how to run tests in the Apache Artemis repository, which uses Maven skip properties to control test execution.

## Skip Test Properties

The repository uses different skip properties for different test modules:

- **`skipUnitTests`** - Controls unit tests (default: `true`)
- **`skipIntegrationTests`** - Controls integration tests (default: `true`)
- **`e2e-tests.skipTests`** - Controls end-to-end tests (default: `true`)

By default, tests are **skipped** to speed up the build. You must explicitly set these properties to `false` to run tests.

## Running Unit Tests

Unit tests are located in `tests/unit-tests/`.

### Run a specific unit test:
```bash
mvn test -pl tests/unit-tests -Dtest=ResourceQuotaManagerTest -DskipUnitTests=false
```

### Run all unit tests in the module:
```bash
mvn test -pl tests/unit-tests -DskipUnitTests=false
```

### Run all unit tests from root:
```bash
mvn test -DskipUnitTests=false
```

## Running Integration Tests

Integration tests are located in `tests/integration-tests/`.

### Run a specific integration test:
```bash
mvn test -pl tests/integration-tests -Dtest=ResourceQuotaReloadTest -DskipIntegrationTests=false
```

### Run all integration tests in the module:
```bash
mvn test -pl tests/integration-tests -DskipIntegrationTests=false
```

## Running Tests from artemis-server

Some tests are located directly in the `artemis-server` module. These use the standard `-DskipTests` property:

```bash
mvn test -pl artemis-server -Dtest=SomeTest -DskipTests=false
```

## Common Patterns

### Run tests with wildcard pattern:
```bash
mvn test -pl tests/unit-tests -Dtest=ResourceQuota* -DskipUnitTests=false
```

### Run multiple specific tests:
```bash
mvn test -pl tests/unit-tests -Dtest=TestA,TestB,TestC -DskipUnitTests=false
```

### Build a module and run its tests:
```bash
mvn clean install -pl tests/unit-tests -DskipUnitTests=false
```

### Run tests with debug output:
```bash
mvn test -pl tests/unit-tests -Dtest=ResourceQuotaManagerTest -DskipUnitTests=false -X
```

## Maven Build Profiles

The repository defines profiles in the root `pom.xml` that control test execution:

- **Default profile**: All tests skipped (`skipUnitTests=true`, `skipIntegrationTests=true`)
- **`dev` profile**: Unit tests enabled (`skipUnitTests=false`)
- **`release` profile**: Unit and integration tests enabled

### Activate a profile:
```bash
mvn test -Pdev
```

## Quick Reference

| Test Location | Module Path | Skip Property |
|---------------|-------------|---------------|
| artemis-server tests | `artemis-server` | `-DskipTests=false` |
| Unit tests | `tests/unit-tests` | `-DskipUnitTests=false` |
| Integration tests | `tests/integration-tests` | `-DskipIntegrationTests=false` |
| E2E tests | `tests/e2e-tests` | `-De2e-tests.skipTests=false` |


**IMPORTANT:** After modifying any source code in `artemis-server/`, you MUST rebuild before running tests:
```bash
mvn clean install -pl artemis-server -DskipTests
```
Otherwise tests will run against the old compiled code and won't reflect your changes.
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ public ActiveMQException createException(String msg) {
public ActiveMQException createException(String msg) {
return new ActiveMQTimeoutException(msg);
}
},
RESOURCE_QUOTA_EXCEEDED(224) {
@Override
public ActiveMQException createException(String msg) {
return new ActiveMQResourceQuotaExceededException(msg);
}
};
private static final Map<Integer, ActiveMQExceptionType> TYPE_MAP;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.api.core;

/**
* A resource quota has been exceeded.
* Resource quotas enforce hierarchical limits on message bytes, address count, and queue count.
*/
public final class ActiveMQResourceQuotaExceededException extends ActiveMQException {

private static final long serialVersionUID = 1L;

public ActiveMQResourceQuotaExceededException(String message) {
super(ActiveMQExceptionType.RESOURCE_QUOTA_EXCEEDED, message);
}

public ActiveMQResourceQuotaExceededException() {
super(ActiveMQExceptionType.RESOURCE_QUOTA_EXCEEDED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,15 @@ private String getActiveMQServerName() {
return String.format("%s:broker=%s", domain, (jmxUseBrokerName && brokerName != null) ? ObjectName.quote(brokerName) : "artemis");
}

/**
* {@return the ObjectName used by ResourceQuotaControl}
*
* @see ResourceQuotaControl
*/
public ObjectName getResourceQuotaObjectName(final String quotaName) throws Exception {
return createObjectName("quota", quotaName);
}

@Deprecated()
public ObjectName getManagementContextObjectName() throws Exception {
return getSecurityObjectName();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.api.core.management;

/**
* A ResourceQuotaControl is used to manage and monitor a resource quota.
* <p>
* Resource quotas track and enforce limits on addresses, queues, and message bytes
* across groups of addresses. They support hierarchical organization where child
* quotas count toward parent limits.
*/
public interface ResourceQuotaControl {

/**
* {@return the name of this resource quota}
*/
@Attribute(desc = "the name of this resource quota")
String getName();

/**
* {@return the name of the parent quota this quota is part of, or null if this is a root quota}
*/
@Attribute(desc = "the name of the parent quota this quota is part of")
String getPartOf();

/**
* {@return the maximum number of addresses allowed by this quota, or -1 if unlimited}
*/
@Attribute(desc = "the maximum number of addresses allowed by this quota")
int getMaxAddresses();

/**
* {@return the current number of addresses tracked by this quota}
*/
@Attribute(desc = "the current number of addresses tracked by this quota")
int getCurrentAddressCount();

/**
* {@return the percentage of the address limit currently in use (0-100), or -1 if no limit configured}
*/
@Attribute(desc = "the percentage of the address limit currently in use")
double getAddressUtilizationPercent();

/**
* {@return the maximum number of queues allowed by this quota, or -1 if unlimited}
*/
@Attribute(desc = "the maximum number of queues allowed by this quota")
int getMaxQueues();

/**
* {@return the current number of queues tracked by this quota}
*/
@Attribute(desc = "the current number of queues tracked by this quota")
int getCurrentQueueCount();

/**
* {@return the percentage of the queue limit currently in use (0-100), or -1 if no limit configured}
*/
@Attribute(desc = "the percentage of the queue limit currently in use")
double getQueueUtilizationPercent();

/**
* {@return the maximum number of bytes allowed for messages in this quota, or -1 if unlimited}
*/
@Attribute(desc = "the maximum number of bytes allowed for messages in this quota")
long getMaxMessageBytes();

/**
* {@return the current number of bytes used by messages in this quota}
*/
@Attribute(desc = "the current number of bytes used by messages in this quota")
long getCurrentMessageBytes();

/**
* {@return the percentage of the message bytes limit currently in use (0-100), or -1 if no limit configured}
*/
@Attribute(desc = "the percentage of the message bytes limit currently in use")
double getMessageBytesUtilizationPercent();

/**
* {@return true if this quota has at least one limit configured (addresses, queues, or bytes)}
*/
@Attribute(desc = "whether this quota has any limits configured")
boolean hasLimits();

/**
* {@return true if this quota has reached any of its limits}
*/
@Attribute(desc = "whether this quota has reached any of its limits")
boolean isLimitReached();
}
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,9 @@ protected void failIfCoreTunnelNotEnabled() {
}

protected static boolean isAddressFull(final Exception e) {
return e instanceof ActiveMQException amqe && ActiveMQExceptionType.ADDRESS_FULL.equals(amqe.getType());
return e instanceof ActiveMQException amqe &&
(ActiveMQExceptionType.ADDRESS_FULL.equals(amqe.getType()) ||
ActiveMQExceptionType.RESOURCE_QUOTA_EXCEEDED.equals(amqe.getType()));
}

protected static boolean outcomeSupported(final Source source, final Symbol outcome) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.netty.handler.codec.mqtt.MqttTopicSubscription;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQIllegalStateException;
import org.apache.activemq.artemis.api.core.ActiveMQResourceQuotaExceededException;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.ICoreMessage;
import org.apache.activemq.artemis.api.core.Message;
Expand Down Expand Up @@ -266,6 +267,17 @@ void sendToQueue(MqttPublishMessage message, boolean internal) throws Exception
*/
logger.debug("MQTT 3.1 client not authorized to publish message.");
}
} catch (ActiveMQResourceQuotaExceededException e) {
tx.rollback();
if (internal) {
throw e;
}
if (session.getVersion() == MQTTVersion.MQTT_5) {
sendMessageAck(internal, qos, packetId, MQTTReasonCodes.QUOTA_EXCEEDED);
return;
} else {
throw new DisconnectException();
}
} catch (Throwable t) {
MQTTLogger.LOGGER.failedToPublishMqttMessage(t.getMessage(), t);
tx.rollback();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.netty.handler.codec.mqtt.MqttSubscriptionOption;
import io.netty.handler.codec.mqtt.MqttTopicSubscription;
import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException;
import org.apache.activemq.artemis.api.core.ActiveMQResourceQuotaExceededException;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.FilterConstants;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
Expand Down Expand Up @@ -334,6 +335,14 @@ int[] addSubscriptions(List<MqttTopicSubscription> subscriptions, Integer subscr
*/
qos[i] = subscriptions.get(i).qualityOfService().value();
}
} catch (ActiveMQResourceQuotaExceededException e) {
if (session.getVersion() == MQTTVersion.MQTT_5) {
qos[i] = MQTTReasonCodes.QUOTA_EXCEEDED;
} else if (session.getVersion() == MQTTVersion.MQTT_3_1_1) {
qos[i] = MQTTReasonCodes.UNSPECIFIED_ERROR;
} else {
qos[i] = subscriptions.get(i).qualityOfService().value();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ public interface Configuration {
*/
Configuration addResourceLimitSettings(ResourceLimitSettings resourceLimitSettings);

/**
* {@return resource quota configurations (limits only, no runtime counters)}
*/
Map<String, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig> getResourceQuotas();

/**
* Set the collection of resource quota configurations indexed by name.
*
* @param quotaConfigs quota names mapped to ResourceQuotaConfig
*/
Configuration setResourceQuotas(Map<String, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig> quotaConfigs);

/**
* Add a resource quota configuration to the underlying collection.
*
* @param name the unique name of the quota
* @param config the ResourceQuotaConfig defining limits
*/
Configuration addResourceQuota(String name, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig config);

/**
* {@return the period (in milliseconds) to scan configuration files used by deployment; default is {@link
* ActiveMQDefaultConfiguration#DEFAULT_FILE_DEPLOYER_SCAN_PERIOD}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i

private Map<String, ResourceLimitSettings> resourceLimitSettings = new HashMap<>();

private Map<String, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig> resourceQuotas = new HashMap<>();

private Map<String, Set<Role>> securitySettings = new HashMap<>();

private List<SecuritySettingPlugin> securitySettingPlugins = new ArrayList<>();
Expand Down Expand Up @@ -2531,6 +2533,23 @@ public ConfigurationImpl addResourceLimitSetting(ResourceLimitSettings resourceL
return this.addResourceLimitSettings(resourceLimitSettings);
}

@Override
public Map<String, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig> getResourceQuotas() {
return resourceQuotas;
}

@Override
public ConfigurationImpl setResourceQuotas(final Map<String, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig> quotaConfigs) {
this.resourceQuotas = quotaConfigs;
return this;
}

@Override
public ConfigurationImpl addResourceQuota(String name, org.apache.activemq.artemis.core.settings.impl.ResourceQuotaConfig config) {
this.resourceQuotas.put(name, config);
return this;
}

@Override
public Map<String, Set<Role>> getSecurityRoles() {
for (SecuritySettingPlugin securitySettingPlugin : securitySettingPlugins) {
Expand Down
Loading
Loading