Merge "Add connectivity test via 2 routers and tenant networks"
diff --git a/.zuul.yaml b/.zuul.yaml
index 8c6072a..bd8619f 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -16,7 +16,7 @@
       tox_envlist: all
       devstack_localrc:
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
-        NETWORK_API_EXTENSIONS: address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-domain-ports,dns-integration,dvr,empty-string-filtering,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,filter-validation,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-mac-address-regenerate,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-segment,standard-attr-timestamp,standard-attr-tag,subnet_allocation,trunk,trunk-details
+        NETWORK_API_EXTENSIONS: address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-domain-ports,dns-integration,dvr,empty-string-filtering,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,filter-validation,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-bw-minimum-ingress,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-mac-address-regenerate,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-segment,standard-attr-timestamp,standard-attr-tag,subnet_allocation,trunk,trunk-details
       devstack_plugins:
         neutron: git://git.openstack.org/openstack/neutron.git
         neutron-tempest-plugin: git://git.openstack.org/openstack/neutron-tempest-plugin.git
@@ -145,7 +145,7 @@
     vars:
       devstack_localrc:
         Q_AGENT: linuxbridge
-        NETWORK_API_EXTENSIONS: address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-domain-ports,dns-integration,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,filter-validation,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-timestamp,standard-attr-tag,subnet_allocation,tag,tag-ext,trunk,trunk-details
+        NETWORK_API_EXTENSIONS: address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-domain-ports,dns-integration,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,filter-validation,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-bw-minimum-ingress,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-timestamp,standard-attr-tag,subnet_allocation,tag,tag-ext,trunk,trunk-details
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -202,7 +202,7 @@
       tempest_test_regex: ^neutron_tempest_plugin\.scenario
       devstack_localrc:
         TEMPEST_PLUGINS: /opt/stack/neutron-tempest-plugin
-        NETWORK_API_EXTENSIONS: "address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-integration,dvr,empty-string-filtering,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-segment,standard-attr-timestamp,standard-attr-tag,subnet_allocation,trunk,trunk-details"
+        NETWORK_API_EXTENSIONS: "address-scope,agent,allowed-address-pairs,auto-allocated-topology,availability_zone,binding,default-subnetpools,dhcp_agent_scheduler,dns-integration,dvr,empty-string-filtering,ext-gw-mode,external-net,extra_dhcp_opt,extraroute,fip-port-details,flavors,ip-substring-filtering,l3-flavors,l3-ha,l3_agent_scheduler,logging,metering,multi-provider,net-mtu,net-mtu-writable,network-ip-availability,network_availability_zone,pagination,port-security,project-id,provider,qos,qos-bw-minimum-ingress,qos-fip,quotas,quota_details,rbac-policies,router,router_availability_zone,security-group,port-security-groups-filtering,segment,service-type,sorting,standard-attr-description,standard-attr-revisions,standard-attr-segment,standard-attr-timestamp,standard-attr-tag,subnet_allocation,trunk,trunk-details"
         PHYSICAL_NETWORK: default
         DOWNLOAD_DEFAULT_IMAGES: false
         IMAGE_URLS: "http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-i386-disk.img,http://cloud-images.ubuntu.com/releases/16.04/release-20180622/ubuntu-16.04-server-cloudimg-amd64-disk1.img"
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index e7e1e86..1b8239b 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -431,6 +431,8 @@
                 ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
             if gateway is not None:
                 kwargs['gateway_ip'] = str(gateway or (subnet_cidr.ip + 1))
+            else:
+                kwargs['gateway_ip'] = None
             try:
                 body = client.create_subnet(
                     network_id=network['id'],
@@ -611,7 +613,11 @@
                                cls.external_network_id)
 
         if port:
-            kwargs['port_id'] = port['id']
+            port_id = kwargs.setdefault('port_id', port['id'])
+            if port_id != port['id']:
+                message = "Port ID specified twice: {!s} != {!s}".format(
+                    port_id, port['id'])
+                raise ValueError(message)
 
         fip = client.create_floatingip(external_network_id,
                                        **kwargs)['floatingip']
diff --git a/neutron_tempest_plugin/api/test_availability_zones.py b/neutron_tempest_plugin/api/test_availability_zones.py
new file mode 100644
index 0000000..9d75c28
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_availability_zones.py
@@ -0,0 +1,30 @@
+# Copyright 2018 AT&T Corporation.
+# All Rights Reserved.
+#
+#    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.lib import decorators
+
+from neutron_tempest_plugin.api import base
+
+
+class ListAvailableZonesTest(base.BaseNetworkTest):
+
+    @decorators.idempotent_id('5a8a8a1a-c265-11e8-a611-080027758b73')
+    @utils.requires_ext(extension="availability_zone",
+                        service="network")
+    def test_list_available_zones(self):
+        body = self.client.list_availability_zones()
+        self.assertIsNotNone(body)
+        self.assertIsInstance(body['availability_zones'], list)
diff --git a/neutron_tempest_plugin/api/test_network_ip_availability.py b/neutron_tempest_plugin/api/test_network_ip_availability.py
index 1cdfc7e..e798680 100644
--- a/neutron_tempest_plugin/api/test_network_ip_availability.py
+++ b/neutron_tempest_plugin/api/test_network_ip_availability.py
@@ -19,7 +19,7 @@
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
+from tempest.lib import exceptions
 
 from neutron_tempest_plugin.api import base
 
@@ -53,27 +53,26 @@
     def skip_checks(cls):
         super(NetworksIpAvailabilityTest, cls).skip_checks()
 
-    def _get_used_ips(self, network, net_availability):
-        if network:
+    @staticmethod
+    def _get_availability(network, net_availability):
+        if 'network_ip_availabilities' in net_availability:
             for availability in net_availability['network_ip_availabilities']:
                 if availability['network_id'] == network['id']:
-                    return availability['used_ips']
+                    return availability
+            raise exceptions.TempestException('Network IP Availability not '
+                                              'found')
+        else:
+            return net_availability['network_ip_availability']
 
-    def _cleanUp_port(self, port_id):
-        # delete port, any way to avoid race
-        try:
-            self.client.delete_port(port_id)
-        # if port is not found, this means it was deleted in the test
-        except lib_exc.NotFound:
-            pass
+    def _get_used_ips(self, network, net_availability):
+        availability = self._get_availability(network, net_availability)
+        return availability and availability['used_ips']
 
     def _assert_total_and_used_ips(self, expected_used, expected_total,
                                    network, net_availability):
-        if network:
-            for availability in net_availability['network_ip_availabilities']:
-                if availability['network_id'] == network['id']:
-                    self.assertEqual(expected_total, availability['total_ips'])
-                    self.assertEqual(expected_used, availability['used_ips'])
+        availability = self._get_availability(network, net_availability)
+        self.assertEqual(expected_total, availability['total_ips'])
+        self.assertEqual(expected_used, availability['used_ips'])
 
 
 def calc_total_ips(prefix, ip_version):
@@ -89,56 +88,87 @@
 
 class NetworksIpAvailabilityIPv4Test(NetworksIpAvailabilityTest):
 
-    @decorators.idempotent_id('0f33cc8c-1bf6-47d1-9ce1-010618240599')
-    def test_admin_network_availability_before_subnet(self):
+    def setUp(self):
+        super(NetworksIpAvailabilityIPv4Test, self).setUp()
         net_name = data_utils.rand_name('network')
-        network = self.create_network(network_name=net_name)
-        self.addCleanup(self.client.delete_network, network['id'])
+        self.network = self.create_network(network_name=net_name)
+
+    @decorators.idempotent_id('0f33cc8c-1bf6-47d1-9ce1-010618240599')
+    def test_list_ip_availability_before_subnet(self):
         net_availability = self.admin_client.list_network_ip_availabilities()
-        self._assert_total_and_used_ips(0, 0, network, net_availability)
+        self._assert_total_and_used_ips(0, 0, self.network, net_availability)
 
     @decorators.idempotent_id('3aecd3b2-16ed-4b87-a54a-91d7b3c2986b')
-    def test_net_ip_availability_after_subnet_and_ports(self):
-        net_name = data_utils.rand_name('network')
-        network = self.create_network(network_name=net_name)
-        self.addCleanup(self.client.delete_network, network['id'])
-        subnet = self.create_subnet(network, enable_dhcp=False)
+    def test_list_ip_availability_after_subnet_and_ports(self):
+        subnet = self.create_subnet(self.network, enable_dhcp=False)
         prefix = netaddr.IPNetwork(subnet['cidr']).prefixlen
-        self.addCleanup(self.client.delete_subnet, subnet['id'])
         body = self.admin_client.list_network_ip_availabilities()
-        used_ip = self._get_used_ips(network, body)
-        port1 = self.client.create_port(network_id=network['id'])
-        self.addCleanup(self.client.delete_port, port1['port']['id'])
-        port2 = self.client.create_port(network_id=network['id'])
-        self.addCleanup(self.client.delete_port, port2['port']['id'])
+        used_ips_before_port_create = self._get_used_ips(self.network, body)
+        self.create_port(self.network)
         net_availability = self.admin_client.list_network_ip_availabilities()
         self._assert_total_and_used_ips(
-            used_ip + 2,
+            used_ips_before_port_create + 1,
             calc_total_ips(prefix, self._ip_version),
-            network, net_availability)
+            self.network, net_availability)
 
     @decorators.idempotent_id('9f11254d-757b-492e-b14b-f52144e4ee7b')
-    def test_net_ip_availability_after_port_delete(self):
-        net_name = data_utils.rand_name('network')
-        network = self.create_network(network_name=net_name)
-        self.addCleanup(self.client.delete_network, network['id'])
-        subnet = self.create_subnet(network, enable_dhcp=False)
-        self.addCleanup(self.client.delete_subnet, subnet['id'])
-        port = self.client.create_port(network_id=network['id'])
-        self.addCleanup(self._cleanUp_port, port['port']['id'])
+    def test_list_ip_availability_after_port_delete(self):
+        self.create_subnet(self.network, enable_dhcp=False)
+        port = self.create_port(self.network)
         net_availability = self.admin_client.list_network_ip_availabilities()
-        used_ip = self._get_used_ips(network, net_availability)
-        self.client.delete_port(port['port']['id'])
+        used_ips = self._get_used_ips(self.network, net_availability)
+        self.client.delete_port(port['id'])
 
-        def get_net_availability():
+        def is_count_ip_availability_valid():
             availabilities = self.admin_client.list_network_ip_availabilities()
-            used_ip_after_port_delete = self._get_used_ips(network,
-                                                           availabilities)
-            return used_ip - 1 == used_ip_after_port_delete
+            used_ips_after_port_delete = self._get_used_ips(self.network,
+                                                            availabilities)
+            return used_ips - 1 == used_ips_after_port_delete
 
         self.assertTrue(
             test_utils.call_until_true(
-                get_net_availability, DELETE_TIMEOUT, DELETE_SLEEP),
+                is_count_ip_availability_valid, DELETE_TIMEOUT, DELETE_SLEEP),
+            msg="IP address did not become available after port delete")
+
+    @decorators.idempotent_id('da1fbed5-b4a9-45b3-bdcb-b1660710d565')
+    def test_show_ip_availability_after_subnet_and_ports_create(self):
+        net_availability = self.admin_client.show_network_ip_availability(
+            self.network['id'])
+        self._assert_total_and_used_ips(0, 0, self.network, net_availability)
+        subnet = self.create_subnet(self.network, enable_dhcp=False)
+        prefix = netaddr.IPNetwork(subnet['cidr']).prefixlen
+        net_availability = self.admin_client.show_network_ip_availability(
+            self.network['id'])
+        used_ips_before_port_create = self._get_used_ips(self.network,
+                                                         net_availability)
+        self.create_port(self.network)
+        net_availability = self.admin_client.show_network_ip_availability(
+            self.network['id'])
+        self._assert_total_and_used_ips(
+            used_ips_before_port_create + 1,
+            calc_total_ips(prefix, self._ip_version),
+            self.network,
+            net_availability)
+
+    @decorators.idempotent_id('a4d1e291-c152-4d62-9316-8c9bf1c6aee2')
+    def test_show_ip_availability_after_port_delete(self):
+        self.create_subnet(self.network, enable_dhcp=False)
+        port = self.create_port(self.network)
+        net_availability = self.admin_client.show_network_ip_availability(
+            self.network['id'])
+        used_ips = self._get_used_ips(self.network, net_availability)
+        self.client.delete_port(port['id'])
+
+        def is_count_ip_availability_valid():
+            availabilities = self.admin_client.show_network_ip_availability(
+                self.network['id'])
+            used_ips_after_port_delete = self._get_used_ips(self.network,
+                                                            availabilities)
+            return used_ips - 1 == used_ips_after_port_delete
+
+        self.assertTrue(
+            test_utils.call_until_true(
+                is_count_ip_availability_valid, DELETE_TIMEOUT, DELETE_SLEEP),
             msg="IP address did not become available after port delete")
 
 
diff --git a/neutron_tempest_plugin/api/test_qos.py b/neutron_tempest_plugin/api/test_qos.py
index 1b88a7a..4f93577 100644
--- a/neutron_tempest_plugin/api/test_qos.py
+++ b/neutron_tempest_plugin/api/test_qos.py
@@ -1053,6 +1053,25 @@
                           policy_id=policy['id'],
                           direction=self.DIRECTION_EGRESS, min_kbps=201)
 
+    @decorators.idempotent_id('35baf998-ae65-495c-9902-35a0d11e8936')
+    @utils.requires_ext(extension="qos-bw-minimum-ingress",
+                        service="network")
+    def test_rule_create_pass_for_direction_ingress(self):
+        policy = self.create_qos_policy(name='test-policy',
+                                        description='test policy',
+                                        shared=False)
+        self.admin_client.create_minimum_bandwidth_rule(
+            policy_id=policy['id'],
+            direction=self.DIRECTION_INGRESS,
+            min_kbps=201)
+
+        retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+        policy_rules = retrieved_policy['policy']['rules']
+        self.assertEqual(1, len(policy_rules))
+        self.assertEqual(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH,
+                         policy_rules[0]['type'])
+        self.assertEqual(self.DIRECTION_INGRESS, policy_rules[0]['direction'])
+
     @decorators.idempotent_id('a49a6988-2568-47d2-931e-2dbc858943b3')
     def test_rule_update(self):
         policy = self.create_qos_policy(name='test-policy',
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index a2c5c72..32c5db8 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -176,13 +176,13 @@
         client.delete_interface(server_id, port_id=port_id)
 
     def setup_network_and_server(
-        self, router=None, server_name=None, **kwargs):
+        self, router=None, server_name=None, network=None, **kwargs):
         """Create network resources and a server.
 
         Creating a network, subnet, router, keypair, security group
         and a server.
         """
-        self.network = self.create_network()
+        self.network = network or self.create_network()
         LOG.debug("Created network %s", self.network['name'])
         self.subnet = self.create_subnet(self.network)
         LOG.debug("Created subnet %s", self.subnet['id'])
diff --git a/neutron_tempest_plugin/scenario/test_internal_dns.py b/neutron_tempest_plugin/scenario/test_internal_dns.py
index dd89727..fadabb0 100644
--- a/neutron_tempest_plugin/scenario/test_internal_dns.py
+++ b/neutron_tempest_plugin/scenario/test_internal_dns.py
@@ -27,16 +27,17 @@
 
     @utils.requires_ext(extension="dns-integration", service="network")
     @decorators.idempotent_id('988347de-07af-471a-abfa-65aea9f452a6')
-    def test_dns_name(self):
+    def test_dns_domain_and_name(self):
         """Test the ability to ping a VM's hostname from another VM.
 
         1) Create two VMs on the same network, giving each a name
         2) SSH in to the first VM:
           2.1) ping the other VM's internal IP
-          2.2) ping the otheR VM's hostname
+          2.2) ping the other VM's hostname
         """
 
-        self.setup_network_and_server(server_name='luke')
+        network = self.create_network(dns_domain='starwars.')
+        self.setup_network_and_server(network=network, server_name='luke')
         self.create_pingable_secgroup_rule(
             secgroup_id=self.security_groups[-1]['id'])
         self.check_connectivity(self.fip['floating_ip_address'],
@@ -70,4 +71,8 @@
         self.check_remote_connectivity(
             ssh_client, leia_port['fixed_ips'][0]['ip_address'],
             timeout=CONF.validation.ping_timeout * 10)
+        self.assertIn(
+            'starwars', ssh_client.exec_command('cat /etc/resolv.conf'))
+
         self.check_remote_connectivity(ssh_client, 'leia')
+        self.check_remote_connectivity(ssh_client, 'leia.starwars')
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
index 6febb79..1ec79f9 100644
--- a/neutron_tempest_plugin/scenario/test_qos.py
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -79,6 +79,7 @@
     FILE_PATH = "/tmp/img"
 
     NC_PORT = 1234
+    FILE_DOWNLOAD_TIMEOUT = 120
 
     def _create_file_for_bw_tests(self, ssh_client):
         cmd = ("(dd if=/dev/zero bs=%(bs)d count=%(count)d of=%(file_path)s) "
@@ -91,7 +92,7 @@
             raise sc_exceptions.FileCreationFailedException(
                 file=QoSTestMixin.FILE_PATH)
 
-    def _check_bw(self, ssh_client, host, port):
+    def _check_bw(self, ssh_client, host, port, expected_bw=LIMIT_BYTES_SEC):
         cmd = "killall -q nc"
         try:
             ssh_client.exec_command(cmd)
@@ -101,14 +102,15 @@
                 'port': port, 'file_path': QoSTestMixin.FILE_PATH})
         ssh_client.exec_command(cmd)
 
+        # Open TCP socket to remote VM and download big file
         start_time = time.time()
         client_socket = _connect_socket(host, port)
         total_bytes_read = 0
-
         while total_bytes_read < QoSTestMixin.FILE_SIZE:
             data = client_socket.recv(QoSTestMixin.BUFFER_SIZE)
             total_bytes_read += len(data)
 
+        # Calculate and return actual BW + logging result
         time_elapsed = time.time() - start_time
         bytes_per_second = total_bytes_read / time_elapsed
 
@@ -118,8 +120,7 @@
                   {'time_elapsed': time_elapsed,
                    'total_bytes_read': total_bytes_read,
                    'bytes_per_second': bytes_per_second})
-
-        return bytes_per_second <= QoSTestMixin.LIMIT_BYTES_SEC
+        return bytes_per_second <= expected_bw
 
     def _create_ssh_client(self):
         return ssh.Client(self.fip['floating_ip_address'],
@@ -154,30 +155,118 @@
     def resource_setup(cls):
         super(QoSTest, cls).resource_setup()
 
-    @decorators.idempotent_id('1f7ed39b-428f-410a-bd2b-db9f465680df')
-    def test_qos(self):
-        """This is a basic test that check that a QoS policy with
+    @decorators.idempotent_id('00682a0c-b72e-11e8-b81e-8c16450ea513')
+    def test_qos_basic_and_update(self):
+        """This test covers both:
 
-           a bandwidth limit rule is applied correctly by sending
-           a file from the instance to the test node.
-           Then calculating the bandwidth every ~1 sec by the number of bits
-           received / elapsed time.
+            1) Basic QoS functionality
+            This is a basic test that check that a QoS policy with
+            a bandwidth limit rule is applied correctly by sending
+            a file from the instance to the test node.
+            Then calculating the bandwidth every ~1 sec by the number of bits
+            received / elapsed time.
+
+            2) Update QoS policy
+            Administrator has the ability to update existing QoS policy,
+            this test is planned to verify that:
+            - actual BW is affected as expected after updating QoS policy.
+            Test scenario:
+            1) Associating QoS Policy with "Original_bandwidth"
+               to the test node
+            2) BW validation - by downloading file on test node.
+               ("Original_bandwidth" is expected)
+            3) Updating existing QoS Policy to a new BW value
+               "Updated_bandwidth"
+            4) BW validation - by downloading file on test node.
+               ("Updated_bandwidth" is expected)
+            Note:
+            There are two options to associate QoS policy to VM:
+            "Neutron Port" or "Network", in this test
+            both options are covered.
         """
+
+        # Setup resources
         self._test_basic_resources()
-        policy_id = self._create_qos_policy()
         ssh_client = self._create_ssh_client()
-        self.os_admin.network_client.create_bandwidth_limit_rule(
-            policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
-            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)
-        port = self.client.list_ports(network_id=self.network['id'],
-                                      device_id=self.server[
-                                      'server']['id'])['ports'][0]
-        self.os_admin.network_client.update_port(port['id'],
-                                                 qos_policy_id=policy_id)
+
+        # Create QoS policy
+        bw_limit_policy_id = self._create_qos_policy()
+
+        # As admin user create QoS rule
+        rule_id = self.os_admin.network_client.create_bandwidth_limit_rule(
+            policy_id=bw_limit_policy_id,
+            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
+            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
+                'bandwidth_limit_rule']['id']
+
+        # Associate QoS to the network
+        self.os_admin.network_client.update_network(
+            self.network['id'], qos_policy_id=bw_limit_policy_id)
+
+        # Create file on VM
         self._create_file_for_bw_tests(ssh_client)
+
+        # Basic test, Check that actual BW while downloading file
+        # is as expected (Original BW)
         utils.wait_until_true(lambda: self._check_bw(
             ssh_client,
             self.fip['floating_ip_address'],
             port=self.NC_PORT),
-            timeout=120,
+            timeout=self.FILE_DOWNLOAD_TIMEOUT,
+            sleep=1)
+
+        # As admin user update QoS rule
+        self.os_admin.network_client.update_bandwidth_limit_rule(
+            bw_limit_policy_id,
+            rule_id,
+            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2,
+            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 2)
+
+        # Check that actual BW while downloading file
+        # is as expected (Update BW)
+        utils.wait_until_true(lambda: self._check_bw(
+            ssh_client,
+            self.fip['floating_ip_address'],
+            port=self.NC_PORT,
+            expected_bw=QoSTest.LIMIT_BYTES_SEC * 2),
+            timeout=self.FILE_DOWNLOAD_TIMEOUT,
+            sleep=1)
+
+        # Create a new QoS policy
+        bw_limit_policy_id_new = self._create_qos_policy()
+
+        # As admin user create a new QoS rule
+        rule_id_new = self.os_admin.network_client.create_bandwidth_limit_rule(
+            policy_id=bw_limit_policy_id_new,
+            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
+            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)[
+                'bandwidth_limit_rule']['id']
+
+        # Associate a new QoS policy to Neutron port
+        self.os_admin.network_client.update_port(
+            self.port['id'], qos_policy_id=bw_limit_policy_id_new)
+
+        # Check that actual BW while downloading file
+        # is as expected (Original BW)
+        utils.wait_until_true(lambda: self._check_bw(
+            ssh_client,
+            self.fip['floating_ip_address'],
+            port=self.NC_PORT),
+            timeout=self.FILE_DOWNLOAD_TIMEOUT,
+            sleep=1)
+
+        # As admin user update QoS rule
+        self.os_admin.network_client.update_bandwidth_limit_rule(
+            bw_limit_policy_id_new,
+            rule_id_new,
+            max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3,
+            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND * 3)
+
+        # Check that actual BW while downloading file
+        # is as expected (Update BW)
+        utils.wait_until_true(lambda: self._check_bw(
+            ssh_client,
+            self.fip['floating_ip_address'],
+            port=self.NC_PORT, expected_bw=QoSTest.LIMIT_BYTES_SEC * 3),
+            timeout=self.FILE_DOWNLOAD_TIMEOUT,
             sleep=1)
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index 2681cd1..7db893b 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -41,7 +41,7 @@
 
         # The following list represents resource names that do not require
         # changing underscore to a hyphen
-        hyphen_exceptions = ["service_profiles"]
+        hyphen_exceptions = ["service_profiles", "availability_zones"]
         # the following map is used to construct proper URI
         # for the given neutron resource
         service_resource_prefix_map = {