commit 3e5bbd851dab23de04dea84eb9725e093b5fe43c
Author: Ilias Tsitsimpis <iliastsi@grnet.gr>
Date:   Mon Nov 25 16:49:50 2013 +0200

    burnin: Verify quotas
    
    After every action that changes the user's quotas
    verify that quotas' changes are consistent.

diff --git a/snf-tools/synnefo_tools/burnin/common.py b/snf-tools/synnefo_tools/burnin/common.py
index b608469..faaa31c 100644
--- a/snf-tools/synnefo_tools/burnin/common.py
+++ b/snf-tools/synnefo_tools/burnin/common.py
@@ -59,6 +59,9 @@ logger = None  # Invalid constant name. pylint: disable-msg=C0103
 SNF_TEST_PREFIX = "snf-test-"
 CONNECTION_RETRY_LIMIT = 2
 SYSTEM_USERS = ["images@okeanos.grnet.gr", "images@demo.synnefo.org"]
+KB = 2**10
+MB = 2**20
+GB = 2**30
 
 
 # --------------------------------------------------------------------
@@ -205,13 +208,13 @@ class BurninTests(unittest.TestCase):
         self.info("Image url is %s", self.clients.image_url)
 
         self.quotas = self._get_quotas()
-        self.info("  Disk usage is %s",
+        self.info("  Disk usage is %s bytes",
                   self.quotas['system']['cyclades.disk']['usage'])
         self.info("  VM usage is %s",
                   self.quotas['system']['cyclades.vm']['usage'])
-        self.info("  DiskSpace usage is %s",
+        self.info("  DiskSpace usage is %s bytes",
                   self.quotas['system']['pithos.diskspace']['usage'])
-        self.info("  Ram usage is %s",
+        self.info("  Ram usage is %s bytes",
                   self.quotas['system']['cyclades.ram']['usage'])
         self.info("  CPU usage is %s",
                   self.quotas['system']['cyclades.cpu']['usage'])
@@ -480,10 +483,54 @@ class BurninTests(unittest.TestCase):
     # Quotas
     def _get_quotas(self):
         """Get quotas"""
-        self.info("Getting quotas for user %s", self._get_uuid())
+        self.info("Getting quotas")
         astakos_client = self.clients.astakos.get_client()
         return astakos_client.get_quotas()
 
+    # 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):
+        """Check that quotas' changes are consistent"""
+        assert any(v is None for v in
+                   [disk, vm, diskspace, ram, cpu, network]), \
+            "_check_quotas require arguments"
+
+        self.info("Check that quotas' changes are consistent")
+        old_quotas = self.quotas
+        new_quotas = self._get_quotas()
+        self.quotas = new_quotas
+
+        # Check Disk usage
+        self._check_quotas_aux(
+            old_quotas, new_quotas, 'cyclades.disk', disk)
+        # Check VM usage
+        self._check_quotas_aux(
+            old_quotas, new_quotas, 'cyclades.vm', vm)
+        # Check DiskSpace usage
+        self._check_quotas_aux(
+            old_quotas, new_quotas, 'pithos.diskspace', diskspace)
+        # Check Ram usage
+        self._check_quotas_aux(
+            old_quotas, new_quotas, 'cyclades.ram', ram)
+        # Check CPU usage
+        self._check_quotas_aux(
+            old_quotas, new_quotas, 'cyclades.cpu', cpu)
+        # Check Network usage
+        self._check_quotas_aux(
+            old_quotas, new_quotas, 'cyclades.network.private', network)
+
+    def _check_quotas_aux(self, old_quotas, new_quotas, resource, value):
+        """Auxiliary function for _check_quotas"""
+        old_value = old_quotas['system'][resource]['usage']
+        new_value = new_quotas['system'][resource]['usage']
+        if value is not None:
+            assert isinstance(value, int), \
+                "%s value has to be integer" % resource
+            old_value += value
+        self.assertEqual(old_value, new_value,
+                         "%s quotas don't match" % resource)
+
 
 # --------------------------------------------------------------------
 # Initialize Burnin
diff --git a/snf-tools/synnefo_tools/burnin/cyclades_common.py b/snf-tools/synnefo_tools/burnin/cyclades_common.py
index 4a231cb..8be7d6c 100644
--- a/snf-tools/synnefo_tools/burnin/cyclades_common.py
+++ b/snf-tools/synnefo_tools/burnin/cyclades_common.py
@@ -46,7 +46,7 @@ import paramiko
 import tempfile
 import subprocess
 
-from synnefo_tools.burnin.common import BurninTests
+from synnefo_tools.burnin.common import BurninTests, MB, GB
 
 
 # Too many public methods. pylint: disable-msg=R0904
@@ -127,8 +127,30 @@ class CycladesTests(BurninTests):
         self.assertEqual(server['image']['id'], image['id'])
         self.assertEqual(server['status'], "BUILD")
 
+        # Verify quotas
+        self._check_quotas(disk=+int(flavor['disk'])*GB,
+                           vm=+1,
+                           ram=+int(flavor['ram'])*MB,
+                           cpu=+int(flavor['vcpus']))
+
         return server
 
+    def _verify_quotas_deleted(self, flavors):
+        """Verify quotas for a number of deleted servers"""
+        used_disk = 0
+        used_vm = 0
+        used_ram = 0
+        used_cpu = 0
+        for flavor in flavors:
+            used_disk += int(flavor['disk']) * GB
+            used_vm += 1
+            used_ram += int(flavor['ram']) * MB
+            used_cpu += int(flavor['vcpus'])
+        self._check_quotas(disk=-used_disk,
+                           vm=-used_vm,
+                           ram=-used_ram,
+                           cpu=-used_cpu)
+
     def _get_connection_username(self, server):
         """Determine the username to use to connect to the server"""
         users = server['metadata'].get("users", None)
@@ -360,6 +382,20 @@ class CycladesTests(BurninTests):
         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):
+        """Create a new private network"""
+        network = self.clients.cyclades.create_network(
+            name, cidr=cidr, dhcp=dhcp)
+        self.info("Network with id %s created", network['id'])
+
+        # Verify quotas
+        self._check_quotas(network=+1)
+
+        #Test if the right name is assigned
+        self.assertEqual(network['name'], name)
+
+        return network
+
 
 class Retry(Exception):
     """Retry the action
diff --git a/snf-tools/synnefo_tools/burnin/images_tests.py b/snf-tools/synnefo_tools/burnin/images_tests.py
index 9a07456..8e33cba 100644
--- a/snf-tools/synnefo_tools/burnin/images_tests.py
+++ b/snf-tools/synnefo_tools/burnin/images_tests.py
@@ -166,9 +166,13 @@ class ImagesTestSuite(BurninTests):
         """Upload the image to Pithos"""
         self._set_pithos_account(self._get_uuid())
         self._create_pithos_container("burnin-images")
+        file_size = os.path.getsize(self.temp_image_file)
         with open(self.temp_image_file, "r+b") as fin:
             self.clients.pithos.upload_object(self.temp_image_name, fin)
 
+        # Verify quotas
+        self._check_quotas(diskspace=file_size)
+
     def test_009_register_image(self):
         """Register image to Plankton"""
         location = "pithos://" + self._get_uuid() + \
@@ -192,6 +196,9 @@ class ImagesTestSuite(BurninTests):
         # Remove uploaded image
         self.info("Deleting uploaded image %s", self.temp_image_name)
         self.clients.pithos.del_object(self.temp_image_name)
+        # Verify quotas
+        file_size = os.path.getsize(self.temp_image_file)
+        self._check_quotas(diskspace=-file_size)
         self.temp_image_name = None
         # Remove temp directory
         self.info("Deleting temp directory %s", self.temp_dir)
diff --git a/snf-tools/synnefo_tools/burnin/logger.py b/snf-tools/synnefo_tools/burnin/logger.py
index b4c63e6..9fcbc6f 100644
--- a/snf-tools/synnefo_tools/burnin/logger.py
+++ b/snf-tools/synnefo_tools/burnin/logger.py
@@ -72,9 +72,9 @@ SECTION_FAILED = "  * Failed:"
 
 # --------------------------------------------------------------------
 # Helper functions
-def _blue(msg):
-    """Blue color"""
-    return "\x1b[1;34m" + str(msg) + "\x1b[0m"
+def _cyan(msg):
+    """Bold High Intensity Cyan color"""
+    return "\x1b[1;96m" + str(msg) + "\x1b[0m"
 
 
 def _yellow(msg):
@@ -467,7 +467,7 @@ class Log(object):
                 else:
                     return color_fun(msg) + "\n"
             else:
-                args = tuple([_blue(arg) for arg in args])
+                args = tuple([_cyan(arg) for arg in args])
                 return _format_message(msg, *args)
         else:
             return _format_message(msg, *args)
diff --git a/snf-tools/synnefo_tools/burnin/network_tests.py b/snf-tools/synnefo_tools/burnin/network_tests.py
index 1a4fa99..c5c1e64 100644
--- a/snf-tools/synnefo_tools/burnin/network_tests.py
+++ b/snf-tools/synnefo_tools/burnin/network_tests.py
@@ -114,12 +114,7 @@ class NetworkTestSuite(CycladesTests):
     def test_006_create_network(self):
         """Submit a create network request"""
         name = self.run_id
-        self.network = self.clients.cyclades.create_network(
-            name, cidr="10.0.1.0/28", dhcp=True)
-        self.info("Network with id %s created", self.network['id'])
-
-        #Test if right the name is assigned
-        self.assertEqual(self.network['name'], name)
+        self.network = self._create_network(name)
 
         self._insist_on_network_transition(
             self.network, ["BUILD"], "ACTIVE")
@@ -217,6 +212,9 @@ class NetworkTestSuite(CycladesTests):
         networks = [net['id'] for net in self._get_list_of_networks()]
         self.assertNotIn(self.network['id'], networks)
 
+        # Verify quotas
+        self._check_quotas(network=-1)
+
     def test_015_cleanup_servers(self):
         """Cleanup servers created for this test"""
         self.clients.cyclades.delete_server(self.server_a['server']['id'])
@@ -226,3 +224,7 @@ class NetworkTestSuite(CycladesTests):
             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']])
diff --git a/snf-tools/synnefo_tools/burnin/pithos_tests.py b/snf-tools/synnefo_tools/burnin/pithos_tests.py
index 398920d..516db50 100644
--- a/snf-tools/synnefo_tools/burnin/pithos_tests.py
+++ b/snf-tools/synnefo_tools/burnin/pithos_tests.py
@@ -36,6 +36,7 @@ This is the burnin class that tests the Pithos functionality
 
 """
 
+import os
 import random
 import tempfile
 
@@ -90,6 +91,8 @@ class PithosTestSuite(BurninTests):
             # Upload the file,
             # The container is the one choosen during the `create_container'
             self.clients.pithos.upload_object("test.txt", fout)
+            # Verify quotas
+            self._check_quotas(diskspace=+os.fstat(fout.fileno()).st_size)
 
     def test_005_download_file(self):
         """Test downloading the file from Pithos"""
@@ -108,8 +111,13 @@ class PithosTestSuite(BurninTests):
         self.info("Removing the file %s from container %s",
                   "test.txt", self.created_container)
         # The container is the one choosen during the `create_container'
+        content_length = \
+            self.clients.pithos.get_object_info("test.txt")['content-length']
         self.clients.pithos.del_object("test.txt")
 
+        # Verify quotas
+        self._check_quotas(diskspace=-int(content_length))
+
         self.info("Removing the container %s", self.created_container)
         self.clients.pithos.purge_container()
 
diff --git a/snf-tools/synnefo_tools/burnin/server_tests.py b/snf-tools/synnefo_tools/burnin/server_tests.py
index 8eab83c..2379b19 100644
--- a/snf-tools/synnefo_tools/burnin/server_tests.py
+++ b/snf-tools/synnefo_tools/burnin/server_tests.py
@@ -262,6 +262,8 @@ class GeneratedServerTestSuite(CycladesTests):
     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"""
