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) |