common.py 21.8 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
41
import os
import re
import shutil
42
import unittest
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
43
import datetime
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
44
import tempfile
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
45
46
import traceback

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

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


# --------------------------------------------------------------------
# Global variables
logger = 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
125
126
127
    # Cyclades
    cyclades = None
    # Pithos
    pithos = None
    pithos_url = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
128
129
130
    # Image
    image = None
    image_url = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
131

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
    def initialize_clients(self):
        """Initialize all the Kamaki Clients"""
        self.astakos = AstakosClient(self.auth_url, self.token)
        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

        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
162

163
164
165
166
167
168
169
170
171
172
173
174
175
    """
    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
176
177
178
# 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
179
    clients = Clients()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
180
    run_id = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
181
182
183
184
    use_ipv6 = None
    action_timeout = None
    action_warning = None
    query_interval = None
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
185
    system_user = None
186
187
    images = None
    flavors = None
188
    delete_stale = False
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
189

190
191
    quotas = Proper(value=None)

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
192
193
194
195
196
197
198
199
200
    @classmethod
    def setUpClass(cls):  # noqa
        """Initialize BurninTests"""
        cls.suite_name = cls.__name__
        logger.testsuite_start(cls.suite_name)

        # Set test parameters
        cls.longMessage = True

201
    def test_000_clients_setup(self):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
202
203
        """Initializing astakos/cyclades/pithos clients"""
        # Update class attributes
204
        self.clients.initialize_clients()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
205
206
        self.info("Astakos auth url is %s", self.clients.auth_url)
        self.info("Cyclades url is %s", self.clients.compute_url)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
207
        self.info("Pithos url is %s", self.clients.pithos_url)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
208
        self.info("Image url is %s", self.clients.image_url)
209
210

        self.quotas = self._get_quotas()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
211
        self.info("  Disk usage is %s bytes",
212
213
214
                  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
215
        self.info("  DiskSpace usage is %s bytes",
216
                  self.quotas['system']['pithos.diskspace']['usage'])
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
217
        self.info("  Ram usage is %s bytes",
218
219
220
221
222
                  self.quotas['system']['cyclades.ram']['usage'])
        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
223

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
224
225
    # ----------------------------------
    # Loggers helper functions
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    # ----------------------------------
    # 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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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
    def _create_tmp_directory(self):
        """Create a tmp directory

        In my machine /tmp has not enough space for an image
        to be saves, so we are going to use the current directory.

        """
        temp_dir = tempfile.mkdtemp(dir=os.getcwd())
        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

318
319
320
321
322
323
    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
324
325
    # ----------------------------------
    # Flavors
326
327
328
329
330
331
332
333
334
    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

335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
    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
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
    # ----------------------------------
    # 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

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

407
408
        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
409
410
411
412
413

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

414
        ret_images = []
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
        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")

440
441
            # Append and continue
            ret_images.extend(filtered_imgs)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
442

443
444
445
        self.assertGreater(len(ret_images), 0,
                           "No matching images found")
        return ret_images
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
446
447
448

    # ----------------------------------
    # Pithos
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
449
    def _set_pithos_account(self, account):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
450
        """Set the Pithos account"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
451
452
        assert account, "No pithos account was given"

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
453
        self.info("Setting Pithos account to %s", account)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
454
455
        self.clients.pithos.account = account

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
456
457
458
459
460
461
462
    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
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
    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()

482
483
484
485
    # ----------------------------------
    # Quotas
    def _get_quotas(self):
        """Get quotas"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
486
        self.info("Getting quotas")
487
488
489
        astakos_client = self.clients.astakos.get_client()
        return astakos_client.get_quotas()

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
    # 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)

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
534

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
535
536
# --------------------------------------------------------------------
# Initialize Burnin
537
def initialize(opts, testsuites, stale_testsuites):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
538
539
540
541
542
543
544
    """Initalize burnin

    Initialize our logger and burnin state

    """
    # Initialize logger
    global logger  # Using global statement. pylint: disable-msg=C0103,W0603
545
    curr_time = datetime.datetime.now()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
546
    logger = Log(opts.log_folder, verbose=opts.verbose,
547
                 use_colors=opts.use_colors, in_parallel=False,
548
                 log_level=opts.log_level, curr_time=curr_time)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
549
550
551
552
553
554

    # 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
555
556
557
558
    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
559
    BurninTests.system_user = opts.system_user
560
561
    BurninTests.flavors = opts.flavors
    BurninTests.images = opts.images
562
    BurninTests.delete_stale = opts.delete_stale
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
563
    BurninTests.run_id = SNF_TEST_PREFIX + \
564
        datetime.datetime.strftime(curr_time, "%Y%m%d%H%M%S")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
565
566

    # Choose tests to run
567
568
    if opts.show_stale:
        # We will run the stale_testsuites
569
        return (stale_testsuites, True)
570

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
571
572
573
574
575
576
    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]

577
    return (testsuites, opts.failfast)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
578
579
580
581


# --------------------------------------------------------------------
# Run Burnin
582
def run_burnin(testsuites, failfast=False):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
583
584
585
586
587
    """Run burnin testsuites"""
    global logger  # Using global. pylint: disable-msg=C0103,W0603,W0602

    success = True
    for tcase in testsuites:
588
        was_success = run_test(tcase)
589
        success = success and was_success
590
591
        if failfast and not success:
            break
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
592
593
594
595
596
597
598
599

    # Clean up our logger
    del(logger)

    # Return
    return 0 if success else 1


600
601
602
603
604
605
606
607
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
608
609
# --------------------------------------------------------------------
# Helper functions
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
610
def was_successful(tsuite, success):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
611
612
613
    """Handle whether a testsuite was succesful or not"""
    if success:
        logger.testsuite_success(tsuite)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
614
        return True
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
615
616
    else:
        logger.testsuite_failure(tsuite)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
617
        return False
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632


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