mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-27 16:48:58 +00:00
refactor: expose extension runtime snapshots (#3644)
This commit is contained in:
@@ -5,16 +5,16 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
## Current Context
|
||||
|
||||
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
||||
- Branch: `overtrue/arch-ops-profiler-runtime-contract`
|
||||
- Baseline: `overtrue/arch-embedded-lifecycle-publication-reuse`
|
||||
(`e27c079d4c2a577bd9a632a615b2f0b96b468149`).
|
||||
- Stacked on: local R-031, which is stacked on rustfs/rustfs#3641.
|
||||
- Branch: `overtrue/arch-extension-runtime-snapshots`
|
||||
- Baseline: `overtrue/arch-ops-profiler-runtime-contract`
|
||||
(`6d4ff92f398a9decdce53a7203744c7c2a70a4e9`).
|
||||
- Stacked on: local R-032, which is stacked on rustfs/rustfs#3642.
|
||||
- PR type for this branch: `contract`
|
||||
- Runtime behavior changes: none.
|
||||
- Rust code changes: publish the builtin ops profiler catalog contract through
|
||||
targets and add a read-only profiler registry contract.
|
||||
- Rust code changes: expose builtin diagnostics/profiler runtime capability
|
||||
snapshots through the admin extension catalog response.
|
||||
- CI/script changes: none.
|
||||
- Docs changes: record the R-032 ops profiler runtime contract slice.
|
||||
- Docs changes: record the R-033 extension runtime snapshot slice.
|
||||
|
||||
## Phase 0 Tasks
|
||||
|
||||
@@ -2198,20 +2198,33 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
||||
migration/layer guards, formatting, diff hygiene, Rust risk scan, branch
|
||||
freshness check, pre-commit quality gate, and three-expert review.
|
||||
|
||||
- [x] `R-033` Expose extension runtime capability snapshots.
|
||||
- Do: add read-only diagnostics/profiler runtime capability snapshots to the
|
||||
admin extension catalog response using existing schema and contract DTOs.
|
||||
- Acceptance: `/v4/extensions/catalog` reports builtin diagnostics and
|
||||
profiler capability contracts with their runtime boundaries, disabled
|
||||
defaults, and non-fatal startup flags while preserving schema validation.
|
||||
- Must preserve: existing extension catalog route/auth, plugin instance
|
||||
listing, profiler/diagnostics execution paths, and external plugin flow
|
||||
status semantics.
|
||||
- Verification: focused admin catalog and targets runtime checks, RustFS lib
|
||||
check, migration/layer guards, formatting, diff hygiene, Rust risk scan,
|
||||
branch freshness check, pre-commit quality gate, and three-expert review.
|
||||
|
||||
## Next PRs
|
||||
|
||||
1. `pure-move`: continue pruning residual embedded startup-only orchestration
|
||||
once the lifecycle helpers are merged.
|
||||
2. `contract`: add the next read-only extension handoff once admin/reporting
|
||||
consumers need profiler or diagnostics snapshots.
|
||||
consumers need additional extension runtime snapshots.
|
||||
|
||||
## Pre-Push Review Log
|
||||
|
||||
| Expert | Status | Notes |
|
||||
|---|---|---|
|
||||
| Quality/architecture | passed | R-032 keeps profiler execution in the existing admin/profiling paths while adding only catalog and registry contract boundaries. |
|
||||
| Migration preservation | passed | Existing profile collection routes, startup/shutdown hook behavior, redaction requirements, and disabled external-runtime defaults remain unchanged. |
|
||||
| Testing/verification | passed | Focused targets/admin extension checks, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. |
|
||||
| Quality/architecture | passed | R-033 exposes only read-only diagnostics/profiler capability snapshots through the admin extension catalog response. |
|
||||
| Migration preservation | passed | Catalog auth, plugin instance listing, profiler/diagnostics execution paths, and external plugin flow status semantics remain unchanged. |
|
||||
| Testing/verification | passed | Focused admin catalog and targets runtime checks, lib check, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. |
|
||||
|
||||
## Verification Notes
|
||||
|
||||
@@ -2247,6 +2260,20 @@ Passed before push:
|
||||
- `make pre-commit`: passed.
|
||||
- Three-expert review: passed.
|
||||
|
||||
- Issue #660 R-033 current slice:
|
||||
- `cargo test -p rustfs --lib extension_catalog -- --nocapture`: passed.
|
||||
- `cargo test -p rustfs-targets ops_diagnostics -- --nocapture`: passed.
|
||||
- `cargo test -p rustfs-targets ops_profiler -- --nocapture`: passed.
|
||||
- `cargo check -p rustfs --lib`: passed.
|
||||
- `./scripts/check_architecture_migration_rules.sh`: passed.
|
||||
- `./scripts/check_layer_dependencies.sh`: passed.
|
||||
- `cargo fmt --all --check`: passed.
|
||||
- `git diff --check`: passed.
|
||||
- Rust risk scan on changed Rust files: passed; only test-only
|
||||
expectations/assertion paths were present.
|
||||
- `make pre-commit`: passed.
|
||||
- Three-expert review: passed.
|
||||
|
||||
- Issue #660 X-012 current slice:
|
||||
- `cargo test -p rustfs-extension-schema`: passed.
|
||||
- `cargo check -p rustfs-extension-schema`: passed.
|
||||
|
||||
@@ -26,11 +26,16 @@ use crate::server::{ADMIN_PREFIX, RemoteAddr};
|
||||
use http::{HeaderMap, HeaderValue, StatusCode};
|
||||
use hyper::Method;
|
||||
use matchit::Params;
|
||||
use rustfs_extension_schema::{ExtensionKind, ExtensionSchema};
|
||||
use rustfs_extension_schema::{
|
||||
ExtensionCapabilityRef, ExtensionKind, ExtensionRuntimeContract, ExtensionSchema, OPS_DIAGNOSTICS_CAPABILITY,
|
||||
OPS_PROFILER_CAPABILITY, OpsDiagnosticsContract, OpsProfilerContract,
|
||||
};
|
||||
use rustfs_policy::policy::action::{Action, AdminAction};
|
||||
use rustfs_targets::{
|
||||
TargetPluginExternalFlowGate, TargetPluginExternalFlowGateStatus, builtin_extension_schemas,
|
||||
catalog::example_external_webhook_plugin, target_marketplace_extension_schema,
|
||||
OpsDiagnosticsRegistry, OpsProfilerRegistry, TargetPluginExternalFlowGate, TargetPluginExternalFlowGateStatus,
|
||||
builtin_extension_schemas, builtin_ops_diagnostics_contract, builtin_ops_diagnostics_extension_schema,
|
||||
builtin_ops_profiler_contract, builtin_ops_profiler_extension_schema, catalog::example_external_webhook_plugin,
|
||||
target_marketplace_extension_schema,
|
||||
};
|
||||
use s3s::header::CONTENT_TYPE;
|
||||
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
|
||||
@@ -55,9 +60,29 @@ pub fn register_extension_route(r: &mut S3Router<AdminOperation>) -> std::io::Re
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct ExtensionCatalogResponse {
|
||||
pub extensions: Vec<ExtensionSchema>,
|
||||
pub runtime_capabilities: ExtensionRuntimeCapabilitiesResponse,
|
||||
pub external_plugin_flow: TargetPluginExternalFlowGateStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct ExtensionRuntimeCapabilitiesResponse {
|
||||
pub ops_diagnostics: ExtensionRuntimeCapabilityResponse<OpsDiagnosticsContract>,
|
||||
pub ops_profiler: ExtensionRuntimeCapabilityResponse<OpsProfilerContract>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub(crate) struct ExtensionRuntimeCapabilityResponse<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
pub extension_id: String,
|
||||
pub capability: ExtensionCapabilityRef,
|
||||
pub runtime: ExtensionRuntimeContract,
|
||||
pub disabled_by_default: bool,
|
||||
pub startup_fatal: bool,
|
||||
pub contract: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) struct ExtensionInstanceEntry {
|
||||
@@ -95,10 +120,50 @@ fn build_extension_catalog_response() -> ExtensionCatalogResponse {
|
||||
|
||||
ExtensionCatalogResponse {
|
||||
extensions,
|
||||
runtime_capabilities: build_extension_runtime_capabilities_response(),
|
||||
external_plugin_flow: TargetPluginExternalFlowGate::default().status(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_extension_runtime_capabilities_response() -> ExtensionRuntimeCapabilitiesResponse {
|
||||
let ops_diagnostics_schema = builtin_ops_diagnostics_extension_schema();
|
||||
let ops_diagnostics_contract = builtin_ops_diagnostics_contract();
|
||||
let mut ops_diagnostics_registry = OpsDiagnosticsRegistry::new();
|
||||
debug_assert!(
|
||||
ops_diagnostics_registry
|
||||
.register_schema(&ops_diagnostics_schema, &ops_diagnostics_contract)
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
let ops_profiler_schema = builtin_ops_profiler_extension_schema();
|
||||
let ops_profiler_contract = builtin_ops_profiler_contract();
|
||||
let mut ops_profiler_registry = OpsProfilerRegistry::new();
|
||||
debug_assert!(
|
||||
ops_profiler_registry
|
||||
.register_schema(&ops_profiler_schema, &ops_profiler_contract)
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
ExtensionRuntimeCapabilitiesResponse {
|
||||
ops_diagnostics: ExtensionRuntimeCapabilityResponse {
|
||||
extension_id: ops_diagnostics_schema.extension_id,
|
||||
capability: ExtensionCapabilityRef::new(OPS_DIAGNOSTICS_CAPABILITY),
|
||||
runtime: ops_diagnostics_schema.runtime,
|
||||
disabled_by_default: ops_diagnostics_schema.disabled_by_default,
|
||||
startup_fatal: false,
|
||||
contract: ops_diagnostics_contract,
|
||||
},
|
||||
ops_profiler: ExtensionRuntimeCapabilityResponse {
|
||||
extension_id: ops_profiler_schema.extension_id,
|
||||
capability: ExtensionCapabilityRef::new(OPS_PROFILER_CAPABILITY),
|
||||
runtime: ops_profiler_schema.runtime,
|
||||
disabled_by_default: ops_profiler_schema.disabled_by_default,
|
||||
startup_fatal: false,
|
||||
contract: ops_profiler_contract,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn map_extension_instance(instance: PluginInstanceEntry) -> ExtensionInstanceEntry {
|
||||
ExtensionInstanceEntry {
|
||||
id: instance.id,
|
||||
@@ -206,7 +271,10 @@ mod tests {
|
||||
PluginContractDomain, PluginEnableState, PluginInstallState, PluginInstanceEntry, PluginInstanceSource,
|
||||
PluginOperationalRuntimeState, PluginOperationalStateContract,
|
||||
};
|
||||
use rustfs_extension_schema::{ExtensionKind, ExtensionRuntimeBoundary, validate_extension_schemas};
|
||||
use rustfs_extension_schema::{
|
||||
ExtensionKind, ExtensionRuntimeBoundary, OPS_DIAGNOSTICS_CAPABILITY, OPS_PROFILER_CAPABILITY, validate_extension_schemas,
|
||||
validate_ops_diagnostics_contract, validate_ops_profiler_contract,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
@@ -305,6 +373,36 @@ mod tests {
|
||||
assert!(validate_extension_schemas(&response.extensions).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_catalog_exposes_read_only_runtime_capability_snapshots() {
|
||||
let response = build_extension_catalog_response();
|
||||
|
||||
assert_eq!(response.runtime_capabilities.ops_diagnostics.extension_id, "builtin:ops-diagnostics");
|
||||
assert_eq!(
|
||||
response.runtime_capabilities.ops_diagnostics.capability.as_str(),
|
||||
OPS_DIAGNOSTICS_CAPABILITY
|
||||
);
|
||||
assert_eq!(
|
||||
response.runtime_capabilities.ops_diagnostics.runtime.boundary,
|
||||
ExtensionRuntimeBoundary::Builtin
|
||||
);
|
||||
assert!(!response.runtime_capabilities.ops_diagnostics.disabled_by_default);
|
||||
assert!(!response.runtime_capabilities.ops_diagnostics.startup_fatal);
|
||||
assert!(response.runtime_capabilities.ops_diagnostics.contract.requires_admin_action);
|
||||
assert!(!response.runtime_capabilities.ops_diagnostics.contract.mutates_object_data);
|
||||
assert!(validate_ops_diagnostics_contract(&response.runtime_capabilities.ops_diagnostics.contract).is_ok());
|
||||
|
||||
assert_eq!(response.runtime_capabilities.ops_profiler.extension_id, "builtin:ops-profiler");
|
||||
assert_eq!(response.runtime_capabilities.ops_profiler.capability.as_str(), OPS_PROFILER_CAPABILITY);
|
||||
assert_eq!(
|
||||
response.runtime_capabilities.ops_profiler.runtime.boundary,
|
||||
ExtensionRuntimeBoundary::Builtin
|
||||
);
|
||||
assert!(!response.runtime_capabilities.ops_profiler.disabled_by_default);
|
||||
assert!(!response.runtime_capabilities.ops_profiler.startup_fatal);
|
||||
assert!(validate_ops_profiler_contract(&response.runtime_capabilities.ops_profiler.contract).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_instance_view_maps_plugin_instance_identity() {
|
||||
let instance = PluginInstanceEntry {
|
||||
|
||||
Reference in New Issue
Block a user