Add integration scenario test for Neutron LBaaS

- added new scenario test that checks work of Neutron LBaaS resource:
test creates stack with two servers and LB resource, checks connection,
adds one more member to LP and finally checks load balancing.

Co-Authored-by: Sergey Kraynev <skraynev@mirantis.com>

Change-Id: I5d05909d437a2ba7b047ae758f3b5d8669fc8b1b
diff --git a/common/config.py b/common/config.py
index 878cd33..158d087 100644
--- a/common/config.py
+++ b/common/config.py
@@ -103,6 +103,10 @@
     cfg.BoolOpt('skip_stack_abandon_tests',
                 default=False,
                 help="Skip Stack Abandon Integration tests"),
+    cfg.IntOpt('connectivity_timeout',
+               default=120,
+               help="Timeout in seconds to wait for connectivity to "
+                    "server."),
 ]
 
 
diff --git a/common/test.py b/common/test.py
index 0ffa2b0..70d8708 100644
--- a/common/test.py
+++ b/common/test.py
@@ -16,6 +16,7 @@
 import re
 import subprocess
 import time
+import urllib
 
 import fixtures
 from heatclient import exc as heat_exceptions
@@ -107,6 +108,22 @@
 
         return linux_client
 
+    def check_connectivity(self, check_ip):
+        def try_connect(ip):
+            try:
+                urllib.urlopen('http://%s/' % ip)
+                return True
+            except IOError:
+                return False
+
+        timeout = self.conf.connectivity_timeout
+        elapsed_time = 0
+        while not try_connect(check_ip):
+            time.sleep(10)
+            elapsed_time += 10
+            if elapsed_time > timeout:
+                raise exceptions.TimeoutException()
+
     def _log_console_output(self, servers=None):
         if not servers:
             servers = self.compute_client.servers.list()
diff --git a/scenario/templates/test_neutron_loadbalancer.yaml b/scenario/templates/test_neutron_loadbalancer.yaml
new file mode 100644
index 0000000..fad7db8
--- /dev/null
+++ b/scenario/templates/test_neutron_loadbalancer.yaml
@@ -0,0 +1,108 @@
+heat_template_version: 2014-10-16
+
+description: |
+  Template which tests neutron load balancing resources
+
+parameters:
+  key_name:
+    type: string
+  flavor:
+    type: string
+  image:
+    type: string
+  private_subnet_id:
+    type: string
+  external_network_id:
+    type: string
+  port:
+    type: string
+    default: '80'
+
+resources:
+  sec_group:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      description: Add security group rules for servers
+      name: security-group
+      rules:
+        - remote_ip_prefix: 0.0.0.0/0
+          protocol: tcp
+          port_range_min: { get_param: port }
+          port_range_max: { get_param: port }
+        - remote_ip_prefix: 0.0.0.0/0
+          protocol: icmp
+
+  server1 :
+    type: OS::Nova::Server
+    properties:
+      name: Server1
+      image: { get_param: image }
+      flavor: { get_param: flavor }
+      key_name: { get_param: key_name }
+      security_groups: [{ get_resource: sec_group }]
+      user_data:
+        list_join:
+        - ''
+        - - '#!/bin/bash -v
+
+            '
+          - 'echo  $(hostname) > index.html
+
+            '
+          - 'python -m SimpleHTTPServer '
+          - { get_param: port }
+
+  server2 :
+    type: OS::Nova::Server
+    properties:
+      name: Server2
+      image: { get_param: image }
+      flavor: { get_param: flavor }
+      key_name: { get_param: key_name }
+      security_groups: [{ get_resource: sec_group }]
+      user_data:
+        list_join:
+        - ''
+        - - '#!/bin/bash -v
+
+            '
+          - 'echo  $(hostname) > index.html
+
+            '
+          - 'python -m SimpleHTTPServer '
+          - { get_param: port }
+
+  health_monitor:
+    type: OS::Neutron::HealthMonitor
+    properties:
+      delay: 3
+      type: HTTP
+      timeout: 3
+      max_retries: 3
+
+  test_pool:
+    type: OS::Neutron::Pool
+    properties:
+      lb_method: ROUND_ROBIN
+      protocol: HTTP
+      subnet: { get_param: private_subnet_id }
+      monitors:
+      - { get_resource: health_monitor }
+      vip:
+        protocol_port: { get_param: port }
+
+  floating_ip:
+     type: OS::Neutron::FloatingIP
+     properties:
+       floating_network: { get_param: external_network_id }
+       port_id:
+         { get_attr: [test_pool, vip, 'port_id'] }
+
+  LBaaS:
+    type: OS::Neutron::LoadBalancer
+    properties:
+      pool_id: { get_resource: test_pool }
+      protocol_port: { get_param: port }
+      members:
+      - { get_resource: server1 }
+
diff --git a/scenario/test_neutron_loadbalancer.py b/scenario/test_neutron_loadbalancer.py
new file mode 100644
index 0000000..bc2d77c
--- /dev/null
+++ b/scenario/test_neutron_loadbalancer.py
@@ -0,0 +1,118 @@
+#
+# 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 time
+import urllib
+
+from heat_integrationtests.scenario import scenario_base
+
+
+class NeutronLoadBalancerTest(scenario_base.ScenarioTestsBase):
+    """
+    The class is responsible for testing of neutron resources balancer.
+    """
+
+    def setUp(self):
+        super(NeutronLoadBalancerTest, self).setUp()
+        self.public_net = self._get_network('public')
+        self.template_name = 'test_neutron_loadbalancer.yaml'
+
+    def collect_responses(self, ip, expected_resp):
+        resp = set()
+        for count in range(10):
+            time.sleep(1)
+            resp.add(urllib.urlopen('http://%s/' % ip).read())
+
+        self.assertEqual(expected_resp, resp)
+
+    def test_neutron_loadbalancer(self):
+        """
+        Check work of Neutron LBaaS resource in Heat.
+
+        The alternative scenario is the following:
+            1. Launch a stack with a load balancer, two servers,
+               but use only one as a LB member.
+            2. Check connection to the servers and LB.
+            3. Collect info about responces, which were received by LB from
+               its members (responces have to be received only from 'server1').
+            4. Update stack definition: include 'server2' into LBaaS.
+            5. Check that number of members in LB was increased and
+               responces were received from 'server1' and 'server2'.
+        """
+
+        parameters = {
+            'key_name': self.keypair_name,
+            'flavor': self.conf.instance_type,
+            'image': self.conf.image_ref,
+            'private_subnet_id': self.net['subnets'][0],
+            'external_network_id': self.public_net['id']
+        }
+
+        # Launch stack
+        sid = self.launch_stack(
+            template_name=self.template_name,
+            parameters=parameters
+        )
+
+        server1_id = self.client.resources.get(
+            sid, 'server1').physical_resource_id
+        server2_id = self.client.resources.get(
+            sid, 'server2').physical_resource_id
+        floating_ip_id = self.client.resources.get(
+            sid, 'floating_ip').physical_resource_id
+        floating_ip = self.network_client.show_floatingip(
+            floating_ip_id)['floatingip']['floating_ip_address']
+        pool_id = self.client.resources.get(
+            sid, 'test_pool').physical_resource_id
+        vip_id = self.network_client.show_pool(pool_id)['pool']['vip_id']
+
+        vip = self.network_client.show_vip(vip_id)['vip']['address']
+        server1_ip = self.compute_client.servers.get(
+            server1_id).networks['private'][0]
+        server2_ip = self.compute_client.servers.get(
+            server2_id).networks['private'][0]
+
+        # Check connection and info about received responces
+        self.check_connectivity(server1_ip)
+        self.collect_responses(server1_ip, {'server1\n'})
+
+        self.check_connectivity(server2_ip)
+        self.collect_responses(server2_ip, {'server2\n'})
+
+        self.check_connectivity(vip)
+        self.collect_responses(vip, {'server1\n'})
+
+        self.check_connectivity(floating_ip)
+        self.collect_responses(floating_ip, {'server1\n'})
+
+        # Include 'server2' to LB and update the stack
+        template = self._load_template(
+            __file__, self.template_name, self.sub_dir
+        )
+
+        template = template.replace(
+            '- { get_resource: server1 }',
+            '- { get_resource: server1 }\n      - { get_resource: server2 }\n'
+        )
+
+        self.update_stack(
+            sid,
+            template=template,
+            parameters=parameters
+        )
+
+        self.check_connectivity(vip)
+        self.collect_responses(vip, {'server1\n', 'server2\n'})
+
+        self.check_connectivity(floating_ip)
+        self.collect_responses(floating_ip, {'server1\n', 'server2\n'})