161 lines
6.7 KiB
Python
161 lines
6.7 KiB
Python
# -*- 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)
|