diff --git a/.travis.yml b/.travis.yml index 57130261a8d..2d2450142c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,6 +88,7 @@ env: smoke/test_portable_publicip smoke/test_portforwardingrules smoke/test_privategw_acl + smoke/test_privategw_acl_ovs_gre smoke/test_projects smoke/test_public_ip_range" 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 5dffe905b41..4b0b2b17609 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 @@ -1806,7 +1806,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return true; } - public synchronized boolean configureTunnelNetwork(final long networkId, + public synchronized boolean configureTunnelNetwork(final Long networkId, final long hostId, final String nwName) { try { final boolean findResult = findOrCreateTunnelNetwork(nwName); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java index d39ee0d4a99..185ef898c8a 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java @@ -159,9 +159,8 @@ public class OvsVifDriver extends VifDriverBase { String brName = (trafficLabel != null && !trafficLabel.isEmpty()) ? _pifs.get(trafficLabel) : _pifs.get("private"); intf.defBridgeNet(brName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); } else if (nic.getBroadcastType() == Networks.BroadcastDomainType.Vswitch) { - String vnetId = Networks.BroadcastDomainType.getValue(nic.getBroadcastUri()); - String brName = "OVSTunnel" + vnetId; - s_logger.debug("nic " + nic + " needs to be connected to LogicalSwitch " + brName); + String brName = getOvsTunnelNetworkName(nic.getBroadcastUri().getAuthority()); + s_logger.debug("nic " + nic + " needs to be connected to Open vSwitch bridge " + brName); intf.defBridgeNet(brName, null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); } else { intf.defBridgeNet(_bridges.get("guest"), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps); @@ -193,6 +192,19 @@ public class OvsVifDriver extends VifDriverBase { return intf; } + private String getOvsTunnelNetworkName(final String broadcastUri) { + if (broadcastUri.contains(".")) { + final String[] parts = broadcastUri.split("\\."); + return "OVS-DR-VPC-Bridge" + parts[0]; + } else { + try { + return "OVSTunnel" + broadcastUri; + } catch (final Exception e) { + return null; + } + } + } + @Override public void unplug(InterfaceDef iface, boolean deleteBr) { // Libvirt apparently takes care of this, see BridgeVifDriver unplug diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java index 3da77d5f6c4..6f6a36b48af 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java @@ -19,6 +19,7 @@ package com.cloud.hypervisor.kvm.resource.wrapper; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; @@ -40,9 +41,18 @@ public final class LibvirtOvsFetchInterfaceCommandWrapper extends CommandWrapper s_logger.debug("Will look for network with name-label:" + label); try { - final String ipadd = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'"); - final String mask = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f4"); - final String mac = Script.runSimpleBashScript("ifconfig " + label + " | grep HWaddr | awk -F \" \" '{print $5}'"); + String ipadd = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'"); + if (StringUtils.isEmpty(ipadd)) { + ipadd = Script.runSimpleBashScript("ifconfig " + label + " | grep ' inet ' | awk '{ print $2}'"); + } + String mask = Script.runSimpleBashScript("ifconfig " + label + " | grep 'inet addr:' | cut -d: -f4"); + if (StringUtils.isEmpty(mask)) { + mask = Script.runSimpleBashScript("ifconfig " + label + " | grep ' inet ' | awk '{ print $4}'"); + } + String mac = Script.runSimpleBashScript("ifconfig " + label + " | grep HWaddr | awk -F \" \" '{print $5}'"); + if (StringUtils.isEmpty(mac)) { + mac = Script.runSimpleBashScript("ifconfig " + label + " | grep ' ether ' | awk '{ print $2}'"); + } return new OvsFetchInterfaceAnswer(command, true, "Interface " + label + " retrieved successfully", ipadd, mask, mac); diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index 04bbcb0272f..bef6c41ad78 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -884,33 +884,18 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe // Invoke plugin to setup the bridge which will be used by this // network final String bridge = nw.getBridge(conn); - final Map nwOtherConfig = nw.getOtherConfig(conn); - final String configuredHosts = nwOtherConfig.get("ovs-host-setup"); - boolean configured = false; - if (configuredHosts != null) { - final String hostIdsStr[] = configuredHosts.split(","); - for (final String hostIdStr : hostIdsStr) { - if (hostIdStr.equals(((Long)hostId).toString())) { - configured = true; - break; - } - } + String result; + if (bridgeName.startsWith("OVS-DR-VPC-Bridge")) { + result = callHostPlugin(conn, "ovstunnel", "setup_ovs_bridge_for_distributed_routing", "bridge", bridge, "key", bridgeName, "xs_nw_uuid", nw.getUuid(conn), "cs_host_id", + ((Long)hostId).toString()); + } else { + result = callHostPlugin(conn, "ovstunnel", "setup_ovs_bridge", "bridge", bridge, "key", bridgeName, "xs_nw_uuid", nw.getUuid(conn), "cs_host_id", ((Long)hostId).toString()); } - if (!configured) { - String result; - if (bridgeName.startsWith("OVS-DR-VPC-Bridge")) { - result = callHostPlugin(conn, "ovstunnel", "setup_ovs_bridge_for_distributed_routing", "bridge", bridge, "key", bridgeName, "xs_nw_uuid", nw.getUuid(conn), "cs_host_id", - ((Long)hostId).toString()); - } else { - result = callHostPlugin(conn, "ovstunnel", "setup_ovs_bridge", "bridge", bridge, "key", bridgeName, "xs_nw_uuid", nw.getUuid(conn), "cs_host_id", ((Long)hostId).toString()); - } - - // Note down the fact that the ovs bridge has been setup - final String[] res = result.split(":"); - if (res.length != 2 || !res[0].equalsIgnoreCase("SUCCESS")) { - throw new CloudRuntimeException("Unable to pre-configure OVS bridge " + bridge); - } + // Note down the fact that the ovs bridge has been setup + final String[] res = result.split(":"); + if (res.length != 2 || !res[0].equalsIgnoreCase("SUCCESS")) { + throw new CloudRuntimeException("Unable to pre-configure OVS bridge " + bridge); } return nw; } catch (final Exception e) { diff --git a/plugins/network-elements/ovs/src/main/java/com/cloud/network/guru/OvsGuestNetworkGuru.java b/plugins/network-elements/ovs/src/main/java/com/cloud/network/guru/OvsGuestNetworkGuru.java index 45969e7e972..7b0ccdbc07e 100644 --- a/plugins/network-elements/ovs/src/main/java/com/cloud/network/guru/OvsGuestNetworkGuru.java +++ b/plugins/network-elements/ovs/src/main/java/com/cloud/network/guru/OvsGuestNetworkGuru.java @@ -81,10 +81,14 @@ public class OvsGuestNetworkGuru extends GuestNetworkGuru { && _ntwkOfferingSrvcDao.areServicesSupportedByNetworkOffering( offering.getId(), Service.Connectivity)) { return true; + } else if (networkType == NetworkType.Advanced + && offering.getGuestType() == GuestType.Shared + && _ntwkOfferingSrvcDao.isProviderForNetworkOffering(offering.getId(), Network.Provider.Ovs) + && physicalNetwork.getIsolationMethods().contains("GRE")) { + return true; } else { - s_logger.trace("We only take care of Guest networks of type " - + GuestType.Isolated + " in zone of type " - + NetworkType.Advanced); + s_logger.trace(String.format("We only take care of Guest networks of type %s with Service %s or type with %s provider %s in %s zone", + GuestType.Isolated, Service.Connectivity, GuestType.Shared, Network.Provider.Ovs, NetworkType.Advanced)); return false; } } @@ -107,6 +111,9 @@ public class OvsGuestNetworkGuru extends GuestNetworkGuru { } config.setBroadcastDomainType(BroadcastDomainType.Vswitch); + if (config.getBroadcastUri() != null) { + config.setBroadcastUri(BroadcastDomainType.Vswitch.toUri(config.getBroadcastUri().toString().replace("vlan://", ""))); + } return config; } diff --git a/scripts/vm/hypervisor/xenserver/ovstunnel b/scripts/vm/hypervisor/xenserver/ovstunnel index 0ae99f595bc..72855cde443 100755 --- a/scripts/vm/hypervisor/xenserver/ovstunnel +++ b/scripts/vm/hypervisor/xenserver/ovstunnel @@ -106,9 +106,16 @@ def setup_ovs_bridge(session, args): "uuid=%s" % xs_nw_uuid, "param-name=other-config", "param-key=ovs-host-setup"]) - conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') - lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid, - "other-config:ovs-host-setup=%s" % conf_hosts]) + host_found = False + if conf_hosts: + setup_hosts = conf_hosts.split(",") + for host in setup_hosts: + if host == cs_host_id: + host_found = True + if not host_found: + conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') + lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid, + "other-config:ovs-host-setup=%s" % conf_hosts]) # BLOCK IPv6 - Flow spec changes with ovs version # Temporarily no need BLOCK IPv6 @@ -161,9 +168,16 @@ def setup_ovs_bridge_for_distributed_routing(session, args): "uuid=%s" % xs_nw_uuid, "param-name=other-config", "param-key=ovs-host-setup"]) - conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') - lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid, - "other-config:ovs-host-setup=%s" % conf_hosts]) + host_found = False + if conf_hosts: + setup_hosts = conf_hosts.split(",") + for host in setup_hosts: + if host == cs_host_id: + host_found = True + if not host_found: + conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') + lib.do_cmd([lib.XE_PATH, "network-param-set", "uuid=%s" % xs_nw_uuid, + "other-config:ovs-host-setup=%s" % conf_hosts]) # first clear the default rule (rule for 'NORMAL' processing which makes a bridge simple L2 learn & flood switch) lib.del_flows(bridge, table=0) diff --git a/scripts/vm/network/vnet/cloudstack_pluginlib.py b/scripts/vm/network/vnet/cloudstack_pluginlib.py index 87e504ed29e..8d036fbebc2 100755 --- a/scripts/vm/network/vnet/cloudstack_pluginlib.py +++ b/scripts/vm/network/vnet/cloudstack_pluginlib.py @@ -104,7 +104,7 @@ def do_cmd(cmd): "%s (stderr output:%s)" % (ret_code, err)) raise PluginError(err) output = proc.stdout.read() - if output.endswith('\n'): + if output.endswith(b'\n'): output = output[:-1] return output diff --git a/scripts/vm/network/vnet/ovstunnel.py b/scripts/vm/network/vnet/ovstunnel.py index 230055c0bf0..b6b30c1cb53 100755 --- a/scripts/vm/network/vnet/ovstunnel.py +++ b/scripts/vm/network/vnet/ovstunnel.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # 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 @@ -37,7 +37,7 @@ def setup_ovs_bridge(bridge, key, cs_host_id): res = lib.check_switch() if res != "SUCCESS": #return "FAILURE:%s" % res - return 'false' + return 'false' logging.debug("About to manually create the bridge:%s" % bridge) #set gre_key to bridge @@ -50,23 +50,23 @@ def setup_ovs_bridge(bridge, key, cs_host_id): logging.debug("Bridge has been manually created:%s" % res) if res: # result = "FAILURE:%s" % res - result = 'false' + result = 'false' else: # Verify the bridge actually exists, with the gre_key properly set res = lib.do_cmd([lib.VSCTL_PATH, "get", "bridge", bridge, "other_config:gre_key"]) - if key in res: + if key in str(res): # result = "SUCCESS:%s" % bridge result = 'true' else: # result = "FAILURE:%s" % res result = 'false' - lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:is-ovs-tun-network=True"]) + lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:is-ovs-tun-network=True"]) #get list of hosts using this bridge conf_hosts = lib.do_cmd([lib.VSCTL_PATH, "get","bridge", bridge,"other_config:ovs-host-setup"]) #add cs_host_id to list of hosts using this bridge - conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') + conf_hosts = cs_host_id + (conf_hosts and ',%s' % eval(conf_hosts) or '') lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:ovs-host-setup=%s" % conf_hosts]) @@ -92,7 +92,7 @@ def setup_ovs_bridge_for_distributed_routing(bridge, cs_host_id): res = lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:is-ovs_vpc_distributed_vr_network=True"]) conf_hosts = lib.do_cmd([lib.VSCTL_PATH, "get","bridge", bridge,"other:ovs-host-setup"]) - conf_hosts = cs_host_id + (conf_hosts and ',%s' % conf_hosts or '') + conf_hosts = cs_host_id + (conf_hosts and ',%s' % eval(conf_hosts) or '') lib.do_cmd([lib.VSCTL_PATH, "set", "bridge", bridge, "other_config:ovs-host-setup=%s" % conf_hosts]) @@ -162,7 +162,7 @@ def create_tunnel(bridge, remote_ip, key, src_host, dst_host): wait = [lib.VSCTL_PATH, "--timeout=30", "wait-until", "bridge", bridge, "--", "get", "bridge", bridge, "name"] res = lib.do_cmd(wait) - if bridge not in res: + if bridge not in str(res): logging.debug("WARNING:Can't find bridge %s for creating " + "tunnel!" % bridge) # return "FAILURE:NO_BRIDGE" @@ -185,7 +185,7 @@ def create_tunnel(bridge, remote_ip, key, src_host, dst_host): # Expecting python-style list as output iface_list = [] if len(res) > 2: - iface_list = res.strip()[1:-1].split(',') + iface_list = res.strip()[1:-1].split(b',') if len(iface_list) != 1: logging.debug("WARNING: Unexpected output while verifying " + "port %s on bridge %s" % (name, bridge)) @@ -202,7 +202,7 @@ def create_tunnel(bridge, remote_ip, key, src_host, dst_host): key_validation = lib.do_cmd(verify_interface_key) ip_validation = lib.do_cmd(verify_interface_ip) - if not key in key_validation or not remote_ip in ip_validation: + if not key in str(key_validation) or not remote_ip in str(ip_validation): logging.debug("WARNING: Unexpected output while verifying " + "interface %s on bridge %s" % (name, bridge)) # return "FAILURE:VERIFY_INTERFACE_FAILED" @@ -213,12 +213,18 @@ def create_tunnel(bridge, remote_ip, key, src_host, dst_host): iface_uuid, "ofport"] tun_ofport = lib.do_cmd(cmd_tun_ofport) # Ensure no trailing LF - if tun_ofport.endswith('\n'): + if tun_ofport.endswith(b'\n'): tun_ofport = tun_ofport[:-1] - ovs_tunnel_network = lib.do_cmd([lib.VSCTL_PATH, "get", "bridge", bridge, "other_config:is-ovs-tun-network"]) - ovs_vpc_distributed_vr_network = lib.do_cmd([lib.VSCTL_PATH, "get", "bridge", bridge, - "other_config:is-ovs_vpc_distributed_vr_network"]) + try: + ovs_tunnel_network = lib.do_cmd([lib.VSCTL_PATH, "get", "bridge", bridge, "other_config:is-ovs-tun-network"]) + except: + ovs_tunnel_network = 'False' + try: + ovs_vpc_distributed_vr_network = lib.do_cmd([lib.VSCTL_PATH, "get", "bridge", bridge, + "other_config:is-ovs_vpc_distributed_vr_network"]) + except: + ovs_vpc_distributed_vr_network = 'False' if ovs_tunnel_network == 'True': # add flow entryies for dropping broadcast coming in from gre tunnel diff --git a/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java index 07a563591d9..5629eb2d1c1 100644 --- a/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/DirectNetworkGuru.java @@ -148,8 +148,13 @@ public class DirectNetworkGuru extends AdapterBase implements NetworkGuru { && offering.getGuestType() == GuestType.Shared && !_ntwkOfferingSrvcDao.isProviderForNetworkOffering(offering.getId(), Network.Provider.NiciraNvp)) { return true; + } else if (dc.getNetworkType() == NetworkType.Advanced + && offering.getGuestType() == GuestType.Shared + && ! _ntwkOfferingSrvcDao.isProviderForNetworkOffering(offering.getId(), Network.Provider.Ovs) + && physnet.getIsolationMethods().contains("GRE")) { + return true; } else { - s_logger.trace("We only take care of Guest networks of type " + GuestType.Shared); + s_logger.trace("We only take care of Shared Guest networks without Ovs or NiciraNvp provider"); return false; } } diff --git a/test/integration/component/test_vpc_distributed_routing_offering.py b/test/integration/component/test_vpc_distributed_routing_offering.py index 7cd544bdcdd..baa8f9b5462 100644 --- a/test/integration/component/test_vpc_distributed_routing_offering.py +++ b/test/integration/component/test_vpc_distributed_routing_offering.py @@ -68,7 +68,7 @@ class Services: "name": 'VPC Network offering', "displaytext": 'VPC Network off', "guestiptype": 'Isolated', - "supportedservices": 'Vpn,Dhcp,Dns,SourceNat,PortForwarding,Lb,UserData,StaticNat,NetworkACL, Connectivity', + "supportedservices": 'Vpn,Dhcp,Dns,SourceNat,PortForwarding,Lb,UserData,StaticNat,NetworkACL,Connectivity', "traffictype": 'GUEST', "availability": 'Optional', "useVpc": 'on', @@ -535,7 +535,7 @@ class TestVPCDistributedRouterOffering(cloudstackTestCase): public_ips = PublicIPAddress.list( self.apiclient, - networkid=network.id, + associatednetworkid=network.id, listall=True, isstaticnat=True, account=self.account.name, diff --git a/test/integration/smoke/test_privategw_acl_ovs_gre.py b/test/integration/smoke/test_privategw_acl_ovs_gre.py new file mode 100644 index 00000000000..94e5c3c839a --- /dev/null +++ b/test/integration/smoke/test_privategw_acl_ovs_gre.py @@ -0,0 +1,707 @@ +# 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. +from marvin.cloudstackAPI.createStaticRoute import createStaticRouteCmd +""" Tests for Network ACLs in VPC +""" +#Import Local Modules +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.lib.utils import * +from marvin.lib.base import * +from marvin.lib.common import * +from nose.plugins.attrib import attr +from marvin.codes import PASS + +import time +import logging +import random + + +class Services: + """Test ACL on private gateway in VPC, in Openvswitch/GRE environments + """ + + def __init__(self): + self.services = { + "configurableData": { + "host": { + "port": 22 + } + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "host1": None, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + "memory": 128, + }, + "network_offering": { + "name": 'VPC Network offering', + "displaytext": 'VPC Network off', + "guestiptype": 'Isolated', + "supportedservices": 'Vpn,Dhcp,Dns,SourceNat,PortForwarding,Lb,UserData,StaticNat,NetworkACL,Connectivity', + "traffictype": 'GUEST', + "availability": 'Optional', + "useVpc": 'on', + "serviceProviderList": { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "Connectivity": 'Ovs', + "NetworkACL": 'VpcVirtualRouter' + }, + }, + "network_offering_no_lb": { + "name": 'VPC Network offering', + "displaytext": 'VPC Network off', + "guestiptype": 'Isolated', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding,UserData,StaticNat,NetworkACL,Connectivity', + "traffictype": 'GUEST', + "availability": 'Optional', + "useVpc": 'on', + "serviceProviderList": { + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "Connectivity": 'Ovs', + "NetworkACL": 'VpcVirtualRouter' + }, + }, + "vpc_offering": { + "name": "VPC off", + "displaytext": "VPC off", + "supportedservices": + "Dhcp,Dns,SourceNat,PortForwarding,Vpn,Lb,UserData,StaticNat,NetworkACL,Connectivity", + "serviceProviderList": { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "Connectivity": 'Ovs', + "NetworkACL": 'VpcVirtualRouter' + }, + }, + "vpc": { + "name": "TestVPC", + "displaytext": "TestVPC", + "cidr": '10.0.0.1/24' + }, + "network": { + "name": "Test Network", + "displaytext": "Test Network", + "netmask": '255.255.255.0' + }, + "virtual_machine": { + "displayname": "Test VM", + "username": "root", + "password": "password", + "ssh_port": 22, + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "natrule": { + "privateport": 22, + "publicport": 22, + "startport": 22, + "endport": 22, + "protocol": "TCP", + "cidrlist": '0.0.0.0/0', + }, + "ostype": 'CentOS 5.3 (64-bit)', + "timeout": 10, + } + +class TestPrivateGwACLOvsGRE(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + cls.testClient = super(TestPrivateGwACLOvsGRE, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + cls.template = get_template( + cls.api_client, + cls.zone.id, + cls.hypervisor) + + cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"]) + cls._cleanup = [cls.service_offering] + + cls.logger = logging.getLogger('TestPrivateGwACLOvsGRE') + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.hypervisor = self.testClient.getHypervisorInfo() + + self.physical_network = self.get_guest_traffic_physical_network(self.apiclient, self.zone.id) + if not self.physical_network: + self.skipTest("No Guest Physical Networks with GRE isolation found!") + + self.vlan = self.get_free_vlan() + + self.logger.debug("Creating Admin Account for Domain ID ==> %s" % self.domain.id) + self.account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id) + + self.cleanup = [] + return + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def _replaceAcl(self, command): + try: + successResponse = self.apiclient.replaceNetworkACLList(command); + except Exception as e: + self.fail("Failed to replace ACL list due to %s" % e) + + self.assertTrue(successResponse.success, "Failed to replace ACL list.") + + @attr(tags=["advanced"], required_hardware="true") + def test_01_vpc_privategw_acl(self): + self.logger.debug("Creating a VPC offering..") + vpc_off = VpcOffering.create( + self.apiclient, + self.services["vpc_offering"]) + + self.logger.debug("Enabling the VPC offering created") + vpc_off.update(self.apiclient, state='Enabled') + + vpc = self.createVPC(vpc_off) + + self.cleanup = [vpc, vpc_off, self.account] + + acl = self.createACL(vpc) + self.createACLItem(acl.id) + self.createNetwork(vpc) + privateGw = self.createPvtGw(vpc, "10.0.3.99", "10.0.3.100", acl.id, self.vlan) + self.replacePvtGwACL(acl.id, privateGw.id) + + @attr(tags=["advanced"], required_hardware="true") + def test_03_vpc_privategw_restart_vpc_cleanup(self): + self.logger.debug("Creating a VPC offering..") + vpc_off = VpcOffering.create( + self.apiclient, + self.services["vpc_offering"]) + + self.logger.debug("Enabling the VPC offering created") + vpc_off.update(self.apiclient, state='Enabled') + + self.performVPCTests(vpc_off, restart_with_cleanup = True) + + @attr(tags=["advanced"], required_hardware="true") + def test_05_vpc_privategw_check_interface(self): + self.logger.debug("Creating a VPC offering..") + vpc_off = VpcOffering.create( + self.apiclient, + self.services["vpc_offering"]) + + self.logger.debug("Enabling the VPC offering created") + vpc_off.update(self.apiclient, state='Enabled') + + self.performPrivateGWInterfaceTests(vpc_off) + + def performVPCTests(self, vpc_off, restart_with_cleanup = False): + self.logger.debug("Creating VPCs with offering ID %s" % vpc_off.id) + vpc_1 = self.createVPC(vpc_off, cidr = '10.0.1.0/24') + vpc_2 = self.createVPC(vpc_off, cidr = '10.0.2.0/24') + + self.cleanup = [vpc_1, vpc_2, vpc_off, self.account] + + network_1 = self.createNetwork(vpc_1, gateway = '10.0.1.1') + network_2 = self.createNetwork(vpc_2, gateway = '10.0.2.1') + + vm1 = self.createVM(network_1) + vm2 = self.createVM(network_2) + + self.cleanup.insert(0, vm1) + self.cleanup.insert(0, vm2) + + acl1 = self.createACL(vpc_1) + self.createACLItem(acl1.id, cidr = "0.0.0.0/0") + privateGw_1 = self.createPvtGw(vpc_1, "10.0.3.100", "10.0.3.101", acl1.id, self.vlan) + self.replacePvtGwACL(acl1.id, privateGw_1.id) + + acl2 = self.createACL(vpc_2) + self.createACLItem(acl2.id, cidr = "0.0.0.0/0") + privateGw_2 = self.createPvtGw(vpc_2, "10.0.3.101", "10.0.3.100", acl2.id, self.vlan) + self.replacePvtGwACL(acl2.id, privateGw_2.id) + + self.replaceNetworkAcl(acl1.id, network_1) + self.replaceNetworkAcl(acl2.id, network_2) + + staticRoute_1 = self.createStaticRoute(privateGw_1.id, cidr = '10.0.2.0/24') + staticRoute_2 = self.createStaticRoute(privateGw_2.id, cidr = '10.0.1.0/24') + + public_ip_1 = self.acquire_publicip(vpc_1, network_1) + public_ip_2 = self.acquire_publicip(vpc_2, network_2) + + nat_rule_1 = self.create_natrule(vpc_1, vm1, public_ip_1, network_1) + nat_rule_2 = self.create_natrule(vpc_2, vm2, public_ip_2, network_2) + + self.check_pvt_gw_connectivity(vm1, public_ip_1, [vm2.nic[0].ipaddress, vm1.nic[0].ipaddress]) + self.check_pvt_gw_connectivity(vm2, public_ip_2, [vm2.nic[0].ipaddress, vm1.nic[0].ipaddress]) + + if restart_with_cleanup: + self.reboot_vpc_with_cleanup(vpc_1, cleanup = restart_with_cleanup) + self.reboot_vpc_with_cleanup(vpc_2, cleanup = restart_with_cleanup) + time.sleep(30) + self.check_pvt_gw_connectivity(vm1, public_ip_1, [vm2.nic[0].ipaddress, vm1.nic[0].ipaddress]) + self.check_pvt_gw_connectivity(vm2, public_ip_2, [vm2.nic[0].ipaddress, vm1.nic[0].ipaddress]) + + vm1.migrate(self.apiclient) + vm2.migrate(self.apiclient) + self.check_pvt_gw_connectivity(vm1, public_ip_1, [vm2.nic[0].ipaddress, vm1.nic[0].ipaddress]) + self.check_pvt_gw_connectivity(vm2, public_ip_2, [vm2.nic[0].ipaddress, vm1.nic[0].ipaddress]) + + def performPrivateGWInterfaceTests(self, vpc_off): + self.logger.debug("Creating VPCs with offering ID %s" % vpc_off.id) + vpc_1 = self.createVPC(vpc_off, cidr = '10.0.0.0/16') + + self.cleanup = [vpc_1, vpc_off, self.account] + + acl1 = self.createACL(vpc_1) + self.createACLItem(acl1.id, cidr = "0.0.0.0/0") + net_offering_no_lb = "network_offering_no_lb" + + network_1 = self.createNetwork(vpc_1, gateway = '10.0.0.1') + network_2 = self.createNetwork(vpc_1, net_offering = net_offering_no_lb, gateway = '10.0.1.1') + network_3 = self.createNetwork(vpc_1, net_offering = net_offering_no_lb, gateway = '10.0.2.1') + network_4 = self.createNetwork(vpc_1, net_offering = net_offering_no_lb, gateway = '10.0.3.1') + + vm1 = self.createVM(network_1) + vm2 = self.createVM(network_2) + vm3 = self.createVM(network_3) + vm4 = self.createVM(network_4) + + self.cleanup.insert(0, vm1) + self.cleanup.insert(0, vm2) + self.cleanup.insert(0, vm3) + self.cleanup.insert(0, vm4) + + acl1 = self.createACL(vpc_1) + self.createACLItem(acl1.id, cidr = "0.0.0.0/0") + + self.replaceNetworkAcl(acl1.id, network_1) + self.replaceNetworkAcl(acl1.id, network_2) + self.replaceNetworkAcl(acl1.id, network_3) + self.replaceNetworkAcl(acl1.id, network_4) + + public_ip_1 = self.acquire_publicip(vpc_1, network_1) + nat_rule_1 = self.create_natrule(vpc_1, vm1, public_ip_1, network_1) + + public_ip_2 = self.acquire_publicip(vpc_1, network_2) + nat_rule_2 = self.create_natrule(vpc_1, vm2, public_ip_2, network_2) + + self.check_pvt_gw_connectivity(vm1, public_ip_1, [vm2.nic[0].ipaddress, vm3.nic[0].ipaddress, vm4.nic[0].ipaddress]) + self.check_pvt_gw_connectivity(vm2, public_ip_2, [vm2.nic[0].ipaddress, vm3.nic[0].ipaddress, vm4.nic[0].ipaddress]) + + self.reboot_vpc_with_cleanup(vpc_1, cleanup = True) + self.check_pvt_gw_connectivity(vm1, public_ip_1, [vm2.nic[0].ipaddress, vm3.nic[0].ipaddress, vm4.nic[0].ipaddress]) + self.check_pvt_gw_connectivity(vm2, public_ip_2, [vm2.nic[0].ipaddress, vm3.nic[0].ipaddress, vm4.nic[0].ipaddress]) + + self.reboot_routers() + self.check_pvt_gw_connectivity(vm1, public_ip_1, [vm2.nic[0].ipaddress, vm3.nic[0].ipaddress, vm4.nic[0].ipaddress]) + self.check_pvt_gw_connectivity(vm2, public_ip_2, [vm2.nic[0].ipaddress, vm3.nic[0].ipaddress, vm4.nic[0].ipaddress]) + + def query_routers(self): + routers = list_routers(self.apiclient, + account=self.account.name, + domainid=self.account.domainid) + + self.assertEqual(isinstance(routers, list), True, + "Check for list routers response return valid data") + + self.assertEqual(len(routers), 1, + "Check for list routers size returned '%s' instead of 2" % len(routers)) + + return routers + + def reboot_routers(self): + self.logger.debug('Rebooting routers or Starting stopped routers') + routers = self.query_routers() + for router in routers: + self.logger.debug('Router %s has state %s' % (router.id, router.state)) + if router.state == "Stopped": + self.logger.debug('Starting stopped router %s' % router.id) + cmd = startRouter.startRouterCmd() + cmd.id = router.id + self.apiclient.startRouter(cmd) + else: + self.logger.debug('Rebooting router %s' % router.id) + cmd = rebootRouter.rebootRouterCmd() + cmd.id = router.id + self.apiclient.rebootRouter(cmd) + + def createVPC(self, vpc_offering, cidr = '10.1.1.1/16'): + try: + self.logger.debug("Creating a VPC network in the account: %s" % self.account.name) + self.services["vpc"]["cidr"] = cidr + + vpc = VPC.create( + self.apiclient, + self.services["vpc"], + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid) + + self.logger.debug("Created VPC with ID: %s" % vpc.id) + except Exception as e: + self.fail('Unable to create VPC due to %s ' % e) + + return vpc + + def createVM(self, network): + try: + self.logger.debug('Creating VM in network=%s' % network.name) + vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + networkids=[str(network.id)] + ) + self.logger.debug("Created VM with ID: %s" % vm.id) + except Exception as e: + self.fail('Unable to create virtual machine due to %s ' % e) + + return vm + + def createStaticRoute(self, privateGwId, cidr = '10.0.0.0/16'): + staticRouteCmd = createStaticRoute.createStaticRouteCmd() + staticRouteCmd.cidr = cidr + staticRouteCmd.gatewayid = privateGwId + + try: + staticRoute = self.apiclient.createStaticRoute(staticRouteCmd) + self.assertIsNotNone(staticRoute.id, "Failed to create static route.") + + self.logger.debug("Created staticRoute with ID: %s" % staticRoute.id) + except Exception as e: + self.fail('Unable to create static route due to %s ' % e) + + return staticRoute + + def createACL(self, vpc): + createAclCmd = createNetworkACLList.createNetworkACLListCmd() + createAclCmd.name = "ACL-Test-%s" % vpc.id + createAclCmd.description = createAclCmd.name + createAclCmd.vpcid = vpc.id + try: + acl = self.apiclient.createNetworkACLList(createAclCmd) + self.assertIsNotNone(acl.id, "Failed to create ACL.") + + self.logger.debug("Created ACL with ID: %s" % acl.id) + except Exception as e: + self.fail('Unable to create ACL due to %s ' % e) + + return acl + + def createACLItem(self, aclId, cidr = "0.0.0.0/0"): + createAclItemCmd = createNetworkACL.createNetworkACLCmd() + createAclItemCmd.cidr = cidr + createAclItemCmd.protocol = "All" + createAclItemCmd.number = "1" + createAclItemCmd.action = "Allow" + createAclItemCmd.aclid = aclId + try: + aclItem = self.apiclient.createNetworkACL(createAclItemCmd) + self.assertIsNotNone(aclItem.id, "Failed to create ACL item.") + + self.logger.debug("Created ACL Item ID: %s" % aclItem.id) + except Exception as e: + self.fail('Unable to create ACL Item due to %s ' % e) + + def createNetwork(self, vpc, net_offering = "network_offering", gateway = '10.1.1.1'): + try: + self.logger.debug('Create NetworkOffering') + net_offerring = self.services[net_offering] + net_offerring["name"] = "NET_OFF-%s" % gateway + nw_off = NetworkOffering.create( + self.apiclient, + net_offerring, + conservemode=False) + + nw_off.update(self.apiclient, state='Enabled') + + self.logger.debug('Created and Enabled NetworkOffering') + + self.services["network"]["name"] = "NETWORK-%s" % gateway + + self.logger.debug('Adding Network=%s' % self.services["network"]) + obj_network = Network.create( + self.apiclient, + self.services["network"], + accountid=self.account.name, + domainid=self.account.domainid, + networkofferingid=nw_off.id, + zoneid=self.zone.id, + gateway=gateway, + vpcid=vpc.id + ) + + self.logger.debug("Created network with ID: %s" % obj_network.id) + except Exception as e: + self.fail('Unable to create a Network with offering=%s because of %s ' % (net_offerring, e)) + + self.cleanup.insert(0, nw_off) + self.cleanup.insert(0, obj_network) + + return obj_network + + def createPvtGw(self, vpc, ip_address, gateway, aclId, vlan): + physical_network = self.get_guest_traffic_physical_network(self.apiclient, self.zone.id) + if not physical_network: + self.fail("No Physical Networks found!") + + self.logger.debug('::: Physical Networks ::: ==> %s' % physical_network) + + createPrivateGatewayCmd = createPrivateGateway.createPrivateGatewayCmd() + createPrivateGatewayCmd.physicalnetworkid = physical_network.id + createPrivateGatewayCmd.gateway = gateway + createPrivateGatewayCmd.netmask = "255.255.255.0" + createPrivateGatewayCmd.ipaddress = ip_address + createPrivateGatewayCmd.vlan = vlan + createPrivateGatewayCmd.bypassvlanoverlapcheck = "true" + createPrivateGatewayCmd.vpcid = vpc.id + createPrivateGatewayCmd.sourcenatsupported = "false" + createPrivateGatewayCmd.aclid = aclId + + try: + privateGw = self.apiclient.createPrivateGateway(createPrivateGatewayCmd) + except Exception as e: + self.fail("Failed to create Private Gateway ==> %s" % e) + + self.assertIsNotNone(privateGw.id, "Failed to create Private Gateway.") + + return privateGw + + def deletePvtGw(self, private_gw_id): + deletePrivateGatewayCmd = deletePrivateGateway.deletePrivateGatewayCmd() + deletePrivateGatewayCmd.id = private_gw_id + + privateGwResponse = None + try: + privateGwResponse = self.apiclient.deletePrivateGateway(deletePrivateGatewayCmd) + except Exception as e: + self.fail("Failed to create Private Gateway ==> %s" % e) + + self.assertIsNotNone(privateGwResponse, "Failed to Delete Private Gateway.") + self.assertTrue(privateGwResponse.success, "Failed to Delete Private Gateway.") + + def replaceNetworkAcl(self, aclId, network): + self.logger.debug("Replacing Network ACL with ACL ID ==> %s" % aclId) + + replaceNetworkACLListCmd = replaceNetworkACLList.replaceNetworkACLListCmd() + replaceNetworkACLListCmd.aclid = aclId + replaceNetworkACLListCmd.networkid = network.id + + self._replaceAcl(replaceNetworkACLListCmd) + + def replacePvtGwACL(self, aclId, privateGwId): + self.logger.debug("Replacing Private GW ACL with ACL ID ==> %s" % aclId) + + replaceNetworkACLListCmd = replaceNetworkACLList.replaceNetworkACLListCmd() + replaceNetworkACLListCmd.aclid = aclId + replaceNetworkACLListCmd.gatewayid = privateGwId + + self._replaceAcl(replaceNetworkACLListCmd) + + def acquire_publicip(self, vpc, network): + self.logger.debug("Associating public IP for network: %s" % network.name) + public_ip = PublicIPAddress.create( + self.apiclient, + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + networkid=network.id, + vpcid=vpc.id + ) + self.logger.debug("Associated %s with network %s" % ( + public_ip.ipaddress.ipaddress, + network.id + )) + + return public_ip + + def create_natrule(self, vpc, virtual_machine, public_ip, network): + self.logger.debug("Creating NAT rule in network for vm with public IP") + + nat_service = self.services["natrule"] + nat_rule = NATRule.create( + self.apiclient, + virtual_machine, + nat_service, + ipaddressid=public_ip.ipaddress.id, + openfirewall=False, + networkid=network.id, + vpcid=vpc.id) + + self.logger.debug("Adding NetworkACL rules to make NAT rule accessible") + nwacl_nat = NetworkACL.create( + self.apiclient, + networkid=network.id, + services=nat_service, + traffictype='Ingress' + ) + self.logger.debug('nwacl_nat=%s' % nwacl_nat.__dict__) + + return nat_rule + + def check_pvt_gw_connectivity(self, virtual_machine, public_ip, vms_ips): + sleep_time = 5 + succeeded_pings = 0 + minimum_vms_to_pass = 2 + for vm_ip in vms_ips: + ssh_command = "ping -c 10 %s" % vm_ip + + # Should be able to SSH VM + packet_loss = 100 + try: + self.logger.debug("SSH into VM: %s" % public_ip.ipaddress.ipaddress) + + ssh = virtual_machine.get_ssh_client(ipaddress=public_ip.ipaddress.ipaddress) + + self.logger.debug("Sleeping for %s seconds in order to get the firewall applied..." % sleep_time) + time.sleep(sleep_time) + + self.logger.debug("Ping to VM inside another Network Tier") + result = ssh.execute(ssh_command) + + for line in result: + if "packet loss" in line: + packet_loss = int(line.split("% packet loss")[0].split(" ")[-1]) + break + + self.logger.debug("SSH result: %s; COUNT is ==> %s" % (result, packet_loss < 50)) + except Exception as e: + self.fail("SSH Access failed for %s: %s" % (virtual_machine, e)) + + if packet_loss < 50: + succeeded_pings += 1 + + + self.assertTrue(succeeded_pings >= minimum_vms_to_pass, + "Ping to VM on Network Tier N from VM in Network Tier A should be successful at least for 2 out of 3 VMs" + ) + + def reboot_vpc_with_cleanup(self, vpc, cleanup = True): + self.logger.debug("Restarting VPC %s with cleanup" % vpc.id) + + # Reboot the router + cmd = restartVPC.restartVPCCmd() + cmd.id = vpc.id + cmd.cleanup = cleanup + cmd.makeredundant = False + self.api_client.restartVPC(cmd) + + def get_guest_traffic_physical_network(self, apiclient, zoneid): + physical_networks = get_physical_networks(apiclient, zoneid) + if not physical_networks: + return None + for physical_network in physical_networks: + if not physical_network.removed and physical_network.vlan: + isolation_method_list = self.dbclient.execute( + "select isolation_method from physical_network_isolation_methods \ + where isolation_method = 'GRE' and physical_network_id=\ + (select id from physical_network where uuid='%s');" % physical_network.id + ) + if not isolation_method_list: + continue + traffic_type_list = self.dbclient.execute( + "select traffic_type from physical_network_traffic_types where physical_network_id=\ + (select id from physical_network where uuid='%s');" % physical_network.id + ) + for traffic_type in traffic_type_list: + if "Guest" in str(traffic_type[0]): + return physical_network + return None + + def get_free_vlan(self): + qresultset = self.dbclient.execute( + "select vnet from op_dc_vnet_alloc where physical_network_id=\ + (select id from physical_network where uuid='%s');" % self.physical_network.id) + self.assertEqual(validateList(qresultset)[0], + PASS, + "Invalid sql query response" + ) + + # Find all the vlans that are for dynamic vlan allocation + dc_vlans = sorted([x[0] for x in qresultset]) + + # Use VLAN id that is not in physical network vlan range for dynamic vlan allocation + vlan_1 = int(self.physical_network.vlan.split('-')[-1]) + 1 + if vlan_1 in dc_vlans: + vlan_1 = dc_vlans[-1] + random.randint(1, 5) + + return vlan_1; diff --git a/tools/marvin/marvin/lib/common.py b/tools/marvin/marvin/lib/common.py index 0c2140c30d8..07fbbca402f 100644 --- a/tools/marvin/marvin/lib/common.py +++ b/tools/marvin/marvin/lib/common.py @@ -1144,8 +1144,8 @@ def get_free_vlan(apiclient, zoneid): usedVlanIds = [] if isinstance(networks, list) and len(networks) > 0: - usedVlanIds = [int(nw.vlan) - for nw in networks if (nw.vlan and str(nw.vlan).lower() != "untagged")] + usedVlanIds = [int(nw.broadcasturi.split("//")[-1]) + for nw in networks if (nw.broadcasturi and nw.broadcasturi.lower() != "vlan://untagged")] ipranges = list_vlan_ipranges(apiclient, zoneid=zoneid) if isinstance(ipranges, list) and len(ipranges) > 0: