commit 60a80953201aae84bc0fafe0350e33d67a100311
Author: Ilias Tsitsimpis <iliastsi@grnet.gr>
Date:   Tue Nov 26 12:48:30 2013 +0200

    burnin: Implement the new Network API

diff --git a/snf-tools/synnefo_tools/burnin/__init__.py b/snf-tools/synnefo_tools/burnin/__init__.py
index d5276e0..6b35ba8 100644
--- a/snf-tools/synnefo_tools/burnin/__init__.py
+++ b/snf-tools/synnefo_tools/burnin/__init__.py
@@ -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(
diff --git a/snf-tools/synnefo_tools/burnin/common.py b/snf-tools/synnefo_tools/burnin/common.py
index faaa31c..0d0ce8a 100644
--- a/snf-tools/synnefo_tools/burnin/common.py
+++ b/snf-tools/synnefo_tools/burnin/common.py
@@ -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)
diff --git a/snf-tools/synnefo_tools/burnin/cyclades_common.py b/snf-tools/synnefo_tools/burnin/cyclades_common.py
index 8be7d6c..d9784b3 100644
--- a/snf-tools/synnefo_tools/burnin/cyclades_common.py
+++ b/snf-tools/synnefo_tools/burnin/cyclades_common.py
@@ -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
diff --git a/snf-tools/synnefo_tools/burnin/network_tests.py b/snf-tools/synnefo_tools/burnin/network_tests.py
index c5c1e64..69387cb 100644
--- a/snf-tools/synnefo_tools/burnin/network_tests.py
+++ b/snf-tools/synnefo_tools/burnin/network_tests.py
@@ -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_servers([self.server_a['server'],
+                              self.server_b['server']])
diff --git a/snf-tools/synnefo_tools/burnin/server_tests.py b/snf-tools/synnefo_tools/burnin/server_tests.py
index 2379b19..be0048f 100644
--- a/snf-tools/synnefo_tools/burnin/server_tests.py
+++ b/snf-tools/synnefo_tools/burnin/server_tests.py
@@ -37,7 +37,6 @@ This is the burnin class that tests the Servers' functionality
 """
 
 import sys
-import IPy
 import stat
 import base64
 import random
@@ -50,6 +49,7 @@ from synnefo_tools.burnin.cyclades_common import CycladesTests
 
 
 # Too many public methods. pylint: disable-msg=R0904
+# Too many instance attributes. pylint: disable-msg=R0902
 # This class gets replicated into actual TestCases dynamically
 class GeneratedServerTestSuite(CycladesTests):
     """Test Spawning Serverfunctionality"""
@@ -78,7 +78,8 @@ class GeneratedServerTestSuite(CycladesTests):
         self.use_flavor = random.choice(self.avail_flavors)
 
         self.server = self._create_server(
-            self.use_image, self.use_flavor, self.personality)
+            self.use_image, self.use_flavor,
+            personality=self.personality, network=True)
         self.username = self._get_connection_username(self.server)
         self.password = self.server['adminPass']
 
@@ -119,7 +120,14 @@ class GeneratedServerTestSuite(CycladesTests):
         """Test server becomes ACTIVE"""
         self._insist_on_server_transition(self.server, ["BUILD"], "ACTIVE")
 
-    def test_006_get_server_oob_console(self):
+    def test_006_attach_second_network(self):
+        """Attach a second public IP to our server"""
+        floating_ip = self._create_floating_ip()
+        self._create_port(floating_ip['floating_network_id'],
+                          device_id=self.server['id'],
+                          floating_ip=floating_ip)
+
+    def test_007_get_server_oob_console(self):
         """Test getting OOB server console over VNC
 
         Implementation of RFB protocol follows
@@ -157,94 +165,98 @@ class GeneratedServerTestSuite(CycladesTests):
         self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
         sock.close()
 
-    def test_007_server_has_ipv4(self):
+    def test_008_server_has_ipv4(self):
         """Test active server has a valid IPv4 address"""
         server = self.clients.cyclades.get_server_details(self.server['id'])
         # Update the server attribute
         self.server = server
 
-        self.ipv4 = self._get_ip(server, version=4)
-        self.assertEquals(IPy.IP(self.ipv4).version(), 4)
+        self.ipv4 = self._get_ips(server, version=4)
 
-    def test_008_server_has_ipv6(self):
+    def test_009_server_has_ipv6(self):
         """Test active server has a valid IPv6 address"""
         self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
 
-        self.ipv6 = self._get_ip(self.server, version=6)
-        self.assertEquals(IPy.IP(self.ipv6).version(), 6)
+        self.ipv6 = self._get_ips(self.server, version=6)
 
-    def test_009_server_ping_ipv4(self):
+    def test_010_server_ping_ipv4(self):
         """Test server responds to ping on IPv4 address"""
-        self._insist_on_ping(self.ipv4, version=4)
+        self._insist_on_ping(self.ipv4[0], version=4)
+        self._insist_on_ping(self.ipv4[1], version=4)
 
-    def test_010_server_ping_ipv6(self):
+    def test_011_server_ping_ipv6(self):
         """Test server responds to ping on IPv6 address"""
         self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
-        self._insist_on_ping(self.ipv6, version=6)
+        self._insist_on_ping(self.ipv6[0], version=6)
 
-    def test_011_submit_shutdown(self):
+    def test_012_submit_shutdown(self):
         """Test submit request to shutdown server"""
         self.clients.cyclades.shutdown_server(self.server['id'])
 
-    def test_012_server_becomes_stopped(self):
+    def test_013_server_becomes_stopped(self):
         """Test server becomes STOPPED"""
         self._insist_on_server_transition(self.server, ["ACTIVE"], "STOPPED")
 
-    def test_013_submit_start(self):
+    def test_014_submit_start(self):
         """Test submit start server request"""
         self.clients.cyclades.start_server(self.server['id'])
 
-    def test_014_server_becomes_active(self):
+    def test_015_server_becomes_active(self):
         """Test server becomes ACTIVE again"""
         self._insist_on_server_transition(self.server, ["STOPPED"], "ACTIVE")
 
-    def test_015_server_ping_ipv4(self):
+    def test_016_server_ping_ipv4(self):
         """Test server OS is actually up and running again"""
-        self.test_009_server_ping_ipv4()
+        self.test_010_server_ping_ipv4()
 
-    def test_016_ssh_to_server_ipv4(self):
+    def test_017_ssh_to_server_ipv4(self):
         """Test SSH to server public IPv4 works, verify hostname"""
         self._skip_if(not self._image_is(self.use_image, "linux"),
                       "only valid for Linux servers")
-        hostname = self._insist_get_hostname_over_ssh(
-            self.ipv4, self.username, self.password)
+        hostname1 = self._insist_get_hostname_over_ssh(
+            self.ipv4[0], self.username, self.password)
+        hostname2 = self._insist_get_hostname_over_ssh(
+            self.ipv4[1], self.username, self.password)
         # The hostname must be of the form 'prefix-id'
-        self.assertTrue(hostname.endswith("-%d" % self.server['id']))
+        self.assertTrue(hostname1.endswith("-%d" % self.server['id']))
+        self.assertEqual(hostname1, hostname2)
 
-    def test_017_ssh_to_server_ipv6(self):
+    def test_018_ssh_to_server_ipv6(self):
         """Test SSH to server public IPv6 works, verify hostname"""
         self._skip_if(not self._image_is(self.use_image, "linux"),
                       "only valid for Linux servers")
         self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
         hostname = self._insist_get_hostname_over_ssh(
-            self.ipv6, self.username, self.password)
+            self.ipv6[0], self.username, self.password)
         # The hostname must be of the form 'prefix-id'
         self.assertTrue(hostname.endswith("-%d" % self.server['id']))
 
-    def test_018_rdp_to_server_ipv4(self):
+    def test_019_rdp_to_server_ipv4(self):
         """Test RDP connection to server public IPv4 works"""
         self._skip_if(not self._image_is(self.use_image, "windows"),
                       "only valid for Windows servers")
-        sock = self._insist_on_tcp_connection(socket.AF_INET, self.ipv4, 3389)
+        sock = self._insist_on_tcp_connection(
+            socket.AF_INET, self.ipv4[0], 3389)
         # No actual RDP processing done. We assume the RDP server is there
         # if the connection to the RDP port is successful.
         # pylint: disable-msg=W0511
         # FIXME: Use rdesktop, analyze exit code? see manpage
         sock.close()
 
-    def test_019_rdp_to_server_ipv6(self):
+    def test_020_rdp_to_server_ipv6(self):
         """Test RDP connection to server public IPv6 works"""
         self._skip_if(not self._image_is(self.use_image, "windows"),
                       "only valid for Windows servers")
         self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
-        sock = self._insist_on_tcp_connection(socket.AF_INET, self.ipv6, 3389)
+        sock = self._insist_on_tcp_connection(
+            socket.AF_INET, self.ipv6[0], 3389)
         # No actual RDP processing done. We assume the RDP server is there
         # if the connection to the RDP port is successful.
         # pylint: disable-msg=W0511
         # FIXME: Use rdesktop, analyze exit code? see manpage
         sock.close()
 
-    def test_020_personality(self):
+    def test_021_personality(self):
         """Test file injection for personality enforcement"""
         self._skip_if(not self._image_is(self.use_image, "linux"),
                       "only implemented for linux servers")
@@ -252,23 +264,16 @@ class GeneratedServerTestSuite(CycladesTests):
 
         for inj_file in self.personality:
             self._check_file_through_ssh(
-                self.ipv4, inj_file['owner'], self.password,
+                self.ipv4[0], inj_file['owner'], self.password,
                 inj_file['path'], inj_file['contents'])
 
-    def test_021_submit_delete_request(self):
+    def test_022_destroy_floating_ips(self):
+        """Destroy the floating IPs"""
+        self._disconnect_from_network(self.server)
+
+    def test_023_submit_delete_request(self):
         """Test submit request to delete server"""
-        self.clients.cyclades.delete_server(self.server['id'])
-
-    def test_022_server_becomes_deleted(self):
-        """Test server becomes DELETED"""
-        self._insist_on_server_transition(self.server, ["ACTIVE"], "DELETED")
-        # Verify quotas
-        self._verify_quotas_deleted([self.use_flavor])
-
-    def test_023_server_no_longer(self):
-        """Test server is no longer in server list"""
-        servers = self._get_list_of_servers()
-        self.assertNotIn(self.server['id'], [s['id'] for s in servers])
+        self._delete_servers([self.server])
 
 
 # --------------------------------------------------------------------
diff --git a/snf-tools/synnefo_tools/burnin/stale_tests.py b/snf-tools/synnefo_tools/burnin/stale_tests.py
index 46b6ca4..24fc026 100644
--- a/snf-tools/synnefo_tools/burnin/stale_tests.py
+++ b/snf-tools/synnefo_tools/burnin/stale_tests.py
@@ -47,7 +47,7 @@ class StaleServersTestSuite(CycladesTests):
 
     def test_001_show_stale_servers(self):
         """Show staled servers (servers left from previous runs)"""
-        servers = self._get_list_of_servers()
+        servers = self._get_list_of_servers(detail=True)
         self.stale_servers = [s for s in servers
                               if s['name'].startswith(SNF_TEST_PREFIX)]
 
@@ -64,21 +64,47 @@ class StaleServersTestSuite(CycladesTests):
         """Delete staled servers (servers left from previous runs)"""
         len_stale = len(self.stale_servers)
         if not self.delete_stale and len_stale != 0:
-            msg = "Use --delete-stale flag to delete stale servers"
-            self.error(msg)
-            self.fail(msg)
+            self.fail("Use --delete-stale flag to delete stale servers")
         elif len_stale == 0:
             self.info("No stale servers found")
         else:
-            self.info("Deleting %s stale servers:", len_stale)
-            for stl in self.stale_servers:
-                self.info("  Deleting server \"%s\" with id %s",
-                          stl['name'], stl['id'])
-                self.clients.cyclades.delete_server(stl['id'])
+            self.info("Deleting %s stale servers", len_stale)
+            self._delete_servers(self.stale_servers, error=True)
 
-            for stl in self.stale_servers:
-                self._insist_on_server_transition(
-                    stl, ["ACTIVE", "ERROR", "STOPPED"], "DELETED")
+
+# Too many public methods. pylint: disable-msg=R0904
+class StaleFloatingIPsTestSuite(CycladesTests):
+    """Handle stale Floating IPs"""
+    stale_ips = Proper(value=None)
+
+    def test_001_show_stale_ips(self):
+        """Show staled floating IPs"""
+        floating_ips = self.clients.network.list_floatingips()
+        # We consider all the floating ips that are not attached
+        # anywhere as stale ips.
+        self.stale_ips = [ip for ip in floating_ips
+                          if ip['instance_id'] is None]
+
+        len_stale = len(self.stale_ips)
+        if len_stale == 0:
+            self.info("No stale floating IPs found")
+            return
+
+        self.info("Found %s stale floating IPs:", len_stale)
+        for stl in self.stale_ips:
+            self.info("  Floating IP %s with id %s",
+                      stl['floating_ip_address'], stl['id'])
+
+    def test_002_delete_stale_ips(self):
+        """Delete staled floating IPs"""
+        len_stale = len(self.stale_ips)
+        if not self.delete_stale and len_stale != 0:
+            self.fail("Use --delete-stale flag to delete stale floating IPs")
+        elif len_stale == 0:
+            self.info("No stale floating IPs found")
+        else:
+            self.info("Deleteing %s stale floating IPs", len_stale)
+            self._disconnect_from_floating_ips(self.stale_ips)
 
 
 # Too many public methods. pylint: disable-msg=R0904
@@ -105,18 +131,9 @@ class StaleNetworksTestSuite(CycladesTests):
         """Delete staled networks (networks left from previous runs)"""
         len_stale = len(self.stale_networks)
         if not self.delete_stale and len_stale != 0:
-            msg = "Use --delete-stale flag to delete stale networks"
-            self.error(msg)
-            self.fail(msg)
+            self.fail("Use --delete-stale flag to delete stale networks")
         elif len_stale == 0:
             self.info("No stale networks found")
         else:
-            self.info("Deleting %s stale networks:", len_stale)
-            for stl in self.stale_networks:
-                self.info("  Deleting network \"%s\" with id %s",
-                          stl['name'], stl['id'])
-                self.clients.cyclades.delete_network(stl['id'])
-
-            for stl in self.stale_networks:
-                self._insist_on_network_transition(
-                    stl, ["ACTIVE", "ERROR"], "DELETED")
+            self.info("Deleting %s stale networks", len_stale)
+            self._delete_networks(self.stale_networks)
