Commit 5e01cbb5 authored by Ilias Tsitsimpis's avatar Ilias Tsitsimpis
Browse files

burnin: Implement the new Network API

parent 0eb83d97
......@@ -48,7 +48,7 @@ from synnefo_tools.burnin.pithos_tests import PithosTestSuite
from synnefo_tools.burnin.server_tests import ServerTestSuite
from synnefo_tools.burnin.network_tests import NetworkTestSuite
from synnefo_tools.burnin.stale_tests import \
StaleServersTestSuite, StaleNetworksTestSuite
StaleServersTestSuite, StaleFloatingIPsTestSuite, StaleNetworksTestSuite
# --------------------------------------------------------------------
......@@ -66,6 +66,7 @@ TSUITES_NAMES = [tsuite.__name__ for tsuite in TESTSUITES]
STALE_TESTSUITES = [
# Must be runned in this order
StaleServersTestSuite,
StaleFloatingIPsTestSuite,
StaleNetworksTestSuite,
]
STALE_TSUITES_NAMES = [tsuite.__name__ for tsuite in STALE_TESTSUITES]
......@@ -113,12 +114,12 @@ def parse_arguments(args):
help="Disable IPv6 related tests")
parser.add_option(
"--action-timeout", action="store",
type="int", default=300, dest="action_timeout", metavar="TIMEOUT",
type="int", default=420, dest="action_timeout", metavar="TIMEOUT",
help="Wait TIMEOUT seconds for a server action to complete, "
"then the test is considered failed")
parser.add_option(
"--action-warning", action="store",
type="int", default=120, dest="action_warning", metavar="TIMEOUT",
type="int", default=180, dest="action_warning", metavar="TIMEOUT",
help="Warn if TIMEOUT seconds have passed and a server action "
"has not been completed yet")
parser.add_option(
......
......@@ -44,7 +44,7 @@ import datetime
import tempfile
import traceback
from kamaki.clients.cyclades import CycladesClient
from kamaki.clients.cyclades import CycladesClient, CycladesNetworkClient
from kamaki.clients.astakos import AstakosClient
from kamaki.clients.compute import ComputeClient
from kamaki.clients.pithos import PithosClient
......@@ -122,6 +122,9 @@ class Clients(object):
compute_url = None
# Cyclades
cyclades = None
# Network
network = None
network_url = None
# Pithos
pithos = None
pithos_url = None
......@@ -142,6 +145,11 @@ class Clients(object):
self.cyclades = CycladesClient(self.compute_url, self.token)
self.cyclades.CONNECTION_RETRY_LIMIT = self.retry
self.network_url = \
self.astakos.get_service_endpoints('network')['publicURL']
self.network = CycladesNetworkClient(self.network_url, self.token)
self.network.CONNECTION_RETRY_LIMIT = self.retry
self.pithos_url = self.astakos.\
get_service_endpoints('object-store')['publicURL']
self.pithos = PithosClient(self.pithos_url, self.token)
......@@ -204,6 +212,7 @@ class BurninTests(unittest.TestCase):
self.clients.initialize_clients()
self.info("Astakos auth url is %s", self.clients.auth_url)
self.info("Cyclades url is %s", self.clients.compute_url)
self.info("Network url is %s", self.clients.network_url)
self.info("Pithos url is %s", self.clients.pithos_url)
self.info("Image url is %s", self.clients.image_url)
......@@ -216,6 +225,8 @@ class BurninTests(unittest.TestCase):
self.quotas['system']['pithos.diskspace']['usage'])
self.info(" Ram usage is %s bytes",
self.quotas['system']['cyclades.ram']['usage'])
self.info(" Floating IPs usage is %s",
self.quotas['system']['cyclades.floating_ip']['usage'])
self.info(" CPU usage is %s",
self.quotas['system']['cyclades.cpu']['usage'])
self.info(" Network usage is %s",
......@@ -490,10 +501,10 @@ class BurninTests(unittest.TestCase):
# Invalid argument name. pylint: disable-msg=C0103
# Too many arguments. pylint: disable-msg=R0913
def _check_quotas(self, disk=None, vm=None, diskspace=None,
ram=None, cpu=None, network=None):
ram=None, ip=None, cpu=None, network=None):
"""Check that quotas' changes are consistent"""
assert any(v is None for v in
[disk, vm, diskspace, ram, cpu, network]), \
[disk, vm, diskspace, ram, ip, cpu, network]), \
"_check_quotas require arguments"
self.info("Check that quotas' changes are consistent")
......@@ -513,6 +524,9 @@ class BurninTests(unittest.TestCase):
# Check Ram usage
self._check_quotas_aux(
old_quotas, new_quotas, 'cyclades.ram', ram)
# Check Floating IPs usage
self._check_quotas_aux(
old_quotas, new_quotas, 'cyclades.floating_ip', ip)
# Check CPU usage
self._check_quotas_aux(
old_quotas, new_quotas, 'cyclades.cpu', cpu)
......
......@@ -39,6 +39,7 @@ had grown too much.
"""
import time
import IPy
import base64
import socket
import random
......@@ -46,6 +47,8 @@ import paramiko
import tempfile
import subprocess
from kamaki.clients import ClientError
from synnefo_tools.burnin.common import BurninTests, MB, GB
......@@ -101,7 +104,7 @@ class CycladesTests(BurninTests):
self.info("Getting detailed list of networks")
else:
self.info("Getting simple list of networks")
return self.clients.cyclades.list_networks(detail=detail)
return self.clients.network.list_networks(detail=detail)
def _get_server_details(self, server, quiet=False):
"""Get details for a server"""
......@@ -110,14 +113,23 @@ class CycladesTests(BurninTests):
server['name'], server['id'])
return self.clients.cyclades.get_server_details(server['id'])
def _create_server(self, image, flavor, personality=None):
def _create_server(self, image, flavor, personality=None, network=False):
"""Create a new server"""
if network:
fip = self._create_floating_ip()
port = self._create_port(fip['floating_network_id'],
floating_ip=fip)
networks = [{'port': port['id']}]
else:
networks = None
servername = "%s for %s" % (self.run_id, image['name'])
self.info("Creating a server with name %s", servername)
self.info("Using image %s with id %s", image['name'], image['id'])
self.info("Using flavor %s with id %s", flavor['name'], flavor['id'])
server = self.clients.cyclades.create_server(
servername, flavor['id'], image['id'], personality=personality)
servername, flavor['id'], image['id'],
personality=personality, networks=networks)
self.info("Server id: %s", server['id'])
self.info("Server password: %s", server['adminPass'])
......@@ -135,6 +147,40 @@ class CycladesTests(BurninTests):
return server
def _delete_servers(self, servers, error=False):
"""Deleting a number of servers in parallel"""
# Disconnect floating IPs
for srv in servers:
self.info("Disconnecting all floating IPs from server with id %s",
srv['id'])
self._disconnect_from_network(srv)
# Delete servers
for srv in servers:
self.info("Sending the delete request for server with id %s",
srv['id'])
self.clients.cyclades.delete_server(srv['id'])
if error:
curr_states = ["ACTIVE", "ERROR", "STOPPED", "BUILD"]
else:
curr_states = ["ACTIVE"]
for srv in servers:
self._insist_on_server_transition(srv, curr_states, "DELETED")
# Servers no longer in server list
new_servers = [s['id'] for s in self._get_list_of_servers()]
for srv in servers:
self.info("Verifying that server with id %s is no longer in "
"server list", srv['id'])
self.assertNotIn(srv['id'], new_servers)
# Verify quotas
flavors = \
[self.clients.compute.get_flavor_details(srv['flavor']['id'])
for srv in servers]
self._verify_quotas_deleted(flavors)
def _verify_quotas_deleted(self, flavors):
"""Verify quotas for a number of deleted servers"""
used_disk = 0
......@@ -199,7 +245,7 @@ class CycladesTests(BurninTests):
"""Insist on network transiting from curr_statuses to new_status"""
def check_fun():
"""Check network status"""
ntw = self.clients.cyclades.get_network_details(network['id'])
ntw = self.clients.network.get_network_details(network['id'])
if ntw['status'] in curr_statuses:
raise Retry()
elif ntw['status'] == new_status:
......@@ -214,25 +260,6 @@ class CycladesTests(BurninTests):
opmsg = opmsg % (network['name'], network['id'], new_status)
self._try_until_timeout_expires(opmsg, check_fun)
def _insist_on_network_connection(self, server, network, disconnect=False):
"""Insist that the server has connected to the network"""
def check_fun():
"""Check network connection"""
dsrv = self._get_server_details(server, quiet=True)
nets = [s['network_id'] for s in dsrv['attachments']]
if not disconnect and network['id'] not in nets:
raise Retry()
if disconnect and network['id'] in nets:
raise Retry()
if disconnect:
opmsg = \
"Waiting for server \"%s\" to disconnect from network \"%s\""
else:
opmsg = "Waiting for server \"%s\" to connect to network \"%s\""
self.info(opmsg, server['name'], network['name'])
opmsg = opmsg % (server['name'], network['name'])
self._try_until_timeout_expires(opmsg, check_fun)
def _insist_on_tcp_connection(self, family, host, port):
"""Insist on tcp connection"""
def check_fun():
......@@ -263,37 +290,42 @@ class CycladesTests(BurninTests):
opmsg = opmsg % (familystr.get(family, "Unknown"), host, port)
return self._try_until_timeout_expires(opmsg, check_fun)
def _get_ip(self, server, version=4, network=None):
"""Get the IP of a server from the detailed server info
def _get_ips(self, server, version=4, network=None):
"""Get the IPs of a server from the detailed server info
If network not given then get the public IP. Else the ip
If network not given then get the public IPs. Else the IPs
attached to that network
"""
assert version in (4, 6)
nics = server['attachments']
addrs = None
addrs = []
for nic in nics:
net_id = nic['network_id']
if network is None:
if self.clients.cyclades.get_network_details(net_id)['public']:
if self.clients.network.get_network_details(net_id)['public']:
if nic['ipv' + str(version)]:
addrs = nic['ipv' + str(version)]
break
addrs.append(nic['ipv' + str(version)])
else:
if net_id == network['id']:
if nic['ipv' + str(version)]:
addrs = nic['ipv' + str(version)]
break
addrs.append(nic['ipv' + str(version)])
self.assertGreater(len(addrs), 0,
"Can not get IPs from server attachments")
for addr in addrs:
self.assertEquals(IPy.IP(addr).version(), version)
self.assertIsNotNone(addrs, "Can not get IP from server attachments")
if network is None:
msg = "Server's public IPv%s is %s"
self.info(msg, version, addrs)
for addr in addrs:
self.info(msg, version, addr)
else:
msg = "Server's IPv%s attached to network \"%s\" is %s"
self.info(msg, version, network['id'], addrs)
for addr in addrs:
self.info(msg, version, network['id'], addr)
return addrs
def _insist_on_ping(self, ip_addr, version=4):
......@@ -372,21 +404,17 @@ class CycladesTests(BurninTests):
remote_content = base64.b64encode(ftmp.read())
self.assertEqual(content, remote_content)
def _disconnect_from_network(self, server, network):
"""Disconnect server from network"""
nid = None
for nic in server['attachments']:
if nic['network_id'] == network['id']:
nid = nic['id']
break
self.assertIsNotNone(nid, "Could not find network card")
self.clients.cyclades.disconnect_server(server['id'], nid)
def _create_network(self, name, cidr="10.0.1.0/28", dhcp=True):
# ----------------------------------
# Networks
def _create_network(self, cidr="10.0.1.0/28", dhcp=True):
"""Create a new private network"""
network = self.clients.cyclades.create_network(
name, cidr=cidr, dhcp=dhcp)
name = self.run_id
network = self.clients.network.create_network(
"MAC_FILTERED", name=name, shared=False)
self.info("Network with id %s created", network['id'])
subnet = self.clients.network.create_subnet(
network['id'], cidr=cidr, enable_dhcp=dhcp)
self.info("Subnet with id %s created", subnet['id'])
# Verify quotas
self._check_quotas(network=+1)
......@@ -396,6 +424,152 @@ class CycladesTests(BurninTests):
return network
def _delete_networks(self, networks, error=False):
"""Delete a network"""
for net in networks:
self.info("Deleting network with id %s", net['id'])
self.clients.network.delete_network(net['id'])
if error:
curr_states = ["ACTIVE", "SNF:DRAINED", "ERROR"]
else:
curr_states = ["ACTIVE", "SNF:DRAINED"]
for net in networks:
self._insist_on_network_transition(net, curr_states, "DELETED")
# Networks no longer in network list
new_networks = [n['id'] for n in self._get_list_of_networks()]
for net in networks:
self.info("Verifying that network with id %s is no longer in "
"network list", net['id'])
self.assertNotIn(net['id'], new_networks)
# Verify quotas
self._check_quotas(network=-len(networks))
def _get_public_network(self, networks=None):
"""Get the public network"""
if networks is None:
networks = self._get_list_of_networks(detail=True)
self.info("Getting the public network")
for net in networks:
if net['SNF:floating_ip_pool'] and net['public']:
return net
self.fail("Could not find a public network to use")
def _create_floating_ip(self):
"""Create a new floating ip"""
pub_net = self._get_public_network()
self.info("Creating a new floating ip for network with id %s",
pub_net['id'])
fip = self.clients.network.create_floatingip(pub_net['id'])
# Verify that floating ip has been created
fips = self.clients.network.list_floatingips()
fips = [f['id'] for f in fips]
self.assertIn(fip['id'], fips)
# Verify quotas
self._check_quotas(ip=+1)
# Check that IP is IPv4
self.assertEquals(IPy.IP(fip['floating_ip_address']).version(), 4)
self.info("Floating IP %s with id %s created",
fip['floating_ip_address'], fip['id'])
return fip
def _create_port(self, network_id, device_id=None, floating_ip=None):
"""Create a new port attached to the a specific network"""
self.info("Creating a new port to network with id %s", network_id)
if floating_ip is not None:
fixed_ips = [{'ip_address': floating_ip['floating_ip_address']}]
else:
fixed_ips = None
port = self.clients.network.create_port(network_id,
device_id=device_id,
fixed_ips=fixed_ips)
# Verify that port created
ports = self.clients.network.list_ports()
ports = [p['id'] for p in ports]
self.assertIn(port['id'], ports)
# Insist on creation
if device_id is None:
self._insist_on_port_transition(port, ["BUILD"], "DOWN")
else:
self._insist_on_port_transition(port, ["BUILD", "DOWN"], "ACTIVE")
self.info("Port with id %s created", port['id'])
return port
def _insist_on_port_transition(self, port, curr_statuses, new_status):
"""Insist on port transiting from curr_statuses to new_status"""
def check_fun():
"""Check port status"""
portd = self.clients.network.get_port_details(port['id'])
if portd['status'] in curr_statuses:
raise Retry()
elif portd['status'] == new_status:
return
else:
msg = "Port %s went to unexpected status %s"
self.fail(msg % (portd['id'], portd['status']))
opmsg = "Waiting for port %s to become %s"
self.info(opmsg, port['id'], new_status)
opmsg = opmsg % (port['id'], new_status)
self._try_until_timeout_expires(opmsg, check_fun)
def _insist_on_port_deletion(self, portid):
"""Insist on port deletion"""
def check_fun():
"""Check port details"""
try:
self.clients.network.get_port_details(portid)
except ClientError as err:
if err.status != 404:
raise
else:
raise Retry()
opmsg = "Waiting for port %s to be deleted"
self.info(opmsg, portid)
opmsg = opmsg % portid
self._try_until_timeout_expires(opmsg, check_fun)
def _disconnect_from_network(self, server, network=None):
"""Disconnnect server from network"""
if network is None:
# Disconnect from public network
network = self._get_public_network()
lports = self.clients.network.list_ports()
ports = []
for port in lports:
dport = self.clients.network.get_port_details(port['id'])
if str(dport['network_id']) == str(network['id']) \
and str(dport['device_id']) == str(server['id']):
ports.append(dport)
# Find floating IPs attached to these ports
ports_id = [p['id'] for p in ports]
fips = [f for f in self.clients.network.list_floatingips()
if str(f['port_id']) in ports_id]
# First destroy the ports
for port in ports:
self.info("Destroying port with id %s", port['id'])
self.clients.network.delete_port(port['id'])
self._insist_on_port_deletion(port['id'])
# Then delete the floating IPs
for fip in fips:
self.info("Destroying floating IP %s with id %s",
fip['floating_ip_address'], fip['id'])
self.clients.network.delete_floatingip(fip['id'])
# Check that floating IPs have been deleted
list_ips = [f['id'] for f in self.clients.network.list_floatingips()]
for fip in fips:
self.assertNotIn(fip['id'], list_ips)
# Verify quotas
self._check_quotas(ip=-len(fips))
class Retry(Exception):
"""Retry the action
......
......@@ -79,7 +79,7 @@ class NetworkTestSuite(CycladesTests):
"""Submit create server request for server A"""
use_image = random.choice(self.avail_images)
use_flavor = random.choice(self.avail_flavors)
server = self._create_server(use_image, use_flavor)
server = self._create_server(use_image, use_flavor, network=True)
self.server_a = {}
self.server_a['server'] = server
......@@ -92,7 +92,7 @@ class NetworkTestSuite(CycladesTests):
"""Submit create server request for server B"""
use_image = random.choice(self.avail_images)
use_flavor = random.choice(self.avail_flavors)
server = self._create_server(use_image, use_flavor)
server = self._create_server(use_image, use_flavor, network=True)
self.server_b = {}
self.server_b['server'] = server
......@@ -113,23 +113,15 @@ class NetworkTestSuite(CycladesTests):
def test_006_create_network(self):
"""Submit a create network request"""
name = self.run_id
self.network = self._create_network(name)
self.network = self._create_network()
self._insist_on_network_transition(
self.network, ["BUILD"], "ACTIVE")
def test_007_connect_to_network(self):
"""Connect the two VMs to the newly created network"""
self.clients.cyclades.connect_server(
self.server_a['server']['id'], self.network['id'])
self.clients.cyclades.connect_server(
self.server_b['server']['id'], self.network['id'])
self._insist_on_network_connection(
self.server_a['server'], self.network)
self._insist_on_network_connection(
self.server_b['server'], self.network)
self._create_port(self.network['id'], self.server_a['server']['id'])
self._create_port(self.network['id'], self.server_b['server']['id'])
# Update servers
self.server_a['server'] = self._get_server_details(
......@@ -138,10 +130,12 @@ class NetworkTestSuite(CycladesTests):
self.server_b['server'])
# Check that servers got private IPs
self.server_a['pr_ipv4'] = self._get_ip(
self.server_a['server'], network=self.network)
self.server_b['pr_ipv4'] = self._get_ip(
self.server_b['server'], network=self.network)
ipv4 = self._get_ips(self.server_a['server'], network=self.network)
self.assertEqual(len(ipv4), 1)
self.server_a['pr_ipv4'] = ipv4[0]
ipv4 = self._get_ips(self.server_b['server'], network=self.network)
self.assertEqual(len(ipv4), 1)
self.server_b['pr_ipv4'] = ipv4
def test_008_reboot_server_a(self):
"""Rebooting server A"""
......@@ -155,7 +149,7 @@ class NetworkTestSuite(CycladesTests):
def test_009_ping_server_a(self):
"""Test if server A responds to IPv4 pings"""
self._insist_on_ping(self._get_ip(self.server_a['server']))
self._insist_on_ping(self._get_ips(self.server_a['server'])[0])
def test_010_reboot_server_b(self):
"""Rebooting server B"""
......@@ -169,7 +163,7 @@ class NetworkTestSuite(CycladesTests):
def test_011_ping_server_b(self):
"""Test that server B responds to IPv4 pings"""
self._insist_on_ping(self._get_ip(self.server_b['server']))
self._insist_on_ping(self._get_ips(self.server_b['server'])[0])
def test_012_test_connection_exists(self):
"""Ping server B from server A to test if connection exists"""
......@@ -178,9 +172,9 @@ class NetworkTestSuite(CycladesTests):
self._skip_if(not self._image_is(self.server_b['image'], "linux"),
"only valid for Linux servers")
server_a_public_ip = self._get_ip(self.server_a['server'])
server_b_private_ip = self._get_ip(
self.server_b['server'], network=self.network)
server_a_public_ip = self._get_ips(self.server_a['server'])[0]
server_b_private_ip = self._get_ips(
self.server_b['server'], network=self.network)[0]
msg = "Will try to connect to server A (%s) and ping to server B (%s)"
self.info(msg, server_a_public_ip, server_b_private_ip)
......@@ -198,33 +192,11 @@ class NetworkTestSuite(CycladesTests):
self._disconnect_from_network(self.server_a['server'], self.network)
self._disconnect_from_network(self.server_b['server'], self.network)
self._insist_on_network_connection(
self.server_a['server'], self.network, disconnect=True)
self._insist_on_network_connection(
self.server_b['server'], self.network, disconnect=True)
def test_014_destroy_network(self):
"""Submit delete network request"""
self.clients.cyclades.delete_network(self.network['id'])
self._insist_on_network_transition(
self.network, ["ACTIVE"], "DELETED")
networks = [net['id'] for net in self._get_list_of_networks()]
self.assertNotIn(self.network['id'], networks)
# Verify quotas
self._check_quotas(network=-1)
self._delete_networks([self.network])
def test_015_cleanup_servers(self):
"""Cleanup servers created for this test"""
self.clients.cyclades.delete_server(self.server_a['server']['id'])
self.clients.cyclades.delete_server(self.server_b['server']['id'])
self._insist_on_server_transition(
self.server_a['server'], ["ACTIVE"], "DELETED")
self._insist_on_server_transition(
self.server_b['server'], ["ACTIVE"], "DELETED")
# Verify quotas
self._verify_quotas_deleted([self.server_a['flavor'],
self.server_b['flavor']])
self._delete_server