--- /dev/null
+heat_template_version: 2013-05-23
+
+description: >
+  Template for SDNVPN testcase 4
+   VPN provides connectivity between subnets using router association
+
+parameters:
+  flavor:
+    type: string
+    description: flavor for the servers to be created
+    constraints:
+      - custom_constraint: nova.flavor
+  image_n:
+    type: string
+    description: image for the servers to be created
+    constraints:
+      - custom_constraint: glance.image
+  av_zone_1:
+    type: string
+    description: availability zone 1
+  av_zone_2:
+    type: string
+    description: availability zone 2
+
+  net_1_name:
+    type: string
+    description: network 1
+  subnet_1_name:
+    type: string
+    description: subnet 1 name
+  subnet_1_cidr:
+    type: string
+    description: subnet 1 cidr
+  router_1_name:
+    type: string
+    description: router 1 cidr
+  net_2_name:
+    type: string
+    description: network 2
+  subnet_2_name:
+    type: string
+    description: subnet 2 name
+  subnet_2_cidr:
+    type: string
+    description: subnet 1 cidr
+
+  secgroup_name:
+    type: string
+    description: security group name
+  secgroup_descr:
+    type: string
+    description: security group slogan
+
+  instance_1_name:
+    type: string
+    description: instance name
+  instance_2_name:
+    type: string
+    description: instance name
+  instance_3_name:
+    type: string
+    description: instance name
+  instance_4_name:
+    type: string
+    description: instance name
+  instance_5_name:
+    type: string
+    description: instance name
+
+  ping_count:
+    type: string
+    description: ping count for user data script
+    default: 10
+
+resources:
+  net_1:
+    type: OS::Neutron::Net
+    properties:
+      name: { get_param: net_1_name }
+  subnet_1:
+    type: OS::Neutron::Subnet
+    properties:
+      name: { get_param: subnet_1_name }
+      network: { get_resource: net_1 }
+      cidr: { get_param: subnet_1_cidr }
+  router_1:
+    type: OS::Neutron::Router
+    properties:
+      name: { get_param: router_1_name }
+  routerinterface_1:
+    type: OS::Neutron::RouterInterface
+    properties:
+      router_id: { get_resource: router_1 }
+      subnet_id: { get_resource: subnet_1 }
+
+  net_2:
+    type: OS::Neutron::Net
+    properties:
+      name: { get_param: net_2_name }
+  subnet_2:
+    type: OS::Neutron::Subnet
+    properties:
+      name: { get_param: subnet_2_name }
+      network: { get_resource: net_2 }
+      cidr: { get_param: subnet_2_cidr }
+
+  sec_group:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      name: { get_param: secgroup_name }
+      description: { get_param: secgroup_descr }
+      rules:
+        - protocol: icmp
+          remote_ip_prefix: 0.0.0.0/0
+        - protocol: tcp
+          port_range_min: 22
+          port_range_max: 22
+          remote_ip_prefix: 0.0.0.0/0
+
+  vm1:
+    type: OS::Nova::Server
+    depends_on: [ vm2, vm3, vm4, vm5 ]
+    properties:
+      name: { get_param: instance_1_name }
+      image: { get_param: image_n }
+      flavor: { get_param: flavor }
+      availability_zone: { get_param: av_zone_1 }
+      security_groups:
+        - { get_resource: sec_group }
+      networks:
+        - subnet: { get_resource: subnet_1 }
+      config_drive: True
+      user_data_format: RAW
+      user_data:
+        str_replace:
+          template: |
+            #!/bin/sh
+            set $IP_VM2 $IP_VM3 $IP_VM4 $IP_VM5
+            while true; do
+             for i do
+              ip=$i
+              ping -c $COUNT $ip 2>&1 >/dev/null
+              RES=$?
+              if [ \"Z$RES\" = \"Z0\" ] ; then
+               echo ping $ip OK
+              else echo ping $ip KO
+              fi
+              done
+             sleep 1
+            done
+          params:
+            $IP_VM2: { get_attr: [vm2, addresses, { get_resource: net_1}, 0, addr] }
+            $IP_VM3: { get_attr: [vm3, addresses, { get_resource: net_1}, 0, addr] }
+            $IP_VM4: { get_attr: [vm4, addresses, { get_resource: net_2}, 0, addr] }
+            $IP_VM5: { get_attr: [vm5, addresses, { get_resource: net_2}, 0, addr] }
+            $COUNT: { get_param: ping_count }
+  vm2:
+    type: OS::Nova::Server
+    properties:
+      name: { get_param: instance_2_name }
+      image: { get_param: image_n }
+      flavor: { get_param: flavor }
+      availability_zone: { get_param: av_zone_1 }
+      security_groups:
+        - { get_resource: sec_group }
+      networks:
+        - subnet: { get_resource: subnet_1 }
+  vm3:
+    type: OS::Nova::Server
+    properties:
+      name: { get_param: instance_3_name }
+      image: { get_param: image_n }
+      flavor: { get_param: flavor }
+      availability_zone: { get_param: av_zone_2 }
+      security_groups:
+        - { get_resource: sec_group }
+      networks:
+        - subnet: { get_resource: subnet_1 }
+  vm4:
+    type: OS::Nova::Server
+    depends_on: vm5
+    properties:
+      name: { get_param: instance_4_name }
+      image: { get_param: image_n }
+      flavor: { get_param: flavor }
+      availability_zone: { get_param: av_zone_1 }
+      security_groups:
+        - { get_resource: sec_group }
+      networks:
+        - subnet: { get_resource: subnet_2 }
+      config_drive: True
+      user_data_format: RAW
+      user_data:
+        str_replace:
+          template: |
+            #!/bin/sh
+            set $IP_VM5
+            while true; do
+             for i do
+              ip=$i
+              ping -c $COUNT $ip 2>&1 >/dev/null
+              RES=$?
+              if [ \"Z$RES\" = \"Z0\" ] ; then
+               echo ping $ip OK
+              else echo ping $ip KO
+              fi
+              done
+             sleep 1
+            done
+          params:
+            $IP_VM5: { get_attr: [vm5, addresses, { get_resource: net_2}, 0, addr] }
+            $COUNT: { get_param: ping_count }
+
+  vm5:
+    type: OS::Nova::Server
+    properties:
+      name: { get_param: instance_5_name }
+      image: { get_param: image_n }
+      flavor: { get_param: flavor }
+      availability_zone: { get_param: av_zone_2 }
+      security_groups:
+        - { get_resource: sec_group }
+      networks:
+        - subnet: { get_resource: subnet_2 }
+
+outputs:
+  router_1_o:
+    description: the id of network 1
+    value: { get_attr: [router_1, show, id] }
+  net_2_o:
+    description: the id of network 2
+    value: { get_attr: [net_2, show, id] }
+  vm1_o:
+    description: the deployed vm resource
+    value: { get_attr: [vm1, show, name] }
+  vm2_o:
+    description: the deployed vm resource
+    value: { get_attr: [vm2, show, name] }
+  vm3_o:
+    description: the deployed vm resource
+    value: { get_attr: [vm3, show, name] }
+  vm4_o:
+    description: the deployed vm resource
+    value: { get_attr: [vm4, show, name] }
+  vm5_o:
+    description: the deployed vm resource
+    value: { get_attr: [vm5, show, name] }
 
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (c) 2018 All rights reserved
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+
+import logging
+import sys
+import pkg_resources
+
+from random import randint
+from sdnvpn.lib import config as sdnvpn_config
+from sdnvpn.lib import openstack_utils as os_utils
+from sdnvpn.lib import utils as test_utils
+from sdnvpn.lib.results import Results
+
+logger = logging.getLogger(__name__)
+
+COMMON_CONFIG = sdnvpn_config.CommonConfig()
+TESTCASE_CONFIG = sdnvpn_config.TestcaseConfig(
+    'sdnvpn.test.functest.testcase_4bis')
+
+
+def main():
+    conn = os_utils.get_os_connection()
+    results = Results(COMMON_CONFIG.line_length, conn)
+
+    results.add_to_summary(0, '=')
+    results.add_to_summary(2, 'STATUS', 'SUBTEST')
+    results.add_to_summary(0, '=')
+
+    conn = os_utils.get_os_connection()
+    # neutron client is needed as long as bgpvpn heat module
+    # is not yet installed by default in apex (APEX-618)
+    neutron_client = os_utils.get_neutron_client()
+
+    image_ids = []
+    bgpvpn_ids = []
+
+    try:
+        image_id = os_utils.create_glance_image(
+            conn, TESTCASE_CONFIG.image_name,
+            COMMON_CONFIG.image_path, disk=COMMON_CONFIG.image_format,
+            container='bare', public='public')
+        image_ids = [image_id]
+
+        compute_nodes = test_utils.assert_and_get_compute_nodes(conn)
+        az_1 = 'nova:' + compute_nodes[0]
+        az_2 = 'nova:' + compute_nodes[1]
+
+        file_path = pkg_resources.resource_filename(
+            'sdnvpn', TESTCASE_CONFIG.hot_file_name)
+        templ = open(file_path, 'r').read()
+        logger.debug("Template is read: '%s'" % templ)
+        env = test_utils.get_heat_environment(TESTCASE_CONFIG, COMMON_CONFIG)
+        logger.debug("Environment is read: '%s'" % env)
+
+        env['name'] = TESTCASE_CONFIG.stack_name
+        env['template'] = templ
+        env['parameters']['image_n'] = TESTCASE_CONFIG.image_name
+        env['parameters']['av_zone_1'] = az_1
+        env['parameters']['av_zone_2'] = az_2
+
+        stack_id = os_utils.create_stack(conn, **env)
+        if stack_id is None:
+            logger.error('Stack create start failed')
+            raise SystemError('Stack create start failed')
+
+        test_utils.wait_stack_for_status(conn, stack_id, 'CREATE_COMPLETE')
+
+        router_1_output = os_utils.get_output(conn, stack_id, 'router_1_o')
+        router_1_id = router_1_output['output_value']
+        net_2_output = os_utils.get_output(conn, stack_id, 'net_2_o')
+        network_2_id = net_2_output['output_value']
+
+        vm_stack_output_keys = ['vm1_o', 'vm2_o', 'vm3_o', 'vm4_o', 'vm5_o']
+        vms = test_utils.get_vms_from_stack_outputs(conn,
+                                                    stack_id,
+                                                    vm_stack_output_keys)
+
+        logger.debug("Entering base test case with stack '%s'" % stack_id)
+
+        msg = ('Create VPN with eRT<>iRT')
+        results.record_action(msg)
+        vpn_name = 'sdnvpn-' + str(randint(100000, 999999))
+        kwargs = {
+            'import_targets': TESTCASE_CONFIG.targets1,
+            'export_targets': TESTCASE_CONFIG.targets2,
+            'route_distinguishers': TESTCASE_CONFIG.route_distinguishers,
+            'name': vpn_name
+        }
+        bgpvpn = test_utils.create_bgpvpn(neutron_client, **kwargs)
+        bgpvpn_id = bgpvpn['bgpvpn']['id']
+        logger.debug("VPN created details: %s" % bgpvpn)
+        bgpvpn_ids.append(bgpvpn_id)
+
+        msg = ("Associate router '%s' to the VPN." %
+               TESTCASE_CONFIG.heat_parameters['router_1_name'])
+        results.record_action(msg)
+        results.add_to_summary(0, '-')
+
+        test_utils.create_router_association(
+            neutron_client, bgpvpn_id, router_1_id)
+
+        # Remember: vms[X] is former vm_X+1
+
+        results.get_ping_status(vms[0], vms[1], expected='PASS', timeout=200)
+        results.get_ping_status(vms[0], vms[2], expected='PASS', timeout=30)
+        results.get_ping_status(vms[0], vms[3], expected='FAIL', timeout=30)
+
+        msg = ("Associate network '%s' to the VPN." %
+               TESTCASE_CONFIG.heat_parameters['net_2_name'])
+        results.add_to_summary(0, '-')
+        results.record_action(msg)
+        results.add_to_summary(0, '-')
+
+        test_utils.create_network_association(
+            neutron_client, bgpvpn_id, network_2_id)
+
+        test_utils.wait_for_bgp_router_assoc(
+            neutron_client, bgpvpn_id, router_1_id)
+        test_utils.wait_for_bgp_net_assocs(
+            neutron_client, bgpvpn_id, network_2_id)
+
+        logger.info('Waiting for the VMs to connect to each other using the'
+                    ' updated network configuration')
+        test_utils.wait_before_subtest()
+
+        results.get_ping_status(vms[3], vms[4], expected='PASS', timeout=30)
+        # TODO enable again when isolation in VPN with iRT != eRT works
+        # results.get_ping_status(vms[0], vms[3], expected="FAIL", timeout=30)
+        # results.get_ping_status(vms[0], vms[4], expected="FAIL", timeout=30)
+
+        msg = ('Update VPN with eRT=iRT ...')
+        results.add_to_summary(0, "-")
+        results.record_action(msg)
+        results.add_to_summary(0, "-")
+
+        # use bgpvpn-create instead of update till NETVIRT-1067 bug is fixed
+        # kwargs = {"import_targets": TESTCASE_CONFIG.targets1,
+        #           "export_targets": TESTCASE_CONFIG.targets1,
+        #           "name": vpn_name}
+        # bgpvpn = test_utils.update_bgpvpn(neutron_client,
+        #                                   bgpvpn_id, **kwargs)
+
+        test_utils.delete_bgpvpn(neutron_client, bgpvpn_id)
+        bgpvpn_ids.remove(bgpvpn_id)
+        kwargs = {
+            'import_targets': TESTCASE_CONFIG.targets1,
+            'export_targets': TESTCASE_CONFIG.targets1,
+            'route_distinguishers': TESTCASE_CONFIG.route_distinguishers,
+            'name': vpn_name
+        }
+
+        test_utils.wait_before_subtest()
+
+        bgpvpn = test_utils.create_bgpvpn(neutron_client, **kwargs)
+        bgpvpn_id = bgpvpn['bgpvpn']['id']
+        logger.debug("VPN re-created details: %s" % bgpvpn)
+        bgpvpn_ids.append(bgpvpn_id)
+
+        msg = ("Associate again network '%s' and router '%s 'to the VPN."
+               % (TESTCASE_CONFIG.heat_parameters['net_2_name'],
+                  TESTCASE_CONFIG.heat_parameters['router_1_name']))
+        results.add_to_summary(0, '-')
+        results.record_action(msg)
+        results.add_to_summary(0, '-')
+
+        test_utils.create_router_association(
+            neutron_client, bgpvpn_id, router_1_id)
+
+        test_utils.create_network_association(
+            neutron_client, bgpvpn_id, network_2_id)
+
+        test_utils.wait_for_bgp_router_assoc(
+            neutron_client, bgpvpn_id, router_1_id)
+        test_utils.wait_for_bgp_net_assoc(
+            neutron_client, bgpvpn_id, network_2_id)
+        # The above code has to be removed after re-enabling bgpvpn-update
+
+        logger.info('Waiting for the VMs to connect to each other using the'
+                    ' updated network configuration')
+        test_utils.wait_before_subtest()
+
+        # TODO: uncomment the following once ODL netvirt fixes the following
+        # bug: https://jira.opendaylight.org/browse/NETVIRT-932
+        # results.get_ping_status(vms[0], vms[3], expected="PASS", timeout=30)
+        # results.get_ping_status(vms[0], vms[4], expected="PASS", timeout=30)
+
+        results.add_to_summary(0, '=')
+        logger.info("\n%s" % results.summary)
+
+    except Exception as e:
+        logger.error("exception occurred while executing testcase_4bis: %s", e)
+        raise
+    finally:
+        test_utils.cleanup_glance(conn, image_ids)
+        test_utils.cleanup_neutron(conn, neutron_client, [], bgpvpn_ids,
+                                   [], [], [], [])
+
+        try:
+            test_utils.delete_stack_and_wait(conn, stack_id)
+        except Exception as e:
+            logger.error(
+                "exception occurred while executing testcase_4bis: %s", e)
+
+    return results.compile_summary()
+
+
+if __name__ == '__main__':
+    sys.exit(main())