diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index eb468cbbbd4..3b5661f8a80 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -253,4 +253,8 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll public void setIpv6Routes(Set ipv6Routes) { this.ipv6Routes = ipv6Routes; } + + public Set getIpv6Routes() { + return ipv6Routes; + } } diff --git a/debian/changelog b/debian/changelog index 9bf3ad57848..63875316c87 100644 --- a/debian/changelog +++ b/debian/changelog @@ -4,6 +4,12 @@ cloudstack (4.18.0.0) unstable; urgency=low -- the Apache CloudStack project Tue, 31 May 2022 14:33:47 -0300 +cloudstack (4.17.0.1) unstable; urgency=low + + * Update the version to 4.17.0.1 + + -- the Apache CloudStack project Fri, 15 Jul 2022 18:18:39 +0530 + cloudstack (4.17.0.0) unstable; urgency=low * Update the version to 4.17.0.0 diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java index 65362eba3c7..a35e7914895 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java @@ -279,7 +279,7 @@ public class NetworkOfferingDaoImpl extends GenericDaoBase org.opensaml opensaml + ${cs.opensaml.version} org.apache.cloudstack diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java index 86034e72a8b..e5f1ade4d55 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/GetServiceProviderMetaDataCmd.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.auth.PluggableAPIAuthenticator; import org.apache.cloudstack.api.response.SAMLMetaDataResponse; import org.apache.cloudstack.saml.SAML2AuthManager; import org.apache.cloudstack.saml.SAMLProviderMetadata; +import org.apache.cloudstack.utils.security.ParserUtils; import org.apache.log4j.Logger; import org.opensaml.Configuration; import org.opensaml.DefaultBootstrap; @@ -239,7 +240,7 @@ public class GetServiceProviderMetaDataCmd extends BaseCmd implements APIAuthent StringWriter stringWriter = new StringWriter(); try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory factory = ParserUtils.getSaferDocumentBuilderFactory(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.newDocument(); Marshaller out = Configuration.getMarshallerFactory().getMarshaller(spEntityDescriptor); diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java index f0a5bab4c4a..ba85b151eea 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java @@ -78,7 +78,6 @@ import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.XMLObject; -import org.opensaml.xml.parse.BasicParserPool; import org.opensaml.xml.security.credential.UsageType; import org.opensaml.xml.security.keyinfo.KeyInfoHelper; import org.springframework.stereotype.Component; @@ -389,7 +388,7 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage } } _idpMetaDataProvider.setRequireValidMetadata(true); - _idpMetaDataProvider.setParserPool(new BasicParserPool()); + _idpMetaDataProvider.setParserPool(SAMLUtils.getSaferParserPool()); _idpMetaDataProvider.initialize(); _timer.scheduleAtFixedRate(new MetadataRefreshTask(), 0, _refreshInterval * 1000); diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java index c65e4c09be6..b5934a8ba30 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAMLUtils.java @@ -42,12 +42,15 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -56,6 +59,7 @@ import javax.xml.stream.FactoryConfigurationError; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.response.LoginCmdResponse; import org.apache.cloudstack.utils.security.CertUtils; +import org.apache.cloudstack.utils.security.ParserUtils; import org.apache.log4j.Logger; import org.bouncycastle.operator.OperatorCreationException; import org.joda.time.DateTime; @@ -88,6 +92,7 @@ import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.io.Unmarshaller; import org.opensaml.xml.io.UnmarshallerFactory; import org.opensaml.xml.io.UnmarshallingException; +import org.opensaml.xml.parse.BasicParserPool; import org.opensaml.xml.signature.SignatureConstants; import org.opensaml.xml.util.Base64; import org.opensaml.xml.util.XMLHelper; @@ -231,7 +236,7 @@ public class SAMLUtils { public static Response decodeSAMLResponse(String responseMessage) throws ConfigurationException, ParserConfigurationException, SAXException, IOException, UnmarshallingException { - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilderFactory documentBuilderFactory = ParserUtils.getSaferDocumentBuilderFactory(); documentBuilderFactory.setNamespaceAware(true); DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder(); byte[] base64DecodedResponse = Base64.decode(responseMessage); @@ -365,4 +370,19 @@ public class SAMLUtils { "CN=ApacheCloudStack", "CN=ApacheCloudStack", 3, "SHA256WithRSA"); } + + public static BasicParserPool getSaferParserPool() { + final Map features = new HashMap<>(); + features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true); + features.put("http://apache.org/xml/features/disallow-doctype-decl", true); + features.put("http://xml.org/sax/features/external-general-entities", false); + features.put("http://xml.org/sax/features/external-parameter-entities", false); + features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + final BasicParserPool parserPool = new BasicParserPool(); + parserPool.setXincludeAware(false); + parserPool.setIgnoreComments(true); + parserPool.setExpandEntityReferences(false); + parserPool.setBuilderFeatures(features); + return parserPool; + } } diff --git a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java index 528cd353305..d0943ad5281 100644 --- a/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java +++ b/server/src/test/java/com/cloud/configuration/ConfigurationManagerTest.java @@ -39,6 +39,11 @@ import java.util.Map; import java.util.Random; import java.util.UUID; +import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; +import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd; +import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd; +import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd; import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd; import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd; @@ -58,6 +63,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import com.cloud.api.query.dao.NetworkOfferingJoinDao; import com.cloud.api.query.vo.NetworkOfferingJoinVO; @@ -65,6 +71,8 @@ import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterGuestIpv6Prefix; +import com.cloud.dc.DataCenterGuestIpv6PrefixVO; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.Vlan; @@ -72,20 +80,27 @@ import com.cloud.dc.VlanVO; import com.cloud.dc.dao.AccountVlanMapDao; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DataCenterGuestIpv6PrefixDao; import com.cloud.dc.dao.DataCenterIpAddressDao; import com.cloud.dc.dao.DomainVlanMapDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.network.IpAddressManager; +import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMapVO; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.projects.ProjectManager; @@ -104,6 +119,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Ip; +import com.cloud.utils.net.NetUtils; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.VMInstanceDao; @@ -169,6 +185,10 @@ public class ConfigurationManagerTest { ConfigurationDao _configDao; @Mock DiskOfferingVO diskOfferingVOMock; + @Mock + DataCenterGuestIpv6PrefixDao dataCenterGuestIpv6PrefixDao; + @Mock + Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao; VlanVO vlan = new VlanVO(Vlan.VlanType.VirtualNetwork, "vlantag", "vlangateway", "vlannetmask", 1L, "iprange", 1L, 1L, null, null, null); @@ -1002,4 +1022,152 @@ public class ConfigurationManagerTest { this.configurationMgr.updateOfferingTagsIfIsNotNull(tags, diskOfferingVOMock); Mockito.verify(configurationMgr, Mockito.times(1)).updateOfferingTagsIfIsNotNull(tags, diskOfferingVOMock); } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCreateDataCenterGuestIpv6Prefix() { + CreateGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(CreateGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getZoneId()).thenReturn(1L); + Mockito.when(cmd.getPrefix()).thenReturn("Invalid"); + Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); + configurationMgr.createDataCenterGuestIpv6Prefix(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testWrongCreateDataCenterGuestIpv6Prefix() { + CreateGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(CreateGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getZoneId()).thenReturn(1L); + Mockito.when(cmd.getPrefix()).thenReturn("fd17:5:8a43:e2a4:c000::/66"); + Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); + configurationMgr.createDataCenterGuestIpv6Prefix(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testConflictingCreateDataCenterGuestIpv6Prefix() { + CreateGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(CreateGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getZoneId()).thenReturn(1L); + Mockito.when(cmd.getPrefix()).thenReturn("fd17:5:8a43:e2a5::/64"); + Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); + DataCenterGuestIpv6PrefixVO prefix = Mockito.mock(DataCenterGuestIpv6PrefixVO.class); + Mockito.when(prefix.getPrefix()).thenReturn("fd17:5:8a43:e2a4::/62"); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(Mockito.anyLong())).thenReturn(List.of(prefix)); + configurationMgr.createDataCenterGuestIpv6Prefix(cmd); + } + + @Test + public void testCreateDataCenterGuestIpv6Prefix() { + final Long zoneId = 1L; + final String prefix = "fd17:5:8a43:e2a5::/64"; + CreateGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(CreateGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getZoneId()).thenReturn(zoneId); + Mockito.when(cmd.getPrefix()).thenReturn(prefix); + Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(Mockito.anyLong())).thenReturn(new ArrayList<>()); + final List persistedPrefix = new ArrayList<>(); + Mockito.when(dataCenterGuestIpv6PrefixDao.persist(Mockito.any(DataCenterGuestIpv6PrefixVO.class))).thenAnswer((Answer) invocation -> { + DataCenterGuestIpv6PrefixVO prefixVO = (DataCenterGuestIpv6PrefixVO)invocation.getArgument(0); + persistedPrefix.add(prefixVO); + return prefixVO; + }); + configurationMgr.createDataCenterGuestIpv6Prefix(cmd); + Assert.assertEquals(1, persistedPrefix.size()); + DataCenterGuestIpv6PrefixVO prefixVO = persistedPrefix.get(0); + Assert.assertEquals(zoneId, prefixVO.getDataCenterId()); + Assert.assertEquals(prefix, prefixVO.getPrefix()); + } + + @Test + public void testListDataCenterGuestIpv6Prefixes() { + ListGuestNetworkIpv6PrefixesCmd cmd = Mockito.mock(ListGuestNetworkIpv6PrefixesCmd.class); + Mockito.when(cmd.getId()).thenReturn(1L); + Mockito.when(cmd.getZoneId()).thenReturn(1L); + Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); + Mockito.when(dataCenterGuestIpv6PrefixDao.findById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterGuestIpv6PrefixVO.class)); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(Mockito.anyLong())) + .thenReturn(List.of(Mockito.mock(DataCenterGuestIpv6PrefixVO.class), Mockito.mock(DataCenterGuestIpv6PrefixVO.class))); + Mockito.when(dataCenterGuestIpv6PrefixDao.listAll()) + .thenReturn(List.of(Mockito.mock(DataCenterGuestIpv6PrefixVO.class), + Mockito.mock(DataCenterGuestIpv6PrefixVO.class), + Mockito.mock(DataCenterGuestIpv6PrefixVO.class))); + List prefixes = configurationMgr.listDataCenterGuestIpv6Prefixes(cmd); + Assert.assertEquals(1, prefixes.size()); + ListGuestNetworkIpv6PrefixesCmd cmd1 = Mockito.mock(ListGuestNetworkIpv6PrefixesCmd.class); + Mockito.when(cmd1.getId()).thenReturn(null); + Mockito.when(cmd1.getZoneId()).thenReturn(1L); + prefixes = configurationMgr.listDataCenterGuestIpv6Prefixes(cmd1); + Assert.assertEquals(2, prefixes.size()); + ListGuestNetworkIpv6PrefixesCmd cmd2 = Mockito.mock(ListGuestNetworkIpv6PrefixesCmd.class); + Mockito.when(cmd2.getId()).thenReturn(null); + Mockito.when(cmd2.getZoneId()).thenReturn(null); + prefixes = configurationMgr.listDataCenterGuestIpv6Prefixes(cmd2); + Assert.assertEquals(3, prefixes.size()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testInvalidDeleteDataCenterGuestIpv6Prefix() { + DeleteGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(DeleteGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getId()).thenReturn(1L); + Mockito.when(dataCenterGuestIpv6PrefixDao.findById(Mockito.anyLong())).thenReturn(null); + configurationMgr.deleteDataCenterGuestIpv6Prefix(cmd); + } + + @Test(expected = CloudRuntimeException.class) + public void testUsedDeleteDataCenterGuestIpv6Prefix() { + final Long prefixId = 1L; + DeleteGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(DeleteGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getId()).thenReturn(prefixId); + DataCenterGuestIpv6PrefixVO prefixVO = Mockito.mock(DataCenterGuestIpv6PrefixVO.class); + Mockito.when(prefixVO.getId()).thenReturn(prefixId); + Mockito.when(dataCenterGuestIpv6PrefixDao.findById(Mockito.anyLong())).thenReturn(prefixVO); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(Mockito.anyLong())) + .thenReturn(List.of(Mockito.mock(Ipv6GuestPrefixSubnetNetworkMapVO.class))); + configurationMgr.deleteDataCenterGuestIpv6Prefix(cmd); + } + + @Test + public void testDeleteDataCenterGuestIpv6Prefix() { + final Long prefixId = 1L; + DeleteGuestNetworkIpv6PrefixCmd cmd = Mockito.mock(DeleteGuestNetworkIpv6PrefixCmd.class); + Mockito.when(cmd.getId()).thenReturn(prefixId); + DataCenterGuestIpv6PrefixVO prefixVO = Mockito.mock(DataCenterGuestIpv6PrefixVO.class); + Mockito.when(prefixVO.getId()).thenReturn(prefixId); + Mockito.when(dataCenterGuestIpv6PrefixDao.findById(Mockito.anyLong())).thenReturn(prefixVO); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(Mockito.anyLong())).thenReturn(new ArrayList<>()); + final List removedPrefix = new ArrayList<>(); + Mockito.when(dataCenterGuestIpv6PrefixDao.remove(Mockito.anyLong())).thenAnswer((Answer) invocation -> { + removedPrefix.add(invocation.getArgument(0)); + return true; + }); + configurationMgr.deleteDataCenterGuestIpv6Prefix(cmd); + Assert.assertEquals(1, removedPrefix.size()); + Assert.assertEquals(prefixId, removedPrefix.get(0)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testInvalidNetworkTypeCreateIpv6NetworkOffering() { + CreateNetworkOfferingCmd cmd = Mockito.mock(CreateNetworkOfferingCmd.class); + Mockito.when(cmd.getTraffictype()).thenReturn(Networks.TrafficType.Guest.toString()); + Mockito.when(cmd.getGuestIpType()).thenReturn(Network.GuestType.L2.toString()); + Mockito.when(cmd.getInternetProtocol()).thenReturn(NetUtils.InternetProtocol.DualStack.toString()); + configurationMgr.createNetworkOffering(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testDisabledConfigCreateIpv6NetworkOffering() { + CreateNetworkOfferingCmd cmd = Mockito.mock(CreateNetworkOfferingCmd.class); + Mockito.when(cmd.getTraffictype()).thenReturn(Networks.TrafficType.Guest.toString()); + Mockito.when(cmd.getGuestIpType()).thenReturn(Network.GuestType.Isolated.toString()); + Mockito.when(cmd.getInternetProtocol()).thenReturn(NetUtils.InternetProtocol.DualStack.toString()); + configurationMgr.createNetworkOffering(cmd); + } + + @Test(expected = InvalidParameterValueException.class) + public void testWrongIpv6CreateVlanAndPublicIpRange() { + CreateVlanIpRangeCmd cmd = Mockito.mock(CreateVlanIpRangeCmd.class); + Mockito.when(cmd.getIp6Cidr()).thenReturn("fd17:5:8a43:e2a4:c000::/66"); + try { + configurationMgr.createVlanAndPublicIpRange(cmd); + } catch (InsufficientCapacityException | ResourceUnavailableException | ResourceAllocationException e) { + throw new RuntimeException(e); + } + } } diff --git a/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java b/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java new file mode 100644 index 00000000000..a648b968262 --- /dev/null +++ b/server/src/test/java/com/cloud/network/Ipv6ServiceImplTest.java @@ -0,0 +1,781 @@ +// 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.network; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; + +import org.apache.cloudstack.api.command.user.ipv6.CreateIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.command.user.ipv6.UpdateIpv6FirewallRuleCmd; +import org.apache.cloudstack.api.response.Ipv6RouteResponse; +import org.apache.cloudstack.api.response.VpcResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.cloud.api.ApiDBUtils; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterGuestIpv6PrefixVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.DataCenterGuestIpv6PrefixDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.firewall.FirewallService; +import com.cloud.network.guru.PublicNetworkGuru; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.vpc.Vpc; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.UserVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; +import com.googlecode.ipv6.IPv6Network; +import com.googlecode.ipv6.IPv6NetworkMask; + +@PowerMockIgnore("javax.management.*") +@RunWith(PowerMockRunner.class) +@PrepareForTest({ApiDBUtils.class, ActionEventUtils.class, UsageEventUtils.class}) +public class Ipv6ServiceImplTest { + + @Mock + NetworkOfferingDao networkOfferingDao; + @Mock + VlanDao vlanDao; + @Mock + DataCenterGuestIpv6PrefixDao dataCenterGuestIpv6PrefixDao; + @Mock + Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao; + @Mock + FirewallRulesDao firewallDao; + @Mock + FirewallService firewallService; + @Mock + NetworkDetailsDao networkDetailsDao; + @Mock + NicDao nicDao; + @Mock + DomainRouterDao domainRouterDao; + @Mock + AccountManager accountManager; + @Mock + NetworkModel networkModel = Mockito.mock(NetworkModelImpl.class); + @Mock + IPAddressDao ipAddressDao; + @Mock + NetworkOrchestrationService networkOrchestrationService; + + FirewallManager firewallManager = Mockito.mock(FirewallManager.class); + + @InjectMocks + private Ipv6ServiceImpl ipv6Service = new Ipv6ServiceImpl(); + + List updatedPrefixSubnetMap; + + List persistedPrefixSubnetMap; + + final String publicReserver = PublicNetworkGuru.class.getSimpleName(); + final String vlan = "vlan"; + final Long networkId = 101L; + final Long nicId = 100L; + final String ipv6Prefix = "fd17:6:8a43:e2a4::/62"; // Will have 4 /64 subnets + final String cidr = "fd17:5:8a43:e2a5::/64"; + final String gateway = "fd17:5:8a43:e2a5::1"; + final String macAddress = "1e:00:4c:00:00:03"; + final String ipv6Address = "fd17:5:8a43:e2a5:1c00:4cff:fe00:3"; // Resulting IPv6 address using SLAAC + public static final long ACCOUNT_ID = 1; + + private AccountVO account; + private UserVO user; + + @Before + public void setup() { + updatedPrefixSubnetMap = new ArrayList<>(); + persistedPrefixSubnetMap = new ArrayList<>(); + MockitoAnnotations.initMocks(this); + ipv6Service.firewallManager = firewallManager; + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.update(Mockito.anyLong(), Mockito.any(Ipv6GuestPrefixSubnetNetworkMapVO.class))).thenAnswer((Answer) invocation -> { + Ipv6GuestPrefixSubnetNetworkMapVO map = (Ipv6GuestPrefixSubnetNetworkMapVO)invocation.getArguments()[1]; + updatedPrefixSubnetMap.add(map); + return true; + }); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.persist(Mockito.any(Ipv6GuestPrefixSubnetNetworkMapVO.class))).thenAnswer((Answer) invocation -> { + Ipv6GuestPrefixSubnetNetworkMapVO map = (Ipv6GuestPrefixSubnetNetworkMapVO)invocation.getArguments()[0]; + persistedPrefixSubnetMap.add(map); + return map; + }); + PowerMockito.mockStatic(ApiDBUtils.class); + Mockito.when(ApiDBUtils.findZoneById(Mockito.anyLong())).thenReturn(Mockito.mock(DataCenterVO.class)); + } + + private DataCenterGuestIpv6PrefixVO prepareMocksForIpv6Subnet() { + final long prefixId = 1L; + DataCenterGuestIpv6PrefixVO prefix = Mockito.mock(DataCenterGuestIpv6PrefixVO.class); + Mockito.when(prefix.getId()).thenReturn(prefixId); + Mockito.when(prefix.getPrefix()).thenReturn(ipv6Prefix); + List subnets = new ArrayList<>(); + Ipv6GuestPrefixSubnetNetworkMapVO subnetMap = new Ipv6GuestPrefixSubnetNetworkMapVO(prefixId, "subnet", 1L, Ipv6GuestPrefixSubnetNetworkMap.State.Allocated); + subnets.add(subnetMap); + subnetMap = new Ipv6GuestPrefixSubnetNetworkMapVO(1L, "subnet", 2L, Ipv6GuestPrefixSubnetNetworkMap.State.Allocated); + subnets.add(subnetMap); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefixId)).thenReturn(subnets); + return prefix; + } + + @Test + public void testGetUsedTotalIpv6SubnetForPrefix() { + DataCenterGuestIpv6PrefixVO prefix = prepareMocksForIpv6Subnet(); + Pair results = ipv6Service.getUsedTotalIpv6SubnetForPrefix(prefix); + Assert.assertEquals(2, results.first().intValue()); + Assert.assertEquals(4, results.second().intValue()); + } + + @Test + public void testNoPrefixesGetUsedTotalIpv6SubnetForZone() { + final long zoneId = 1L; + final List prefixes = new ArrayList<>(); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes); + Pair results = ipv6Service.getUsedTotalIpv6SubnetForZone(zoneId); + Assert.assertEquals(0, results.first().intValue()); + Assert.assertEquals(0, results.second().intValue()); + } + + @Test + public void testGetUsedTotalIpv6SubnetForZone() { + final long zoneId = 1L; + final List prefixes = new ArrayList<>(); + DataCenterGuestIpv6PrefixVO prefix = prepareMocksForIpv6Subnet(); + prefixes.add(prefix); + prefixes.add(prefix); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes); + Pair results = ipv6Service.getUsedTotalIpv6SubnetForZone(zoneId); + Assert.assertEquals(4, results.first().intValue()); + Assert.assertEquals(8, results.second().intValue()); + } + + @Test(expected = ResourceAllocationException.class) + @DB + public void testNoPrefixesPreAllocateIpv6SubnetForNetwork() throws ResourceAllocationException, MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException { + final long zoneId = 1L; + final List prefixes = new ArrayList<>(); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes); + TransactionLegacy txn = TransactionLegacy.open("testNoPrefixesPreAllocateIpv6SubnetForNetwork"); + try { + ipv6Service.preAllocateIpv6SubnetForNetwork(zoneId); + } finally { + txn.close("testNoPrefixesPreAllocateIpv6SubnetForNetwork"); + } + } + + @Test + @DB + public void testExistingPreAllocateIpv6SubnetForNetwork() { + final long zoneId = 1L; + final List prefixes = new ArrayList<>(); + DataCenterGuestIpv6PrefixVO prefix = prepareMocksForIpv6Subnet(); + prefixes.add(prefix); + Ipv6GuestPrefixSubnetNetworkMapVO ipv6GuestPrefixSubnetNetworkMap = new Ipv6GuestPrefixSubnetNetworkMapVO(1L, "fd17:5:8a43:e2a4::/64", null, Ipv6GuestPrefixSubnetNetworkMap.State.Free); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.findFirstAvailable(prefix.getId())).thenReturn(ipv6GuestPrefixSubnetNetworkMap); + updatedPrefixSubnetMap.clear(); + try (TransactionLegacy txn = TransactionLegacy.open("testNoPrefixesPreAllocateIpv6SubnetForNetwork")) { + try { + ipv6Service.preAllocateIpv6SubnetForNetwork(zoneId); + } catch (ResourceAllocationException e) { + Assert.fail("ResourceAllocationException"); + } + } + Assert.assertEquals(1, updatedPrefixSubnetMap.size()); + Ipv6GuestPrefixSubnetNetworkMapVO map = updatedPrefixSubnetMap.get(0); + Assert.assertEquals(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating, map.getState()); + Assert.assertEquals(ipv6GuestPrefixSubnetNetworkMap.getSubnet(), map.getSubnet()); + Assert.assertEquals(ipv6GuestPrefixSubnetNetworkMap.getPrefixId(), map.getPrefixId()); + Assert.assertNull(map.getNetworkId()); + } + + @Test + @DB + public void testNewPreAllocateIpv6SubnetForNetwork() { + final long zoneId = 1L; + final List prefixes = new ArrayList<>(); + DataCenterGuestIpv6PrefixVO prefix = prepareMocksForIpv6Subnet(); + final IPv6Network ip6Prefix = IPv6Network.fromString(prefix.getPrefix()); + Iterator splits = ip6Prefix.split(IPv6NetworkMask.fromPrefixLength(Ipv6Service.IPV6_SLAAC_CIDR_NETMASK)); + List subnets = new ArrayList<>(); + while(splits.hasNext()) { + subnets.add(splits.next().toString()); + } + prefixes.add(prefix); + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.findFirstAvailable(prefix.getId())).thenReturn(null); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefix.getId())).thenReturn(new ArrayList<>()); + persistedPrefixSubnetMap.clear(); + // No subnet is used from the prefix, should allocate any subnet + try (TransactionLegacy txn = TransactionLegacy.open("testNewPreAllocateIpv6SubnetForNetwork")) { + try { + ipv6Service.preAllocateIpv6SubnetForNetwork(zoneId); + } catch (ResourceAllocationException e) { + Assert.fail("ResourceAllocationException"); + } + } + Assert.assertEquals(1, persistedPrefixSubnetMap.size()); + Ipv6GuestPrefixSubnetNetworkMapVO map = persistedPrefixSubnetMap.get(0); + Assert.assertEquals(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating, map.getState()); + Assert.assertTrue(subnets.contains(map.getSubnet())); + Assert.assertEquals(prefix.getId(), map.getPrefixId()); + Assert.assertNull(map.getNetworkId()); + List usedSubnets = new ArrayList<>(); + for (String subnet : subnets) { + usedSubnets.add(new Ipv6GuestPrefixSubnetNetworkMapVO(prefix.getId(), subnet, 1L, Ipv6GuestPrefixSubnetNetworkMap.State.Allocated)); + } + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.listUsedByPrefix(prefix.getId())).thenReturn(usedSubnets); + + // All subnets from the prefix are already in use, should return ResourceAllocationException + try (TransactionLegacy txn = TransactionLegacy.open("testNewPreAllocateIpv6SubnetForNetwork")) { + try { + ipv6Service.preAllocateIpv6SubnetForNetwork(zoneId); + Assert.fail("ResourceAllocationException expected but not returned"); + } catch (ResourceAllocationException ignored) {} + } + persistedPrefixSubnetMap.clear(); + + // 3 out of 4 subnet from the prefix are in use, should return the remaining one + Ipv6GuestPrefixSubnetNetworkMapVO poppedUsedSubnetMap = usedSubnets.remove(2); + try (TransactionLegacy txn = TransactionLegacy.open("testNewPreAllocateIpv6SubnetForNetwork")) { + try { + ipv6Service.preAllocateIpv6SubnetForNetwork(zoneId); + } catch (ResourceAllocationException e) { + Assert.fail("ResourceAllocationException"); + } + } + Assert.assertEquals(1, persistedPrefixSubnetMap.size()); + map = persistedPrefixSubnetMap.get(0); + Assert.assertEquals(Ipv6GuestPrefixSubnetNetworkMap.State.Allocating, map.getState()); + Assert.assertEquals(poppedUsedSubnetMap.getSubnet(), map.getSubnet()); + Assert.assertEquals(prefix.getId(), map.getPrefixId()); + Assert.assertNull(map.getNetworkId()); + } + + @Test + @DB + public void testAssignIpv6SubnetToNetwork() { + final long prefixId = 1L; + final String subnet = "fd17:5:8a43:e2a5::/64"; + final Long networkId = 100L; + Ipv6GuestPrefixSubnetNetworkMapVO allocatingMap = new Ipv6GuestPrefixSubnetNetworkMapVO(prefixId, subnet, null, Ipv6GuestPrefixSubnetNetworkMap.State.Allocating); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.findBySubnet(subnet)).thenReturn(allocatingMap); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.createForUpdate(Mockito.anyLong())).thenReturn(allocatingMap); + updatedPrefixSubnetMap.clear(); + try (TransactionLegacy txn = TransactionLegacy.open("testNewPreAllocateIpv6SubnetForNetwork")) { + ipv6Service.assignIpv6SubnetToNetwork(subnet, networkId); + } + Assert.assertEquals(1, updatedPrefixSubnetMap.size()); + Ipv6GuestPrefixSubnetNetworkMapVO map = updatedPrefixSubnetMap.get(0); + Assert.assertEquals(Ipv6GuestPrefixSubnetNetworkMap.State.Allocated, map.getState()); + Assert.assertEquals(subnet, map.getSubnet()); + Assert.assertEquals(prefixId, map.getPrefixId()); + Assert.assertEquals(networkId, map.getNetworkId()); + } + + @Test + @DB + public void testReleaseIpv6SubnetForNetwork() { + final long prefixId = 1L; + final String subnet = "fd17:5:8a43:e2a5::/64"; + Ipv6GuestPrefixSubnetNetworkMapVO allocatingMap = new Ipv6GuestPrefixSubnetNetworkMapVO(prefixId, subnet, networkId, Ipv6GuestPrefixSubnetNetworkMap.State.Allocated); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.findByNetworkId(networkId)).thenReturn(allocatingMap); + Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.createForUpdate(Mockito.anyLong())).thenReturn(allocatingMap); + updatedPrefixSubnetMap.clear(); + try (TransactionLegacy txn = TransactionLegacy.open("testNewPreAllocateIpv6SubnetForNetwork")) { + ipv6Service.releaseIpv6SubnetForNetwork(networkId); + } + Assert.assertEquals(1, updatedPrefixSubnetMap.size()); + Ipv6GuestPrefixSubnetNetworkMapVO map = updatedPrefixSubnetMap.get(0); + Assert.assertEquals(Ipv6GuestPrefixSubnetNetworkMap.State.Free, map.getState()); + Assert.assertEquals(subnet, map.getSubnet()); + Assert.assertEquals(prefixId, map.getPrefixId()); + Assert.assertNull(map.getNetworkId()); + } + + @Test + public void testGetAllocatedIpv6FromVlanRange() { + Vlan vlan = Mockito.mock(Vlan.class); + Mockito.when(vlan.getIp6Cidr()).thenReturn(null); + Mockito.when(vlan.getIp6Gateway()).thenReturn(null); + Assert.assertNull(ipv6Service.getAllocatedIpv6FromVlanRange(vlan)); + List addresses = Arrays.asList("fd17:5:8a43:e2a5::1000", "fd17:5:8a43:e2a5::1001"); + Vlan vlan1 = Mockito.mock(Vlan.class); + Mockito.when(vlan1.getIp6Cidr()).thenReturn(cidr); + Mockito.when(vlan1.getIp6Gateway()).thenReturn(gateway); + + List nics = new ArrayList<>(); + for (String address : addresses) { + NicVO nic = new NicVO(publicReserver, 100L, 1L, VirtualMachine.Type.DomainRouter); + nic.setIPv6Address(address); + nics.add(nic); + } + Mockito.when(nicDao.findNicsByIpv6GatewayIpv6CidrAndReserver(gateway, cidr, publicReserver)).thenReturn(nics); + List result = ipv6Service.getAllocatedIpv6FromVlanRange(vlan1); + Assert.assertEquals(addresses.size(), result.size()); + for (String address : addresses) { + Assert.assertTrue(result.contains(address)); + } + } + + @Test + public void testAlreadyExistAssignPublicIpv6ToNetwork() { + Nic nic = Mockito.mock(Nic.class); + Mockito.when(nic.getIPv6Address()).thenReturn(ipv6Address); + Nic assignedNic = ipv6Service.assignPublicIpv6ToNetwork(Mockito.mock(Network.class), nic); + Assert.assertEquals(ipv6Address, assignedNic.getIPv6Address()); + } + + @Test(expected = CloudRuntimeException.class) + public void testNewErrorAssignPublicIpv6ToNetwork() { + Nic nic = Mockito.mock(Nic.class); + Mockito.when(nic.getIPv6Address()).thenReturn(null); + Mockito.when(nic.getBroadcastUri()).thenReturn(URI.create(vlan)); + Mockito.when(vlanDao.listIpv6RangeByPhysicalNetworkIdAndVlanId(1L, "vlan")).thenReturn(new ArrayList<>()); + try (TransactionLegacy txn = TransactionLegacy.open("testNewErrorAssignPublicIpv6ToNetwork")) { + ipv6Service.assignPublicIpv6ToNetwork(Mockito.mock(Network.class), nic); + } + } + + private List mockPlaceholderNics() { + NicVO placeholderNic = Mockito.mock(NicVO.class); + Mockito.when(placeholderNic.getIPv6Address()).thenReturn(ipv6Address); + Mockito.when(placeholderNic.getIPv6Gateway()).thenReturn(gateway); + Mockito.when(placeholderNic.getIPv6Cidr()).thenReturn(cidr); + Mockito.when(placeholderNic.getReserver()).thenReturn(publicReserver); + List placeholderNics = new ArrayList<>(); + placeholderNics.add(placeholderNic); + return placeholderNics; + } + + private void prepareMocksForPublicIpv6(boolean fromPlaceholder) { + VlanVO vlanVO = Mockito.mock(VlanVO.class); + Mockito.when(vlanVO.getIp6Cidr()).thenReturn(cidr); + Mockito.when(vlanVO.getIp6Gateway()).thenReturn(gateway); + Mockito.when(vlanVO.getVlanType()).thenReturn(Vlan.VlanType.VirtualNetwork); + List vlans = new ArrayList<>(); + vlans.add(vlanVO); + Mockito.when(vlanDao.listIpv6RangeByPhysicalNetworkIdAndVlanId(Mockito.anyLong(), Mockito.anyString())).thenReturn(vlans); + List placeholderNics = new ArrayList<>(); + if (fromPlaceholder) { + placeholderNics = mockPlaceholderNics(); + } + Mockito.when(nicDao.listPlaceholderNicsByNetworkIdAndVmType(networkId, VirtualMachine.Type.DomainRouter)).thenReturn(placeholderNics); + Mockito.when(nicDao.createForUpdate(nicId)).thenReturn(new NicVO(publicReserver, 100L, 1L, VirtualMachine.Type.DomainRouter)); + PowerMockito.mockStatic(ActionEventUtils.class); + Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L); + PowerMockito.mockStatic(UsageEventUtils.class); + } + + @Test + @DB + public void testNewAssignPublicIpv6ToNetwork() { + NicVO nic = Mockito.mock(NicVO.class); + Mockito.when(nic.getIPv6Address()).thenReturn(null); + Mockito.when(nic.getBroadcastUri()).thenReturn(URI.create(vlan)); + Mockito.when(nic.getMacAddress()).thenReturn(macAddress); + Mockito.when(nic.getId()).thenReturn(nicId); + Network network = Mockito.mock(Network.class); + Mockito.when(network.getId()).thenReturn(networkId); + prepareMocksForPublicIpv6(false); + Nic assignedNic; + try (TransactionLegacy txn = TransactionLegacy.open("testNewPreAllocateIpv6SubnetForNetwork")) { + assignedNic = ipv6Service.assignPublicIpv6ToNetwork(network, nic); + } + Assert.assertEquals(ipv6Address, assignedNic.getIPv6Address()); + Assert.assertEquals(gateway, assignedNic.getIPv6Gateway()); + Assert.assertEquals(cidr, assignedNic.getIPv6Cidr()); + } + + @Test + public void testFromPlaceholderAssignPublicIpv6ToNetwork() { + NicVO nic = Mockito.mock(NicVO.class); + Mockito.when(nic.getIPv6Address()).thenReturn(null); + Mockito.when(nic.getBroadcastUri()).thenReturn(URI.create(vlan)); + Mockito.when(nic.getId()).thenReturn(nicId); + Network network = Mockito.mock(Network.class); + Mockito.when(network.getId()).thenReturn(networkId); + prepareMocksForPublicIpv6(true); + Nic assignedNic = ipv6Service.assignPublicIpv6ToNetwork(network, nic); + Assert.assertEquals(ipv6Address, assignedNic.getIPv6Address()); + Assert.assertEquals(gateway, assignedNic.getIPv6Gateway()); + Assert.assertEquals(cidr, assignedNic.getIPv6Cidr()); + } + + @Test + public void testIpv4NetworkUpdateNicIpv6() { + Mockito.when(networkOfferingDao.isIpv6Supported(Mockito.anyLong())).thenReturn(false); + NicProfile nicProfile = new NicProfile(); + try { + ipv6Service.updateNicIpv6(nicProfile, Mockito.mock(DataCenter.class), Mockito.mock(Network.class)); + } catch (InsufficientAddressCapacityException e) { + Assert.fail("InsufficientAddressCapacityException"); + } + Assert.assertNull(nicProfile.getIPv6Address()); + Assert.assertNull(nicProfile.getIPv6Gateway()); + Assert.assertNull(nicProfile.getIPv6Cidr()); + } + + @Test + public void testIpv6NetworkUpdateNicIpv6() { + Mockito.when(networkOfferingDao.isIpv6Supported(Mockito.anyLong())).thenReturn(true); + NicProfile nicProfile = new NicProfile(); + nicProfile.setBroadcastUri(URI.create(vlan)); + nicProfile.setMacAddress(macAddress); + prepareMocksForPublicIpv6(false); + try { + ipv6Service.updateNicIpv6(nicProfile, Mockito.mock(DataCenter.class), Mockito.mock(Network.class)); + } catch (InsufficientAddressCapacityException e) { + Assert.fail("InsufficientAddressCapacityException"); + } + Assert.assertEquals(ipv6Address, nicProfile.getIPv6Address()); + Assert.assertEquals(gateway, nicProfile.getIPv6Gateway()); + Assert.assertEquals(cidr, nicProfile.getIPv6Cidr()); + } + + @Test + public void testIpv6NetworkFromPlaceholderUpdateNicIpv6() { + Mockito.when(networkOfferingDao.isIpv6Supported(Mockito.anyLong())).thenReturn(true); + NicProfile nicProfile = new NicProfile(); + nicProfile.setBroadcastUri(URI.create(vlan)); + nicProfile.setMacAddress(macAddress); + prepareMocksForPublicIpv6(true); + try { + ipv6Service.updateNicIpv6(nicProfile, Mockito.mock(DataCenter.class), Mockito.mock(Network.class)); + } catch (InsufficientAddressCapacityException e) { + Assert.fail("InsufficientAddressCapacityException"); + } + Assert.assertEquals(ipv6Address, nicProfile.getIPv6Address()); + Assert.assertEquals(gateway, nicProfile.getIPv6Gateway()); + Assert.assertEquals(cidr, nicProfile.getIPv6Cidr()); + } + + @Test + public void testEmptyGetPublicIpv6AddressesForNetwork(){ + Mockito.when(domainRouterDao.findByNetwork(Mockito.anyLong())).thenReturn(new ArrayList<>()); + List addresses = ipv6Service.getPublicIpv6AddressesForNetwork(Mockito.mock(Network.class)); + Assert.assertTrue(CollectionUtils.isEmpty(addresses)); + List routers = List.of(Mockito.mock(DomainRouterVO.class)); + Mockito.when(domainRouterDao.findByNetwork(Mockito.anyLong())).thenReturn(routers); + Mockito.when(nicDao.listByVmId(Mockito.anyLong())).thenReturn(new ArrayList<>()); + addresses = ipv6Service.getPublicIpv6AddressesForNetwork(Mockito.mock(Network.class)); + Assert.assertTrue(CollectionUtils.isEmpty(addresses)); + NicVO nic = Mockito.mock(NicVO.class); + Mockito.when(nic.getIPv6Address()).thenReturn(null); + List nics = List.of(nic); + Mockito.when(nicDao.listByVmId(Mockito.anyLong())).thenReturn(nics); + addresses = ipv6Service.getPublicIpv6AddressesForNetwork(Mockito.mock(Network.class)); + Assert.assertTrue(CollectionUtils.isEmpty(addresses)); + } + + @Test + public void testGetPublicIpv6AddressesForNetwork(){ + List routers = List.of(Mockito.mock(DomainRouterVO.class), Mockito.mock(DomainRouterVO.class)); + Mockito.when(domainRouterDao.findByNetwork(Mockito.anyLong())).thenReturn(routers); + NicVO nic = Mockito.mock(NicVO.class); + Mockito.when(nic.getIPv6Address()).thenReturn(ipv6Address); + Mockito.when(nic.getReserver()).thenReturn(publicReserver); + List nics = List.of(nic); + Mockito.when(nicDao.listByVmId(Mockito.anyLong())).thenReturn(nics); + List addresses = ipv6Service.getPublicIpv6AddressesForNetwork(Mockito.mock(Network.class)); + Assert.assertEquals(1, addresses.size()); + Assert.assertEquals(ipv6Address, addresses.get(0)); + } + + @Test + public void testEmptyUpdateIpv6RoutesForVpcResponse() { + VpcResponse response = new VpcResponse(); + Vpc vpc = Mockito.mock(Vpc.class); + List networks = new ArrayList<>(); + Mockito.doReturn(networks).when(networkModel).listNetworksByVpc(Mockito.anyLong()); + ipv6Service.updateIpv6RoutesForVpcResponse(vpc, response); + Assert.assertTrue(CollectionUtils.isEmpty(response.getIpv6Routes())); + } + + @Test + public void testUpdateIpv6RoutesForVpcResponse() { + VpcResponse response = new VpcResponse(); + Vpc vpc = Mockito.mock(Vpc.class); + List networks = new ArrayList<>(); + NetworkVO network = Mockito.mock(NetworkVO.class); + Mockito.when(network.getIp6Cidr()).thenReturn(cidr); + networks.add(network); + List routers = List.of(Mockito.mock(DomainRouterVO.class)); + Mockito.when(domainRouterDao.findByNetwork(Mockito.anyLong())).thenReturn(routers); + NicVO nic = Mockito.mock(NicVO.class); + Mockito.when(nic.getIPv6Address()).thenReturn(ipv6Address); + Mockito.when(nic.getReserver()).thenReturn(publicReserver); + Mockito.when(nicDao.listByVmId(Mockito.anyLong())).thenReturn(List.of(nic)); + Mockito.doReturn(networks).when(networkModel).listNetworksByVpc(Mockito.anyLong()); + Mockito.when(networkOfferingDao.isIpv6Supported(Mockito.anyLong())).thenReturn(true); + ipv6Service.updateIpv6RoutesForVpcResponse(vpc, response); + Assert.assertEquals(1, response.getIpv6Routes().size()); + Ipv6RouteResponse routeResponse = new ArrayList<>(response.getIpv6Routes()).get(0); + Assert.assertEquals(ipv6Address, routeResponse.getGateway()); + Assert.assertEquals(cidr, routeResponse.getSubnet()); + } + + @Test + public void testCheckNetworkIpv6UpgradeForNoPrefixes() { + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(Mockito.anyLong())).thenReturn(new ArrayList<>()); + try { + ipv6Service.checkNetworkIpv6Upgrade(Mockito.mock(Network.class)); + Assert.fail("No ResourceAllocationException"); + } catch (InsufficientAddressCapacityException | ResourceAllocationException ignored) {} + } + + @Test + public void testCheckNetworkIpv6UpgradeForNoIpv6Vlan() { + final long physicalNetworkId = 1L; + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(Mockito.anyLong())).thenReturn(List.of(Mockito.mock(DataCenterGuestIpv6PrefixVO.class))); + Network network = Mockito.mock(Network.class); + Mockito.when(network.getPhysicalNetworkId()).thenReturn(physicalNetworkId); + Mockito.when(network.getVpcId()).thenReturn(null); + Mockito.when(ipAddressDao.listByAssociatedNetwork(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(Mockito.mock(IPAddressVO.class))); + VlanVO vlanVO = Mockito.mock(VlanVO.class); + Mockito.when(vlanVO.getVlanTag()).thenReturn(vlan); + Mockito.when(vlanDao.findById(Mockito.anyLong())).thenReturn(vlanVO); + Mockito.when(vlanDao.listIpv6RangeByPhysicalNetworkIdAndVlanId(Mockito.anyLong(), Mockito.anyString())).thenReturn(new ArrayList<>()); + try { + ipv6Service.checkNetworkIpv6Upgrade(network); + Assert.fail("No InsufficientAddressCapacityException"); + } catch (InsufficientAddressCapacityException | ResourceAllocationException ignored) {} + } + + @Test + public void testCheckNetworkIpv6UpgradeForNetwork() { + final long physicalNetworkId = 1L; + Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(Mockito.anyLong())).thenReturn(List.of(Mockito.mock(DataCenterGuestIpv6PrefixVO.class))); + Network network = Mockito.mock(Network.class); + Mockito.when(network.getPhysicalNetworkId()).thenReturn(physicalNetworkId); + Mockito.when(network.getVpcId()).thenReturn(null); + Mockito.when(ipAddressDao.listByAssociatedNetwork(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(Mockito.mock(IPAddressVO.class))); + VlanVO vlanVO = Mockito.mock(VlanVO.class); + Mockito.when(vlanVO.getVlanTag()).thenReturn(vlan); + Mockito.when(vlanDao.findById(Mockito.anyLong())).thenReturn(vlanVO); + Mockito.when(vlanDao.listIpv6RangeByPhysicalNetworkIdAndVlanId(physicalNetworkId, vlan)).thenReturn(List.of(vlanVO)); + try { + ipv6Service.checkNetworkIpv6Upgrade(network); + } catch (InsufficientAddressCapacityException | ResourceAllocationException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testUpdateIpv6FirewallRule() { + final Long firewallRuleId = 1L; + UpdateIpv6FirewallRuleCmd cmd = Mockito.mock(UpdateIpv6FirewallRuleCmd.class); + Mockito.when(cmd.getId()).thenReturn(firewallRuleId); + Mockito.when(firewallDao.findById(firewallRuleId)).thenReturn(null); + try { + ipv6Service.updateIpv6FirewallRule(cmd); + Assert.fail("No InvalidParameterValueException"); + } catch (InvalidParameterValueException ignored) {} + FirewallRuleVO ingressFirewallRule = Mockito.mock(FirewallRuleVO.class); + Mockito.when(ingressFirewallRule.getTrafficType()).thenReturn(FirewallRule.TrafficType.Ingress); + Mockito.when(firewallDao.findById(firewallRuleId)).thenReturn(ingressFirewallRule); + try { + ipv6Service.updateIpv6FirewallRule(cmd); + } catch (InvalidParameterValueException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testDeleteIpv6FirewallRule() { + final Long firewallRuleId = 1L; + Mockito.when(firewallDao.findById(firewallRuleId)).thenReturn(null); + try { + ipv6Service.revokeIpv6FirewallRule(firewallRuleId); + Assert.fail("No InvalidParameterValueException"); + } catch (InvalidParameterValueException ignored) {} + FirewallRuleVO ingressFirewallRule = Mockito.mock(FirewallRuleVO.class); + Mockito.when(ingressFirewallRule.getTrafficType()).thenReturn(FirewallRule.TrafficType.Ingress); + Mockito.when(firewallDao.findById(firewallRuleId)).thenReturn(ingressFirewallRule); + try { + ipv6Service.revokeIpv6FirewallRule(firewallRuleId); + } catch (InvalidParameterValueException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testGetIpv6FirewallRule() { + final Long firewallRuleId = 1L; + final String uuid = UUID.randomUUID().toString(); + Mockito.when(firewallDao.findById(firewallRuleId)).thenReturn(null); + FirewallRule rule = ipv6Service.getIpv6FirewallRule(firewallRuleId); + Assert.assertNull(rule); + FirewallRuleVO ingressFirewallRule = Mockito.mock(FirewallRuleVO.class); + Mockito.when(ingressFirewallRule.getUuid()).thenReturn(uuid); + Mockito.when(firewallDao.findById(firewallRuleId)).thenReturn(ingressFirewallRule); + rule = ipv6Service.getIpv6FirewallRule(firewallRuleId); + Assert.assertEquals(uuid, rule.getUuid()); + } + + private void registerCallContext() { + account = new AccountVO("testaccount", 1L, "networkdomain", Account.Type.NORMAL, "uuid"); + account.setId(ACCOUNT_ID); + user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", + UUID.randomUUID().toString(), User.Source.UNKNOWN); + CallContext.register(user, account); + } + + @Test(expected = InvalidParameterValueException.class) + public void testInvalidSourceCidrCreateIpv6FirewallRule() { + registerCallContext(); + CreateIpv6FirewallRuleCmd cmd = Mockito.mock(CreateIpv6FirewallRuleCmd.class); + Mockito.when(cmd.getSourceCidrList()).thenReturn(List.of("10.1.1.1")); + try { + ipv6Service.createIpv6FirewallRule(cmd); + } catch (NetworkRuleConflictException e) { + throw new RuntimeException(e); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testInvalidDestinationCidrCreateIpv6FirewallRule() { + registerCallContext(); + CreateIpv6FirewallRuleCmd cmd = Mockito.mock(CreateIpv6FirewallRuleCmd.class); + Mockito.when(cmd.getDestinationCidrList()).thenReturn(List.of("10.1.1.1")); + try { + ipv6Service.createIpv6FirewallRule(cmd); + } catch (NetworkRuleConflictException e) { + throw new RuntimeException(e); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testStartPortCidrCreateIpv6FirewallRule() { + registerCallContext(); + CreateIpv6FirewallRuleCmd cmd = Mockito.mock(CreateIpv6FirewallRuleCmd.class); + Mockito.when(cmd.getSourcePortStart()).thenReturn(800000); + try { + ipv6Service.createIpv6FirewallRule(cmd); + } catch (NetworkRuleConflictException e) { + throw new RuntimeException(e); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testEndPortCidrCreateIpv6FirewallRule() { + registerCallContext(); + CreateIpv6FirewallRuleCmd cmd = Mockito.mock(CreateIpv6FirewallRuleCmd.class); + Mockito.when(cmd.getSourcePortEnd()).thenReturn(800000); + try { + ipv6Service.createIpv6FirewallRule(cmd); + } catch (NetworkRuleConflictException e) { + throw new RuntimeException(e); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void testPortRangeCidrCreateIpv6FirewallRule() { + registerCallContext(); + CreateIpv6FirewallRuleCmd cmd = Mockito.mock(CreateIpv6FirewallRuleCmd.class); + Mockito.when(cmd.getSourcePortStart()).thenReturn(900); + Mockito.when(cmd.getSourcePortEnd()).thenReturn(800); + try { + ipv6Service.createIpv6FirewallRule(cmd); + } catch (NetworkRuleConflictException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testRemovePublicIpv6PlaceholderNics() { + Network network = Mockito.mock(NetworkVO.class); + Mockito.when(network.getId()).thenReturn(networkId); + NicVO nic = Mockito.mock(NicVO.class); + Mockito.when(nic.getId()).thenReturn(nicId); + Mockito.when(nic.getIPv6Address()).thenReturn(ipv6Address); + Mockito.when(nic.getIPv6Cidr()).thenReturn(cidr); + Mockito.when(nic.getIPv6Gateway()).thenReturn(gateway); + Mockito.when(nic.getReserver()).thenReturn(publicReserver); + Mockito.when(nicDao.listPlaceholderNicsByNetworkId(Mockito.anyLong())).thenReturn(List.of(nic)); + final List removedNics = new ArrayList<>(); + Mockito.when(nicDao.remove(Mockito.anyLong())).thenAnswer((Answer) invocation -> { + removedNics.add((Long)invocation.getArguments()[0]); + return true; + }); + PowerMockito.mockStatic(ActionEventUtils.class); + Mockito.when(ActionEventUtils.onCompletedActionEvent(Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyLong())).thenReturn(1L); + PowerMockito.mockStatic(UsageEventUtils.class); + ipv6Service.removePublicIpv6PlaceholderNics(network); + Assert.assertEquals(1, removedNics.size()); + Assert.assertEquals(nicId, removedNics.get(0)); + removedNics.clear(); + NicVO nic1 = Mockito.mock(NicVO.class); + Mockito.when(nic1.getId()).thenReturn(nicId); + Mockito.when(nic1.getIPv6Address()).thenReturn(null); + Mockito.when(nicDao.listPlaceholderNicsByNetworkId(Mockito.anyLong())).thenReturn(List.of(nic1)); + ipv6Service.removePublicIpv6PlaceholderNics(network); + Assert.assertEquals(0, removedNics.size()); + } +} diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java index fb0776f551a..10b59fde8b1 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -20,31 +20,33 @@ package com.cloud.network.vpc; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.powermock.api.mockito.PowerMockito.when; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.network.NetworkModel; -import com.cloud.network.element.NetworkElement; - -import com.cloud.network.Network; -import com.cloud.network.Network.Capability; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; + +import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; - -import com.cloud.network.Network.Provider; -import com.cloud.network.Network.Service; -import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import org.powermock.reflect.Whitebox; -import static org.mockito.Mockito.mock; -import static org.powermock.api.mockito.PowerMockito.when; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; +import com.cloud.utils.net.NetUtils; public class VpcManagerImplTest { @@ -162,4 +164,11 @@ public class VpcManagerImplTest { return providers; } + + @Test(expected = InvalidParameterValueException.class) + public void testDisabledConfigCreateIpv6VpcOffering() { + CreateVPCOfferingCmd cmd = Mockito.mock(CreateVPCOfferingCmd.class); + Mockito.when(cmd.getInternetProtocol()).thenReturn(NetUtils.InternetProtocol.DualStack.toString()); + manager.createVpcOffering(cmd); + } } diff --git a/test/integration/component/test_network_ipv6.py b/test/integration/component/test_network_ipv6.py new file mode 100644 index 00000000000..29afb8d97be --- /dev/null +++ b/test/integration/component/test_network_ipv6.py @@ -0,0 +1,935 @@ +# 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. +""" BVT tests for IPv6 Network""" + +#Import Local Modules +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createGuestNetworkIpv6Prefix, + listGuestNetworkIpv6Prefixes, + deleteGuestNetworkIpv6Prefix, + listIpv6FirewallRules, + createIpv6FirewallRule, + deleteIpv6FirewallRule) +from marvin.lib.utils import (random_gen, + get_process_status, + get_host_credentials) +from marvin.lib.base import (Configurations, + Domain, + NetworkOffering, + Account, + PublicIpRange, + Network, + Router, + ServiceOffering, + VirtualMachine, + NIC, + Host) +from marvin.lib.common import (get_domain, + get_zone, + list_hosts, + get_test_template) +from marvin.sshClient import SshClient +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.decoratorGenerators import skipTestIf + +from nose.plugins.attrib import attr +from ipaddress import IPv6Network +from random import getrandbits, choice, randint +import time +import logging +import threading + +ipv6_offering_config_name = "ipv6.offering.enabled" +ULA_BASE = IPv6Network("fd00::/8") +PREFIX_OPTIONS = [i for i in range(48, 65, 4)] +FIREWALL_TABLE = "ip6_firewall" +FIREWALL_CHAINS = { + "Ingress": "fw_chain_ingress", + "Egress": "fw_chain_egress" +} +CIDR_IPV6_ANY = "::/0" +ICMPV6_TYPE = { + 1: "destination-unreachable", + 2: "packet-too-big", + 3: "time-exceeded", + 4: "parameter-problem", + 128: "echo-request", + 129: "echo-reply", + 130: "mld-listener-query", + 131: "mld-listener-report", + 132: "mld-listener-done", + 133: "nd-router-solicit", + 134: "nd-router-advert", + 135: "nd-neighbor-solicit", + 136: "nd-neighbor-advert", + 137: "nd-redirect", + 138: "router-renumbering", + 141: "ind-neighbor-solicit", + 142: "ind-neighbor-advert", + 143: "mld2-listener-report" +} +ICMPV6_CODE_TYPE = { + 0: "no-route", + 1: "admin-prohibited", + 3: "addr-unreachable", + 4: "port-unreachable", + 5: "policy-fail", + 6: "reject-route" +} +ICMPV6_TYPE_ANY = "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering }" +TCP_UDP_PORT_ANY = "{ 0-65535 }" +SLEEP_BEFORE_VR_CHANGES = 45 +PING_RETRIES = 5 +PING_SLEEP = 20 + +class TestIpv6Network(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6Network, cls).getClsTestClient() + cls.services = testClient.getParsedTestDataConfig() + cls.apiclient = testClient.getApiClient() + cls.dbclient = testClient.getDbConnection() + cls.test_ipv6_guestprefix = None + cls.initial_ipv6_offering_enabled = None + cls._cleanup = [] + cls.routerDetailsMap = {} + + cls.logger = logging.getLogger('TestIpv6Network') + + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.ipv6NotSupported = False + + ipv6_guestprefix = cls.getGuestIpv6Prefix() + if ipv6_guestprefix == None: + cls.ipv6NotSupported = True + if cls.ipv6NotSupported == False: + ipv6_publiciprange = cls.getPublicIpv6Range() + if ipv6_publiciprange == None: + cls.ipv6NotSupported = True + + if cls.ipv6NotSupported == False: + cls.initial_ipv6_offering_enabled = Configurations.list( + cls.apiclient, + name=ipv6_offering_config_name)[0].value + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + "true") + cls.domain = get_domain(cls.apiclient) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.hypervisor = testClient.getHypervisorInfo() + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.debug("IPv6 is not supported, skipping tests!") + return + + @classmethod + def tearDownClass(cls): + if cls.initial_ipv6_offering_enabled != None: + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + cls.initial_ipv6_offering_enabled) + try: + super(TestIpv6Network, cls).tearDownClass() + finally: + if cls.test_ipv6_guestprefix != None: + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = cls.test_ipv6_guestprefix.id + cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + + @classmethod + def getGuestIpv6Prefix(cls): + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.zoneid = cls.zone.id + ipv6_prefixes_response = cls.apiclient.listGuestNetworkIpv6Prefixes(cmd) + if isinstance(ipv6_prefixes_response, list) == True and len(ipv6_prefixes_response) > 0: + return ipv6_prefixes_response[0] + ipv6_guestprefix_service = cls.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = cls.zone.id + cmd.prefix = ipv6_guestprefix_service["prefix"] + ipv6_guestprefix = cls.apiclient.createGuestNetworkIpv6Prefix(cmd) + cls.test_ipv6_guestprefix = ipv6_guestprefix + return ipv6_guestprefix + + @classmethod + def getPublicIpv6Range(cls): + list_public_ip_range_response = PublicIpRange.list( + cls.apiclient, + zoneid=cls.zone.id + ) + ipv4_range_vlan = None + if isinstance(list_public_ip_range_response, list) == True and len(list_public_ip_range_response) > 0: + for ip_range in list_public_ip_range_response: + if ip_range.ip6cidr != None and ip_range.ip6gateway != None: + return ip_range + if ip_range.netmask != None and ip_range.gateway != None: + vlan = ip_range.vlan + if ipv4_range_vlan == None and vlan.startswith("vlan://"): + vlan = vlan.replace("vlan://", "") + if vlan == "untagged": + ipv4_range_vlan = None + else: + ipv4_range_vlan = int(vlan) + ipv6_publiciprange_service = cls.services["publicip6range"] + ipv6_publiciprange_service["zoneid"] = cls.zone.id + ipv6_publiciprange_service["vlan"] = ipv4_range_vlan + ipv6_publiciprange = PublicIpRange.create( + cls.apiclient, + ipv6_publiciprange_service + ) + cls._cleanup.append(ipv6_publiciprange) + return ipv6_publiciprange + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.thread = None + self.cleanup = [] + return + + def tearDown(self): + try: + if self.thread and self.thread.is_alive(): + self.thread.join(5*60) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + finally: + super(TestIpv6Network, self).tearDown() + return + + def getRandomIpv6Cidr(self): + prefix_length = choice(PREFIX_OPTIONS) + random_suffix = getrandbits(40) << (128-prefix_length) + base_address = ULA_BASE.network_address + random_suffix + return str(IPv6Network((base_address, prefix_length))) + + def createTinyServiceOffering(self): + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["big"], + ) + self.cleanup.append(self.service_offering) + + def createNetworkOfferingInternal(self, is_redundant, is_ipv6, egressdefaultpolicy=True): + off_service = self.services["network_offering"] + if is_redundant: + off_service = self.services["nw_off_isolated_RVR"] + if is_ipv6: + off_service["internetprotocol"] = "dualstack" + if egressdefaultpolicy: + off_service["egress_policy"] = egressdefaultpolicy + network_offering = NetworkOffering.create( + self.apiclient, + off_service + ) + self.cleanup.append(network_offering) + network_offering.update(self.apiclient, state='Enabled') + return network_offering + + def createIpv4NetworkOffering(self, is_redundant=False): + self.network_offering = self.createNetworkOfferingInternal(is_redundant, False, False) + + def createIpv6NetworkOffering(self, is_redundant=False): + self.network_offering = self.createNetworkOfferingInternal(is_redundant, True, False) + + def createIpv6NetworkOfferingForUpdate(self, is_redundant=False): + self.network_offering_update = self.createNetworkOfferingInternal(is_redundant, True) + + + def deployNetwork(self): + self.services["network"]["networkoffering"] = self.network_offering.id + self.network = Network.create( + self.apiclient, + self.services["network"], + self.account.name, + self.account.domainid, + zoneid=self.zone.id + ) + self.cleanup.append(self.network) + + def deployNetworkVm(self): + if self.template == FAILED: + assert False, "get_test_template() failed to return template" + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=self.network.id, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(self.virtual_machine) + + def checkIpv6NetworkBasic(self): + self.debug("Listing network: %s" % (self.network.name)) + ipv6_network = Network.list(self.apiclient,listall="true",id=self.network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network, + None, + "User is not able to retrieve network details %s" % self.network.id) + self.assertNotEqual(ipv6_network.ip6cidr, + None, + "IPv6 CIDR for network is empty") + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + self.network_ipv6_routes = ipv6_network.ip6routes + + def checkIpv6NetworkRoutersBasic(self): + self.debug("Listing routers for network: %s" % self.network.name) + self.routers = Router.list( + self.apiclient, + networkid=self.network.id, + listall=True + ) + self.assertTrue( + isinstance(self.routers, list), + "Check listRouters response returns a valid list" + ) + self.assertTrue( + len(self.routers) > 0, + "Router for the network isn't found" + ) + for router in self.routers: + self.assertFalse( + router.isredundantrouter == True and router.redundantstate == "FAULT", + "Router for the network is in FAULT state" + ) + nics = router.nic + for nic in nics: + if (nic.traffictype == 'Guest' and router.isredundantrouter == False) or nic.traffictype == 'Public': + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for router %s NIC is empty" % nic.traffictype) + + + def getRouterProcessStatus(self, router, cmd): + if router.id not in self.routerDetailsMap or self.routerDetailsMap[router.id] is None: + connect_ip = self.apiclient.connection.mgtSvr + connect_user = self.apiclient.connection.user + connect_passwd = self.apiclient.connection.passwd + hypervisor = self.hypervisor + if self.hypervisor.lower() not in ('vmware', 'hyperv'): + hosts = Host.list( + self.apiclient, + zoneid=router.zoneid, + type='Routing', + state='Up', + id=router.hostid + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list host returns a valid list" + ) + host = hosts[0] + connect_ip = host.ipaddress + hypervisor = None + try: + connect_user, connect_passwd= get_host_credentials( + self.config, host.ipaddress) + except KeyError: + self.skipTest( + "Marvin configuration has no host credentials to\ + check router services") + details = {} + details['connect_ip'] = connect_ip + details['connect_user'] = connect_user + details['connect_passwd'] = connect_passwd + details['hypervisor'] = hypervisor + self.routerDetailsMap[router.id] = details + result = get_process_status( + self.routerDetailsMap[router.id]['connect_ip'], + 22, + self.routerDetailsMap[router.id]['connect_user'], + self.routerDetailsMap[router.id]['connect_passwd'], + router.linklocalip, + cmd, + hypervisor=self.routerDetailsMap[router.id]['hypervisor'] + ) + self.assertTrue(type(result) == list and len(result) > 0, + "%s on router %s returned invalid result" % (cmd, router.id)) + result = '\n'.join(result) + return result + + def getNetworkRouter(self, network, red_state="PRIMARY"): + routers = Router.list( + self.apiclient, + networkid=network.id, + listall=True + ) + self.assertTrue( + isinstance(routers, list) and len(routers) > 0, + "No routers found for network %s" % network.id + ) + if len(routers) == 1: + return routers[0] + for router in routers: + if router.redundantstate == red_state: + return router + + def getNetworkGateway(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + return ipv6_network.ip6gateway + + def getNetworkRoutes(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + return ipv6_network.ip6routes + + def isNetworkEgressDefaultPolicyAllow(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + if len(ipv6_network) == 1: + ipv6_network = ipv6_network[0] + return ipv6_network.egressdefaultpolicy + return False + + def checkRouterNicState(self, router, dev, state): + st = "state %s" % state + cmd = "ip link show %s | grep '%s'" % (dev, st) + res = self.getRouterProcessStatus(router, cmd) + self.assertTrue(type(res) == str and len(res) > 0 and st in res, + "%s failed on router %s" % (cmd, router.id)) + + def checkIpv6NetworkPrimaryRouter(self, router, network_ip6gateway): + self.checkRouterNicState(router, "eth0", "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", network_ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and network_ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for IPv6 guest gateway on router %s" % router.id) + self.checkRouterNicState(router, "eth2", "UP") + public_ipv6 = None + public_ipv6_gateway = None + nics = router.nic + for nic in nics: + if nic.traffictype == 'Public': + public_ipv6 = nic.ip6address + public_ipv6_gateway = nic.ip6gateway + break + self.assertNotEqual(public_ipv6, + None, + "IPv6 address for router Public NIC is empty") + public_ip_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth2", public_ipv6) + res = self.getRouterProcessStatus(router, public_ip_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6 in res, + "%s failed on router %s" % (public_ip_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for public IPv6 on router %s" % router.id) + self.assertNotEqual(public_ipv6_gateway, + None, + "IPv6 gateway for router Public NIC is empty") + default_route_check_cmd = "ip -6 route | grep 'default via %s'" % (public_ipv6_gateway) + res = self.getRouterProcessStatus(router, default_route_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6_gateway in res, + "%s failed on router %s" % (default_route_check_cmd, router.id)) + + def checkIpv6NetworkBackupRouter(self, router, network_ip6gateway): + self.checkRouterNicState(router, "eth0", "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", network_ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertFalse(type(res) == str and len(res) > 0 and network_ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.checkRouterNicState(router, "eth2", "DOWN") + + def checkIpv6NetworkRoutersInternal(self): + network_ip6gateway = self.getNetworkGateway(self.network) + for router in self.routers: + if router.state != "Running": + continue + if router.isredundantrouter == True and router.redundantstate == 'BACKUP': + self.checkIpv6NetworkBackupRouter(router, network_ip6gateway) + continue + self.checkIpv6NetworkPrimaryRouter(router, network_ip6gateway) + + + def checkIpv6NetworkVm(self): + self.debug("Listing NICS for VM %s in network: %s" % (self.virtual_machine.name, self.network.name)) + nics = NIC.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + networkid=self.network.id + ) + self.assertEqual( + len(nics), + 1, + "VM NIC for the network isn't found" + ) + nic = nics[0] + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for VM %s NIC is empty" % nic.traffictype) + self.virtual_machine_ipv6_address = nic.ip6address + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for VM %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for VM %s NIC is empty" % nic.traffictype) + + def restartNetworkWithCleanup(self): + self.network.restart(self.apiclient, cleanup=True) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + + def updateNetworkWithOffering(self): + self.network.update(self.apiclient, networkofferingid=self.network_offering_update.id) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + + def createIpv6FirewallRuleInNetwork(self, network_id, traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, icmp_type, icmp_code): + cmd = createIpv6FirewallRule.createIpv6FirewallRuleCmd() + cmd.networkid = network_id + cmd.traffictype = traffic_type + if source_cidr: + cmd.cidrlist = source_cidr + if dest_cidr: + cmd.destcidrlist = dest_cidr + if protocol: + cmd.protocol = protocol + if start_port: + cmd.startport = start_port + if end_port: + cmd.endport = end_port + if icmp_type is not None: + cmd.icmptype = icmp_type + if icmp_code is not None: + cmd.icmpcode = icmp_code + fw_rule = self.apiclient.createIpv6FirewallRule(cmd) + return fw_rule + + def deployRoutingTestResources(self): + self.routing_test_network_offering = self.createNetworkOfferingInternal(False, True) + self.services["network"]["networkoffering"] = self.routing_test_network_offering.id + self.routing_test_network = Network.create( + self.apiclient, + self.services["network"], + self.account.name, + self.account.domainid, + zoneid=self.zone.id + ) + self.cleanup.append(self.routing_test_network) + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.routing_test_vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=[self.routing_test_network.id], + serviceofferingid=self.service_offering.id, + mode="advanced" + ) + self.cleanup.append(self.routing_test_vm) + + def prepareRoutingTestResourcesInBackground(self): + self.thread = threading.Thread(target=self.deployRoutingTestResources, args=()) + self.thread.daemon = True + self.thread.start() + + def checkIpv6NetworkRouting(self): + if not self.thread: + self.deployRoutingTestResources() + else: + self.thread.join(5*60) + self.assertFalse(not self.routing_test_network or not self.routing_test_vm, + "Routing resources failure") + + fw1 = self.createIpv6FirewallRuleInNetwork(self.routing_test_network.id, "Ingress", None, None, "icmp", + None, None, None, None) + fw2 = self.createIpv6FirewallRuleInNetwork(self.network.id, "Ingress", None, None, "icmp", + None, None, None, None) + + test_network_router = self.getNetworkRouter(self.routing_test_network) + routes = self.getNetworkRoutes(self.network) + self.logger.debug("Adding network routes in routing_test_network %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(test_network_router, add_route_cmd) + + network_router = self.getNetworkRouter(self.network) + routes = self.getNetworkRoutes(self.routing_test_network) + self.logger.debug("Adding routing_test_network routes in network %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(network_router, add_route_cmd) + + ping_cmd = "ping6 -c 4 %s" % self.virtual_machine_ipv6_address + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = self.getRouterProcessStatus(test_network_router, ping_cmd) + if " 0% packet loss" in res: + break + time.sleep(PING_SLEEP) + self.assertTrue(" 0% packet loss" in res, + "Ping from router %s of network %s to VM %s of network %s is unsuccessful" % (test_network_router.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) + + ssh = self.routing_test_vm.get_ssh_client(retries=5) + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = ssh.execute(ping_cmd) + if type(res) == list and len(res) > 0 and " 0% packet loss" in '\n'.join(res): + break + time.sleep(PING_SLEEP) + self.assertTrue(type(res) == list and len(res) > 0, + "%s on VM %s returned invalid result" % (ping_cmd, self.routing_test_vm.id)) + self.logger.debug(res) + res = '\n'.join(res) + + self.assertTrue(" 0% packet loss" in res, + "Ping from VM %s of network %s to VM %s of network %s is unsuccessful" % (self.routing_test_vm.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) + + cmd = deleteIpv6FirewallRule.deleteIpv6FirewallRuleCmd() + cmd.id = fw2.id + self.apiclient.deleteIpv6FirewallRule(cmd) + + def createAndVerifyIpv6FirewallRule(self, traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, icmp_type, icmp_code, parsed_rule, delete=False): + self.logger.debug("createAndVerifyIpv6FirewallRule - %s" % parsed_rule) + fw_rule = self.createIpv6FirewallRuleInNetwork(self.network.id, traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, icmp_type, icmp_code) + cmd = listIpv6FirewallRules.listIpv6FirewallRulesCmd() + cmd.id = fw_rule.id + rules = self.apiclient.listIpv6FirewallRules(cmd) + self.assertTrue( + isinstance(rules, list), + "Check listIpv6FirewallRules response returns a valid list" + ) + rule = rules[0] + self.assertEqual(rule.networkid, self.network.id, + "IPv6 firewall rule network ID mismatch %s, %s" % (rule.networkid, self.network.id)) + self.assertEqual(rule.traffictype, traffic_type, + "IPv6 firewall rule traffic type mismatch %s, %s" % (rule.traffictype, traffic_type)) + if source_cidr: + self.assertEqual(rule.cidrlist, source_cidr, + "IPv6 firewall rule source CIDR mismatch %s, %s" % (rule.cidrlist, source_cidr)) + if dest_cidr: + self.assertEqual(rule.destcidrlist, dest_cidr, + "IPv6 firewall rule destination CIDR mismatch %s, %s" % (rule.destcidrlist, dest_cidr)) + if protocol: + self.assertEqual(rule.protocol, protocol, + "IPv6 firewall rule protocol mismatch %s, %s" % (rule.protocol, protocol)) + if start_port: + self.assertEqual(rule.startport, start_port, + "IPv6 firewall rule start port mismatch %d, %d" % (rule.startport, start_port)) + if end_port: + self.assertEqual(rule.endport, end_port, + "IPv6 firewall rule end port mismatch %d, %d" % (rule.endport, end_port)) + if icmp_type is not None: + self.assertEqual(rule.icmptype, icmp_type, + "IPv6 firewall rule ICMP type mismatch %d, %d" % (rule.icmptype, icmp_type)) + if icmp_code is not None: + self.assertEqual(rule.icmpcode, icmp_code, + "IPv6 firewall rule ICMP code mismatch %d, %d" % (rule.icmpcode, icmp_code)) + routerCmd = "nft list chain ip6 %s %s" % (FIREWALL_TABLE, FIREWALL_CHAINS[traffic_type]) + res = self.getRouterProcessStatus(self.getNetworkRouter(self.network), routerCmd) + self.assertTrue(parsed_rule in res, + "Listing firewall rule with nft list chain failure for rule: %s" % parsed_rule) + if delete == True: + cmd = deleteIpv6FirewallRule.deleteIpv6FirewallRuleCmd() + cmd.id = fw_rule.id + self.apiclient.deleteIpv6FirewallRule(cmd) + res = self.getRouterProcessStatus(self.getNetworkRouter(self.network), routerCmd) + self.assertFalse(parsed_rule in res, + "Firewall rule present in nft list chain failure despite delete for rule: %s" % parsed_rule) + + def checkIpv6FirewallRule(self): + traffic_type = "Ingress" + + # Ingress - ip6 saddr SOURCE_CIDR ip6 daddr DEST_CIDR tcp dport { START_PORT-END_PORT } accept + source_cidr = self.getRandomIpv6Cidr() + dest_cidr = self.getRandomIpv6Cidr() + protocol = "tcp" + start_port = randint(3000, 5000) + end_port = start_port + randint(1, 8) + rule = "ip6 saddr %s ip6 daddr %s %s dport { %d-%d } accept" % (source_cidr, dest_cidr, protocol, start_port, end_port) + self.createAndVerifyIpv6FirewallRule(traffic_type, source_cidr, dest_cidr, protocol, + start_port, end_port, None, None, rule, True) + + # Ingress - ip6 daddr DEST_CIDR icmpv6 type TYPE code CODE accept + source_cidr = self.getRandomIpv6Cidr() + protocol = "icmp" + icmp_type = choice(list(ICMPV6_TYPE.keys())) + icmp_code = choice(list(ICMPV6_CODE_TYPE.keys())) + rule = "ip6 saddr %s ip6 daddr %s %sv6 type %s %sv6 code %s accept" % (source_cidr, CIDR_IPV6_ANY, protocol, ICMPV6_TYPE[icmp_type], protocol, ICMPV6_CODE_TYPE[icmp_code]) + self.createAndVerifyIpv6FirewallRule(traffic_type, source_cidr, None, protocol, + None, None, icmp_type, icmp_code, rule) + + action = "accept" + if self.isNetworkEgressDefaultPolicyAllow(self.network): + action = "drop" + traffic_type = "Egress" + + # Egress - ip6 saddr ::/0 ip6 daddr ::/0 udp dport { 0-65355 } ACTION + protocol = "udp" + rule = "ip6 saddr %s ip6 daddr %s %s dport %s %s" % (CIDR_IPV6_ANY, CIDR_IPV6_ANY, protocol, TCP_UDP_PORT_ANY, action) + self.createAndVerifyIpv6FirewallRule(traffic_type, None, None, protocol, + None, None, None, None, rule) + + # Egress - ip6 saddr ::/0 ip6 daddr ::/0 icmpv6 type ANY_TYPE ACTION + protocol = "icmp" + rule = "ip6 saddr %s ip6 daddr %s %sv6 type %s %s" % (CIDR_IPV6_ANY, CIDR_IPV6_ANY, protocol, ICMPV6_TYPE_ANY, action) + self.createAndVerifyIpv6FirewallRule(traffic_type, None, None, protocol, + None, None, None, None, rule) + + # Egress - ip6 saddr ::/0 ip6 daddr DEST_CIDR ACTION + protocol = "all" + dest_cidr = self.getRandomIpv6Cidr() + rule = "ip6 saddr %s ip6 daddr %s %s" % (CIDR_IPV6_ANY, CIDR_IPV6_ANY, action) + self.createAndVerifyIpv6FirewallRule(traffic_type, None, None, protocol, + None, None, None, None, rule) + + def checkNetworkVRRedundancy(self): + network_ip6gateway = self.getNetworkGateway(self.network) + primary_router = self.getNetworkRouter(self.network) + Router.stop( + self.apiclient, + id=primary_router.id + ) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + new_primary_router = self.getNetworkRouter(self.network) + self.assertNotEqual(new_primary_router.id, primary_router.id, + "Original primary router ID: %s of network is still the primary router after stopping" % (primary_router.id)) + self.checkIpv6NetworkPrimaryRouter(new_primary_router, network_ip6gateway) + + def checkIpv6Network(self): + self.checkIpv6NetworkBasic() + self.checkIpv6NetworkRoutersBasic() + self.checkIpv6NetworkRoutersInternal() + + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_01_verify_ipv6_network(self): + """Test to verify IPv6 network + + # Validate the following: + # 1. Create IPv6 network, deploy VM + # 2. Verify network has required IPv6 details + # 3. List router for the network and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network has required IPv6 details + # 6. Restart network with cleanup and re-verify network details + # 7. Update network with a new offering and re-verify network details + # 8. Deploy another IPv6 network and check routing between two networks and their VM + # 9. Create IPv6 firewall rules and verify in VR if they get implemented + """ + + self.createIpv6NetworkOffering() + self.createIpv6NetworkOfferingForUpdate() + self.createTinyServiceOffering() + self.deployNetwork() + self.deployNetworkVm() + self.checkIpv6Network() + self.checkIpv6NetworkVm() + self.prepareRoutingTestResourcesInBackground() + self.restartNetworkWithCleanup() + self.checkIpv6Network() + self.updateNetworkWithOffering() + self.checkIpv6Network() + self.checkIpv6NetworkRouting() + self.checkIpv6FirewallRule() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_02_verify_ipv6_network_redundant(self): + """Test to verify redundant IPv6 network + + # Validate the following: + # 1. Create IPv6 network, deploy VM + # 2. Verify network has required IPv6 details + # 3. List VRs for the network and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network has required IPv6 details + # 6. Restart network with cleanup and re-verify network details + # 7. Update network with a new offering and re-verify network details + # 8. Deploy another IPv6 network and check routing between two networks and their VM + # 9. Create IPv6 firewall rules and verify in VR if they get implemented + # 10. Stop primary router and verify internals in backup VR + """ + + self.createIpv6NetworkOffering(True) + self.createIpv6NetworkOfferingForUpdate(True) + self.createTinyServiceOffering() + self.deployNetwork() + self.deployNetworkVm() + self.checkIpv6Network() + self.checkIpv6NetworkVm() + self.prepareRoutingTestResourcesInBackground() + self.restartNetworkWithCleanup() + self.checkIpv6Network() + self.updateNetworkWithOffering() + self.checkIpv6Network() + self.checkIpv6NetworkRouting() + self.checkIpv6FirewallRule() + self.checkNetworkVRRedundancy() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_03_verify_upgraded_ipv6_network(self): + """Test to verify IPv4 network upgraded to IPv6 network + + # Validate the following: + # 1. Create IPv4 network, deploy VM + # 2. Update network to a IPv6 offering + # 3. Verify network has required IPv6 details + # 4. List VRs for the network and verify it has required IPv6 details for Guest and Public NIC of the VR + # 5. SSH into VR(s) and verify correct details are present for its NICs + # 6. Verify VM in network has required IPv6 details + # 7. Restart network with cleanup and re-verify network details + # 8. Deploy another IPv6 network and check routing between two networks and their VM + # 9. Create IPv6 firewall rules and verify in VR if they get implemented + """ + + self.createIpv4NetworkOffering(False) + self.createIpv6NetworkOfferingForUpdate(False) + self.createTinyServiceOffering() + self.prepareRoutingTestResourcesInBackground() + self.deployNetwork() + self.deployNetworkVm() + self.updateNetworkWithOffering() + self.checkIpv6Network() + self.checkIpv6NetworkVm() + self.restartNetworkWithCleanup() + self.checkIpv6Network() + self.checkIpv6NetworkRouting() + self.checkIpv6FirewallRule() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_04_verify_upgraded_ipv6_network_redundant(self): + """Test to verify redundant IPv4 network upgraded to redundant IPv6 network + + # Validate the following: + # 1. Create IPv4 network, deploy VM + # 2. Update network to a IPv6 offering + # 3. Verify network has required IPv6 details + # 4. List VRs for the network and verify it has required IPv6 details for Guest and Public NIC of the VR + # 5. SSH into VR(s) and verify correct details are present for its NICs + # 6. Verify VM in network has required IPv6 details + # 7. Restart network with cleanup and re-verify network details + # 8. Deploy another IPv6 network and check routing between two networks and their VM + # 9. Create IPv6 firewall rules and verify in VR if they get implemented + # 10. Stop primary router and verify internals in backup VR + """ + + self.createIpv4NetworkOffering(True) + self.createIpv6NetworkOfferingForUpdate(True) + self.createTinyServiceOffering() + self.prepareRoutingTestResourcesInBackground() + self.deployNetwork() + self.deployNetworkVm() + self.updateNetworkWithOffering() + self.checkIpv6Network() + self.checkIpv6NetworkVm() + self.restartNetworkWithCleanup() + self.checkIpv6Network() + self.checkIpv6NetworkRouting() + self.checkIpv6FirewallRule() + self.checkNetworkVRRedundancy() diff --git a/test/integration/component/test_vpc_ipv6.py b/test/integration/component/test_vpc_ipv6.py new file mode 100644 index 00000000000..fda46ea2378 --- /dev/null +++ b/test/integration/component/test_vpc_ipv6.py @@ -0,0 +1,1026 @@ +# 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. +""" BVT tests for IPv6 VPC""" + +#Import Local Modules +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createGuestNetworkIpv6Prefix, + listGuestNetworkIpv6Prefixes, + deleteGuestNetworkIpv6Prefix) +from marvin.lib.utils import (isAlmostEqual, + random_gen, + get_process_status, + get_host_credentials) +from marvin.lib.base import (Configurations, + Domain, + NetworkOffering, + VpcOffering, + Account, + PublicIpRange, + Network, + VPC, + Router, + ServiceOffering, + VirtualMachine, + NIC, + Host, + NetworkACLList, + NetworkACL) +from marvin.lib.common import (get_domain, + get_zone, + get_test_template, + get_template) +from marvin.sshClient import SshClient +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.decoratorGenerators import skipTestIf + +from nose.plugins.attrib import attr +from ipaddress import IPv6Network +from random import getrandbits, choice, randint +import time +import logging +import threading + +ipv6_offering_config_name = "ipv6.offering.enabled" +ULA_BASE = IPv6Network("fd00::/8") +PREFIX_OPTIONS = [i for i in range(48, 65, 4)] +ACL_TABLE = "ip6_acl" +ACL_CHAINS_SUFFIX = { + "Ingress": "_ingress_policy", + "Egress": "_egress_policy" +} +CIDR_IPV6_ANY = "::/0" +ICMPV6_TYPE = { + 1: "destination-unreachable", + 2: "packet-too-big", + 3: "time-exceeded", + 4: "parameter-problem", + 128: "echo-request", + 129: "echo-reply", + 130: "mld-listener-query", + 131: "mld-listener-report", + 132: "mld-listener-done", + 133: "nd-router-solicit", + 134: "nd-router-advert", + 135: "nd-neighbor-solicit", + 136: "nd-neighbor-advert", + 137: "nd-redirect", + 138: "router-renumbering", + 141: "ind-neighbor-solicit", + 142: "ind-neighbor-advert", + 143: "mld2-listener-report" +} +ICMPV6_CODE_TYPE = { + 0: "no-route", + 1: "admin-prohibited", + 3: "addr-unreachable", + 4: "port-unreachable", + 5: "policy-fail", + 6: "reject-route" +} +ICMPV6_TYPE_ANY = "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering }" +TCP_UDP_PORT_ANY = "{ 0-65535 }" +VPC_ROUTER_PUBLIC_NIC = "eth1" +VPC_ROUTER_GUEST_NIC = "eth2" +VPC_DATA = { + "cidr": "10.1.0.0/22", + "tier1_gateway": "10.1.1.1", + "tier2_gateway": "10.1.2.1", + "tier_netmask": "255.255.255.0" +} +ROUTE_TEST_VPC_DATA = { + "cidr": "10.2.0.0/22", + "tier1_gateway": "10.2.1.1", + "tier_netmask": "255.255.255.0" +} +SLEEP_BEFORE_VR_CHANGES = 90 +PING_RETRIES = 5 +PING_SLEEP = 20 + + +class TestIpv6Vpc(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6Vpc, cls).getClsTestClient() + cls.services = testClient.getParsedTestDataConfig() + cls.apiclient = testClient.getApiClient() + cls.dbclient = testClient.getDbConnection() + cls.test_ipv6_guestprefix = None + cls.initial_ipv6_offering_enabled = None + cls._cleanup = [] + cls.routerDetailsMap = {} + cls.vpcAllowAllAclDetailsMap = {} + + cls.logger = logging.getLogger('TestIpv6Vpc') + + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.ipv6NotSupported = False + + ipv6_guestprefix = cls.getGuestIpv6Prefix() + if ipv6_guestprefix == None: + cls.ipv6NotSupported = True + if cls.ipv6NotSupported == False: + ipv6_publiciprange = cls.getPublicIpv6Range() + if ipv6_publiciprange == None: + cls.ipv6NotSupported = True + + if cls.ipv6NotSupported == False: + cls.initial_ipv6_offering_enabled = Configurations.list( + cls.apiclient, + name=ipv6_offering_config_name)[0].value + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + "true") + cls.domain = get_domain(cls.apiclient) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.hypervisor = testClient.getHypervisorInfo() + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.debug("IPv6 is not supported, skipping tests!") + return + + @classmethod + def tearDownClass(cls): + if cls.initial_ipv6_offering_enabled != None: + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + cls.initial_ipv6_offering_enabled) + try: + super(TestIpv6Vpc, cls).tearDownClass() + finally: + if cls.test_ipv6_guestprefix != None: + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = cls.test_ipv6_guestprefix.id + cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + + @classmethod + def getGuestIpv6Prefix(cls): + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.zoneid = cls.zone.id + ipv6_prefixes_response = cls.apiclient.listGuestNetworkIpv6Prefixes(cmd) + if isinstance(ipv6_prefixes_response, list) == True and len(ipv6_prefixes_response) > 0: + return ipv6_prefixes_response[0] + ipv6_guestprefix_service = cls.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = cls.zone.id + cmd.prefix = ipv6_guestprefix_service["prefix"] + ipv6_guestprefix = cls.apiclient.createGuestNetworkIpv6Prefix(cmd) + cls.test_ipv6_guestprefix = ipv6_guestprefix + return ipv6_guestprefix + + @classmethod + def getPublicIpv6Range(cls): + list_public_ip_range_response = PublicIpRange.list( + cls.apiclient, + zoneid=cls.zone.id + ) + ipv4_range_vlan = None + if isinstance(list_public_ip_range_response, list) == True and len(list_public_ip_range_response) > 0: + for ip_range in list_public_ip_range_response: + if ip_range.ip6cidr != None and ip_range.ip6gateway != None: + return ip_range + if ip_range.netmask != None and ip_range.gateway != None: + vlan = ip_range.vlan + if ipv4_range_vlan == None and vlan.startswith("vlan://"): + vlan = vlan.replace("vlan://", "") + if vlan == "untagged": + ipv4_range_vlan = None + else: + ipv4_range_vlan = int(vlan) + ipv6_publiciprange_service = cls.services["publicip6range"] + ipv6_publiciprange_service["zoneid"] = cls.zone.id + ipv6_publiciprange_service["vlan"] = ipv4_range_vlan + ipv6_publiciprange = PublicIpRange.create( + cls.apiclient, + ipv6_publiciprange_service + ) + cls._cleanup.append(ipv6_publiciprange) + return ipv6_publiciprange + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.thread = None + self.cleanup = [] + return + + def tearDown(self): + try: + if self.thread and self.thread.is_alive(): + self.thread.join(5*60) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + finally: + super(TestIpv6Vpc, self).tearDown() + return + + def getRandomIpv6Cidr(self): + prefix_length = choice(PREFIX_OPTIONS) + random_suffix = getrandbits(40) << (128-prefix_length) + base_address = ULA_BASE.network_address + random_suffix + return str(IPv6Network((base_address, prefix_length))) + + def createTinyServiceOffering(self): + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["big"], + ) + self.cleanup.append(self.service_offering) + + def createVpcOfferingInternal(self, is_redundant, is_ipv6): + off_service = self.services["vpc_offering"] + if is_redundant: + off_service["serviceCapabilityList"] = { + "SourceNat": { + "RedundantRouter": 'true' + }, + } + if is_ipv6: + off_service["internetprotocol"] = "dualstack" + vpc_offering = VpcOffering.create( + self.apiclient, + off_service + ) + self.cleanup.append(vpc_offering) + vpc_offering.update(self.apiclient, state='Enabled') + return vpc_offering + + def createIpv4VpcOffering(self, is_redundant=False): + self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False) + + def createIpv6VpcOffering(self, is_redundant=False): + self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True) + + def createIpv6VpcOfferingForUpdate(self, is_redundant=False): + self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True) + + def createNetworkTierOfferingInternal(self, is_ipv6, remove_lb=True): + off_service = self.services["nw_offering_isolated_vpc"] + if not remove_lb: # Remove Lb service + if "serviceProviderList" in off_service and "Lb" in off_service["serviceProviderList"].keys(): + providers = off_service["serviceProviderList"] + providers.pop("Lb") + off_service["serviceProviderList"] = providers + if "supportedservices" in off_service and "Lb" in off_service["supportedservices"]: + supportedServices = off_service["supportedservices"].split(",") + supportedServices.remove("Lb") + off_service["supportedservices"] = ",".join(supportedServices) + if is_ipv6: + off_service["internetprotocol"] = "dualstack" + network_offering = NetworkOffering.create( + self.apiclient, + off_service, + conservemode=False + ) + self.cleanup.append(network_offering) + network_offering.update(self.apiclient, state='Enabled') + return network_offering + + def createIpv4NetworkTierOffering(self): + self.network_offering = self.createNetworkTierOfferingInternal(False) + + def createIpv6NetworkTierOffering(self, remove_lb=True): + self.network_offering = self.createNetworkTierOfferingInternal(True) + + def createIpv6NetworkTierOfferingForUpdate(self): + self.network_offering_update = self.createNetworkTierOfferingInternal(True) + + def deployAllowAllVpcInternal(self, cidr): + service = self.services["vpc"] + service["cidr"] = cidr + vpc = VPC.create( + self.apiclient, + self.services["vpc"], + vpcofferingid=self.vpc_offering.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + self.cleanup.append(vpc) + acl = NetworkACLList.create( + self.apiclient, + services={}, + name="allowall", + description="allowall", + vpcid=vpc.id + ) + rule ={ + "protocol": "all", + "traffictype": "ingress", + } + NetworkACL.create(self.apiclient, + services=rule, + aclid=acl.id + ) + rule["traffictype"] = "egress" + NetworkACL.create(self.apiclient, + services=rule, + aclid=acl.id + ) + self.vpcAllowAllAclDetailsMap[vpc.id] = acl.id + return vpc + + def deployVpc(self): + self.vpc = self.deployAllowAllVpcInternal(VPC_DATA["cidr"]) + + def deployNetworkTierInternal(self, network_offering_id, vpc_id, tier_gateway, tier_netmask, acl_id=None, tier_name=None): + if not acl_id and vpc_id in self.vpcAllowAllAclDetailsMap: + acl_id = self.vpcAllowAllAclDetailsMap[vpc_id] + service = self.services["ntwk"] + if tier_name: + service["name"] = tier_name + service["displaytext"] = "vpc-%s" % tier_name + network = Network.create( + self.apiclient, + service, + self.account.name, + self.account.domainid, + networkofferingid=network_offering_id, + vpcid=vpc_id, + zoneid=self.zone.id, + gateway=tier_gateway, + netmask=tier_netmask, + aclid=acl_id + ) + self.cleanup.append(network) + return network + + def deployNetworkTier(self): + self.network = self.deployNetworkTierInternal( + self.network_offering.id, + self.vpc.id, + VPC_DATA["tier1_gateway"], + VPC_DATA["tier_netmask"] + ) + + def deployNetworkTierVmInternal(self, network): + if self.template == FAILED: + assert False, "get_test_template() failed to return template" + self.services["virtual_machine"]["zoneid"] = self.zone.id + virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=network, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(virtual_machine) + return virtual_machine + + def deployNetworkTierVm(self): + self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id) + + def checkIpv6Vpc(self): + self.debug("Listing VPC: %s" % (self.vpc.name)) + ipv6_vpc = VPC.list(self.apiclient,listall="true",id=self.vpc.id) + self.assertTrue( + isinstance(ipv6_vpc, list), + "Check listVpcs response returns a valid list" + ) + self.assertEqual( + len(ipv6_vpc), + 1, + "Network not found" + ) + ipv6_vpc = ipv6_vpc[0] + self.assertNotEqual(ipv6_vpc.ip6routes, + None, + "IPv6 routes for network is empty") + + def checkIpv6NetworkTierBasic(self): + self.debug("Listing network: %s" % (self.network.name)) + ipv6_network = Network.list(self.apiclient,listall="true",id=self.network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network, + None, + "User is not able to retrieve network details %s" % self.network.id) + self.assertNotEqual(ipv6_network.ip6cidr, + None, + "IPv6 CIDR for network is empty") + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + + def checkIpv6VpcRoutersBasic(self): + self.debug("Listing routers for VPC: %s" % self.vpc.name) + self.routers = Router.list( + self.apiclient, + vpcid=self.vpc.id, + listall=True + ) + self.assertTrue( + isinstance(self.routers, list), + "Check listRouters response returns a valid list" + ) + self.assertTrue( + len(self.routers) > 0, + "Router for the network isn't found" + ) + for router in self.routers: + self.assertFalse( + router.isredundantrouter == True and router.redundantstate == "FAULT", + "Router for the network is in FAULT state" + ) + nics = router.nic + for nic in nics: + if (nic.traffictype == 'Guest' and router.isredundantrouter == False) or nic.traffictype == 'Public': + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for router %s NIC is empty" % nic.traffictype) + + + def getRouterProcessStatus(self, router, cmd): + if router.id not in self.routerDetailsMap or self.routerDetailsMap[router.id] is None: + connect_ip = self.apiclient.connection.mgtSvr + connect_user = self.apiclient.connection.user + connect_passwd = self.apiclient.connection.passwd + hypervisor = self.hypervisor + if self.hypervisor.lower() not in ('vmware', 'hyperv'): + hosts = Host.list( + self.apiclient, + zoneid=router.zoneid, + type='Routing', + state='Up', + id=router.hostid + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list host returns a valid list" + ) + host = hosts[0] + connect_ip = host.ipaddress + hypervisor = None + try: + connect_user, connect_passwd= get_host_credentials( + self.config, host.ipaddress) + except KeyError: + self.skipTest( + "Marvin configuration has no host credentials to\ + check router services") + details = {} + details['connect_ip'] = connect_ip + details['connect_user'] = connect_user + details['connect_passwd'] = connect_passwd + details['hypervisor'] = hypervisor + self.routerDetailsMap[router.id] = details + result = get_process_status( + self.routerDetailsMap[router.id]['connect_ip'], + 22, + self.routerDetailsMap[router.id]['connect_user'], + self.routerDetailsMap[router.id]['connect_passwd'], + router.linklocalip, + cmd, + hypervisor=self.routerDetailsMap[router.id]['hypervisor'] + ) + self.assertTrue(type(result) == list and len(result) > 0, + "%s on router %s returned invalid result" % (cmd, router.id)) + result = '\n'.join(result) + return result + + def getVpcRouter(self, vpc, red_state="PRIMARY"): + routers = Router.list( + self.apiclient, + vpcid=vpc.id, + listall=True + ) + self.assertTrue( + isinstance(routers, list) and len(routers) > 0, + "No routers found for VPC %s" % vpc.id + ) + if len(routers) == 1: + return routers[0] + for router in routers: + if router.redundantstate == red_state: + return router + + def getNetworkGateway(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + return ipv6_network.ip6gateway + + def getNetworkRoutes(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + return ipv6_network.ip6routes + + def isNetworkEgressDefaultPolicyAllow(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + if len(ipv6_network) == 1: + ipv6_network = ipv6_network[0] + return ipv6_network.egressdefaultpolicy + return False + + def checkRouterNicState(self, router, dev, state): + st = "state %s" % state + cmd = "ip link show %s | grep '%s'" % (dev, st) + res = self.getRouterProcessStatus(router, cmd) + self.assertTrue(type(res) == str and len(res) > 0 and st in res, + "%s failed on router %s" % (cmd, router.id)) + + def checkIpv6VpcPrimaryRouter(self, router, network_ip6gateway): + self.checkRouterNicState(router, VPC_ROUTER_GUEST_NIC, "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % (VPC_ROUTER_GUEST_NIC, network_ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and network_ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for IPv6 guest gateway on router %s" % router.id) + self.checkRouterNicState(router, VPC_ROUTER_PUBLIC_NIC, "UP") + public_ipv6 = None + public_ipv6_gateway = None + nics = router.nic + for nic in nics: + if nic.traffictype == 'Public': + public_ipv6 = nic.ip6address + public_ipv6_gateway = nic.ip6gateway + break + self.assertNotEqual(public_ipv6, + None, + "IPv6 address for router Public NIC is empty") + public_ip_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % (VPC_ROUTER_PUBLIC_NIC, public_ipv6) + res = self.getRouterProcessStatus(router, public_ip_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6 in res, + "%s failed on router %s" % (public_ip_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for public IPv6 on router %s" % router.id) + self.assertNotEqual(public_ipv6_gateway, + None, + "IPv6 gateway for router Public NIC is empty") + default_route_check_cmd = "ip -6 route | grep 'default via %s'" % (public_ipv6_gateway) + res = self.getRouterProcessStatus(router, default_route_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6_gateway in res, + "%s failed on router %s" % (default_route_check_cmd, router.id)) + + def checkIpv6VpcBackupRouter(self, router, network_ip6gateway): + self.checkRouterNicState(router, VPC_ROUTER_GUEST_NIC, "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", network_ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertFalse(type(res) == str and len(res) > 0 and network_ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.checkRouterNicState(router, VPC_ROUTER_PUBLIC_NIC, "DOWN") + + def checkIpv6VpcRoutersInternal(self): + network_ip6gateway = self.getNetworkGateway(self.network) + for router in self.routers: + if router.state != "Running": + continue + if router.isredundantrouter == True and router.redundantstate == 'BACKUP': + self.checkIpv6VpcBackupRouter(router, network_ip6gateway) + continue + self.checkIpv6VpcPrimaryRouter(router, network_ip6gateway) + + + def checkIpv6NetworkTierVm(self): + self.debug("Listing NICS for VM %s in network tier: %s" % (self.virtual_machine.name, self.network.name)) + nics = NIC.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + networkid=self.network.id + ) + self.assertEqual( + len(nics), + 1, + "VM NIC for the network tier isn't found" + ) + nic = nics[0] + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for VM %s NIC is empty" % nic.traffictype) + self.virtual_machine_ipv6_address = nic.ip6address + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for VM %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for VM %s NIC is empty" % nic.traffictype) + + def restartVpcWithCleanup(self): + self.vpc.restart(self.apiclient, cleanup=True) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + + def updateNetworkTierWithOffering(self): + self.network.update(self.apiclient, networkofferingid=self.network_offering_update.id) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + + def deployRoutingTestResources(self): + self.routing_test_vpc = self.deployAllowAllVpcInternal(ROUTE_TEST_VPC_DATA["cidr"]) + self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True) + self.routing_test_network = self.deployNetworkTierInternal( + self.routing_test_network_offering.id, + self.routing_test_vpc.id, + ROUTE_TEST_VPC_DATA["tier1_gateway"], + ROUTE_TEST_VPC_DATA["tier_netmask"] + ) + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.routing_test_vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=[self.routing_test_network.id], + serviceofferingid=self.service_offering.id, + mode="advanced", + vpcid=self.routing_test_vpc.id + ) + self.cleanup.append(self.routing_test_vm) + + def prepareRoutingTestResourcesInBackground(self): + self.thread = threading.Thread(target=self.deployRoutingTestResources, args=()) + self.thread.daemon = True + self.thread.start() + + def checkVpcRouting(self): + if not self.thread: + self.deployRoutingTestResources() + else: + self.thread.join(5*60) + self.assertFalse(not self.routing_test_vpc or not self.routing_test_network or not self.routing_test_vm, + "Routing resources failure") + + test_vpc_router = self.getVpcRouter(self.routing_test_vpc) + routes = self.getNetworkRoutes(self.network) + self.logger.debug("Adding vpc routes in routing_test_vpc %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(test_vpc_router, add_route_cmd) + + vpc_router = self.getVpcRouter(self.vpc) + routes = self.getNetworkRoutes(self.routing_test_network) + self.logger.debug("Adding routing_test_vpc routes in vpc %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(vpc_router, add_route_cmd) + + ping_cmd = "ping6 -c 4 %s" % self.virtual_machine_ipv6_address + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = self.getRouterProcessStatus(test_vpc_router, ping_cmd) + if " 0% packet loss" in res: + break + time.sleep(PING_SLEEP) + self.assertTrue(" 0% packet loss" in res, + "Ping from router %s of VPC %s to VM %s of VPC %s is unsuccessful" % (test_vpc_router.id, self.routing_test_vpc.id, self.virtual_machine.id, self.vpc.id)) + + ssh = self.routing_test_vm.get_ssh_client(retries=5) + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = ssh.execute(ping_cmd) + if type(res) == list and len(res) > 0 and " 0% packet loss" in '\n'.join(res): + break + time.sleep(PING_SLEEP) + self.assertTrue(type(res) == list and len(res) > 0, + "%s on VM %s returned invalid result" % (ping_cmd, self.routing_test_vm.id)) + self.logger.debug(res) + res = '\n'.join(res) + self.assertTrue(" 0% packet loss" in res, + "Ping from VM %s of VPC %s to VM %s of VPC %s is unsuccessful" % (self.routing_test_vm.id, self.routing_test_vpc.id, self.virtual_machine.id, self.vpc.id)) + + def createNetworkAclRule(self, rule, aclid): + return NetworkACL.create(self.apiclient, + services=rule, + aclid=aclid + ) + + def verifyAclRulesInRouter(self, nic, rules, router): + for rule in rules: + acl_chain = nic + ACL_CHAINS_SUFFIX[rule["traffictype"]] + routerCmd = "nft list chain ip6 %s %s" % (ACL_TABLE, acl_chain) + res = self.getRouterProcessStatus(router, routerCmd) + self.assertTrue(rule["parsedrule"] in res, + "Listing firewall rule with nft list chain failure for rule: %s" % rule["parsedrule"]) + + def checkIpv6AclRule(self): + router = self.getVpcRouter(self.vpc) + + tier1_acl = NetworkACLList.create( + self.apiclient, + services={}, + name="tier1_acl", + description="tier1_acl", + vpcid=self.vpc.id + ) + rules = [] + # Ingress - ip6 saddr SOURCE_CIDR tcp dport { START_PORT-END_PORT } accept + rule = {} + rule["traffictype"] = "Ingress" + rule["cidrlist"] = self.getRandomIpv6Cidr() + rule["protocol"] = "tcp" + rule["startport"] = randint(3000, 5000) + rule["endport"] = rule["startport"] + randint(1, 8) + parsedrule = "ip6 saddr %s %s dport { %d-%d } accept" % (rule["cidrlist"], rule["protocol"], rule["startport"], rule["endport"]) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier1_acl.id) + # Egress - ip6 daddr DEST_CIDR icmpv6 type TYPE code CODE accept + rule = {} + rule["traffictype"] = "Egress" + rule["cidrlist"] = self.getRandomIpv6Cidr() + rule["protocol"] = "icmp" + rule["icmptype"] = choice(list(ICMPV6_TYPE.keys())) + rule["icmpcode"] = choice(list(ICMPV6_CODE_TYPE.keys())) + parsedrule = "ip6 daddr %s %sv6 type %s %sv6 code %s accept" % (rule["cidrlist"], rule["protocol"], ICMPV6_TYPE[rule["icmptype"]], rule["protocol"], ICMPV6_CODE_TYPE[rule["icmpcode"]]) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier1_acl.id) + + self.network.replaceACLList(self.apiclient, tier1_acl.id) + + self.verifyAclRulesInRouter("eth2", rules, router) + + + tier2_acl = NetworkACLList.create( + self.apiclient, + services={}, + name="tier2_acl", + description="tier2_acl", + vpcid=self.vpc.id + ) + rules = [] + # Ingress - ip6 saddr ::/0 udp dport { 0-65355 } ACTION + rule = {} + rule["traffictype"] = "Ingress" + rule["cidrlist"] = CIDR_IPV6_ANY + rule["protocol"] = "udp" + parsedrule = "ip6 saddr %s %s dport %s accept" % (rule["cidrlist"], rule["protocol"], TCP_UDP_PORT_ANY) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier2_acl.id) + # Egress - ip6 daddr DEST_CIDR icmpv6 type TYPE code CODE accept + rule = {} + rule["traffictype"] = "Egress" + rule["protocol"] = "all" + parsedrule = "ip6 daddr %s accept" % (CIDR_IPV6_ANY) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier2_acl.id) + + self.network_offering_tier2 = self.createNetworkTierOfferingInternal(True, False) + self.tier2_network = self.deployNetworkTierInternal( + self.network_offering_tier2.id, + self.vpc.id, + VPC_DATA["tier2_gateway"], + VPC_DATA["tier_netmask"], + tier2_acl.id, + "tier2" + ) + self.tier2_vm = self.deployNetworkTierVmInternal(self.tier2_network.id) + + self.verifyAclRulesInRouter("eth3", rules, router) + + def checkVpcVRRedundancy(self): + network_ip6gateway = self.getNetworkGateway(self.network) + primary_router = self.getVpcRouter(self.vpc) + Router.stop( + self.apiclient, + id=primary_router.id + ) + time.sleep(self.services["sleep"]/2) + new_primary_router = self.getVpcRouter(self.vpc) + self.assertNotEqual(new_primary_router.id, primary_router.id, + "Original primary router ID: %s of VPC is still the primary router after stopping" % (primary_router.id)) + self.checkIpv6VpcPrimaryRouter(new_primary_router, network_ip6gateway) + + def checkIpv6VpcNetworking(self, check_vm=False): + self.checkIpv6Vpc() + self.checkIpv6NetworkTierBasic() + self.checkIpv6VpcRoutersBasic() + self.checkIpv6VpcRoutersInternal() + if check_vm: + self.checkIpv6NetworkTierVm() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_01_verify_ipv6_vpc(self): + """Test to verify IPv6 VPC + + # Validate the following: + # 1. Create IPv6 VPC, add tiers, deploy VM + # 2. Verify VPC, tier has required IPv6 details + # 3. List router for the VPC and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network tier has required IPv6 details + # 6. Restart VPC with cleanup and re-verify VPC networking + # 7. Update network tier with a new offering and re-verify VPC networking + # 8. Deploy another IPv6 VPC with tier and check routing between two VPC and their VM + # 9. Create IPv6 ACL rules in two different VPC tiers and verify in VR if they get implemented correctly + """ + + self.createIpv6VpcOffering() + self.deployVpc() + self.createIpv6NetworkTierOffering() + self.createIpv6NetworkTierOfferingForUpdate() + self.createTinyServiceOffering() + self.deployNetworkTier() + self.deployNetworkTierVm() + self.checkIpv6VpcNetworking(True) + self.prepareRoutingTestResourcesInBackground() + self.restartVpcWithCleanup() + self.checkIpv6VpcNetworking() + self.updateNetworkTierWithOffering() + self.checkIpv6VpcNetworking() + self.checkVpcRouting() + self.checkIpv6AclRule() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_02_verify_ipv6_vpc_redundant(self): + """Test to verify redundant IPv6 VPC + + # Validate the following: + # 1. Create redundant IPv6 VPC, add tiers, deploy VM + # 2. Verify VPC, tier has required IPv6 details + # 3. List router for the VPC and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network tier has required IPv6 details + # 6. Restart VPC with cleanup and re-verify VPC networking + # 7. Update network tier with a new offering and re-verify VPC networking + # 8. Deploy another IPv6 VPC with tier and check routing between two VPC and their VM + # 9. Create IPv6 ACL rules in two different VPC tiers and verify in VR if they get implemented correctly + # 10. Stop primary router and verify internals in backup VR + """ + + self.createIpv6VpcOffering(True) + self.deployVpc() + self.createIpv6NetworkTierOffering() + self.createIpv6NetworkTierOfferingForUpdate() + self.createTinyServiceOffering() + self.deployNetworkTier() + self.deployNetworkTierVm() + self.checkIpv6VpcNetworking(True) + self.prepareRoutingTestResourcesInBackground() + self.restartVpcWithCleanup() + self.checkIpv6VpcNetworking() + self.updateNetworkTierWithOffering() + self.checkIpv6VpcNetworking() + self.checkVpcRouting() + self.checkIpv6AclRule() + self.checkVpcVRRedundancy() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_03_verify_upgraded_ipv6_vpc(self): + """Test to verify IPv4 VPC tier upgraded to IPv6 VPC tier + + # Validate the following: + # 1. Create IPv4 VPC, add tiers, deploy VM + # 2. Update VPC tier to IPv6 offering + # 3. Verify VPC, tier has required IPv6 details + # 4. List router for the VPC and verify it has required IPv6 details for Guest and Public NIC of the VR + # 5. SSH into VR(s) and verify correct details are present for its NICs + # 6. Verify VM in network tier has required IPv6 details + # 7. Restart VPC with cleanup and re-verify VPC networking + # 8. Deploy another IPv6 VPC with tier and check routing between two VPC and their VM + # 9. Create IPv6 ACL rules in two different VPC tiers and verify in VR if they get implemented correctly + """ + + self.createIpv6VpcOffering() + self.deployVpc() + self.prepareRoutingTestResourcesInBackground() + self.createIpv4NetworkTierOffering() + self.createIpv6NetworkTierOfferingForUpdate() + self.createTinyServiceOffering() + self.deployNetworkTier() + self.deployNetworkTierVm() + self.updateNetworkTierWithOffering() + self.checkIpv6VpcNetworking(True) + self.restartVpcWithCleanup() + self.checkIpv6VpcNetworking() + self.checkVpcRouting() + self.checkIpv6AclRule() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_04_verify_upgraded_ipv6_vpc_redundant(self): + """Test to verify redunadnt IPv4 VPC tier upgraded to IPv6 VPC tier + + # Validate the following: + # 1. Create redundant IPv4 VPC, add tiers, deploy VM + # 2. Update VPC tier to IPv6 offering + # 3. Verify VPC, tier has required IPv6 details + # 4. List router for the VPC and verify it has required IPv6 details for Guest and Public NIC of the VR + # 5. SSH into VR(s) and verify correct details are present for its NICs + # 6. Verify VM in network tier has required IPv6 details + # 7. Restart VPC with cleanup and re-verify VPC networking + # 8. Deploy another IPv6 VPC with tier and check routing between two VPC and their VM + # 9. Create IPv6 ACL rules in two different VPC tiers and verify in VR if they get implemented correctly + # 10. Stop primary router and verify internals in backup VR + """ + + self.createIpv6VpcOffering(True) + self.deployVpc() + self.prepareRoutingTestResourcesInBackground() + self.createIpv4NetworkTierOffering() + self.createIpv6NetworkTierOfferingForUpdate() + self.createTinyServiceOffering() + self.deployNetworkTier() + self.deployNetworkTierVm() + self.updateNetworkTierWithOffering() + self.checkIpv6VpcNetworking(True) + self.restartVpcWithCleanup() + self.checkIpv6VpcNetworking() + self.checkVpcRouting() + self.checkIpv6AclRule() + self.checkVpcVRRedundancy() diff --git a/test/integration/smoke/test_ipv6_infra.py b/test/integration/smoke/test_ipv6_infra.py new file mode 100644 index 00000000000..30f57ee6526 --- /dev/null +++ b/test/integration/smoke/test_ipv6_infra.py @@ -0,0 +1,488 @@ +# 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. +""" BVT tests for IPv6 infra operations""" + +#Import Local Modules +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createGuestNetworkIpv6Prefix, + listGuestNetworkIpv6Prefixes, + deleteGuestNetworkIpv6Prefix) +from marvin.lib.utils import (cleanup_resources) +from marvin.lib.base import (Configurations, + NetworkOffering, + VpcOffering, + PublicIpRange) +from marvin.lib.common import (get_zone) +from marvin.cloudstackException import CloudstackAPIException + +from nose.plugins.attrib import attr +import logging + +ipv6_offering_config_name = "ipv6.offering.enabled" + +class TestCreateIpv6NetworkVpcOffering(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestCreateIpv6NetworkVpcOffering, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.initial_ipv6_offering_enabled = Configurations.list( + cls.apiclient, + name=ipv6_offering_config_name)[0].value + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + if cls.initial_ipv6_offering_enabled != None: + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + cls.initial_ipv6_offering_enabled) + super(TestCreateIpv6NetworkVpcOffering, cls).tearDownClass() + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_01_create_ipv6_network_offering(self): + """Test to create network offering + + # Validate the following: + # 1. createNetworkOffering should return valid info for new offering + # 2. The Cloud Database contains the valid information + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "true") + ipv6_service = self.services["network_offering"] + ipv6_service["internetprotocol"] = "dualstack" + network_offering = NetworkOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(network_offering) + + self.debug("Created Network offering with ID: %s" % network_offering.id) + + list_network_off_response = NetworkOffering.list(self.apiclient, + id=network_offering.id) + self.assertEqual( + isinstance(list_network_off_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_network_off_response), + 0, + "Check Network offering is created" + ) + network_off_response = list_network_off_response[0] + + self.assertEqual( + network_off_response.id, + network_offering.id, + "Check server id in listNetworkOfferings" + ) + self.assertEqual( + network_off_response.internetprotocol.lower(), + ipv6_service["internetprotocol"].lower(), + "Check internetprotocol in listNetworkOfferings" + ) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_02_create_ipv6_network_offering_fail(self): + """Test to create network offering + + # Validate the following: + # 1. createNetworkOffering should fail + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "false") + ipv6_service = self.services["network_offering"] + ipv6_service["internetprotocol"] = "dualstack" + try: + network_offering = NetworkOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(network_offering) + self.fail("Network offering created despite global setting - %s set to false" % ipv6_offering_config_name) + except CloudstackAPIException as e: + self.debug("Network offering creation failed as expected %s " % e) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_03_create_ipv6_vpc_offering(self): + """Test to create network offering + + # Validate the following: + # 1. createVpcOffering should return valid info for new offering + # 2. The Cloud Database contains the valid information + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "true") + ipv6_service = self.services["vpc_offering"] + ipv6_service["internetprotocol"] = "dualstack" + vpc_offering = VpcOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(vpc_offering) + + self.debug("Created VPC offering with ID: %s" % vpc_offering.id) + + list_vpc_off_response = VpcOffering.list(self.apiclient, + id=vpc_offering.id) + self.assertEqual( + isinstance(list_vpc_off_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vpc_off_response), + 0, + "Check VPC offering is created" + ) + vpc_off_response = list_vpc_off_response[0] + self.assertEqual( + vpc_off_response.id, + vpc_offering.id, + "Check server id in listVpcOfferings" + ) + self.assertEqual( + vpc_off_response.internetprotocol.lower(), + ipv6_service["internetprotocol"].lower(), + "Check internetprotocol in listVpcOfferings" + ) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_04_create_ipv6_vpc_offering_fail(self): + """Test to create VPC offering failure + + # Validate the following: + # 1. createVpcOffering should fail + """ + Configurations.update(self.apiclient, + ipv6_offering_config_name, + "false") + ipv6_service = self.services["vpc_offering"] + ipv6_service["internetprotocol"] = "dualstack" + try: + vpc_offering = VpcOffering.create( + self.apiclient, + ipv6_service + ) + self.cleanup.append(vpc_offering) + self.fail("VPC offering created despite global setting - %s set to false" % ipv6_offering_config_name) + except CloudstackAPIException as e: + self.debug("VPC offering creation failed as expected %s " % e) + return + +class TestIpv6PublicIpRange(cloudstackTestCase): + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6PublicIpRange, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + super(TestIpv6PublicIpRange, cls).tearDownClass() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_01_create_ipv6_public_ip_range(self): + """Test to add IPv6 public IP range + + # Validate the following: + # 1. createVlanIpRange should return valid info for new public range + # 2. The Cloud Database contains the valid information + """ + ipv6_publiciprange_service = self.services["publicip6range"] + ipv6_publiciprange_service["zoneid"] = self.zone.id + ipv6_publiciprange = PublicIpRange.create( + self.apiclient, + ipv6_publiciprange_service + ) + self.cleanup.append(ipv6_publiciprange) + + self.debug("Created IPv6 public IP range with ID: %s" % ipv6_publiciprange.vlan.id) + ipv6_publiciprange = ipv6_publiciprange.vlan + + public_ip_ranges = PublicIpRange.list( + self.apiclient, + id=ipv6_publiciprange.id + ) + self.assertEqual( + isinstance(public_ip_ranges, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(public_ip_ranges), + 0, + "Check public IP range is created" + ) + public_ip_range = public_ip_ranges[0] + + self.assertEqual( + public_ip_range.id, + ipv6_publiciprange.id, + "Check server id" + ) + self.assertEqual( + public_ip_range.ip6cidr, + ipv6_publiciprange_service["ip6cidr"], + "Check ip6cidr for IPv6 public IP range" + ) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_02_create_ipv6_public_ip_range_fail(self): + """Test to add IPv6 public IP range failure + + # Validate the following: + # 1. createVlanIpRange should return valid info for new public range + # 2. The Cloud Database contains the valid information + """ + ipv6_publiciprange_service = self.services["publicip6range"] + cidr = ipv6_publiciprange_service["ip6cidr"] + x = cidr.split("/") + x[1] = "72" + cidr = "/".join(x) + ipv6_publiciprange_service["ip6cidr"] = cidr + ipv6_publiciprange_service["zoneid"] = self.zone.id + try: + ipv6_publiciprange = PublicIpRange.create( + self.apiclient, + ipv6_publiciprange_service + ) + except Exception as e: + self.debug("IPv6 public range creation failed as expected %s " % e) + ipv6_publiciprange = None + if ipv6_publiciprange != None: + self.debug("Created IPv6 public range with ID: %s. Deleting it before failure" % ipv6_publiciprange.id) + self.cleanup.append(ipv6_publiciprange) + self.fail("IPv6 guest prefix created despite CIDR size greater than 64") + return + +class TestIpv6GuestPrefix(cloudstackTestCase): + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created templates + cleanup_resources(self.apiclient, self.cleanup) + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6GuestPrefix, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls._cleanup = [] + return + + @classmethod + def tearDownClass(cls): + super(TestIpv6GuestPrefix, cls).tearDownClass() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_01_create_ipv6_guest_prefix(self): + """Test to add IPv6 guest prefix + + # Validate the following: + # 1. createGuestNetworkIpv6Prefix should return valid info for new IPv6 prefix + # 2. The Cloud Database contains the valid information + """ + ipv6_guestprefix_service = self.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = self.zone.id + cmd.prefix = ipv6_guestprefix_service["prefix"] + ipv6_guestprefix = self.apiclient.createGuestNetworkIpv6Prefix(cmd) + + self.debug("Created IPv6 guest prefix with ID: %s" % ipv6_guestprefix.id) + + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.id = ipv6_guestprefix.id + ipv6_guestprefixes = self.apiclient.listGuestNetworkIpv6Prefixes(cmd) + self.assertEqual( + isinstance(ipv6_guestprefixes, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(ipv6_guestprefixes), + 0, + "Check guest IPv6 prefix is created" + ) + ipv6_guestprefix_response = ipv6_guestprefixes[0] + + self.assertEqual( + ipv6_guestprefix.id, + ipv6_guestprefix_response.id, + "Check server id" + ) + self.assertEqual( + ipv6_guestprefix_response.prefix, + ipv6_guestprefix_service["prefix"], + "Check prefix for IPv6" + ) + + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = ipv6_guestprefix.id + self.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + return + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_02_create_ipv6_guest_prefix_fail(self): + """Test to add IPv6 guest prefix failure + + # Validate the following: + # 1. createGuestNetworkIpv6Prefix should fail + """ + ipv6_guestprefix_service = self.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = self.zone.id + prefix = ipv6_guestprefix_service["prefix"] + x = prefix.split("/") + x[1] = "72" + prefix = "/".join(x) + cmd.prefix = prefix + try: + ipv6_guestprefix = self.apiclient.createGuestNetworkIpv6Prefix(cmd) + except Exception as e: + self.debug("IPv6 guest prefix creation failed as expected %s " % e) + ipv6_guestprefix = None + if ipv6_guestprefix != None: + self.debug("Created IPv6 guest prefix with ID: %s. Deleting it before failure" % ipv6_guestprefix.id) + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = ipv6_guestprefix.id + self.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + self.fail("IPv6 guest prefix created despite CIDR size greater than 64") diff --git a/test/integration/smoke/test_network_ipv6.py b/test/integration/smoke/test_network_ipv6.py index 51ce21a6a43..385ff636b16 100644 --- a/test/integration/smoke/test_network_ipv6.py +++ b/test/integration/smoke/test_network_ipv6.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -""" BVT tests for Network offerings""" +""" BVT test for IPv6 Network""" #Import Local Modules from marvin.codes import FAILED @@ -25,15 +25,12 @@ from marvin.cloudstackAPI import (createGuestNetworkIpv6Prefix, listIpv6FirewallRules, createIpv6FirewallRule, deleteIpv6FirewallRule) -from marvin.lib.utils import (isAlmostEqual, - cleanup_resources, - random_gen, +from marvin.lib.utils import (random_gen, get_process_status, get_host_credentials) from marvin.lib.base import (Configurations, Domain, NetworkOffering, - VpcOffering, Account, PublicIpRange, Network, @@ -45,8 +42,7 @@ from marvin.lib.base import (Configurations, from marvin.lib.common import (get_domain, get_zone, list_hosts, - get_test_template, - get_template) + get_test_template) from marvin.sshClient import SshClient from marvin.cloudstackException import CloudstackAPIException from marvin.lib.decoratorGenerators import skipTestIf @@ -56,6 +52,7 @@ from ipaddress import IPv6Network from random import getrandbits, choice, randint import time import logging +import threading ipv6_offering_config_name = "ipv6.offering.enabled" ULA_BASE = IPv6Network("fd00::/8") @@ -96,459 +93,9 @@ ICMPV6_CODE_TYPE = { } ICMPV6_TYPE_ANY = "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering }" TCP_UDP_PORT_ANY = "{ 0-65535 }" - -class TestCreateIpv6NetworkVpcOffering(cloudstackTestCase): - - @classmethod - def setUpClass(cls): - testClient = super(TestCreateIpv6NetworkVpcOffering, cls).getClsTestClient() - cls.apiclient = testClient.getApiClient() - cls.services = testClient.getParsedTestDataConfig() - cls.initial_ipv6_offering_enabled = Configurations.list( - cls.apiclient, - name=ipv6_offering_config_name)[0].value - cls._cleanup = [] - return - - @classmethod - def tearDownClass(cls): - if cls.initial_ipv6_offering_enabled != None: - Configurations.update(cls.apiclient, - ipv6_offering_config_name, - cls.initial_ipv6_offering_enabled) - super(TestCreateIpv6NetworkVpcOffering, cls).tearDownClass() - - def setUp(self): - self.services = self.testClient.getParsedTestDataConfig() - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created templates - cleanup_resources(self.apiclient, self.cleanup) - - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_01_create_ipv6_network_offering(self): - """Test to create network offering - - # Validate the following: - # 1. createNetworkOffering should return valid info for new offering - # 2. The Cloud Database contains the valid information - """ - Configurations.update(self.apiclient, - ipv6_offering_config_name, - "true") - ipv6_service = self.services["network_offering"] - ipv6_service["internetprotocol"] = "dualstack" - network_offering = NetworkOffering.create( - self.apiclient, - ipv6_service - ) - self.cleanup.append(network_offering) - - self.debug("Created Network offering with ID: %s" % network_offering.id) - - list_network_off_response = NetworkOffering.list(self.apiclient, - id=network_offering.id) - self.assertEqual( - isinstance(list_network_off_response, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(list_network_off_response), - 0, - "Check Network offering is created" - ) - network_off_response = list_network_off_response[0] - - self.assertEqual( - network_off_response.id, - network_offering.id, - "Check server id in listNetworkOfferings" - ) - self.assertEqual( - network_off_response.internetprotocol.lower(), - ipv6_service["internetprotocol"].lower(), - "Check internetprotocol in listNetworkOfferings" - ) - return - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_02_create_ipv6_network_offering_fail(self): - """Test to create network offering - - # Validate the following: - # 1. createNetworkOffering should fail - """ - Configurations.update(self.apiclient, - ipv6_offering_config_name, - "false") - ipv6_service = self.services["network_offering"] - ipv6_service["internetprotocol"] = "dualstack" - try: - network_offering = NetworkOffering.create( - self.apiclient, - ipv6_service - ) - self.cleanup.append(network_offering) - self.fail("Network offering created despite global setting - %s set to false" % ipv6_offering_config_name) - except CloudstackAPIException as e: - self.debug("Network offering creation failed as expected %s " % e) - return - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_03_create_ipv6_vpc_offering(self): - """Test to create network offering - - # Validate the following: - # 1. createVpcOffering should return valid info for new offering - # 2. The Cloud Database contains the valid information - """ - Configurations.update(self.apiclient, - ipv6_offering_config_name, - "true") - ipv6_service = self.services["vpc_offering"] - ipv6_service["internetprotocol"] = "dualstack" - vpc_offering = VpcOffering.create( - self.apiclient, - ipv6_service - ) - self.cleanup.append(vpc_offering) - - self.debug("Created VPC offering with ID: %s" % vpc_offering.id) - - list_vpc_off_response = VpcOffering.list(self.apiclient, - id=vpc_offering.id) - self.assertEqual( - isinstance(list_vpc_off_response, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(list_vpc_off_response), - 0, - "Check VPC offering is created" - ) - vpc_off_response = list_vpc_off_response[0] - self.assertEqual( - vpc_off_response.id, - vpc_offering.id, - "Check server id in listVpcOfferings" - ) - self.assertEqual( - vpc_off_response.internetprotocol.lower(), - ipv6_service["internetprotocol"].lower(), - "Check internetprotocol in listVpcOfferings" - ) - return - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_04_create_ipv6_vpc_offering_fail(self): - """Test to create VPC offering failure - - # Validate the following: - # 1. createVpcOffering should fail - """ - Configurations.update(self.apiclient, - ipv6_offering_config_name, - "false") - ipv6_service = self.services["vpc_offering"] - ipv6_service["internetprotocol"] = "dualstack" - try: - vpc_offering = VpcOffering.create( - self.apiclient, - ipv6_service - ) - self.cleanup.append(vpc_offering) - self.fail("VPC offering created despite global setting - %s set to false" % ipv6_offering_config_name) - except CloudstackAPIException as e: - self.debug("VPC offering creation failed as expected %s " % e) - return - -class TestIpv6PublicIpRange(cloudstackTestCase): - - def setUp(self): - self.services = self.testClient.getParsedTestDataConfig() - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created templates - cleanup_resources(self.apiclient, self.cleanup) - - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @classmethod - def setUpClass(cls): - testClient = super(TestIpv6PublicIpRange, cls).getClsTestClient() - cls.apiclient = testClient.getApiClient() - cls.services = testClient.getParsedTestDataConfig() - cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) - cls._cleanup = [] - return - - @classmethod - def tearDownClass(cls): - super(TestIpv6PublicIpRange, cls).tearDownClass() - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_01_create_ipv6_public_ip_range(self): - """Test to add IPv6 public IP range - - # Validate the following: - # 1. createVlanIpRange should return valid info for new public range - # 2. The Cloud Database contains the valid information - """ - ipv6_publiciprange_service = self.services["publicip6range"] - ipv6_publiciprange_service["zoneid"] = self.zone.id - ipv6_publiciprange = PublicIpRange.create( - self.apiclient, - ipv6_publiciprange_service - ) - self.cleanup.append(ipv6_publiciprange) - - self.debug("Created IPv6 public IP range with ID: %s" % ipv6_publiciprange.vlan.id) - ipv6_publiciprange = ipv6_publiciprange.vlan - - public_ip_ranges = PublicIpRange.list( - self.apiclient, - id=ipv6_publiciprange.id - ) - self.assertEqual( - isinstance(public_ip_ranges, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(public_ip_ranges), - 0, - "Check public IP range is created" - ) - public_ip_range = public_ip_ranges[0] - - self.assertEqual( - public_ip_range.id, - ipv6_publiciprange.id, - "Check server id" - ) - self.assertEqual( - public_ip_range.ip6cidr, - ipv6_publiciprange_service["ip6cidr"], - "Check ip6cidr for IPv6 public IP range" - ) - return - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_02_create_ipv6_public_ip_range_fail(self): - """Test to add IPv6 public IP range failure - - # Validate the following: - # 1. createVlanIpRange should return valid info for new public range - # 2. The Cloud Database contains the valid information - """ - ipv6_publiciprange_service = self.services["publicip6range"] - cidr = ipv6_publiciprange_service["ip6cidr"] - x = cidr.split("/") - x[1] = "72" - cidr = "/".join(x) - ipv6_publiciprange_service["ip6cidr"] = cidr - ipv6_publiciprange_service["zoneid"] = self.zone.id - try: - ipv6_publiciprange = PublicIpRange.create( - self.apiclient, - ipv6_publiciprange_service - ) - except Exception as e: - self.debug("IPv6 public range creation failed as expected %s " % e) - ipv6_publiciprange = None - if ipv6_publiciprange != None: - self.debug("Created IPv6 public range with ID: %s. Deleting it before failure" % ipv6_publiciprange.id) - self.cleanup.append(ipv6_publiciprange) - self.fail("IPv6 guest prefix created despite CIDR size greater than 64") - return - -class TestIpv6GuestPrefix(cloudstackTestCase): - - def setUp(self): - self.services = self.testClient.getParsedTestDataConfig() - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created templates - cleanup_resources(self.apiclient, self.cleanup) - - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @classmethod - def setUpClass(cls): - testClient = super(TestIpv6GuestPrefix, cls).getClsTestClient() - cls.apiclient = testClient.getApiClient() - cls.services = testClient.getParsedTestDataConfig() - cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) - cls._cleanup = [] - return - - @classmethod - def tearDownClass(cls): - super(TestIpv6GuestPrefix, cls).tearDownClass() - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_01_create_ipv6_guest_prefix(self): - """Test to add IPv6 guest prefix - - # Validate the following: - # 1. createGuestNetworkIpv6Prefix should return valid info for new IPv6 prefix - # 2. The Cloud Database contains the valid information - """ - ipv6_guestprefix_service = self.services["guestip6prefix"] - cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() - cmd.zoneid = self.zone.id - cmd.prefix = ipv6_guestprefix_service["prefix"] - ipv6_guestprefix = self.apiclient.createGuestNetworkIpv6Prefix(cmd) - - self.debug("Created IPv6 guest prefix with ID: %s" % ipv6_guestprefix.id) - - cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() - cmd.id = ipv6_guestprefix.id - ipv6_guestprefixes = self.apiclient.listGuestNetworkIpv6Prefixes(cmd) - self.assertEqual( - isinstance(ipv6_guestprefixes, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(ipv6_guestprefixes), - 0, - "Check guest IPv6 prefix is created" - ) - ipv6_guestprefix_response = ipv6_guestprefixes[0] - - self.assertEqual( - ipv6_guestprefix.id, - ipv6_guestprefix_response.id, - "Check server id" - ) - self.assertEqual( - ipv6_guestprefix_response.prefix, - ipv6_guestprefix_service["prefix"], - "Check prefix for IPv6" - ) - - cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() - cmd.id = ipv6_guestprefix.id - self.apiclient.deleteGuestNetworkIpv6Prefix(cmd) - return - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - def test_02_create_ipv6_guest_prefix_fail(self): - """Test to add IPv6 guest prefix failure - - # Validate the following: - # 1. createGuestNetworkIpv6Prefix should fail - """ - ipv6_guestprefix_service = self.services["guestip6prefix"] - cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() - cmd.zoneid = self.zone.id - prefix = ipv6_guestprefix_service["prefix"] - x = prefix.split("/") - x[1] = "72" - prefix = "/".join(x) - cmd.prefix = prefix - try: - ipv6_guestprefix = self.apiclient.createGuestNetworkIpv6Prefix(cmd) - except Exception as e: - self.debug("IPv6 guest prefix creation failed as expected %s " % e) - ipv6_guestprefix = None - if ipv6_guestprefix != None: - self.debug("Created IPv6 guest prefix with ID: %s. Deleting it before failure" % ipv6_guestprefix.id) - cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() - cmd.id = ipv6_guestprefix.id - self.apiclient.deleteGuestNetworkIpv6Prefix(cmd) - self.fail("IPv6 guest prefix created despite CIDR size greater than 64") - return +SLEEP_BEFORE_VR_CHANGES = 45 +PING_RETRIES = 5 +PING_SLEEP = 20 class TestIpv6Network(cloudstackTestCase): @@ -593,17 +140,10 @@ class TestIpv6Network(cloudstackTestCase): ) cls._cleanup.append(cls.account) cls.hypervisor = testClient.getHypervisorInfo() - cls.template = get_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"] - ) - if cls.hypervisor.lower() in ('xenserver'): - # Default Xenserver template has IPv6 disabled - cls.template = get_test_template( - cls.apiclient, - cls.zone.id, - cls.hypervisor) + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) else: cls.debug("IPv6 is not supported, skipping tests!") return @@ -614,11 +154,13 @@ class TestIpv6Network(cloudstackTestCase): Configurations.update(cls.apiclient, ipv6_offering_config_name, cls.initial_ipv6_offering_enabled) - super(TestIpv6Network, cls).tearDownClass() - if cls.test_ipv6_guestprefix != None: - cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() - cmd.id = cls.test_ipv6_guestprefix.id - cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + try: + super(TestIpv6Network, cls).tearDownClass() + finally: + if cls.test_ipv6_guestprefix != None: + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = cls.test_ipv6_guestprefix.id + cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd) @classmethod def getGuestIpv6Prefix(cls): @@ -650,7 +192,10 @@ class TestIpv6Network(cloudstackTestCase): vlan = ip_range.vlan if ipv4_range_vlan == None and vlan.startswith("vlan://"): vlan = vlan.replace("vlan://", "") - ipv4_range_vlan = int(vlan) + if vlan == "untagged": + ipv4_range_vlan = None + else: + ipv4_range_vlan = int(vlan) ipv6_publiciprange_service = cls.services["publicip6range"] ipv6_publiciprange_service["zoneid"] = cls.zone.id ipv6_publiciprange_service["vlan"] = ipv4_range_vlan @@ -665,16 +210,18 @@ class TestIpv6Network(cloudstackTestCase): self.services = self.testClient.getParsedTestDataConfig() self.apiclient = self.testClient.getApiClient() self.dbclient = self.testClient.getDbConnection() + self.thread = None self.cleanup = [] return def tearDown(self): try: - #Clean up, terminate the created templates - cleanup_resources(self.apiclient, reversed(self.cleanup)) - + if self.thread and self.thread.is_alive(): + self.thread.join(5*60) except Exception as e: raise Exception("Warning: Exception during cleanup : %s" % e) + finally: + super(TestIpv6Network, self).tearDown() return def getRandomIpv6Cidr(self): @@ -690,30 +237,33 @@ class TestIpv6Network(cloudstackTestCase): ) self.cleanup.append(self.service_offering) - def createIpv6NetworkOfferingInternal(self, is_redundant, egressdefaultpolicy=True): - ipv6_service = self.services["network_offering"] + def createNetworkOfferingInternal(self, is_redundant, is_ipv6, egressdefaultpolicy=True): + off_service = self.services["network_offering"] if is_redundant: - ipv6_service = self.services["nw_off_isolated_RVR"] - ipv6_service["internetprotocol"] = "dualstack" + off_service = self.services["nw_off_isolated_RVR"] + if is_ipv6: + off_service["internetprotocol"] = "dualstack" if egressdefaultpolicy: - ipv6_service["egress_policy"] = egressdefaultpolicy + off_service["egress_policy"] = egressdefaultpolicy network_offering = NetworkOffering.create( self.apiclient, - ipv6_service + off_service ) + self.cleanup.append(network_offering) network_offering.update(self.apiclient, state='Enabled') return network_offering - def createIpv6NetworkOffering(self, is_redundant): - self.network_offering = self.createIpv6NetworkOfferingInternal(is_redundant, False) - self.cleanup.append(self.network_offering) + def createIpv4NetworkOffering(self, is_redundant=False): + self.network_offering = self.createNetworkOfferingInternal(is_redundant, False, False) - def createIpv6NetworkOfferingForUpdate(self, is_redundant): - self.network_offering_update = self.createIpv6NetworkOfferingInternal(is_redundant) - self.cleanup.append(self.network_offering_update) + def createIpv6NetworkOffering(self, is_redundant=False): + self.network_offering = self.createNetworkOfferingInternal(is_redundant, True, False) + + def createIpv6NetworkOfferingForUpdate(self, is_redundant=False): + self.network_offering_update = self.createNetworkOfferingInternal(is_redundant, True) - def deployIpv6Network(self): + def deployNetwork(self): self.services["network"]["networkoffering"] = self.network_offering.id self.network = Network.create( self.apiclient, @@ -724,7 +274,7 @@ class TestIpv6Network(cloudstackTestCase): ) self.cleanup.append(self.network) - def deployIpv6NetworkVm(self): + def deployNetworkVm(self): if self.template == FAILED: assert False, "get_test_template() failed to return template" self.services["virtual_machine"]["zoneid"] = self.zone.id @@ -844,7 +394,7 @@ class TestIpv6Network(cloudstackTestCase): cmd, hypervisor=self.routerDetailsMap[router.id]['hypervisor'] ) - self.assertTrue(type(result) == list, + self.assertTrue(type(result) == list and len(result) > 0, "%s on router %s returned invalid result" % (cmd, router.id)) result = '\n'.join(result) return result @@ -865,6 +415,23 @@ class TestIpv6Network(cloudstackTestCase): if router.redundantstate == red_state: return router + def getNetworkGateway(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + return ipv6_network.ip6gateway + def getNetworkRoutes(self, network): ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) self.assertTrue( @@ -896,11 +463,11 @@ class TestIpv6Network(cloudstackTestCase): self.assertTrue(type(res) == str and len(res) > 0 and st in res, "%s failed on router %s" % (cmd, router.id)) - def checkIpv6NetworkPrimaryRouter(self, router): + def checkIpv6NetworkPrimaryRouter(self, router, network_ip6gateway): self.checkRouterNicState(router, "eth0", "UP") - guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", self.network.ip6gateway) + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", network_ip6gateway) res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) - self.assertTrue(type(res) == str and len(res) > 0 and self.network.ip6gateway in res, + self.assertTrue(type(res) == str and len(res) > 0 and network_ip6gateway in res, "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) self.assertFalse("dadfailed" in res, "dadfailed for IPv6 guest gateway on router %s" % router.id) @@ -930,22 +497,23 @@ class TestIpv6Network(cloudstackTestCase): self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6_gateway in res, "%s failed on router %s" % (default_route_check_cmd, router.id)) - def checkIpv6NetworkBackupRouter(self, router): + def checkIpv6NetworkBackupRouter(self, router, network_ip6gateway): self.checkRouterNicState(router, "eth0", "UP") - guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", self.network.ip6gateway) + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", network_ip6gateway) res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) - self.assertFalse(type(res) == str and len(res) > 0 and self.network.ip6gateway in res, + self.assertFalse(type(res) == str and len(res) > 0 and network_ip6gateway in res, "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) self.checkRouterNicState(router, "eth2", "DOWN") def checkIpv6NetworkRoutersInternal(self): + network_ip6gateway = self.getNetworkGateway(self.network) for router in self.routers: if router.state != "Running": continue if router.isredundantrouter == True and router.redundantstate == 'BACKUP': - self.checkIpv6NetworkBackupRouter(router) + self.checkIpv6NetworkBackupRouter(router, network_ip6gateway) continue - self.checkIpv6NetworkPrimaryRouter(router) + self.checkIpv6NetworkPrimaryRouter(router, network_ip6gateway) def checkIpv6NetworkVm(self): @@ -958,7 +526,7 @@ class TestIpv6Network(cloudstackTestCase): self.assertEqual( len(nics), 1, - "Router for the network isn't found" + "VM NIC for the network isn't found" ) nic = nics[0] self.assertNotEqual(nic.ip6address, @@ -974,9 +542,11 @@ class TestIpv6Network(cloudstackTestCase): def restartNetworkWithCleanup(self): self.network.restart(self.apiclient, cleanup=True) + time.sleep(SLEEP_BEFORE_VR_CHANGES) def updateNetworkWithOffering(self): self.network.update(self.apiclient, networkofferingid=self.network_offering_update.id) + time.sleep(SLEEP_BEFORE_VR_CHANGES) def createIpv6FirewallRuleInNetwork(self, network_id, traffic_type, source_cidr, dest_cidr, protocol, start_port, end_port, icmp_type, icmp_code): @@ -1000,9 +570,8 @@ class TestIpv6Network(cloudstackTestCase): fw_rule = self.apiclient.createIpv6FirewallRule(cmd) return fw_rule - def checkNetworkRouting(self): - self.routing_test_network_offering = self.createIpv6NetworkOfferingInternal(False, True) - self.cleanup.append(self.routing_test_network_offering) + def deployRoutingTestResources(self): + self.routing_test_network_offering = self.createNetworkOfferingInternal(False, True) self.services["network"]["networkoffering"] = self.routing_test_network_offering.id self.routing_test_network = Network.create( self.apiclient, @@ -1025,37 +594,62 @@ class TestIpv6Network(cloudstackTestCase): ) self.cleanup.append(self.routing_test_vm) + def prepareRoutingTestResourcesInBackground(self): + self.thread = threading.Thread(target=self.deployRoutingTestResources, args=()) + self.thread.daemon = True + self.thread.start() + + def checkIpv6NetworkRouting(self): + if not self.thread: + self.deployRoutingTestResources() + else: + self.thread.join(5*60) + self.assertFalse(not self.routing_test_network or not self.routing_test_vm, + "Routing resources failure") + fw1 = self.createIpv6FirewallRuleInNetwork(self.routing_test_network.id, "Ingress", None, None, "icmp", None, None, None, None) fw2 = self.createIpv6FirewallRuleInNetwork(self.network.id, "Ingress", None, None, "icmp", None, None, None, None) - router = self.getNetworkRouter(self.routing_test_network) - self.logger.debug("Adding network routes in routing_test_network %s" % self.network_ipv6_routes) - for route in self.network_ipv6_routes: + test_network_router = self.getNetworkRouter(self.routing_test_network) + routes = self.getNetworkRoutes(self.network) + self.logger.debug("Adding network routes in routing_test_network %s" % routes) + for route in routes: add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) - self.getRouterProcessStatus(router, add_route_cmd) + self.getRouterProcessStatus(test_network_router, add_route_cmd) - router = self.getNetworkRouter(self.network) + network_router = self.getNetworkRouter(self.network) routes = self.getNetworkRoutes(self.routing_test_network) self.logger.debug("Adding routing_test_network routes in network %s" % routes) for route in routes: add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) - self.getRouterProcessStatus(router, add_route_cmd) - - time.sleep(self.services["sleep"]) + self.getRouterProcessStatus(network_router, add_route_cmd) ping_cmd = "ping6 -c 4 %s" % self.virtual_machine_ipv6_address - res = self.getRouterProcessStatus(router, ping_cmd) + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = self.getRouterProcessStatus(test_network_router, ping_cmd) + if " 0% packet loss" in res: + break + time.sleep(PING_SLEEP) self.assertTrue(" 0% packet loss" in res, - "Ping from router %s of network %s to VM %s of network %s is unsuccessful" % (router.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) + "Ping from router %s of network %s to VM %s of network %s is unsuccessful" % (test_network_router.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) ssh = self.routing_test_vm.get_ssh_client(retries=5) - res = ssh.execute(ping_cmd) + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = ssh.execute(ping_cmd) + if type(res) == list and len(res) > 0 and " 0% packet loss" in '\n'.join(res): + break + time.sleep(PING_SLEEP) self.assertTrue(type(res) == list and len(res) > 0, "%s on VM %s returned invalid result" % (ping_cmd, self.routing_test_vm.id)) self.logger.debug(res) res = '\n'.join(res) + self.assertTrue(" 0% packet loss" in res, "Ping from VM %s of network %s to VM %s of network %s is unsuccessful" % (self.routing_test_vm.id, self.routing_test_network.id, self.virtual_machine.id, self.network.id)) @@ -1160,17 +754,22 @@ class TestIpv6Network(cloudstackTestCase): None, None, None, None, rule) def checkNetworkVRRedundancy(self): + network_ip6gateway = self.getNetworkGateway(self.network) primary_router = self.getNetworkRouter(self.network) Router.stop( self.apiclient, id=primary_router.id ) - time.sleep(self.services["sleep"]/2) + time.sleep(SLEEP_BEFORE_VR_CHANGES) new_primary_router = self.getNetworkRouter(self.network) self.assertNotEqual(new_primary_router.id, primary_router.id, "Original primary router ID: %s of network is still the primary router after stopping" % (primary_router.id)) - print(new_primary_router) - self.checkIpv6NetworkPrimaryRouter(new_primary_router) + self.checkIpv6NetworkPrimaryRouter(new_primary_router, network_ip6gateway) + + def checkIpv6Network(self): + self.checkIpv6NetworkBasic() + self.checkIpv6NetworkRoutersBasic() + self.checkIpv6NetworkRoutersInternal() @attr( @@ -1192,69 +791,23 @@ class TestIpv6Network(cloudstackTestCase): # 3. List router for the network and verify it has required IPv6 details for Guest and Public NIC of the VR # 4. SSH into VR(s) and verify correct details are present for its NICs # 5. Verify VM in network has required IPv6 details - # 6. Restart network with cleanup - # 7. Update network with a new offering - # 8. Again verify network and VR details - # 9. Deploy another IPv6 network and check routing between two networks and their VM - # 10. Create IPv6 firewall rules and verify in VR if they get implemented + # 6. Restart network with cleanup and re-verify network details + # 7. Update network with a new offering and re-verify network details + # 8. Deploy another IPv6 network and check routing between two networks and their VM + # 9. Create IPv6 firewall rules and verify in VR if they get implemented """ - self.createIpv6NetworkOffering(False) - self.createIpv6NetworkOfferingForUpdate(False) + self.createIpv6NetworkOffering() + self.createIpv6NetworkOfferingForUpdate() self.createTinyServiceOffering() - self.deployIpv6Network() - self.deployIpv6NetworkVm() - self.checkIpv6NetworkBasic() - self.checkIpv6NetworkRoutersBasic() - self.checkIpv6NetworkRoutersInternal() + self.deployNetwork() + self.deployNetworkVm() + self.checkIpv6Network() self.checkIpv6NetworkVm() + self.prepareRoutingTestResourcesInBackground() self.restartNetworkWithCleanup() + self.checkIpv6Network() self.updateNetworkWithOffering() - self.checkIpv6NetworkBasic() - self.checkIpv6NetworkRoutersBasic() - self.checkNetworkRouting() + self.checkIpv6Network() + self.checkIpv6NetworkRouting() self.checkIpv6FirewallRule() - - @attr( - tags=[ - "advanced", - "basic", - "eip", - "sg", - "advancedns", - "smoke"], - required_hardware="false") - @skipTestIf("ipv6NotSupported") - def test_02_verify_ipv6_network_redundant(self): - """Test to verify redundant IPv6 network - - # Validate the following: - # 1. Create IPv6 network, deploy VM - # 2. Verify network has required IPv6 details - # 3. List VRs for the network and verify it has required IPv6 details for Guest and Public NIC of the VR - # 4. SSH into VR(s) and verify correct details are present for its NICs - # 5. Verify VM in network has required IPv6 details - # 6. Restart network with cleanup - # 7. Update network with a new offering - # 8. Again verify network and VR details - # 9. Deploy another IPv6 network and check routing between two networks and their VM - # 10. Create IPv6 firewall rules and verify in VR if they get implemented - # 11. Stop primary router and verify internals in backup VR - """ - - self.createIpv6NetworkOffering(True) - self.createIpv6NetworkOfferingForUpdate(True) - self.createTinyServiceOffering() - self.deployIpv6Network() - self.deployIpv6NetworkVm() - self.checkIpv6NetworkBasic() - self.checkIpv6NetworkRoutersBasic() - self.checkIpv6NetworkRoutersInternal() - self.checkIpv6NetworkVm() - self.restartNetworkWithCleanup() - self.updateNetworkWithOffering() - self.checkIpv6NetworkBasic() - self.checkIpv6NetworkRoutersBasic() - self.checkNetworkRouting() - self.checkIpv6FirewallRule() - self.checkNetworkVRRedundancy() diff --git a/test/integration/smoke/test_vpc_ipv6.py b/test/integration/smoke/test_vpc_ipv6.py new file mode 100644 index 00000000000..e00c578809c --- /dev/null +++ b/test/integration/smoke/test_vpc_ipv6.py @@ -0,0 +1,901 @@ +# 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. +""" BVT test for IPv6 VPC""" + +#Import Local Modules +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createGuestNetworkIpv6Prefix, + listGuestNetworkIpv6Prefixes, + deleteGuestNetworkIpv6Prefix) +from marvin.lib.utils import (isAlmostEqual, + random_gen, + get_process_status, + get_host_credentials) +from marvin.lib.base import (Configurations, + Domain, + NetworkOffering, + VpcOffering, + Account, + PublicIpRange, + Network, + VPC, + Router, + ServiceOffering, + VirtualMachine, + NIC, + Host, + NetworkACLList, + NetworkACL) +from marvin.lib.common import (get_domain, + get_zone, + get_test_template, + get_template) +from marvin.sshClient import SshClient +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.decoratorGenerators import skipTestIf + +from nose.plugins.attrib import attr +from ipaddress import IPv6Network +from random import getrandbits, choice, randint +import time +import logging +import threading + +ipv6_offering_config_name = "ipv6.offering.enabled" +ULA_BASE = IPv6Network("fd00::/8") +PREFIX_OPTIONS = [i for i in range(48, 65, 4)] +ACL_TABLE = "ip6_acl" +ACL_CHAINS_SUFFIX = { + "Ingress": "_ingress_policy", + "Egress": "_egress_policy" +} +CIDR_IPV6_ANY = "::/0" +ICMPV6_TYPE = { + 1: "destination-unreachable", + 2: "packet-too-big", + 3: "time-exceeded", + 4: "parameter-problem", + 128: "echo-request", + 129: "echo-reply", + 130: "mld-listener-query", + 131: "mld-listener-report", + 132: "mld-listener-done", + 133: "nd-router-solicit", + 134: "nd-router-advert", + 135: "nd-neighbor-solicit", + 136: "nd-neighbor-advert", + 137: "nd-redirect", + 138: "router-renumbering", + 141: "ind-neighbor-solicit", + 142: "ind-neighbor-advert", + 143: "mld2-listener-report" +} +ICMPV6_CODE_TYPE = { + 0: "no-route", + 1: "admin-prohibited", + 3: "addr-unreachable", + 4: "port-unreachable", + 5: "policy-fail", + 6: "reject-route" +} +ICMPV6_TYPE_ANY = "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering }" +TCP_UDP_PORT_ANY = "{ 0-65535 }" +VPC_ROUTER_PUBLIC_NIC = "eth1" +VPC_ROUTER_GUEST_NIC = "eth2" +VPC_DATA = { + "cidr": "10.1.0.0/22", + "tier1_gateway": "10.1.1.1", + "tier2_gateway": "10.1.2.1", + "tier_netmask": "255.255.255.0" +} +ROUTE_TEST_VPC_DATA = { + "cidr": "10.2.0.0/22", + "tier1_gateway": "10.2.1.1", + "tier_netmask": "255.255.255.0" +} +SLEEP_BEFORE_VR_CHANGES = 90 +PING_RETRIES = 5 +PING_SLEEP = 20 + + +class TestIpv6Vpc(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestIpv6Vpc, cls).getClsTestClient() + cls.services = testClient.getParsedTestDataConfig() + cls.apiclient = testClient.getApiClient() + cls.dbclient = testClient.getDbConnection() + cls.test_ipv6_guestprefix = None + cls.initial_ipv6_offering_enabled = None + cls._cleanup = [] + cls.routerDetailsMap = {} + cls.vpcAllowAllAclDetailsMap = {} + + cls.logger = logging.getLogger('TestIpv6Vpc') + + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.ipv6NotSupported = False + + ipv6_guestprefix = cls.getGuestIpv6Prefix() + if ipv6_guestprefix == None: + cls.ipv6NotSupported = True + if cls.ipv6NotSupported == False: + ipv6_publiciprange = cls.getPublicIpv6Range() + if ipv6_publiciprange == None: + cls.ipv6NotSupported = True + + if cls.ipv6NotSupported == False: + cls.initial_ipv6_offering_enabled = Configurations.list( + cls.apiclient, + name=ipv6_offering_config_name)[0].value + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + "true") + cls.domain = get_domain(cls.apiclient) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.hypervisor = testClient.getHypervisorInfo() + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.debug("IPv6 is not supported, skipping tests!") + return + + @classmethod + def tearDownClass(cls): + if cls.initial_ipv6_offering_enabled != None: + Configurations.update(cls.apiclient, + ipv6_offering_config_name, + cls.initial_ipv6_offering_enabled) + try: + super(TestIpv6Vpc, cls).tearDownClass() + finally: + if cls.test_ipv6_guestprefix != None: + cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd() + cmd.id = cls.test_ipv6_guestprefix.id + cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd) + + @classmethod + def getGuestIpv6Prefix(cls): + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.zoneid = cls.zone.id + ipv6_prefixes_response = cls.apiclient.listGuestNetworkIpv6Prefixes(cmd) + if isinstance(ipv6_prefixes_response, list) == True and len(ipv6_prefixes_response) > 0: + return ipv6_prefixes_response[0] + ipv6_guestprefix_service = cls.services["guestip6prefix"] + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = cls.zone.id + cmd.prefix = ipv6_guestprefix_service["prefix"] + ipv6_guestprefix = cls.apiclient.createGuestNetworkIpv6Prefix(cmd) + cls.test_ipv6_guestprefix = ipv6_guestprefix + return ipv6_guestprefix + + @classmethod + def getPublicIpv6Range(cls): + list_public_ip_range_response = PublicIpRange.list( + cls.apiclient, + zoneid=cls.zone.id + ) + ipv4_range_vlan = None + if isinstance(list_public_ip_range_response, list) == True and len(list_public_ip_range_response) > 0: + for ip_range in list_public_ip_range_response: + if ip_range.ip6cidr != None and ip_range.ip6gateway != None: + return ip_range + if ip_range.netmask != None and ip_range.gateway != None: + vlan = ip_range.vlan + if ipv4_range_vlan == None and vlan.startswith("vlan://"): + vlan = vlan.replace("vlan://", "") + if vlan == "untagged": + ipv4_range_vlan = None + else: + ipv4_range_vlan = int(vlan) + ipv6_publiciprange_service = cls.services["publicip6range"] + ipv6_publiciprange_service["zoneid"] = cls.zone.id + ipv6_publiciprange_service["vlan"] = ipv4_range_vlan + ipv6_publiciprange = PublicIpRange.create( + cls.apiclient, + ipv6_publiciprange_service + ) + cls._cleanup.append(ipv6_publiciprange) + return ipv6_publiciprange + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.thread = None + self.cleanup = [] + return + + def tearDown(self): + try: + if self.thread and self.thread.is_alive(): + self.thread.join(5*60) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + finally: + super(TestIpv6Vpc, self).tearDown() + return + + def getRandomIpv6Cidr(self): + prefix_length = choice(PREFIX_OPTIONS) + random_suffix = getrandbits(40) << (128-prefix_length) + base_address = ULA_BASE.network_address + random_suffix + return str(IPv6Network((base_address, prefix_length))) + + def createTinyServiceOffering(self): + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["big"], + ) + self.cleanup.append(self.service_offering) + + def createVpcOfferingInternal(self, is_redundant, is_ipv6): + off_service = self.services["vpc_offering"] + if is_redundant: + off_service["serviceCapabilityList"] = { + "SourceNat": { + "RedundantRouter": 'true' + }, + } + if is_ipv6: + off_service["internetprotocol"] = "dualstack" + vpc_offering = VpcOffering.create( + self.apiclient, + off_service + ) + self.cleanup.append(vpc_offering) + vpc_offering.update(self.apiclient, state='Enabled') + return vpc_offering + + def createIpv4VpcOffering(self, is_redundant=False): + self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False) + + def createIpv6VpcOffering(self, is_redundant=False): + self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True) + + def createIpv6VpcOfferingForUpdate(self, is_redundant=False): + self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True) + + def createNetworkTierOfferingInternal(self, is_ipv6, remove_lb=True): + off_service = self.services["nw_offering_isolated_vpc"] + if not remove_lb: # Remove Lb service + if "serviceProviderList" in off_service and "Lb" in off_service["serviceProviderList"].keys(): + providers = off_service["serviceProviderList"] + providers.pop("Lb") + off_service["serviceProviderList"] = providers + if "supportedservices" in off_service and "Lb" in off_service["supportedservices"]: + supportedServices = off_service["supportedservices"].split(",") + supportedServices.remove("Lb") + off_service["supportedservices"] = ",".join(supportedServices) + if is_ipv6: + off_service["internetprotocol"] = "dualstack" + network_offering = NetworkOffering.create( + self.apiclient, + off_service, + conservemode=False + ) + self.cleanup.append(network_offering) + network_offering.update(self.apiclient, state='Enabled') + return network_offering + + def createIpv4NetworkTierOffering(self): + self.network_offering = self.createNetworkTierOfferingInternal(False) + + def createIpv6NetworkTierOffering(self, remove_lb=True): + self.network_offering = self.createNetworkTierOfferingInternal(True) + + def createIpv6NetworkTierOfferingForUpdate(self): + self.network_offering_update = self.createNetworkTierOfferingInternal(True) + + def deployAllowAllVpcInternal(self, cidr): + service = self.services["vpc"] + service["cidr"] = cidr + vpc = VPC.create( + self.apiclient, + self.services["vpc"], + vpcofferingid=self.vpc_offering.id, + zoneid=self.zone.id, + account=self.account.name, + domainid=self.account.domainid + ) + self.cleanup.append(vpc) + acl = NetworkACLList.create( + self.apiclient, + services={}, + name="allowall", + description="allowall", + vpcid=vpc.id + ) + rule ={ + "protocol": "all", + "traffictype": "ingress", + } + NetworkACL.create(self.apiclient, + services=rule, + aclid=acl.id + ) + rule["traffictype"] = "egress" + NetworkACL.create(self.apiclient, + services=rule, + aclid=acl.id + ) + self.vpcAllowAllAclDetailsMap[vpc.id] = acl.id + return vpc + + def deployVpc(self): + self.vpc = self.deployAllowAllVpcInternal(VPC_DATA["cidr"]) + + def deployNetworkTierInternal(self, network_offering_id, vpc_id, tier_gateway, tier_netmask, acl_id=None, tier_name=None): + if not acl_id and vpc_id in self.vpcAllowAllAclDetailsMap: + acl_id = self.vpcAllowAllAclDetailsMap[vpc_id] + service = self.services["ntwk"] + if tier_name: + service["name"] = tier_name + service["displaytext"] = "vpc-%s" % tier_name + network = Network.create( + self.apiclient, + service, + self.account.name, + self.account.domainid, + networkofferingid=network_offering_id, + vpcid=vpc_id, + zoneid=self.zone.id, + gateway=tier_gateway, + netmask=tier_netmask, + aclid=acl_id + ) + self.cleanup.append(network) + return network + + def deployNetworkTier(self): + self.network = self.deployNetworkTierInternal( + self.network_offering.id, + self.vpc.id, + VPC_DATA["tier1_gateway"], + VPC_DATA["tier_netmask"] + ) + + def deployNetworkTierVmInternal(self, network): + if self.template == FAILED: + assert False, "get_test_template() failed to return template" + self.services["virtual_machine"]["zoneid"] = self.zone.id + virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=network, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(virtual_machine) + return virtual_machine + + def deployNetworkTierVm(self): + self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id) + + def checkIpv6Vpc(self): + self.debug("Listing VPC: %s" % (self.vpc.name)) + ipv6_vpc = VPC.list(self.apiclient,listall="true",id=self.vpc.id) + self.assertTrue( + isinstance(ipv6_vpc, list), + "Check listVpcs response returns a valid list" + ) + self.assertEqual( + len(ipv6_vpc), + 1, + "Network not found" + ) + ipv6_vpc = ipv6_vpc[0] + self.assertNotEqual(ipv6_vpc.ip6routes, + None, + "IPv6 routes for network is empty") + + def checkIpv6NetworkTierBasic(self): + self.debug("Listing network: %s" % (self.network.name)) + ipv6_network = Network.list(self.apiclient,listall="true",id=self.network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network, + None, + "User is not able to retrieve network details %s" % self.network.id) + self.assertNotEqual(ipv6_network.ip6cidr, + None, + "IPv6 CIDR for network is empty") + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + + def checkIpv6VpcRoutersBasic(self): + self.debug("Listing routers for VPC: %s" % self.vpc.name) + self.routers = Router.list( + self.apiclient, + vpcid=self.vpc.id, + listall=True + ) + self.assertTrue( + isinstance(self.routers, list), + "Check listRouters response returns a valid list" + ) + self.assertTrue( + len(self.routers) > 0, + "Router for the network isn't found" + ) + for router in self.routers: + self.assertFalse( + router.isredundantrouter == True and router.redundantstate == "FAULT", + "Router for the network is in FAULT state" + ) + nics = router.nic + for nic in nics: + if (nic.traffictype == 'Guest' and router.isredundantrouter == False) or nic.traffictype == 'Public': + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for router %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for router %s NIC is empty" % nic.traffictype) + + + def getRouterProcessStatus(self, router, cmd): + if router.id not in self.routerDetailsMap or self.routerDetailsMap[router.id] is None: + connect_ip = self.apiclient.connection.mgtSvr + connect_user = self.apiclient.connection.user + connect_passwd = self.apiclient.connection.passwd + hypervisor = self.hypervisor + if self.hypervisor.lower() not in ('vmware', 'hyperv'): + hosts = Host.list( + self.apiclient, + zoneid=router.zoneid, + type='Routing', + state='Up', + id=router.hostid + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list host returns a valid list" + ) + host = hosts[0] + connect_ip = host.ipaddress + hypervisor = None + try: + connect_user, connect_passwd= get_host_credentials( + self.config, host.ipaddress) + except KeyError: + self.skipTest( + "Marvin configuration has no host credentials to\ + check router services") + details = {} + details['connect_ip'] = connect_ip + details['connect_user'] = connect_user + details['connect_passwd'] = connect_passwd + details['hypervisor'] = hypervisor + self.routerDetailsMap[router.id] = details + result = get_process_status( + self.routerDetailsMap[router.id]['connect_ip'], + 22, + self.routerDetailsMap[router.id]['connect_user'], + self.routerDetailsMap[router.id]['connect_passwd'], + router.linklocalip, + cmd, + hypervisor=self.routerDetailsMap[router.id]['hypervisor'] + ) + self.assertTrue(type(result) == list and len(result) > 0, + "%s on router %s returned invalid result" % (cmd, router.id)) + result = '\n'.join(result) + return result + + def getVpcRouter(self, vpc, red_state="PRIMARY"): + routers = Router.list( + self.apiclient, + vpcid=vpc.id, + listall=True + ) + self.assertTrue( + isinstance(routers, list) and len(routers) > 0, + "No routers found for VPC %s" % vpc.id + ) + if len(routers) == 1: + return routers[0] + for router in routers: + if router.redundantstate == red_state: + return router + + def getNetworkGateway(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6gateway, + None, + "IPv6 gateway for network is empty") + return ipv6_network.ip6gateway + + def getNetworkRoutes(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + self.assertTrue( + isinstance(ipv6_network, list), + "Check listNetworks response returns a valid list" + ) + self.assertEqual( + len(ipv6_network), + 1, + "Network not found" + ) + ipv6_network = ipv6_network[0] + self.assertNotEqual(ipv6_network.ip6routes, + None, + "IPv6 routes for network is empty") + return ipv6_network.ip6routes + + def isNetworkEgressDefaultPolicyAllow(self, network): + ipv6_network = Network.list(self.apiclient,listall="true",id=network.id) + if len(ipv6_network) == 1: + ipv6_network = ipv6_network[0] + return ipv6_network.egressdefaultpolicy + return False + + def checkRouterNicState(self, router, dev, state): + st = "state %s" % state + cmd = "ip link show %s | grep '%s'" % (dev, st) + res = self.getRouterProcessStatus(router, cmd) + self.assertTrue(type(res) == str and len(res) > 0 and st in res, + "%s failed on router %s" % (cmd, router.id)) + + def checkIpv6VpcPrimaryRouter(self, router, network_ip6gateway): + self.checkRouterNicState(router, VPC_ROUTER_GUEST_NIC, "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % (VPC_ROUTER_GUEST_NIC, network_ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and network_ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for IPv6 guest gateway on router %s" % router.id) + self.checkRouterNicState(router, VPC_ROUTER_PUBLIC_NIC, "UP") + public_ipv6 = None + public_ipv6_gateway = None + nics = router.nic + for nic in nics: + if nic.traffictype == 'Public': + public_ipv6 = nic.ip6address + public_ipv6_gateway = nic.ip6gateway + break + self.assertNotEqual(public_ipv6, + None, + "IPv6 address for router Public NIC is empty") + public_ip_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % (VPC_ROUTER_PUBLIC_NIC, public_ipv6) + res = self.getRouterProcessStatus(router, public_ip_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6 in res, + "%s failed on router %s" % (public_ip_check_cmd, router.id)) + self.assertFalse("dadfailed" in res, + "dadfailed for public IPv6 on router %s" % router.id) + self.assertNotEqual(public_ipv6_gateway, + None, + "IPv6 gateway for router Public NIC is empty") + default_route_check_cmd = "ip -6 route | grep 'default via %s'" % (public_ipv6_gateway) + res = self.getRouterProcessStatus(router, default_route_check_cmd) + self.assertTrue(type(res) == str and len(res) > 0 and public_ipv6_gateway in res, + "%s failed on router %s" % (default_route_check_cmd, router.id)) + + def checkIpv6VpcBackupRouter(self, router, network_ip6gateway): + self.checkRouterNicState(router, VPC_ROUTER_GUEST_NIC, "UP") + guest_gateway_check_cmd = "ip -6 address show %s | grep 'inet6 %s'" % ("eth0", network_ip6gateway) + res = self.getRouterProcessStatus(router, guest_gateway_check_cmd) + self.assertFalse(type(res) == str and len(res) > 0 and network_ip6gateway in res, + "%s failed on router %s" % (guest_gateway_check_cmd, router.id)) + self.checkRouterNicState(router, VPC_ROUTER_PUBLIC_NIC, "DOWN") + + def checkIpv6VpcRoutersInternal(self): + network_ip6gateway = self.getNetworkGateway(self.network) + for router in self.routers: + if router.state != "Running": + continue + if router.isredundantrouter == True and router.redundantstate == 'BACKUP': + self.checkIpv6VpcBackupRouter(router, network_ip6gateway) + continue + self.checkIpv6VpcPrimaryRouter(router, network_ip6gateway) + + + def checkIpv6NetworkTierVm(self): + self.debug("Listing NICS for VM %s in network tier: %s" % (self.virtual_machine.name, self.network.name)) + nics = NIC.list( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + networkid=self.network.id + ) + self.assertEqual( + len(nics), + 1, + "VM NIC for the network tier isn't found" + ) + nic = nics[0] + self.assertNotEqual(nic.ip6address, + None, + "IPv6 address for VM %s NIC is empty" % nic.traffictype) + self.virtual_machine_ipv6_address = nic.ip6address + self.assertNotEqual(nic.ip6cidr, + None, + "IPv6 CIDR for VM %s NIC is empty" % nic.traffictype) + self.assertNotEqual(nic.ip6gateway, + None, + "IPv6 gateway for VM %s NIC is empty" % nic.traffictype) + + def restartVpcWithCleanup(self): + self.vpc.restart(self.apiclient, cleanup=True) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + + def updateNetworkTierWithOffering(self): + self.network.update(self.apiclient, networkofferingid=self.network_offering_update.id) + time.sleep(SLEEP_BEFORE_VR_CHANGES) + + def deployRoutingTestResources(self): + self.routing_test_vpc = self.deployAllowAllVpcInternal(ROUTE_TEST_VPC_DATA["cidr"]) + self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True) + self.routing_test_network = self.deployNetworkTierInternal( + self.routing_test_network_offering.id, + self.routing_test_vpc.id, + ROUTE_TEST_VPC_DATA["tier1_gateway"], + ROUTE_TEST_VPC_DATA["tier_netmask"] + ) + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.routing_test_vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + networkids=[self.routing_test_network.id], + serviceofferingid=self.service_offering.id, + mode="advanced", + vpcid=self.routing_test_vpc.id + ) + self.cleanup.append(self.routing_test_vm) + + def prepareRoutingTestResourcesInBackground(self): + self.thread = threading.Thread(target=self.deployRoutingTestResources, args=()) + self.thread.daemon = True + self.thread.start() + + def checkVpcRouting(self): + if not self.thread: + self.deployRoutingTestResources() + else: + self.thread.join(5*60) + self.assertFalse(not self.routing_test_vpc or not self.routing_test_network or not self.routing_test_vm, + "Routing resources failure") + + test_vpc_router = self.getVpcRouter(self.routing_test_vpc) + routes = self.getNetworkRoutes(self.network) + self.logger.debug("Adding vpc routes in routing_test_vpc %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(test_vpc_router, add_route_cmd) + + vpc_router = self.getVpcRouter(self.vpc) + routes = self.getNetworkRoutes(self.routing_test_network) + self.logger.debug("Adding routing_test_vpc routes in vpc %s" % routes) + for route in routes: + add_route_cmd = "ip -6 route add %s via %s" % (route.subnet, route.gateway) + self.getRouterProcessStatus(vpc_router, add_route_cmd) + + ping_cmd = "ping6 -c 4 %s" % self.virtual_machine_ipv6_address + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = self.getRouterProcessStatus(test_vpc_router, ping_cmd) + if " 0% packet loss" in res: + break + time.sleep(PING_SLEEP) + self.assertTrue(" 0% packet loss" in res, + "Ping from router %s of VPC %s to VM %s of VPC %s is unsuccessful" % (test_vpc_router.id, self.routing_test_vpc.id, self.virtual_machine.id, self.vpc.id)) + + ssh = self.routing_test_vm.get_ssh_client(retries=5) + count = 0 + while count < PING_RETRIES: + count = count + 1 + res = ssh.execute(ping_cmd) + if type(res) == list and len(res) > 0 and " 0% packet loss" in '\n'.join(res): + break + time.sleep(PING_SLEEP) + self.assertTrue(type(res) == list and len(res) > 0, + "%s on VM %s returned invalid result" % (ping_cmd, self.routing_test_vm.id)) + self.logger.debug(res) + res = '\n'.join(res) + self.assertTrue(" 0% packet loss" in res, + "Ping from VM %s of VPC %s to VM %s of VPC %s is unsuccessful" % (self.routing_test_vm.id, self.routing_test_vpc.id, self.virtual_machine.id, self.vpc.id)) + + def createNetworkAclRule(self, rule, aclid): + return NetworkACL.create(self.apiclient, + services=rule, + aclid=aclid + ) + + def verifyAclRulesInRouter(self, nic, rules, router): + for rule in rules: + acl_chain = nic + ACL_CHAINS_SUFFIX[rule["traffictype"]] + routerCmd = "nft list chain ip6 %s %s" % (ACL_TABLE, acl_chain) + res = self.getRouterProcessStatus(router, routerCmd) + self.assertTrue(rule["parsedrule"] in res, + "Listing firewall rule with nft list chain failure for rule: %s" % rule["parsedrule"]) + + def checkIpv6AclRule(self): + router = self.getVpcRouter(self.vpc) + + tier1_acl = NetworkACLList.create( + self.apiclient, + services={}, + name="tier1_acl", + description="tier1_acl", + vpcid=self.vpc.id + ) + rules = [] + # Ingress - ip6 saddr SOURCE_CIDR tcp dport { START_PORT-END_PORT } accept + rule = {} + rule["traffictype"] = "Ingress" + rule["cidrlist"] = self.getRandomIpv6Cidr() + rule["protocol"] = "tcp" + rule["startport"] = randint(3000, 5000) + rule["endport"] = rule["startport"] + randint(1, 8) + parsedrule = "ip6 saddr %s %s dport { %d-%d } accept" % (rule["cidrlist"], rule["protocol"], rule["startport"], rule["endport"]) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier1_acl.id) + # Egress - ip6 daddr DEST_CIDR icmpv6 type TYPE code CODE accept + rule = {} + rule["traffictype"] = "Egress" + rule["cidrlist"] = self.getRandomIpv6Cidr() + rule["protocol"] = "icmp" + rule["icmptype"] = choice(list(ICMPV6_TYPE.keys())) + rule["icmpcode"] = choice(list(ICMPV6_CODE_TYPE.keys())) + parsedrule = "ip6 daddr %s %sv6 type %s %sv6 code %s accept" % (rule["cidrlist"], rule["protocol"], ICMPV6_TYPE[rule["icmptype"]], rule["protocol"], ICMPV6_CODE_TYPE[rule["icmpcode"]]) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier1_acl.id) + + self.network.replaceACLList(self.apiclient, tier1_acl.id) + + self.verifyAclRulesInRouter("eth2", rules, router) + + + tier2_acl = NetworkACLList.create( + self.apiclient, + services={}, + name="tier2_acl", + description="tier2_acl", + vpcid=self.vpc.id + ) + rules = [] + # Ingress - ip6 saddr ::/0 udp dport { 0-65355 } ACTION + rule = {} + rule["traffictype"] = "Ingress" + rule["cidrlist"] = CIDR_IPV6_ANY + rule["protocol"] = "udp" + parsedrule = "ip6 saddr %s %s dport %s accept" % (rule["cidrlist"], rule["protocol"], TCP_UDP_PORT_ANY) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier2_acl.id) + # Egress - ip6 daddr DEST_CIDR icmpv6 type TYPE code CODE accept + rule = {} + rule["traffictype"] = "Egress" + rule["protocol"] = "all" + parsedrule = "ip6 daddr %s accept" % (CIDR_IPV6_ANY) + rules.append({"traffictype": rule["traffictype"], "parsedrule": parsedrule}) + self.createNetworkAclRule(rule, tier2_acl.id) + + self.network_offering_tier2 = self.createNetworkTierOfferingInternal(True, False) + self.tier2_network = self.deployNetworkTierInternal( + self.network_offering_tier2.id, + self.vpc.id, + VPC_DATA["tier2_gateway"], + VPC_DATA["tier_netmask"], + tier2_acl.id, + "tier2" + ) + self.tier2_vm = self.deployNetworkTierVmInternal(self.tier2_network.id) + + self.verifyAclRulesInRouter("eth3", rules, router) + + def checkVpcVRRedundancy(self): + network_ip6gateway = self.getNetworkGateway(self.network) + primary_router = self.getVpcRouter(self.vpc) + Router.stop( + self.apiclient, + id=primary_router.id + ) + time.sleep(self.services["sleep"]/2) + new_primary_router = self.getVpcRouter(self.vpc) + self.assertNotEqual(new_primary_router.id, primary_router.id, + "Original primary router ID: %s of VPC is still the primary router after stopping" % (primary_router.id)) + self.checkIpv6VpcPrimaryRouter(new_primary_router, network_ip6gateway) + + def checkIpv6VpcNetworking(self, check_vm=False): + self.checkIpv6Vpc() + self.checkIpv6NetworkTierBasic() + self.checkIpv6VpcRoutersBasic() + self.checkIpv6VpcRoutersInternal() + if check_vm: + self.checkIpv6NetworkTierVm() + + @attr( + tags=[ + "advanced", + "basic", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + @skipTestIf("ipv6NotSupported") + def test_01_verify_ipv6_vpc(self): + """Test to verify IPv6 VPC + + # Validate the following: + # 1. Create IPv6 VPC, add tiers, deploy VM + # 2. Verify VPC, tier has required IPv6 details + # 3. List router for the VPC and verify it has required IPv6 details for Guest and Public NIC of the VR + # 4. SSH into VR(s) and verify correct details are present for its NICs + # 5. Verify VM in network tier has required IPv6 details + # 6. Restart VPC with cleanup and re-verify VPC networking + # 7. Update network tier with a new offering and re-verify VPC networking + # 8. Deploy another IPv6 VPC with tier and check routing between two VPC and their VM + # 9. Create IPv6 ACL rules in two different VPC tiers and verify in VR if they get implemented correctly + """ + + self.createIpv6VpcOffering() + self.deployVpc() + self.createIpv6NetworkTierOffering() + self.createIpv6NetworkTierOfferingForUpdate() + self.createTinyServiceOffering() + self.deployNetworkTier() + self.deployNetworkTierVm() + self.checkIpv6VpcNetworking(True) + self.prepareRoutingTestResourcesInBackground() + self.restartVpcWithCleanup() + self.checkIpv6VpcNetworking() + self.updateNetworkTierWithOffering() + self.checkIpv6VpcNetworking() + self.checkVpcRouting() + self.checkIpv6AclRule() diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 6567d944759..00d212b139f 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -3292,6 +3292,14 @@ class Network: cmd.resume = resume return (apiclient.migrateNetwork(cmd)) + def replaceACLList(self, apiclient, aclid, gatewayid=None): + cmd = replaceNetworkACLList.replaceNetworkACLListCmd() + cmd.networkid = self.id + cmd.aclid = aclid + if gatewayid: + cmd.gatewayid = gatewayid + return (apiclient.replaceNetworkACLList(cmd)) + @classmethod def list(cls, apiclient, **kwargs): """List all Networks matching criteria""" @@ -3329,6 +3337,11 @@ class NetworkACL: elif protocol: cmd.protocol = protocol + if "icmptype" in services: + cmd.icmptype = services["icmptype"] + if "icmpcode" in services: + cmd.icmpcode = services["icmpcode"] + if "startport" in services: cmd.startport = services["startport"] if "endport" in services: diff --git a/tools/marvin/marvin/sshClient.py b/tools/marvin/marvin/sshClient.py index 8b8405cc8c8..f34720fb798 100644 --- a/tools/marvin/marvin/sshClient.py +++ b/tools/marvin/marvin/sshClient.py @@ -61,9 +61,9 @@ class SshClient(object): self.retryCnt = 0 self.delay = 0 self.timeout = 3.0 - ch = logging.StreamHandler() - ch.setLevel(log_lvl) - self.logger.addHandler(ch) + self.ch = logging.StreamHandler() + self.ch.setLevel(log_lvl) + self.logger.addHandler(self.ch) # Check invalid host value and raise exception # Atleast host is required for connection @@ -205,6 +205,8 @@ class SshClient(object): if self.ssh is not None: self.ssh.close() self.ssh = None + if self.ch is not None: + self.logger.removeHandler(self.ch) if __name__ == "__main__": diff --git a/ui/src/views/compute/backup/FormSchedule.vue b/ui/src/views/compute/backup/FormSchedule.vue index eb059efa772..d1512623099 100644 --- a/ui/src/views/compute/backup/FormSchedule.vue +++ b/ui/src/views/compute/backup/FormSchedule.vue @@ -226,9 +226,6 @@ export default { } }, handleChangeIntervalType (e) { - this.form.intervaltype = e.target.value - this.resetForm() - switch (this.form.intervaltype) { case 'weekly': this.fetchDayOfWeek() diff --git a/utils/src/main/java/org/apache/cloudstack/utils/security/ParserUtils.java b/utils/src/main/java/org/apache/cloudstack/utils/security/ParserUtils.java new file mode 100644 index 00000000000..0e8e9d61740 --- /dev/null +++ b/utils/src/main/java/org/apache/cloudstack/utils/security/ParserUtils.java @@ -0,0 +1,41 @@ +// 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 org.apache.cloudstack.utils.security; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +public class ParserUtils { + public static DocumentBuilderFactory getSaferDocumentBuilderFactory() throws ParserConfigurationException { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + // REDHAT https://www.blackhat.com/docs/us-15/materials/us-15-Wang-FileCry-The-New-Age-Of-XXE-java-wp.pdf + // OWASP https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks" + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + return factory; + } +}