Add a scenario test for remote address group

Add a scenario test for SG rules with remote address group.

Implements: blueprint address-groups-in-sg-rules
Change-Id: I982552f47297a83a351ec67090c9101d80d88d4a
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index 2290d0f..3812383 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -68,6 +68,11 @@
                default=None,
                choices=['None', 'linuxbridge', 'ovs', 'sriov'],
                help='Agent used for devstack@q-agt.service'),
+    cfg.StrOpt('firewall_driver',
+               default=None,
+               choices=['None', 'openvswitch', 'ovn',
+                        'iptables_hybrid', 'iptables'],
+               help='Driver for security groups firewall in the L2 agent'),
 
     # Multicast tests settings
     cfg.StrOpt('multicast_group_range',
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
index 6910c11..334d543 100644
--- a/neutron_tempest_plugin/scenario/base.py
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -158,13 +158,14 @@
                 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
                     secgroup_id = sg['id']
                     break
-
+        resp = []
         for rule in rule_list:
             direction = rule.pop('direction')
-            client.create_security_group_rule(
-                direction=direction,
-                security_group_id=secgroup_id,
-                **rule)
+            resp.append(client.create_security_group_rule(
+                        direction=direction,
+                        security_group_id=secgroup_id,
+                        **rule)['security_group_rule'])
+        return resp
 
     @classmethod
     def create_loginable_secgroup_rule(cls, secgroup_id=None,
diff --git a/neutron_tempest_plugin/scenario/test_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 9059a2f..8b7098e 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -12,8 +12,12 @@
 #    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 neutron_lib import constants
 
+import netaddr
+from neutron_lib import constants
+import testtools
+
+from tempest.common import utils as tempest_utils
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -310,6 +314,95 @@
         self.ping_ip_address(fips[0]['floating_ip_address'],
                              should_succeed=False)
 
+    @testtools.skipUnless(
+        CONF.neutron_plugin_options.firewall_driver == 'openvswitch',
+        "Openvswitch agent is required to run this test")
+    @decorators.idempotent_id('678dd4c0-2953-4626-b89c-8e7e4110ec4b')
+    @tempest_utils.requires_ext(extension="address-group", service="network")
+    @tempest_utils.requires_ext(
+        extension="security-groups-remote-address-group", service="network")
+    def test_remote_group_and_remote_address_group(self):
+        """Test SG rules with remote group and remote address group
+
+        This test checks the ICMP connection among two servers using a security
+        group rule with remote group and another rule with remote address
+        group. The connection should be granted when at least one of the rules
+        is applied. When both rules are applied (overlapped), removing one of
+        them should not disable the connection.
+        """
+        # create a new sec group
+        ssh_secgrp_name = data_utils.rand_name('ssh_secgrp')
+        ssh_secgrp = self.os_primary.network_client.create_security_group(
+            name=ssh_secgrp_name)
+        # add cleanup
+        self.security_groups.append(ssh_secgrp['security_group'])
+        # configure sec group to support SSH connectivity
+        self.create_loginable_secgroup_rule(
+            secgroup_id=ssh_secgrp['security_group']['id'])
+        # spawn two instances with the sec group created
+        server_ssh_clients, fips, servers = self.create_vm_testing_sec_grp(
+            security_groups=[{'name': ssh_secgrp_name}])
+        # verify SSH functionality
+        for i in range(2):
+            self.check_connectivity(fips[i]['floating_ip_address'],
+                                    CONF.validation.image_ssh_user,
+                                    self.keypair['private_key'])
+        # try to ping instances without ICMP permissions
+        self.check_remote_connectivity(
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            should_succeed=False)
+        # add ICMP support to the remote group
+        rule_list = [{'protocol': constants.PROTO_NUM_ICMP,
+                      'direction': constants.INGRESS_DIRECTION,
+                      'remote_group_id': ssh_secgrp['security_group']['id']}]
+        remote_sg_rid = self.create_secgroup_rules(
+            rule_list, secgroup_id=ssh_secgrp['security_group']['id'])[0]['id']
+        # verify ICMP connectivity between instances works
+        self.check_remote_connectivity(
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            servers=servers)
+        # make sure ICMP connectivity doesn't work from framework
+        self.ping_ip_address(fips[0]['floating_ip_address'],
+                             should_succeed=False)
+
+        # add ICMP rule with remote address group
+        test_ag = self.create_address_group(
+            name=data_utils.rand_name('test_ag'),
+            addresses=[str(netaddr.IPNetwork(fips[0]['fixed_ip_address']))])
+        rule_list = [{'protocol': constants.PROTO_NUM_ICMP,
+                      'direction': constants.INGRESS_DIRECTION,
+                      'remote_address_group_id': test_ag['id']}]
+        remote_ag_rid = self.create_secgroup_rules(
+            rule_list, secgroup_id=ssh_secgrp['security_group']['id'])[0]['id']
+        # verify ICMP connectivity between instances still works
+        self.check_remote_connectivity(
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            servers=servers)
+        # make sure ICMP connectivity doesn't work from framework
+        self.ping_ip_address(fips[0]['floating_ip_address'],
+                             should_succeed=False)
+
+        # Remove the ICMP rule with remote group
+        self.client.delete_security_group_rule(remote_sg_rid)
+        # verify ICMP connectivity between instances still works as granted
+        # by the rule with remote address group
+        self.check_remote_connectivity(
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            servers=servers)
+        # make sure ICMP connectivity doesn't work from framework
+        self.ping_ip_address(fips[0]['floating_ip_address'],
+                             should_succeed=False)
+
+        # Remove the ICMP rule with remote address group
+        self.client.delete_security_group_rule(remote_ag_rid)
+        # verify ICMP connectivity between instances doesn't work now
+        self.check_remote_connectivity(
+            server_ssh_clients[0], fips[1]['fixed_ip_address'],
+            should_succeed=False)
+        # make sure ICMP connectivity doesn't work from framework
+        self.ping_ip_address(fips[0]['floating_ip_address'],
+                             should_succeed=False)
+
     @decorators.idempotent_id('f07d0159-8f9e-4faa-87f5-a869ab0ad488')
     def test_multiple_ports_secgroup_inheritance(self):
         """Test multiple port security group inheritance
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 0d197e0..207a4f1 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -64,6 +64,7 @@
         - router-admin-state-down-before-update
         - router_availability_zone
         - security-group
+        - security-groups-remote-address-group
         - segment
         - service-type
         - sorting
@@ -162,6 +163,7 @@
               available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
+              firewall_driver: openvswitch
     irrelevant-files: &openvswitch-scenario-irrelevant-files
       - ^(test-|)requirements.txt$
       - ^releasenotes/.*$
@@ -231,6 +233,7 @@
               available_features: "{{ network_available_features | join(',') }}"
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
+              firewall_driver: iptables_hybrid
     irrelevant-files:
       - ^(test-|)requirements.txt$
       - ^releasenotes/.*$
@@ -306,6 +309,7 @@
             neutron_plugin_options:
               available_type_drivers: flat,vlan,local,vxlan
               q_agent: linuxbridge
+              firewall_driver: iptables
     irrelevant-files:
       - ^(test-|)requirements.txt$
       - ^releasenotes/.*$
@@ -403,6 +407,7 @@
             neutron_plugin_options:
               available_type_drivers: local,flat,vlan,geneve
               is_igmp_snooping_enabled: True
+              firewall_driver: ovn
     irrelevant-files:
       - ^(test-|)requirements.txt$
       - ^releasenotes/.*$
@@ -543,6 +548,7 @@
               image_is_advanced: true
               available_type_drivers: flat,geneve,vlan,gre,local,vxlan
               l3_agent_mode: dvr_snat
+              firewall_driver: openvswitch
     group-vars:
       subnode:
         devstack_services: