Skip to content

Commit 6dfbf27

Browse files
authored
Merge pull request #111 from devsapp/fix-resource-group
feat: support resourceGroupId
2 parents 9551998 + f75d43b commit 6dfbf27

5 files changed

Lines changed: 142 additions & 85 deletions

File tree

‎__tests__/e2e/model/deploy_and_test_model.py‎

Lines changed: 105 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -53,91 +53,92 @@ def deploy_model(model_id: str, region: str = "cn-hangzhou", storage: str = "nas
5353
Returns:
5454
tuple: (部署的URL, 配置文件路径)
5555
"""
56-
# 生成函数名称
57-
# 加个随机数
58-
function_name = f"test-{simple_hash(model_id)}-{secrets.token_hex(4)}"
59-
60-
# 准备请求数据
61-
deploy_data = {
62-
"functionName": function_name,
63-
"region": region,
64-
"authType": "bearer",
65-
"tokenData": token,
66-
}
56+
s_yaml_file = f"s.yaml"
57+
58+
try:
59+
# 生成函数名称
60+
# 加个随机数
61+
function_name = f"test-{simple_hash(model_id)}-{secrets.token_hex(4)}"
62+
63+
# 准备请求数据
64+
deploy_data = {
65+
"functionName": function_name,
66+
"region": region,
67+
"authType": "bearer",
68+
"tokenData": token,
69+
}
70+
71+
# 打印生成的token
72+
print(f"生成的随机token: {token}")
73+
74+
# URL编码模型ID
75+
encoded_model_id = urllib.parse.quote(model_id, safe="")
6776

68-
# 打印生成的token
69-
print(f"生成的随机token: {token}")
77+
# 调用部署接口
78+
model_registry_url = os.getenv("MODEL_REGISTRY_URL", "model-registry.devsapp.cn")
79+
deploy_url = (
80+
f"http://{model_registry_url}/api/v1/models/{encoded_model_id}/deploy-info"
81+
)
82+
print(f"deploy url: {deploy_url}")
83+
print(f"正在部署模型: {model_id}")
84+
print(f"请求URL: {deploy_url}")
85+
print(f"请求数据: {json.dumps(deploy_data, indent=2)}")
7086

71-
# URL编码模型ID
72-
encoded_model_id = urllib.parse.quote(model_id, safe="")
87+
response = requests.post(
88+
deploy_url,
89+
headers={"accept": "application/json", "Content-Type": "application/json"},
90+
data=json.dumps(deploy_data),
91+
)
7392

74-
# 调用部署接口
75-
model_registry_url = os.getenv("MODEL_REGISTRY_URL", "model-registry.devsapp.cn")
76-
deploy_url = (
77-
f"http://{model_registry_url}/api/v1/models/{encoded_model_id}/deploy-info"
78-
)
79-
print(f"deploy url: {deploy_url}")
80-
print(f"正在部署模型: {model_id}")
81-
print(f"请求URL: {deploy_url}")
82-
print(f"请求数据: {json.dumps(deploy_data, indent=2)}")
83-
84-
response = requests.post(
85-
deploy_url,
86-
headers={"accept": "application/json", "Content-Type": "application/json"},
87-
data=json.dumps(deploy_data),
88-
)
93+
if response.status_code != 200:
94+
raise Exception(f"部署请求失败: {response.status_code} - {response.text}")
95+
96+
result = response.json()
97+
print(f"部署响应: {json.dumps(result, indent=2)}")
98+
99+
# 检查响应是否成功
100+
if not result.get("success", False):
101+
raise Exception(f"部署失败: {result.get('error', 'Unknown error')}")
102+
103+
# 提取部署信息
104+
deploy_info = result.get("data", {})
105+
106+
# 使用当前文件夹下的s.yaml内容作为基础配置
107+
with open("s.yaml", "r", encoding="utf-8") as f:
108+
s_yaml = yaml.safe_load(f)
109+
110+
# 更新resources中的props
111+
s_yaml["resources"]["test_func"]["props"] = deploy_info
112+
113+
# 下载到 oss
114+
if storage == "oss":
115+
s_yaml["resources"]["test_func"]["props"].pop("nasConfig", None)
116+
s_yaml["resources"]["test_func"]["props"].pop("vpcConfig", None)
117+
s_yaml["resources"]["test_func"]["props"]["ossMountConfig"] = f"auto|bucketPath={function_name}"
118+
s_yaml["resources"]["test_func"]["props"][
119+
"role"
120+
] = "acs:ram::${config('AccountID')}:role/aliyunfcdefaultrole"
121+
s_yaml["resources"]["test_func"]["props"]["annotations"]["modelConfig"][
122+
"storage"
123+
] = storage
124+
# 修改 customContainerConfig.entrypoint 中的路径
125+
custom_container_config = s_yaml["resources"]["test_func"]["props"].get("customContainerConfig", {})
126+
if "entrypoint" in custom_container_config:
127+
entrypoint = custom_container_config["entrypoint"]
128+
if isinstance(entrypoint, list):
129+
# 遍历 entrypoint 数组,替换包含 /mnt/ 的路径
130+
for i, item in enumerate(entrypoint):
131+
if isinstance(item, str) and "/mnt/" in item and not item.startswith("vllm") and not item.isdigit() and item not in ["--port", "--served-model-name", "--trust-remote-code"]:
132+
entrypoint[i] = f"/mnt/serverless-{region}-d70a9a8a-c817-5ed1-a293-4be0908f0a5"
133+
custom_container_config["entrypoint"] = entrypoint
89134

90-
if response.status_code != 200:
91-
raise Exception(f"部署请求失败: {response.status_code} - {response.text}")
92-
93-
result = response.json()
94-
print(f"部署响应: {json.dumps(result, indent=2)}")
95-
96-
# 检查响应是否成功
97-
if not result.get("success", False):
98-
raise Exception(f"部署失败: {result.get('error', 'Unknown error')}")
99-
100-
# 提取部署信息
101-
deploy_info = result.get("data", {})
102-
103-
# 使用当前文件夹下的s.yaml内容作为基础配置
104-
with open("s.yaml", "r", encoding="utf-8") as f:
105-
s_yaml = yaml.safe_load(f)
106-
107-
# 更新resources中的props
108-
s_yaml["resources"]["test_func"]["props"] = deploy_info
109-
110-
# 下载到 oss
111-
if storage == "oss":
112-
s_yaml["resources"]["test_func"]["props"].pop("nasConfig", None)
113-
s_yaml["resources"]["test_func"]["props"].pop("vpcConfig", None)
114-
s_yaml["resources"]["test_func"]["props"]["ossMountConfig"] = "auto"
115-
s_yaml["resources"]["test_func"]["props"][
116-
"role"
117-
] = "acs:ram::${config('AccountID')}:role/aliyunfcdefaultrole"
118-
s_yaml["resources"]["test_func"]["props"]["annotations"]["modelConfig"][
119-
"storage"
120-
] = storage
121-
# 修改 customContainerConfig.entrypoint 中的路径
122-
custom_container_config = s_yaml["resources"]["test_func"]["props"].get("customContainerConfig", {})
123-
if "entrypoint" in custom_container_config:
124-
entrypoint = custom_container_config["entrypoint"]
125-
if isinstance(entrypoint, list):
126-
# 遍历 entrypoint 数组,替换包含 /mnt/ 的路径
127-
for i, item in enumerate(entrypoint):
128-
if isinstance(item, str) and "/mnt/" in item and not item.startswith("vllm") and not item.isdigit() and item not in ["--port", "--served-model-name", "--trust-remote-code"]:
129-
entrypoint[i] = f"/mnt/serverless-{region}-d70a9a8a-c817-5ed1-a293-4be0908f0a5"
130-
custom_container_config["entrypoint"] = entrypoint
131-
132-
# 保存配置到临时文件
133-
s_yaml_file = f"s.yaml"
134-
with open(s_yaml_file, "w", encoding="utf-8") as f:
135-
yaml.dump(s_yaml, f, default_flow_style=False, allow_unicode=True)
135+
# 保存配置到临时文件
136+
with open(s_yaml_file, "w", encoding="utf-8") as f:
137+
yaml.dump(s_yaml, f, default_flow_style=False, allow_unicode=True)
136138

137-
print(f"使用配置文件部署: {s_yaml_file}")
139+
print(f"使用配置文件部署: {s_yaml_file}")
138140

139-
# 执行部署命令
140-
try:
141+
# 执行部署命令
141142
# 下载模型
142143
print("正在下载模型...")
143144
subprocess.check_call(f"s model download -y -t {s_yaml_file}", shell=True)
@@ -160,6 +161,13 @@ def deploy_model(model_id: str, region: str = "cn-hangzhou", storage: str = "nas
160161
return deploy_url, s_yaml_file
161162

162163
except subprocess.CalledProcessError as e:
164+
print(f"部署过程失败: {e}")
165+
print("即使部署失败,仍将尝试执行清理操作...")
166+
# 即使部署失败,也要尝试清理资源
167+
try:
168+
cleanup_deployment(s_yaml_file)
169+
except Exception as cleanup_error:
170+
print(f"清理操作也失败了: {cleanup_error}")
163171
raise Exception(f"部署过程失败: {e}")
164172
except Exception as e:
165173
raise Exception(f"获取部署信息失败: {e}")
@@ -349,15 +357,19 @@ def cleanup_deployment(s_yaml_file: str):
349357
Args:
350358
s_yaml_file: Serverless Devs 配置文件路径
351359
"""
360+
if not s_yaml_file or not os.path.exists(s_yaml_file):
361+
print(f"配置文件不存在: {s_yaml_file}")
362+
return
363+
352364
try:
353365
print(f"正在清除部署资源: {s_yaml_file}")
354366
# 清除模型
355-
subprocess.check_call(f"s model remove -y -t {s_yaml_file}", shell=True)
367+
print("正在清除模型...")
368+
subprocess.run(f"s model remove -y -t {s_yaml_file}", shell=True, check=False)
356369
# 清除函数
357-
subprocess.check_call(f"s remove -y -t {s_yaml_file} --skip-push", shell=True)
370+
print("正在清除函数...")
371+
subprocess.run(f"s remove -y -t {s_yaml_file} --skip-push", shell=True, check=False)
358372
print("部署资源清除完成!")
359-
except subprocess.CalledProcessError as e:
360-
print(f"清除部署资源失败: {e}")
361373
except Exception as e:
362374
print(f"清除部署资源时发生错误: {e}")
363375

@@ -379,6 +391,8 @@ def main():
379391

380392
args = parser.parse_args()
381393

394+
s_yaml_file = None
395+
382396
try:
383397
if args.cleanup:
384398
# 只执行清除操作
@@ -411,6 +425,14 @@ def main():
411425

412426
except Exception as e:
413427
print(f"错误: {e}")
428+
# 如果启用了自动清理且有配置文件,即使出错也要尝试清理
429+
if args.auto_cleanup and s_yaml_file:
430+
print("尝试执行清理操作...")
431+
try:
432+
cleanup_deployment(s_yaml_file)
433+
print("清理操作完成!")
434+
except Exception as cleanup_error:
435+
print(f"清理操作失败: {cleanup_error}")
414436
return 1
415437

416438
return 0

‎src/interface/function.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export interface IFunction {
137137
sessionAffinityConfig?: ISessionAffinityConfig | string;
138138
instanceIsolationMode?: string;
139139
enableLongLiving?: boolean;
140+
resourceGroupId?: string;
140141

141142
logConfig?: 'auto' | ILogConfig;
142143
nasConfig?: 'auto' | INasConfig;

‎src/resources/fc/index.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
ListSessionsRequest,
2525
UpdateSessionRequest,
2626
UpdateSessionInput,
27+
ChangeResourceGroupRequest,
28+
ChangeResourceGroupInput,
2729
} from '@alicloud/fc20230330';
2830
import { RuntimeOptions } from '@alicloud/tea-util';
2931

@@ -283,6 +285,13 @@ export default class FC extends FC_Client {
283285
}
284286
await this.updateFunction(config);
285287
await this.untilFunctionStateOK(config, 'UPDATE');
288+
if (config.resourceGroupId) {
289+
const remoteResourceGroupId = remoteConfig?.body?.resourceGroupId;
290+
if (remoteResourceGroupId !== config.resourceGroupId) {
291+
logger.info(`Updating resource group for function ${config.functionName}`);
292+
await this.changeFunctionResourceGroup(config.functionName, config.resourceGroupId);
293+
}
294+
}
286295
return;
287296
} catch (ex) {
288297
logger.debug(`Deploy function error: ${ex}`);
@@ -1099,4 +1108,22 @@ export default class FC extends FC_Client {
10991108
}
11001109
}
11011110
}
1111+
1112+
async changeFunctionResourceGroup(functionName: string, resourceGroupId: string) {
1113+
const request = new ChangeResourceGroupRequest({
1114+
body: new ChangeResourceGroupInput({
1115+
newResourceGroupId: resourceGroupId,
1116+
resourceId: functionName,
1117+
resourceType: 'function',
1118+
}),
1119+
});
1120+
const result = await this.fc20230330Client.changeResourceGroup(request);
1121+
const { statusCode } = result.toMap();
1122+
if (statusCode === 200) {
1123+
logger.debug(`changeResourceGroup success`);
1124+
} else {
1125+
logger.error(`changeResourceGroup failed, statusCode: ${statusCode}`);
1126+
}
1127+
return statusCode;
1128+
}
11021129
}

‎src/schema.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,9 @@
13371337
"region": {
13381338
"$ref": "#/definitions/IRegion"
13391339
},
1340+
"resourceGroupId": {
1341+
"type": "string"
1342+
},
13401343
"role": {
13411344
"type": "string"
13421345
},

‎src/subCommands/model/index.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ export class Model {
133133

134134
const { nasConfig, vpcConfig, ossMountConfig } = this.local;
135135
logger.info(
136-
`[Download-model] nasConfig: ${nasConfig} vpcConfig: ${vpcConfig} ossMountConfig: ${ossMountConfig}`,
136+
`[Download-model] nasConfig: ${JSON.stringify(nasConfig)} vpcConfig: ${JSON.stringify(
137+
vpcConfig,
138+
)} ossMountConfig: ${JSON.stringify(ossMountConfig)}`,
137139
);
138140

139141
return this._buildParams(
@@ -161,7 +163,9 @@ export class Model {
161163
}
162164
logger.info(`ossAuto code to ${ossEndpoint}`);
163165
const oss = new OSS(region, credential as ICredentials, ossEndpoint);
164-
const { ossBucket, readOnly, mountDir, bucketPath } = await oss.deploy();
166+
const { ossBucket, readOnly, mountDir, bucketPath } = await oss.deploy(
167+
this.inputs.props.ossMountConfig as string,
168+
);
165169
logger.write(
166170
yellow(`Created oss resource succeeded, please replace ossMountConfig: auto in yaml with:
167171
ossMountConfig:

0 commit comments

Comments
 (0)