Revision c2f037ff

b/snf-tools/synnefo_tools/burnin/common.py
63 63
MB = 2**20
64 64
GB = 2**30
65 65

  
66
QADD = 1
67
QREMOVE = -1
68

  
69
QDISK = "cyclades.disk"
70
QVM = "cyclades.vm"
71
QPITHOS = "pithos.diskspace"
72
QRAM = "cyclades.ram"
73
QIP = "cyclades.floating_ip"
74
QCPU = "cyclades.cpu"
75
QNET = "cyclades.network.private"
76

  
66 77

  
67 78
# --------------------------------------------------------------------
68 79
# BurninTestResult class
......
217 228
    failfast = None
218 229

  
219 230
    quotas = Proper(value=None)
231
    uuid = Proper(value=None)
220 232

  
221 233
    @classmethod
222 234
    def setUpClass(cls):  # noqa
......
288 300
    def error(self, msg, *args):
289 301
        """Pass the section value to logger"""
290 302
        logger.error(self.suite_name, msg, *args)
303
        self.fail(msg % args)
291 304

  
292 305
    # ----------------------------------
293 306
    # Helper functions that every testsuite may need
294 307
    def _get_uuid(self):
295 308
        """Get our uuid"""
296
        authenticate = self.clients.astakos.authenticate()
297
        uuid = authenticate['access']['user']['id']
298
        self.info("User's uuid is %s", uuid)
299
        return uuid
309
        if self.uuid is None:
310
            authenticate = self.clients.astakos.authenticate()
311
            self.uuid = authenticate['access']['user']['id']
312
            self.info("User's uuid is %s", self.uuid)
313
        return self.uuid
300 314

  
301 315
    def _get_username(self):
302 316
        """Get our User Name"""
......
337 351
                    return su_value
338 352
                else:
339 353
                    self.error("Unrecognized system-user type %s", su_type)
340
                    self.fail("Unrecognized system-user type")
341 354
            except ValueError:
342 355
                msg = "Invalid system-user format: %s. Must be [id|name]:.+"
343 356
                self.warning(msg, self.system_user)
......
412 425
                    [f for f in flavors if str(f['id']) == flv_value]
413 426
            else:
414 427
                self.error("Unrecognized flavor type %s", flv_type)
415
                self.fail("Unrecognized flavor type")
416 428

  
417 429
            # Append and continue
418 430
            ret_flavors.extend(filtered_flvs)
......
483 495
                     i['id'].lower() == img_value.lower()]
484 496
            else:
485 497
                self.error("Unrecognized image type %s", img_type)
486
                self.fail("Unrecognized image type")
487 498

  
488 499
            # Append and continue
489 500
            ret_images.extend(filtered_imgs)
......
536 547

  
537 548
    # pylint: disable=invalid-name
538 549
    # pylint: disable=too-many-arguments
539
    def _check_quotas(self, puuid=None, disk=None, vm=None, diskspace=None,
540
                      ram=None, ip=None, cpu=None, network=None):
550
    def _check_quotas(self, changes):
541 551
        """Check that quotas' changes are consistent
542 552

  
543
        @param puuid: The uuid of the project, quotas are assigned to
553
        @param changes: A dict of the changes that have been made in quotas
544 554

  
545 555
        """
546

  
547
        assert any(v is None for v in
548
                   [disk, vm, diskspace, ram, ip, cpu, network]), \
549
            "_check_quotas require arguments"
556
        if not changes:
557
            return
550 558

  
551 559
        self.info("Check that quotas' changes are consistent")
552 560
        old_quotas = self.quotas
553 561
        new_quotas = self._get_quotas()
554 562
        self.quotas = new_quotas
555 563

  
556
        user_uuid = self._get_uuid()
557
        if puuid is None:
558
            puuid = user_uuid
559

  
560 564
        self.assertListEqual(sorted(old_quotas.keys()),
561 565
                             sorted(new_quotas.keys()))
562
        for project in old_quotas.keys():
563
            # Check Disk usage
564
            project_name = self._get_project_name(project, user_uuid)
565
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
566
                                   project_name, "cyclades.disk",
567
                                   disk, project == puuid)
568
            # Check VM usage
569
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
570
                                   project_name, "cyclades.vm",
571
                                   vm, project == puuid)
572
            # Check DiskSpace usage
573
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
574
                                   project_name, "pithos.diskspace",
575
                                   diskspace, project == puuid)
576
            # Check Ram usage
577
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
578
                                   project_name, "cyclades.ram",
579
                                   ram, project == puuid)
580
            # Check Floating IPs usage
581
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
582
                                   project_name, "cyclades.floating_ip",
583
                                   ip, project == puuid)
584
            # Check CPU usage
585
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
586
                                   project_name, "cyclades.cpu",
587
                                   cpu, project == puuid)
588
            # Check Network usage
589
            self._check_quotas_aux(old_quotas[project], new_quotas[project],
590
                                   project_name, "cyclades.network.private",
591
                                   network, project == puuid)
592

  
593
    def _check_quotas_aux(self, old_quotas, new_quotas,
594
                          project_name, resource, value, check):
595
        """Auxiliary function for _check_quotas"""
596
        old_value = old_quotas[resource]['usage']
597
        new_value = new_quotas[resource]['usage']
598
        if check and value is not None:
599
            assert isinstance(value, int), \
600
                "%s value has to be integer" % resource
601
            old_value += value
602
        self.assertEqual(old_value, new_value,
603
                         "Project %s: %s quotas don't match" %
604
                         (project_name, resource))
566

  
567
        # Take old_quotas and apply changes
568
        for prj, values in changes.items():
569
            self.assertIn(prj, old_quotas.keys())
570
            for q_name, q_mult, q_value, q_unit in values:
571
                if q_unit is None:
572
                    q_unit = 1
573
                q_value = q_mult*int(q_value)*q_unit
574
                assert isinstance(q_value, int), \
575
                    "Project %s: %s value has to be integer" % (prj, q_name)
576
                old_quotas[prj][q_name]['usage'] += q_value
577
                old_quotas[prj][q_name]['project_usage'] += q_value
578

  
579
        self.assertEqual(old_quotas, new_quotas)
605 580

  
606 581
    # ----------------------------------
607 582
    # Projects
b/snf-tools/synnefo_tools/burnin/cyclades_common.py
49 49

  
50 50
from kamaki.clients import ClientError
51 51

  
52
from synnefo_tools.burnin.common import BurninTests, MB, GB
52
from synnefo_tools.burnin.common import BurninTests, MB, GB, QADD, QREMOVE, \
53
    QDISK, QVM, QRAM, QIP, QCPU, QNET
53 54

  
54 55

  
55 56
# pylint: disable=too-many-public-methods
56 57
class CycladesTests(BurninTests):
57 58
    """Extends the BurninTests class for Cyclades"""
59
    def _parse_images(self):
60
        """Find images given to command line"""
61
        if self.images is None:
62
            self.info("No --images given. Will use the default %s",
63
                      "^Debian Base$")
64
            filters = ["name:^Debian Base$"]
65
        else:
66
            filters = self.images
67
        avail_images = self._find_images(filters)
68
        self.info("Found %s images to choose from", len(avail_images))
69
        return avail_images
70

  
71
    def _parse_flavors(self):
72
        """Find flavors given to command line"""
73
        flavors = self._get_list_of_flavors(detail=True)
74

  
75
        if self.flavors is None:
76
            self.info("No --flavors given. Will use all of them")
77
            avail_flavors = flavors
78
        else:
79
            avail_flavors = self._find_flavors(self.flavors, flavors=flavors)
80

  
81
        self.info("Found %s flavors to choose from", len(avail_flavors))
82
        return avail_flavors
83

  
58 84
    def _try_until_timeout_expires(self, opmsg, check_fun):
59 85
        """Try to perform an action until timeout expires"""
60 86
        assert callable(check_fun), "Not a function"
......
113 139
                      server['name'], server['id'])
114 140
        return self.clients.cyclades.get_server_details(server['id'])
115 141

  
116
    def _create_server(self, image, flavor, personality=None, network=False):
142
    # pylint: disable=too-many-arguments
143
    def _create_server(self, image, flavor, personality=None,
144
                       network=False, project_id=None):
117 145
        """Create a new server"""
118 146
        if network:
119
            fip = self._create_floating_ip()
147
            fip = self._create_floating_ip(project_id=project_id)
120 148
            port = self._create_port(fip['floating_network_id'],
121 149
                                     floating_ip=fip)
122 150
            networks = [{'port': port['id']}]
......
129 157
        self.info("Using flavor %s with id %s", flavor['name'], flavor['id'])
130 158
        server = self.clients.cyclades.create_server(
131 159
            servername, flavor['id'], image['id'],
132
            personality=personality, networks=networks)
160
            personality=personality, networks=networks,
161
            project=project_id)
133 162

  
134 163
        self.info("Server id: %s", server['id'])
135 164
        self.info("Server password: %s", server['adminPass'])
......
138 167
        self.assertEqual(server['flavor']['id'], flavor['id'])
139 168
        self.assertEqual(server['image']['id'], image['id'])
140 169
        self.assertEqual(server['status'], "BUILD")
170
        if project_id is None:
171
            project_id = self._get_uuid()
172
        self.assertEqual(server['tenant_id'], project_id)
141 173

  
142 174
        # Verify quotas
143
        self._check_quotas(disk=+int(flavor['disk'])*GB,
144
                           vm=+1,
145
                           ram=+int(flavor['ram'])*MB,
146
                           cpu=+int(flavor['vcpus']))
175
        changes = \
176
            {project_id:
177
                [(QDISK, QADD, flavor['disk'], GB),
178
                 (QVM, QADD, 1, None),
179
                 (QRAM, QADD, flavor['ram'], MB),
180
                 (QCPU, QADD, flavor['vcpus'], None)]}
181
        self._check_quotas(changes)
147 182

  
148 183
        return server
149 184

  
......
180 215
            self.assertNotIn(srv['id'], new_servers)
181 216

  
182 217
        # Verify quotas
183
        flavors = \
184
            [self.clients.compute.get_flavor_details(srv['flavor']['id'])
185
             for srv in servers]
186
        self._verify_quotas_deleted(flavors)
218
        self._verify_quotas_deleted(servers)
187 219

  
188
    def _verify_quotas_deleted(self, flavors):
220
    def _verify_quotas_deleted(self, servers):
189 221
        """Verify quotas for a number of deleted servers"""
190
        used_disk = 0
191
        used_vm = 0
192
        used_ram = 0
193
        used_cpu = 0
194
        for flavor in flavors:
195
            used_disk += int(flavor['disk']) * GB
196
            used_vm += 1
197
            used_ram += int(flavor['ram']) * MB
198
            used_cpu += int(flavor['vcpus'])
199
        self._check_quotas(disk=-used_disk,
200
                           vm=-used_vm,
201
                           ram=-used_ram,
202
                           cpu=-used_cpu)
222
        changes = dict()
223
        for server in servers:
224
            project = server['tenant_id']
225
            if project not in changes:
226
                changes[project] = []
227
            flavor = \
228
                self.clients.compute.get_flavor_details(server['flavor']['id'])
229
            new_changes = [
230
                (QDISK, QREMOVE, flavor['disk'], GB),
231
                (QVM, QREMOVE, 1, None),
232
                (QRAM, QREMOVE, flavor['ram'], MB),
233
                (QCPU, QREMOVE, flavor['vcpus'], None)]
234
            changes[project].extend(new_changes)
235

  
236
        self._check_quotas(changes)
203 237

  
204 238
    def _get_connection_username(self, server):
205 239
        """Determine the username to use to connect to the server"""
......
320 354
                           "Can not get IPs from server attachments")
321 355

  
322 356
        for addr in addrs:
323
            self.assertEquals(IPy.IP(addr).version(), version)
357
            self.assertEqual(IPy.IP(addr).version(), version)
324 358

  
325 359
        if network is None:
326 360
            msg = "Server's public IPv%s is %s"
......
411 445

  
412 446
    # ----------------------------------
413 447
    # Networks
414
    def _create_network(self, cidr="10.0.1.0/28", dhcp=True):
448
    def _create_network(self, cidr="10.0.1.0/28", dhcp=True,
449
                        project_id=None):
415 450
        """Create a new private network"""
416 451
        name = self.run_id
417 452
        network = self.clients.network.create_network(
418
            "MAC_FILTERED", name=name, shared=False)
453
            "MAC_FILTERED", name=name, shared=False,
454
            project=project_id)
419 455
        self.info("Network with id %s created", network['id'])
420 456
        subnet = self.clients.network.create_subnet(
421 457
            network['id'], cidr=cidr, enable_dhcp=dhcp)
422 458
        self.info("Subnet with id %s created", subnet['id'])
423 459

  
424 460
        # Verify quotas
425
        self._check_quotas(network=+1)
461
        if project_id is None:
462
            project_id = self._get_uuid()
463
        changes = \
464
            {project_id: [(QNET, QADD, 1, None)]}
465
        self._check_quotas(changes)
426 466

  
427 467
        #Test if the right name is assigned
428 468
        self.assertEqual(network['name'], name)
469
        self.assertEqual(network['tenant_id'], project_id)
429 470

  
430 471
        return network
431 472

  
......
450 491
            self.assertNotIn(net['id'], new_networks)
451 492

  
452 493
        # Verify quotas
453
        self._check_quotas(network=-len(networks))
494
        changes = \
495
            {self._get_uuid(): [(QNET, QREMOVE, len(networks), None)]}
496
        self._check_quotas(changes)
454 497

  
455 498
    def _get_public_network(self, networks=None):
456 499
        """Get the public network"""
......
462 505
                return net
463 506
        self.fail("Could not find a public network to use")
464 507

  
465
    def _create_floating_ip(self):
508
    def _create_floating_ip(self, project_id=None):
466 509
        """Create a new floating ip"""
467 510
        pub_net = self._get_public_network()
468 511
        self.info("Creating a new floating ip for network with id %s",
469 512
                  pub_net['id'])
470
        fip = self.clients.network.create_floatingip(pub_net['id'])
513
        fip = self.clients.network.create_floatingip(
514
            pub_net['id'], project=project_id)
471 515
        # Verify that floating ip has been created
472 516
        fips = self.clients.network.list_floatingips()
473 517
        fips = [f['id'] for f in fips]
474 518
        self.assertIn(fip['id'], fips)
475 519
        # Verify quotas
476
        self._check_quotas(ip=+1)
520
        if project_id is None:
521
            project_id = self._get_uuid()
522
        changes = \
523
            {project_id: [(QIP, QADD, 1, None)]}
524
        self._check_quotas(changes)
525

  
477 526
        # Check that IP is IPv4
478
        self.assertEquals(IPy.IP(fip['floating_ip_address']).version(), 4)
527
        self.assertEqual(IPy.IP(fip['floating_ip_address']).version(), 4)
528
        self.assertEqual(fip['tenant_id'], project_id)
479 529

  
480 530
        self.info("Floating IP %s with id %s created",
481 531
                  fip['floating_ip_address'], fip['id'])
......
588 638
        list_ips = [f['id'] for f in self.clients.network.list_floatingips()]
589 639
        for fip in fips:
590 640
            self.assertNotIn(fip['id'], list_ips)
641

  
591 642
        # Verify quotas
592
        self._check_quotas(ip=-len(fips))
643
        changes = dict()
644
        for fip in fips:
645
            project = fip['tenant_id']
646
            if project not in changes:
647
                changes[project] = []
648
            changes[project].append((QIP, QREMOVE, 1, None))
649
        self._check_quotas(changes)
650

  
651
    def _find_project(self, flavors, projects=None):
652
        """Return a pair of flavor, project that we can use"""
653
        if projects is None:
654
            projects = self.quotas.keys()
655

  
656
        # XXX: Well there seems to be no easy way to find how many resources
657
        # we have left in a project (we have to substract usage from limit,
658
        # check both per_user and project quotas, blah, blah). For now
659
        # just return the first flavor with the first project and lets hope
660
        # that it fits.
661
        return (flavors[0], projects[0])
662

  
663
        # # Get only the quotas for the given 'projects'
664
        # quotas = dict()
665
        # for prj, qts in self.quotas.items():
666
        #     if prj in projects:
667
        #         quotas[prj] = qts
668
        #
669
        # results = []
670
        # for flv in flavors:
671
        #     for prj, qts in quotas.items():
672
        #         self.debug("Testing flavor %s, project %s", flv['name'], prj)
673
        #         condition = \
674
        #             (flv['ram'] <= qts['cyclades.ram']['usage'] and
675
        #              flv['vcpus'] <= qts['cyclades.cpu']['usage'] and
676
        #              flv['disk'] <= qts['cyclades.disk']['usage'] and
677
        #              qts['cyclades.vm']['usage'] >= 1)
678
        #         if condition:
679
        #             results.append((flv, prj))
680
        #
681
        # if not results:
682
        #     msg = "Couldn't find a suitable flavor to use for current qutoas"
683
        #     self.error(msg)
684
        #
685
        # return random.choice(results)
593 686

  
594 687

  
595 688
class Retry(Exception):
b/snf-tools/synnefo_tools/burnin/images_tests.py
42 42

  
43 43
from kamaki.clients import ClientError
44 44

  
45
from synnefo_tools.burnin.common import BurninTests, Proper
45
from synnefo_tools.burnin.common import BurninTests, Proper, \
46
    QPITHOS, QADD, QREMOVE
46 47

  
47 48

  
48 49
# pylint: disable=too-many-public-methods
......
171 172
            self.clients.pithos.upload_object(self.temp_image_name, fin)
172 173

  
173 174
        # Verify quotas
174
        self._check_quotas(diskspace=file_size)
175
        changes = \
176
            {self._get_uuid(): [(QPITHOS, QADD, file_size, None)]}
177
        self._check_quotas(changes)
175 178

  
176 179
    def test_009_register_image(self):
177 180
        """Register image to Plankton"""
......
198 201
        self.clients.pithos.del_object(self.temp_image_name)
199 202
        # Verify quotas
200 203
        file_size = os.path.getsize(self.temp_image_file)
201
        self._check_quotas(diskspace=-file_size)
204
        changes = \
205
            {self._get_uuid(): [(QPITHOS, QREMOVE, file_size, None)]}
206
        self._check_quotas(changes)
202 207
        self.temp_image_name = None
203 208
        # Remove temp directory
204 209
        self.info("Deleting temp directory %s", self.temp_dir)
b/snf-tools/synnefo_tools/burnin/network_tests.py
53 53

  
54 54
    def test_001_images_to_use(self):
55 55
        """Find images to be used to create our machines"""
56
        if self.images is None:
57
            self.info("No --images given. Will use the default %s",
58
                      "^Debian Base$")
59
            filters = ["name:^Debian Base$"]
60
        else:
61
            filters = self.images
62

  
63
        self.avail_images = self._find_images(filters)
64
        self.info("Found %s images to choose from", len(self.avail_images))
56
        self.avail_images = self._parse_images()
65 57

  
66 58
    def test_002_flavors_to_use(self):
67 59
        """Find flavors to be used to create our machines"""
68
        flavors = self._get_list_of_flavors(detail=True)
69

  
70
        if self.flavors is None:
71
            self.info("No --flavors given. Will use all of them")
72
            self.avail_flavors = flavors
73
        else:
74
            self.avail_flavors = self._find_flavors(
75
                self.flavors, flavors=flavors)
76
        self.info("Found %s flavors to choose from", len(self.avail_flavors))
60
        self.avail_flavors = self._parse_flavors()
77 61

  
78 62
    def test_003_submit_create_server_a(self):
79 63
        """Submit create server request for server A"""
b/snf-tools/synnefo_tools/burnin/pithos_tests.py
40 40
import random
41 41
import tempfile
42 42

  
43
from synnefo_tools.burnin.common import BurninTests, Proper
43
from synnefo_tools.burnin.common import BurninTests, Proper, \
44
    QPITHOS, QADD, QREMOVE
44 45

  
45 46

  
46 47
# pylint: disable=too-many-public-methods
......
92 93
            # The container is the one choosen during the `create_container'
93 94
            self.clients.pithos.upload_object("test.txt", fout)
94 95
            # Verify quotas
95
            self._check_quotas(diskspace=+os.fstat(fout.fileno()).st_size)
96
            size = os.fstat(fout.fileno()).st_size
97
            changes = \
98
                {self._get_uuid(): [(QPITHOS, QADD, size, None)]}
99
            self._check_quotas(changes)
96 100

  
97 101
    def test_005_download_file(self):
98 102
        """Test downloading the file from Pithos"""
......
116 120
        self.clients.pithos.del_object("test.txt")
117 121

  
118 122
        # Verify quotas
119
        self._check_quotas(diskspace=-int(content_length))
123
        changes = \
124
            {self._get_uuid(): [(QPITHOS, QREMOVE, content_length, None)]}
125
        self._check_quotas(changes)
120 126

  
121 127
        self.info("Removing the container %s", self.created_container)
122 128
        self.clients.pithos.purge_container()
b/snf-tools/synnefo_tools/burnin/projects_tests.py
36 36

  
37 37
"""
38 38

  
39
from synnefo_tools.burnin.common import BurninTests, Proper
39
import random
40

  
41
from synnefo_tools.burnin.common import Proper
42
from synnefo_tools.burnin.cyclades_common import CycladesTests
40 43

  
41 44

  
42 45
# pylint: disable=too-many-public-methods
43
class QuotasTestSuite(BurninTests):
46
class QuotasTestSuite(CycladesTests):
44 47
    """Test Quotas functionality"""
45
    project = Proper(value=None)
48
    server = Proper(value=None)
46 49

  
47 50
    def test_001_check_skip(self):
48 51
        """Check if we are members in more than one projects"""
49 52
        self._skip_suite_if(len(self.quotas.keys()) < 2,
50 53
                            "This user is not a member of 2 or more projects")
54

  
55
    def test_002_create(self):
56
        """Create a machine to a different project than base"""
57
        image = random.choice(self._parse_images())
58
        flavors = self._parse_flavors()
59

  
60
        # We want to create our machine in a project other than 'base'
61
        projects = self.quotas.keys()
62
        projects.remove(self._get_uuid())
63
        (flavor, project) = self._find_project(flavors, projects)
64

  
65
        # Create machine
66
        self.server = self._create_server(image, flavor, network=True,
67
                                          project_id=project)
68

  
69
        # Wait for server to become active
70
        self._insist_on_server_transition(
71
            self.server, ["BUILD"], "ACTIVE")
b/snf-tools/synnefo_tools/burnin/server_tests.py
44 44

  
45 45
from vncauthproxy.d3des import generate_response as d3des_generate_response
46 46

  
47
from synnefo_tools.burnin.common import BurninTests, Proper
47
from synnefo_tools.burnin.common import Proper
48 48
from synnefo_tools.burnin.cyclades_common import CycladesTests
49 49

  
50 50

  
......
290 290
# will run the same tests using different images and or flavors.
291 291
# The creation and running of our GeneratedServerTestSuite class will
292 292
# happen as a testsuite itself (everything here is a test!).
293
class ServerTestSuite(BurninTests):
293
class ServerTestSuite(CycladesTests):
294 294
    """Generate and run the GeneratedServerTestSuite
295 295

  
296 296
    We will generate as many testsuites as the number of images given.
......
303 303

  
304 304
    def test_001_images_to_use(self):
305 305
        """Find images to be used by GeneratedServerTestSuite"""
306
        if self.images is None:
307
            self.info("No --images given. Will use the default %s",
308
                      "^Debian Base$")
309
            filters = ["name:^Debian Base$"]
310
        else:
311
            filters = self.images
312

  
313
        self.avail_images = self._find_images(filters)
314
        self.info("Found %s images. Let's create an equal number of tests",
315
                  len(self.avail_images))
306
        self.avail_images = self._parse_images()
316 307

  
317 308
    def test_002_flavors_to_use(self):
318 309
        """Find flavors to be used by GeneratedServerTestSuite"""
319
        flavors = self._get_list_of_flavors(detail=True)
320

  
321
        if self.flavors is None:
322
            self.info("No --flavors given. Will use all of them")
323
            self.avail_flavors = flavors
324
        else:
325
            self.avail_flavors = self._find_flavors(
326
                self.flavors, flavors=flavors)
327
        self.info("Found %s flavors to choose from", len(self.avail_flavors))
310
        self.avail_flavors = self._parse_flavors()
328 311

  
329 312
    def test_003_create_testsuites(self):
330 313
        """Generate the GeneratedServerTestSuite tests"""

Also available in: Unified diff