Merge "Refactoring - Use existing Tempest APIs in "QoS bandwidth limit rule" tests"
diff --git a/neutron_tempest_plugin/scenario/test_ipv6.py b/neutron_tempest_plugin/scenario/test_ipv6.py
index 32fb581..d9d1a22 100644
--- a/neutron_tempest_plugin/scenario/test_ipv6.py
+++ b/neutron_tempest_plugin/scenario/test_ipv6.py
@@ -20,6 +20,7 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
+import testtools
 
 from neutron_tempest_plugin.common import ip
 from neutron_tempest_plugin.common import ssh
@@ -87,6 +88,12 @@
     ipv6_address_mode = 'slaac'
 
     @classmethod
+    def skip_checks(cls):
+        super(IPv6Test, cls).skip_checks()
+        if not CONF.network_feature_enabled.ipv6:
+            raise cls.skipException("IPv6 is not enabled")
+
+    @classmethod
     @tempest_utils.requires_ext(extension="router", service="network")
     def resource_setup(cls):
         super(IPv6Test, cls).resource_setup()
@@ -180,10 +187,14 @@
             ssh_client=ssh_client, mac_address=ipv6_port['mac_address'])
         self._test_ipv6_address_configured(ssh_client, vm, ipv6_port)
 
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "DHCPv6 attributes are not enabled.")
     @decorators.idempotent_id('b13e5408-5250-4a42-8e46-6996ce613e91')
     def test_ipv6_hotplug_slaac(self):
         self._test_ipv6_hotplug("slaac", "slaac")
 
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "DHCPv6 attributes are not enabled.")
     @decorators.idempotent_id('9aaedbc4-986d-42d5-9177-3e721728e7e0')
     def test_ipv6_hotplug_dhcpv6stateless(self):
         self._test_ipv6_hotplug("dhcpv6-stateless", "dhcpv6-stateless")
diff --git a/neutron_tempest_plugin/tap_as_a_service/__init__.py b/neutron_tempest_plugin/tap_as_a_service/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/__init__.py
diff --git a/neutron_tempest_plugin/tap_as_a_service/api/__init__.py b/neutron_tempest_plugin/tap_as_a_service/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/api/__init__.py
diff --git a/neutron_tempest_plugin/tap_as_a_service/api/test_taas.py b/neutron_tempest_plugin/tap_as_a_service/api/test_taas.py
new file mode 100644
index 0000000..06dce53
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/api/test_taas.py
@@ -0,0 +1,129 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.tap_as_a_service import base
+
+CONF = config.CONF
+
+
+class TaaSExtensionTestJSON(base.BaseTaasTest):
+
+    @classmethod
+    @utils.requires_ext(extension='taas', service='network')
+    def skip_checks(cls):
+        super(TaaSExtensionTestJSON, cls).skip_checks()
+
+    @classmethod
+    def resource_setup(cls):
+        super(TaaSExtensionTestJSON, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.ts_port = cls.create_port(cls.network)
+        cls.tf_port = cls.create_port(cls.network)
+        cls.tf2_port = cls.create_port(cls.network)
+
+    @decorators.idempotent_id('b993c14e-797a-4c91-b4da-8cb1a450aa2f')
+    def test_create_tap_service_and_flow(self):
+        """create tap service adn tap flow
+
+        Test create tap service and flow.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        self.create_tap_flow(tap_service_id=tap_service['id'],
+                             direction='BOTH', source_port=self.tf_port['id'])
+
+    @decorators.idempotent_id('897a0aaf-1b55-4ea8-9d9f-1bc0fd09cb60')
+    @utils.requires_ext(extension='taas-vlan-filter', service='network')
+    def test_create_tap_service_and_flow_vlan_filter(self):
+        """create tap service with vlan_filter
+
+        Test create tap service with additional vlan_filter argument.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(tap_service_id=tap_service['id'],
+                                        direction='BOTH',
+                                        source_port=self.tf_port['id'],
+                                        vlan_filter='189,279,999-1008')
+        self.assertEqual(tap_flow['vlan_filter'], '189,279,999-1008')
+
+    @decorators.idempotent_id('d7a2115d-16b4-41cf-95a6-dcebc3682b24')
+    def test_delete_tap_resources_after_ts_port_delete(self):
+        """delete tap resources after ts port delete
+
+        Test delete tap resources after deletion of ts port.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(tap_service_id=tap_service['id'],
+                                        direction='BOTH',
+                                        source_port=self.tf2_port['id'])
+        # delete ts_port; it shall also delete the associated tap-service and
+        # subsequently the tap-flow as well
+        self.ports_client.delete_port(self.ts_port['id'])
+        # Attempt tap-service deletion; it should throw not found exception.
+        self.assertRaises(lib_exc.NotFound,
+                          self.tap_services_client.delete_tap_service,
+                          tap_service['id'])
+        # Attempt tap-flow deletion; it should throw not found exception.
+        self.assertRaises(lib_exc.NotFound,
+                          self.tap_flows_client.delete_tap_flow,
+                          tap_flow['id'])
+
+    @decorators.idempotent_id('9ba4edfd-4002-4c44-b02b-6c4f71b40a92')
+    def test_delete_tap_resources_after_tf_port_delete(self):
+        """delete tap resources after tf port delete
+
+        Test delete tap service after deletion of tf port.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(tap_service_id=tap_service['id'],
+                                        direction='BOTH',
+                                        source_port=self.tf_port['id'])
+        # delete tf port; it shall also delete the associated tap-flow
+        self.ports_client.delete_port(self.tf_port['id'])
+        # Attempt tap-flow deletion; it should throw not found exception.
+        self.assertRaises(lib_exc.NotFound,
+                          self.tap_flows_client.delete_tap_flow,
+                          tap_flow['id'])
+        # delete tap service; it shall go fine
+        self.tap_services_client.delete_tap_service(tap_service['id'])
+
+    @decorators.idempotent_id('687089b8-b045-496d-86bf-030b380039d1')
+    def test_create_and_update_tap_service(self):
+        """create and update tap service
+
+        Test update tap service - update description.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+
+        # Update description of the tap service
+        self.update_tap_service(
+            tap_service['id'],
+            description='Tap Service Description Updated')
+
+    @decorators.idempotent_id('bb4d5482-37fc-46b5-85a5-5867e9adbfae')
+    def test_create_and_update_tap_flow(self):
+        """create and update tap flow
+
+        Test update tap flow - update description.
+        """
+        tap_service = self.create_tap_service(port_id=self.ts_port['id'])
+        tap_flow = self.create_tap_flow(
+            tap_service_id=tap_service['id'],
+            direction='BOTH', source_port=self.tf_port['id'])
+        # Update description of the tap flow
+        self.update_tap_flow(
+            tap_flow['id'],
+            description='Tap Flow Description Updated')
diff --git a/neutron_tempest_plugin/tap_as_a_service/base.py b/neutron_tempest_plugin/tap_as_a_service/base.py
new file mode 100644
index 0000000..3ddc797
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/base.py
@@ -0,0 +1,82 @@
+#    Licensed 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.
+
+import tempest.api.network.base as test
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+
+from neutron_tempest_plugin.tap_as_a_service.services import taas_client
+
+CONF = config.CONF
+
+
+class BaseTaasTest(test.BaseAdminNetworkTest):
+
+    @classmethod
+    def resource_setup(cls):
+        super(BaseTaasTest, cls).resource_setup()
+        os_primary = cls.os_primary
+        cls.tap_services_client = taas_client.TapServicesClient(
+            os_primary.auth_provider,
+            CONF.network.catalog_type,
+            CONF.network.region or CONF.identity.region,
+            endpoint_type=CONF.network.endpoint_type,
+            build_interval=CONF.network.build_interval,
+            build_timeout=CONF.network.build_timeout,
+            **os_primary.default_params)
+        cls.tap_flows_client = taas_client.TapFlowsClient(
+            os_primary.auth_provider,
+            CONF.network.catalog_type,
+            CONF.network.region or CONF.identity.region,
+            endpoint_type=CONF.network.endpoint_type,
+            build_interval=CONF.network.build_interval,
+            build_timeout=CONF.network.build_timeout,
+            **os_primary.default_params)
+
+    def create_tap_service(self, **kwargs):
+        body = self.tap_services_client.create_tap_service(
+            name=data_utils.rand_name("tap_service"),
+            **kwargs)
+        tap_service = body['tap_service']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_services_client.delete_tap_service,
+                        tap_service['id'])
+        return tap_service
+
+    def create_tap_flow(self, **kwargs):
+        body = self.tap_flows_client.create_tap_flow(
+            name=data_utils.rand_name("tap_service"),
+            **kwargs)
+        tap_flow = body['tap_flow']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_flows_client.delete_tap_flow,
+                        tap_flow['id'])
+        return tap_flow
+
+    def update_tap_service(self, tap_service_id, **kwargs):
+        body = self.tap_services_client.update_tap_service(
+            tap_service_id,
+            **kwargs)
+        tap_service = body['tap_service']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_services_client.delete_tap_service,
+                        tap_service['id'])
+
+    def update_tap_flow(self, tap_flow_id, **kwargs):
+        body = self.tap_flows_client.update_tap_flow(
+            tap_flow_id,
+            **kwargs)
+        tap_flow = body['tap_flow']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.tap_flows_client.delete_tap_flow,
+                        tap_flow['id'])
diff --git a/neutron_tempest_plugin/tap_as_a_service/services/__init__.py b/neutron_tempest_plugin/tap_as_a_service/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/services/__init__.py
diff --git a/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py b/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py
new file mode 100644
index 0000000..7230cbb
--- /dev/null
+++ b/neutron_tempest_plugin/tap_as_a_service/services/taas_client.py
@@ -0,0 +1,63 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.services.network import base
+
+
+class TapServicesClient(base.BaseNetworkClient):
+
+    def create_tap_service(self, **kwargs):
+        uri = '/taas/tap_services'
+        post_data = {'tap_service': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_tap_service(self, tap_service_id, **kwargs):
+        uri = '/taas/tap_services/%s' % tap_service_id
+        post_data = {'tap_service': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_tap_service(self, tap_service_id, **fields):
+        uri = '/taas/tap_services/%s' % tap_service_id
+        return self.show_resource(uri, **fields)
+
+    def delete_tap_service(self, tap_service_id):
+        uri = '/taas/tap_services/%s' % tap_service_id
+        return self.delete_resource(uri)
+
+    def list_tap_services(self, **filters):
+        uri = '/taas/tap_services'
+        return self.list_resources(uri, **filters)
+
+
+class TapFlowsClient(base.BaseNetworkClient):
+
+    def create_tap_flow(self, **kwargs):
+        uri = '/taas/tap_flows'
+        post_data = {'tap_flow': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_tap_flow(self, tap_flow_id, **kwargs):
+        uri = '/taas/tap_flows/%s' % tap_flow_id
+        post_data = {'tap_flow': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_tap_flow(self, tap_flow_id, **fields):
+        uri = '/taas/tap_flows/%s' % tap_flow_id
+        return self.show_resource(uri, **fields)
+
+    def delete_tap_flow(self, tap_flow_id):
+        uri = '/taas/tap_flows/%s' % tap_flow_id
+        return self.delete_resource(uri)
+
+    def list_tap_flows(self, **filters):
+        uri = '/taas/tap_flows'
+        return self.list_resources(uri, **filters)
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index f9fdd05..97be1ce 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -412,7 +412,7 @@
         # TODO(eolivare): Remove OVN_BUILD_FROM_SOURCE once vlan-transparency
         # is included in an ovn released version
         OVN_BUILD_FROM_SOURCE: True
-        OVN_BRANCH: "v20.12.0"
+        OVN_BRANCH: "v21.06.0"
         OVS_BRANCH: "branch-2.15"
         OVS_SYSCONFDIR: "/usr/local/etc/openvswitch"
       devstack_services:
@@ -896,3 +896,93 @@
       devstack_localrc:
         IPSEC_PACKAGE: strongswan
         NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_vpnaas) | join(',') }}"
+
+- job:
+    name: neutron-tempest-plugin-tap-as-a-service
+    parent: neutron-tempest-plugin-base
+    description: |
+      Perform setup common to all tap-as-a-service tempest tests
+    roles:
+      - zuul: openstack/devstack
+    required-projects:
+      - openstack/devstack-gate
+      - openstack/neutron
+      - openstack/neutron-tempest-plugin
+      - openstack/tap-as-a-service
+      - openstack/tempest
+    vars:
+      tempest_test_regex: ^neutron_tempest_plugin\.tap_as_a_service
+      tox_envlist: all
+      network_api_extensions_common: *api_extensions
+      network_api_extensions_tempest:
+        - taas
+        - taas-vlan-filter
+      devstack_localrc:
+        NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_tempest) | join(',') }}"
+        DOWNLOAD_DEFAULT_IMAGES: false
+        IMAGE_URLS: "http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-i386-disk.img,https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-amd64.img"
+        DEFAULT_IMAGE_NAME: cirros-0.3.4-i386-disk
+        ADVANCED_IMAGE_NAME: ubuntu-18.04-server-cloudimg-amd64
+        BUILD_TIMEOUT: 784
+        Q_AGENT: openvswitch
+        Q_ML2_TENANT_NETWORK_TYPE: vxlan
+        Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch
+      devstack_local_conf:
+        post-config:
+          /$NEUTRON_CORE_PLUGIN_CONF:
+            AGENT:
+              tunnel_types: vxlan,gre
+        test-config:
+          $TEMPEST_CONFIG:
+            taas_plugin_options:
+              advanced_image_ref: ubuntu-18.04-server-cloudimg-amd64
+              advanced_image_ssh_user: ubuntu
+              provider_physical_network: public
+              provider_segmentation_id: 100
+            image_feature_enabled:
+              api_v2: true
+      devstack_plugins:
+        neutron: git://opendev.org/openstack/neutron.git
+        neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
+        tap-as-a-service: git://opendev.org/openstack/tap-as-a-service.git
+      devstack_services:
+        # Disable OVN services
+        ovn-controller: false
+        ovn-northd: false
+        ovs-vswitchd: false
+        ovsdb-server: false
+        q-ovn-metadata-agent: false
+        # Enable Neutron services that are not used by OVN
+        q-agt: true
+        q-dhcp: true
+        q-l3: true
+        q-meta: true
+        q-metering: true
+        br-ex-tcpdump: true
+        br-int-flows: true
+        base: false
+        key: true
+        mysql: true
+        rabbit: true
+        g-api: true
+        g-reg: true
+        n-api: true
+        n-cond: true
+        n-cpu: true
+        n-crt: true
+        n-sch: true
+        placement-api: true
+        n-api-meta: true
+        q-svc: true
+        quantum: true
+        taas: true
+        taas_openvswitch_agent: true
+        tempest: true
+        dstat: true
+    irrelevant-files: &tempest-irrelevant-files
+      - ^(test-|)requirements.txt$
+      - ^releasenotes/.*$
+      - ^doc/.*$
+      - ^.*\.rst$
+      - ^tools/.*$
+      - ^tox.ini$
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 08fb58c..031860f 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -186,6 +186,7 @@
         - neutron-tempest-plugin-vpnaas-ussuri
         - neutron-tempest-plugin-vpnaas-victoria
         - neutron-tempest-plugin-vpnaas-wallaby
+        - neutron-tempest-plugin-tap-as-a-service
 
     gate:
       jobs:
diff --git a/zuul.d/queens_jobs.yaml b/zuul.d/queens_jobs.yaml
index b3e521b..0b56b32 100644
--- a/zuul.d/queens_jobs.yaml
+++ b/zuul.d/queens_jobs.yaml
@@ -245,3 +245,4 @@
         USE_PYTHON3: false
         CIRROS_VERSION: 0.3.5
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
+        ADVANCED_INSTANCE_TYPE: ds512M
diff --git a/zuul.d/rocky_jobs.yaml b/zuul.d/rocky_jobs.yaml
index 211e896..5cb6125 100644
--- a/zuul.d/rocky_jobs.yaml
+++ b/zuul.d/rocky_jobs.yaml
@@ -615,6 +615,7 @@
       devstack_localrc:
         USE_PYTHON3: false
         TEMPEST_PLUGINS: '"/opt/stack/designate-tempest-plugin /opt/stack/neutron-tempest-plugin"'
+        ADVANCED_INSTANCE_TYPE: ds512M
     branches:
       - stable/rocky
 
diff --git a/zuul.d/stein_jobs.yaml b/zuul.d/stein_jobs.yaml
index 037bba2..f84df2a 100644
--- a/zuul.d/stein_jobs.yaml
+++ b/zuul.d/stein_jobs.yaml
@@ -338,3 +338,5 @@
     vars:
       branch_override: stable/stein
       network_api_extensions_common: *api_extensions
+      devstack_localrc:
+        ADVANCED_INSTANCE_TYPE: ds512M
diff --git a/zuul.d/train_jobs.yaml b/zuul.d/train_jobs.yaml
index a7ac8bf..5caf127 100644
--- a/zuul.d/train_jobs.yaml
+++ b/zuul.d/train_jobs.yaml
@@ -220,6 +220,8 @@
     vars:
       branch_override: stable/train
       network_api_extensions_common: *api_extensions
+      devstack_localrc:
+        ADVANCED_INSTANCE_TYPE: ds512M
 
 - job:
     name: neutron-tempest-plugin-sfc-train