Commit c0649302 authored by Ilias Tsitsimpis's avatar Ilias Tsitsimpis Committed by Giorgos Korfiatis
Browse files

burnin: Rewrite check_quotas functionality

This patch enhances check_quotas functionality for better supporting
projects quotas.
parent 03583243
......@@ -63,6 +63,17 @@ KB = 2**10
MB = 2**20
GB = 2**30
QADD = 1
QREMOVE = -1
QDISK = "cyclades.disk"
QVM = "cyclades.vm"
QPITHOS = "pithos.diskspace"
QRAM = "cyclades.ram"
QIP = "cyclades.floating_ip"
QCPU = "cyclades.cpu"
QNET = "cyclades.network.private"
# --------------------------------------------------------------------
# BurninTestResult class
......@@ -217,6 +228,7 @@ class BurninTests(unittest.TestCase):
failfast = None
quotas = Proper(value=None)
uuid = Proper(value=None)
@classmethod
def setUpClass(cls): # noqa
......@@ -288,15 +300,17 @@ class BurninTests(unittest.TestCase):
def error(self, msg, *args):
"""Pass the section value to logger"""
logger.error(self.suite_name, msg, *args)
self.fail(msg % args)
# ----------------------------------
# Helper functions that every testsuite may need
def _get_uuid(self):
"""Get our uuid"""
if self.uuid is None:
authenticate = self.clients.astakos.authenticate()
uuid = authenticate['access']['user']['id']
self.info("User's uuid is %s", uuid)
return uuid
self.uuid = authenticate['access']['user']['id']
self.info("User's uuid is %s", self.uuid)
return self.uuid
def _get_username(self):
"""Get our User Name"""
......@@ -337,7 +351,6 @@ class BurninTests(unittest.TestCase):
return su_value
else:
self.error("Unrecognized system-user type %s", su_type)
self.fail("Unrecognized system-user type")
except ValueError:
msg = "Invalid system-user format: %s. Must be [id|name]:.+"
self.warning(msg, self.system_user)
......@@ -412,7 +425,6 @@ class BurninTests(unittest.TestCase):
[f for f in flavors if str(f['id']) == flv_value]
else:
self.error("Unrecognized flavor type %s", flv_type)
self.fail("Unrecognized flavor type")
# Append and continue
ret_flavors.extend(filtered_flvs)
......@@ -483,7 +495,6 @@ class BurninTests(unittest.TestCase):
i['id'].lower() == img_value.lower()]
else:
self.error("Unrecognized image type %s", img_type)
self.fail("Unrecognized image type")
# Append and continue
ret_images.extend(filtered_imgs)
......@@ -536,72 +547,36 @@ class BurninTests(unittest.TestCase):
# pylint: disable=invalid-name
# pylint: disable=too-many-arguments
def _check_quotas(self, puuid=None, disk=None, vm=None, diskspace=None,
ram=None, ip=None, cpu=None, network=None):
def _check_quotas(self, changes):
"""Check that quotas' changes are consistent
@param puuid: The uuid of the project, quotas are assigned to
@param changes: A dict of the changes that have been made in quotas
"""
assert any(v is None for v in
[disk, vm, diskspace, ram, ip, cpu, network]), \
"_check_quotas require arguments"
if not changes:
return
self.info("Check that quotas' changes are consistent")
old_quotas = self.quotas
new_quotas = self._get_quotas()
self.quotas = new_quotas
user_uuid = self._get_uuid()
if puuid is None:
puuid = user_uuid
self.assertListEqual(sorted(old_quotas.keys()),
sorted(new_quotas.keys()))
for project in old_quotas.keys():
# Check Disk usage
project_name = self._get_project_name(project, user_uuid)
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "cyclades.disk",
disk, project == puuid)
# Check VM usage
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "cyclades.vm",
vm, project == puuid)
# Check DiskSpace usage
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "pithos.diskspace",
diskspace, project == puuid)
# Check Ram usage
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "cyclades.ram",
ram, project == puuid)
# Check Floating IPs usage
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "cyclades.floating_ip",
ip, project == puuid)
# Check CPU usage
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "cyclades.cpu",
cpu, project == puuid)
# Check Network usage
self._check_quotas_aux(old_quotas[project], new_quotas[project],
project_name, "cyclades.network.private",
network, project == puuid)
def _check_quotas_aux(self, old_quotas, new_quotas,
project_name, resource, value, check):
"""Auxiliary function for _check_quotas"""
old_value = old_quotas[resource]['usage']
new_value = new_quotas[resource]['usage']
if check and value is not None:
assert isinstance(value, int), \
"%s value has to be integer" % resource
old_value += value
self.assertEqual(old_value, new_value,
"Project %s: %s quotas don't match" %
(project_name, resource))
# Take old_quotas and apply changes
for prj, values in changes.items():
self.assertIn(prj, old_quotas.keys())
for q_name, q_mult, q_value, q_unit in values:
if q_unit is None:
q_unit = 1
q_value = q_mult*int(q_value)*q_unit
assert isinstance(q_value, int), \
"Project %s: %s value has to be integer" % (prj, q_name)
old_quotas[prj][q_name]['usage'] += q_value
old_quotas[prj][q_name]['project_usage'] += q_value
self.assertEqual(old_quotas, new_quotas)
# ----------------------------------
# Projects
......
......@@ -49,12 +49,38 @@ import subprocess
from kamaki.clients import ClientError
from synnefo_tools.burnin.common import BurninTests, MB, GB
from synnefo_tools.burnin.common import BurninTests, MB, GB, QADD, QREMOVE, \
QDISK, QVM, QRAM, QIP, QCPU, QNET
# pylint: disable=too-many-public-methods
class CycladesTests(BurninTests):
"""Extends the BurninTests class for Cyclades"""
def _parse_images(self):
"""Find images given to command line"""
if self.images is None:
self.info("No --images given. Will use the default %s",
"^Debian Base$")
filters = ["name:^Debian Base$"]
else:
filters = self.images
avail_images = self._find_images(filters)
self.info("Found %s images to choose from", len(avail_images))
return avail_images
def _parse_flavors(self):
"""Find flavors given to command line"""
flavors = self._get_list_of_flavors(detail=True)
if self.flavors is None:
self.info("No --flavors given. Will use all of them")
avail_flavors = flavors
else:
avail_flavors = self._find_flavors(self.flavors, flavors=flavors)
self.info("Found %s flavors to choose from", len(avail_flavors))
return avail_flavors
def _try_until_timeout_expires(self, opmsg, check_fun):
"""Try to perform an action until timeout expires"""
assert callable(check_fun), "Not a function"
......@@ -113,10 +139,12 @@ class CycladesTests(BurninTests):
server['name'], server['id'])
return self.clients.cyclades.get_server_details(server['id'])
def _create_server(self, image, flavor, personality=None, network=False):
# pylint: disable=too-many-arguments
def _create_server(self, image, flavor, personality=None,
network=False, project_id=None):
"""Create a new server"""
if network:
fip = self._create_floating_ip()
fip = self._create_floating_ip(project_id=project_id)
port = self._create_port(fip['floating_network_id'],
floating_ip=fip)
networks = [{'port': port['id']}]
......@@ -129,7 +157,8 @@ class CycladesTests(BurninTests):
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, networks=networks)
personality=personality, networks=networks,
project=project_id)
self.info("Server id: %s", server['id'])
self.info("Server password: %s", server['adminPass'])
......@@ -138,12 +167,18 @@ class CycladesTests(BurninTests):
self.assertEqual(server['flavor']['id'], flavor['id'])
self.assertEqual(server['image']['id'], image['id'])
self.assertEqual(server['status'], "BUILD")
if project_id is None:
project_id = self._get_uuid()
self.assertEqual(server['tenant_id'], project_id)
# Verify quotas
self._check_quotas(disk=+int(flavor['disk'])*GB,
vm=+1,
ram=+int(flavor['ram'])*MB,
cpu=+int(flavor['vcpus']))
changes = \
{project_id:
[(QDISK, QADD, flavor['disk'], GB),
(QVM, QADD, 1, None),
(QRAM, QADD, flavor['ram'], MB),
(QCPU, QADD, flavor['vcpus'], None)]}
self._check_quotas(changes)
return server
......@@ -180,26 +215,25 @@ class CycladesTests(BurninTests):
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)
self._verify_quotas_deleted(servers)
def _verify_quotas_deleted(self, flavors):
def _verify_quotas_deleted(self, servers):
"""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)
changes = dict()
for server in servers:
project = server['tenant_id']
if project not in changes:
changes[project] = []
flavor = \
self.clients.compute.get_flavor_details(server['flavor']['id'])
new_changes = [
(QDISK, QREMOVE, flavor['disk'], GB),
(QVM, QREMOVE, 1, None),
(QRAM, QREMOVE, flavor['ram'], MB),
(QCPU, QREMOVE, flavor['vcpus'], None)]
changes[project].extend(new_changes)
self._check_quotas(changes)
def _get_connection_username(self, server):
"""Determine the username to use to connect to the server"""
......@@ -320,7 +354,7 @@ class CycladesTests(BurninTests):
"Can not get IPs from server attachments")
for addr in addrs:
self.assertEquals(IPy.IP(addr).version(), version)
self.assertEqual(IPy.IP(addr).version(), version)
if network is None:
msg = "Server's public IPv%s is %s"
......@@ -411,21 +445,28 @@ class CycladesTests(BurninTests):
# ----------------------------------
# Networks
def _create_network(self, cidr="10.0.1.0/28", dhcp=True):
def _create_network(self, cidr="10.0.1.0/28", dhcp=True,
project_id=None):
"""Create a new private network"""
name = self.run_id
network = self.clients.network.create_network(
"MAC_FILTERED", name=name, shared=False)
"MAC_FILTERED", name=name, shared=False,
project=project_id)
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)
if project_id is None:
project_id = self._get_uuid()
changes = \
{project_id: [(QNET, QADD, 1, None)]}
self._check_quotas(changes)
#Test if the right name is assigned
self.assertEqual(network['name'], name)
self.assertEqual(network['tenant_id'], project_id)
return network
......@@ -450,7 +491,9 @@ class CycladesTests(BurninTests):
self.assertNotIn(net['id'], new_networks)
# Verify quotas
self._check_quotas(network=-len(networks))
changes = \
{self._get_uuid(): [(QNET, QREMOVE, len(networks), None)]}
self._check_quotas(changes)
def _get_public_network(self, networks=None):
"""Get the public network"""
......@@ -462,20 +505,27 @@ class CycladesTests(BurninTests):
return net
self.fail("Could not find a public network to use")
def _create_floating_ip(self):
def _create_floating_ip(self, project_id=None):
"""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'])
fip = self.clients.network.create_floatingip(
pub_net['id'], project=project_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)
if project_id is None:
project_id = self._get_uuid()
changes = \
{project_id: [(QIP, QADD, 1, None)]}
self._check_quotas(changes)
# Check that IP is IPv4
self.assertEquals(IPy.IP(fip['floating_ip_address']).version(), 4)
self.assertEqual(IPy.IP(fip['floating_ip_address']).version(), 4)
self.assertEqual(fip['tenant_id'], project_id)
self.info("Floating IP %s with id %s created",
fip['floating_ip_address'], fip['id'])
......@@ -588,8 +638,51 @@ class CycladesTests(BurninTests):
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))
changes = dict()
for fip in fips:
project = fip['tenant_id']
if project not in changes:
changes[project] = []
changes[project].append((QIP, QREMOVE, 1, None))
self._check_quotas(changes)
def _find_project(self, flavors, projects=None):
"""Return a pair of flavor, project that we can use"""
if projects is None:
projects = self.quotas.keys()
# XXX: Well there seems to be no easy way to find how many resources
# we have left in a project (we have to substract usage from limit,
# check both per_user and project quotas, blah, blah). For now
# just return the first flavor with the first project and lets hope
# that it fits.
return (flavors[0], projects[0])
# # Get only the quotas for the given 'projects'
# quotas = dict()
# for prj, qts in self.quotas.items():
# if prj in projects:
# quotas[prj] = qts
#
# results = []
# for flv in flavors:
# for prj, qts in quotas.items():
# self.debug("Testing flavor %s, project %s", flv['name'], prj)
# condition = \
# (flv['ram'] <= qts['cyclades.ram']['usage'] and
# flv['vcpus'] <= qts['cyclades.cpu']['usage'] and
# flv['disk'] <= qts['cyclades.disk']['usage'] and
# qts['cyclades.vm']['usage'] >= 1)
# if condition:
# results.append((flv, prj))
#
# if not results:
# msg = "Couldn't find a suitable flavor to use for current qutoas"
# self.error(msg)
#
# return random.choice(results)
class Retry(Exception):
......
......@@ -42,7 +42,8 @@ import shutil
from kamaki.clients import ClientError
from synnefo_tools.burnin.common import BurninTests, Proper
from synnefo_tools.burnin.common import BurninTests, Proper, \
QPITHOS, QADD, QREMOVE
# pylint: disable=too-many-public-methods
......@@ -171,7 +172,9 @@ class ImagesTestSuite(BurninTests):
self.clients.pithos.upload_object(self.temp_image_name, fin)
# Verify quotas
self._check_quotas(diskspace=file_size)
changes = \
{self._get_uuid(): [(QPITHOS, QADD, file_size, None)]}
self._check_quotas(changes)
def test_009_register_image(self):
"""Register image to Plankton"""
......@@ -198,7 +201,9 @@ class ImagesTestSuite(BurninTests):
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)
changes = \
{self._get_uuid(): [(QPITHOS, QREMOVE, file_size, None)]}
self._check_quotas(changes)
self.temp_image_name = None
# Remove temp directory
self.info("Deleting temp directory %s", self.temp_dir)
......
......@@ -53,27 +53,11 @@ class NetworkTestSuite(CycladesTests):
def test_001_images_to_use(self):
"""Find images to be used to create our machines"""
if self.images is None:
self.info("No --images given. Will use the default %s",
"^Debian Base$")
filters = ["name:^Debian Base$"]
else:
filters = self.images
self.avail_images = self._find_images(filters)
self.info("Found %s images to choose from", len(self.avail_images))
self.avail_images = self._parse_images()
def test_002_flavors_to_use(self):
"""Find flavors to be used to create our machines"""
flavors = self._get_list_of_flavors(detail=True)
if self.flavors is None:
self.info("No --flavors given. Will use all of them")
self.avail_flavors = flavors
else:
self.avail_flavors = self._find_flavors(
self.flavors, flavors=flavors)
self.info("Found %s flavors to choose from", len(self.avail_flavors))
self.avail_flavors = self._parse_flavors()
def test_003_submit_create_server_a(self):
"""Submit create server request for server A"""
......
......@@ -40,7 +40,8 @@ import os
import random
import tempfile
from synnefo_tools.burnin.common import BurninTests, Proper
from synnefo_tools.burnin.common import BurninTests, Proper, \
QPITHOS, QADD, QREMOVE
# pylint: disable=too-many-public-methods
......@@ -92,7 +93,10 @@ class PithosTestSuite(BurninTests):
# 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)
size = os.fstat(fout.fileno()).st_size
changes = \
{self._get_uuid(): [(QPITHOS, QADD, size, None)]}
self._check_quotas(changes)
def test_005_download_file(self):
"""Test downloading the file from Pithos"""
......@@ -116,7 +120,9 @@ class PithosTestSuite(BurninTests):
self.clients.pithos.del_object("test.txt")
# Verify quotas
self._check_quotas(diskspace=-int(content_length))
changes = \
{self._get_uuid(): [(QPITHOS, QREMOVE, content_length, None)]}
self._check_quotas(changes)
self.info("Removing the container %s", self.created_container)
self.clients.pithos.purge_container()
......
......@@ -36,15 +36,36 @@ This is the burnin class that tests the Projects functionality
"""
from synnefo_tools.burnin.common import BurninTests, Proper
import random
from synnefo_tools.burnin.common import Proper
from synnefo_tools.burnin.cyclades_common import CycladesTests
# pylint: disable=too-many-public-methods
class QuotasTestSuite(BurninTests):
class QuotasTestSuite(CycladesTests):
"""Test Quotas functionality"""
project = Proper(value=None)
server = Proper(value=None)
def test_001_check_skip(self):
"""Check if we are members in more than one projects"""
self._skip_suite_if(len(self.quotas.keys()) < 2,
"This user is not a member of 2 or more projects")
def test_002_create(self):
"""Create a machine to a different project than base"""
image = random.choice(self._parse_images())
flavors = self._parse_flavors()
# We want to create our machine in a project other than 'base'
projects = self.quotas.keys()
projects.remove(self._get_uuid())
(flavor, project) = self._find_project(flavors, projects)
# Create machine
self.server = self._create_server(image, flavor, network=True,
project_id=project)
# Wait for server to become active
self._insist_on_server_transition(
self.server, ["BUILD"], "ACTIVE")
......@@ -44,7 +44,7 @@ import socket
from vncauthproxy.d3des import generate_response as d3des_generate_response
from synnefo_tools.burnin.common import BurninTests, Proper
from synnefo_tools.burnin.common import Proper
from synnefo_tools.burnin.cyclades_common import CycladesTests
......@@ -290,7 +290,7 @@ class GeneratedServerTestSuite(CycladesTests):
# will run the same tests using different images and or flavors.
# The creation and running of our GeneratedServerTestSuite class will
# happen as a testsuite itself (everything here is a test!).
class ServerTestSuite(BurninTests):
class ServerTestSuite(CycladesTests):
"""Generate and run the GeneratedServerTestSuite
We will generate as many testsuites as the number of images given.
......@@ -303,28 +303,11 @@ class ServerTestSuite(BurninTests):
def test_001_images_to_use(self):
"""Find images to be used by GeneratedServerTestSuite"""
if self.images is None:
self.info("No --images given. Will use the default %s",
"^Debian Base$")
filters = ["name:^Debian Base$"]
else:
filters = self.images
self.avail_images = self._find_images(filters)
self.info("Found %s images. Let's create an equal number of tests",
len(self.avail_images))
self.avail_images = self._parse_images()
def test_002_flavors_to_use(self):
"""Find flavors to be used by GeneratedServerTestSuite"""
flavors = self._get_list_of_flavors(detail=True)
if self.flavors is None:<