forked from wangqifan/calc
387 lines
18 KiB
Python
387 lines
18 KiB
Python
|
|
from fastapi import FastAPI, HTTPException
|
|||
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|||
|
|
from pydantic import BaseModel
|
|||
|
|
from typing import List, Optional, Dict
|
|||
|
|
import boto3
|
|||
|
|
import json
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
import os
|
|||
|
|
from dotenv import load_dotenv
|
|||
|
|
|
|||
|
|
# 加载环境变量
|
|||
|
|
load_dotenv()
|
|||
|
|
|
|||
|
|
app = FastAPI()
|
|||
|
|
|
|||
|
|
# 配置CORS
|
|||
|
|
app.add_middleware(
|
|||
|
|
CORSMiddleware,
|
|||
|
|
allow_origins=["*"],
|
|||
|
|
allow_credentials=True,
|
|||
|
|
allow_methods=["*"],
|
|||
|
|
allow_headers=["*"],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
pricing_ebs = {
|
|||
|
|
"us-east-1": 0.08,
|
|||
|
|
"us-east-2": 0.08,
|
|||
|
|
"us-west-1": 0.096,
|
|||
|
|
"us-west-2": 0.08,
|
|||
|
|
"af-south-1": 0.1047,
|
|||
|
|
"ap-east-1": 0.1056,
|
|||
|
|
"ap-south-1": 0.0912,
|
|||
|
|
"ap-northeast-3": 0.096,
|
|||
|
|
"ap-northeast-2": 0.0912,
|
|||
|
|
"ap-southeast-1": 0.096,
|
|||
|
|
"ap-southeast-2": 0.096,
|
|||
|
|
"ap-northeast-1": 0.096,
|
|||
|
|
"ca-central-1": 0.088,
|
|||
|
|
"eu-central-1": 0.0952,
|
|||
|
|
"eu-west-1": 0.088,
|
|||
|
|
"eu-west-2": 0.0928,
|
|||
|
|
"eu-west-3": 0.0928,
|
|||
|
|
"eu-north-1": 0.0836,
|
|||
|
|
"me-central-1": 0.0968,
|
|||
|
|
"sa-east-1": 0.152,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 数据模型
|
|||
|
|
class PriceRequest(BaseModel):
|
|||
|
|
instance_type: str
|
|||
|
|
region: str
|
|||
|
|
operating_system: str
|
|||
|
|
purchase_option: str
|
|||
|
|
duration: Optional[int] = 1
|
|||
|
|
|
|||
|
|
class PriceComparison(BaseModel):
|
|||
|
|
configurations: List[PriceRequest]
|
|||
|
|
|
|||
|
|
class InstanceSearchRequest(BaseModel):
|
|||
|
|
cpu_cores: Optional[int] = None
|
|||
|
|
memory_gb: Optional[float] = None
|
|||
|
|
disk_gb: Optional[int] = None
|
|||
|
|
region: Optional[str] = None
|
|||
|
|
operating_system: Optional[str] = "Linux"
|
|||
|
|
|
|||
|
|
# EC2实例信息
|
|||
|
|
instance_info = {
|
|||
|
|
# T2 系列 - 入门级通用型
|
|||
|
|
"t2.nano": {"cpu": 1, "memory": 0.5, "description": "入门级通用型实例,适合轻量级工作负载"},
|
|||
|
|
"t2.micro": {"cpu": 1, "memory": 1, "description": "具成本效益的入门级实例"},
|
|||
|
|
"t2.small": {"cpu": 1, "memory": 2, "description": "低成本通用实例"},
|
|||
|
|
"t2.medium": {"cpu": 2, "memory": 4, "description": "中等负载通用实例"},
|
|||
|
|
"t2.large": {"cpu": 2, "memory": 8, "description": "大型通用实例"},
|
|||
|
|
"t2.xlarge": {"cpu": 4, "memory": 16, "description": "超大型通用实例"},
|
|||
|
|
"t2.2xlarge": {"cpu": 8, "memory": 32, "description": "双倍超大型通用实例"},
|
|||
|
|
|
|||
|
|
# T3 系列 - 新一代通用型
|
|||
|
|
"t3.nano": {"cpu": 1, "memory": 0.5, "description": "新一代入门级通用型实例"},
|
|||
|
|
"t3.micro": {"cpu": 1, "memory": 1, "description": "新一代低成本通用实例"},
|
|||
|
|
"t3.small": {"cpu": 1, "memory": 2, "description": "新一代小型通用实例"},
|
|||
|
|
"t3.medium": {"cpu": 2, "memory": 4, "description": "新一代中等负载通用实例"},
|
|||
|
|
"t3.large": {"cpu": 2, "memory": 8, "description": "新一代大型通用实例"},
|
|||
|
|
"t3.xlarge": {"cpu": 4, "memory": 16, "description": "新一代超大型通用实例"},
|
|||
|
|
"t3.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代双倍超大型通用实例"},
|
|||
|
|
|
|||
|
|
# T3a 系列 - 新一代通用型
|
|||
|
|
"t3a.nano": {"cpu": 1, "memory": 0.5, "description": "新一代入门级通用型实例"},
|
|||
|
|
"t3a.micro": {"cpu": 1, "memory": 1, "description": "新一代低成本通用实例"},
|
|||
|
|
"t3a.small": {"cpu": 1, "memory": 2, "description": "新一代小型通用实例"},
|
|||
|
|
"t3a.medium": {"cpu": 2, "memory": 4, "description": "新一代中等负载通用实例"},
|
|||
|
|
"t3a.large": {"cpu": 2, "memory": 8, "description": "新一代大型通用实例"},
|
|||
|
|
"t3a.xlarge": {"cpu": 4, "memory": 16, "description": "新一代超大型通用实例"},
|
|||
|
|
"t3a.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代双倍超大型通用实例"},
|
|||
|
|
|
|||
|
|
# C5 系列 - 计算优化型
|
|||
|
|
"c5.large": {"cpu": 2, "memory": 4, "description": "计算优化实例"},
|
|||
|
|
"c5.xlarge": {"cpu": 4, "memory": 8, "description": "高性能计算优化实例"},
|
|||
|
|
"c5.2xlarge": {"cpu": 8, "memory": 16, "description": "大规模计算优化实例"},
|
|||
|
|
"c5.4xlarge": {"cpu": 16, "memory": 32, "description": "超大规模计算优化实例"},
|
|||
|
|
"c5.9xlarge": {"cpu": 36, "memory": 72, "description": "高性能计算优化实例"},
|
|||
|
|
"c5.12xlarge": {"cpu": 48, "memory": 96, "description": "大规模计算优化实例"},
|
|||
|
|
"c5.18xlarge": {"cpu": 72, "memory": 144, "description": "超大规模计算优化实例"},
|
|||
|
|
"c5.24xlarge": {"cpu": 96, "memory": 192, "description": "最大规模计算优化实例"},
|
|||
|
|
|
|||
|
|
|
|||
|
|
# c6a 系列 - 新一代计算优化型
|
|||
|
|
"c6a.large": {"cpu": 2, "memory": 4, "description": "新一代计算优化实例"},
|
|||
|
|
"c6a.xlarge": {"cpu": 4, "memory": 8, "description": "新一代高性能计算优化实例"},
|
|||
|
|
"c6a.2xlarge": {"cpu": 8, "memory": 16, "description": "新一代大规模计算优化实例"},
|
|||
|
|
"c6a.4xlarge": {"cpu": 16, "memory": 32, "description": "新一代超大规模计算优化实例"},
|
|||
|
|
"c6a.8xlarge": {"cpu": 32, "memory": 64, "description": "新一代高性能计算优化实例"},
|
|||
|
|
"c6a.12xlarge": {"cpu": 48, "memory": 96, "description": "新一代大规模计算优化实例"},
|
|||
|
|
"c6a.16xlarge": {"cpu": 64, "memory": 128, "description": "新一代超大规模计算优化实例"},
|
|||
|
|
"c6a.24xlarge": {"cpu": 96, "memory": 192, "description": "新一代最大规模计算优化实例"},
|
|||
|
|
"c6a.32xlarge": {"cpu": 128, "memory": 256, "description": "新一代最大规模计算优化实例"},
|
|||
|
|
# R5 系列 - 内存优化型
|
|||
|
|
"r5.large": {"cpu": 2, "memory": 16, "description": "内存优化实例"},
|
|||
|
|
"r5.xlarge": {"cpu": 4, "memory": 32, "description": "高性能内存优化实例"},
|
|||
|
|
"r5.2xlarge": {"cpu": 8, "memory": 64, "description": "大规模内存优化实例"},
|
|||
|
|
"r5.4xlarge": {"cpu": 16, "memory": 128, "description": "超大规模内存优化实例"},
|
|||
|
|
"r5.8xlarge": {"cpu": 32, "memory": 256, "description": "高性能内存优化实例"},
|
|||
|
|
"r5.12xlarge": {"cpu": 48, "memory": 384, "description": "大规模内存优化实例"},
|
|||
|
|
"r5.16xlarge": {"cpu": 64, "memory": 512, "description": "超大规模内存优化实例"},
|
|||
|
|
"r5.24xlarge": {"cpu": 96, "memory": 768, "description": "最大规模内存优化实例"},
|
|||
|
|
|
|||
|
|
# R6a 系列 - 新一代内存优化型
|
|||
|
|
"r6a.large": {"cpu": 2, "memory": 16, "description": "新一代内存优化实例"},
|
|||
|
|
"r6a.xlarge": {"cpu": 4, "memory": 32, "description": "新一代高性能内存优化实例"},
|
|||
|
|
"r6a.2xlarge": {"cpu": 8, "memory": 64, "description": "新一代大规模内存优化实例"},
|
|||
|
|
"r6a.4xlarge": {"cpu": 16, "memory": 128, "description": "新一代超大规模内存优化实例"},
|
|||
|
|
"r6a.8xlarge": {"cpu": 32, "memory": 256, "description": "新一代高性能内存优化实例"},
|
|||
|
|
"r6a.12xlarge": {"cpu": 48, "memory": 384, "description": "新一代大规模内存优化实例"},
|
|||
|
|
"r6a.16xlarge": {"cpu": 64, "memory": 512, "description": "新一代超大规模内存优化实例"},
|
|||
|
|
"r6a.24xlarge": {"cpu": 96, "memory": 768, "description": "新一代最大规模内存优化实例"},
|
|||
|
|
"r6a.32xlarge": {"cpu": 128, "memory": 1024, "description": "新一代最大规模内存优化实例"},
|
|||
|
|
|
|||
|
|
# M5 系列 - 通用型
|
|||
|
|
"m5.large": {"cpu": 2, "memory": 8, "description": "平衡型计算和内存实例"},
|
|||
|
|
"m5.xlarge": {"cpu": 4, "memory": 16, "description": "高性能平衡型实例"},
|
|||
|
|
"m5.2xlarge": {"cpu": 8, "memory": 32, "description": "大规模工作负载平衡型实例"},
|
|||
|
|
"m5.4xlarge": {"cpu": 16, "memory": 64, "description": "超大规模工作负载平衡型实例"},
|
|||
|
|
"m5.8xlarge": {"cpu": 32, "memory": 128, "description": "高性能工作负载平衡型实例"},
|
|||
|
|
"m5.12xlarge": {"cpu": 48, "memory": 192, "description": "大规模工作负载平衡型实例"},
|
|||
|
|
"m5.16xlarge": {"cpu": 64, "memory": 256, "description": "超大规模工作负载平衡型实例"},
|
|||
|
|
"m5.24xlarge": {"cpu": 96, "memory": 384, "description": "最大规模工作负载平衡型实例"},
|
|||
|
|
|
|||
|
|
# M6a 系列 - 新一代通用型
|
|||
|
|
"m6a.large": {"cpu": 2, "memory": 8, "description": "新一代平衡型计算和内存实例"},
|
|||
|
|
"m6a.xlarge": {"cpu": 4, "memory": 16, "description": "新一代高性能平衡型实例"},
|
|||
|
|
"m6a.2xlarge": {"cpu": 8, "memory": 32, "description": "新一代大规模工作负载平衡型实例"},
|
|||
|
|
"m6a.4xlarge": {"cpu": 16, "memory": 64, "description": "新一代超大规模工作负载平衡型实例"},
|
|||
|
|
"m6a.8xlarge": {"cpu": 32, "memory": 128, "description": "新一代高性能工作负载平衡型实例"},
|
|||
|
|
"m6a.12xlarge": {"cpu": 48, "memory": 192, "description": "新一代大规模工作负载平衡型实例"},
|
|||
|
|
"m6a.16xlarge": {"cpu": 64, "memory": 256, "description": "新一代超大规模工作负载平衡型实例"},
|
|||
|
|
"m6a.24xlarge": {"cpu": 96, "memory": 384, "description": "新一代最大规模工作负载平衡型实例"},
|
|||
|
|
"m6a.32xlarge": {"cpu": 128, "memory": 512, "description": "新一代最大规模工作负载平衡型实例"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 区域中文名称映射
|
|||
|
|
region_names: Dict[str, str] = {
|
|||
|
|
"us-east-1": "美国东部 (弗吉尼亚北部)",
|
|||
|
|
"us-east-2": "美国东部 (俄亥俄)",
|
|||
|
|
"us-west-1": "美国西部 (加利福尼亚北部)",
|
|||
|
|
"us-west-2": "美国西部 (俄勒冈)",
|
|||
|
|
"ap-south-1": "亚太地区 (孟买)",
|
|||
|
|
"ap-east-1": "亚太地区 (香港)",
|
|||
|
|
"ap-northeast-1": "亚太地区 (东京)",
|
|||
|
|
"ap-northeast-2": "亚太地区 (首尔)",
|
|||
|
|
"ap-southeast-1": "亚太地区 (新加坡)",
|
|||
|
|
"ap-southeast-2": "亚太地区 (悉尼)",
|
|||
|
|
"ca-central-1": "加拿大 (中部)",
|
|||
|
|
"eu-central-1": "欧洲 (法兰克福)",
|
|||
|
|
"eu-west-1": "欧洲 (爱尔兰)",
|
|||
|
|
"eu-west-2": "欧洲 (伦敦)",
|
|||
|
|
"eu-west-3": "欧洲 (巴黎)",
|
|||
|
|
"sa-east-1": "南美洲 (圣保罗)",
|
|||
|
|
"me-central-1": "中东 (阿联酋)",
|
|||
|
|
"eu-north-1": "欧洲 (斯德哥尔摩)",
|
|||
|
|
"eu-west-4": "欧洲 (比利时)",
|
|||
|
|
"eu-south-1": "欧洲 (米兰)",
|
|||
|
|
"eu-west-5": "欧洲 (阿姆斯特丹)",
|
|||
|
|
"eu-west-6": "欧洲 (华沙)",
|
|||
|
|
"eu-west-7": "欧洲 (伦敦)",
|
|||
|
|
"eu-west-8": "欧洲 (米兰)",
|
|||
|
|
"eu-west-9": "欧洲 (马德里)",
|
|||
|
|
"eu-west-10": "欧洲 (巴黎)",
|
|||
|
|
"eu-west-11": "欧洲 (阿姆斯特丹)",
|
|||
|
|
"eu-west-12": "欧洲 (米兰)",
|
|||
|
|
"eu-west-13": "欧洲 (米兰)",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 获取EC2价格
|
|||
|
|
@app.get("/api/regions")
|
|||
|
|
async def get_regions():
|
|||
|
|
# 从环境变量获取区域,如果未设置则使用默认区域
|
|||
|
|
region = os.environ.get('AWS_DEFAULT_REGION', 'us-east-1')
|
|||
|
|
ec2 = boto3.client('ec2', region_name=region)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
regions_response = ec2.describe_regions()
|
|||
|
|
regions = [region['RegionName'] for region in regions_response['Regions']]
|
|||
|
|
|
|||
|
|
# 创建包含中文名称的结果
|
|||
|
|
result = []
|
|||
|
|
for region_code in regions:
|
|||
|
|
region_info = {
|
|||
|
|
"code": region_code,
|
|||
|
|
"name": region_names.get(region_code, f"未知区域 ({region_code})")
|
|||
|
|
}
|
|||
|
|
result.append(region_info)
|
|||
|
|
|
|||
|
|
# 按照区域名称排序
|
|||
|
|
result.sort(key=lambda x: x["name"])
|
|||
|
|
return result
|
|||
|
|
except Exception as e:
|
|||
|
|
# 如果API调用失败,返回常用区域列表
|
|||
|
|
print(f"Error fetching regions: {str(e)}")
|
|||
|
|
result = []
|
|||
|
|
for region_code in [
|
|||
|
|
"us-east-1", "us-east-2", "us-west-1", "us-west-2",
|
|||
|
|
"ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-southeast-1",
|
|||
|
|
"ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1",
|
|||
|
|
"eu-west-2", "eu-west-3", "sa-east-1"
|
|||
|
|
]:
|
|||
|
|
region_info = {
|
|||
|
|
"code": region_code,
|
|||
|
|
"name": region_names.get(region_code, f"未知区域 ({region_code})")
|
|||
|
|
}
|
|||
|
|
result.append(region_info)
|
|||
|
|
|
|||
|
|
# 按照区域名称排序
|
|||
|
|
result.sort(key=lambda x: x["name"])
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
@app.get("/api/instance-types")
|
|||
|
|
async def get_instance_types():
|
|||
|
|
# 返回所有实例类型和它们的详细信息
|
|||
|
|
return instance_info
|
|||
|
|
|
|||
|
|
@app.post("/api/search-instances")
|
|||
|
|
async def search_instances(request: InstanceSearchRequest):
|
|||
|
|
try:
|
|||
|
|
matching_instances = []
|
|||
|
|
print(f"request: {request}")
|
|||
|
|
if request.cpu_cores is None:
|
|||
|
|
raise HTTPException(status_code=500, detail=str("cpu_cores is required"))
|
|||
|
|
if request.memory_gb is None:
|
|||
|
|
raise HTTPException(status_code=500, detail=str("memory_gb is required"))
|
|||
|
|
if request.disk_gb is None:
|
|||
|
|
raise HTTPException(status_code=500, detail=str("disk_gb is required"))
|
|||
|
|
if request.region is None:
|
|||
|
|
raise HTTPException(status_code=500, detail=str("region is required"))
|
|||
|
|
# 遍历所有实例类型
|
|||
|
|
for instance_type, info in instance_info.items():
|
|||
|
|
try:
|
|||
|
|
# 检查是否满足CPU要求(严格匹配)
|
|||
|
|
if request.cpu_cores and info['cpu'] != request.cpu_cores:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
# 检查是否满足内存要求(严格匹配)
|
|||
|
|
if request.memory_gb and info['memory'] != request.memory_gb:
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
print(f"instance_type: {instance_type}")
|
|||
|
|
# 计算价格
|
|||
|
|
price_info = await calculate_price(
|
|||
|
|
instance_type=instance_type,
|
|||
|
|
region=request.region,
|
|||
|
|
disk_gb=request.disk_gb,
|
|||
|
|
operating_system=request.operating_system
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 添加到匹配列表
|
|||
|
|
matching_instances.append({
|
|||
|
|
"instance_type": instance_type,
|
|||
|
|
"description": info['description'],
|
|||
|
|
"cpu": info['cpu'],
|
|||
|
|
"memory": info['memory'],
|
|||
|
|
"disk_gb": request.disk_gb,
|
|||
|
|
"hourly_price": price_info['hourly_price'],
|
|||
|
|
"monthly_price": price_info['monthly_price'],
|
|||
|
|
"disk_monthly_price": price_info['disk_monthly_price'],
|
|||
|
|
"total_monthly_price": price_info['total_monthly_price']
|
|||
|
|
})
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error calculating price: {str(e)}")
|
|||
|
|
|
|||
|
|
# 按总价格排序
|
|||
|
|
matching_instances.sort(key=lambda x: x['total_monthly_price'])
|
|||
|
|
|
|||
|
|
return matching_instances
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error searching instances: {str(e)}")
|
|||
|
|
# raise HTTPException(status_code=500, detail=str(e))
|
|||
|
|
|
|||
|
|
async def calculate_price(instance_type: str, region: str, disk_gb: int, operating_system: str = "Linux"):
|
|||
|
|
print(f"operating_system: {operating_system}")
|
|||
|
|
try:
|
|||
|
|
# 创建Pricing API客户端
|
|||
|
|
pricing_client = boto3.client('pricing', region_name='us-east-1')
|
|||
|
|
|
|||
|
|
# 构建基础过滤器
|
|||
|
|
filters = [
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'instanceType', 'Value': instance_type},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'regionCode', 'Value': region},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'productFamily', 'Value': 'Compute Instance'},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'serviceCode', 'Value': 'AmazonEC2'},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'tenancy', 'Value': 'Shared'},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'operatingSystem', 'Value': operating_system},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'preInstalledSw', 'Value': 'NA'},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'termType', 'Value': 'OnDemand'},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'capacitystatus', 'Value': 'Used'},
|
|||
|
|
{'Type': 'TERM_MATCH', 'Field': 'currentGeneration', 'Value': 'Yes'}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 根据操作系统设置不同的许可证模型
|
|||
|
|
# if operating_system == "Windows":
|
|||
|
|
# filters.append({'Type': 'TERM_MATCH', 'Field': 'licenseModel', 'Value': 'Windows'})
|
|||
|
|
# else:
|
|||
|
|
# filters.append({'Type': 'TERM_MATCH', 'Field': 'licenseModel', 'Value': 'No License required'})
|
|||
|
|
|
|||
|
|
# 获取实例价格
|
|||
|
|
response = pricing_client.get_products(
|
|||
|
|
ServiceCode='AmazonEC2',
|
|||
|
|
Filters=filters,
|
|||
|
|
MaxResults=1
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if not response['PriceList']:
|
|||
|
|
raise Exception(f"未找到实例 {instance_type} 的价格信息")
|
|||
|
|
|
|||
|
|
price_list = json.loads(response['PriceList'][0])
|
|||
|
|
terms = price_list['terms']['OnDemand']
|
|||
|
|
price_dimensions = list(terms.values())[0]['priceDimensions']
|
|||
|
|
price_per_hour = float(list(price_dimensions.values())[0]['pricePerUnit']['USD'])
|
|||
|
|
print(f"price_per_hour: {price_per_hour}")
|
|||
|
|
# 计算GP3存储价格
|
|||
|
|
storage_price_per_gb = calculate_gp3_price(region)
|
|||
|
|
disk_monthly_price = storage_price_per_gb * disk_gb
|
|||
|
|
|
|||
|
|
# 计算每月价格
|
|||
|
|
monthly_price = price_per_hour * 730
|
|||
|
|
total_monthly_price = monthly_price + disk_monthly_price
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"hourly_price": price_per_hour,
|
|||
|
|
"monthly_price": monthly_price,
|
|||
|
|
"disk_monthly_price": disk_monthly_price,
|
|||
|
|
"total_monthly_price": total_monthly_price
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error calculating price: {str(e)}")
|
|||
|
|
# raise HTTPException(status_code=500, detail=str(e))
|
|||
|
|
|
|||
|
|
# 添加计算 GP3 存储价格的函数
|
|||
|
|
def calculate_gp3_price(region: str) -> float:
|
|||
|
|
if region in pricing_ebs.keys():
|
|||
|
|
price_dimensions = pricing_ebs[region]
|
|||
|
|
else:
|
|||
|
|
price_dimensions = 0.1
|
|||
|
|
|
|||
|
|
return price_dimensions
|
|||
|
|
|
|||
|
|
@app.post("/api/compare-prices")
|
|||
|
|
async def compare_prices(comparison: PriceComparison):
|
|||
|
|
try:
|
|||
|
|
results = []
|
|||
|
|
for config in comparison.configurations:
|
|||
|
|
price = await calculate_price(
|
|||
|
|
config.instance_type,
|
|||
|
|
config.region,
|
|||
|
|
config.disk_gb,
|
|||
|
|
config.operating_system
|
|||
|
|
)
|
|||
|
|
results.append({
|
|||
|
|
"configuration": config.dict(),
|
|||
|
|
"price": price
|
|||
|
|
})
|
|||
|
|
return results
|
|||
|
|
except Exception as e:
|
|||
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
import uvicorn
|
|||
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|