mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 21:26:28 +00:00
refactor: publish ops profiler runtime contract (#3643)
This commit is contained in:
@@ -21,11 +21,14 @@ use crate::manifest::{
|
|||||||
};
|
};
|
||||||
use rustfs_extension_schema::{
|
use rustfs_extension_schema::{
|
||||||
EXTENSION_SCHEMA_VERSION, ExtensionCapabilityRef, ExtensionKind, ExtensionRuntimeBoundary, ExtensionRuntimeContract,
|
EXTENSION_SCHEMA_VERSION, ExtensionCapabilityRef, ExtensionKind, ExtensionRuntimeBoundary, ExtensionRuntimeContract,
|
||||||
ExtensionSchema, OPS_DIAGNOSTICS_CAPABILITY, OpsDiagnosticSurface, OpsDiagnosticsContract, S3_POST_AUTH_HOOK_CAPABILITY,
|
ExtensionSchema, OPS_DIAGNOSTICS_CAPABILITY, OPS_PROFILER_CAPABILITY, OpsDiagnosticSurface, OpsDiagnosticsContract,
|
||||||
S3HookContract, S3HookPoint,
|
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_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 S3_HOOK_EXTENSION_API_VERSION: &str = "rustfs.s3-hook.v1";
|
||||||
pub const TARGET_AUDIT_CAPABILITY: &str = "target.audit.v1";
|
pub const TARGET_AUDIT_CAPABILITY: &str = "target.audit.v1";
|
||||||
pub const TARGET_NOTIFY_CAPABILITY: &str = "target.notify.v1";
|
pub const TARGET_NOTIFY_CAPABILITY: &str = "target.notify.v1";
|
||||||
@@ -34,6 +37,7 @@ pub fn builtin_extension_schemas() -> Vec<ExtensionSchema> {
|
|||||||
let mut schemas = builtin_target_extension_schemas();
|
let mut schemas = builtin_target_extension_schemas();
|
||||||
schemas.push(builtin_s3_hook_extension_schema());
|
schemas.push(builtin_s3_hook_extension_schema());
|
||||||
schemas.push(builtin_ops_diagnostics_extension_schema());
|
schemas.push(builtin_ops_diagnostics_extension_schema());
|
||||||
|
schemas.push(builtin_ops_profiler_extension_schema());
|
||||||
schemas
|
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 {
|
pub fn target_marketplace_extension_schema(manifest: &TargetPluginMarketplaceManifest) -> ExtensionSchema {
|
||||||
let boundary = target_runtime_boundary(manifest.entrypoint_kind);
|
let boundary = target_runtime_boundary(manifest.entrypoint_kind);
|
||||||
|
|
||||||
@@ -159,8 +214,9 @@ fn target_domain_capability(domain: TargetDomain) -> ExtensionCapabilityRef {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
OPS_DIAGNOSTICS_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION, TARGET_AUDIT_CAPABILITY, TARGET_NOTIFY_CAPABILITY,
|
OPS_DIAGNOSTICS_EXTENSION_API_VERSION, OPS_PROFILER_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION,
|
||||||
builtin_extension_schemas, builtin_ops_diagnostics_contract, builtin_ops_diagnostics_extension_schema,
|
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,
|
builtin_s3_hook_contract, builtin_s3_hook_extension_schema, builtin_target_extension_schemas,
|
||||||
target_marketplace_extension_schema,
|
target_marketplace_extension_schema,
|
||||||
};
|
};
|
||||||
@@ -171,9 +227,9 @@ mod tests {
|
|||||||
builtin_target_marketplace_manifest,
|
builtin_target_marketplace_manifest,
|
||||||
};
|
};
|
||||||
use rustfs_extension_schema::{
|
use rustfs_extension_schema::{
|
||||||
EXTENSION_SCHEMA_VERSION, ExtensionKind, ExtensionRuntimeBoundary, OPS_DIAGNOSTICS_CAPABILITY, OpsDiagnosticSurface,
|
EXTENSION_SCHEMA_VERSION, ExtensionKind, ExtensionRuntimeBoundary, OPS_DIAGNOSTICS_CAPABILITY, OPS_PROFILER_CAPABILITY,
|
||||||
S3_POST_AUTH_HOOK_CAPABILITY, S3HookPoint, validate_extension_schemas, validate_ops_diagnostics_contract,
|
OpsDiagnosticSurface, OpsProfilerBackendStatus, S3_POST_AUTH_HOOK_CAPABILITY, S3HookPoint, validate_extension_schemas,
|
||||||
validate_s3_hook_contract,
|
validate_ops_diagnostics_contract, validate_ops_profiler_contract, validate_s3_hook_contract,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -254,14 +310,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 schemas = builtin_extension_schemas();
|
||||||
let mut extension_ids = schemas.iter().map(|schema| schema.extension_id.as_str()).collect::<Vec<_>>();
|
let mut extension_ids = schemas.iter().map(|schema| schema.extension_id.as_str()).collect::<Vec<_>>();
|
||||||
extension_ids.sort_unstable();
|
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:s3-post-auth-hooks"));
|
||||||
assert!(extension_ids.contains(&"builtin:ops-diagnostics"));
|
assert!(extension_ids.contains(&"builtin:ops-diagnostics"));
|
||||||
|
assert!(extension_ids.contains(&"builtin:ops-profiler"));
|
||||||
assert!(validate_extension_schemas(&schemas).is_ok());
|
assert!(validate_extension_schemas(&schemas).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,4 +371,40 @@ mod tests {
|
|||||||
assert!(contract.requires_admin_action);
|
assert!(contract.requires_admin_action);
|
||||||
assert!(validate_ops_diagnostics_contract(&contract).is_ok());
|
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::<Vec<_>>();
|
||||||
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ pub mod sys;
|
|||||||
pub mod target;
|
pub mod target;
|
||||||
|
|
||||||
pub use catalog::extension::{
|
pub use catalog::extension::{
|
||||||
OPS_DIAGNOSTICS_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION, TARGET_AUDIT_CAPABILITY, TARGET_NOTIFY_CAPABILITY,
|
OPS_DIAGNOSTICS_EXTENSION_API_VERSION, OPS_PROFILER_EXTENSION_API_VERSION, S3_HOOK_EXTENSION_API_VERSION,
|
||||||
builtin_extension_schemas, builtin_ops_diagnostics_contract, builtin_ops_diagnostics_extension_schema,
|
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,
|
builtin_s3_hook_contract, builtin_s3_hook_extension_schema, builtin_target_extension_schemas,
|
||||||
target_marketplace_extension_schema, target_runtime_boundary,
|
target_marketplace_extension_schema, target_runtime_boundary,
|
||||||
};
|
};
|
||||||
@@ -71,6 +72,9 @@ pub use runtime::{
|
|||||||
OpsDiagnosticsAccessDecision, OpsDiagnosticsReadRequest, OpsDiagnosticsRegistration, OpsDiagnosticsRegistry,
|
OpsDiagnosticsAccessDecision, OpsDiagnosticsReadRequest, OpsDiagnosticsRegistration, OpsDiagnosticsRegistry,
|
||||||
OpsDiagnosticsRegistryError,
|
OpsDiagnosticsRegistryError,
|
||||||
},
|
},
|
||||||
|
ops_profiler::{
|
||||||
|
OpsProfilerAccessDecision, OpsProfilerReadRequest, OpsProfilerRegistration, OpsProfilerRegistry, OpsProfilerRegistryError,
|
||||||
|
},
|
||||||
s3_hooks::{S3HookContext, S3HookDecision, S3HookRegistration, S3HookRegistry, S3HookRegistryError},
|
s3_hooks::{S3HookContext, S3HookDecision, S3HookRegistration, S3HookRegistry, S3HookRegistryError},
|
||||||
sidecar::{SidecarPluginRuntime, SidecarRuntimePolicy, SidecarRuntimePolicyError, SidecarRuntimeSafetyChecks},
|
sidecar::{SidecarPluginRuntime, SidecarRuntimePolicy, SidecarRuntimePolicyError, SidecarRuntimeSafetyChecks},
|
||||||
sidecar_protocol::{SIDECAR_RUNTIME_PROTOCOL_VERSION, SidecarHandshake, SidecarPluginCapability},
|
sidecar_protocol::{SIDECAR_RUNTIME_PROTOCOL_VERSION, SidecarHandshake, SidecarPluginCapability},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
pub mod adapter;
|
pub mod adapter;
|
||||||
pub mod ops_diagnostics;
|
pub mod ops_diagnostics;
|
||||||
|
pub mod ops_profiler;
|
||||||
pub mod s3_hooks;
|
pub mod s3_hooks;
|
||||||
pub mod sidecar;
|
pub mod sidecar;
|
||||||
pub mod sidecar_protocol;
|
pub mod sidecar_protocol;
|
||||||
|
|||||||
@@ -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<String, OpsProfilerRegistration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,16 +5,16 @@ Status values: `[ ]` not started, `[~]` in progress, `[x]` complete, `[!]` block
|
|||||||
## Current Context
|
## Current Context
|
||||||
|
|
||||||
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
- Issue: [`rustfs/backlog#660`](https://github.com/rustfs/backlog/issues/660)
|
||||||
- Branch: `overtrue/arch-embedded-lifecycle-publication-reuse`
|
- Branch: `overtrue/arch-ops-profiler-runtime-contract`
|
||||||
- Baseline: `overtrue/arch-embedded-runtime-service-reuse`
|
- Baseline: `overtrue/arch-embedded-lifecycle-publication-reuse`
|
||||||
(`00cf2509338ec6acdddc3b5fdf1a02dd52ed309a`).
|
(`e27c079d4c2a577bd9a632a615b2f0b96b468149`).
|
||||||
- Stacked on: rustfs/rustfs#3641.
|
- Stacked on: local R-031, which is stacked on rustfs/rustfs#3641.
|
||||||
- PR type for this branch: `pure-move`
|
- PR type for this branch: `contract`
|
||||||
- Runtime behavior changes: none.
|
- Runtime behavior changes: none.
|
||||||
- Rust code changes: route embedded ready publication, global init-time
|
- Rust code changes: publish the builtin ops profiler catalog contract through
|
||||||
publication, and ready logging through startup lifecycle helpers.
|
targets and add a read-only profiler registry contract.
|
||||||
- CI/script changes: none.
|
- 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
|
## 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
|
migration/layer guards, formatting, diff hygiene, Rust risk scan, branch
|
||||||
freshness check, pre-commit quality gate, and three-expert review.
|
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
|
## Next PRs
|
||||||
|
|
||||||
1. `contract`: continue extension contract coverage for future diagnostics and
|
1. `pure-move`: continue pruning residual embedded startup-only orchestration
|
||||||
profiler handoff surfaces after runtime owners are stable.
|
|
||||||
2. `pure-move`: continue pruning residual embedded startup-only orchestration
|
|
||||||
once the lifecycle helpers are merged.
|
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
|
## Pre-Push Review Log
|
||||||
|
|
||||||
| Expert | Status | Notes |
|
| 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`. |
|
| 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 | Runtime readiness error wrapping, IAM bootstrap disposition handling, global init-time ordering, and endpoint-address normalization remain unchanged. |
|
| Migration preservation | passed | Existing profile collection routes, startup/shutdown hook behavior, redaction requirements, and disabled external-runtime defaults remain unchanged. |
|
||||||
| Testing/verification | passed | Focused embedded/lifecycle checks, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. |
|
| Testing/verification | passed | Focused targets/admin extension checks, guards, formatting, diff hygiene, Rust risk scan, and full pre-commit passed. |
|
||||||
|
|
||||||
## Verification Notes
|
## Verification Notes
|
||||||
|
|
||||||
@@ -2216,6 +2231,22 @@ Passed before push:
|
|||||||
examples use `Box<dyn Error>` / `println!`.
|
examples use `Box<dyn Error>` / `println!`.
|
||||||
- `make pre-commit`: passed.
|
- `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:
|
- Issue #660 X-012 current slice:
|
||||||
- `cargo test -p rustfs-extension-schema`: passed.
|
- `cargo test -p rustfs-extension-schema`: passed.
|
||||||
- `cargo check -p rustfs-extension-schema`: passed.
|
- `cargo check -p rustfs-extension-schema`: passed.
|
||||||
|
|||||||
@@ -287,6 +287,14 @@ mod tests {
|
|||||||
assert_eq!(diagnostics.kind, ExtensionKind::OpsDiagnostics);
|
assert_eq!(diagnostics.kind, ExtensionKind::OpsDiagnostics);
|
||||||
assert_eq!(diagnostics.runtime.boundary, ExtensionRuntimeBoundary::Builtin);
|
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.enabled);
|
||||||
assert!(response.external_plugin_flow.install_requires_signature);
|
assert!(response.external_plugin_flow.install_requires_signature);
|
||||||
assert!(response.external_plugin_flow.install_requires_provenance);
|
assert!(response.external_plugin_flow.install_requires_provenance);
|
||||||
|
|||||||
Reference in New Issue
Block a user