This is an automated email from the ASF dual-hosted git repository. Pearl1594 pushed a commit to branch support-multi-vlan-guestnet in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit c2ebb3a6ec5be616ff9e2c1bbecd5d99b657ff99 Author: Pearl1594 <[email protected]> AuthorDate: Mon Sep 14 15:33:15 2026 -0400 Support multi-VLAN trunk NICs --- api/src/main/java/com/cloud/host/Host.java | 2 + api/src/main/java/com/cloud/vm/Nic.java | 2 + .../org/apache/cloudstack/api/ApiConstants.java | 2 + .../cloudstack/api/response/HostResponse.java | 24 ++++ .../com/cloud/agent/manager/AgentManagerImpl.java | 12 +- .../schema/src/main/java/com/cloud/vm/NicVO.java | 14 ++- .../java/com/cloud/vm/dao/NicNetworkMapDao.java | 32 +++++ .../com/cloud/vm/dao/NicNetworkMapDaoImpl.java | 77 ++++++++++++ .../java/com/cloud/vm/dao/NicNetworkMapVO.java | 140 +++++++++++++++++++++ .../spring-engine-schema-core-daos-context.xml | 1 + .../resources/META-INF/db/schema-42300to2400.sql | 22 ++++ .../kvm/resource/LibvirtComputingResource.java | 41 ++++++ .../wrapper/LibvirtReadyCommandWrapper.java | 3 + .../com/cloud/api/query/dao/HostJoinDaoImpl.java | 2 + 14 files changed, 372 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/cloud/host/Host.java b/api/src/main/java/com/cloud/host/Host.java index c110e4ca94e..3cf36db8ad5 100644 --- a/api/src/main/java/com/cloud/host/Host.java +++ b/api/src/main/java/com/cloud/host/Host.java @@ -64,6 +64,8 @@ public interface Host extends StateObject<Status>, Identity, Partition, HAResour String HOST_VIRTV2V_VERSION = "host.virtv2v.version"; String HOST_SSH_PORT = "host.ssh.port"; String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count"; + String HOST_VLAN_FILTERING_ENABLED = "vlan.filtering.enabled"; + String HOST_VLAN_TRUNK_XML_SUPPORTED = "vlan.trunk.xml.supported"; String GUEST_OS_CATEGORY_ID = "guest.os.category.id"; String GUEST_OS_RULE = "guest.os.rule"; diff --git a/api/src/main/java/com/cloud/vm/Nic.java b/api/src/main/java/com/cloud/vm/Nic.java index 3722e5769c9..71612cd355f 100644 --- a/api/src/main/java/com/cloud/vm/Nic.java +++ b/api/src/main/java/com/cloud/vm/Nic.java @@ -146,6 +146,8 @@ public interface Nic extends Identity, InternalIdentity { boolean getSecondaryIp(); + boolean getMultiNetwork(); + // // IPv4 // diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index f74c4616118..de6aeed50a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -318,6 +318,8 @@ public class ApiConstants { public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash"; public static final String IMAGE_PATH = "imagepath"; public static final String INSTANCE_CONVERSION_SUPPORTED = "instanceconversionsupported"; + public static final String VLAN_FILTERING_ENABLED = "vlanfilteringenabled"; + public static final String VLAN_TRUNK_XML_SUPPORTED = "vlantrunkxmlsupported"; public static final String INTERNAL_DNS1 = "internaldns1"; public static final String INTERNAL_DNS2 = "internaldns2"; public static final String INTERNET_PROTOCOL = "internetprotocol"; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java index 10bd62804fb..52804cc980c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java @@ -315,6 +315,14 @@ public class HostResponse extends BaseResponseWithAnnotations { @Param(description = "true if the host supports instance conversion (using virt-v2v)", since = "4.19.1") private Boolean instanceConversionSupported; + @SerializedName(ApiConstants.VLAN_FILTERING_ENABLED) + @Param(description = "true if vlan_filtering is enabled on this host's guest bridge, so it can accept multi-VLAN trunk nics", since = "24.0.0") + private Boolean vlanFilteringEnabled; + + @SerializedName(ApiConstants.VLAN_TRUNK_XML_SUPPORTED) + @Param(description = "true if this host's libvirt version supports trunk vlan tap membership natively", since = "24.0.0") + private Boolean vlanTrunkXmlSupported; + @SerializedName(ApiConstants.ARCH) @Param(description = "CPU Arch of the host", since = "4.20") private String arch; @@ -904,6 +912,14 @@ public class HostResponse extends BaseResponseWithAnnotations { this.instanceConversionSupported = instanceConversionSupported; } + public void setVlanFilteringEnabled(Boolean vlanFilteringEnabled) { + this.vlanFilteringEnabled = vlanFilteringEnabled; + } + + public void setVlanTrunkXmlSupported(Boolean vlanTrunkXmlSupported) { + this.vlanTrunkXmlSupported = vlanTrunkXmlSupported; + } + public Boolean getIsTagARule() { return isTagARule; } @@ -1000,6 +1016,14 @@ public class HostResponse extends BaseResponseWithAnnotations { return instanceConversionSupported; } + public Boolean getVlanFilteringEnabled() { + return vlanFilteringEnabled; + } + + public Boolean getVlanTrunkXmlSupported() { + return vlanTrunkXmlSupported; + } + public void setExtensionId(String extensionId) { this.extensionId = extensionId; } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index 1215829d92f..eede907b187 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -808,8 +808,10 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl String vddkSupport = detailsMap.get(Host.HOST_VDDK_SUPPORT); String vddkLibDir = detailsMap.get(Host.HOST_VDDK_LIB_DIR); String vddkVersion = detailsMap.get(Host.HOST_VDDK_VERSION); + String vlanFilteringEnabled = detailsMap.get(Host.HOST_VLAN_FILTERING_ENABLED); + String vlanTrunkXmlSupported = detailsMap.get(Host.HOST_VLAN_TRUNK_XML_SUPPORTED); logger.debug("Got HOST_UEFI_ENABLE [{}] for host [{}]:", uefiEnabled, host); - if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion, vddkSupport, vddkLibDir, vddkVersion)) { + if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion, vddkSupport, vddkLibDir, vddkVersion, vlanFilteringEnabled, vlanTrunkXmlSupported)) { _hostDao.loadDetails(host); boolean updateNeeded = false; if (StringUtils.isNotBlank(uefiEnabled) && !uefiEnabled.equals(host.getDetails().get(Host.HOST_UEFI_ENABLE))) { @@ -828,6 +830,14 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl host.getDetails().put(Host.HOST_VDDK_SUPPORT, vddkSupport); updateNeeded = true; } + if (StringUtils.isNotBlank(vlanFilteringEnabled) && !vlanFilteringEnabled.equals(host.getDetails().get(Host.HOST_VLAN_FILTERING_ENABLED))) { + host.getDetails().put(Host.HOST_VLAN_FILTERING_ENABLED, vlanFilteringEnabled); + updateNeeded = true; + } + if (StringUtils.isNotBlank(vlanTrunkXmlSupported) && !vlanTrunkXmlSupported.equals(host.getDetails().get(Host.HOST_VLAN_TRUNK_XML_SUPPORTED))) { + host.getDetails().put(Host.HOST_VLAN_TRUNK_XML_SUPPORTED, vlanTrunkXmlSupported); + updateNeeded = true; + } if (!StringUtils.defaultString(vddkLibDir).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_VDDK_LIB_DIR)))) { if (StringUtils.isBlank(vddkLibDir)) { host.getDetails().remove(Host.HOST_VDDK_LIB_DIR); diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index 65946b8d821..bcda4f78775 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -128,6 +128,9 @@ public class NicVO implements Nic { @Column(name = "secondary_ip") boolean secondaryIp; + @Column(name = "multi_network") + boolean multiNetwork; + @Column(name = "mtu") Integer mtu; @@ -337,7 +340,7 @@ public class NicVO implements Nic { return String.format("Nic %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( this, "id", "uuid", "instanceId", - "deviceId", "broadcastUri", "reservationId", "iPv4Address")); + "deviceId", "broadcastUri", "reservationId", "iPv4Address", "multiNetwork")); } @Override @@ -381,6 +384,15 @@ public class NicVO implements Nic { this.secondaryIp = secondaryIp; } + @Override + public boolean getMultiNetwork() { + return multiNetwork; + } + + public void setMultiNetwork(boolean multiNetwork) { + this.multiNetwork = multiNetwork; + } + public void setVmType(VirtualMachine.Type vmType) { this.vmType = vmType; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapDao.java new file mode 100644 index 00000000000..b5a1ed3137a --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapDao.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import java.util.List; + +import com.cloud.utils.db.GenericDao; + +public interface NicNetworkMapDao extends GenericDao<NicNetworkMapVO, Long> { + + List<NicNetworkMapVO> listByNicId(long nicId); + + List<NicNetworkMapVO> listByNetworkId(long networkId); + + NicNetworkMapVO findByNicIdAndNetworkId(long nicId, long networkId); + + List<Long> listNicIdsByNetworkId(long networkId); +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapDaoImpl.java new file mode 100644 index 00000000000..f905809350e --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapDaoImpl.java @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Func; +import com.cloud.utils.db.SearchCriteria.Op; + +@Component +public class NicNetworkMapDaoImpl extends GenericDaoBase<NicNetworkMapVO, Long> implements NicNetworkMapDao { + + private final SearchBuilder<NicNetworkMapVO> AllFieldsSearch; + private final GenericSearchBuilder<NicNetworkMapVO, Long> NicIdsByNetworkSearch; + + public NicNetworkMapDaoImpl() { + super(); + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("nicId", AllFieldsSearch.entity().getNicId(), Op.EQ); + AllFieldsSearch.and("networkId", AllFieldsSearch.entity().getNetworkId(), Op.EQ); + AllFieldsSearch.done(); + + NicIdsByNetworkSearch = createSearchBuilder(Long.class); + NicIdsByNetworkSearch.select(null, Func.DISTINCT, NicIdsByNetworkSearch.entity().getNicId()); + NicIdsByNetworkSearch.and("networkId", NicIdsByNetworkSearch.entity().getNetworkId(), Op.EQ); + NicIdsByNetworkSearch.done(); + } + + @Override + public List<NicNetworkMapVO> listByNicId(long nicId) { + SearchCriteria<NicNetworkMapVO> sc = AllFieldsSearch.create(); + sc.setParameters("nicId", nicId); + return listBy(sc); + } + + @Override + public List<NicNetworkMapVO> listByNetworkId(long networkId) { + SearchCriteria<NicNetworkMapVO> sc = AllFieldsSearch.create(); + sc.setParameters("networkId", networkId); + return listBy(sc); + } + + @Override + public NicNetworkMapVO findByNicIdAndNetworkId(long nicId, long networkId) { + SearchCriteria<NicNetworkMapVO> sc = AllFieldsSearch.create(); + sc.setParameters("nicId", nicId); + sc.setParameters("networkId", networkId); + return findOneBy(sc); + } + + @Override + public List<Long> listNicIdsByNetworkId(long networkId) { + SearchCriteria<Long> sc = NicIdsByNetworkSearch.create(); + sc.setParameters("networkId", networkId); + return customSearch(sc, null); + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapVO.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapVO.java new file mode 100644 index 00000000000..79344535460 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicNetworkMapVO.java @@ -0,0 +1,140 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import java.util.Date; +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import com.cloud.utils.db.GenericDao; + +@Entity +@Table(name = "nic_network_map") +public class NicNetworkMapVO implements Identity, InternalIdentity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid = UUID.randomUUID().toString(); + + @Column(name = "nic_id") + private long nicId; + + @Column(name = "network_id") + private long networkId; + + @Column(name = "ip4_address") + private String ip4Address; + + @Column(name = "ip6_address") + private String ip6Address; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + private Date removed; + + protected NicNetworkMapVO() { + } + + public NicNetworkMapVO(long nicId, long networkId) { + this.nicId = nicId; + this.networkId = networkId; + } + + public NicNetworkMapVO(long nicId, long networkId, String ip4Address, String ip6Address) { + this.nicId = nicId; + this.networkId = networkId; + this.ip4Address = ip4Address; + this.ip6Address = ip6Address; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public long getNicId() { + return nicId; + } + + public void setNicId(long nicId) { + this.nicId = nicId; + } + + public long getNetworkId() { + return networkId; + } + + public void setNetworkId(long networkId) { + this.networkId = networkId; + } + + public String getIp4Address() { + return ip4Address; + } + + public void setIp4Address(String ip4Address) { + this.ip4Address = ip4Address; + } + + public String getIp6Address() { + return ip6Address; + } + + public void setIp6Address(String ip6Address) { + this.ip6Address = ip6Address; + } + + public Date getCreated() { + return created; + } + + public Date getRemoved() { + return removed; + } + + @Override + public String toString() { + return String.format("NicNetworkMap %s", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "nicId", "networkId", "ip4Address", "ip6Address")); + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 932db538f30..e89d87242b2 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -135,6 +135,7 @@ <bean id="networkServiceMapDaoImpl" class="com.cloud.network.dao.NetworkServiceMapDaoImpl" /> <bean id="nicDetailsDaoImpl" class="com.cloud.vm.dao.NicDetailsDaoImpl" /> <bean id="nicExtraDhcpOptionDaoImpl" class="com.cloud.vm.dao.NicExtraDhcpOptionDaoImpl" /> + <bean id="nicNetworkMapDaoImpl" class="com.cloud.vm.dao.NicNetworkMapDaoImpl" /> <bean id="nicSecondaryIpDaoImpl" class="com.cloud.vm.dao.NicSecondaryIpDaoImpl" /> <bean id="nicIpAliasDaoImpl" class="com.cloud.vm.dao.NicIpAliasDaoImpl" /> <bean id="objectInDataStoreDaoImpl" class="org.apache.cloudstack.storage.db.ObjectInDataStoreDaoImpl" /> diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql index 7c11013a17d..aaedd564b77 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42300to2400.sql @@ -18,3 +18,25 @@ --; -- Schema upgrade from 4.23.0.0 to 24.0.0 --; + +-- Multi-VLAN trunk nics: a nic may be associated with additional networks beyond its primary nics.network_id. +CREATE TABLE IF NOT EXISTS `cloud`.`nic_network_map` ( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `uuid` varchar(40), + `nic_id` bigint unsigned NOT NULL COMMENT 'nic this association belongs to', + `network_id` bigint unsigned NOT NULL COMMENT 'additional network this nic is associated with', + `ip4_address` char(40) COMMENT 'ip4 address assigned to this nic from this network', + `ip6_address` char(40) COMMENT 'ip6 address assigned to this nic from this network', + `created` datetime NOT NULL COMMENT 'date created', + `removed` datetime COMMENT 'date removed if not null', + PRIMARY KEY (`id`), + CONSTRAINT `fk_nic_network_map__nic_id` FOREIGN KEY (`nic_id`) REFERENCES `nics`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_nic_network_map__network_id` FOREIGN KEY (`network_id`) REFERENCES `networks`(`id`), + CONSTRAINT `uc_nic_network_map__uuid` UNIQUE (`uuid`), + UNIQUE KEY `uk_nic_network_map__nic_id_network_id` (`nic_id`, `network_id`), + INDEX `i_nic_network_map__nic_id` (`nic_id`), + INDEX `i_nic_network_map__network_id` (`network_id`), + INDEX `i_nic_network_map__removed` (`removed`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics', 'multi_network', 'tinyint(1) NOT NULL DEFAULT 0 COMMENT "true if this nic has additional network associations in nic_network_map"'); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 8e8ae314312..71c862ccfc2 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -23,6 +23,8 @@ import static com.cloud.host.Host.HOST_VDDK_LIB_DIR; import static com.cloud.host.Host.HOST_VDDK_SUPPORT; import static com.cloud.host.Host.HOST_VDDK_VERSION; import static com.cloud.host.Host.HOST_VIRTV2V_VERSION; +import static com.cloud.host.Host.HOST_VLAN_FILTERING_ENABLED; +import static com.cloud.host.Host.HOST_VLAN_TRUNK_XML_SUPPORTED; import static com.cloud.host.Host.HOST_VOLUME_ENCRYPTION; import static org.apache.cloudstack.utils.linux.KVMHostInfo.isHostS390x; @@ -352,6 +354,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv * It is supported since Libvirt 0.9.0 */ private static final int MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE = 9000; + /** + * Libvirt supports multi-tag trunk mode (<vlan trunk='yes'>) on a standard Linux bridge since 11.0.0. + */ + private static final long MIN_LIBVIRT_VERSION_FOR_VLAN_TRUNK = 11000000; /** * Constant that defines ARM64 (aarch64) guest architectures. */ @@ -4415,6 +4421,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv cmd.getHostDetails().put(HOST_INSTANCE_CONVERSION, String.valueOf(instanceConversionSupported)); cmd.getHostDetails().put(HOST_VDDK_SUPPORT, String.valueOf(hostSupportsVddk())); cmd.getHostDetails().put(HOST_CDROM_MAX_COUNT, String.valueOf(LibvirtVMDef.MAX_CDROMS_PER_VM)); + cmd.getHostDetails().put(HOST_VLAN_FILTERING_ENABLED, String.valueOf(hostSupportsVlanFiltering())); + cmd.getHostDetails().put(HOST_VLAN_TRUNK_XML_SUPPORTED, String.valueOf(hostSupportsVlanTrunkXml())); if (StringUtils.isNotBlank(vddkLibDir)) { cmd.getHostDetails().put(HOST_VDDK_LIB_DIR, vddkLibDir); } @@ -6236,6 +6244,39 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return exitValue == 0; } + /** + * Static capability: whether this host's libvirt understands multi-tag trunk vlan XML + * (<vlan trunk='yes'>) on a standard Linux bridge interface. + */ + public boolean hostSupportsVlanTrunkXml() { + return hypervisorLibvirtVersion >= MIN_LIBVIRT_VERSION_FOR_VLAN_TRUNK; + } + + /** + * Live state: whether vlan_filtering is currently enabled on this host's guest bridge. + * A vlan_filtering=0 bridge floods every tagged frame to every port regardless of tag, + * so this must be true before any multi-VLAN trunk nic can be placed on this host. + */ + public boolean hostSupportsVlanFiltering() { + return isBridgeVlanFilteringEnabled(guestBridgeName); + } + + protected boolean isBridgeVlanFilteringEnabled(String bridgeName) { + if (StringUtils.isBlank(bridgeName)) { + return false; + } + File vlanFilteringFile = new File("/sys/class/net/" + bridgeName + "/bridge/vlan_filtering"); + if (!vlanFilteringFile.exists()) { + return false; + } + try { + return "1".equals(FileUtils.readFileToString(vlanFilteringFile).trim()); + } catch (IOException e) { + LOGGER.warn("Failed to read vlan_filtering state for bridge " + bridgeName, e); + return false; + } + } + public boolean hostSupportsVddk() { return hostSupportsVddk(null); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java index 5a7d6d2c203..8af4e5f95d0 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java @@ -59,6 +59,9 @@ public final class LibvirtReadyCommandWrapper extends CommandWrapper<ReadyComman hostDetails.put(Host.HOST_OVFTOOL_VERSION, libvirtComputingResource.getHostOvfToolVersion()); } + hostDetails.put(Host.HOST_VLAN_FILTERING_ENABLED, Boolean.toString(libvirtComputingResource.hostSupportsVlanFiltering())); + hostDetails.put(Host.HOST_VLAN_TRUNK_XML_SUPPORTED, Boolean.toString(libvirtComputingResource.hostSupportsVlanTrunkXml())); + return new ReadyAnswer(command, hostDetails); } diff --git a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java index 2e4025ca001..f942a38746c 100644 --- a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java @@ -283,6 +283,8 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements } else { hostResponse.setUefiCapability(new Boolean(false)); } + hostResponse.setVlanFilteringEnabled(Boolean.parseBoolean((String) hostDetails.get(Host.HOST_VLAN_FILTERING_ENABLED))); + hostResponse.setVlanTrunkXmlSupported(Boolean.parseBoolean((String) hostDetails.get(Host.HOST_VLAN_TRUNK_XML_SUPPORTED))); } if (details.contains(HostDetails.all) && Arrays.asList(Hypervisor.HypervisorType.KVM,
