From 89f989a4ecfe8f9979ba1e0393f05be7f15dd6db Mon Sep 17 00:00:00 2001 From: "yaohua.wu" Date: Wed, 25 Feb 2026 16:31:14 +0800 Subject: [PATCH] [storage]: fix wrong BS selected in mixed VCenter env When SelectBackupStorageMsg carries preferBsTypes, the sorting logic uses indexOf() which returns -1 for non-preferred types (e.g. VCenter BS), causing them to sort before preferred ones. This fix filters backup storages by preferBsTypes first, then sorts within the filtered set. Also adds error code ADDON_PRIMARY_10015 when no matching backup storage is available. 1. Why is this change necessary? SelectBackupStorageMsg.preferBsTypes is used to sort backup storages via indexOf() on the preference list. However, indexOf() returns -1 for types not in the list, which sorts non-preferred types (like VCenter BS) before preferred ones. In a mixed VCenter + Expon environment, this causes VCenter BS to be incorrectly selected over the intended Expon BS. 2. How does it address the problem? Instead of only sorting, the code now first filters the candidate backup storages to only include those whose bsType matches the preferBsTypes list. Then it sorts within the filtered set. A new error code ADDON_PRIMARY_10015 is added for the case where no matching backup storage is available after filtering. All 10 locale i18n JSON files are updated. 3. Are there any side effects? None. The filtering is only applied when preferBsTypes is non-empty, preserving existing behavior for other callers. # Summary of changes (by module): - storage: filter BS candidates by preferBsTypes before sort - utils: add ADDON_PRIMARY_10015 error code constant - conf/i18n: add i18n entries for new error code (10 locales) - test: add ExternalPrimaryStorageSelectBackupStorageCase Related: ZSTAC-71706 Change-Id: Ia3af38cc50e69132a1c769180792363495c1080f --- .../global-error-de-DE.json | 1 + .../global-error-en_US.json | 1 + .../global-error-fr-FR.json | 1 + .../global-error-id-ID.json | 1 + .../global-error-ja-JP.json | 1 + .../global-error-ko-KR.json | 1 + .../global-error-ru-RU.json | 1 + .../global-error-th-TH.json | 1 + .../global-error-zh_CN.json | 1 + .../global-error-zh_TW.json | 1 + .../addon/primary/ExternalPrimaryStorage.java | 15 +- ...imaryStorageSelectBackupStorageCase.groovy | 200 ++++++++++++++++++ .../CloudOperationsErrorCode.java | 2 + 13 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/ExternalPrimaryStorageSelectBackupStorageCase.groovy diff --git a/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json b/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json index 41e1915fed..f23ec0e9e1 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "Instanztyp[%s] wird nicht unterstützt", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "Gruppenadresse [%s] ist keine Multicast-Gruppenadresse", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "Speicher ist nicht gesund:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "Kein verfügbarer Backup-Speicher mit bevorzugten Typen %s für primären Speicher[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "multicastRouter[uuid:%s] wurde keinem VPC-Router zugeordnet", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "Multicast ist bereits auf dem VPC-Router UUID[:%s] aktiviert", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "Der Eigentümer-Volume-Pfad kann aus dem internen Snapshot-Pfad[%s] nicht gefunden werden, da der reguläre Ausdruck[%s] nicht mit dem Snapshot-Pfad übereinstimmt", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-en_US.json b/conf/i18n/globalErrorCodeMapping/global-error-en_US.json index 715e823d95..5e59252520 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-en_US.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-en_US.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "unsupported instance type[%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "group address [%s] is not a multicast group address", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "storage is not healthy:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "no available backup storage with preferred types %s for primary storage[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "multicastRouter[uuid:%s] has not been associated with a VPC router", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "multicast already enabled on VPC router UUID[:%s]", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "cannot find the owning volume path from the internal snapshot path[%s], as the regex[%s] fails to match the snapshot path", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json b/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json index 449344a6ce..a742212f40 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "type d'instance[%s] non supporté", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "l'adresse de groupe [%s] n'est pas une adresse de groupe multicast", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "le stockage n'est pas sain : %s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "aucun stockage de sauvegarde disponible avec les types préférés %s pour le stockage principal[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "Le multicastRouter[uuid:%s] n'a pas été associé à un routeur VPC", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "Le multicast est déjà activé sur le routeur VPC UUID[:%s]", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "Impossible de trouver le chemin du volume propriétaire à partir du chemin d'instantané interne[%s], car l'expression régulière[%s] ne correspond pas au chemin d'instantané", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json b/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json index d8cd5bf487..58d87228b7 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "jenis instance[%s] tidak didukung", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "alamat grup [%s] bukan alamat grup multicast", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "penyimpanan tidak sehat:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "tidak ada penyimpanan cadangan yang tersedia dengan tipe yang disukai %s untuk penyimpanan utama[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "multicastRouter[uuid:%s] belum dikaitkan dengan router VPC", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "multicast sudah diaktifkan pada router VPC UUID[:%s]", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "tidak dapat menemukan jalur volume pemilik dari jalur snapshot internal[%s], karena regex[%s] tidak cocok dengan jalur snapshot", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json b/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json index 2924f65847..a935c05b20 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "サポートされていないインスタンスタイプ[%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "グループアドレス[%s]はマルチキャストグループアドレスではありません", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "ストレージは正常ではありません:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "優先タイプ%sに一致する利用可能なバックアップストレージがプライマリストレージ[uuid:%s]に存在しません", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "マルチキャストルーター[uuid:%s]はVPCルーターに関連付けられていません", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "マルチキャストは既にVPCルーターUUID[:%s]で有効化されています", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "内部スナップショットパス[%s]から所有ボリュームパスが見つかりません。正規表現[%s]がスナップショットパスに一致しません", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json b/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json index c73d9f274a..ad11aec2dd 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "지원되지 않는 인스턴스 유형[%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "그룹 주소 [%s]는 멀티캐스트 그룹 주소가 아닙니다", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "저장소가 정상 상태가 아닙니다:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "기본 저장소[uuid:%s]에 대해 선호하는 유형 %s의 사용 가능한 백업 저장소가 없습니다", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "multicastRouter[uuid:%s]가 VPC 라우터와 연결되지 않았습니다", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "VPC 라우터 UUID[:%s]에서 멀티캐스트가 이미 활성화되어 있습니다", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "정규식[%s]이 스냅샷 경로와 일치하지 않아 내부 스냅샷 경로[%s]에서 소유 볼륨 경로를 찾을 수 없습니다", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json b/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json index 2454d7e565..6bea92476a 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "неподдерживаемый тип экземпляра[%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "групповой адрес [%s] не является мультикаст-групповым адресом", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "хранилище нездорово:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "нет доступного резервного хранилища с предпочитаемыми типами %s для первичного хранилища[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "multicastRouter[uuid:%s] не был связан с VPC-роутером", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "multicast уже включен на VPC-роутере UUID[:%s]", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "невозможно найти путь к принадлежащему тому из внутреннего пути снимка[%s], так как регулярное выражение[%s] не соответствует пути снимка", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json b/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json index 9db32b3426..09f39b96cc 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "instance type[%s] ไม่รองรับ", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "group address [%s] ไม่ใช่ multicast group address", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "storage ไม่พร้อมใช้งาน:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "ไม่มี backup storage ที่ใช้ได้ซึ่งมีประเภทที่ต้องการ %s สำหรับ primary storage[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "multicastRouter[uuid:%s] ไม่ได้เชื่อมโยงกับ VPC router", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "multicast เปิดใช้งานอยู่แล้วบน VPC router UUID[:%s]", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "ไม่พบ path ของ volume ที่เป็นเจ้าของจาก internal snapshot path[%s] เนื่องจาก regex[%s] ไม่สามารถจับคู่กับ snapshot path ได้", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json b/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json index 01960e8eb4..94747404ca 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "不支持的实例配置类型[%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "组地址 [%s] 不是多播地址", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "存储不健康:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "没有可用的镜像服务器匹配首选类型 %s,主存储[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "多播路由器[uuid:%s]尚未绑定到VPC路由器", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "多播已在VPC路由器uuid[:%s]上启用", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "无法从内部快照路径[%s]找到所属卷路径,因为正则表达式[%s]与快照路径不匹配", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json b/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json index 35f1b8c844..57157f5d3a 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json @@ -629,6 +629,7 @@ "ORG_ZSTACK_CONFIGURATION_10006": "不支持的实例配置類型[%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10007": "组地址 [%s] 不是多播地址", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014": "儲儲不健康:%s", + "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015": "沒有可用的鏡像服務器匹配首選類型 %s,主儲存[uuid:%s]", "ORG_ZSTACK_MULTICAST_ROUTER_10000": "多播路由器[uuid:%s]尚未綁定到VPC路由器", "ORG_ZSTACK_MULTICAST_ROUTER_10001": "多播已在VPC路由器uuid[:%s]上啟用", "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10012": "無法从内部快照路径[%s]找到所属卷路径,因为正则表達式[%s]与快照路径不匹配", diff --git a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java index 673cfed7c1..332b10331a 100644 --- a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java +++ b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorage.java @@ -510,8 +510,19 @@ private void handle(final SelectBackupStorageMsg msg) { .param("size", msg.getRequiredSize()) .list(); - // sort by prefer type - availableBs.sort(Comparator.comparingInt(o -> preferBsTypes.indexOf(o.getType()))); + // filter out non-preferred types, then sort by preference order + availableBs = availableBs.stream() + .filter(bs -> preferBsTypes.contains(bs.getType())) + .sorted(Comparator.comparingInt(o -> preferBsTypes.indexOf(o.getType()))) + .collect(Collectors.toList()); + + if (availableBs.isEmpty()) { + reply.setError(operr(ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015, + "no available backup storage with preferred types %s for primary storage[uuid:%s]", + preferBsTypes, self.getUuid())); + bus.reply(msg, reply); + return; + } reply.setInventory(BackupStorageInventory.valueOf(availableBs.get(0))); bus.reply(msg, reply); diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/ExternalPrimaryStorageSelectBackupStorageCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/ExternalPrimaryStorageSelectBackupStorageCase.groovy new file mode 100644 index 0000000000..9419db1221 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/ExternalPrimaryStorageSelectBackupStorageCase.groovy @@ -0,0 +1,200 @@ +package org.zstack.test.integration.storage.primary.addon + +import org.zstack.core.Platform +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.db.DatabaseFacade +import org.zstack.core.db.SQL +import org.zstack.header.message.MessageReply +import org.zstack.header.storage.backup.BackupStorageEO +import org.zstack.header.storage.backup.BackupStorageState +import org.zstack.header.storage.backup.BackupStorageStatus +import org.zstack.header.storage.backup.BackupStorageZoneRefVO +import org.zstack.header.storage.backup.BackupStorageZoneRefVO_ +import org.zstack.header.storage.primary.PrimaryStorageConstant +import org.zstack.header.storage.primary.SelectBackupStorageMsg +import org.zstack.header.storage.primary.SelectBackupStorageReply +import org.zstack.sdk.PrimaryStorageInventory +import org.zstack.sdk.ZoneInventory +import org.zstack.test.integration.storage.StorageTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit + +/** + * ZSTAC-71706: ExternalPrimaryStorage backup storage selection in mixed environment. + * + * Bug: List.indexOf() returns -1 for types not in preferBsTypes, + * causing ascending sort to place non-preferred types (e.g. VCenterBackupStorage) + * before preferred types in the sorted result. + * + * Fix: Filter out non-preferred backup storage types before sorting by preference. + * + * This case sets up a ZBS ExternalPrimaryStorage (preferBsTypes = [ImageStoreBackupStorage]) + * with multiple backup storage types attached to the zone, then sends SelectBackupStorageMsg + * via CloudBus to verify the handler selects the correct preferred backup storage. + */ +class ExternalPrimaryStorageSelectBackupStorageCase extends SubCase { + EnvSpec env + ZoneInventory zone + PrimaryStorageInventory ps + DatabaseFacade dbf + CloudBus bus + List manualBsUuids = [] + + @Override + void clean() { + manualBsUuids.each { uuid -> + SQL.New(BackupStorageZoneRefVO.class) + .eq(BackupStorageZoneRefVO_.backupStorageUuid, uuid) + .hardDelete() + dbf.removeByPrimaryKey(uuid, BackupStorageEO.class) + } + env.delete() + } + + @Override + void setup() { + useSpring(StorageTest.springSpec) + } + + @Override + void environment() { + env = makeEnv { + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "127.0.0.2" + + image { + name = "image" + url = "http://zstack.org/download/test.qcow2" + size = SizeUnit.GIGABYTE.toByte(1) + virtio = true + } + } + + zone { + name = "zone" + description = "test" + + cluster { + name = "cluster" + hypervisorType = "KVM" + + kvm { + name = "kvm" + managementIp = "127.0.0.1" + username = "root" + password = "password" + } + + attachL2Network("l2") + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + + ip { + startIp = "192.168.100.10" + endIp = "192.168.100.100" + netmask = "255.255.255.0" + gateway = "192.168.100.1" + } + } + } + + externalPrimaryStorage { + name = "zbs-ps" + identity = "zbs" + defaultOutputProtocol = "CBD" + config = '{"mdsUrls":["root:password@127.0.1.1","root:password@127.0.1.2","root:password@127.0.1.3"],"logicalPoolName":"lpool1"}' + url = "zbs" + } + + attachBackupStorage("sftp") + } + } + } + + @Override + void test() { + env.create { + zone = env.inventoryByName("zone") as ZoneInventory + ps = env.inventoryByName("zbs-ps") as PrimaryStorageInventory + dbf = bean(DatabaseFacade.class) + bus = bean(CloudBus.class) + + testErrorWhenNoPreferredTypeAvailable() + testSelectPreferredOverNonPreferred() + } + } + + /** + * When only non-preferred backup storage types exist in the zone, + * the selection should return an error (no matching preferred types). + * Zone has SftpBackupStorage (from env) and VCenterBackupStorage, + * neither of which is in zbs's preferBsTypes [ImageStoreBackupStorage]. + */ + void testErrorWhenNoPreferredTypeAvailable() { + createAndAttachBackupStorage("vcenter-bs", "VCenterBackupStorage") + + SelectBackupStorageMsg msg = new SelectBackupStorageMsg() + msg.setPrimaryStorageUuid(ps.uuid) + msg.setRequiredSize(SizeUnit.MEGABYTE.toByte(1)) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ps.uuid) + MessageReply reply = bus.call(msg) + + assert !reply.isSuccess() : "Should fail when no preferred BS type is available" + } + + /** + * Reproduces ZSTAC-71706: zone has both ImageStoreBackupStorage (preferred) + * and VCenterBackupStorage (non-preferred, created in previous test). + * Before the fix, indexOf() returns -1 for VCenterBackupStorage causing + * it to sort before ImageStoreBackupStorage. After the fix, non-preferred + * types are filtered out entirely, and ImageStoreBackupStorage is correctly selected. + */ + void testSelectPreferredOverNonPreferred() { + createAndAttachBackupStorage("imagestore-bs", "ImageStoreBackupStorage") + + SelectBackupStorageMsg msg = new SelectBackupStorageMsg() + msg.setPrimaryStorageUuid(ps.uuid) + msg.setRequiredSize(SizeUnit.MEGABYTE.toByte(1)) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ps.uuid) + MessageReply reply = bus.call(msg) + + assert reply.isSuccess() : "SelectBackupStorageMsg should succeed" + SelectBackupStorageReply bsReply = reply as SelectBackupStorageReply + assert bsReply.inventory != null + assert bsReply.inventory.type == "ImageStoreBackupStorage" : + "Should select preferred ImageStoreBackupStorage, but got ${bsReply.inventory.type}" + } + + private void createAndAttachBackupStorage(String name, String type) { + String uuid = Platform.getUuid() + + def bsEo = new BackupStorageEO() + bsEo.setUuid(uuid) + bsEo.setName(name) + bsEo.setType(type) + bsEo.setState(BackupStorageState.Enabled) + bsEo.setStatus(BackupStorageStatus.Connected) + bsEo.setTotalCapacity(SizeUnit.TERABYTE.toByte(100)) + bsEo.setAvailableCapacity(SizeUnit.TERABYTE.toByte(100)) + bsEo.setUrl("http://test-" + name) + dbf.persist(bsEo) + + def ref = new BackupStorageZoneRefVO() + ref.setBackupStorageUuid(uuid) + ref.setZoneUuid(zone.uuid) + dbf.persist(ref) + + manualBsUuids.add(uuid) + } +} diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 73fb1fd99e..6a4087cae0 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -6506,6 +6506,8 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014 = "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10014"; + public static final String ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015 = "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10015"; + public static final String ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10040 = "ORG_ZSTACK_STORAGE_ADDON_PRIMARY_10040"; public static final String ORG_ZSTACK_NETWORK_HOSTNETWORKINTERFACE_LLDP_10000 = "ORG_ZSTACK_NETWORK_HOSTNETWORKINTERFACE_LLDP_10000";