2020package com .cloud .hypervisor .kvm .resource ;
2121
2222import java .io .File ;
23+ import java .net .URI ;
2324import java .util .ArrayList ;
25+ import java .util .LinkedHashSet ;
2426import java .util .List ;
2527import java .util .Map ;
28+ import java .util .Set ;
29+ import java .util .concurrent .ConcurrentHashMap ;
2630import java .util .regex .Matcher ;
2731import java .util .regex .Pattern ;
2832
3337import org .apache .commons .lang3 .StringUtils ;
3438import org .libvirt .LibvirtException ;
3539
40+ import com .cloud .agent .api .to .NetworkTO ;
3641import com .cloud .agent .api .to .NicTO ;
3742import com .cloud .agent .properties .AgentProperties ;
3843import com .cloud .agent .properties .AgentPropertiesFileHandler ;
4348
4449public class BridgeVifDriver extends VifDriverBase {
4550
51+ private static final String GUEST_UPLINK_TRUNK_VLAN_RANGE = "2-4094" ;
52+
4653 private int _timeout ;
4754
4855 private final Object _vnetBridgeMonitor = new Object ();
@@ -51,6 +58,7 @@ public class BridgeVifDriver extends VifDriverBase {
5158 private String _macIpScriptPath ;
5259 private String _controlCidr = NetUtils .getLinkLocalCIDR ();
5360 private Long libvirtVersion ;
61+ private final Set <String > uplinkVlanTrunkEnsuredBridges = ConcurrentHashMap .newKeySet ();
5462
5563 private static boolean isVxlanOrNetris (String protocol ) {
5664 return protocol .equals (Networks .BroadcastDomainType .Vxlan .scheme ()) || protocol .equals (Networks .BroadcastDomainType .Netris .scheme ());
@@ -199,6 +207,102 @@ protected boolean isValidProtocolAndVnetId(final String vNetId, final String pro
199207 return vNetId != null && protocol != null && !vNetId .equalsIgnoreCase ("untagged" );
200208 }
201209
210+ protected void plugTrunkVlanNic (LibvirtVMDef .InterfaceDef intf , NicTO nic , String trafficLabel , String guestOsType , String nicAdapter ,
211+ Integer networkRateKBps ) throws InternalErrorException {
212+ if (nic .getBroadcastType () != Networks .BroadcastDomainType .Vlan ) {
213+ throw new InternalErrorException ("Multi-VLAN trunk nics are only supported on VLAN-isolated guest networks" );
214+ }
215+ if (!_libvirtComputingResource .hostSupportsVlanFiltering ()) {
216+ throw new InternalErrorException ("vlan_filtering is not enabled on this host's guest bridge; "
217+ + "this host cannot accept a multi-VLAN trunk nic" );
218+ }
219+
220+ String brName = trafficLabel != null && !trafficLabel .isEmpty () ? trafficLabel : _bridges .get ("guest" );
221+
222+ ensureUplinkAllowsAllVlans (brName );
223+
224+ List <Integer > vlanTags = collectTrunkVlanTags (nic );
225+
226+ logger .debug ("plugging trunk nic " + nic .getMac () + " onto guest bridge " + brName + " with vlan tags " + vlanTags );
227+ intf .defBridgeNet (brName , null , nic .getMac (), getGuestNicModel (guestOsType , nicAdapter ), networkRateKBps );
228+
229+ if (_libvirtComputingResource .hostSupportsVlanTrunkXml ()) {
230+ intf .setTrunkVlanTags (vlanTags );
231+ }
232+ // else: older libvirt can't express trunk membership; ensureVlanTrunkMembership() applies it manually once the tap exists
233+ }
234+
235+ private List <Integer > collectTrunkVlanTags (NicTO nic ) throws InternalErrorException {
236+ Set <Integer > vlanTags = new LinkedHashSet <>();
237+ vlanTags .add (parseVlanTag (nic .getBroadcastUri (), "primary network of nic " + nic .getMac ()));
238+ if (nic .getAssociatedNetworks () != null ) {
239+ for (NetworkTO associatedNetwork : nic .getAssociatedNetworks ()) {
240+ if (associatedNetwork .getBroadcastType () != Networks .BroadcastDomainType .Vlan ) {
241+ throw new InternalErrorException ("Multi-VLAN trunk nics only support VLAN-isolated associated networks" );
242+ }
243+ vlanTags .add (parseVlanTag (associatedNetwork .getBroadcastUri (), "associated network " + associatedNetwork .getUuid ()));
244+ }
245+ }
246+ return new ArrayList <>(vlanTags );
247+ }
248+
249+ private Integer parseVlanTag (URI broadcastUri , String description ) throws InternalErrorException {
250+ String vlanValue = broadcastUri == null ? null : Networks .BroadcastDomainType .getValue (broadcastUri );
251+ if (StringUtils .isBlank (vlanValue )) {
252+ throw new InternalErrorException ("Cannot determine VLAN for " + description
253+ + ": no VLAN has been assigned yet (is the network implemented?). Refusing to plug this multi-VLAN trunk nic." );
254+ }
255+ try {
256+ return Integer .valueOf (vlanValue );
257+ } catch (NumberFormatException e ) {
258+ throw new InternalErrorException ("Invalid VLAN value '" + vlanValue + "' for " + description );
259+ }
260+ }
261+
262+ private void ensureUplinkAllowsAllVlans (String brName ) throws InternalErrorException {
263+ if (uplinkVlanTrunkEnsuredBridges .contains (brName )) {
264+ return ;
265+ }
266+ synchronized (_vnetBridgeMonitor ) {
267+ if (uplinkVlanTrunkEnsuredBridges .contains (brName )) {
268+ return ;
269+ }
270+ String uplinkPif = _pifs .get (brName );
271+ if (StringUtils .isBlank (uplinkPif )) {
272+ throw new InternalErrorException ("Cannot determine the uplink interface for guest bridge " + brName
273+ + "; refusing to plug a multi-VLAN trunk nic" );
274+ }
275+ runBridgeVlanCommand ("add" , uplinkPif , GUEST_UPLINK_TRUNK_VLAN_RANGE );
276+ uplinkVlanTrunkEnsuredBridges .add (brName );
277+ }
278+ }
279+
280+ @ Override
281+ public void ensureVlanTrunkMembership (LibvirtVMDef .InterfaceDef iface , NicTO nic ) throws InternalErrorException {
282+ if (!nic .isTrunkVlan () || _libvirtComputingResource .hostSupportsVlanTrunkXml ()) {
283+ return ;
284+ }
285+ String tapName = iface .getDevName ();
286+ if (StringUtils .isBlank (tapName )) {
287+ throw new InternalErrorException ("Cannot apply manual VLAN trunk membership: tap device name unknown for nic " + nic .getMac ());
288+ }
289+ for (Integer vlanTag : collectTrunkVlanTags (nic )) {
290+ runBridgeVlanCommand ("add" , tapName , String .valueOf (vlanTag ));
291+ }
292+ }
293+
294+ protected void runBridgeVlanCommand (String operation , String dev , String vid ) throws InternalErrorException {
295+ final Script command = new Script ("bridge" , _timeout , logger );
296+ command .add ("vlan" );
297+ command .add (operation );
298+ command .add ("dev" , dev );
299+ command .add ("vid" , vid );
300+ final String result = command .execute ();
301+ if (result != null ) {
302+ throw new InternalErrorException ("Failed to " + operation + " VLAN " + vid + " membership on " + dev + ": " + result );
303+ }
304+ }
305+
202306 protected String createStorageVnetBridgeIfNeeded (NicTO nic , String trafficLabel ,
203307 String storageBrName ) throws InternalErrorException {
204308 if (nic .getBroadcastUri () == null ) {
@@ -248,7 +352,9 @@ public LibvirtVMDef.InterfaceDef plug(NicTO nic, String guestOsType, String nicA
248352 }
249353
250354 if (nic .getType () == Networks .TrafficType .Guest ) {
251- if (isBroadcastTypeVlanOrVxlan (nic ) && isValidProtocolAndVnetId (vNetId , protocol )) {
355+ if (nic .isTrunkVlan ()) {
356+ plugTrunkVlanNic (intf , nic , trafficLabel , guestOsType , nicAdapter , networkRateKBps );
357+ } else if (isBroadcastTypeVlanOrVxlan (nic ) && isValidProtocolAndVnetId (vNetId , protocol )) {
252358 if (trafficLabel != null && !trafficLabel .isEmpty ()) {
253359 logger .debug ("creating a vNet dev and bridge for guest traffic per traffic label " + trafficLabel );
254360 String brName = createVnetBr (vNetId , trafficLabel , protocol );
0 commit comments