From 5ad47d135400e2fad2d6ef5131f7f2cbf05aafbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Sat, 20 Jun 2026 09:42:13 +0800 Subject: [PATCH] refactor: publish ops profiler runtime contract (#3643) --- crates/targets/src/catalog/extension.rs | 111 +++++++++- crates/targets/src/lib.rs | 8 +- crates/targets/src/runtime/mod.rs | 1 + crates/targets/src/runtime/ops_profiler.rs | 236 +++++++++++++++++++++ docs/architecture/migration-progress.md | 59 ++++-- rustfs/src/admin/handlers/extensions.rs | 8 + 6 files changed, 398 insertions(+), 25 deletions(-) create mode 100644 crates/targets/src/runtime/ops_profiler.rs diff --git a/crates/targets/src/catalog/extension.rs b/crates/targets/src/catalog/extension.rs index 8c4f2e948..219f24f6c 100644 --- a/crates/targets/src/catalog/extension.rs +++ b/crates/targets/src/catalog/extension.rs @@ -21,11 +21,14 @@ use crate::manifest::{ }; use rustfs_extension_schema::{ EXTENSION_SCHEMA_VERSION, ExtensionCapabilityRef, ExtensionKind, ExtensionRuntimeBoundary, ExtensionRuntimeContract, - ExtensionSchema, OPS_DIAGNOSTICS_CAPABILITY, OpsDiagnosticSurface, OpsDiagnosticsContract, S3_POST_AUTH_HOOK_CAPABILITY, - S3HookContract, S3HookPoint, + ExtensionSchema, OPS_DIAGNOSTICS_CAPABILITY, OPS_PROFILER_CAPABILITY, OpsDiagnosticSurface, OpsDiagnosticsContract, + OpsProfilerBackendCapability, OpsProfilerBackendName, OpsProfilerBackendStatus, OpsProfilerContract, OpsProfilerContractMode, + OpsProfilerProvenance, OpsProfilerRedactionField, OpsProfilerTrustLevel, S3_POST_AUTH_HOOK_CAPABILITY, S3HookContract, + S3HookPoint, }; pub const OPS_DIAGNOSTICS_EXTENSION_API_VERSION: &str = "rustfs.ops-diagnostics.v1"; +pub const OPS_PROFILER_EXTENSION_API_VERSION: &str = "rustfs.ops-profiler.v1"; pub const S3_HOOK_EXTENSION_API_VERSION: &str = "rustfs.s3-hook.v1"; pub const TARGET_AUDIT_CAPABILITY: &str = "target.audit.v1"; pub const TARGET_NOTIFY_CAPABILITY: &str = "target.notify.v1"; @@ -34,6 +37,7 @@ pub fn builtin_extension_schemas() -> Vec { let mut schemas = builtin_target_extension_schemas(); schemas.push(builtin_s3_hook_extension_schema()); schemas.push(builtin_ops_diagnostics_extension_schema()); + schemas.push(builtin_ops_profiler_extension_schema()); schemas } @@ -98,6 +102,57 @@ pub fn builtin_ops_diagnostics_contract() -> OpsDiagnosticsContract { } } +pub fn builtin_ops_profiler_extension_schema() -> ExtensionSchema { + ExtensionSchema { + schema_version: EXTENSION_SCHEMA_VERSION.to_string(), + extension_id: "builtin:ops-profiler".to_string(), + display_name: "Ops Profiler".to_string(), + provider: "rustfs".to_string(), + version: "1.0.0".to_string(), + kind: ExtensionKind::OpsProfiler, + runtime: ExtensionRuntimeContract { + api_version: OPS_PROFILER_EXTENSION_API_VERSION.to_string(), + boundary: ExtensionRuntimeBoundary::Builtin, + }, + capabilities: vec![ExtensionCapabilityRef::new(OPS_PROFILER_CAPABILITY)], + disabled_by_default: false, + } +} + +pub fn builtin_ops_profiler_contract() -> OpsProfilerContract { + OpsProfilerContract { + mode: OpsProfilerContractMode::CapabilityDescription, + backends: vec![ + builtin_ops_profiler_backend("cpu_pprof", OpsProfilerBackendStatus::Enabled, true), + builtin_ops_profiler_backend("memory_pprof", OpsProfilerBackendStatus::Disabled, true), + builtin_ops_profiler_backend("ebpf", OpsProfilerBackendStatus::Unsupported, false), + ], + } +} + +fn builtin_ops_profiler_backend( + backend: &str, + status: OpsProfilerBackendStatus, + supports_profile_export: bool, +) -> OpsProfilerBackendCapability { + OpsProfilerBackendCapability { + backend: OpsProfilerBackendName::new(backend), + status, + supports_profile_export, + redaction_required: vec![ + OpsProfilerRedactionField::Secret, + OpsProfilerRedactionField::Token, + OpsProfilerRedactionField::LocalPath, + OpsProfilerRedactionField::Host, + ], + provenance: OpsProfilerProvenance { + source: "rustfs.profiling".to_string(), + collection_boundary: "rustfs-process".to_string(), + trust_level: OpsProfilerTrustLevel::RuntimeTrusted, + }, + } +} + pub fn target_marketplace_extension_schema(manifest: &TargetPluginMarketplaceManifest) -> ExtensionSchema { let boundary = target_runtime_boundary(manifest.entrypoint_kind); @@ -159,8 +214,9 @@ fn target_domain_capability(domain: TargetDomain) -> ExtensionCapabilityRef { #[cfg(test)] mod tests { use super::{ - OPS_DIAGNOSTICS_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION, TARGET_AUDIT_CAPABILITY, TARGET_NOTIFY_CAPABILITY, - builtin_extension_schemas, builtin_ops_diagnostics_contract, builtin_ops_diagnostics_extension_schema, + OPS_DIAGNOSTICS_EXTENSION_API_VERSION, OPS_PROFILER_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION, + TARGET_AUDIT_CAPABILITY, TARGET_NOTIFY_CAPABILITY, builtin_extension_schemas, builtin_ops_diagnostics_contract, + builtin_ops_diagnostics_extension_schema, builtin_ops_profiler_contract, builtin_ops_profiler_extension_schema, builtin_s3_hook_contract, builtin_s3_hook_extension_schema, builtin_target_extension_schemas, target_marketplace_extension_schema, }; @@ -171,9 +227,9 @@ mod tests { builtin_target_marketplace_manifest, }; use rustfs_extension_schema::{ - EXTENSION_SCHEMA_VERSION, ExtensionKind, ExtensionRuntimeBoundary, OPS_DIAGNOSTICS_CAPABILITY, OpsDiagnosticSurface, - S3_POST_AUTH_HOOK_CAPABILITY, S3HookPoint, validate_extension_schemas, validate_ops_diagnostics_contract, - validate_s3_hook_contract, + EXTENSION_SCHEMA_VERSION, ExtensionKind, ExtensionRuntimeBoundary, OPS_DIAGNOSTICS_CAPABILITY, OPS_PROFILER_CAPABILITY, + OpsDiagnosticSurface, OpsProfilerBackendStatus, S3_POST_AUTH_HOOK_CAPABILITY, S3HookPoint, validate_extension_schemas, + validate_ops_diagnostics_contract, validate_ops_profiler_contract, validate_s3_hook_contract, }; #[test] @@ -254,14 +310,15 @@ mod tests { } #[test] - fn builtin_extension_catalog_includes_hooks_and_ops_diagnostics() { + fn builtin_extension_catalog_includes_hooks_and_ops_capabilities() { let schemas = builtin_extension_schemas(); let mut extension_ids = schemas.iter().map(|schema| schema.extension_id.as_str()).collect::>(); extension_ids.sort_unstable(); - assert_eq!(schemas.len(), 11); + assert_eq!(schemas.len(), 12); assert!(extension_ids.contains(&"builtin:s3-post-auth-hooks")); assert!(extension_ids.contains(&"builtin:ops-diagnostics")); + assert!(extension_ids.contains(&"builtin:ops-profiler")); assert!(validate_extension_schemas(&schemas).is_ok()); } @@ -314,4 +371,40 @@ mod tests { assert!(contract.requires_admin_action); assert!(validate_ops_diagnostics_contract(&contract).is_ok()); } + + #[test] + fn builtin_ops_profiler_schema_describes_read_only_profile_capabilities() { + let schema = builtin_ops_profiler_extension_schema(); + let contract = builtin_ops_profiler_contract(); + + assert_eq!(schema.kind, ExtensionKind::OpsProfiler); + assert_eq!(schema.runtime.boundary, ExtensionRuntimeBoundary::Builtin); + assert_eq!(schema.runtime.api_version, OPS_PROFILER_EXTENSION_API_VERSION); + assert_eq!(schema.capabilities[0].as_str(), OPS_PROFILER_CAPABILITY); + assert!(!schema.disabled_by_default); + + let backends = contract + .backends + .iter() + .map(|backend| (backend.backend.as_str(), backend.status, backend.supports_profile_export)) + .collect::>(); + assert_eq!( + backends, + vec![ + ("cpu_pprof", OpsProfilerBackendStatus::Enabled, true), + ("memory_pprof", OpsProfilerBackendStatus::Disabled, true), + ("ebpf", OpsProfilerBackendStatus::Unsupported, false), + ] + ); + assert!( + contract + .backends + .iter() + .filter(|backend| backend.supports_profile_export) + .all(|backend| backend + .redaction_required + .contains(&rustfs_extension_schema::OpsProfilerRedactionField::LocalPath)) + ); + assert!(validate_ops_profiler_contract(&contract).is_ok()); + } } diff --git a/crates/targets/src/lib.rs b/crates/targets/src/lib.rs index 1a7a77521..04f000c58 100644 --- a/crates/targets/src/lib.rs +++ b/crates/targets/src/lib.rs @@ -28,8 +28,9 @@ pub mod sys; pub mod target; pub use catalog::extension::{ - OPS_DIAGNOSTICS_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION, TARGET_AUDIT_CAPABILITY, TARGET_NOTIFY_CAPABILITY, - builtin_extension_schemas, builtin_ops_diagnostics_contract, builtin_ops_diagnostics_extension_schema, + OPS_DIAGNOSTICS_EXTENSION_API_VERSION, OPS_PROFILER_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION, + TARGET_AUDIT_CAPABILITY, TARGET_NOTIFY_CAPABILITY, builtin_extension_schemas, builtin_ops_diagnostics_contract, + builtin_ops_diagnostics_extension_schema, builtin_ops_profiler_contract, builtin_ops_profiler_extension_schema, builtin_s3_hook_contract, builtin_s3_hook_extension_schema, builtin_target_extension_schemas, target_marketplace_extension_schema, target_runtime_boundary, }; @@ -71,6 +72,9 @@ pub use runtime::{ OpsDiagnosticsAccessDecision, OpsDiagnosticsReadRequest, OpsDiagnosticsRegistration, OpsDiagnosticsRegistry, OpsDiagnosticsRegistryError, }, + ops_profiler::{ + OpsProfilerAccessDecision, OpsProfilerReadRequest, OpsProfilerRegistration, OpsProfilerRegistry, OpsProfilerRegistryError, + }, s3_hooks::{S3HookContext, S3HookDecision, S3HookRegistration, S3HookRegistry, S3HookRegistryError}, sidecar::{SidecarPluginRuntime, SidecarRuntimePolicy, SidecarRuntimePolicyError, SidecarRuntimeSafetyChecks}, sidecar_protocol::{SIDECAR_RUNTIME_PROTOCOL_VERSION, SidecarHandshake, SidecarPluginCapability}, diff --git a/crates/targets/src/runtime/mod.rs b/crates/targets/src/runtime/mod.rs index a8df698f6..4dce1f831 100644 --- a/crates/targets/src/runtime/mod.rs +++ b/crates/targets/src/runtime/mod.rs @@ -14,6 +14,7 @@ pub mod adapter; pub mod ops_diagnostics; +pub mod ops_profiler; pub mod s3_hooks; pub mod sidecar; pub mod sidecar_protocol; diff --git a/crates/targets/src/runtime/ops_profiler.rs b/crates/targets/src/runtime/ops_profiler.rs new file mode 100644 index 000000000..11eae712a --- /dev/null +++ b/crates/targets/src/runtime/ops_profiler.rs @@ -0,0 +1,236 @@ +// Copyright 2024 RustFS Team +// +// Licensed 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. + +use std::collections::BTreeMap; + +use rustfs_extension_schema::{ + ExtensionCapabilityRef, ExtensionContractError, ExtensionKind, ExtensionSchema, OPS_PROFILER_CAPABILITY, + OpsProfilerBackendStatus, OpsProfilerCapabilitySnapshot, OpsProfilerContract, OpsProfilerRuntimeSnapshot, + validate_ops_profiler_capability_snapshot, +}; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum OpsProfilerRegistryError { + #[error(transparent)] + InvalidContract(#[from] ExtensionContractError), + + #[error("extension {extension_id} is {kind:?}, not an ops profiler extension")] + UnsupportedExtensionKind { extension_id: String, kind: ExtensionKind }, + + #[error("ops profiler extension {extension_id} is missing capability {capability}")] + MissingCapability { extension_id: String, capability: &'static str }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OpsProfilerRegistration { + pub extension_id: String, + pub backend: String, + pub status: OpsProfilerBackendStatus, + pub supports_profile_export: bool, +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct OpsProfilerRegistry { + registrations: BTreeMap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OpsProfilerReadRequest<'a> { + pub backend: &'a str, + pub capability: &'a str, + pub admin_action_authorized: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OpsProfilerAccessDecision { + AllowReadOnly, + DenyMissingAdminAction, + DenyMissingCapability, + DenyUnknownBackend, +} + +impl OpsProfilerRegistry { + pub fn new() -> Self { + Self::default() + } + + pub fn register_schema( + &mut self, + schema: &ExtensionSchema, + contract: &OpsProfilerContract, + ) -> Result<(), OpsProfilerRegistryError> { + if schema.kind != ExtensionKind::OpsProfiler { + return Err(OpsProfilerRegistryError::UnsupportedExtensionKind { + extension_id: schema.extension_id.clone(), + kind: schema.kind, + }); + } + + if !schema + .capabilities + .iter() + .any(|capability| capability.as_str() == OPS_PROFILER_CAPABILITY) + { + return Err(OpsProfilerRegistryError::MissingCapability { + extension_id: schema.extension_id.clone(), + capability: OPS_PROFILER_CAPABILITY, + }); + } + + validate_ops_profiler_capability_snapshot(&OpsProfilerCapabilitySnapshot { + capability: ExtensionCapabilityRef::new(OPS_PROFILER_CAPABILITY), + runtime: OpsProfilerRuntimeSnapshot { + boundary: schema.runtime.boundary, + disabled_by_default: schema.disabled_by_default, + startup_fatal: false, + }, + contract: contract.clone(), + })?; + + for backend in &contract.backends { + self.registrations.insert( + backend.backend.as_str().to_string(), + OpsProfilerRegistration { + extension_id: schema.extension_id.clone(), + backend: backend.backend.as_str().to_string(), + status: backend.status, + supports_profile_export: backend.supports_profile_export, + }, + ); + } + + Ok(()) + } + + pub fn registered_backend_count(&self) -> usize { + self.registrations.len() + } + + pub fn registration_for(&self, backend: &str) -> Option<&OpsProfilerRegistration> { + self.registrations.get(backend) + } + + pub fn authorize_read(&self, request: OpsProfilerReadRequest<'_>) -> OpsProfilerAccessDecision { + if !self.registrations.contains_key(request.backend) { + return OpsProfilerAccessDecision::DenyUnknownBackend; + } + + if !request.admin_action_authorized { + return OpsProfilerAccessDecision::DenyMissingAdminAction; + } + + if request.capability != OPS_PROFILER_CAPABILITY { + return OpsProfilerAccessDecision::DenyMissingCapability; + } + + OpsProfilerAccessDecision::AllowReadOnly + } +} + +#[cfg(test)] +mod tests { + use super::{OpsProfilerAccessDecision, OpsProfilerReadRequest, OpsProfilerRegistry, OpsProfilerRegistryError}; + use crate::{builtin_ops_profiler_contract, builtin_ops_profiler_extension_schema}; + use rustfs_extension_schema::{ExtensionContractError, ExtensionKind, OPS_PROFILER_CAPABILITY, OpsProfilerBackendStatus}; + + #[test] + fn default_registry_denies_unknown_profiler_backend() { + let registry = OpsProfilerRegistry::new(); + + assert_eq!( + registry.authorize_read(OpsProfilerReadRequest { + backend: "cpu_pprof", + capability: OPS_PROFILER_CAPABILITY, + admin_action_authorized: true, + }), + OpsProfilerAccessDecision::DenyUnknownBackend + ); + } + + #[test] + fn registered_ops_profiler_backends_are_read_only_and_capability_limited() { + let mut registry = OpsProfilerRegistry::new(); + let schema = builtin_ops_profiler_extension_schema(); + let contract = builtin_ops_profiler_contract(); + + registry + .register_schema(&schema, &contract) + .expect("builtin ops profiler contract should register"); + + assert_eq!(registry.registered_backend_count(), contract.backends.len()); + assert_eq!( + registry.registration_for("cpu_pprof").map(|registration| registration.status), + Some(OpsProfilerBackendStatus::Enabled) + ); + assert_eq!( + registry + .registration_for("memory_pprof") + .map(|registration| registration.supports_profile_export), + Some(true) + ); + assert_eq!( + registry.authorize_read(OpsProfilerReadRequest { + backend: "cpu_pprof", + capability: OPS_PROFILER_CAPABILITY, + admin_action_authorized: true, + }), + OpsProfilerAccessDecision::AllowReadOnly + ); + assert_eq!( + registry.authorize_read(OpsProfilerReadRequest { + backend: "cpu_pprof", + capability: "ops.diagnostics.v1", + admin_action_authorized: true, + }), + OpsProfilerAccessDecision::DenyMissingCapability + ); + assert_eq!( + registry.authorize_read(OpsProfilerReadRequest { + backend: "cpu_pprof", + capability: OPS_PROFILER_CAPABILITY, + admin_action_authorized: false, + }), + OpsProfilerAccessDecision::DenyMissingAdminAction + ); + } + + #[test] + fn rejects_non_profiler_schema_and_invalid_runtime_boundaries() { + let mut registry = OpsProfilerRegistry::new(); + let mut schema = builtin_ops_profiler_extension_schema(); + schema.kind = ExtensionKind::TargetPlugin; + + assert_eq!( + registry + .register_schema(&schema, &builtin_ops_profiler_contract()) + .expect_err("target plugin schema should not register as ops profiler"), + OpsProfilerRegistryError::UnsupportedExtensionKind { + extension_id: "builtin:ops-profiler".to_string(), + kind: ExtensionKind::TargetPlugin + } + ); + + schema.kind = ExtensionKind::OpsProfiler; + schema.runtime.boundary = rustfs_extension_schema::ExtensionRuntimeBoundary::Sidecar; + schema.disabled_by_default = false; + + assert_eq!( + registry + .register_schema(&schema, &builtin_ops_profiler_contract()) + .expect_err("external profiler runtimes must be disabled by default"), + OpsProfilerRegistryError::InvalidContract(ExtensionContractError::OpsProfilerExternalRuntimeEnabledByDefault) + ); + } +} diff --git a/docs/architecture/migration-progress.md b/docs/architecture/migration-progress.md index b6905cb8a..1addc0812 100644 --- a/docs/architecture/migration-progress.md +++ b/docs/architecture/migration-progress.md @@ -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-embedded-lifecycle-publication-reuse` -- Baseline: `overtrue/arch-embedded-runtime-service-reuse` - (`00cf2509338ec6acdddc3b5fdf1a02dd52ed309a`). -- Stacked on: rustfs/rustfs#3641. -- PR type for this branch: `pure-move` +- 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. +- PR type for this branch: `contract` - Runtime behavior changes: none. -- Rust code changes: route embedded ready publication, global init-time - publication, and ready logging through startup lifecycle helpers. +- Rust code changes: publish the builtin ops profiler catalog contract through + targets and add a read-only profiler registry contract. - CI/script changes: none. -- Docs changes: record the R-031 embedded lifecycle publication reuse slice. +- Docs changes: record the R-032 ops profiler runtime contract slice. ## Phase 0 Tasks @@ -2183,20 +2183,35 @@ 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-032` Publish ops profiler runtime contract boundaries. + - Do: add the builtin ops profiler extension schema/contract to the targets + catalog, expose it through the admin extension catalog, and add a read-only + registry for profiler backend capability descriptions. + - Acceptance: the catalog advertises `builtin:ops-profiler` with + `ops.profiler.v1`, backend capability descriptions validate through the + extension-schema contract, and registry access is admin/capability limited + without executing profiler collection. + - Must preserve: existing `/debug/pprof/*` admin behavior, profiling startup + and shutdown hooks, disabled external profiler runtime defaults, local path + redaction requirements, and no plugin execution or sidecar startup. + - Verification: focused targets/admin extension 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. `contract`: continue extension contract coverage for future diagnostics and - profiler handoff surfaces after runtime owners are stable. -2. `pure-move`: continue pruning residual embedded startup-only orchestration +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. ## Pre-Push Review Log | Expert | Status | Notes | |---|---|---| -| Quality/architecture | passed | R-031 keeps embedded server-handle ownership in `embedded.rs` while moving readiness/global-init/ready-log publication into `startup_lifecycle`. | -| Migration preservation | passed | Runtime readiness error wrapping, IAM bootstrap disposition handling, global init-time ordering, and endpoint-address normalization remain unchanged. | -| Testing/verification | passed | Focused embedded/lifecycle checks, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. | +| 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. | ## Verification Notes @@ -2216,6 +2231,22 @@ Passed before push: examples use `Box` / `println!`. - `make pre-commit`: passed. +- Issue #660 R-032 current slice: + - `cargo test -p rustfs-targets ops_profiler -- --nocapture`: passed. + - `cargo test -p rustfs-targets builtin_ops_profiler -- --nocapture`: + passed. + - `cargo test -p rustfs --lib extension_catalog -- --nocapture`: passed. + - `cargo check -p rustfs-targets`: 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. diff --git a/rustfs/src/admin/handlers/extensions.rs b/rustfs/src/admin/handlers/extensions.rs index c42725cc5..27e28fc82 100644 --- a/rustfs/src/admin/handlers/extensions.rs +++ b/rustfs/src/admin/handlers/extensions.rs @@ -287,6 +287,14 @@ mod tests { assert_eq!(diagnostics.kind, ExtensionKind::OpsDiagnostics); assert_eq!(diagnostics.runtime.boundary, ExtensionRuntimeBoundary::Builtin); + let profiler = response + .extensions + .iter() + .find(|schema| schema.extension_id == "builtin:ops-profiler") + .expect("builtin ops profiler extension should be present"); + assert_eq!(profiler.kind, ExtensionKind::OpsProfiler); + assert_eq!(profiler.runtime.boundary, ExtensionRuntimeBoundary::Builtin); + assert!(!response.external_plugin_flow.enabled); assert!(response.external_plugin_flow.install_requires_signature); assert!(response.external_plugin_flow.install_requires_provenance);