跳到主内容

v0.12.0 版本说明

EnerOS v0.12.0

发布日期:2024年12月01日 版本代号:Tenant Git Tag:v0.12.0 支持状态:稳定(Stable) Crate 总数:26(新增 4 个) 测试用例数:3580+(新增 480)

版本概述

EnerOS v0.12.0「Tenant」是 EnerOS 面向「多主体共享」场景的关键版本,本版本的核心目标是引入**多租户隔离(Multi-Tenant Isolation)**能力,使单套 EnerOS 集群能够安全承载多个独立电力主体(如省级电网公司、售电公司、虚拟电厂运营商、大型工业用户)的工作负载,且各租户之间在命名空间、资源配额、权限模型与数据访问四个层面实现完全隔离。

在电力市场改革与新型电力系统建设的背景下,越来越多的主体需要共享同一套调度与运维基础设施——例如省级电力交易中心可能同时为 30+ 售电公司提供申报服务,虚拟电厂运营商需要聚合数百家分布式资源。传统方案是为每个主体部署独立集群,存在成本高、资源利用率低、跨主体协同困难等问题。v0.12.0 通过内核级多租户支持,使单集群即可安全承载数十至上百个租户,资源利用率提升 4-6 倍。

本版本引入了 eneros-tenant(租户管理)、eneros-tenant-quota(资源配额)、eneros-tenant-rbac(租户级 RBAC)、eneros-tenant-isolation(数据隔离)四个新 crate。设计哲学是「电力责任边界即租户边界」——租户的划分与电力系统的安全责任区、调度管辖范围、资产所有权严格对齐,确保跨租户操作不会绕过电力安全规程。

关键数据

指标v0.11.0(单租户)v0.12.0(32 租户)说明
单集群租户数11-256硬隔离
租户间延迟干扰N/A< 2%CPU 隔离
资源利用率22%78%共享提升
租户创建耗时N/A120ms热路径
配额校验延迟N/A8μs内核级

新特性

1. 多租户隔离架构

引入 eneros-tenant crate,提供完整的租户生命周期管理。每个租户拥有独立的命名空间(Namespace)、资源配额、权限策略与数据视图,租户之间默认完全隔离,仅可通过显式授权进行受控协作。

租户模型

use eneros_tenant::{Tenant, TenantId, TenantConfig};

// 创建一个省级电网租户
let tenant = Tenant::new(TenantId::from("grid-province-a"))
    .config(TenantConfig {
        display_name: "A省电力公司".into(),
        namespace_prefix: "gpa".into(),
        max_agents: 256,
        max_topology_nodes: 50000,
        max_timeseries_throughput: 1_000_000,  // 点/秒
        storage_quota: 512 * 1024 * 1024 * 1024,  // 512GB
        isolation_level: IsolationLevel::Strict,
    })?;

// 启用租户
tenant.activate().await?;

隔离层级

层级隔离对象实现机制强度
L1 命名空间拓扑/Agent/任务前缀路由逻辑隔离
L2 资源CPU/内存/存储cgroup + 配额硬隔离
L3 权限API/操作RBAC + ABAC强制访问
L4 数据时序/快照/日志行级加密 + 视图加密隔离

2. 命名空间隔离

每个租户拥有独立的命名空间,所有资源(拓扑节点、Agent、任务、时序数据)均归属特定命名空间,跨命名空间访问需显式授权。命名空间通过前缀路由实现,避免全局查找带来的性能开销。

命名空间操作

use eneros_tenant::Namespace;

// 在租户命名空间下创建拓扑
let bus = topology.create_bus(
    Namespace::of("grid-province-a"),
    BusId::from(1),
    BusConfig { voltage_level: 110, base_kv: 110.0 },
)?;

// 跨命名空间访问需要授权
let cross = topology.access(
    Namespace::of("grid-province-a"),
    BusId::from(1),
    AccessScope::Read,
).authorize(&admin_token)?;

// 查询自动限定在当前租户命名空间
let buses = topology.list_buses()
    .in_namespace(Namespace::of("grid-province-a"))
    .execute()?;

命名空间路由表

命名空间前缀租户资源数量路由延迟
gpagrid-province-a482301.2μs
vppvirtual-pp-112400.8μs
indindustrial-user-75600.6μs

3. 资源配额管理

引入 eneros-tenant-quota crate,对每个租户的 CPU、内存、存储、Agent 数量、时序写入吞吐等资源进行精细化配额管理。配额校验在内核热路径完成,延迟低于 10μs。

配额配置

use eneros_tenant_quota::{Quota, QuotaConfig, Resource};

let quota = Quota::for_tenant(TenantId::from("vpp-1"))
    .limit(Resource::Cpu, QuotaConfig::cores(8))
    .limit(Resource::Memory, QuotaConfig::bytes(16 * 1024 * 1024 * 1024))
    .limit(Resource::Agents, QuotaConfig::count(64))
    .limit(Resource::TimeseriesWrite, QuotaConfig::per_second(100_000))
    .limit(Resource::Storage, QuotaConfig::bytes(128 * 1024 * 1024 * 1024))
    .burst_allowance(0.2)  // 允许 20% 突发
    .enforce_mode(EnforceMode::Hard);

配额校验

// 在 Agent 创建时校验配额
match quota.check_and_reserve(Resource::Agents, 1) {
    Ok(reservation) => {
        let agent = Agent::new(...).spawn()?;
        reservation.commit();
    }
    Err(QuotaError::Exceeded { current: 64, limit: 64 }) => {
        return Err("租户 Agent 数量已达上限");
    }
}

资源配额默认值

资源标准租户大型租户小型租户
CPU 核数8322
内存16GB64GB4GB
Agent 数量645128
时序写入10万点/秒100万点/秒1万点/秒
存储空间128GB2TB16GB

4. 租户级 RBAC

引入 eneros-tenant-rbac crate,为每个租户维护独立的角色与权限模型。权限校验同时考虑租户边界与角色边界,防止跨租户越权。

角色定义

use eneros_tenant_rbac::{Role, Permission, Scope};

let dispatcher_role = Role::for_tenant(TenantId::from("grid-province-a"))
    .name("dispatcher")
    .permission(Permission::new("topology:read"))
    .permission(Permission::new("dispatch:command"))
    .permission(Permission::new("agent:manage"))
    .scope(Scope::TenantOnly);  // 仅限本租户

let observer_role = Role::for_tenant(TenantId::from("grid-province-a"))
    .name("observer")
    .permission(Permission::new("topology:read"))
    .permission(Permission::new("timeseries:read"))
    .scope(Scope::TenantOnly);

权限校验

// 校验用户在特定租户下的权限
let ctx = TenantContext::new(user_id, TenantId::from("grid-province-a"));

if ctx.has_permission("dispatch:command")? {
    ctx.syscall(DispatchCommand { ... }).await?;
} else {
    return Err(PermissionDenied);
}

// 跨租户操作需要显式跨租授权
ctx.cross_tenant(TenantId::from("vpp-1"))
    .require_permission("topology:read")?;

5. 数据隔离

引入 eneros-tenant-isolation crate,对时序数据、拓扑快照、审计日志进行行级隔离。每个租户的数据在物理上可共享存储,在逻辑上完全隔离,且支持按租户独立加密。

时序数据隔离

use eneros_tenant_isolation::TenantTimeseries;

// 写入时自动标记租户
let ts = TenantTimeseries::for_tenant(TenantId::from("grid-province-a"));
ts.write(Point::new("bus.1.voltage", 1.024).at(now())).await?;

// 查询时自动过滤
let result = ts.query()
    .metric("bus.1.voltage")
    .range(now() - Duration::minutes(30), now())
    .execute()?;  // 仅返回本租户数据

数据加密策略

数据类型隔离方式加密密钥管理
时序数据租户前缀 + 行级AES-256-GCM每租户独立密钥
拓扑快照命名空间AES-256-GCM每租户独立密钥
审计日志租户前缀AES-256-GCM每租户独立密钥
Agent 状态命名空间可选加密租户自管理

改进

  • API 网关:所有 REST/GraphQL API 自动注入租户上下文,无需应用层处理
  • 审计日志:所有操作自动记录租户标识,支持按租户检索
  • 可观测性:新增 eneros-tenant-metrics 子模块,按租户维度暴露指标
  • 配额告警:支持配置配额使用率阈值,达到阈值自动告警

Bug 修复

  • 修复 eneros-agent 在租户切换时上下文残留的问题(#1208)
  • 修复 eneros-timeseries 跨租户查询在索引未命中时泄露数据的问题(#1215)
  • 修复 eneros-topology 命名空间前缀冲突时未报错的问题(#1221)
  • 修复 eneros-sched 配额校验在任务快速创建时偶发逃逸的问题(#1227)

破坏性变更

  • Agent::new 签名变更:新增 tenant: TenantId 参数,原签名迁移至 Agent::new_standalone
  • Topology::create_bus:首参数改为 Namespace,原 BusId 参数顺序调整
  • Timeseries::query:默认限定当前租户,跨租户查询需使用 query_cross_tenant

性能提升

  • 租户创建耗时从 800ms 降至 120ms(提升 6.7 倍)
  • 配额校验延迟从 45μs 降至 8μs(提升 5.6 倍)
  • 命名空间路由延迟稳定在 1.2μs 以内
  • 单集群可承载租户数从 8 提升至 256

贡献者

本版本由 21 位贡献者共同完成,提交 358 次。特别感谢:

  • @tenant-core:租户模型与生命周期管理
  • @quota-master:内核级配额校验引擎
  • @rbac-arch:租户级 RBAC 权限模型
  • @crypto-isolation:行级数据隔离与加密方案

升级指南

从 v0.11.0 升级

1. 更新依赖

# Cargo.toml
[dependencies]
eneros-tenant = { version = "0.12" }
eneros-tenant-quota = { version = "0.12" }
eneros-tenant-rbac = { version = "0.12" }
eneros-tenant-isolation = { version = "0.12", features = ["encryption"] }

2. 初始化默认租户

升级后需创建默认租户并迁移现有资源:

use eneros_tenant::migrate;

// 自动将 v0.11.0 的所有资源迁移到 "default" 租户
migrate::from_single_tenant(TenantId::from("default")).await?;

3. 适配新 API

// v0.11.0 旧写法
let agent = Agent::new("my-agent").spawn()?;
let bus = topology.create_bus(1, BusConfig { ... })?;

// v0.12.0 新写法
let agent = Agent::new("my-agent", TenantId::from("default")).spawn()?;
let bus = topology.create_bus(
    Namespace::of("default"),
    BusId::from(1),
    BusConfig { ... },
)?;

4. 配置配额

为默认租户配置合理配额,避免单租户耗尽集群资源:

# eneros.toml
[tenant.default]
cpu_cores = 32
memory = "64GB"
max_agents = 512
timeseries_write_per_second = 1000000
storage = "2TB"

5. 数据加密(可选)

如需启用租户级数据加密,需配置密钥管理服务:

[tenant.encryption]
enabled = true
algorithm = "aes-256-gcm"
kms_endpoint = "https://kms.internal:8200"
key_rotation_days = 90