This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new 08b0804be0 Surface BanyanDB config in /debugging/config/dump via a
ConfigDumpExtension SPI (#13932)
08b0804be0 is described below
commit 08b0804be0d41c61c6371383ec5b6e2bde363605
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Tue Jun 30 16:41:46 2026 +0800
Surface BanyanDB config in /debugging/config/dump via a ConfigDumpExtension
SPI (#13932)
* Surface BanyanDB config in /debugging/config/dump via ConfigDumpExtension
SPI
The BanyanDB storage config moved to a separate bydb.yml / bydb-topn.yml in
10.2.0, so a BanyanDB deployment showed an empty storage.banyandb block in
the
/debugging/config/dump admin API while ES/JDBC showed theirs.
Add a generic ConfigDumpExtension SPI on ServerStatusService that any module
loading configuration from a secondary file can implement. BanyanDB
registers
an extension that flattens its already-loaded, environment-resolved config
into
storage.banyandb.* (TopN rules under storage.banyandb.topN.*), merged into
the
same dump and masked centrally by the existing secret-keyword list. The
status
module stays storage-agnostic (dependency arrow plugin -> core); the
Horizon UI
needs no change since it groups by the first dotted segment.
Adds UTs for the core merge+mask and the flattener, plus a representative
config-dump assertion on the banyandb storage e2e case.
---
docs/en/changes/changes.md | 1 +
docs/en/debugging/config_dump.md | 28 ++++-
.../server/core/status/ConfigDumpExtension.java | 43 +++++++
.../server/core/status/ServerStatusService.java | 18 +++
.../core/status/ServerStatusServiceTest.java | 83 +++++++++++++
.../banyandb/BanyanDBConfigDumpExtension.java | 137 +++++++++++++++++++++
.../plugin/banyandb/BanyanDBStorageProvider.java | 6 +
.../banyandb/BanyanDBConfigDumpExtensionTest.java | 77 ++++++++++++
test/e2e-v2/cases/storage/banyandb/e2e.yaml | 19 +++
.../storage/expected/config-dump-banyandb.yml | 18 +++
10 files changed, 427 insertions(+), 3 deletions(-)
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 28f0c62c14..86a0094398 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -319,6 +319,7 @@
* Fix: `layer-extensions.yml` is now excluded from the `skywalking-oap` jar
and shipped to the distribution `config/` directory, so an operator-edited
`config/layer-extensions.yml` is no longer shadowed by the empty template
bundled in the jar. Because the OAP launch script puts `oap-libs/*.jar` ahead
of `config/` on the classpath, `ResourceUtils.read("layer-extensions.yml")`
previously always resolved the jar-bundled `layers: []` and silently ignored
the operator's file — custom layers [...]
* Fix: the v2 MAL compiler now resolves custom layers referenced as
`Layer.NAME` in an expression. A custom layer declared through a
`layerDefinitions:` block (or `layer-extensions.yml` / the `LayerExtension`
SPI) has no generated `Layer.*` static field, so `service(['svc'],
Layer.IOT_FLEET)` previously failed code generation because `Layer` has no
`IOT_FLEET` field. The compiler now lowers every `Layer.NAME` static-field
reference to a runtime `Layer.nameOf("NAME")` registry lookup, so [...]
* Fix Envoy ALS rendering for the LAL live-debugger and the persisted log
`content`: an Istio metadata-exchange peer in
`common_properties.filter_state_objects` (legacy Wasm `wasm.*_peer` =
`Any{BytesValue}` wrapping a FlatBuffer, or modern `*_peer` = `Any{Struct}`) is
now decoded into the readable peer metadata (pod / namespace / labels) instead
of an opaque `jsonformat-failed` envelope or base64. The serialization is
hardened so a single un-printable field can no longer blank the whole [...]
+* Surface the effective BanyanDB configuration (`bydb.yml` / `bydb-topn.yml`)
in the `/debugging/config/dump` admin API. Because the BanyanDB config moved to
a separate file in 10.2.0, a BanyanDB deployment previously showed an empty
`storage.banyandb` block in the dump; its post-environment-resolution values
are now merged into the same response under `storage.banyandb.*` (TopN rules
under `storage.banyandb.topN.*`), masked by the same secret-keyword list, via a
generic `ConfigDumpExten [...]
#### UI
* Add Airflow layer dashboards and menu i18n under Workflow Scheduler in
Horizon UI (SWIP-7).
diff --git a/docs/en/debugging/config_dump.md b/docs/en/debugging/config_dump.md
index 753f8dd31d..d521cd26b0 100644
--- a/docs/en/debugging/config_dump.md
+++ b/docs/en/debugging/config_dump.md
@@ -75,12 +75,12 @@ storage:
password: ${SW_ES_PASSWORD:""}
```
-It would be masked and shown as `********` in the dump result.
+It would be masked and shown as `******` in the dump result.
```shell
> curl http://127.0.0.1:17128/debugging/config/dump
...
-storage.elasticsearch.password=********
+storage.elasticsearch.password=******
...
```
@@ -89,4 +89,26 @@ By default, we mask the config keys through the following
configurations.
```yaml
# Include the list of keywords to filter configurations including secrets.
Separate keywords by a comma.
keywords4MaskingSecretsOfConfig:
${SW_DEBUGGING_QUERY_KEYWORDS_FOR_MASKING_SECRETS:user,password,trustStorePass,keyStorePass,token,accessKey,secretKey,authentication}
-```
\ No newline at end of file
+```
+
+## BanyanDB Storage Configurations
+
+When BanyanDB is the active storage, the OAP loads its configuration from the
dedicated
+`bydb.yml` and `bydb-topn.yml` files (separated out from `application.yml`
since 10.2.0). The
+effective, environment-resolved values of these files are included in the same
dump under the
+`storage.banyandb.*` keys (TopN rules under `storage.banyandb.topN.*`), for
example:
+
+```shell
+> curl http://127.0.0.1:17128/debugging/config/dump
+...
+storage.banyandb.global.targets=127.0.0.1:17912
+storage.banyandb.global.user=******
+storage.banyandb.global.password=******
+storage.banyandb.metricsMinute.ttl=7
+storage.banyandb.topN.endpoint_cpm.endpoint_cpm-service.countersNumber=1000
+...
+```
+
+These rows reflect what the server actually loaded from `bydb.yml` /
`bydb-topn.yml` (after
+environment-variable overrides), so editing `storage.banyandb.*` under
`application.yml` has no
+effect. Secret values are masked using the same keyword list described above.
\ No newline at end of file
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ConfigDumpExtension.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ConfigDumpExtension.java
new file mode 100644
index 0000000000..ec85397c24
--- /dev/null
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ConfigDumpExtension.java
@@ -0,0 +1,43 @@
+/*
+ * 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.skywalking.oap.server.core.status;
+
+import java.util.Map;
+
+/**
+ * Contributes additional effective (post-environment-resolution)
configurations to the
+ * {@code /debugging/config/dump} output. It exists for modules whose runtime
configuration is
+ * loaded from a secondary file outside {@code application.yml} — for example
the BanyanDB storage
+ * plugin loads {@code bydb.yml} / {@code bydb-topn.yml} into its own POJO,
which the boot-time
+ * {@link ServerStatusService#dumpBootingConfigurations(String)} cannot
otherwise see.
+ *
+ * <p>Implementations register themselves through
+ * {@link
ServerStatusService#registerConfigDumpExtension(ConfigDumpExtension)}.
+ *
+ * @since 11.0.0
+ */
+public interface ConfigDumpExtension {
+ /**
+ * @return the effective configurations as fully-qualified {@code
module.provider.key} to value
+ * pairs, keyed exactly like the boot dump so they interleave with it.
Values are returned raw;
+ * the dump masks secrets centrally by key, so each key should carry its
field name as the last
+ * segment (e.g. {@code storage.banyandb.global.password}).
+ */
+ Map<String, String> dumpConfigurations();
+}
diff --git
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ServerStatusService.java
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ServerStatusService.java
index db9141e8aa..c97984b263 100644
---
a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ServerStatusService.java
+++
b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/status/ServerStatusService.java
@@ -48,6 +48,8 @@ public class ServerStatusService implements Service {
private List<ServerStatusWatcher> statusWatchers = new
CopyOnWriteArrayList<>();
+ private final List<ConfigDumpExtension> configDumpExtensions = new
CopyOnWriteArrayList<>();
+
private List<ApplicationConfiguration.ModuleConfiguration> configurations;
public void bootedNow(List<ApplicationConfiguration.ModuleConfiguration>
configurations, long uptime) {
@@ -81,6 +83,18 @@ public class ServerStatusService implements Service {
this.statusWatchers.add(watcher);
}
+ /**
+ * Register a {@link ConfigDumpExtension} whose effective configurations
are merged into the
+ * {@code /debugging/config/dump} output. Used by modules (e.g. the
BanyanDB storage plugin)
+ * that load configuration from a secondary file outside {@code
application.yml}.
+ *
+ * @param extension the extension contributing extra effective
configurations to the dump
+ * @since 11.0.0
+ */
+ public void registerConfigDumpExtension(ConfigDumpExtension extension) {
+ this.configDumpExtensions.add(extension);
+ }
+
/**
* @return a complete list of booting configurations with effected values.
* @since 9.7.0
@@ -117,6 +131,10 @@ public class ServerStatusService implements Service {
)
);
}
+ for (ConfigDumpExtension extension : configDumpExtensions) {
+ extension.dumpConfigurations().forEach(
+ (key, value) -> configList.put(key, maskConfigValue(key,
value, keywords)));
+ }
return configList;
}
diff --git
a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/status/ServerStatusServiceTest.java
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/status/ServerStatusServiceTest.java
new file mode 100644
index 0000000000..b49c50c2d3
--- /dev/null
+++
b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/status/ServerStatusServiceTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.skywalking.oap.server.core.status;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import
org.apache.skywalking.oap.server.core.status.ServerStatusService.ConfigList;
+import
org.apache.skywalking.oap.server.library.module.ApplicationConfiguration;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.mock;
+
+public class ServerStatusServiceTest {
+ private static final String KEYWORDS = "user,password";
+
+ @Test
+ public void shouldMergeAndMaskConfigDumpExtensions() throws Exception {
+ ServerStatusService service = new
ServerStatusService(mock(ModuleManager.class));
+ seedBootingConfigurations(service);
+
+ service.registerConfigDumpExtension(() -> Map.of(
+ "storage.banyandb.global.targets", "127.0.0.1:17912",
+ "storage.banyandb.global.user", "admin",
+ "storage.banyandb.global.password", "s3cret",
+ "storage.banyandb.metricsMinute.ttl", "7"
+ ));
+
+ ConfigList dump = service.dumpBootingConfigurations(KEYWORDS);
+
+ // Secrets masked by their field-name keyword, applied centrally to
extension rows too.
+ assertEquals("******", dump.get("storage.banyandb.global.user"));
+ assertEquals("******", dump.get("storage.banyandb.global.password"));
+ // Non-secret extension values pass through, merged into the same dump.
+ assertEquals("127.0.0.1:17912",
dump.get("storage.banyandb.global.targets"));
+ assertEquals("7", dump.get("storage.banyandb.metricsMinute.ttl"));
+ // The application.yml-derived rows are still present.
+ assertEquals("12800", dump.get("core.default.restPort"));
+ }
+
+ @Test
+ public void shouldNotLeakExtensionsBeforeBoot() {
+ ServerStatusService service = new
ServerStatusService(mock(ModuleManager.class));
+ service.registerConfigDumpExtension(
+ () -> Map.of("storage.banyandb.global.targets",
"127.0.0.1:17912"));
+
+ // configurations not set yet (pre-boot) -> dump is empty; extensions
are not dumped early.
+ assertFalse(service.dumpBootingConfigurations(KEYWORDS)
+ .containsKey("storage.banyandb.global.targets"));
+ }
+
+ private void seedBootingConfigurations(ServerStatusService service) throws
Exception {
+ ApplicationConfiguration appConfig = new ApplicationConfiguration();
+ Properties props = new Properties();
+ props.put("restPort", "12800");
+ ApplicationConfiguration.ModuleConfiguration core =
appConfig.addModule("core");
+ core.addProviderConfiguration("default", props);
+
+ Field field =
ServerStatusService.class.getDeclaredField("configurations");
+ field.setAccessible(true);
+ field.set(service, List.of(core));
+ }
+}
diff --git
a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBConfigDumpExtension.java
b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBConfigDumpExtension.java
new file mode 100644
index 0000000000..bc047509b9
--- /dev/null
+++
b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBConfigDumpExtension.java
@@ -0,0 +1,137 @@
+/*
+ * 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.skywalking.oap.server.storage.plugin.banyandb;
+
+import com.google.common.base.Strings;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.skywalking.oap.server.core.status.ConfigDumpExtension;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.Global;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.GroupResource;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.Stage;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.TopN;
+
+/**
+ * Flattens the loaded, environment-resolved {@link BanyanDBStorageConfig}
(from {@code bydb.yml}
+ * and {@code bydb-topn.yml}) into {@code /debugging/config/dump} rows.
Without this, a BanyanDB
+ * deployment shows an empty {@code storage.banyandb} block in the dump,
because that configuration
+ * lives in a separate file the boot-time dump does not parse.
+ *
+ * <p>Keys read straight from the loaded POJO, never re-read from the file, so
the values are the
+ * effective post-environment-override ones. Group segment names mirror the
{@code groups:} keys in
+ * {@code bydb.yml} so the dump reads like the source file. Secret masking
(e.g. {@code global.user}
+ * / {@code global.password}) is applied centrally by the dump, so this
extension returns raw values.
+ *
+ * @since 11.0.0
+ */
+public class BanyanDBConfigDumpExtension implements ConfigDumpExtension {
+ private final String prefix;
+ private final BanyanDBStorageConfig config;
+
+ public BanyanDBConfigDumpExtension(final String prefix, final
BanyanDBStorageConfig config) {
+ this.prefix = prefix;
+ this.config = config;
+ }
+
+ @Override
+ public Map<String, String> dumpConfigurations() {
+ final Map<String, String> dump = new LinkedHashMap<>();
+ flattenGlobal(prefix + ".global", config.getGlobal(), dump);
+
+ // Only the groups populated from bydb.yml are emitted; segment names
match its `groups:` keys.
+ final Map<String, GroupResource> groups = new LinkedHashMap<>();
+ groups.put("records", config.getRecordsNormal());
+ groups.put("recordsLog", config.getRecordsLog());
+ groups.put("trace", config.getTrace());
+ groups.put("zipkinTrace", config.getZipkinTrace());
+ groups.put("recordsBrowserErrorLog",
config.getRecordsBrowserErrorLog());
+ groups.put("metricsMinute", config.getMetricsMin());
+ groups.put("metricsHour", config.getMetricsHour());
+ groups.put("metricsDay", config.getMetricsDay());
+ groups.put("metadata", config.getMetadata());
+ groups.put("property", config.getProperty());
+ groups.forEach((name, group) -> flattenGroup(prefix + "." + name,
group, dump));
+
+ config.getTopNConfigs().forEach((metric, rules) ->
+ rules.forEach((ruleName, topN) ->
+ flattenTopN(prefix + ".topN." + metric + "." + ruleName, topN,
dump)));
+ return dump;
+ }
+
+ private void flattenGlobal(final String p, final Global g, final
Map<String, String> dump) {
+ // getTargets()/getCompatibleServerApiVersions() split the backing
String into an array;
+ // join them back so the dump shows the raw comma-separated form the
operator configured.
+ dump.put(p + ".targets", String.join(",", g.getTargets()));
+ dump.put(p + ".maxBulkSize", String.valueOf(g.getMaxBulkSize()));
+ dump.put(p + ".flushInterval", String.valueOf(g.getFlushInterval()));
+ dump.put(p + ".flushTimeout", String.valueOf(g.getFlushTimeout()));
+ dump.put(p + ".concurrentWriteThreads",
String.valueOf(g.getConcurrentWriteThreads()));
+ dump.put(p + ".profileTaskQueryMaxSize",
String.valueOf(g.getProfileTaskQueryMaxSize()));
+ dump.put(p + ".asyncProfilerTaskQueryMaxSize",
String.valueOf(g.getAsyncProfilerTaskQueryMaxSize()));
+ dump.put(p + ".pprofTaskQueryMaxSize",
String.valueOf(g.getPprofTaskQueryMaxSize()));
+ dump.put(p + ".resultWindowMaxSize",
String.valueOf(g.getResultWindowMaxSize()));
+ dump.put(p + ".metadataQueryMaxSize",
String.valueOf(g.getMetadataQueryMaxSize()));
+ dump.put(p + ".segmentQueryMaxSize",
String.valueOf(g.getSegmentQueryMaxSize()));
+ dump.put(p + ".profileDataQueryBatchSize",
String.valueOf(g.getProfileDataQueryBatchSize()));
+ dump.put(p + ".cleanupUnusedTopNRules",
String.valueOf(g.isCleanupUnusedTopNRules()));
+ dump.put(p + ".namespace", Strings.nullToEmpty(g.getNamespace()));
+ dump.put(p + ".compatibleServerApiVersions", String.join(",",
g.getCompatibleServerApiVersions()));
+ dump.put(p + ".user", Strings.nullToEmpty(g.getUser()));
+ dump.put(p + ".password", Strings.nullToEmpty(g.getPassword()));
+ dump.put(p + ".sslTrustCAPath",
Strings.nullToEmpty(g.getSslTrustCAPath()));
+ }
+
+ private void flattenGroup(final String p, final GroupResource group, final
Map<String, String> dump) {
+ dump.put(p + ".shardNum", String.valueOf(group.getShardNum()));
+ dump.put(p + ".segmentInterval",
String.valueOf(group.getSegmentInterval()));
+ dump.put(p + ".ttl", String.valueOf(group.getTtl()));
+ dump.put(p + ".replicas", String.valueOf(group.getReplicas()));
+ dump.put(p + ".enableWarmStage",
String.valueOf(group.isEnableWarmStage()));
+ dump.put(p + ".enableColdStage",
String.valueOf(group.isEnableColdStage()));
+ dump.put(p + ".defaultQueryStages",
group.getDefaultQueryStages().toString());
+ final List<Stage> stages = group.getAdditionalLifecycleStages();
+ for (int i = 0; i < stages.size(); i++) {
+ flattenStage(p + ".additionalLifecycleStages." + i, stages.get(i),
dump);
+ }
+ }
+
+ private void flattenStage(final String p, final Stage stage, final
Map<String, String> dump) {
+ dump.put(p + ".name", stage.getName() == null ? "" :
stage.getName().name());
+ dump.put(p + ".nodeSelector",
Strings.nullToEmpty(stage.getNodeSelector()));
+ dump.put(p + ".shardNum", String.valueOf(stage.getShardNum()));
+ dump.put(p + ".segmentInterval",
String.valueOf(stage.getSegmentInterval()));
+ dump.put(p + ".ttl", String.valueOf(stage.getTtl()));
+ dump.put(p + ".replicas", String.valueOf(stage.getReplicas()));
+ dump.put(p + ".close", String.valueOf(stage.isClose()));
+ }
+
+ private void flattenTopN(final String p, final TopN topN, final
Map<String, String> dump) {
+ dump.put(p + ".lruSizeMinute",
String.valueOf(topN.getLruSizeMinute()));
+ dump.put(p + ".lruSizeHourDay",
String.valueOf(topN.getLruSizeHourDay()));
+ dump.put(p + ".countersNumber",
String.valueOf(topN.getCountersNumber()));
+ dump.put(p + ".sort", topN.getSort() == null ? "" :
topN.getSort().name());
+ dump.put(p + ".groupByTagNames",
+ topN.getGroupByTagNames() == null ? "[]" :
topN.getGroupByTagNames().toString());
+ dump.put(p + ".excludes", topN.getExcludes().stream()
+ .map(kv -> kv.getKey() + "=" + kv.getValue())
+ .collect(Collectors.joining(",", "[", "]")));
+ }
+}
diff --git
a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
index fd858ab688..dbb2b7f3df 100644
---
a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
+++
b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBStorageProvider.java
@@ -27,6 +27,7 @@ import
org.apache.skywalking.banyandb.database.v1.BanyandbDatabase;
import
org.apache.skywalking.library.banyandb.v1.client.grpc.exception.BanyanDBException;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.RunningMode;
+import org.apache.skywalking.oap.server.core.status.ServerStatusService;
import org.apache.skywalking.oap.server.core.storage.IBatchDAO;
import org.apache.skywalking.oap.server.core.storage.IHistoryDeleteDAO;
import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
@@ -244,6 +245,11 @@ public class BanyanDBStorageProvider extends
ModuleProvider {
this.modelInstaller.start();
getManager().find(CoreModule.NAME).provider().getService(ModelRegistry.class).addModelListener(modelInstaller);
+
+ ServerStatusService serverStatusService =
+
getManager().find(CoreModule.NAME).provider().getService(ServerStatusService.class);
+ serverStatusService.registerConfigDumpExtension(
+ new BanyanDBConfigDumpExtension(StorageModule.NAME + "." +
name(), this.config));
} catch (Exception e) {
throw new ModuleStartException(e.getMessage(), e);
}
diff --git
a/oap-server/server-storage-plugin/storage-banyandb-plugin/src/test/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBConfigDumpExtensionTest.java
b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/test/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBConfigDumpExtensionTest.java
new file mode 100644
index 0000000000..daeef8fac5
--- /dev/null
+++
b/oap-server/server-storage-plugin/storage-banyandb-plugin/src/test/java/org/apache/skywalking/oap/server/storage/plugin/banyandb/BanyanDBConfigDumpExtensionTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.skywalking.oap.server.storage.plugin.banyandb;
+
+import java.util.Map;
+import org.apache.skywalking.oap.server.core.query.type.KeyValue;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.MetricsMin;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.Stage;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.StageName;
+import
org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig.TopN;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BanyanDBConfigDumpExtensionTest {
+ @Test
+ public void shouldFlattenLoadedConfig() {
+ BanyanDBStorageConfig config = new BanyanDBStorageConfig();
+ config.getGlobal().setTargets("a:1,b:2");
+ config.getGlobal().setUser("admin");
+ config.getGlobal().setPassword("admin");
+ config.getGlobal().setCompatibleServerApiVersions("0.10");
+ config.getGlobal().setNamespace("sw");
+
+ // A metrics-minute group carrying a warm lifecycle stage.
+ MetricsMin metricsMin = config.getMetricsMin();
+ metricsMin.setShardNum(1);
+ metricsMin.setTtl(7);
+ metricsMin.setEnableWarmStage(true);
+ Stage warm = new Stage();
+ warm.setName(StageName.warm);
+ warm.setTtl(30);
+ metricsMin.getAdditionalLifecycleStages().add(warm);
+
+ // A TopN rule under bydb-topn.yml.
+ TopN topN = new TopN();
+ topN.setCountersNumber(1000);
+ topN.setSort(TopN.Sort.des);
+ topN.getExcludes().add(new KeyValue("a", "b"));
+ config.getTopNConfigs().put("endpoint_cpm",
Map.of("endpoint_cpm-service", topN));
+
+ Map<String, String> dump =
+ new BanyanDBConfigDumpExtension("storage.banyandb",
config).dumpConfigurations();
+
+ // global: targets joined back to the raw comma form, not the split
array.
+ assertEquals("a:1,b:2", dump.get("storage.banyandb.global.targets"));
+ // Raw values; masking is the dump's responsibility, not the
extension's.
+ assertEquals("admin", dump.get("storage.banyandb.global.user"));
+ assertEquals("admin", dump.get("storage.banyandb.global.password"));
+ assertEquals("0.10",
dump.get("storage.banyandb.global.compatibleServerApiVersions"));
+ // Group + indexed lifecycle stage (enum -> name()).
+ assertEquals("7", dump.get("storage.banyandb.metricsMinute.ttl"));
+ assertEquals("true",
dump.get("storage.banyandb.metricsMinute.enableWarmStage"));
+ assertEquals("warm",
dump.get("storage.banyandb.metricsMinute.additionalLifecycleStages.0.name"));
+ assertEquals("30",
dump.get("storage.banyandb.metricsMinute.additionalLifecycleStages.0.ttl"));
+ // TopN nested under topN.<metric>.<rule>.
+ assertEquals("1000",
dump.get("storage.banyandb.topN.endpoint_cpm.endpoint_cpm-service.countersNumber"));
+ assertEquals("des",
dump.get("storage.banyandb.topN.endpoint_cpm.endpoint_cpm-service.sort"));
+ assertEquals("[a=b]",
dump.get("storage.banyandb.topN.endpoint_cpm.endpoint_cpm-service.excludes"));
+ }
+}
diff --git a/test/e2e-v2/cases/storage/banyandb/e2e.yaml
b/test/e2e-v2/cases/storage/banyandb/e2e.yaml
index c59388a5d5..8c9d9a7ff9 100644
--- a/test/e2e-v2/cases/storage/banyandb/e2e.yaml
+++ b/test/e2e-v2/cases/storage/banyandb/e2e.yaml
@@ -66,6 +66,25 @@ verify:
- query: |
swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
config ttl
expected: ../expected/ttl-config-banyandb.yml
+ # The effective bydb.yml / bydb-topn.yml config is surfaced in
/debugging/config/dump
+ # (one representative key per section) and the credentials are masked.
Assertions live
+ # in the query; it prints `config-dump-ok` only when all of them pass.
+ - query: |
+ dump=$(curl -s http://${oap_host}:${oap_17128}/debugging/config/dump)
+ exact() { echo "$dump" | grep -Fqx "$1" || { echo "missing or
unexpected: $1"; exit 1; }; }
+ exists() { echo "$dump" | grep -Fq "$1" || { echo "missing: $1"; exit
1; }; }
+ exact 'storage.provider=banyandb'
+ exact 'storage.banyandb.global.user=******'
+ exact 'storage.banyandb.global.password=******'
+ exact 'storage.banyandb.global.namespace=sw'
+ exists 'storage.banyandb.global.targets='
+ exact 'storage.banyandb.records.ttl=3'
+ exact 'storage.banyandb.metricsMinute.ttl=7'
+ exact 'storage.banyandb.metricsMinute.enableWarmStage=true'
+ exact
'storage.banyandb.metricsMinute.additionalLifecycleStages.0.name=warm'
+ exists
'storage.banyandb.topN.endpoint_cpm.endpoint_cpm-service.countersNumber='
+ echo config-dump-ok
+ expected: ../expected/config-dump-banyandb.yml
cleanup:
on: always
collect:
diff --git a/test/e2e-v2/cases/storage/expected/config-dump-banyandb.yml
b/test/e2e-v2/cases/storage/expected/config-dump-banyandb.yml
new file mode 100644
index 0000000000..070a4d3de8
--- /dev/null
+++ b/test/e2e-v2/cases/storage/expected/config-dump-banyandb.yml
@@ -0,0 +1,18 @@
+# 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.
+
+# The representative bydb config-dump assertions run inside the query (it
prints this
+# sentinel only when every picked key is present and the secrets are masked).
+config-dump-ok