Merge "Savanna: add API client and tests for plugins"
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 12e0340..0bf604c 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -24,6 +24,7 @@
 
     @classmethod
     def setUpClass(cls):
+        cls.set_network_resources(network=True, subnet=True, router=True)
         super(ServerRescueTestJSON, cls).setUpClass()
         cls.device = 'vdf'
 
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 37b848c..e439238 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -106,7 +106,7 @@
             cls.os_alt = clients.AltManager()
             identity_client = cls._get_identity_admin_client()
             cls.alt_tenant_id = identity_client.get_tenant_by_name(
-                cls.os_alt.tenant_name)['id']
+                cls.os_alt.credentials['tenant_name'])['id']
 
         cls.alt_img_cli = cls.os_alt.image_client
 
@@ -147,7 +147,7 @@
             cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id']
         else:
             cls.os_alt = clients.AltManager()
-            alt_tenant_name = cls.os_alt.tenant_name
+            alt_tenant_name = cls.os_alt.credentials['tenant_name']
             identity_client = cls._get_identity_admin_client()
             cls.alt_tenant_id = identity_client.get_tenant_by_name(
                 alt_tenant_name)['id']
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index bfb7b48..7c02787 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -17,7 +17,7 @@
 from tempest import test
 
 
-class L3AgentSchedulerJSON(base.BaseAdminNetworkTest):
+class L3AgentSchedulerTestJSON(base.BaseAdminNetworkTest):
     _interface = 'json'
 
     """
@@ -33,7 +33,7 @@
 
     @classmethod
     def setUpClass(cls):
-        super(L3AgentSchedulerJSON, cls).setUpClass()
+        super(L3AgentSchedulerTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('l3_agent_scheduler', 'network'):
             msg = "L3 Agent Scheduler Extension not enabled."
             raise cls.skipException(msg)
@@ -61,5 +61,5 @@
         self.assertEqual(204, resp.status)
 
 
-class L3AgentSchedulerXML(L3AgentSchedulerJSON):
+class L3AgentSchedulerTestXML(L3AgentSchedulerTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index 0ce1769..d68ff1a 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -126,3 +126,21 @@
 
     def delete(self):
         self.client.delete_security_group_rule(self.id)
+
+
+class DeletablePool(DeletableResource):
+
+    def delete(self):
+        self.client.delete_pool(self.id)
+
+
+class DeletableMember(DeletableResource):
+
+    def delete(self):
+        self.client.delete_member(self.id)
+
+
+class DeletableVip(DeletableResource):
+
+    def delete(self):
+        self.client.delete_vip(self.id)
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 65eebf2..d5f2b5b 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -18,7 +18,7 @@
 from tempest import test
 
 
-class LoadBalancerJSON(base.BaseNetworkTest):
+class LoadBalancerTestJSON(base.BaseNetworkTest):
     _interface = 'json'
 
     """
@@ -39,7 +39,7 @@
 
     @classmethod
     def setUpClass(cls):
-        super(LoadBalancerJSON, cls).setUpClass()
+        super(LoadBalancerTestJSON, cls).setUpClass()
         if not test.is_extension_enabled('lbaas', 'network'):
             msg = "lbaas extension not enabled."
             raise cls.skipException(msg)
@@ -210,5 +210,5 @@
         self.assertEqual('204', resp['status'])
 
 
-class LoadBalancerXML(LoadBalancerJSON):
+class LoadBalancerTestXML(LoadBalancerTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 3aa765c..aee2a44 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -239,7 +239,7 @@
     _interface = 'xml'
 
 
-class BulkNetworkOpsJSON(base.BaseNetworkTest):
+class BulkNetworkOpsTestJSON(base.BaseNetworkTest):
     _interface = 'json'
 
     """
@@ -263,7 +263,7 @@
 
     @classmethod
     def setUpClass(cls):
-        super(BulkNetworkOpsJSON, cls).setUpClass()
+        super(BulkNetworkOpsTestJSON, cls).setUpClass()
         cls.network1 = cls.create_network()
         cls.network2 = cls.create_network()
 
@@ -390,5 +390,5 @@
             self.assertIn(n['id'], ports_list)
 
 
-class BulkNetworkOpsXML(BulkNetworkOpsJSON):
+class BulkNetworkOpsTestXML(BulkNetworkOpsTestJSON):
     _interface = 'xml'
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 59a3aeb..9465545 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -22,7 +22,7 @@
 import cinderclient.client
 import glanceclient
 import heatclient.client
-import keystoneclient.apiclient.exceptions
+import keystoneclient.exceptions
 import keystoneclient.v2_0.client
 import netaddr
 from neutronclient.common import exceptions as exc
@@ -146,7 +146,7 @@
             keystone_admin.roles.add_user_role(self.identity_client.user_id,
                                                member_role.id,
                                                self.identity_client.tenant_id)
-        except keystoneclient.apiclient.exceptions.Conflict:
+        except keystoneclient.exceptions.Conflict:
             pass
 
         return swiftclient.Connection(auth_url, username, password,
@@ -670,13 +670,17 @@
                          "Unable to determine which port to target.")
         return ports[0]['id']
 
-    def _create_floating_ip(self, server, external_network_id):
-        port_id = self._get_server_port_id(server)
+    def _create_floating_ip(self, thing, external_network_id,
+                            port_filters=None):
+        if port_filters is None:
+            port_id = self._get_server_port_id(thing)
+        else:
+            port_id = port_filters
         body = dict(
             floatingip=dict(
                 floating_network_id=external_network_id,
                 port_id=port_id,
-                tenant_id=server.tenant_id,
+                tenant_id=thing.tenant_id,
             )
         )
         result = self.network_client.create_floatingip(body=body)
@@ -713,6 +717,58 @@
         return tempest.test.call_until_true(
             ping, CONF.compute.ping_timeout, 1)
 
+    def _create_pool(self, lb_method, protocol, subnet_id):
+        """Wrapper utility that returns a test pool."""
+        name = data_utils.rand_name('pool-')
+        body = {
+            "pool": {
+                "protocol": protocol,
+                "name": name,
+                "subnet_id": subnet_id,
+                "lb_method": lb_method
+            }
+        }
+        resp = self.network_client.create_pool(body=body)
+        pool = net_common.DeletablePool(client=self.network_client,
+                                        **resp['pool'])
+        self.assertEqual(pool['name'], name)
+        self.set_resource(name, pool)
+        return pool
+
+    def _create_member(self, address, protocol_port, pool_id):
+        """Wrapper utility that returns a test member."""
+        body = {
+            "member": {
+                "protocol_port": protocol_port,
+                "pool_id": pool_id,
+                "address": address
+            }
+        }
+        resp = self.network_client.create_member(body)
+        member = net_common.DeletableMember(client=self.network_client,
+                                            **resp['member'])
+        self.set_resource(data_utils.rand_name('member-'), member)
+        return member
+
+    def _create_vip(self, protocol, protocol_port, subnet_id, pool_id):
+        """Wrapper utility that returns a test vip."""
+        name = data_utils.rand_name('vip-')
+        body = {
+            "vip": {
+                "protocol": protocol,
+                "name": name,
+                "subnet_id": subnet_id,
+                "pool_id": pool_id,
+                "protocol_port": protocol_port
+            }
+        }
+        resp = self.network_client.create_vip(body)
+        vip = net_common.DeletableVip(client=self.network_client,
+                                      **resp['vip'])
+        self.assertEqual(vip['name'], name)
+        self.set_resource(name, vip)
+        return vip
+
     def _check_vm_connectivity(self, ip_address,
                                username=None,
                                private_key=None,
diff --git a/tempest/scenario/test_load_balancer_basic.py b/tempest/scenario/test_load_balancer_basic.py
new file mode 100644
index 0000000..68f6e62
--- /dev/null
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -0,0 +1,240 @@
+# Copyright 2014 Mirantis.inc
+# 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.
+
+import time
+import urllib
+
+from tempest.api.network import common as net_common
+from tempest.common import ssh
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest.scenario import manager
+from tempest import test
+
+config = config.CONF
+
+
+class TestLoadBalancerBasic(manager.NetworkScenarioTest):
+
+    """
+    This test checks basic load balancing.
+
+    The following is the scenario outline:
+    1. Create an instance
+    2. SSH to the instance and start two servers
+    3. Create a load balancer with two members and with ROUND_ROBIN algorithm
+       associate the VIP with a floating ip
+    4. Send 10 requests to the floating ip and check that they are shared
+       between the two servers and that both of them get equal portions
+    of the requests
+    """
+
+    @classmethod
+    def check_preconditions(cls):
+        super(TestLoadBalancerBasic, cls).check_preconditions()
+        cfg = config.network
+        if not test.is_extension_enabled('lbaas', 'network'):
+            msg = 'LBaaS Extension is not enabled'
+            cls.enabled = False
+            raise cls.skipException(msg)
+        if not (cfg.tenant_networks_reachable or cfg.public_network_id):
+            msg = ('Either tenant_networks_reachable must be "true", or '
+                   'public_network_id must be defined.')
+            cls.enabled = False
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestLoadBalancerBasic, cls).setUpClass()
+        cls.check_preconditions()
+        cls.security_groups = {}
+        cls.networks = []
+        cls.subnets = []
+        cls.servers_keypairs = {}
+        cls.pools = []
+        cls.members = []
+        cls.vips = []
+        cls.floating_ips = {}
+        cls.port1 = 80
+        cls.port2 = 88
+
+    def _create_security_groups(self):
+        self.security_groups[self.tenant_id] =\
+            self._create_security_group_neutron(tenant_id=self.tenant_id)
+
+    def _create_server(self):
+        tenant_id = self.tenant_id
+        name = data_utils.rand_name("smoke_server-")
+        keypair = self.create_keypair(name='keypair-%s' % name)
+        security_groups = [self.security_groups[tenant_id].name]
+        nets = self.network_client.list_networks()
+        for net in nets['networks']:
+            if net['tenant_id'] == self.tenant_id:
+                self.networks.append(net)
+                create_kwargs = {
+                    'nics': [
+                        {'net-id': net['id']},
+                    ],
+                    'key_name': keypair.name,
+                    'security_groups': security_groups,
+                }
+                server = self.create_server(name=name,
+                                            create_kwargs=create_kwargs)
+                self.servers_keypairs[server] = keypair
+                break
+        self.assertTrue(self.servers_keypairs)
+
+    def _start_servers(self):
+        """
+        1. SSH to the instance
+        2. Start two servers listening on ports 80 and 88 respectively
+        """
+        for server in self.servers_keypairs.keys():
+            ssh_login = config.compute.image_ssh_user
+            private_key = self.servers_keypairs[server].private_key
+            network_name = self.networks[0]['name']
+
+            ip_address = server.networks[network_name][0]
+            ssh_client = ssh.Client(ip_address, ssh_login,
+                                    pkey=private_key,
+                                    timeout=100)
+            start_server = "while true; do echo -e 'HTTP/1.0 200 OK\r\n\r\n" \
+                           "%(server)s' | sudo nc -l -p %(port)s ; done &"
+            cmd = start_server % {'server': 'server1',
+                                  'port': self.port1}
+            ssh_client.exec_command(cmd)
+            cmd = start_server % {'server': 'server2',
+                                  'port': self.port2}
+            ssh_client.exec_command(cmd)
+
+    def _check_connection(self, check_ip):
+        def try_connect(ip):
+            try:
+                urllib.urlopen("http://{0}/".format(ip))
+                return True
+            except IOError:
+                return False
+        timeout = config.compute.ping_timeout
+        timer = 0
+        while not try_connect(check_ip):
+            time.sleep(1)
+            timer += 1
+            if timer >= timeout:
+                message = "Timed out trying to connect to %s" % check_ip
+                raise exceptions.TimeoutException(message)
+
+    def _create_pool(self):
+        """Create a pool with ROUND_ROBIN algorithm."""
+        subnets = self.network_client.list_subnets()
+        for subnet in subnets['subnets']:
+            if subnet['tenant_id'] == self.tenant_id:
+                self.subnets.append(subnet)
+                pool = super(TestLoadBalancerBasic, self)._create_pool(
+                    'ROUND_ROBIN',
+                    'HTTP',
+                    subnet['id'])
+                self.pools.append(pool)
+                break
+        self.assertTrue(self.pools)
+
+    def _create_members(self, network_name, server_ids):
+        """
+        Create two members.
+
+        In case there is only one server, create both members with the same ip
+        but with different ports to listen on.
+        """
+        servers = self.compute_client.servers.list()
+        for server in servers:
+            if server.id in server_ids:
+                ip = server.networks[network_name][0]
+                pool_id = self.pools[0]['id']
+                if len(set(server_ids)) == 1 or len(servers) == 1:
+                    member1 = self._create_member(ip, self.port1, pool_id)
+                    member2 = self._create_member(ip, self.port2, pool_id)
+                    self.members.extend([member1, member2])
+                else:
+                    member = self._create_member(ip, self.port1, pool_id)
+                    self.members.append(member)
+        self.assertTrue(self.members)
+
+    def _assign_floating_ip_to_vip(self, vip):
+        public_network_id = config.network.public_network_id
+        port_id = vip['port_id']
+        floating_ip = self._create_floating_ip(vip,
+                                               public_network_id,
+                                               port_filters=port_id)
+        self.floating_ips.setdefault(vip['id'], [])
+        self.floating_ips[vip['id']].append(floating_ip)
+
+    def _create_load_balancer(self):
+        self._create_pool()
+        self._create_members(self.networks[0]['name'],
+                             [self.servers_keypairs.keys()[0].id])
+        subnet_id = self.subnets[0]['id']
+        pool_id = self.pools[0]['id']
+        vip = super(TestLoadBalancerBasic, self)._create_vip('HTTP', 80,
+                                                             subnet_id,
+                                                             pool_id)
+        self.vips.append(vip)
+        self._status_timeout(NeutronRetriever(self.network_client,
+                                              self.network_client.vip_path,
+                                              net_common.DeletableVip),
+                             self.vips[0]['id'],
+                             expected_status='ACTIVE')
+        self._assign_floating_ip_to_vip(self.vips[0])
+
+    def _check_load_balancing(self):
+        """
+        1. Send 10 requests on the floating ip associated with the VIP
+        2. Check that the requests are shared between
+           the two servers and that both of them get equal portions
+           of the requests
+        """
+
+        vip = self.vips[0]
+        floating_ip_vip = self.floating_ips[
+            vip['id']][0]['floating_ip_address']
+        self._check_connection(floating_ip_vip)
+        resp = []
+        for count in range(10):
+            resp.append(
+                urllib.urlopen(
+                    "http://{0}/".format(floating_ip_vip)).read())
+        self.assertEqual(set(["server1\n", "server2\n"]), set(resp))
+        self.assertEqual(5, resp.count("server1\n"))
+        self.assertEqual(5, resp.count("server2\n"))
+
+    @test.skip_because(bug="1277381")
+    @test.attr(type='smoke')
+    @test.services('compute', 'network')
+    def test_load_balancer_basic(self):
+        self._create_security_groups()
+        self._create_server()
+        self._start_servers()
+        self._create_load_balancer()
+        self._check_load_balancing()
+
+
+class NeutronRetriever(object):
+    def __init__(self, network_client, path, resource):
+        self.network_client = network_client
+        self.path = path
+        self.resource = resource
+
+    def get(self, thing_id):
+        obj = self.network_client.get(self.path % thing_id)
+        return self.resource(client=self.network_client, **obj.values()[0])
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index 0a16b9f..b70b2e8 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -177,7 +177,7 @@
             stack_name = body['stack_name']
             stack_status = body['stack_status']
             if stack_status == status:
-                return
+                return body
             if fail_regexp.search(stack_status):
                 raise exceptions.StackBuildErrorException(
                     stack_identifier=stack_identifier,