Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin / cyclades_common.py @ c2f037ff

History | View | Annotate | Download (27.2 kB)

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

    
34
"""
35
Utility functions for Cyclades Tests
36
Cyclades require a lot helper functions and `common'
37
had grown too much.
38

39
"""
40

    
41
import time
42
import IPy
43
import base64
44
import socket
45
import random
46
import paramiko
47
import tempfile
48
import subprocess
49

    
50
from kamaki.clients import ClientError
51

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

    
55

    
56
# pylint: disable=too-many-public-methods
57
class CycladesTests(BurninTests):
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

    
84
    def _try_until_timeout_expires(self, opmsg, check_fun):
85
        """Try to perform an action until timeout expires"""
86
        assert callable(check_fun), "Not a function"
87

    
88
        action_timeout = self.action_timeout
89
        action_warning = self.action_warning
90
        if action_warning > action_timeout:
91
            action_warning = action_timeout
92

    
93
        start_time = int(time.time())
94
        end_time = start_time + action_warning
95
        while end_time > time.time():
96
            try:
97
                ret_value = check_fun()
98
                self.info("Operation `%s' finished in %s seconds",
99
                          opmsg, int(time.time()) - start_time)
100
                return ret_value
101
            except Retry:
102
                time.sleep(self.query_interval)
103
        self.warning("Operation `%s' is taking too long after %s seconds",
104
                     opmsg, int(time.time()) - start_time)
105

    
106
        end_time = start_time + action_timeout
107
        while end_time > time.time():
108
            try:
109
                ret_value = check_fun()
110
                self.info("Operation `%s' finished in %s seconds",
111
                          opmsg, int(time.time()) - start_time)
112
                return ret_value
113
            except Retry:
114
                time.sleep(self.query_interval)
115
        self.error("Operation `%s' timed out after %s seconds",
116
                   opmsg, int(time.time()) - start_time)
117
        self.fail("time out")
118

    
119
    def _get_list_of_servers(self, detail=False):
120
        """Get (detailed) list of servers"""
121
        if detail:
122
            self.info("Getting detailed list of servers")
123
        else:
124
            self.info("Getting simple list of servers")
125
        return self.clients.cyclades.list_servers(detail=detail)
126

    
127
    def _get_list_of_networks(self, detail=False):
128
        """Get (detailed) list of networks"""
129
        if detail:
130
            self.info("Getting detailed list of networks")
131
        else:
132
            self.info("Getting simple list of networks")
133
        return self.clients.network.list_networks(detail=detail)
134

    
135
    def _get_server_details(self, server, quiet=False):
136
        """Get details for a server"""
137
        if not quiet:
138
            self.info("Getting details for server %s with id %s",
139
                      server['name'], server['id'])
140
        return self.clients.cyclades.get_server_details(server['id'])
141

    
142
    # pylint: disable=too-many-arguments
143
    def _create_server(self, image, flavor, personality=None,
144
                       network=False, project_id=None):
145
        """Create a new server"""
146
        if network:
147
            fip = self._create_floating_ip(project_id=project_id)
148
            port = self._create_port(fip['floating_network_id'],
149
                                     floating_ip=fip)
150
            networks = [{'port': port['id']}]
151
        else:
152
            networks = None
153

    
154
        servername = "%s for %s" % (self.run_id, image['name'])
155
        self.info("Creating a server with name %s", servername)
156
        self.info("Using image %s with id %s", image['name'], image['id'])
157
        self.info("Using flavor %s with id %s", flavor['name'], flavor['id'])
158
        server = self.clients.cyclades.create_server(
159
            servername, flavor['id'], image['id'],
160
            personality=personality, networks=networks,
161
            project=project_id)
162

    
163
        self.info("Server id: %s", server['id'])
164
        self.info("Server password: %s", server['adminPass'])
165

    
166
        self.assertEqual(server['name'], servername)
167
        self.assertEqual(server['flavor']['id'], flavor['id'])
168
        self.assertEqual(server['image']['id'], image['id'])
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)
173

    
174
        # Verify quotas
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)
182

    
183
        return server
184

    
185
    def _delete_servers(self, servers, error=False):
186
        """Deleting a number of servers in parallel"""
187
        # Disconnect floating IPs
188
        if not error:
189
            # If there is the possibility for the machine to be in
190
            # ERROR state we cannot delete its ports.
191
            for srv in servers:
192
                self.info(
193
                    "Disconnecting all floating IPs from server with id %s",
194
                    srv['id'])
195
                self._disconnect_from_network(srv)
196

    
197
        # Delete servers
198
        for srv in servers:
199
            self.info("Sending the delete request for server with id %s",
200
                      srv['id'])
201
            self.clients.cyclades.delete_server(srv['id'])
202

    
203
        if error:
204
            curr_states = ["ACTIVE", "ERROR", "STOPPED", "BUILD"]
205
        else:
206
            curr_states = ["ACTIVE"]
207
        for srv in servers:
208
            self._insist_on_server_transition(srv, curr_states, "DELETED")
209

    
210
        # Servers no longer in server list
211
        new_servers = [s['id'] for s in self._get_list_of_servers()]
212
        for srv in servers:
213
            self.info("Verifying that server with id %s is no longer in "
214
                      "server list", srv['id'])
215
            self.assertNotIn(srv['id'], new_servers)
216

    
217
        # Verify quotas
218
        self._verify_quotas_deleted(servers)
219

    
220
    def _verify_quotas_deleted(self, servers):
221
        """Verify quotas for a number of deleted servers"""
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)
237

    
238
    def _get_connection_username(self, server):
239
        """Determine the username to use to connect to the server"""
240
        users = server['metadata'].get("users", None)
241
        ret_user = None
242
        if users is not None:
243
            user_list = users.split()
244
            if "root" in user_list:
245
                ret_user = "root"
246
            else:
247
                ret_user = random.choice(user_list)
248
        else:
249
            # Return the login name for connections based on the server OS
250
            self.info("Could not find `users' metadata in server. Let's guess")
251
            os_value = server['metadata'].get("os")
252
            if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
253
                ret_user = "user"
254
            elif os_value in ("windows", "windows_alpha1"):
255
                ret_user = "Administrator"
256
            else:
257
                ret_user = "root"
258

    
259
        self.assertIsNotNone(ret_user)
260
        self.info("User's login name: %s", ret_user)
261
        return ret_user
262

    
263
    def _insist_on_server_transition(self, server, curr_statuses, new_status):
264
        """Insist on server transiting from curr_statuses to new_status"""
265
        def check_fun():
266
            """Check server status"""
267
            srv = self._get_server_details(server, quiet=True)
268
            if srv['status'] in curr_statuses:
269
                raise Retry()
270
            elif srv['status'] == new_status:
271
                return
272
            else:
273
                msg = "Server \"%s\" with id %s went to unexpected status %s"
274
                self.error(msg, server['name'], server['id'], srv['status'])
275
                self.fail(msg % (server['name'], server['id'], srv['status']))
276
        opmsg = "Waiting for server \"%s\" with id %s to become %s"
277
        self.info(opmsg, server['name'], server['id'], new_status)
278
        opmsg = opmsg % (server['name'], server['id'], new_status)
279
        self._try_until_timeout_expires(opmsg, check_fun)
280

    
281
    def _insist_on_network_transition(self, network,
282
                                      curr_statuses, new_status):
283
        """Insist on network transiting from curr_statuses to new_status"""
284
        def check_fun():
285
            """Check network status"""
286
            ntw = self.clients.network.get_network_details(network['id'])
287
            if ntw['status'] in curr_statuses:
288
                raise Retry()
289
            elif ntw['status'] == new_status:
290
                return
291
            else:
292
                msg = "Network %s with id %s went to unexpected status %s"
293
                self.error(msg, network['name'], network['id'], ntw['status'])
294
                self.fail(msg %
295
                          (network['name'], network['id'], ntw['status']))
296
        opmsg = "Waiting for network \"%s\" with id %s to become %s"
297
        self.info(opmsg, network['name'], network['id'], new_status)
298
        opmsg = opmsg % (network['name'], network['id'], new_status)
299
        self._try_until_timeout_expires(opmsg, check_fun)
300

    
301
    def _insist_on_tcp_connection(self, family, host, port):
302
        """Insist on tcp connection"""
303
        def check_fun():
304
            """Get a connected socket from the specified family to host:port"""
305
            sock = None
306
            for res in socket.getaddrinfo(host, port, family,
307
                                          socket.SOCK_STREAM, 0,
308
                                          socket.AI_PASSIVE):
309
                fam, socktype, proto, _, saddr = res
310
                try:
311
                    sock = socket.socket(fam, socktype, proto)
312
                except socket.error:
313
                    sock = None
314
                    continue
315
                try:
316
                    sock.connect(saddr)
317
                except socket.error:
318
                    sock.close()
319
                    sock = None
320
                    continue
321
            if sock is None:
322
                raise Retry
323
            return sock
324
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
325
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
326
        opmsg = "Connecting over %s to %s:%s"
327
        self.info(opmsg, familystr.get(family, "Unknown"), host, port)
328
        opmsg = opmsg % (familystr.get(family, "Unknown"), host, port)
329
        return self._try_until_timeout_expires(opmsg, check_fun)
330

    
331
    def _get_ips(self, server, version=4, network=None):
332
        """Get the IPs of a server from the detailed server info
333

334
        If network not given then get the public IPs. Else the IPs
335
        attached to that network
336

337
        """
338
        assert version in (4, 6)
339

    
340
        nics = server['attachments']
341
        addrs = []
342
        for nic in nics:
343
            net_id = nic['network_id']
344
            if network is None:
345
                if self.clients.network.get_network_details(net_id)['public']:
346
                    if nic['ipv' + str(version)]:
347
                        addrs.append(nic['ipv' + str(version)])
348
            else:
349
                if net_id == network['id']:
350
                    if nic['ipv' + str(version)]:
351
                        addrs.append(nic['ipv' + str(version)])
352

    
353
        self.assertGreater(len(addrs), 0,
354
                           "Can not get IPs from server attachments")
355

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

    
359
        if network is None:
360
            msg = "Server's public IPv%s is %s"
361
            for addr in addrs:
362
                self.info(msg, version, addr)
363
        else:
364
            msg = "Server's IPv%s attached to network \"%s\" is %s"
365
            for addr in addrs:
366
                self.info(msg, version, network['id'], addr)
367
        return addrs
368

    
369
    def _insist_on_ping(self, ip_addr, version=4):
370
        """Test server responds to a single IPv4 of IPv6 ping"""
371
        def check_fun():
372
            """Ping to server"""
373
            cmd = ("ping%s -c 3 -w 20 %s" %
374
                   ("6" if version == 6 else "", ip_addr))
375
            ping = subprocess.Popen(
376
                cmd, shell=True, stdout=subprocess.PIPE,
377
                stderr=subprocess.PIPE)
378
            ping.communicate()
379
            ret = ping.wait()
380
            if ret != 0:
381
                raise Retry
382
        assert version in (4, 6)
383
        opmsg = "Sent IPv%s ping requests to %s"
384
        self.info(opmsg, version, ip_addr)
385
        opmsg = opmsg % (version, ip_addr)
386
        self._try_until_timeout_expires(opmsg, check_fun)
387

    
388
    def _image_is(self, image, osfamily):
389
        """Return true if the image is of `osfamily'"""
390
        d_image = self.clients.cyclades.get_image_details(image['id'])
391
        return d_image['metadata']['osfamily'].lower().find(osfamily) >= 0
392

    
393
    # pylint: disable=no-self-use
394
    def _ssh_execute(self, hostip, username, password, command):
395
        """Execute a command via ssh"""
396
        ssh = paramiko.SSHClient()
397
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
398
        try:
399
            ssh.connect(hostip, username=username, password=password)
400
        except paramiko.SSHException as err:
401
            if err.args[0] == "Error reading SSH protocol banner":
402
                raise Retry()
403
            else:
404
                raise
405
        _, stdout, _ = ssh.exec_command(command)
406
        status = stdout.channel.recv_exit_status()
407
        output = stdout.readlines()
408
        ssh.close()
409
        return output, status
410

    
411
    def _insist_get_hostname_over_ssh(self, hostip, username, password):
412
        """Connect to server using ssh and get it's hostname"""
413
        def check_fun():
414
            """Get hostname"""
415
            try:
416
                lines, status = self._ssh_execute(
417
                    hostip, username, password, "hostname")
418
                self.assertEqual(status, 0)
419
                self.assertEqual(len(lines), 1)
420
                # Remove new line
421
                return lines[0].strip('\n')
422
            except AssertionError:
423
                raise Retry()
424
        opmsg = "Connecting to server using ssh and get it's hostname"
425
        self.info(opmsg)
426
        hostname = self._try_until_timeout_expires(opmsg, check_fun)
427
        self.info("Server's hostname is %s", hostname)
428
        return hostname
429

    
430
    # pylint: disable=too-many-arguments
431
    def _check_file_through_ssh(self, hostip, username, password,
432
                                remotepath, content):
433
        """Fetch file from server and compare contents"""
434
        self.info("Fetching file %s from remote server", remotepath)
435
        transport = paramiko.Transport((hostip, 22))
436
        transport.connect(username=username, password=password)
437
        with tempfile.NamedTemporaryFile() as ftmp:
438
            sftp = paramiko.SFTPClient.from_transport(transport)
439
            sftp.get(remotepath, ftmp.name)
440
            sftp.close()
441
            transport.close()
442
            self.info("Comparing file contents")
443
            remote_content = base64.b64encode(ftmp.read())
444
            self.assertEqual(content, remote_content)
445

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

    
460
        # Verify quotas
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)
466

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

    
471
        return network
472

    
473
    def _delete_networks(self, networks, error=False):
474
        """Delete a network"""
475
        for net in networks:
476
            self.info("Deleting network with id %s", net['id'])
477
            self.clients.network.delete_network(net['id'])
478

    
479
        if error:
480
            curr_states = ["ACTIVE", "SNF:DRAINED", "ERROR"]
481
        else:
482
            curr_states = ["ACTIVE", "SNF:DRAINED"]
483
        for net in networks:
484
            self._insist_on_network_transition(net, curr_states, "DELETED")
485

    
486
        # Networks no longer in network list
487
        new_networks = [n['id'] for n in self._get_list_of_networks()]
488
        for net in networks:
489
            self.info("Verifying that network with id %s is no longer in "
490
                      "network list", net['id'])
491
            self.assertNotIn(net['id'], new_networks)
492

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

    
498
    def _get_public_network(self, networks=None):
499
        """Get the public network"""
500
        if networks is None:
501
            networks = self._get_list_of_networks(detail=True)
502
        self.info("Getting the public network")
503
        for net in networks:
504
            if net['SNF:floating_ip_pool'] and net['public']:
505
                return net
506
        self.fail("Could not find a public network to use")
507

    
508
    def _create_floating_ip(self, project_id=None):
509
        """Create a new floating ip"""
510
        pub_net = self._get_public_network()
511
        self.info("Creating a new floating ip for network with id %s",
512
                  pub_net['id'])
513
        fip = self.clients.network.create_floatingip(
514
            pub_net['id'], project=project_id)
515
        # Verify that floating ip has been created
516
        fips = self.clients.network.list_floatingips()
517
        fips = [f['id'] for f in fips]
518
        self.assertIn(fip['id'], fips)
519
        # Verify quotas
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

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

    
530
        self.info("Floating IP %s with id %s created",
531
                  fip['floating_ip_address'], fip['id'])
532
        return fip
533

    
534
    def _create_port(self, network_id, device_id=None, floating_ip=None):
535
        """Create a new port attached to the a specific network"""
536
        self.info("Creating a new port to network with id %s", network_id)
537
        if floating_ip is not None:
538
            fixed_ips = [{'ip_address': floating_ip['floating_ip_address']}]
539
        else:
540
            fixed_ips = None
541
        port = self.clients.network.create_port(network_id,
542
                                                device_id=device_id,
543
                                                fixed_ips=fixed_ips)
544
        # Verify that port created
545
        ports = self.clients.network.list_ports()
546
        ports = [p['id'] for p in ports]
547
        self.assertIn(port['id'], ports)
548
        # Insist on creation
549
        if device_id is None:
550
            self._insist_on_port_transition(port, ["BUILD"], "DOWN")
551
        else:
552
            self._insist_on_port_transition(port, ["BUILD", "DOWN"], "ACTIVE")
553

    
554
        self.info("Port with id %s created", port['id'])
555
        return port
556

    
557
    def _insist_on_port_transition(self, port, curr_statuses, new_status):
558
        """Insist on port transiting from curr_statuses to new_status"""
559
        def check_fun():
560
            """Check port status"""
561
            portd = self.clients.network.get_port_details(port['id'])
562
            if portd['status'] in curr_statuses:
563
                raise Retry()
564
            elif portd['status'] == new_status:
565
                return
566
            else:
567
                msg = "Port %s went to unexpected status %s"
568
                self.fail(msg % (portd['id'], portd['status']))
569
        opmsg = "Waiting for port %s to become %s"
570
        self.info(opmsg, port['id'], new_status)
571
        opmsg = opmsg % (port['id'], new_status)
572
        self._try_until_timeout_expires(opmsg, check_fun)
573

    
574
    def _insist_on_port_deletion(self, portid):
575
        """Insist on port deletion"""
576
        def check_fun():
577
            """Check port details"""
578
            try:
579
                self.clients.network.get_port_details(portid)
580
            except ClientError as err:
581
                if err.status != 404:
582
                    raise
583
            else:
584
                raise Retry()
585
        opmsg = "Waiting for port %s to be deleted"
586
        self.info(opmsg, portid)
587
        opmsg = opmsg % portid
588
        self._try_until_timeout_expires(opmsg, check_fun)
589

    
590
    def _disconnect_from_network(self, server, network=None):
591
        """Disconnnect server from network"""
592
        if network is None:
593
            # Disconnect from public network
594
            network = self._get_public_network()
595

    
596
        lports = self.clients.network.list_ports()
597
        ports = []
598
        for port in lports:
599
            dport = self.clients.network.get_port_details(port['id'])
600
            if str(dport['network_id']) == str(network['id']) \
601
                    and str(dport['device_id']) == str(server['id']):
602
                ports.append(dport)
603

    
604
        # Find floating IPs attached to these ports
605
        ports_id = [p['id'] for p in ports]
606
        fips = [f for f in self.clients.network.list_floatingips()
607
                if str(f['port_id']) in ports_id]
608

    
609
        # First destroy the ports
610
        for port in ports:
611
            self.info("Destroying port with id %s", port['id'])
612
            self.clients.network.delete_port(port['id'])
613
            self._insist_on_port_deletion(port['id'])
614

    
615
        # Then delete the floating IPs
616
        self._delete_floating_ips(fips)
617

    
618
    def _delete_floating_ips(self, fips):
619
        """Delete floating ips"""
620
        # Renew the list of floating IP objects
621
        # (It may have been changed, i.e. a port may have been deleted).
622
        fip_ids = [f['id'] for f in fips]
623
        new_fips = [f for f in self.clients.network.list_floatingips()
624
                    if f['id'] in fip_ids]
625

    
626
        for fip in new_fips:
627
            port_id = fip['port_id']
628
            if port_id:
629
                self.info("Destroying port with id %s", port_id)
630
                self.clients.network.delete_port(port_id)
631
                self._insist_on_port_deletion(port_id)
632

    
633
            self.info("Destroying floating IP %s with id %s",
634
                      fip['floating_ip_address'], fip['id'])
635
            self.clients.network.delete_floatingip(fip['id'])
636

    
637
        # Check that floating IPs have been deleted
638
        list_ips = [f['id'] for f in self.clients.network.list_floatingips()]
639
        for fip in fips:
640
            self.assertNotIn(fip['id'], list_ips)
641

    
642
        # Verify quotas
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)
686

    
687

    
688
class Retry(Exception):
689
    """Retry the action
690

691
    This is used by _try_unit_timeout_expires method.
692

693
    """