This is an automated email from the ASF dual-hosted git repository.
xiangfu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 19ae6b9e64a Add URL filter include patterns for audit logging with
priority semantics (#16807)
19ae6b9e64a is described below
commit 19ae6b9e64a59d5e15d93454387e45899b4a982e
Author: Suvodeep Pyne <[email protected]>
AuthorDate: Sat Sep 13 08:40:09 2025 -0700
Add URL filter include patterns for audit logging with priority semantics
(#16807)
* [audit] Add support for URL filter include patterns and refactor auditing
logic
* [audit] Add unit tests for URL filter include/exclude patterns in
auditing logic
* [audit] Update audit URL path filter logic to use shouldAudit method
* [audit] Improve logging for URL path pattern checks in AuditUrlPathFilter
---
.../org/apache/pinot/common/audit/AuditConfig.java | 12 ++
.../pinot/common/audit/AuditRequestProcessor.java | 4 +-
.../pinot/common}/audit/AuditServiceBinder.java | 6 +-
.../pinot/common/audit/AuditUrlPathFilter.java | 48 +++++-
.../common/audit/AuditRequestProcessorTest.java | 2 +-
.../pinot/common/audit/AuditUrlPathFilterTest.java | 182 ++++++++++++++++-----
.../pinot/controller/BaseControllerStarter.java | 2 +-
7 files changed, 200 insertions(+), 56 deletions(-)
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
index a7231c6a2c2..c8d92d965bf 100644
--- a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
+++ b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditConfig.java
@@ -48,6 +48,9 @@ public final class AuditConfig {
@JsonProperty("url.filter.exclude.patterns")
private String _urlFilterExcludePatterns = "";
+ @JsonProperty("url.filter.include.patterns")
+ private String _urlFilterIncludePatterns = "";
+
@JsonProperty("userid.header")
private String _useridHeader = "";
@@ -94,6 +97,14 @@ public final class AuditConfig {
_urlFilterExcludePatterns = urlFilterExcludePatterns;
}
+ public String getUrlFilterIncludePatterns() {
+ return _urlFilterIncludePatterns;
+ }
+
+ public void setUrlFilterIncludePatterns(String urlFilterIncludePatterns) {
+ _urlFilterIncludePatterns = urlFilterIncludePatterns;
+ }
+
public String getUseridHeader() {
return _useridHeader;
}
@@ -117,6 +128,7 @@ public final class AuditConfig {
.add("_captureRequestHeaders='" + _captureRequestHeaders + "'")
.add("_maxPayloadSize=" + _maxPayloadSize)
.add("_urlFilterExcludePatterns='" + _urlFilterExcludePatterns + "'")
+ .add("_urlFilterIncludePatterns='" + _urlFilterIncludePatterns + "'")
.add("_useridHeader='" + _useridHeader + "'")
.add("_useridJwtClaimName='" + _useridJwtClaimName + "'")
.toString();
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
index 7665a3b2938..1ca17f816e7 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditRequestProcessor.java
@@ -117,8 +117,8 @@ public class AuditRequestProcessor {
UriInfo uriInfo = requestContext.getUriInfo();
String endpoint = uriInfo.getPath();
- // Check endpoint exclusions
- if (_auditUrlPathFilter.isExcluded(endpoint,
_configManager.getCurrentConfig().getUrlFilterExcludePatterns())) {
+ // Check if endpoint should be audited based on include/exclude patterns
+ if (!_auditUrlPathFilter.shouldAudit(endpoint)) {
return null;
}
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditServiceBinder.java
similarity index 87%
rename from
pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
rename to
pinot-common/src/main/java/org/apache/pinot/common/audit/AuditServiceBinder.java
index 8a788775e67..ad5778586a8 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/audit/AuditServiceBinder.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditServiceBinder.java
@@ -16,12 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.pinot.controller.api.audit;
+package org.apache.pinot.common.audit;
-import org.apache.pinot.common.audit.AuditConfigManager;
-import org.apache.pinot.common.audit.AuditIdentityResolver;
-import org.apache.pinot.common.audit.AuditRequestProcessor;
-import org.apache.pinot.common.audit.AuditUrlPathFilter;
import org.apache.pinot.common.config.DefaultClusterConfigChangeHandler;
import org.apache.pinot.spi.services.ServiceRole;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
diff --git
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
index 58db65cee1e..2ae95565133 100644
---
a/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
+++
b/pinot-common/src/main/java/org/apache/pinot/common/audit/AuditUrlPathFilter.java
@@ -23,6 +23,7 @@ import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.Arrays;
+import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -31,7 +32,7 @@ import org.slf4j.LoggerFactory;
/**
* URL path filter utility that uses Java's PathMatcher with glob and regex
patterns
- * to determine if a URL path should be excluded from processing.
+ * to determine if a URL path should be audited based on include/exclude
patterns.
*
* This class provides powerful pattern matching capabilities including:
* - Wildcards: * (within path segment), ** (across path segments), ? (single
character)
@@ -58,6 +59,37 @@ public class AuditUrlPathFilter {
private static final Logger LOG =
LoggerFactory.getLogger(AuditUrlPathFilter.class);
private static final String PREFIX_GLOB = "glob:";
private static final String PREFIX_REGEX = "regex:";
+ private final AuditConfigManager _configManager;
+
+ @Inject
+ public AuditUrlPathFilter(AuditConfigManager configManager) {
+ _configManager = configManager;
+ }
+
+ /**
+ * Determines whether a given endpoint should be audited based on
include/exclude patterns.
+ * Exclusion patterns have priority over inclusion patterns.
+ *
+ * @param endpoint the URL endpoint to check
+ * @return true if the endpoint should be audited, false otherwise
+ */
+ public boolean shouldAudit(String endpoint) {
+ AuditConfig config = _configManager.getCurrentConfig();
+
+ // Priority 1: Exclusion always wins - if excluded, don't audit
+ if (matches(endpoint, config.getUrlFilterExcludePatterns())) {
+ return false;
+ }
+
+ // Priority 2: If include patterns defined, URL must match to be audited
+ String includePatterns = config.getUrlFilterIncludePatterns();
+ if (StringUtils.isNotBlank(includePatterns)) {
+ return matches(endpoint, includePatterns);
+ }
+
+ // Default: No include patterns = audit everything not excluded
+ return true;
+ }
private static boolean matches(Path path, String pattern) {
try {
@@ -77,20 +109,20 @@ public class AuditUrlPathFilter {
}
/**
- * Checks if the given URL path should be excluded based on the provided
patterns.
+ * Checks if the given URL path matches any of the provided patterns.
*
* @param urlPath The URL path to check (e.g., "api/v1/users")
- * @param excludePatterns Comma-separated list of glob patterns
- * @return true if the path matches any exclude pattern, false otherwise
+ * @param patternsCommaSeparated Comma-separated list of glob patterns
+ * @return true if the path matches any pattern, false otherwise
*/
- public boolean isExcluded(String urlPath, String excludePatterns) {
- if (StringUtils.isBlank(urlPath) || StringUtils.isBlank(excludePatterns)) {
+ public boolean matches(String urlPath, String patternsCommaSeparated) {
+ if (StringUtils.isBlank(urlPath) ||
StringUtils.isBlank(patternsCommaSeparated)) {
return false;
}
try {
Path path = Paths.get(urlPath);
- String[] patterns = excludePatterns.split(",");
+ String[] patterns = patternsCommaSeparated.split(",");
if (Arrays.stream(patterns)
.map(String::trim)
@@ -99,7 +131,7 @@ public class AuditUrlPathFilter {
return true;
}
} catch (Exception e) {
- LOG.warn("Error checking URL path '{}' against exclude patterns",
urlPath, e);
+ LOG.warn("Error checking URL path '{}' against pattern: {}", urlPath,
patternsCommaSeparated, e);
}
return false;
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
index 0e0ce75f797..07da31cf473 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditRequestProcessorTest.java
@@ -76,7 +76,7 @@ public class AuditRequestProcessorTest {
when(_configManager.isEnabled()).thenReturn(true);
when(_configManager.getCurrentConfig()).thenReturn(_defaultConfig);
- when(_auditUrlPathFilter.isExcluded(any(), any())).thenReturn(false);
+ when(_auditUrlPathFilter.shouldAudit(any())).thenReturn(true);
}
private MultivaluedMap<String, String> createHeaders(String... headerPairs) {
diff --git
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
index 22b6053a03e..2ab8fd6f184 100644
---
a/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
+++
b/pinot-common/src/test/java/org/apache/pinot/common/audit/AuditUrlPathFilterTest.java
@@ -22,6 +22,8 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
/**
@@ -34,37 +36,37 @@ public class AuditUrlPathFilterTest {
@BeforeMethod
public void setUp() {
- _filter = new AuditUrlPathFilter();
+ _filter = new AuditUrlPathFilter(mock(AuditConfigManager.class));
}
// ===== Input Validation Tests =====
@Test
public void testNullUrlPath() {
- assertThat(_filter.isExcluded(null, "health")).isFalse();
+ assertThat(_filter.matches(null, "health")).isFalse();
}
@Test
public void testEmptyUrlPath() {
- assertThat(_filter.isExcluded("", "health")).isFalse();
- assertThat(_filter.isExcluded(" ", "health")).isFalse();
+ assertThat(_filter.matches("", "health")).isFalse();
+ assertThat(_filter.matches(" ", "health")).isFalse();
}
@Test
public void testNullExcludePatterns() {
- assertThat(_filter.isExcluded("api/users", null)).isFalse();
+ assertThat(_filter.matches("api/users", null)).isFalse();
}
@Test
public void testEmptyExcludePatterns() {
- assertThat(_filter.isExcluded("api/users", "")).isFalse();
- assertThat(_filter.isExcluded("api/users", " ")).isFalse();
+ assertThat(_filter.matches("api/users", "")).isFalse();
+ assertThat(_filter.matches("api/users", " ")).isFalse();
}
@Test
public void testBothParametersBlank() {
- assertThat(_filter.isExcluded(null, null)).isFalse();
- assertThat(_filter.isExcluded("", "")).isFalse();
+ assertThat(_filter.matches(null, null)).isFalse();
+ assertThat(_filter.matches("", "")).isFalse();
}
// ===== Multiple Pattern Processing Tests =====
@@ -73,59 +75,59 @@ public class AuditUrlPathFilterTest {
public void testMultiplePatternsCommaSeparated() {
String patterns = "health,api/users,admin";
- assertThat(_filter.isExcluded("health", patterns)).isTrue();
- assertThat(_filter.isExcluded("api/users", patterns)).isTrue();
- assertThat(_filter.isExcluded("admin", patterns)).isTrue();
- assertThat(_filter.isExcluded("metrics", patterns)).isFalse();
+ assertThat(_filter.matches("health", patterns)).isTrue();
+ assertThat(_filter.matches("api/users", patterns)).isTrue();
+ assertThat(_filter.matches("admin", patterns)).isTrue();
+ assertThat(_filter.matches("metrics", patterns)).isFalse();
}
@Test
public void testMultiplePatternsWithTrimmingAndEmptyElements() {
String patterns = " health , , api/users , , ";
- assertThat(_filter.isExcluded("health", patterns)).isTrue();
- assertThat(_filter.isExcluded("api/users", patterns)).isTrue();
- assertThat(_filter.isExcluded("metrics", patterns)).isFalse();
+ assertThat(_filter.matches("health", patterns)).isTrue();
+ assertThat(_filter.matches("api/users", patterns)).isTrue();
+ assertThat(_filter.matches("metrics", patterns)).isFalse();
}
@Test
public void testAnyPatternMatchesReturnsTrue() {
String patterns = "nonexistent1,health,nonexistent2";
- assertThat(_filter.isExcluded("health", patterns)).isTrue();
- assertThat(_filter.isExcluded("nonexistent1", patterns)).isTrue();
- assertThat(_filter.isExcluded("other", patterns)).isFalse();
+ assertThat(_filter.matches("health", patterns)).isTrue();
+ assertThat(_filter.matches("nonexistent1", patterns)).isTrue();
+ assertThat(_filter.matches("other", patterns)).isFalse();
}
// ===== Prefix Handling Tests =====
@Test
public void testAutomaticGlobPrefixAddition() {
- assertThat(_filter.isExcluded("health", "health")).isTrue();
- assertThat(_filter.isExcluded("api/users", "api/*")).isTrue();
+ assertThat(_filter.matches("health", "health")).isTrue();
+ assertThat(_filter.matches("api/users", "api/*")).isTrue();
}
@Test
public void testExplicitGlobPrefix() {
- assertThat(_filter.isExcluded("health", "glob:health")).isTrue();
- assertThat(_filter.isExcluded("api/users", "glob:api/*")).isTrue();
+ assertThat(_filter.matches("health", "glob:health")).isTrue();
+ assertThat(_filter.matches("api/users", "glob:api/*")).isTrue();
}
@Test
public void testExplicitRegexPrefix() {
String pattern = "regex:api/v[0-9]+/.*";
- assertThat(_filter.isExcluded("api/v1/users", pattern)).isTrue();
- assertThat(_filter.isExcluded("api/v123/anything", pattern)).isTrue();
- assertThat(_filter.isExcluded("api/va/users", pattern)).isFalse();
+ assertThat(_filter.matches("api/v1/users", pattern)).isTrue();
+ assertThat(_filter.matches("api/v123/anything", pattern)).isTrue();
+ assertThat(_filter.matches("api/va/users", pattern)).isFalse();
}
@Test
public void testMixedPrefixes() {
String patterns = "glob:health,regex:api/v[0-9]+/.*,admin";
- assertThat(_filter.isExcluded("health", patterns)).isTrue();
- assertThat(_filter.isExcluded("api/v1/users", patterns)).isTrue();
- assertThat(_filter.isExcluded("admin", patterns)).isTrue();
+ assertThat(_filter.matches("health", patterns)).isTrue();
+ assertThat(_filter.matches("api/v1/users", patterns)).isTrue();
+ assertThat(_filter.matches("admin", patterns)).isTrue();
}
// ===== Error Handling Tests =====
@@ -134,32 +136,134 @@ public class AuditUrlPathFilterTest {
public void testInvalidPatternIsSkipped() {
String patterns = "api/v[123,health,{unclosed";
- assertThat(_filter.isExcluded("health", patterns)).isTrue();
- assertThat(_filter.isExcluded("api/v1", patterns)).isFalse();
+ assertThat(_filter.matches("health", patterns)).isTrue();
+ assertThat(_filter.matches("api/v1", patterns)).isFalse();
}
@Test
public void testInvalidPathHandling() {
String invalidPath = "path\0with\0nulls";
- assertThat(_filter.isExcluded(invalidPath, "health")).isFalse();
+ assertThat(_filter.matches(invalidPath, "health")).isFalse();
}
@Test
public void testAllInvalidPatternsReturnFalse() {
String patterns = "[unclosed,{unclosed,\\invalid";
- assertThat(_filter.isExcluded("anything", patterns)).isFalse();
+ assertThat(_filter.matches("anything", patterns)).isFalse();
}
@Test
public void testBasicIntegrationWithPathMatcher() {
String patterns = "health,api/*,admin/**";
- assertThat(_filter.isExcluded("health", patterns)).isTrue();
- assertThat(_filter.isExcluded("api/users", patterns)).isTrue();
- assertThat(_filter.isExcluded("api/v1/users", patterns)).isFalse();
- assertThat(_filter.isExcluded("admin/config", patterns)).isTrue();
- assertThat(_filter.isExcluded("admin/config/settings", patterns)).isTrue();
- assertThat(_filter.isExcluded("metrics", patterns)).isFalse();
+ assertThat(_filter.matches("health", patterns)).isTrue();
+ assertThat(_filter.matches("api/users", patterns)).isTrue();
+ assertThat(_filter.matches("api/v1/users", patterns)).isFalse();
+ assertThat(_filter.matches("admin/config", patterns)).isTrue();
+ assertThat(_filter.matches("admin/config/settings", patterns)).isTrue();
+ assertThat(_filter.matches("metrics", patterns)).isFalse();
+ }
+
+ // ===== shouldAudit Method Tests =====
+
+ @Test
+ public void testShouldAuditExclusionAlwaysWins() {
+ AuditConfigManager configManager = mock(AuditConfigManager.class);
+ AuditConfig config = new AuditConfig();
+ config.setUrlFilterExcludePatterns("/health,/metrics");
+ config.setUrlFilterIncludePatterns("/api/**,/health");
+ when(configManager.getCurrentConfig()).thenReturn(config);
+
+ AuditUrlPathFilter filter = new AuditUrlPathFilter(configManager);
+
+ // /health is both included and excluded - exclusion should win
+ assertThat(filter.shouldAudit("/health")).isFalse();
+ // /metrics is only excluded
+ assertThat(filter.shouldAudit("/metrics")).isFalse();
+ // /api/users is included and not excluded
+ assertThat(filter.shouldAudit("/api/users")).isTrue();
+ }
+
+ @Test
+ public void testShouldAuditIncludePatternsAsAllowlist() {
+ AuditConfigManager configManager = mock(AuditConfigManager.class);
+ AuditConfig config = new AuditConfig();
+ config.setUrlFilterExcludePatterns("");
+ config.setUrlFilterIncludePatterns("/api/**,/v1/**");
+ when(configManager.getCurrentConfig()).thenReturn(config);
+
+ AuditUrlPathFilter filter = new AuditUrlPathFilter(configManager);
+
+ // URLs matching include patterns should be audited
+ assertThat(filter.shouldAudit("/api/users")).isTrue();
+ assertThat(filter.shouldAudit("/v1/data")).isTrue();
+ assertThat(filter.shouldAudit("/api/v2/products")).isTrue();
+
+ // URLs not matching include patterns should not be audited
+ assertThat(filter.shouldAudit("/admin")).isFalse();
+ assertThat(filter.shouldAudit("/health")).isFalse();
+ assertThat(filter.shouldAudit("/metrics")).isFalse();
+ }
+
+ @Test
+ public void testShouldAuditDefaultBehaviorWithoutIncludePatterns() {
+ AuditConfigManager configManager = mock(AuditConfigManager.class);
+ AuditConfig config = new AuditConfig();
+ config.setUrlFilterExcludePatterns("/health,/metrics");
+ config.setUrlFilterIncludePatterns(""); // No include patterns
+ when(configManager.getCurrentConfig()).thenReturn(config);
+
+ AuditUrlPathFilter filter = new AuditUrlPathFilter(configManager);
+
+ // Without include patterns, everything should be audited except excluded
URLs
+ assertThat(filter.shouldAudit("/api/users")).isTrue();
+ assertThat(filter.shouldAudit("/admin")).isTrue();
+ assertThat(filter.shouldAudit("/v1/data")).isTrue();
+
+ // Excluded URLs should not be audited
+ assertThat(filter.shouldAudit("/health")).isFalse();
+ assertThat(filter.shouldAudit("/metrics")).isFalse();
+ }
+
+ @Test
+ public void testShouldAuditBothPatternsEmpty() {
+ AuditConfigManager configManager = mock(AuditConfigManager.class);
+ AuditConfig config = new AuditConfig();
+ config.setUrlFilterExcludePatterns("");
+ config.setUrlFilterIncludePatterns("");
+ when(configManager.getCurrentConfig()).thenReturn(config);
+
+ AuditUrlPathFilter filter = new AuditUrlPathFilter(configManager);
+
+ // With no patterns configured, everything should be audited
+ assertThat(filter.shouldAudit("/health")).isTrue();
+ assertThat(filter.shouldAudit("/api/users")).isTrue();
+ assertThat(filter.shouldAudit("/admin")).isTrue();
+ assertThat(filter.shouldAudit("/anything")).isTrue();
+ }
+
+ @Test
+ public void testShouldAuditComplexPriorityScenario() {
+ AuditConfigManager configManager = mock(AuditConfigManager.class);
+ AuditConfig config = new AuditConfig();
+ config.setUrlFilterExcludePatterns("/api/*/internal,/metrics");
+ config.setUrlFilterIncludePatterns("/api/**");
+ when(configManager.getCurrentConfig()).thenReturn(config);
+
+ AuditUrlPathFilter filter = new AuditUrlPathFilter(configManager);
+
+ // /api/v1/users is included and not excluded
+ assertThat(filter.shouldAudit("/api/v1/users")).isTrue();
+
+ // /api/v1/internal is included but also excluded - exclusion wins
+ assertThat(filter.shouldAudit("/api/v1/internal")).isFalse();
+ assertThat(filter.shouldAudit("/api/v2/internal")).isFalse();
+
+ // /metrics is excluded
+ assertThat(filter.shouldAudit("/metrics")).isFalse();
+
+ // /admin is not included (include patterns act as allowlist)
+ assertThat(filter.shouldAudit("/admin")).isFalse();
}
}
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java
index d3bc2b78fe9..b11f4850c91 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/BaseControllerStarter.java
@@ -59,6 +59,7 @@ import org.apache.helix.task.TaskDriver;
import org.apache.helix.zookeeper.constant.ZkSystemPropertyKeys;
import org.apache.helix.zookeeper.datamodel.ZNRecord;
import org.apache.pinot.common.Utils;
+import org.apache.pinot.common.audit.AuditServiceBinder;
import org.apache.pinot.common.config.DefaultClusterConfigChangeHandler;
import org.apache.pinot.common.config.TlsConfig;
import org.apache.pinot.common.function.FunctionRegistry;
@@ -87,7 +88,6 @@ import org.apache.pinot.common.utils.tls.TlsUtils;
import org.apache.pinot.common.version.PinotVersion;
import org.apache.pinot.controller.api.ControllerAdminApiApplication;
import org.apache.pinot.controller.api.access.AccessControlFactory;
-import org.apache.pinot.controller.api.audit.AuditServiceBinder;
import org.apache.pinot.controller.api.events.MetadataEventNotifierFactory;
import org.apache.pinot.controller.api.resources.ControllerFilePathProvider;
import
org.apache.pinot.controller.api.resources.InvalidControllerConfigException;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]