From 5e8c40887905bc490435ec80eeebf6d058304919 Mon Sep 17 00:00:00 2001 From: "tao.gan" Date: Mon, 29 Jun 2026 09:43:01 +0800 Subject: [PATCH] [vm]: validate local storage mount point MountBlockDevice now rejects a mount point that is already used by local primary storage attached to the host's cluster, so the API fails before mounting or formatting the block device. Add cluster lookup to ProxyHardwareFactory and KVMHostFactory. Validate APIMountBlockDeviceMsg mountPoint before proxy hardware retrieval. Check existing local primary storage URLs in the resolved cluster and return API error on conflict. Resolves: ZSV-6589 Change-Id: I747a706370786d6b75666b756c6d7469706b636d --- .../compute/host/HostApiInterceptor.java | 66 +++++++++++++++++-- .../header/agent/ProxyHardwareFactory.java | 4 ++ .../java/org/zstack/kvm/KVMHostFactory.java | 8 +++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java b/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java index d75989d896e..ea8f10e87a8 100755 --- a/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java +++ b/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java @@ -22,6 +22,13 @@ import org.zstack.utils.ShellResult; import org.zstack.utils.ShellUtils; import org.zstack.utils.network.NetworkUtils; +import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO; +import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO_; +import org.zstack.header.storage.primary.PrimaryStorageConstants; +import org.zstack.header.storage.primary.PrimaryStorageVO; +import org.zstack.header.storage.primary.PrimaryStorageVO_; + +import java.util.List; import static org.zstack.core.Platform.argerr; import static org.zstack.core.Platform.operr; @@ -152,6 +159,10 @@ private void validate(APIGetPhysicalMachineBlockDevicesMsg msg) { } private void validate(APIMountBlockDeviceMsg msg) { + validatePath(msg.getPath()); + validateMountPoint(msg.getMountPoint()); + validateMountPointNotOccupiedByLocalStorage(msg); + if (msg.getPassword() != null) { return; } @@ -162,9 +173,6 @@ private void validate(APIMountBlockDeviceMsg msg) { } msg.setPassword(proxyHardware.getPassword()); msg.setUsername(msg.getUsername() != null ? msg.getUsername() : proxyHardware.getUsername()); - - validatePath(msg.getPath()); - validateMountPoint(msg.getMountPoint()); } private void validate(APIUpdateHostnameMsg msg) { @@ -215,10 +223,6 @@ private void validateMountPoint(String mountPoint) { "invalid value detected: '%s'", SAFE_MOUNT_POINT_PATTERN, mountPoint)); } - if (mountPoint.endsWith("/") && !mountPoint.equals("/")) { - throw new ApiMessageInterceptionException(operr( - "mountPoint should not end with '/' except root directory")); - } } private ProxyHardware getProxyHardware(String hostname) { @@ -230,4 +234,52 @@ private ProxyHardware getProxyHardware(String hostname) { } return null; } + + private String getClusterUuid(String hostname) { + for (ProxyHardwareFactory factory : pluginRgty.getExtensionList(ProxyHardwareFactory.class)) { + String clusterUuid = factory.getClusterUuid(hostname); + if (clusterUuid != null) { + return clusterUuid; + } + } + return null; + } + + private void validateMountPointNotOccupiedByLocalStorage(APIMountBlockDeviceMsg msg) { + String clusterUuid = getClusterUuid(msg.getHostName()); + if (clusterUuid == null) { + return; + } + + List primaryStorageUuids = Q.New(PrimaryStorageClusterRefVO.class) + .select(PrimaryStorageClusterRefVO_.primaryStorageUuid) + .eq(PrimaryStorageClusterRefVO_.clusterUuid, clusterUuid) + .listValues(); + if (primaryStorageUuids.isEmpty()) { + return; + } + + String mountPoint = normalizeMountPoint(msg.getMountPoint()); + List localStorageUrls = Q.New(PrimaryStorageVO.class) + .select(PrimaryStorageVO_.url) + .eq(PrimaryStorageVO_.type, PrimaryStorageConstants.LOCAL_STORAGE_TYPE) + .in(PrimaryStorageVO_.uuid, primaryStorageUuids) + .listValues(); + + for (String url : localStorageUrls) { + if (!mountPoint.equals(normalizeMountPoint(url))) { + continue; + } + throw new ApiMessageInterceptionException(argerr( + "url[%s] has been occupied by local primary storage in cluster[uuid:%s]", + msg.getMountPoint(), clusterUuid)); + } + } + + private static String normalizeMountPoint(String path) { + while (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + return path; + } } diff --git a/header/src/main/java/org/zstack/header/agent/ProxyHardwareFactory.java b/header/src/main/java/org/zstack/header/agent/ProxyHardwareFactory.java index 505b497946b..1597ff5cd77 100644 --- a/header/src/main/java/org/zstack/header/agent/ProxyHardwareFactory.java +++ b/header/src/main/java/org/zstack/header/agent/ProxyHardwareFactory.java @@ -2,4 +2,8 @@ public interface ProxyHardwareFactory { ProxyHardware getProxyHardware(String hostName); + + default String getClusterUuid(String hostName) { + return null; + } } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java index 4be7abd072e..ff844974397 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java @@ -1232,4 +1232,12 @@ public ProxyHardware getProxyHardware(String hostName) { "and host.managementIp = :hostname"; return SQL.New(sql, KVMHostVO.class).param("hostname", hostName).find(); } + + @Override + public String getClusterUuid(String hostName) { + return Q.New(HostVO.class) + .select(HostVO_.clusterUuid) + .eq(HostVO_.managementIp, hostName) + .findValue(); + } }