Merge "Merge Neutron AutoScaling and LoadBalancer tests"
diff --git a/scenario/templates/app_server_neutron.yaml b/scenario/templates/app_server_neutron.yaml
new file mode 100644
index 0000000..9cbf82a
--- /dev/null
+++ b/scenario/templates/app_server_neutron.yaml
@@ -0,0 +1,65 @@
+heat_template_version: 2015-10-15
+
+description: |
+  App server that is a member of Neutron Pool.
+
+parameters:
+
+  image:
+    type: string
+
+  flavor:
+    type: string
+
+  net:
+    type: string
+
+  sec_group:
+    type: string
+
+  pool_id:
+    type: string
+
+  app_port:
+    type: number
+
+  timeout:
+    type: number
+
+resources:
+
+  config:
+    type: OS::Test::WebAppConfig
+    properties:
+      app_port: { get_param: app_port }
+      wc_curl_cli: { get_attr: [ handle, curl_cli ] }
+
+  server:
+    type: OS::Nova::Server
+    properties:
+      image: { get_param: image }
+      flavor: { get_param: flavor }
+      networks:
+        - network: { get_param: net }
+      security_groups:
+        - { get_param: sec_group }
+      user_data_format: RAW
+      user_data: { get_resource: config }
+
+  handle:
+    type: OS::Heat::WaitConditionHandle
+
+  waiter:
+    type: OS::Heat::WaitCondition
+    depends_on: server
+    properties:
+      timeout: { get_param: timeout }
+      handle: { get_resource: handle }
+
+  pool_member:
+    type: OS::Neutron::PoolMember
+    depends_on: waiter
+    properties:
+      address: { get_attr: [ server, networks, { get_param: net }, 0 ] }
+      pool_id: { get_param: pool_id }
+      protocol_port: { get_param: app_port }
diff --git a/scenario/templates/netcat-webapp.yaml b/scenario/templates/netcat-webapp.yaml
new file mode 100644
index 0000000..fdb0335
--- /dev/null
+++ b/scenario/templates/netcat-webapp.yaml
@@ -0,0 +1,35 @@
+heat_template_version: 2015-10-15
+
+description: |
+  Simplest web-app using netcat reporting only hostname.
+  Specifically tailored for minimal Cirros image.
+
+parameters:
+
+  app_port:
+    type: number
+
+  wc_curl_cli:
+    type: string
+
+resources:
+
+  webapp_nc:
+    type: OS::Heat::SoftwareConfig
+    properties:
+      group: ungrouped
+      config:
+        str_replace:
+          template: |
+            #! /bin/sh -v
+            Body=$(hostname)
+            Response="HTTP/1.1 200 OK\r\nContent-Length: ${#Body}\r\n\r\n$Body"
+            wc_notify --data-binary '{"status": "SUCCESS"}'
+            while true ; do echo -e $Response | nc -llp PORT; done
+          params:
+            PORT: { get_param: app_port }
+            wc_notify: { get_param: wc_curl_cli }
+
+outputs:
+  OS::stack_id:
+    value: { get_resource: webapp_nc }
diff --git a/scenario/templates/test_autoscaling_lb_neutron.yaml b/scenario/templates/test_autoscaling_lb_neutron.yaml
new file mode 100644
index 0000000..d47e787
--- /dev/null
+++ b/scenario/templates/test_autoscaling_lb_neutron.yaml
@@ -0,0 +1,113 @@
+heat_template_version: 2015-04-30
+
+description: |
+  Template which tests Neutron load balancing requests to members of
+  Heat AutoScalingGroup.
+  Instances must be running some webserver on a given app_port
+  producing HTTP response that is different between servers
+  but stable over time for given server.
+
+parameters:
+  flavor:
+    type: string
+  image:
+    type: string
+  net:
+    type: string
+  subnet:
+    type: string
+  public_net:
+    type: string
+  app_port:
+    type: number
+    default: 8080
+  lb_port:
+    type: number
+    default: 80
+  timeout:
+    type: number
+    default: 600
+
+resources:
+
+  sec_group:
+    type: OS::Neutron::SecurityGroup
+    properties:
+      rules:
+      - remote_ip_prefix: 0.0.0.0/0
+        protocol: tcp
+        port_range_min: { get_param: app_port }
+        port_range_max: { get_param: app_port }
+
+  asg:
+    type: OS::Heat::AutoScalingGroup
+    properties:
+      desired_capacity: 1
+      max_size: 2
+      min_size: 1
+      resource:
+        type: OS::Test::NeutronAppServer
+        properties:
+          image: { get_param: image }
+          flavor: { get_param: flavor }
+          net: { get_param: net}
+          sec_group: { get_resource: sec_group }
+          app_port: { get_param: app_port }
+          pool_id: { get_resource: pool }
+          timeout: { get_param: timeout }
+
+  scale_up:
+    type: OS::Heat::ScalingPolicy
+    properties:
+      adjustment_type: change_in_capacity
+      auto_scaling_group_id: { get_resource: asg }
+      scaling_adjustment: 1
+
+  scale_down:
+    type: OS::Heat::ScalingPolicy
+    properties:
+      adjustment_type: change_in_capacity
+      auto_scaling_group_id: { get_resource: asg }
+      scaling_adjustment: -1
+
+  health_monitor:
+    type: OS::Neutron::HealthMonitor
+    properties:
+      delay: 3
+      type: HTTP
+      timeout: 3
+      max_retries: 3
+
+  pool:
+    type: OS::Neutron::Pool
+    properties:
+      lb_method: ROUND_ROBIN
+      protocol: HTTP
+      subnet: { get_param: subnet }
+      monitors:
+      - { get_resource: health_monitor }
+      vip:
+        protocol_port: { get_param: lb_port }
+
+  floating_ip:
+     type: OS::Neutron::FloatingIP
+     properties:
+       floating_network: { get_param: public_net }
+       port_id:
+         { get_attr: [pool, vip, 'port_id'] }
+
+  loadbalancer:
+    type: OS::Neutron::LoadBalancer
+    properties:
+      pool_id: { get_resource: pool }
+      protocol_port: { get_param: app_port }
+
+outputs:
+  lburl:
+    description: URL of the loadbalanced app
+    value:
+      str_replace:
+        template: http://IP_ADDRESS:PORT
+        params:
+          IP_ADDRESS: { get_attr: [ floating_ip, floating_ip_address ] }
+          PORT: { get_param: lb_port }
diff --git a/scenario/templates/test_neutron_autoscaling.yaml b/scenario/templates/test_neutron_autoscaling.yaml
deleted file mode 100644
index a34ec43..0000000
--- a/scenario/templates/test_neutron_autoscaling.yaml
+++ /dev/null
@@ -1,56 +0,0 @@
-heat_template_version: 2014-10-16
-
-description: Auto-scaling Test
-
-parameters:
-  image_id:
-    type: string
-    label: Image ID
-    description: Image ID from configurations
-  capacity:
-    type: string
-    label: Capacity
-    description: Auto-scaling group desired capacity
-  fixed_subnet:
-    type: string
-    label: fixed subnetwork ID
-    description: subnetwork ID used for autoscaling
-  instance_type:
-    type: string
-    label: instance_type
-    description: type of instance to launch
-
-resources:
-  test_pool:
-    type: OS::Neutron::Pool
-    properties:
-      description: Test Pool
-      lb_method: ROUND_ROBIN
-      name: test_pool
-      protocol: HTTP
-      subnet: { get_param: fixed_subnet }
-      vip: {
-        "description": "Test VIP",
-        "protocol_port": 80,
-        "name": "test_vip"
-      }
-  load_balancer:
-    type: OS::Neutron::LoadBalancer
-    properties:
-      protocol_port: 80
-      pool_id: { get_resource: test_pool }
-  launch_config:
-    type: AWS::AutoScaling::LaunchConfiguration
-    properties:
-      ImageId: { get_param: image_id }
-      InstanceType: { get_param: instance_type }
-  server_group:
-    type: AWS::AutoScaling::AutoScalingGroup
-    properties:
-      AvailabilityZones : ["nova"]
-      LaunchConfigurationName : { get_resource : launch_config }
-      VPCZoneIdentifier: [{ get_param: fixed_subnet }]
-      MinSize : 1
-      MaxSize : 5
-      DesiredCapacity: { get_param: capacity }
-      LoadBalancerNames : [ { get_resource : load_balancer } ]
diff --git a/scenario/templates/test_neutron_loadbalancer.yaml b/scenario/templates/test_neutron_loadbalancer.yaml
deleted file mode 100644
index dd659d0..0000000
--- a/scenario/templates/test_neutron_loadbalancer.yaml
+++ /dev/null
@@ -1,133 +0,0 @@
-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
-  network:
-    type: string
-  private_subnet_id:
-    type: string
-  external_network_id:
-    type: string
-  port:
-    type: string
-    default: '80'
-  timeout:
-    type: number
-
-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
-
-  wait_condition:
-    type: OS::Heat::WaitCondition
-    properties:
-      handle: { get_resource: wait_condition_handle }
-      count: 2
-      timeout: { get_param: timeout }
-
-  wait_condition_handle:
-    type: OS::Heat::WaitConditionHandle
-
-  config:
-    type: OS::Heat::SoftwareConfig
-    properties:
-      group: ungrouped
-      config:
-        str_replace:
-          template: |
-            #!/bin/bash -v
-            echo  $(hostname) > index.html
-            python -m SimpleHTTPServer port &
-            wc_notify --data-binary '{"status": "SUCCESS"}'
-          params:
-            wc_notify: { get_attr: ['wait_condition_handle', 'curl_cli'] }
-            port: { get_param: port }
-
-  server1:
-    type: OS::Nova::Server
-    properties:
-      name: Server1
-      image: { get_param: image }
-      flavor: { get_param: flavor }
-      key_name: { get_param: key_name }
-      networks: [{network: {get_param: network} }]
-      security_groups: [{ get_resource: sec_group }]
-      user_data_format: SOFTWARE_CONFIG
-      user_data: { get_resource: config }
-
-  server2:
-    type: OS::Nova::Server
-    properties:
-      name: Server2
-      image: { get_param: image }
-      flavor: { get_param: flavor }
-      key_name: { get_param: key_name }
-      networks: [{network: {get_param: network} }]
-      security_groups: [{ get_resource: sec_group }]
-      user_data_format: SOFTWARE_CONFIG
-      user_data: { get_resource: config }
-
-  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'] }
-       fixed_ip_address:
-         { get_attr: [test_pool, vip, 'address'] }
-
-  LBaaS:
-    type: OS::Neutron::LoadBalancer
-    depends_on: wait_condition
-    properties:
-      pool_id: { get_resource: test_pool }
-      protocol_port: { get_param: port }
-      members:
-      - { get_resource: server1 }
-
-outputs:
-  serv1_ip:
-    value: {get_attr: [server1, networks, { get_param: network }, 0]}
-  serv2_ip:
-    value: {get_attr: [server2, networks, { get_param: network }, 0]}
-  vip:
-    value: {get_attr: [test_pool, vip, address]}
-  fip:
-    value: {get_attr: [floating_ip, floating_ip_address]}
diff --git a/scenario/test_autoscaling_lb.py b/scenario/test_autoscaling_lb.py
new file mode 100644
index 0000000..21b27dd
--- /dev/null
+++ b/scenario/test_autoscaling_lb.py
@@ -0,0 +1,114 @@
+#
+# 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 requests
+
+from heat_integrationtests.common import test
+from heat_integrationtests.scenario import scenario_base
+
+
+class AutoscalingLoadBalancerTest(scenario_base.ScenarioTestsBase):
+    """
+    The class is responsible for testing ASG + LB scenario.
+
+    The very common use case tested is an autoscaling group
+    of some web application servers behind a loadbalancer.
+    """
+
+    def setUp(self):
+        super(AutoscalingLoadBalancerTest, self).setUp()
+        self.template_name = 'test_autoscaling_lb_neutron.yaml'
+        self.app_server_template_name = 'app_server_neutron.yaml'
+        self.webapp_template_name = 'netcat-webapp.yaml'
+
+    def check_num_responses(self, url, expected_num, retries=10):
+        resp = set()
+        for count in range(retries):
+            time.sleep(1)
+            r = requests.get(url)
+            # skip unsuccessfull requests
+            if r.status_code == 200:
+                resp.add(r.text)
+        self.assertEqual(expected_num, len(resp))
+
+    def autoscale_complete(self, stack_id, expected_num):
+        res_list = self.client.resources.list(stack_id)
+        all_res_complete = all(res.resource_status in ('UPDATE_COMPLETE',
+                                                       'CREATE_COMPLETE')
+                               for res in res_list)
+        all_res = len(res_list) == expected_num
+        return all_res and all_res_complete
+
+    def test_autoscaling_loadbalancer_neutron(self):
+        """
+        Check work of AutoScaing and Neutron LBaaS resource in Heat.
+
+        The scenario is the following:
+            1. Launch a stack with a load balancer and autoscaling group
+               of one server, wait until stack create is complete.
+            2. Check that there is only one distinctive response from
+               loadbalanced IP.
+            3. Signal the scale_up policy, wait until all resources in
+               autoscaling group are complete.
+            4. Check that now there are two distinctive responses from
+               loadbalanced IP.
+        """
+
+        parameters = {
+            'flavor': self.conf.minimal_instance_type,
+            'image': self.conf.minimal_image_ref,
+            'net': self.conf.fixed_network_name,
+            'subnet': self.conf.fixed_subnet_name,
+            'public_net': self.conf.floating_network_name,
+            'app_port': 8080,
+            'lb_port': 80,
+            'timeout': 600
+        }
+
+        app_server_template = self._load_template(
+            __file__, self.app_server_template_name, self.sub_dir
+        )
+        webapp_template = self._load_template(
+            __file__, self.webapp_template_name, self.sub_dir
+        )
+        files = {'appserver.yaml': app_server_template,
+                 'webapp.yaml': webapp_template}
+        env = {'resource_registry':
+               {'OS::Test::NeutronAppServer': 'appserver.yaml',
+                'OS::Test::WebAppConfig': 'webapp.yaml'}}
+        # Launch stack
+        sid = self.launch_stack(
+            template_name=self.template_name,
+            parameters=parameters,
+            files=files,
+            environment=env
+        )
+        stack = self.client.stacks.get(sid)
+        lb_url = self._stack_output(stack, 'lburl')
+        # Check number of distinctive responces, must be 1
+        self.check_num_responses(lb_url, 1)
+
+        # Signal the scaling hook
+        self.client.resources.signal(sid, 'scale_up')
+
+        # Wait for AutoScalingGroup update to finish
+        asg = self.client.resources.get(sid, 'asg')
+        test.call_until_true(self.conf.build_timeout,
+                             self.conf.build_interval,
+                             self.autoscale_complete,
+                             asg.physical_resource_id, 2)
+
+        # Check number of distinctive responses, must now be 2
+        self.check_num_responses(lb_url, 2)
diff --git a/scenario/test_neutron_autoscaling.py b/scenario/test_neutron_autoscaling.py
deleted file mode 100644
index e7aae19..0000000
--- a/scenario/test_neutron_autoscaling.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# 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 heat_integrationtests.scenario import scenario_base
-
-
-class NeutronAutoscalingTest(scenario_base.ScenarioTestsBase):
-    """
-    The class is responsible for testing of neutron resources autoscaling.
-    """
-
-    def setUp(self):
-        super(NeutronAutoscalingTest, self).setUp()
-        if not self.conf.fixed_subnet_name:
-            raise self.skipException("No sub-network configured to test")
-        self.template_name = 'test_neutron_autoscaling.yaml'
-
-    def test_neutron_autoscaling(self):
-        """
-        Check autoscaling of load balancer members in Heat.
-
-        The alternative scenario is the following:
-            1. Launch a stack with a load balancer.
-            2. Check that the load balancer created
-            one load balancer member for stack.
-            3. Update stack definition: increase desired capacity of stack.
-            4. Check that number of members in load balancer was increased.
-        """
-
-        parameters = {
-            "image_id": self.conf.minimal_image_ref,
-            "capacity": "1",
-            "instance_type": self.conf.minimal_instance_type,
-            "fixed_subnet": self.net['subnets'][0],
-        }
-
-        # Launch stack
-        stack_id = self.launch_stack(
-            template_name=self.template_name,
-            parameters=parameters
-        )
-
-        # Check number of members
-        pool_resource = self.client.resources.get(stack_id, 'test_pool')
-        pool_members = self.network_client.list_members(
-            pool_id=pool_resource.physical_resource_id)['members']
-        self.assertEqual(1, len(pool_members))
-
-        # Increase desired capacity and update the stack
-        template = self._load_template(
-            __file__, self.template_name, self.sub_dir
-        )
-        parameters["capacity"] = "2"
-        self.update_stack(
-            stack_id,
-            template=template,
-            parameters=parameters
-        )
-
-        # Check number of members
-        pool_members = self.network_client.list_members(
-            pool_id=pool_resource.physical_resource_id)['members']
-        self.assertEqual(2, len(pool_members))
diff --git a/scenario/test_neutron_loadbalancer.py b/scenario/test_neutron_loadbalancer.py
deleted file mode 100644
index d8e0197..0000000
--- a/scenario/test_neutron_loadbalancer.py
+++ /dev/null
@@ -1,108 +0,0 @@
-#
-# 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
-
-from six.moves 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(self.conf.floating_network_name)
-        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.request.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.minimal_instance_type,
-            'image': self.conf.image_ref,
-            'network': self.net['name'],
-            'private_subnet_id': self.net['subnets'][0],
-            'external_network_id': self.public_net['id'],
-            'timeout': self.conf.build_timeout
-        }
-
-        # Launch stack
-        sid = self.launch_stack(
-            template_name=self.template_name,
-            parameters=parameters
-        )
-
-        stack = self.client.stacks.get(sid)
-        floating_ip = self._stack_output(stack, 'fip')
-        vip = self._stack_output(stack, 'vip')
-        server1_ip = self._stack_output(stack, 'serv1_ip')
-        server2_ip = self._stack_output(stack, 'serv2_ip')
-        # Check connection and info about received responses
-        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'})