Floci 仿真 AWS BCM Data Exports 服务:CUR 2.0 / FOCUS 1.2 导出管理面完整指南
2026/9/20 20:39:53 网站建设 项目流程

Floci 仿真 AWS BCM Data Exports 服务:CUR 2.0 / FOCUS 1.2 导出管理面完整指南

【免费下载链接】flociLight, fluffy, and always free - The AWS Local Emulator alternative项目地址: https://gitcode.com/gh_mirrors/fl/floci

导读

本文深入讲解 Floci(AWS 本地仿真器)中bcm-data-exports:*服务的完整实现与用法。该服务仿真 AWS Billing and Cost Management Data Exports(CUR 2.0 / FOCUS 1.2 配套)的管理面 API,支持创建、查询、更新、删除导出(Export)及其执行记录(Execution),并通过与 legacycur:*服务共享的 Parquet 发射流水线,在本地 S3 中产出真实的 FOCUS 1.2 列式成本数据文件。读完本文,你将掌握该服务的协议入口、全部 7 个操作、校验规则、执行状态机、发射机制与三种运行模式,并能用 AWS CLI 和 boto3 在本地完整跑通"创建导出 → 生成 Parquet → 轮询执行状态"的实战流程。

服务概览:协议、端点与定位

BCM Data Exports 是 CUR 2.0 / FOCUS 1.2 的配套数据导出服务。Floci 仿真的是它的管理面(management plane),即定义导出任务、查询执行记录的 API,而数据产出则交给与 legacycur:*服务共享的 Parquet 发射引擎。

  • 协议:JSON 1.1
  • 请求头X-Amz-Target: AWSBillingAndCostManagementDataExports.<Action>
  • Endpoint 前缀bcm-data-exports

从源码看,路由分发由 BcmDataExportsJsonHandler.java 完成:它解析X-Amz-Target头中的 action 名,通过 Java 的switch表达式分派到 BcmDataExportsService.java 的对应方法;遇到未知 action 则返回400 UnknownOperationException

关系说明:BCM Data Exports 与 legacycur:*同一底层导出流水线的两个不同管理面。两者共享EmissionEngineFocusRowProjectorParquetEmitter这条发射链路(详见下文"发射机制"一节),区别在于存储键、校验规则与操作命名不同。

支持的 7 个操作

操作说明(含源码行为细节)
CreateExport创建导出;若Name与已有导出重复,返回ValidationException(见 BcmDataExportsService.java 中findByName去重逻辑)
GetExport按 ARN 返回单个导出;ARN 缺失或不存在时返回ResourceNotFoundException
ListExports返回调用账户拥有的全部导出(管理面返回Exports数组,每项为 ExportReference 摘要)
UpdateExport替换既有导出的可变字段;保留ExportArnCreatedAtOwnerAccountId等不可变属性
DeleteExport幂等删除;同一调用内级联删除该导出的全部执行记录,避免孤儿记录
ListExecutions返回某导出的全部执行记录;导出不存在时返回ResourceNotFoundException
GetExecutionExportArn+ExecutionId返回单条执行记录

响应形状要点(从序列化器看)

BcmDataExportsJsonHandler.java 中的序列化逻辑揭示了几个对 SDK 兼容性很关键的细节:

  • CreateExport/UpdateExport只返回ExportArn字符串({"ExportArn": "..."}),与 AWS 实际响应一致;
  • GetExport返回完整的Export对象,其中ExportStatus被包装成ExportStatus.StatusCode+CreatedAt+LastUpdatedAt(时间戳格式化为 ISO-8601);
  • ListExports的每个元素只含ExportArnExportNameExportStatus.StatusCode
  • GetExecution/ListExecutions返回ExecutionIdExportArn以及ExecutionStatus(含StatusCodeCreatedByCreatedAtCompletedAtStatusReason)。

校验规则详解

Floci 对Export各字段的校验集中在 BcmDataExportsService.java 的validateExport/validateDestination/validateRefreshCadence三个方法中,与 AWS 行为对齐:

字段规则违规响应
Export.Name仅允许字母数字 +-_,最长 128 字符ValidationException
Export.DataQuery.QueryStatement必填;自由形式 SQL,Floci 不做解析(导出形状由内置 FOCUS schema 决定)ValidationException
Export.DestinationConfigurations.S3Destination必填;S3Bucket+S3Region为必填ValidationException
S3Destination.S3Bucket还需通过S3DestinationValidation.requireValidBucketName的桶名校验(3–63 字符、小写字母数字/连字符/点号等)ValidationException
S3Destination.S3Prefix可选;非空时须为安全 key 段(requireSafeKeySegmentValidationException
S3OutputConfigurations.FormatPARQUET(CSV 未实现,传TEXT_OR_CSV返回ValidationExceptionValidationException
S3OutputConfigurations.CompressionPARQUETGZIP未实现)ValidationException
S3OutputConfigurations.OverwriteCREATE_NEW_REPORT/OVERWRITE_REPORTValidationException
S3OutputConfigurations.OutputTypeCUSTOMValidationException
RefreshCadence.FrequencySYNCHRONOUS(撰写本文时 AWS 唯一支持的值)ValidationException
Export.RefreshCadence必填ValidationException

源码中这些白名单以Set常量形式定义(BcmDataExportsService.java):ALLOWED_FORMAT = {PARQUET}ALLOWED_COMPRESSION = {PARQUET}ALLOWED_OVERWRITE = {CREATE_NEW_REPORT, OVERWRITE_REPORT}ALLOWED_OUTPUT_TYPE = {CUSTOM}ALLOWED_FREQUENCY = {SYNCHRONOUS}

S3OutputConfigurations是可选的(out != null时才校验其子字段),但一旦提供,非法取值会被直接拒绝——这样设计是为了防止"能持久化但无人能读取"的导出定义(Floci 的发射引擎只写 Parquet)。

存储键与账户隔离

BCM 导出数据全程按账户(account-scoped)隔离,存储键设计如下:

  • 导出(Export):<accountId>::<exportArn>
  • 执行记录(Execution):<accountId>::<exportArn>::<executionId>

实际写入时通过StorageFactory创建两个后端:bcm-exports.json存导出、bcm-executions.json存执行记录(见 BcmDataExportsService.java)。

几个值得注意的账户处理细节:

  • Export模型带有一个不在 AWS 线协议中暴露ownerAccountId字段,用于在后台调度场景下定位正确的账户分区(见 Export.java);
  • 删除导出时,deleteExport会扫描执行存储中所有以exportArn::为前缀的 key 并一并删除(BcmDataExportsService.java),确保不产生孤儿执行记录;
  • 后台每日发射循环通过AccountAwareStorageBackend.scanAllAccounts()+putForAccount跨账户读写,listAllExportsByAccount()ownerAccountId分组返回全部账户的导出。

执行生命周期(Execution Lifecycle)

synchronous模式下,一次成功的CreateExport会产生恰好一条执行记录,其状态流转如下:

INITIATION_IN_PROCESS (在创建导出时记录) | v DELIVERY_SUCCESS 或 DELIVERY_FAILURE

UpdateExport同样会触发状态流转。成功与失败两种终态都能通过GetExecution.Execution.ExecutionStatus观察。

从源码实现看(CurEmissionScheduler.java),执行记录的编排过程是:

  1. recordExecution()先生成 UUIDexecutionId,状态置为INITIATION_IN_PROCESScreatedBy记为USERSCHEDULE
  2. 调用EmissionEngine.emitForCurrentMonth()执行发射;
  3. 成功则completeExecution(success=true)置为DELIVERY_SUCCESS;失败则completeExecution(success=false)置为DELIVERY_FAILURE并把异常信息写入StatusReason

执行模型类 ExportExecution.java 中注释列出的完整状态集合为:INITIATION_IN_PROCESS | QUERY_QUEUED | QUERY_IN_PROCESS | QUERY_FAILURE | DELIVERY_IN_PROCESS | DELIVERY_SUCCESS | DELIVERY_FAILURE,Floci 当前主要使用首、尾状态。

发射机制:与 CUR 共享的 Parquet 流水线

BCM 导出与cur:*共用同一条 Parquet 发射链路,流程如下:

  1. 收集用量行EmissionEngine从所有实现ResourceUsageEnumeratorSPI 的服务收集UsageLine行(该 SPI 与 Cost Explorer 服务文档 引入的机制一致,见 EmissionEngine.java);单个枚举器失败只会告警跳过,不影响整体发射;
  2. 投影为 FOCUS 行FocusRowProjectorUsageLine转换为 FOCUS 1.2 / CUR 2.0 列形状,使用内置的 Pricing snapshot(参见 pricing 文档);
  3. NDJSON 暂存:行序列化为 newline-delimited JSON,上传到floci-cur-staging桶(key 为cur-staging/<reportName>/<runId>.ndjson),避免手工拼接 SQL 转义 tag、描述与资源 ID;
  4. DuckDB 写 Parquetfloci-ducksidecar 执行COPY (SELECT * FROM read_json_auto('<staging>')) TO '<dest>' (FORMAT PARQUET),从 Floci S3 读取暂存对象并直接写回 Floci S3(见 ParquetEmitter.java);
  5. 清理暂存:在finally块中尽力删除暂存对象与 side-effect CSV,避免并发发射累积噪音。

每次发射携带全新的runId(UUID),最终产物位于:

s3://<S3Bucket>/<S3Prefix>/<Name>/<runId>.parquet

由于runId每次不同,并发发射不会互相覆盖。

发射窗口与账户上下文

EmissionEngine.emitForCurrentMonth的发射窗口固定为now所在的自然月(UTC),与 CUR 计费周期一致(EmissionEngine.java)。ParquetEmitter发射前还会对目标桶名做 S3 命名规则校验,并对插值进 SQL 的路径做单引号转义——这是防止通过S3Prefix等字段注入 SQL 的双重安全网。

值得注意的实现细节:floci-duck/execute端点为了兼容 Athena 代码路径总是把主sql字段包装成输出 CSV 的 COPY,因此 Parquet 写入放在setup_sql(原样执行),而主sql只是一个无副作用的SELECT 1 AS ok(见 ParquetEmitter.java)。floci-ducksidecar 与 Athena 一样,在首次发射时懒启动。

FLOCI_SERVICES_BCM_DATA_EXPORTS_EMIT_MODE三种运行模式

行为
synchronous(默认)每次CreateExport/UpdateExport同步发射一次
daily每 24 小时通过共享的 CUR 调度执行器发射一次
off仅管理面,不产生任何发射

调度器实现见 CurEmissionScheduler.java:daily模式在@PostConstruct启动一个单线程的ScheduledExecutorService(首次延迟 5 秒,周期 24 小时),与面向用户的 EventBridge Scheduler 派发器刻意分离。每日循环在请求作用域之外运行,因此会为每个账户激活合成 CDI 请求作用域(runUnderAccount),确保 Java 侧暂存写入与 DuckDB 侧读写落在同一账户分区(CurEmissionScheduler.java)。

CreateExport同步发射失败不会回滚导出创建——handler 捕获异常后告警,导出依然持久化(BcmDataExportsJsonHandler.java),失败情况通过执行记录的状态暴露。

配置项

环境变量默认值说明
FLOCI_SERVICES_BCM_DATA_EXPORTS_ENABLEDtrue启用或禁用该服务
FLOCI_SERVICES_BCM_DATA_EXPORTS_EMIT_MODEsynchronous运行模式(见上表)

对应配置接口为 EmulatorConfig.java 中的BcmDataExportsServiceConfigenabled()默认trueemitMode()默认synchronous,语义与floci.services.cur.emit-mode一致但作用于 BCM 的Export记录。

实战示例

AWS CLI(bash)

export AWS_ENDPOINT_URL=http://localhost:4566 export AWS_DEFAULT_REGION=us-east-1 export AWS_ACCESS_KEY_ID=test export AWS_SECRET_ACCESS_KEY=test # 创建同步导出的 FOCUS 月报 aws bcm-data-exports create-export --export '{ "Name": "focus-monthly", "DataQuery": {"QueryStatement": "SELECT * FROM COST_AND_USAGE_REPORT"}, "DestinationConfigurations": { "S3Destination": { "S3Bucket": "my-billing", "S3Prefix": "focus", "S3Region": "us-east-1", "S3OutputConfigurations": { "Format": "PARQUET", "Compression": "PARQUET", "OutputType": "CUSTOM", "Overwrite": "OVERWRITE_REPORT" } } }, "RefreshCadence": {"Frequency": "SYNCHRONOUS"} }' # 列出本账户所有导出 aws bcm-data-exports list-exports # 查看某导出的执行记录 aws bcm-data-exports list-executions \ --export-arn arn:aws:bcm-data-exports:us-east-1:000000000000:export/focus-monthly

创建成功后,同步发射会在s3://my-billing/focus/focus-monthly/<runId>.parquet留下真实 Parquet 文件,可用aws s3 ls s3://my-billing/focus/focus-monthly/查看产物。

boto3(Python)

import boto3 client = boto3.client( "bcm-data-exports", endpoint_url="http://localhost:4566", region_name="us-east-1", ) resp = client.create_export(Export={ "Name": "focus-monthly", "DataQuery": {"QueryStatement": "SELECT * FROM COST_AND_USAGE_REPORT"}, "DestinationConfigurations": {"S3Destination": { "S3Bucket": "my-billing", "S3Prefix": "focus", "S3Region": "us-east-1", "S3OutputConfigurations": { "Format": "PARQUET", "Compression": "PARQUET", "OutputType": "CUSTOM", "Overwrite": "OVERWRITE_REPORT", }, }}, "RefreshCadence": {"Frequency": "SYNCHRONOUS"}, }) print(resp["ExportArn"])

boto3 走endpoint_url访问 Floci 时,SDK 会使用正确的X-Amz-Target头,create-exportlist-exportslist-executionsget-execution等操作均可直接工作。

不在范围内(Out of Scope)

  • DataQuery.QueryStatement的自定义 SQL 求值:Floci 不解析 SQL,直接按 FOCUS 形状发射数据;查询字符串会被忠实持久化以保证 SDK 往返(round-trip)正常,但对 Parquet 输出没有任何影响;
  • 成本类别(cost categories)、计费视图(billing views)与定价模型覆盖(pricing-model overrides)
  • 超出SYNCHRONOUS的真实RefreshCadence调度:目前仅支持SYNCHRONOUS以及 Floci 内部的daily模式;
  • TEXT_OR_CSV/GZIP输出:发射引擎当前无条件写 Parquet,CSV 与 GZIP 尚未实现。

延伸阅读

  • 服务文档:docs/services/cur.md(共享发射流水线的 legacy 管理面)
  • 管理面实现:BcmDataExportsService.java
  • 协议分发:BcmDataExportsJsonHandler.java
  • 数据模型:Export.java、ExportExecution.java、DataQuery.java、DestinationConfiguration.java、RefreshCadence.java
  • 发射编排:CurEmissionScheduler.java、EmissionEngine.java、ParquetEmitter.java
  • 配置定义:EmulatorConfig.java

【免费下载链接】flociLight, fluffy, and always free - The AWS Local Emulator alternative项目地址: https://gitcode.com/gh_mirrors/fl/floci

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询