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,