You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by GitBox <gi...@apache.org> on 2022/06/14 14:46:04 UTC

[GitHub] [cloudstack] DaanHoogland commented on a diff in pull request #6338: test: add, refactor ipv6 network, vpc tests

DaanHoogland commented on code in PR #6338:
URL: https://github.com/apache/cloudstack/pull/6338#discussion_r896555594


##########
test/integration/component/test_network_ipv6.py:
##########
@@ -0,0 +1,937 @@
+# 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 (cleanup_resources,
+                              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)
+        super(TestIpv6Network, cls).tearDownClass()
+        if cls.test_ipv6_guestprefix != None:
+            cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd()
+            cmd.id = cls.test_ipv6_guestprefix.id
+            cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd)

Review Comment:
   if the parent throws an exception, the next part of the cleanup will not be executed. Is that intentional?
   or should it be like,
   ```suggestion
           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)
   ```



##########
test/integration/component/test_network_ipv6.py:
##########
@@ -0,0 +1,937 @@
+# 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 (cleanup_resources,
+                              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)
+        super(TestIpv6Network, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            cleanup_resources(self.apiclient, reversed(self.cleanup))
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return

Review Comment:
   in this case it might be
   ```suggestion
           try:
               if self.thread and self.thread.is_alive():
                   self.thread.join(5*60)
               #Clean up, terminate the created templates
           except Exception as e:
               raise Exception("Warning: Exception during cleanup : %s" % e)
           finally:
               super(TestIpv6Network, self).tearDown()
           return
   ```
   this structural part of this sugestion *is* a question, but it seems that any resources created during the test would not get cleaned if the `thread.join` leads to an exception.
   the call to super is really very convenient, and if you use vms very much needed.



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )

Review Comment:
   ```suggestion
           vpc_offering = VpcOffering.create(
               self.apiclient,
               off_service
           )
           self.cleanup.append(vpc_offering)
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))

Review Comment:
   ```suggestion
               super(TestIpv6Vpc, self).tearDown()
   ```
   I see no reason why this would be a comment.



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )

Review Comment:
   ```suggestion
           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)
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        if cls.test_ipv6_guestprefix != None:
+            cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd()
+            cmd.id = cls.test_ipv6_guestprefix.id
+            cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd)

Review Comment:
   same as above
   ```suggestion
           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)
   ```
   ?!?!?



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )

Review Comment:
   ```suggestion
           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)
   ```



##########
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

Review Comment:
   ```suggestion
           try:
   
           except Exception as e:
               raise Exception("Warning: Exception during cleanup : %s" % e)
           finally:
               super(TestCreateIpv6NetworkVpcOffering, self).tearDown()
           return
   ```



##########
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<Ipv6GuestPrefixSubnetNetworkMapVO> updatedPrefixSubnetMap;
+
+    List<Ipv6GuestPrefixSubnetNetworkMapVO> 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<Boolean>) invocation -> {
+            Ipv6GuestPrefixSubnetNetworkMapVO map = (Ipv6GuestPrefixSubnetNetworkMapVO)invocation.getArguments()[1];
+            updatedPrefixSubnetMap.add(map);
+            return true;
+        });
+        Mockito.when(ipv6GuestPrefixSubnetNetworkMapDao.persist(Mockito.any(Ipv6GuestPrefixSubnetNetworkMapVO.class))).thenAnswer((Answer<Ipv6GuestPrefixSubnetNetworkMapVO>) 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<Ipv6GuestPrefixSubnetNetworkMapVO> 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<Integer, Integer> 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<DataCenterGuestIpv6PrefixVO> prefixes = new ArrayList<>();
+        Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes);
+        Pair<Integer, Integer> 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<DataCenterGuestIpv6PrefixVO> prefixes = new ArrayList<>();
+        DataCenterGuestIpv6PrefixVO prefix = prepareMocksForIpv6Subnet();
+        prefixes.add(prefix);
+        prefixes.add(prefix);
+        Mockito.when(dataCenterGuestIpv6PrefixDao.listByDataCenterId(zoneId)).thenReturn(prefixes);
+        Pair<Integer, Integer> results = ipv6Service.getUsedTotalIpv6SubnetForZone(zoneId);
+        Assert.assertEquals(4, results.first().intValue());
+        Assert.assertEquals(8, results.second().intValue());
+    }
+
+    @Test(expected = ResourceAllocationException.class)
+    @DB

Review Comment:
   Unit tests using DB are a bit worrying. Do these tests reallly need this annotation?
   and if so; Are these tests really needed/not covered by the integration tests?



##########
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)

Review Comment:
   ```suggestion
               self.cleanup.append(ipv6_publiciprange)
               self.debug("Created IPv6 public range with ID: %s. Deleting it before failure" % ipv6_publiciprange.id)
   ```
   a bit pompeus, but on io errors we still want to clean up as much as possible.



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return

Review Comment:
   ```suggestion
       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
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')

Review Comment:
   ```suggestion
           network_offering = NetworkOffering.create(
               self.apiclient,
               off_service,
               conservemode=False
           )
           self.cleanup.append(network_offering)
           network_offering.update(self.apiclient, state='Enabled')
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)

Review Comment:
   ```suggestion
           try:
               if self.thread and self.thread.is_alive():
                   self.thread.join(5*60)
               #Clean up, terminate the created templates
               # cleanup_resources(self.apiclient, reversed(self.cleanup))
   
           except Exception as e:
               raise Exception("Warning: Exception during cleanup : %s" % e)
           finally:
               super(TestIpv6Vpc, self).tearDown()
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )

Review Comment:
   ```suggestion
           vpc_offering = VpcOffering.create(
               self.apiclient,
               off_service
           )
           self.cleanup.append(vpc_offering)
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)

Review Comment:
   this appending to the cleanup list should really be inside the `createVpcOfferingInternal()`, directly after the create.
   in this way an exception in the `update()` would leave garbage.
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)

Review Comment:
   same here; this appending to the cleanup list should be inside the createVpcOfferingInternal()
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )

Review Comment:
   ```suggestion
           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)
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )

Review Comment:
   ```suggestion
           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)
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)

Review Comment:
   ```suggestion
   ```



##########
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()

Review Comment:
   ```suggestion
           try:
               if cls.initial_ipv6_offering_enabled != None:
                   Configurations.update(cls.apiclient,
                       ipv6_offering_config_name,
                       cls.initial_ipv6_offering_enabled)
           finally:
               super(TestCreateIpv6NetworkVpcOffering, cls).tearDownClass()
   ```
   , unless of course we are sure no exceptions can be thrown in the `Configurations.update(..)`, but I think it involves network, so pretty sure it can.



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)

Review Comment:
   same here; this appending to the cleanup list should be inside the `createVpcOfferingInternal()`
   ```suggestion
   ```



##########
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

Review Comment:
   ```suggestion
       def tearDown(self):
           super(TestIpv6PublicIpRange, self).tearDown()
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.cleanup.append(self.routing_test_network)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.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",
+            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.cleanup.append(self.network_offering_tier2)
+        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.cleanup.append(self.tier2_network)

Review Comment:
   ```suggestion
   ```



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.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",
+            vpcid=self.routing_test_vpc.id
+        )
+        self.cleanup.append(self.routing_test_vm)

Review Comment:
   why isn´t this one a call to one of the `**Internal()` methods?



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.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",
+            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.cleanup.append(self.network_offering_tier2)

Review Comment:
   ```suggestion
   ```



##########
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

Review Comment:
   ```suggestion
       def tearDown(self):
           super(TestIpv6GuestPrefix, self).tearDown()
           return
   ```



##########
test/integration/smoke/test_network_ipv6.py:
##########
@@ -665,14 +209,16 @@ 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)

Review Comment:
   with this extra code we run the test of the cleanup not being reached. Please see the construct I sugested in other tests to apply it here as well.



##########
test/integration/component/test_vpc_ipv6.py:
##########
@@ -0,0 +1,1036 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.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",
+            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.cleanup.append(self.network_offering_tier2)
+        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.cleanup.append(self.tier2_network)
+        self.tier2_vm = self.deployNetworkTierVmInternal(self.tier2_network.id)
+        self.cleanup.append(self.tier2_vm)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )

Review Comment:
   ```suggestion
           network_offering = NetworkOffering.create(
               self.apiclient,
               off_service,
               conservemode=False
           )
           self.cleanup.append(network_offering)
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )

Review Comment:
   ```suggestion
           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)
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        if cls.test_ipv6_guestprefix != None:
+            cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd()
+            cmd.id = cls.test_ipv6_guestprefix.id
+            cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd)

Review Comment:
   ```suggestion
       def tearDownClass(cls):
           try:
               if cls.initial_ipv6_offering_enabled != None:
                   Configurations.update(cls.apiclient,
                        ipv6_offering_config_name,
                        cls.initial_ipv6_offering_enabled)
           finally:
               super(TestIpv6Vpc, cls).tearDownClass()
           if cls.test_ipv6_guestprefix != None:
               cmd = deleteGuestNetworkIpv6Prefix.deleteGuestNetworkIpv6PrefixCmd()
               cmd.id = cls.test_ipv6_guestprefix.id
               cls.apiclient.deleteGuestNetworkIpv6Prefix(cmd)
   ```
   
   should GuestNetworkIpv6Prefix be added as class in base.py?



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )

Review Comment:
   ```suggestion
           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)
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.cleanup.append(self.routing_test_network)

Review Comment:
   ```suggestion
           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"]
           )
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)

Review Comment:
   ```suggestion
   ```



##########
test/integration/smoke/test_vpc_ipv6.py:
##########
@@ -0,0 +1,911 @@
+# 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,
+                              cleanup_resources,
+                              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)
+        super(TestIpv6Vpc, cls).tearDownClass()
+        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)
+            #Clean up, terminate the created templates
+            # cleanup_resources(self.apiclient, reversed(self.cleanup))
+
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        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
+        )
+        vpc_offering.update(self.apiclient, state='Enabled')
+        return vpc_offering
+
+    def createIpv4VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, False)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOffering(self, is_redundant=False):
+        self.vpc_offering = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering)
+
+    def createIpv6VpcOfferingForUpdate(self, is_redundant=False):
+        self.vpc_offering_update = self.createVpcOfferingInternal(is_redundant, True)
+        self.cleanup.append(self.vpc_offering_update)
+
+    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
+        )
+        network_offering.update(self.apiclient, state='Enabled')
+        return network_offering
+
+    def createIpv4NetworkTierOffering(self):
+        self.network_offering = self.createNetworkTierOfferingInternal(False)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOffering(self, remove_lb=True):
+        self.network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering)
+
+    def createIpv6NetworkTierOfferingForUpdate(self):
+        self.network_offering_update = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.network_offering_update)
+
+    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
+        )
+        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"])
+        self.cleanup.append(self.vpc)
+
+    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
+        )
+        return network
+
+    def deployNetworkTier(self):
+        self.network = self.deployNetworkTierInternal(
+            self.network_offering.id,
+            self.vpc.id,
+            VPC_DATA["tier1_gateway"],
+            VPC_DATA["tier_netmask"]
+        )
+        self.cleanup.append(self.network)
+
+    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
+        )
+        return virtual_machine
+
+    def deployNetworkTierVm(self):
+        self.virtual_machine = self.deployNetworkTierVmInternal(self.network.id)
+        self.cleanup.append(self.virtual_machine)
+
+    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.cleanup.append(self.routing_test_vpc)
+        self.routing_test_network_offering = self.createNetworkTierOfferingInternal(True)
+        self.cleanup.append(self.routing_test_network_offering)
+        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.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",
+            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.cleanup.append(self.network_offering_tier2)
+        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.cleanup.append(self.tier2_network)
+        self.tier2_vm = self.deployNetworkTierVmInternal(self.tier2_network.id)
+        self.cleanup.append(self.tier2_vm)

Review Comment:
   ```suggestion
           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)
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org