common.py 23 KB
Newer Older
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Copyright 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
#   1. Redistributions of source code must retain the above
#      copyright notice, this list of conditions and the following
#      disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#      copyright notice, this list of conditions and the following
#      disclaimer in the documentation and/or other materials
#      provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.

"""
Common utils for burnin tests

"""

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
39
40
import re
import shutil
41
import unittest
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
42
import datetime
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
43
import tempfile
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
44
45
import traceback

46
from kamaki.clients.cyclades import CycladesClient, CycladesNetworkClient
47
from kamaki.clients.astakos import CachedAstakosClient
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
48
from kamaki.clients.compute import ComputeClient
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
49
from kamaki.clients.pithos import PithosClient
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
50
from kamaki.clients.image import ImageClient
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
51

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
52
from synnefo_tools.burnin.logger import Log
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
53
54
55
56


# --------------------------------------------------------------------
# Global variables
57
58
logger = None   # Invalid constant name. pylint: disable-msg=C0103
success = None  # Invalid constant name. pylint: disable-msg=C0103
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
59
SNF_TEST_PREFIX = "snf-test-"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
60
CONNECTION_RETRY_LIMIT = 2
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
61
SYSTEM_USERS = ["images@okeanos.grnet.gr", "images@demo.synnefo.org"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
62
63
64
KB = 2**10
MB = 2**20
GB = 2**30
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79


# --------------------------------------------------------------------
# BurninTestResult class
class BurninTestResult(unittest.TestResult):
    """Modify the TextTestResult class"""
    def __init__(self):
        super(BurninTestResult, self).__init__()

        # Test parameters
        self.failfast = True

    def startTest(self, test):  # noqa
        """Called when the test case test is about to be run"""
        super(BurninTestResult, self).startTest(test)
80
        logger.log(test.__class__.__name__, test.shortDescription())
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
81
82
83
84

    # Method could be a function. pylint: disable-msg=R0201
    def _test_failed(self, test, err):
        """Test failed"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
85
86
87
88
89
        # Get class name
        if test.__class__.__name__ == "_ErrorHolder":
            class_name = test.id().split('.')[-1].rstrip(')')
        else:
            class_name = test.__class__.__name__
90
        err_msg = str(test) + "... failed (%s)."
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
91
92
        timestamp = datetime.datetime.strftime(
            datetime.datetime.now(), "%a %b %d %Y %H:%M:%S")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
93
        logger.error(class_name, err_msg, timestamp)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
94
95
        (err_type, err_value, err_trace) = err
        trcback = traceback.format_exception(err_type, err_value, err_trace)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
96
        logger.info(class_name, trcback)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
97
98
99
100
101
102
103
104
105
106
107
108
109

    def addError(self, test, err):  # noqa
        """Called when the test case test raises an unexpected exception"""
        super(BurninTestResult, self).addError(test, err)
        self._test_failed(test, err)

    def addFailure(self, test, err):  # noqa
        """Called when the test case test signals a failure"""
        super(BurninTestResult, self).addFailure(test, err)
        self._test_failed(test, err)


# --------------------------------------------------------------------
110
# Helper Classes
111
112
# Too few public methods. pylint: disable-msg=R0903
# Too many instance attributes. pylint: disable-msg=R0902
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
113
114
115
116
class Clients(object):
    """Our kamaki clients"""
    auth_url = None
    token = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
117
    # Astakos
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
118
119
    astakos = None
    retry = CONNECTION_RETRY_LIMIT
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
120
    # Compute
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
121
122
    compute = None
    compute_url = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
123
124
    # Cyclades
    cyclades = None
125
126
127
    # Network
    network = None
    network_url = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
128
129
130
    # Pithos
    pithos = None
    pithos_url = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
131
132
133
    # Image
    image = None
    image_url = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
134

135
136
    def initialize_clients(self):
        """Initialize all the Kamaki Clients"""
137
        self.astakos = CachedAstakosClient(self.auth_url, self.token)
138
139
140
141
142
143
144
145
146
147
        self.astakos.CONNECTION_RETRY_LIMIT = self.retry

        self.compute_url = \
            self.astakos.get_service_endpoints('compute')['publicURL']
        self.compute = ComputeClient(self.compute_url, self.token)
        self.compute.CONNECTION_RETRY_LIMIT = self.retry

        self.cyclades = CycladesClient(self.compute_url, self.token)
        self.cyclades.CONNECTION_RETRY_LIMIT = self.retry

148
149
150
151
152
        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

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        self.pithos_url = self.astakos.\
            get_service_endpoints('object-store')['publicURL']
        self.pithos = PithosClient(self.pithos_url, self.token)
        self.pithos.CONNECTION_RETRY_LIMIT = self.retry

        self.image_url = \
            self.astakos.get_service_endpoints('image')['publicURL']
        self.image = ImageClient(self.image_url, self.token)
        self.image.CONNECTION_RETRY_LIMIT = self.retry


class Proper(object):
    """A descriptor used by tests implementing the TestCase class

    Since each instance of the TestCase will only be used to run a single
    test method (a new fixture is created for each test) the attributes can
    not be saved in the class instances. Instead we use descriptors.
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
170

171
172
173
174
175
176
177
178
179
180
181
182
183
    """
    def __init__(self, value=None):
        self.val = value

    def __get__(self, obj, objtype=None):
        return self.val

    def __set__(self, obj, value):
        self.val = value


# --------------------------------------------------------------------
# BurninTests class
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
184
185
186
# Too many public methods (45/20). pylint: disable-msg=R0904
class BurninTests(unittest.TestCase):
    """Common class that all burnin tests should implement"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
187
    clients = Clients()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
188
    run_id = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
189
190
191
192
    use_ipv6 = None
    action_timeout = None
    action_warning = None
    query_interval = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
193
    system_user = None
194
195
    images = None
    flavors = None
196
    delete_stale = False
197
    temp_directory = None
198
    failfast = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
199

200
201
    quotas = Proper(value=None)

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
202
203
204
205
206
207
208
209
210
    @classmethod
    def setUpClass(cls):  # noqa
        """Initialize BurninTests"""
        cls.suite_name = cls.__name__
        logger.testsuite_start(cls.suite_name)

        # Set test parameters
        cls.longMessage = True

211
    def test_000_clients_setup(self):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
212
213
        """Initializing astakos/cyclades/pithos clients"""
        # Update class attributes
214
        self.clients.initialize_clients()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
215
216
        self.info("Astakos auth url is %s", self.clients.auth_url)
        self.info("Cyclades url is %s", self.clients.compute_url)
217
        self.info("Network url is %s", self.clients.network_url)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
218
        self.info("Pithos url is %s", self.clients.pithos_url)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
219
        self.info("Image url is %s", self.clients.image_url)
220
221

        self.quotas = self._get_quotas()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
222
        self.info("  Disk usage is %s bytes",
223
224
225
                  self.quotas['system']['cyclades.disk']['usage'])
        self.info("  VM usage is %s",
                  self.quotas['system']['cyclades.vm']['usage'])
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
226
        self.info("  DiskSpace usage is %s bytes",
227
                  self.quotas['system']['pithos.diskspace']['usage'])
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
228
        self.info("  Ram usage is %s bytes",
229
                  self.quotas['system']['cyclades.ram']['usage'])
230
231
        self.info("  Floating IPs usage is %s",
                  self.quotas['system']['cyclades.floating_ip']['usage'])
232
233
234
235
        self.info("  CPU usage is %s",
                  self.quotas['system']['cyclades.cpu']['usage'])
        self.info("  Network usage is %s",
                  self.quotas['system']['cyclades.network.private']['usage'])
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
236

237
238
239
240
241
242
243
244
245
246
    def _run_tests(self, tcases):
        """Run some generated testcases"""
        global success  # Using global. pylint: disable-msg=C0103,W0603,W0602

        for tcase in tcases:
            self.info("Running testsuite %s", tcase.__name__)
            success = run_test(tcase) and success
            if self.failfast and not success:
                break

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
247
248
    # ----------------------------------
    # Loggers helper functions
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
    def log(self, msg, *args):
        """Pass the section value to logger"""
        logger.log(self.suite_name, msg, *args)

    def info(self, msg, *args):
        """Pass the section value to logger"""
        logger.info(self.suite_name, msg, *args)

    def debug(self, msg, *args):
        """Pass the section value to logger"""
        logger.debug(self.suite_name, msg, *args)

    def warning(self, msg, *args):
        """Pass the section value to logger"""
        logger.warning(self.suite_name, msg, *args)

    def error(self, msg, *args):
        """Pass the section value to logger"""
        logger.error(self.suite_name, msg, *args)

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
    # ----------------------------------
    # Helper functions that every testsuite may need
    def _get_uuid(self):
        """Get our uuid"""
        authenticate = self.clients.astakos.authenticate()
        uuid = authenticate['access']['user']['id']
        self.info("User's uuid is %s", uuid)
        return uuid

    def _get_username(self):
        """Get our User Name"""
        authenticate = self.clients.astakos.authenticate()
        username = authenticate['access']['user']['name']
        self.info("User's name is %s", username)
        return username

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
285
    def _create_tmp_directory(self):
286
287
        """Create a tmp directory"""
        temp_dir = tempfile.mkdtemp(dir=self.temp_directory)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
        self.info("Temp directory %s created", temp_dir)
        return temp_dir

    def _remove_tmp_directory(self, tmp_dir):
        """Remove a tmp directory"""
        try:
            shutil.rmtree(tmp_dir)
            self.info("Temp directory %s deleted", tmp_dir)
        except OSError:
            pass

    def _get_uuid_of_system_user(self):
        """Get the uuid of the system user

        This is the user that upload the 'official' images.

        """
        self.info("Getting the uuid of the system user")
        system_users = None
        if self.system_user is not None:
            parsed_su = parse_typed_option(self.system_user)
            if parsed_su is None:
                msg = "Invalid system-user format: %s. Must be [id|name]:.+"
                self.warning(msg, self.system_user)
            else:
                su_type, su_value = parsed_su
                if su_type == "name":
                    system_users = [su_value]
                elif su_type == "id":
                    self.info("System user's uuid is %s", su_value)
                    return su_value
                else:
                    self.error("Unrecognized system-user type %s", su_type)
                    self.fail("Unrecognized system-user type")

        if system_users is None:
            system_users = SYSTEM_USERS

        uuids = self.clients.astakos.usernames2uuids(system_users)
        for su_name in system_users:
            self.info("Trying username %s", su_name)
            if su_name in uuids:
                self.info("System user's uuid is %s", uuids[su_name])
                return uuids[su_name]

        self.warning("No system user found")
        return None

336
337
338
339
340
341
    def _skip_if(self, condition, msg):
        """Skip tests"""
        if condition:
            self.info("Test skipped: %s" % msg)
            self.skipTest(msg)

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
342
343
    # ----------------------------------
    # Flavors
344
345
346
347
348
349
350
351
352
    def _get_list_of_flavors(self, detail=False):
        """Get (detailed) list of flavors"""
        if detail:
            self.info("Getting detailed list of flavors")
        else:
            self.info("Getting simple list of flavors")
        flavors = self.clients.compute.list_flavors(detail=detail)
        return flavors

353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
    def _find_flavors(self, patterns, flavors=None):
        """Find a list of suitable flavors to use

        The patterns is a list of `typed_options'. A list of all flavors
        matching this patterns will be returned.

        """
        if flavors is None:
            flavors = self._get_list_of_flavors(detail=True)

        ret_flavors = []
        for ptrn in patterns:
            parsed_ptrn = parse_typed_option(ptrn)
            if parsed_ptrn is None:
                msg = "Invalid flavor format: %s. Must be [id|name]:.+"
                self.warning(msg, ptrn)
                continue
            flv_type, flv_value = parsed_ptrn
            if flv_type == "name":
                # Filter flavor by name
                msg = "Trying to find a flavor with name %s"
                self.info(msg, flv_value)
                filtered_flvs = \
                    [f for f in flavors if
                     re.search(flv_value, f['name'], flags=re.I) is not None]
            elif flv_type == "id":
                # Filter flavors by id
                msg = "Trying to find a flavor with id %s"
                self.info(msg, flv_value)
                filtered_flvs = \
                    [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)

        self.assertGreater(len(ret_flavors), 0,
                           "No matching flavors found")
        return ret_flavors

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
    # ----------------------------------
    # Images
    def _get_list_of_images(self, detail=False):
        """Get (detailed) list of images"""
        if detail:
            self.info("Getting detailed list of images")
        else:
            self.info("Getting simple list of images")
        images = self.clients.image.list_public(detail=detail)
        # Remove images registered by burnin
        images = [img for img in images
                  if not img['name'].startswith(SNF_TEST_PREFIX)]
        return images

    def _get_list_of_sys_images(self, images=None):
        """Get (detailed) list of images registered by system user or by me"""
        self.info("Getting list of images registered by system user or by me")
        if images is None:
            images = self._get_list_of_images(detail=True)

        su_uuid = self._get_uuid_of_system_user()
        my_uuid = self._get_uuid()
        ret_images = [i for i in images
                      if i['owner'] == su_uuid or i['owner'] == my_uuid]

        return ret_images

422
423
    def _find_images(self, patterns, images=None):
        """Find a list of suitable images to use
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
424

425
426
        The patterns is a list of `typed_options'. A list of all images
        matching this patterns will be returned.
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
427
428
429
430
431

        """
        if images is None:
            images = self._get_list_of_sys_images()

432
        ret_images = []
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
        for ptrn in patterns:
            parsed_ptrn = parse_typed_option(ptrn)
            if parsed_ptrn is None:
                msg = "Invalid image format: %s. Must be [id|name]:.+"
                self.warning(msg, ptrn)
                continue
            img_type, img_value = parsed_ptrn
            if img_type == "name":
                # Filter image by name
                msg = "Trying to find an image with name %s"
                self.info(msg, img_value)
                filtered_imgs = \
                    [i for i in images if
                     re.search(img_value, i['name'], flags=re.I) is not None]
            elif img_type == "id":
                # Filter images by id
                msg = "Trying to find an image with id %s"
                self.info(msg, img_value)
                filtered_imgs = \
                    [i for i in images if
                     i['id'].lower() == img_value.lower()]
            else:
                self.error("Unrecognized image type %s", img_type)
                self.fail("Unrecognized image type")

458
459
            # Append and continue
            ret_images.extend(filtered_imgs)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
460

461
462
463
        self.assertGreater(len(ret_images), 0,
                           "No matching images found")
        return ret_images
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
464
465
466

    # ----------------------------------
    # Pithos
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
467
    def _set_pithos_account(self, account):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
468
        """Set the Pithos account"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
469
470
        assert account, "No pithos account was given"

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
471
        self.info("Setting Pithos account to %s", account)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
472
473
        self.clients.pithos.account = account

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
474
475
476
477
478
479
480
    def _set_pithos_container(self, container):
        """Set the Pithos container"""
        assert container, "No pithos container was given"

        self.info("Setting Pithos container to %s", container)
        self.clients.pithos.container = container

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
    def _get_list_of_containers(self, account=None):
        """Get list of containers"""
        if account is not None:
            self._set_pithos_account(account)
        self.info("Getting list of containers")
        return self.clients.pithos.list_containers()

    def _create_pithos_container(self, container):
        """Create a pithos container

        If the container exists, nothing will happen

        """
        assert container, "No pithos container was given"

        self.info("Creating pithos container %s", container)
        self.clients.pithos.container = container
        self.clients.pithos.container_put()

500
501
502
503
    # ----------------------------------
    # Quotas
    def _get_quotas(self):
        """Get quotas"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
504
        self.info("Getting quotas")
505
506
507
        astakos_client = self.clients.astakos.get_client()
        return astakos_client.get_quotas()

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
508
509
510
    # Invalid argument name. pylint: disable-msg=C0103
    # Too many arguments. pylint: disable-msg=R0913
    def _check_quotas(self, disk=None, vm=None, diskspace=None,
511
                      ram=None, ip=None, cpu=None, network=None):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
512
513
        """Check that quotas' changes are consistent"""
        assert any(v is None for v in
514
                   [disk, vm, diskspace, ram, ip, cpu, network]), \
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
            "_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)
534
535
536
        # Check Floating IPs usage
        self._check_quotas_aux(
            old_quotas, new_quotas, 'cyclades.floating_ip', ip)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
        # 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)

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
555

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
556
557
# --------------------------------------------------------------------
# Initialize Burnin
558
def initialize(opts, testsuites, stale_testsuites):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
559
560
561
562
563
564
565
    """Initalize burnin

    Initialize our logger and burnin state

    """
    # Initialize logger
    global logger  # Using global statement. pylint: disable-msg=C0103,W0603
566
    curr_time = datetime.datetime.now()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
567
    logger = Log(opts.log_folder, verbose=opts.verbose,
568
                 use_colors=opts.use_colors, in_parallel=False,
569
                 log_level=opts.log_level, curr_time=curr_time)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
570
571
572
573
574
575

    # Initialize clients
    Clients.auth_url = opts.auth_url
    Clients.token = opts.token

    # Pass the rest options to BurninTests
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
576
577
578
579
    BurninTests.use_ipv6 = opts.use_ipv6
    BurninTests.action_timeout = opts.action_timeout
    BurninTests.action_warning = opts.action_warning
    BurninTests.query_interval = opts.query_interval
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
580
    BurninTests.system_user = opts.system_user
581
582
    BurninTests.flavors = opts.flavors
    BurninTests.images = opts.images
583
    BurninTests.delete_stale = opts.delete_stale
584
    BurninTests.temp_directory = opts.temp_directory
585
    BurninTests.failfast = opts.failfast
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
586
    BurninTests.run_id = SNF_TEST_PREFIX + \
587
        datetime.datetime.strftime(curr_time, "%Y%m%d%H%M%S")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
588
589

    # Choose tests to run
590
591
    if opts.show_stale:
        # We will run the stale_testsuites
592
        return (stale_testsuites, True)
593

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
594
595
596
597
598
599
    if opts.tests != "all":
        testsuites = opts.tests
    if opts.exclude_tests is not None:
        testsuites = [tsuite for tsuite in testsuites
                      if tsuite not in opts.exclude_tests]

600
    return (testsuites, opts.failfast)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
601
602
603
604


# --------------------------------------------------------------------
# Run Burnin
605
def run_burnin(testsuites, failfast=False):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
606
    """Run burnin testsuites"""
607
608
    # Using global. pylint: disable-msg=C0103,W0603,W0602
    global logger, success
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
609
610

    success = True
611
    run_tests(testsuites, failfast=failfast)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
612
613
614
615
616
617
618
619

    # Clean up our logger
    del(logger)

    # Return
    return 0 if success else 1


620
621
622
623
624
625
626
627
628
629
630
def run_tests(tcases, failfast=False):
    """Run some testcases"""
    global success  # Using global. pylint: disable-msg=C0103,W0603,W0602

    for tcase in tcases:
        was_success = run_test(tcase)
        success = success and was_success
        if failfast and not success:
            break


631
632
633
634
635
636
637
638
def run_test(tcase):
    """Run a testcase"""
    tsuite = unittest.TestLoader().loadTestsFromTestCase(tcase)
    results = tsuite.run(BurninTestResult())

    return was_successful(tcase.__name__, results.wasSuccessful())


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
639
640
# --------------------------------------------------------------------
# Helper functions
641
def was_successful(tsuite, successful):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
642
    """Handle whether a testsuite was succesful or not"""
643
    if successful:
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
644
        logger.testsuite_success(tsuite)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
645
        return True
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
646
647
    else:
        logger.testsuite_failure(tsuite)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
648
        return False
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663


def parse_typed_option(value):
    """Parse typed options (flavors and images)

    The options are in the form 'id:123-345' or 'name:^Debian Base$'

    """
    try:
        [type_, val] = value.strip().split(':')
        if type_ not in ["id", "name"]:
            raise ValueError
        return type_, val
    except ValueError:
        return None