Files
iAOP/core/data-bus/minio_lifecycle.py
T

161 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""MinIO 对象存储桶 + 生命周期策略模板化 —— issue #31 / PRD 5.2。
在 #4(EPIC)templating 的 MinIO 命名(桶/对象键)之上,交付**完整的
MinIO 对象存储组件**:
- **桶**:`{template}-artifacts`(S3 桶名允许 `-`,无需 SQL 清洗),
对象键沿用 templating:特征快照 `features/{model_id}/{date}/{seq}.jsonl`、
模型 artifact `models/{model_id}/{version}/model.bin`;
- **生命周期策略**:按前缀配置对象过期天数(S3 LifecycleConfiguration 兼容,
MinIO 支持 JSON 形式),如特征快照保留 180 天、模型 artifact 长期保留,
与数据保留/归档策略(#33)联动,避免对象无限堆积;
- **落地方式**:只产出**配置文本**(`mc mb` / `mc ilm` 命令 + 生命周期
策略 JSON),不依赖 minio SDK,供模板配置台预览 / 运维执行;
- **换行业只改配置**:模板 YAML(`config/minio.template.yaml`)驱动,
内核代码零改动。
验收口径(issue #31 / EPIC #4):
- 桶命名模板化(`{template}-{suffix}`),配置可覆盖;
- 生命周期规则按前缀声明(ID / 前缀 / 过期天数),生成可执行命令与策略 JSON;
- 缺省配置覆盖特征快照 + 模型 artifact 两类对象。
"""
from __future__ import annotations
import json
import os
from typing import Dict, List, Optional
from data_bus.templating import TemplateNaming
#: 默认配置资产路径(相对本模块)
DEFAULT_CONFIG_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "config", "minio.template.yaml")
def _normalize_rules(lifecycle: Optional[List[dict]]) -> List[dict]:
"""规范化生命周期规则:过滤非法行,缺省 ID 自动生成。
每条规则形如 ``{"id": ..., "prefix": ..., "expiration_days": ...}``;
- prefix 必填(生命周期按对象键前缀匹配);
- expiration_days 为过期天数(≥ 1);
- 缺省 id 用前缀派生(保证 S3 Rule ID 唯一且稳定)。
"""
rules: List[dict] = []
for r in (lifecycle or []):
prefix = str(r.get("prefix") or "").strip()
days = int(r.get("expiration_days") or 0)
if not prefix or days < 1:
continue
rule_id = str(r.get("id") or "").strip() or f"{prefix}expire"
rules.append({
"id": rule_id,
"prefix": prefix,
"expiration_days": days,
})
return rules
class MinioLifecycle:
"""MinIO 桶 + 生命周期策略模板化(模板 + 前缀维度)。"""
def __init__(
self,
template: str,
bucket_suffix: str = "artifacts",
lifecycle: Optional[List[dict]] = None,
mc_alias: str = "local",
) -> None:
"""
Args:
template: 行业模板名(如 ti-cl4 / resin);
bucket_suffix: 桶后缀(缺省 artifacts → `{template}-artifacts`);
lifecycle: 生命周期规则 `[{"prefix": "features/", "expiration_days": 180}]`;
mc_alias: `mc` 命令的远端别名(联调 local / 生产 cluster)。
"""
self._tpl = TemplateNaming(template, bucket_suffix=bucket_suffix)
self.bucket_suffix = self._tpl.bucket_suffix
self.mc_alias = mc_alias
self._rules = _normalize_rules(lifecycle)
# ------------------------------------------------------------------
@classmethod
def from_template_config(cls, path: str = DEFAULT_CONFIG_PATH) -> "MinioLifecycle":
"""从模板配置资产加载(config/minio.template.yaml)。"""
import yaml
with open(path, "r", encoding="utf-8") as fh:
raw = yaml.safe_load(fh) or {}
m = raw.get("minio", {}) or {}
return cls(
template=str(raw.get("template", "default")),
bucket_suffix=str(m.get("bucket_suffix", "artifacts")),
lifecycle=m.get("lifecycle", []),
mc_alias=str(m.get("mc_alias", "local")),
)
# ------------------------------------------------------------------
# 命名:桶 / 对象键(复用 templating,与 #4 命名一致)
# ------------------------------------------------------------------
def bucket(self) -> str:
"""对象存储桶:`{template}-{bucket_suffix}`(S3 桶名允许 `-`)。"""
return self._tpl.bucket()
def snapshot_key(self, model_id: str, date: str, seq: int) -> str:
"""特征快照对象键:`features/{model_id}/{date}/{seq:06d}.jsonl`。"""
return self._tpl.snapshot_key(model_id, date, seq)
def model_artifact_key(self, model_id: str, version: str) -> str:
"""模型 artifact 对象键:`models/{model_id}/{version}/model.bin`。"""
return self._tpl.model_artifact_key(model_id, version)
# ------------------------------------------------------------------
# 落地:mc 命令 + 生命周期策略 JSON(只产配置文本,不依赖 SDK)
# ------------------------------------------------------------------
def bucket_create_statements(self) -> List[str]:
"""桶创建命令(`mc mb`,--ignore-existing 幂等)。"""
return [f"mc mb --ignore-existing {self.mc_alias}/{self.bucket()}"]
def lifecycle_cli_statements(self) -> List[str]:
"""生命周期规则落地命令(`mc ilm add`,幂等可重跑)。
示例:`mc ilm add --expiry-days 180 --prefix "features/" local/ti-cl4-artifacts`
"""
bucket = self.bucket()
return [
f'mc ilm add --expiry-days {r["expiration_days"]} '
f'--prefix "{r["prefix"]}" {self.mc_alias}/{bucket}'
for r in self._rules
]
def lifecycle_policy_json(self, indent: int = 2) -> str:
"""S3 LifecycleConfiguration 兼容的 JSON 策略(MinIO 支持)。
返回结构:``{"Rules": [{"ID", "Status", "Filter": {"Prefix"},
"Expiration": {"Days"}}]}``,可直接用于
``PutBucketLifecycleConfiguration`` 或配置台预览。
"""
rules = [
{
"ID": r["id"],
"Status": "Enabled",
"Filter": {"Prefix": r["prefix"]},
"Expiration": {"Days": r["expiration_days"]},
}
for r in self._rules
]
return json.dumps({"Rules": rules}, indent=indent, ensure_ascii=False)
def brief(self) -> dict:
"""配置摘要(部署/巡检用)。"""
return {
"template": self._tpl.template,
"bucket": self.bucket(),
"mc_alias": self.mc_alias,
"lifecycle_rules": list(self._rules),
}
@property
def rules(self) -> List[dict]:
"""规范化后的生命周期规则(只读视图)。"""
return list(self._rules)