From fce20c53c2a78f7d6b7a82eb46a11ee47fcc7c25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E9=AD=8F=E6=B4=AA?= Date: Wed, 21 Jan 2026 19:35:57 +0800 Subject: [PATCH] feat: add upgrade model support in fileManager remove operation - Add upgrade property to modelConfig for handling upgrade models - Implement _removeUpgradeSingleFile method to handle upgrade file removal - Refactor remove operation to support both regular and upgrade files - Improve error logging order in retryFileManagerRm function - Consolidate remove promises into a single execution list Co-authored-by: Qwen-Coder --- __tests__/ut/commands/artModelService_test.ts | 191 +++++----- __tests__/ut/commands/model_test.ts | 2 + __tests__/ut/commands/model_utils_test.ts | 345 ++++++++++++++++++ publish.yaml | 2 +- src/subCommands/model/fileManager.ts | 106 ++++-- src/subCommands/model/index.ts | 1 + src/subCommands/model/utils/index.ts | 6 +- 7 files changed, 520 insertions(+), 133 deletions(-) diff --git a/__tests__/ut/commands/artModelService_test.ts b/__tests__/ut/commands/artModelService_test.ts index 87dba4d..e00d770 100644 --- a/__tests__/ut/commands/artModelService_test.ts +++ b/__tests__/ut/commands/artModelService_test.ts @@ -2,7 +2,7 @@ import { ArtModelService } from '../../../src/subCommands/model/fileManager'; import { IInputs } from '../../../src/interface'; import DevClient from '@alicloud/devs20230714'; import { sleep } from '../../../src/utils'; -import { initClient, checkModelStatus } from '../../../src/subCommands/model/utils'; +import { initClient } from '../../../src/subCommands/model/utils'; // Mock dependencies jest.mock('../../../src/logger', () => { @@ -33,11 +33,13 @@ jest.mock('../../../src/utils'); jest.mock('../../../src/subCommands/model/utils', () => { const originalModule = jest.requireActual('../../../src/subCommands/model/utils'); const mockRetryFileManagerRsyncAndCheckStatus = jest.fn(); + const mockRetryFileManagerRm = jest.fn(); return { __esModule: true, ...originalModule, retryWithFileManager: jest.fn((command, fn) => fn()), retryFileManagerRsyncAndCheckStatus: mockRetryFileManagerRsyncAndCheckStatus, + retryFileManagerRm: mockRetryFileManagerRm, initClient: jest.fn(), checkModelStatus: jest.fn(), extractOssMountDir: jest.fn(), @@ -81,6 +83,13 @@ describe('ArtModelService', () => { }; artModelService = new ArtModelService(mockInputs); + // 添加 createResource 属性以防止错误 + Object.defineProperty(artModelService, 'createResource', { + value: { history: [], oss: {}, nas: {}, vpc: {}, sls: {} }, + writable: true, + enumerable: true, + configurable: true, + }); }); afterEach(() => { @@ -203,8 +212,8 @@ describe('ArtModelService', () => { }, ], }, - storage: 'nas', nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, @@ -218,21 +227,19 @@ describe('ArtModelService', () => { }, } as any); - mockDevClient.fileManagerRsync.mockResolvedValue({ - body: { - success: true, - data: { - taskID: 'task-123', - }, - requestId: 'req-123', - }, - } as any); - - // ArtModelService内部会调用checkModelStatus,所以我们需要模拟它 - (checkModelStatus as jest.Mock).mockResolvedValue(undefined); + // 现在使用重试函数,我们需要模拟重试函数成功执行 + ( + require('../../../src/subCommands/model/utils') + .retryFileManagerRsyncAndCheckStatus as jest.Mock + ).mockResolvedValue(undefined); // 成功下载应该正常完成而不抛出异常 await expect(artModelService.downloadModel(name, params)).resolves.toBeUndefined(); + + // 验证重试函数被调用 + expect( + require('../../../src/subCommands/model/utils').retryFileManagerRsyncAndCheckStatus, + ).toHaveBeenCalled(); }); it('should handle download error from fileManagerRsync', async () => { @@ -253,8 +260,8 @@ describe('ArtModelService', () => { }, ], }, - storage: 'nas', nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, @@ -297,8 +304,8 @@ describe('ArtModelService', () => { ], timeout: 10, // 设置较小的超时值以便测试 }, - storage: 'nas', nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, @@ -312,30 +319,6 @@ describe('ArtModelService', () => { }, } as any); - mockDevClient.fileManagerRsync.mockResolvedValue({ - body: { - success: true, - data: { - taskID: 'task-123', - }, - requestId: 'req-123', - }, - } as any); - - // 模拟超时情况 - 任务永远不会完成 - mockDevClient.getFileManagerTask.mockResolvedValue({ - body: { - data: { - finished: false, - startTime: Date.now() - 50 * 60 * 1000, // 50分钟前开始 - progress: { - currentBytes: 512, - totalBytes: 1024, - }, - }, - }, - } as any); - // 现在使用重试函数,我们需要模拟重试函数抛出超时错误 ( require('../../../src/subCommands/model/utils') @@ -364,8 +347,8 @@ describe('ArtModelService', () => { }, ], }, - storage: 'nas', nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, @@ -379,16 +362,6 @@ describe('ArtModelService', () => { }, } as any); - mockDevClient.fileManagerRsync.mockResolvedValue({ - body: { - success: true, - data: { - taskID: 'task-123', - }, - requestId: 'req-123', - }, - } as any); - // 现在使用重试函数,我们需要模拟重试函数抛出错误 ( require('../../../src/subCommands/model/utils') @@ -442,8 +415,8 @@ describe('ArtModelService', () => { ], conflictResolution: 'overwrite', // 这个值应该被环境变量覆盖 }, - storage: 'nas', nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, @@ -461,16 +434,6 @@ describe('ArtModelService', () => { }, } as any); - mockDevClient.fileManagerRsync.mockResolvedValue({ - body: { - success: true, - data: { - taskID: 'task-123', - }, - requestId: 'req-123', - }, - } as any); - // 现在使用重试函数,我们需要模拟重试函数成功执行 ( require('../../../src/subCommands/model/utils') @@ -518,31 +481,16 @@ describe('ArtModelService', () => { ], }, nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, }; - mockDevClient.fileManagerRm.mockResolvedValue({ - body: { - success: true, - data: { - taskID: 'task-123', - }, - requestId: 'req-123', - }, - } as any); - - // 模拟 getFileManagerTask 返回成功状态 - mockDevClient.getFileManagerTask.mockResolvedValue({ - body: { - data: { - finished: true, - success: true, - }, - requestId: 'req-456', - }, - } as any); + // 现在使用重试函数,我们需要模拟重试函数成功执行 + ( + require('../../../src/subCommands/model/utils').retryFileManagerRm as jest.Mock + ).mockResolvedValue({ success: true, fileName: 'file1.txt' }); // 添加 removeFileManagerTasks 的模拟 mockDevClient.removeFileManagerTasks.mockResolvedValue({ @@ -555,6 +503,9 @@ describe('ArtModelService', () => { // 成功移除应该正常完成 await expect(artModelService.removeModel(name, params)).resolves.toBeUndefined(); + + // 验证重试函数被调用 + expect(require('../../../src/subCommands/model/utils').retryFileManagerRm).toHaveBeenCalled(); }); it('should handle remove failure', async () => { @@ -572,35 +523,66 @@ describe('ArtModelService', () => { ], }, nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], role: 'acs:ram::123456789:role/aliyundevsdefaultrole', region: 'cn-hangzhou', vpcConfig: {}, }; - mockDevClient.fileManagerRm.mockResolvedValue({ - body: { - success: true, - data: { - taskID: 'task-123', + // 现在使用重试函数,我们需要模拟重试函数返回失败 + ( + require('../../../src/subCommands/model/utils').retryFileManagerRm as jest.Mock + ).mockResolvedValue({ success: false, fileName: 'file1.txt', error: 'Remove failed' }); + + // 移除失败应该抛出异常 + await expect(artModelService.removeModel(name, params)).rejects.toThrow(); + }); + + it('should handle remove with upgrade files', async () => { + const name = 'test-project$test-env$test-function'; + const params = { + modelConfig: { + target: { + uri: 'nas://auto', + }, + files: [ + { + source: { path: 'file1.txt' }, + target: { path: 'file1.txt' }, + }, + ], + upgrade: { + history: { + 'v1.0': '/mnt/test/v1.0/file1.txt', + }, }, - requestId: 'req-123', }, - } as any); + nasMountPoints: [{ mountDir: '/mnt/test' }], + ossMountPoints: [{ mountDir: '/mnt/oss' }], + role: 'acs:ram::123456789:role/aliyundevsdefaultrole', + region: 'cn-hangzhou', + vpcConfig: {}, + }; - // 模拟 getFileManagerTask 返回错误状态 - mockDevClient.getFileManagerTask.mockResolvedValue({ + // 现在使用重试函数,我们需要模拟重试函数成功执行 + ( + require('../../../src/subCommands/model/utils').retryFileManagerRm as jest.Mock + ).mockResolvedValue({ success: true, fileName: 'file1.txt' }); + + // 添加 removeFileManagerTasks 的模拟 + mockDevClient.removeFileManagerTasks.mockResolvedValue({ body: { - data: { - finished: true, - success: false, - errorMessage: 'Remove failed', - }, - requestId: 'req-456', + success: true, + data: {}, + requestId: 'req-999', }, } as any); - // 移除失败应该抛出异常 - await expect(artModelService.removeModel(name, params)).rejects.toThrow(); + // 成功移除应该正常完成 + await expect(artModelService.removeModel(name, params)).resolves.toBeUndefined(); + + // 验证重试函数被调用 + expect(require('../../../src/subCommands/model/utils').retryFileManagerRm).toHaveBeenCalled(); }); }); @@ -743,5 +725,16 @@ describe('ArtModelService', () => { expect(result).toBe('file://mnt/custom/file1.txt'); // Should remove leading slash }); + + it('should handle mountDir starting with slash', () => { + const result = (artModelService as any)._getDestinationPath( + 'nas://auto', + { target: { path: 'file1.txt' } }, + [{ mountDir: '/mnt/nas' }], // mountDir starts with slash + [{ mountDir: '/mnt/oss' }], + ); + + expect(result).toBe('file://mnt/nas/file1.txt'); + }); }); }); diff --git a/__tests__/ut/commands/model_test.ts b/__tests__/ut/commands/model_test.ts index 2847d88..9eba5f6 100644 --- a/__tests__/ut/commands/model_test.ts +++ b/__tests__/ut/commands/model_test.ts @@ -411,6 +411,7 @@ describe('Model', () => { conflictResolution: 'overwrite', mode: 'once', timeout: 30 * 1000, + upgrade: {}, }, region: 'cn-hangzhou', functionName: 'test-function', @@ -502,6 +503,7 @@ describe('Model', () => { conflictResolution: 'skip', mode: 'always', timeout: 60 * 1000, + upgrade: {}, }, region: 'cn-hangzhou', functionName: 'test-function', diff --git a/__tests__/ut/commands/model_utils_test.ts b/__tests__/ut/commands/model_utils_test.ts index 5eb92e1..c01de6b 100644 --- a/__tests__/ut/commands/model_utils_test.ts +++ b/__tests__/ut/commands/model_utils_test.ts @@ -4,6 +4,9 @@ import { _displayProgress, _displayProgressComplete, checkModelStatus, + retryFileManagerRsyncAndCheckStatus, + retryFileManagerRm, + extractOssMountDir, } from '../../../src/subCommands/model/utils'; import DevClient from '@alicloud/devs20230714'; import * as $OpenApi from '@alicloud/openapi-client'; @@ -270,3 +273,345 @@ describe('Model Utils', () => { }); }); }); + +describe('retryFileManagerRsyncAndCheckStatus', () => { + let mockDevClient: jest.Mocked; + + beforeEach(() => { + mockDevClient = { + fileManagerRsync: jest.fn(), + getFileManagerTask: jest.fn(), + } as any; + + (sleep as jest.Mock).mockResolvedValue(undefined); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should successfully complete rsync and check status', async () => { + const mockRequest = { + source: 'modelscope://test-model', + destination: 'file://mnt/nas/model', + conflictHandling: 'overwrite', + mountConfig: {}, + }; + + (mockDevClient.fileManagerRsync as jest.Mock).mockResolvedValue({ + body: { + success: true, + data: { + taskID: 'task-123', + }, + requestId: 'req-123', + }, + }); + + (mockDevClient.getFileManagerTask as jest.Mock).mockResolvedValue({ + body: { + data: { + finished: true, + success: true, + startTime: Date.now(), + finishedTime: Date.now() + 1000, + progress: { + currentBytes: 1024, + totalBytes: 1024, + total: true, + }, + }, + }, + }); + + await expect( + retryFileManagerRsyncAndCheckStatus(mockDevClient, mockRequest, 'test-file', 30000), + ).resolves.not.toThrow(); + }); + + it('should handle initialization errors with retries', async () => { + const mockRequest = { + source: 'modelscope://test-model', + destination: 'file://mnt/nas/model', + conflictHandling: 'overwrite', + mountConfig: {}, + }; + + (mockDevClient.fileManagerRsync as jest.Mock).mockResolvedValue({ + body: { + success: true, + data: { + taskID: 'task-123', + }, + requestId: 'req-123', + }, + }); + + // First call returns an initialization error, second succeeds + (mockDevClient.getFileManagerTask as jest.Mock) + .mockResolvedValueOnce({ + body: { + data: { + finished: true, + success: false, + startTime: Date.now(), + progress: { + currentBytes: 0, + totalBytes: 0, + }, + errorMessage: + 'initialize download failed: failed to initialize the download environment; this is usually caused by your NAS being inaccessible.', + }, + requestId: 'req-123', + }, + }) + .mockResolvedValueOnce({ + body: { + data: { + finished: true, + success: true, + startTime: Date.now(), + finishedTime: Date.now() + 1000, + progress: { + currentBytes: 1024, + totalBytes: 1024, + total: true, + }, + }, + }, + }); + + await expect( + retryFileManagerRsyncAndCheckStatus( + mockDevClient, + mockRequest, + 'test-file', + 30000, + 2, // maxRetries + 1, // baseDelay (in test we use 1 second) + ), + ).resolves.not.toThrow(); + + // Should have called fileManagerRsync twice (first + 1 retry) + expect(mockDevClient.fileManagerRsync).toHaveBeenCalledTimes(2); + }); + + it('should throw non-initialization errors immediately', async () => { + const mockRequest = { + source: 'modelscope://test-model', + destination: 'file://mnt/nas/model', + conflictHandling: 'overwrite', + mountConfig: {}, + }; + + (mockDevClient.fileManagerRsync as jest.Mock).mockResolvedValue({ + body: { + success: true, + data: { + taskID: 'task-123', + }, + requestId: 'req-123', + }, + }); + + (mockDevClient.getFileManagerTask as jest.Mock).mockResolvedValue({ + body: { + data: { + finished: true, + success: false, + startTime: Date.now(), + progress: { + currentBytes: 0, + totalBytes: 0, + }, + errorMessage: 'Some other error', + }, + requestId: 'req-123', + }, + }); + + await expect( + retryFileManagerRsyncAndCheckStatus(mockDevClient, mockRequest, 'test-file', 30000), + ).rejects.toThrow('Some other error'); + }); +}); + +describe('retryFileManagerRm', () => { + let mockDevClient: jest.Mocked; + + beforeEach(() => { + mockDevClient = { + fileManagerRm: jest.fn(), + getFileManagerTask: jest.fn(), + } as any; + + (sleep as jest.Mock).mockResolvedValue(undefined); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should successfully remove file', async () => { + const mockRequest = { + filepath: '/mnt/nas/test-file', + mountConfig: {}, + }; + + (mockDevClient.fileManagerRm as jest.Mock).mockResolvedValue({ + body: { + success: true, + data: { + taskID: 'task-123', + }, + requestId: 'req-123', + }, + }); + + (mockDevClient.getFileManagerTask as jest.Mock).mockResolvedValue({ + body: { + data: { + finished: true, + success: true, + startTime: Date.now(), + }, + }, + }); + + const result = await retryFileManagerRm(mockDevClient, mockRequest, 'test-file'); + + expect(result).toEqual({ + fileName: 'test-file', + success: true, + }); + }); + + it('should handle NoSuchFileError as success', async () => { + const mockRequest = { + filepath: '/mnt/nas/test-file', + mountConfig: {}, + }; + + (mockDevClient.fileManagerRm as jest.Mock).mockResolvedValue({ + body: { + success: true, + data: { + taskID: 'task-123', + }, + requestId: 'req-123', + }, + }); + + (mockDevClient.getFileManagerTask as jest.Mock).mockResolvedValue({ + body: { + data: { + finished: true, + success: false, + startTime: Date.now(), + errorMessage: 'NoSuchFileError: File does not exist', + }, + }, + }); + + const result = await retryFileManagerRm(mockDevClient, mockRequest, 'test-file'); + + expect(result).toEqual({ + fileName: 'test-file', + success: true, + }); + }); + + it('should handle initialization errors with retries', async () => { + const mockRequest = { + filepath: '/mnt/nas/test-file', + mountConfig: {}, + }; + + (mockDevClient.fileManagerRm as jest.Mock).mockResolvedValue({ + body: { + success: true, + data: { + taskID: 'task-123', + }, + requestId: 'req-123', + }, + }); + + // First call returns an initialization error, second succeeds + (mockDevClient.getFileManagerTask as jest.Mock) + .mockResolvedValueOnce({ + body: { + data: { + finished: true, + success: false, + startTime: Date.now(), + errorMessage: + 'initialize download failed: failed to initialize the download environment; this is usually caused by your NAS being inaccessible.', + }, + }, + }) + .mockResolvedValueOnce({ + body: { + data: { + finished: true, + success: true, + startTime: Date.now(), + }, + }, + }); + + const result = await retryFileManagerRm( + mockDevClient, + mockRequest, + 'test-file', + 3, // maxRetries + 1, // baseDelay (in test we use 1 second) + ); + + expect(result).toEqual({ + fileName: 'test-file', + success: true, + }); + + // Should have called getFileManagerTask twice (first + 1 retry) + expect(mockDevClient.getFileManagerTask).toHaveBeenCalledTimes(2); + }); +}); + +describe('extractOssMountDir', () => { + it('should truncate mountDir if longer than 48 characters', () => { + const ossMountPoints = [ + { mountDir: '/very-long-path-that-exceeds-the-character-limit-and-needs-to-be-truncated' }, + { mountDir: '/short' }, + ]; + + const result = extractOssMountDir(ossMountPoints); + + expect(result[0].mountDir).toBe('/very-long-path-that-exceeds-the-character-limit'); + expect(result[1].mountDir).toBe('/short'); + }); + + it('should not modify mountDir if 48 characters or less', () => { + const ossMountPoints = [ + { mountDir: '/exactly-48-characters-path-for-testing-purposes' }, + { mountDir: '/short' }, + ]; + + const result = extractOssMountDir(ossMountPoints); + + expect(result[0].mountDir).toBe('/exactly-48-characters-path-for-testing-purposes'); + expect(result[1].mountDir).toBe('/short'); + }); + + it('should handle empty array', () => { + const result = extractOssMountDir([]); + + expect(result).toBeUndefined(); + }); + + it('should handle undefined input', () => { + const result = extractOssMountDir(undefined); + + expect(result).toBeUndefined(); + }); +}); diff --git a/publish.yaml b/publish.yaml index 657e6fa..b7e849b 100644 --- a/publish.yaml +++ b/publish.yaml @@ -3,7 +3,7 @@ Type: Component Name: fc3 Provider: - 阿里云 -Version: 0.1.14 +Version: 0.1.15 Description: 阿里云函数计算全生命周期管理 HomePage: https://github.com/devsapp/fc3 Organization: 阿里云函数计算(FC) diff --git a/src/subCommands/model/fileManager.ts b/src/subCommands/model/fileManager.ts index 2a6130e..c4f0ccd 100644 --- a/src/subCommands/model/fileManager.ts +++ b/src/subCommands/model/fileManager.ts @@ -186,17 +186,36 @@ export class ArtModelService { } } - async removeModel(name, params) { + async removeModel(name: string, params: any) { const { nasMountPoints, ossMountPoints, role, vpcConfig, modelConfig, region } = params; try { const devClient = await initClient(this.inputs, this.region, 'fun-art'); - const { files } = modelConfig; + const { files, upgrade } = modelConfig; if (_.isEmpty(files)) { logger.info('[Remove-model] No files specified for removal.'); return; } const processedOssMountPoints = extractOssMountDir(ossMountPoints); + let allRemovePromises = []; + if (upgrade?.history && !_.isEmpty(upgrade.history)) { + logger.info('[Remove-model] Upgrade model, only support once model.'); + const removeUpgradePromises = Object.keys(upgrade.history).map((key) => + this._removeUpgradeSingleFile(devClient, upgrade.history[key], key, { + name, + nasMountPoints, + ossMountPoints: processedOssMountPoints, + role, + vpcConfig, + modelConfig, + region, + timeout: modelConfig?.timeout, + }), + ); + // 将升级文件删除任务添加到总的任务列表中 + allRemovePromises = allRemovePromises.concat(removeUpgradePromises); + } + // 将异步操作重构为并行处理 const removePromises = files.map((file) => this._removeSingleFile(devClient, file, { @@ -210,8 +229,10 @@ export class ArtModelService { timeout: modelConfig?.timeout, }), ); + // 将删除任务也添加到总的任务列表中 + allRemovePromises = allRemovePromises.concat(removePromises); - const removeResults = await Promise.all(removePromises); + const removeResults = await Promise.all(allRemovePromises); // 统计成功和失败的数量 const successfulRemovals = removeResults.filter((result) => result.success); @@ -261,27 +282,59 @@ export class ArtModelService { timeout: number; }, ) { - const { name, nasMountPoints, ossMountPoints, role, vpcConfig, modelConfig, region, timeout } = - config; + const { nasMountPoints, ossMountPoints, modelConfig } = config; + + let filepath: string; + const uri = file.target?.uri || modelConfig.target.uri; + const path = + (file.target?.path.startsWith('/') ? file.target.path.slice(1) : file.target.path) || ''; + const fileName = file.source?.path || 'unknown'; + // 判断uri是否为nas://auto或oss://auto + if (uri.startsWith('nas://auto') && nasMountPoints?.length > 0) { + const { mountDir } = nasMountPoints[0]; + filepath = `${mountDir}/${path}`; + } else if (uri.startsWith('oss://auto') && ossMountPoints?.length > 0) { + const { mountDir } = ossMountPoints[0]; + filepath = `${mountDir}/${path}`; + } else { + // 直接拼接uri和path + let normalizedUri = uri.endsWith('/') ? uri.slice(0, -1) : uri; + normalizedUri = normalizedUri.replace(/^(nas|oss|file):\/\//, '/'); + filepath = `${normalizedUri}/${path}`; + } + + return this._removeFileWithRetry(devClient, filepath, fileName, config); + } + + private async _removeUpgradeSingleFile( + devClient: DevClient, + path: string, + version: string, + config: { + name: string; + nasMountPoints: any[]; + ossMountPoints: any[]; + role: string; + vpcConfig: any; + modelConfig: any; + region: string; + timeout: number; + }, + ) { + const fileName = version || 'unknown'; + + return this._removeFileWithRetry(devClient, path, fileName, config); + } + + private async _removeFileWithRetry( + devClient: DevClient, + filepath: string, + fileName: string, + config: any, + ): Promise { try { - let filepath; - const uri = file.target?.uri || modelConfig.target.uri; - const path = file.target?.path || ''; - - // 判断uri是否为nas://auto或oss://auto - if (uri.startsWith('nas://auto') && nasMountPoints?.length > 0) { - const { mountDir } = nasMountPoints[0]; - filepath = `${mountDir}/${path}`; - } else if (uri.startsWith('oss://auto') && ossMountPoints?.length > 0) { - const { mountDir } = ossMountPoints[0]; - filepath = `${mountDir}/${path}`; - } else { - // 直接拼接uri和path - let normalizedUri = uri.endsWith('/') ? uri.slice(0, -1) : uri; - normalizedUri = normalizedUri.replace(/^(nas|oss|file):\/\//, '/'); - filepath = `${normalizedUri}/${path}`; - } + const { name, nasMountPoints, ossMountPoints, role, vpcConfig, region, timeout } = config; const fileManagerRmRequest = new $Dev20230714.FileManagerRmRequest({ filepath, @@ -296,17 +349,10 @@ export class ArtModelService { }), }); - const result = await retryFileManagerRm( - devClient, - fileManagerRmRequest, - file.source.path, - 3, - 30, - ); + const result = await retryFileManagerRm(devClient, fileManagerRmRequest, fileName, 3, 30); return result; } catch (error) { - const fileName = file.source?.path || 'unknown'; logger.error(`[Remove-model] Error removing file ${fileName}: ${error.message}`); logger.error(`[Remove-model] Error details:`, error.stack || error); return { diff --git a/src/subCommands/model/index.ts b/src/subCommands/model/index.ts index d1d24fc..7dbd248 100644 --- a/src/subCommands/model/index.ts +++ b/src/subCommands/model/index.ts @@ -297,6 +297,7 @@ mountPoints: files: modelConfig.files, conflictResolution: modelConfig?.downloadStrategy?.conflictResolution || 'overwrite', mode: process.env.MODEL_DOWNLOAD_STRATEGY || modelConfig?.downloadStrategy?.mode || 'once', + upgrade: modelConfig?.upgrade || {}, timeout: (modelConfig?.downloadStrategy?.timeout && modelConfig?.downloadStrategy?.timeout * 1000) || diff --git a/src/subCommands/model/utils/index.ts b/src/subCommands/model/utils/index.ts index dc80d21..d106539 100644 --- a/src/subCommands/model/utils/index.ts +++ b/src/subCommands/model/utils/index.ts @@ -318,14 +318,14 @@ export async function retryFileManagerRm( if (isInitializeError(modelStatus.errorMessage) && attempts < maxRetries) { const delay = baseDelay * Math.pow(2, attempts - 1); + logger.error( + `[Remove-model] model: ${modelStatus.errorMessage}, requestId: ${getFileManagerTask.body.requestId}`, + ); logger.warn( `[Remove-model] Detected initialization error for ${fileName}, retrying... (${ attempts + 1 }/${maxRetries}). Waiting ${delay}s`, ); - logger.error( - `[Remove-model] model: ${modelStatus.errorMessage}, requestId: ${getFileManagerTask.body.requestId}`, - ); // eslint-disable-next-line no-await-in-loop await sleep(delay);