Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin / cyclades_common.py @ 3139b628

History | View | Annotate | Download (23.1 kB)

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

39 d246be88 Ilias Tsitsimpis
"""
40 d246be88 Ilias Tsitsimpis
41 d246be88 Ilias Tsitsimpis
import time
42 60a80953 Ilias Tsitsimpis
import IPy
43 cee3ee9b Ilias Tsitsimpis
import base64
44 d246be88 Ilias Tsitsimpis
import socket
45 d246be88 Ilias Tsitsimpis
import random
46 cee3ee9b Ilias Tsitsimpis
import paramiko
47 cee3ee9b Ilias Tsitsimpis
import tempfile
48 d246be88 Ilias Tsitsimpis
import subprocess
49 d246be88 Ilias Tsitsimpis
50 60a80953 Ilias Tsitsimpis
from kamaki.clients import ClientError
51 60a80953 Ilias Tsitsimpis
52 3e5bbd85 Ilias Tsitsimpis
from synnefo_tools.burnin.common import BurninTests, MB, GB
53 d246be88 Ilias Tsitsimpis
54 d246be88 Ilias Tsitsimpis
55 d246be88 Ilias Tsitsimpis
# Too many public methods. pylint: disable-msg=R0904
56 d246be88 Ilias Tsitsimpis
class CycladesTests(BurninTests):
57 d246be88 Ilias Tsitsimpis
    """Extends the BurninTests class for Cyclades"""
58 3eaf0ec5 Ilias Tsitsimpis
    def _try_until_timeout_expires(self, opmsg, check_fun):
59 d246be88 Ilias Tsitsimpis
        """Try to perform an action until timeout expires"""
60 d246be88 Ilias Tsitsimpis
        assert callable(check_fun), "Not a function"
61 d246be88 Ilias Tsitsimpis
62 d246be88 Ilias Tsitsimpis
        action_timeout = self.action_timeout
63 d246be88 Ilias Tsitsimpis
        action_warning = self.action_warning
64 d246be88 Ilias Tsitsimpis
        if action_warning > action_timeout:
65 d246be88 Ilias Tsitsimpis
            action_warning = action_timeout
66 d246be88 Ilias Tsitsimpis
67 8c67f82e Ilias Tsitsimpis
        start_time = int(time.time())
68 8c67f82e Ilias Tsitsimpis
        end_time = start_time + action_warning
69 8c67f82e Ilias Tsitsimpis
        while end_time > time.time():
70 d246be88 Ilias Tsitsimpis
            try:
71 8c67f82e Ilias Tsitsimpis
                ret_value = check_fun()
72 8c67f82e Ilias Tsitsimpis
                self.info("Operation `%s' finished in %s seconds",
73 8c67f82e Ilias Tsitsimpis
                          opmsg, int(time.time()) - start_time)
74 8c67f82e Ilias Tsitsimpis
                return ret_value
75 d246be88 Ilias Tsitsimpis
            except Retry:
76 d246be88 Ilias Tsitsimpis
                time.sleep(self.query_interval)
77 8c67f82e Ilias Tsitsimpis
        self.warning("Operation `%s' is taking too long after %s seconds",
78 8c67f82e Ilias Tsitsimpis
                     opmsg, int(time.time()) - start_time)
79 8c67f82e Ilias Tsitsimpis
80 8c67f82e Ilias Tsitsimpis
        end_time = start_time + action_timeout
81 8c67f82e Ilias Tsitsimpis
        while end_time > time.time():
82 d246be88 Ilias Tsitsimpis
            try:
83 8c67f82e Ilias Tsitsimpis
                ret_value = check_fun()
84 8c67f82e Ilias Tsitsimpis
                self.info("Operation `%s' finished in %s seconds",
85 8c67f82e Ilias Tsitsimpis
                          opmsg, int(time.time()) - start_time)
86 8c67f82e Ilias Tsitsimpis
                return ret_value
87 d246be88 Ilias Tsitsimpis
            except Retry:
88 d246be88 Ilias Tsitsimpis
                time.sleep(self.query_interval)
89 8c67f82e Ilias Tsitsimpis
        self.error("Operation `%s' timed out after %s seconds",
90 8c67f82e Ilias Tsitsimpis
                   opmsg, int(time.time()) - start_time)
91 d246be88 Ilias Tsitsimpis
        self.fail("time out")
92 d246be88 Ilias Tsitsimpis
93 d246be88 Ilias Tsitsimpis
    def _get_list_of_servers(self, detail=False):
94 d246be88 Ilias Tsitsimpis
        """Get (detailed) list of servers"""
95 d246be88 Ilias Tsitsimpis
        if detail:
96 d246be88 Ilias Tsitsimpis
            self.info("Getting detailed list of servers")
97 d246be88 Ilias Tsitsimpis
        else:
98 d246be88 Ilias Tsitsimpis
            self.info("Getting simple list of servers")
99 d246be88 Ilias Tsitsimpis
        return self.clients.cyclades.list_servers(detail=detail)
100 d246be88 Ilias Tsitsimpis
101 3eaf0ec5 Ilias Tsitsimpis
    def _get_list_of_networks(self, detail=False):
102 3eaf0ec5 Ilias Tsitsimpis
        """Get (detailed) list of networks"""
103 3eaf0ec5 Ilias Tsitsimpis
        if detail:
104 3eaf0ec5 Ilias Tsitsimpis
            self.info("Getting detailed list of networks")
105 3eaf0ec5 Ilias Tsitsimpis
        else:
106 3eaf0ec5 Ilias Tsitsimpis
            self.info("Getting simple list of networks")
107 60a80953 Ilias Tsitsimpis
        return self.clients.network.list_networks(detail=detail)
108 3eaf0ec5 Ilias Tsitsimpis
109 3eaf0ec5 Ilias Tsitsimpis
    def _get_server_details(self, server, quiet=False):
110 d246be88 Ilias Tsitsimpis
        """Get details for a server"""
111 3eaf0ec5 Ilias Tsitsimpis
        if not quiet:
112 3eaf0ec5 Ilias Tsitsimpis
            self.info("Getting details for server %s with id %s",
113 3eaf0ec5 Ilias Tsitsimpis
                      server['name'], server['id'])
114 d246be88 Ilias Tsitsimpis
        return self.clients.cyclades.get_server_details(server['id'])
115 d246be88 Ilias Tsitsimpis
116 60a80953 Ilias Tsitsimpis
    def _create_server(self, image, flavor, personality=None, network=False):
117 d246be88 Ilias Tsitsimpis
        """Create a new server"""
118 60a80953 Ilias Tsitsimpis
        if network:
119 60a80953 Ilias Tsitsimpis
            fip = self._create_floating_ip()
120 60a80953 Ilias Tsitsimpis
            port = self._create_port(fip['floating_network_id'],
121 60a80953 Ilias Tsitsimpis
                                     floating_ip=fip)
122 60a80953 Ilias Tsitsimpis
            networks = [{'port': port['id']}]
123 60a80953 Ilias Tsitsimpis
        else:
124 60a80953 Ilias Tsitsimpis
            networks = None
125 60a80953 Ilias Tsitsimpis
126 3eaf0ec5 Ilias Tsitsimpis
        servername = "%s for %s" % (self.run_id, image['name'])
127 3eaf0ec5 Ilias Tsitsimpis
        self.info("Creating a server with name %s", servername)
128 d246be88 Ilias Tsitsimpis
        self.info("Using image %s with id %s", image['name'], image['id'])
129 d246be88 Ilias Tsitsimpis
        self.info("Using flavor %s with id %s", flavor['name'], flavor['id'])
130 d246be88 Ilias Tsitsimpis
        server = self.clients.cyclades.create_server(
131 60a80953 Ilias Tsitsimpis
            servername, flavor['id'], image['id'],
132 60a80953 Ilias Tsitsimpis
            personality=personality, networks=networks)
133 d246be88 Ilias Tsitsimpis
134 d246be88 Ilias Tsitsimpis
        self.info("Server id: %s", server['id'])
135 d246be88 Ilias Tsitsimpis
        self.info("Server password: %s", server['adminPass'])
136 d246be88 Ilias Tsitsimpis
137 3eaf0ec5 Ilias Tsitsimpis
        self.assertEqual(server['name'], servername)
138 d246be88 Ilias Tsitsimpis
        self.assertEqual(server['flavor']['id'], flavor['id'])
139 d246be88 Ilias Tsitsimpis
        self.assertEqual(server['image']['id'], image['id'])
140 d246be88 Ilias Tsitsimpis
        self.assertEqual(server['status'], "BUILD")
141 d246be88 Ilias Tsitsimpis
142 3e5bbd85 Ilias Tsitsimpis
        # Verify quotas
143 3e5bbd85 Ilias Tsitsimpis
        self._check_quotas(disk=+int(flavor['disk'])*GB,
144 3e5bbd85 Ilias Tsitsimpis
                           vm=+1,
145 3e5bbd85 Ilias Tsitsimpis
                           ram=+int(flavor['ram'])*MB,
146 3e5bbd85 Ilias Tsitsimpis
                           cpu=+int(flavor['vcpus']))
147 3e5bbd85 Ilias Tsitsimpis
148 d246be88 Ilias Tsitsimpis
        return server
149 d246be88 Ilias Tsitsimpis
150 60a80953 Ilias Tsitsimpis
    def _delete_servers(self, servers, error=False):
151 60a80953 Ilias Tsitsimpis
        """Deleting a number of servers in parallel"""
152 60a80953 Ilias Tsitsimpis
        # Disconnect floating IPs
153 7c37ab19 Ilias Tsitsimpis
        if not error:
154 7c37ab19 Ilias Tsitsimpis
            # If there is the possibility for the machine to be in
155 7c37ab19 Ilias Tsitsimpis
            # ERROR state we cannot delete its ports.
156 7c37ab19 Ilias Tsitsimpis
            for srv in servers:
157 7c37ab19 Ilias Tsitsimpis
                self.info(
158 7c37ab19 Ilias Tsitsimpis
                    "Disconnecting all floating IPs from server with id %s",
159 7c37ab19 Ilias Tsitsimpis
                    srv['id'])
160 7c37ab19 Ilias Tsitsimpis
                self._disconnect_from_network(srv)
161 60a80953 Ilias Tsitsimpis
162 60a80953 Ilias Tsitsimpis
        # Delete servers
163 60a80953 Ilias Tsitsimpis
        for srv in servers:
164 60a80953 Ilias Tsitsimpis
            self.info("Sending the delete request for server with id %s",
165 60a80953 Ilias Tsitsimpis
                      srv['id'])
166 60a80953 Ilias Tsitsimpis
            self.clients.cyclades.delete_server(srv['id'])
167 60a80953 Ilias Tsitsimpis
168 60a80953 Ilias Tsitsimpis
        if error:
169 60a80953 Ilias Tsitsimpis
            curr_states = ["ACTIVE", "ERROR", "STOPPED", "BUILD"]
170 60a80953 Ilias Tsitsimpis
        else:
171 60a80953 Ilias Tsitsimpis
            curr_states = ["ACTIVE"]
172 60a80953 Ilias Tsitsimpis
        for srv in servers:
173 60a80953 Ilias Tsitsimpis
            self._insist_on_server_transition(srv, curr_states, "DELETED")
174 60a80953 Ilias Tsitsimpis
175 60a80953 Ilias Tsitsimpis
        # Servers no longer in server list
176 60a80953 Ilias Tsitsimpis
        new_servers = [s['id'] for s in self._get_list_of_servers()]
177 60a80953 Ilias Tsitsimpis
        for srv in servers:
178 60a80953 Ilias Tsitsimpis
            self.info("Verifying that server with id %s is no longer in "
179 60a80953 Ilias Tsitsimpis
                      "server list", srv['id'])
180 60a80953 Ilias Tsitsimpis
            self.assertNotIn(srv['id'], new_servers)
181 60a80953 Ilias Tsitsimpis
182 60a80953 Ilias Tsitsimpis
        # Verify quotas
183 60a80953 Ilias Tsitsimpis
        flavors = \
184 60a80953 Ilias Tsitsimpis
            [self.clients.compute.get_flavor_details(srv['flavor']['id'])
185 60a80953 Ilias Tsitsimpis
             for srv in servers]
186 60a80953 Ilias Tsitsimpis
        self._verify_quotas_deleted(flavors)
187 60a80953 Ilias Tsitsimpis
188 3e5bbd85 Ilias Tsitsimpis
    def _verify_quotas_deleted(self, flavors):
189 3e5bbd85 Ilias Tsitsimpis
        """Verify quotas for a number of deleted servers"""
190 3e5bbd85 Ilias Tsitsimpis
        used_disk = 0
191 3e5bbd85 Ilias Tsitsimpis
        used_vm = 0
192 3e5bbd85 Ilias Tsitsimpis
        used_ram = 0
193 3e5bbd85 Ilias Tsitsimpis
        used_cpu = 0
194 3e5bbd85 Ilias Tsitsimpis
        for flavor in flavors:
195 3e5bbd85 Ilias Tsitsimpis
            used_disk += int(flavor['disk']) * GB
196 3e5bbd85 Ilias Tsitsimpis
            used_vm += 1
197 3e5bbd85 Ilias Tsitsimpis
            used_ram += int(flavor['ram']) * MB
198 3e5bbd85 Ilias Tsitsimpis
            used_cpu += int(flavor['vcpus'])
199 3e5bbd85 Ilias Tsitsimpis
        self._check_quotas(disk=-used_disk,
200 3e5bbd85 Ilias Tsitsimpis
                           vm=-used_vm,
201 3e5bbd85 Ilias Tsitsimpis
                           ram=-used_ram,
202 3e5bbd85 Ilias Tsitsimpis
                           cpu=-used_cpu)
203 3e5bbd85 Ilias Tsitsimpis
204 d246be88 Ilias Tsitsimpis
    def _get_connection_username(self, server):
205 d246be88 Ilias Tsitsimpis
        """Determine the username to use to connect to the server"""
206 d246be88 Ilias Tsitsimpis
        users = server['metadata'].get("users", None)
207 d246be88 Ilias Tsitsimpis
        ret_user = None
208 d246be88 Ilias Tsitsimpis
        if users is not None:
209 d246be88 Ilias Tsitsimpis
            user_list = users.split()
210 d246be88 Ilias Tsitsimpis
            if "root" in user_list:
211 d246be88 Ilias Tsitsimpis
                ret_user = "root"
212 d246be88 Ilias Tsitsimpis
            else:
213 d246be88 Ilias Tsitsimpis
                ret_user = random.choice(user_list)
214 d246be88 Ilias Tsitsimpis
        else:
215 d246be88 Ilias Tsitsimpis
            # Return the login name for connections based on the server OS
216 d246be88 Ilias Tsitsimpis
            self.info("Could not find `users' metadata in server. Let's guess")
217 d246be88 Ilias Tsitsimpis
            os_value = server['metadata'].get("os")
218 d246be88 Ilias Tsitsimpis
            if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
219 d246be88 Ilias Tsitsimpis
                ret_user = "user"
220 d246be88 Ilias Tsitsimpis
            elif os_value in ("windows", "windows_alpha1"):
221 d246be88 Ilias Tsitsimpis
                ret_user = "Administrator"
222 d246be88 Ilias Tsitsimpis
            else:
223 d246be88 Ilias Tsitsimpis
                ret_user = "root"
224 d246be88 Ilias Tsitsimpis
225 d246be88 Ilias Tsitsimpis
        self.assertIsNotNone(ret_user)
226 d246be88 Ilias Tsitsimpis
        self.info("User's login name: %s", ret_user)
227 d246be88 Ilias Tsitsimpis
        return ret_user
228 d246be88 Ilias Tsitsimpis
229 3eaf0ec5 Ilias Tsitsimpis
    def _insist_on_server_transition(self, server, curr_statuses, new_status):
230 3eaf0ec5 Ilias Tsitsimpis
        """Insist on server transiting from curr_statuses to new_status"""
231 d246be88 Ilias Tsitsimpis
        def check_fun():
232 d246be88 Ilias Tsitsimpis
            """Check server status"""
233 3eaf0ec5 Ilias Tsitsimpis
            srv = self._get_server_details(server, quiet=True)
234 3eaf0ec5 Ilias Tsitsimpis
            if srv['status'] in curr_statuses:
235 d246be88 Ilias Tsitsimpis
                raise Retry()
236 d246be88 Ilias Tsitsimpis
            elif srv['status'] == new_status:
237 d246be88 Ilias Tsitsimpis
                return
238 d246be88 Ilias Tsitsimpis
            else:
239 3eaf0ec5 Ilias Tsitsimpis
                msg = "Server \"%s\" with id %s went to unexpected status %s"
240 3eaf0ec5 Ilias Tsitsimpis
                self.error(msg, server['name'], server['id'], srv['status'])
241 3eaf0ec5 Ilias Tsitsimpis
                self.fail(msg % (server['name'], server['id'], srv['status']))
242 5bef1f49 Ilias Tsitsimpis
        opmsg = "Waiting for server \"%s\" with id %s to become %s"
243 5bef1f49 Ilias Tsitsimpis
        self.info(opmsg, server['name'], server['id'], new_status)
244 5bef1f49 Ilias Tsitsimpis
        opmsg = opmsg % (server['name'], server['id'], new_status)
245 3eaf0ec5 Ilias Tsitsimpis
        self._try_until_timeout_expires(opmsg, check_fun)
246 3eaf0ec5 Ilias Tsitsimpis
247 3eaf0ec5 Ilias Tsitsimpis
    def _insist_on_network_transition(self, network,
248 3eaf0ec5 Ilias Tsitsimpis
                                      curr_statuses, new_status):
249 3eaf0ec5 Ilias Tsitsimpis
        """Insist on network transiting from curr_statuses to new_status"""
250 3eaf0ec5 Ilias Tsitsimpis
        def check_fun():
251 3eaf0ec5 Ilias Tsitsimpis
            """Check network status"""
252 60a80953 Ilias Tsitsimpis
            ntw = self.clients.network.get_network_details(network['id'])
253 3eaf0ec5 Ilias Tsitsimpis
            if ntw['status'] in curr_statuses:
254 3eaf0ec5 Ilias Tsitsimpis
                raise Retry()
255 3eaf0ec5 Ilias Tsitsimpis
            elif ntw['status'] == new_status:
256 3eaf0ec5 Ilias Tsitsimpis
                return
257 3eaf0ec5 Ilias Tsitsimpis
            else:
258 3eaf0ec5 Ilias Tsitsimpis
                msg = "Network %s with id %s went to unexpected status %s"
259 3eaf0ec5 Ilias Tsitsimpis
                self.error(msg, network['name'], network['id'], ntw['status'])
260 3eaf0ec5 Ilias Tsitsimpis
                self.fail(msg %
261 3eaf0ec5 Ilias Tsitsimpis
                          (network['name'], network['id'], ntw['status']))
262 5bef1f49 Ilias Tsitsimpis
        opmsg = "Waiting for network \"%s\" with id %s to become %s"
263 5bef1f49 Ilias Tsitsimpis
        self.info(opmsg, network['name'], network['id'], new_status)
264 5bef1f49 Ilias Tsitsimpis
        opmsg = opmsg % (network['name'], network['id'], new_status)
265 3eaf0ec5 Ilias Tsitsimpis
        self._try_until_timeout_expires(opmsg, check_fun)
266 3eaf0ec5 Ilias Tsitsimpis
267 d246be88 Ilias Tsitsimpis
    def _insist_on_tcp_connection(self, family, host, port):
268 d246be88 Ilias Tsitsimpis
        """Insist on tcp connection"""
269 d246be88 Ilias Tsitsimpis
        def check_fun():
270 d246be88 Ilias Tsitsimpis
            """Get a connected socket from the specified family to host:port"""
271 d246be88 Ilias Tsitsimpis
            sock = None
272 d246be88 Ilias Tsitsimpis
            for res in socket.getaddrinfo(host, port, family,
273 d246be88 Ilias Tsitsimpis
                                          socket.SOCK_STREAM, 0,
274 d246be88 Ilias Tsitsimpis
                                          socket.AI_PASSIVE):
275 d246be88 Ilias Tsitsimpis
                fam, socktype, proto, _, saddr = res
276 d246be88 Ilias Tsitsimpis
                try:
277 d246be88 Ilias Tsitsimpis
                    sock = socket.socket(fam, socktype, proto)
278 d246be88 Ilias Tsitsimpis
                except socket.error:
279 d246be88 Ilias Tsitsimpis
                    sock = None
280 d246be88 Ilias Tsitsimpis
                    continue
281 d246be88 Ilias Tsitsimpis
                try:
282 d246be88 Ilias Tsitsimpis
                    sock.connect(saddr)
283 d246be88 Ilias Tsitsimpis
                except socket.error:
284 d246be88 Ilias Tsitsimpis
                    sock.close()
285 d246be88 Ilias Tsitsimpis
                    sock = None
286 d246be88 Ilias Tsitsimpis
                    continue
287 d246be88 Ilias Tsitsimpis
            if sock is None:
288 d246be88 Ilias Tsitsimpis
                raise Retry
289 d246be88 Ilias Tsitsimpis
            return sock
290 d246be88 Ilias Tsitsimpis
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
291 d246be88 Ilias Tsitsimpis
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
292 d246be88 Ilias Tsitsimpis
        opmsg = "Connecting over %s to %s:%s"
293 d246be88 Ilias Tsitsimpis
        self.info(opmsg, familystr.get(family, "Unknown"), host, port)
294 d246be88 Ilias Tsitsimpis
        opmsg = opmsg % (familystr.get(family, "Unknown"), host, port)
295 d246be88 Ilias Tsitsimpis
        return self._try_until_timeout_expires(opmsg, check_fun)
296 d246be88 Ilias Tsitsimpis
297 60a80953 Ilias Tsitsimpis
    def _get_ips(self, server, version=4, network=None):
298 60a80953 Ilias Tsitsimpis
        """Get the IPs of a server from the detailed server info
299 3eaf0ec5 Ilias Tsitsimpis

300 60a80953 Ilias Tsitsimpis
        If network not given then get the public IPs. Else the IPs
301 3eaf0ec5 Ilias Tsitsimpis
        attached to that network
302 3eaf0ec5 Ilias Tsitsimpis

303 3eaf0ec5 Ilias Tsitsimpis
        """
304 d246be88 Ilias Tsitsimpis
        assert version in (4, 6)
305 d246be88 Ilias Tsitsimpis
306 d246be88 Ilias Tsitsimpis
        nics = server['attachments']
307 60a80953 Ilias Tsitsimpis
        addrs = []
308 d246be88 Ilias Tsitsimpis
        for nic in nics:
309 d246be88 Ilias Tsitsimpis
            net_id = nic['network_id']
310 3eaf0ec5 Ilias Tsitsimpis
            if network is None:
311 60a80953 Ilias Tsitsimpis
                if self.clients.network.get_network_details(net_id)['public']:
312 ee89df69 Ilias Tsitsimpis
                    if nic['ipv' + str(version)]:
313 60a80953 Ilias Tsitsimpis
                        addrs.append(nic['ipv' + str(version)])
314 3eaf0ec5 Ilias Tsitsimpis
            else:
315 3eaf0ec5 Ilias Tsitsimpis
                if net_id == network['id']:
316 ee89df69 Ilias Tsitsimpis
                    if nic['ipv' + str(version)]:
317 60a80953 Ilias Tsitsimpis
                        addrs.append(nic['ipv' + str(version)])
318 60a80953 Ilias Tsitsimpis
319 60a80953 Ilias Tsitsimpis
        self.assertGreater(len(addrs), 0,
320 60a80953 Ilias Tsitsimpis
                           "Can not get IPs from server attachments")
321 60a80953 Ilias Tsitsimpis
322 60a80953 Ilias Tsitsimpis
        for addr in addrs:
323 60a80953 Ilias Tsitsimpis
            self.assertEquals(IPy.IP(addr).version(), version)
324 d246be88 Ilias Tsitsimpis
325 3eaf0ec5 Ilias Tsitsimpis
        if network is None:
326 3eaf0ec5 Ilias Tsitsimpis
            msg = "Server's public IPv%s is %s"
327 60a80953 Ilias Tsitsimpis
            for addr in addrs:
328 60a80953 Ilias Tsitsimpis
                self.info(msg, version, addr)
329 3eaf0ec5 Ilias Tsitsimpis
        else:
330 3eaf0ec5 Ilias Tsitsimpis
            msg = "Server's IPv%s attached to network \"%s\" is %s"
331 60a80953 Ilias Tsitsimpis
            for addr in addrs:
332 60a80953 Ilias Tsitsimpis
                self.info(msg, version, network['id'], addr)
333 3eaf0ec5 Ilias Tsitsimpis
        return addrs
334 d246be88 Ilias Tsitsimpis
335 cee3ee9b Ilias Tsitsimpis
    def _insist_on_ping(self, ip_addr, version=4):
336 d246be88 Ilias Tsitsimpis
        """Test server responds to a single IPv4 of IPv6 ping"""
337 d246be88 Ilias Tsitsimpis
        def check_fun():
338 d246be88 Ilias Tsitsimpis
            """Ping to server"""
339 d246be88 Ilias Tsitsimpis
            cmd = ("ping%s -c 3 -w 20 %s" %
340 d246be88 Ilias Tsitsimpis
                   ("6" if version == 6 else "", ip_addr))
341 d246be88 Ilias Tsitsimpis
            ping = subprocess.Popen(
342 d246be88 Ilias Tsitsimpis
                cmd, shell=True, stdout=subprocess.PIPE,
343 d246be88 Ilias Tsitsimpis
                stderr=subprocess.PIPE)
344 d246be88 Ilias Tsitsimpis
            ping.communicate()
345 d246be88 Ilias Tsitsimpis
            ret = ping.wait()
346 d246be88 Ilias Tsitsimpis
            if ret != 0:
347 d246be88 Ilias Tsitsimpis
                raise Retry
348 cee3ee9b Ilias Tsitsimpis
        assert version in (4, 6)
349 d246be88 Ilias Tsitsimpis
        opmsg = "Sent IPv%s ping requests to %s"
350 d246be88 Ilias Tsitsimpis
        self.info(opmsg, version, ip_addr)
351 d246be88 Ilias Tsitsimpis
        opmsg = opmsg % (version, ip_addr)
352 d246be88 Ilias Tsitsimpis
        self._try_until_timeout_expires(opmsg, check_fun)
353 d246be88 Ilias Tsitsimpis
354 cee3ee9b Ilias Tsitsimpis
    def _image_is(self, image, osfamily):
355 cee3ee9b Ilias Tsitsimpis
        """Return true if the image is of `osfamily'"""
356 cee3ee9b Ilias Tsitsimpis
        d_image = self.clients.cyclades.get_image_details(image['id'])
357 cee3ee9b Ilias Tsitsimpis
        return d_image['metadata']['osfamily'].lower().find(osfamily) >= 0
358 cee3ee9b Ilias Tsitsimpis
359 449a1c40 Ilias Tsitsimpis
    # Method could be a function. pylint: disable-msg=R0201
360 cee3ee9b Ilias Tsitsimpis
    def _ssh_execute(self, hostip, username, password, command):
361 cee3ee9b Ilias Tsitsimpis
        """Execute a command via ssh"""
362 cee3ee9b Ilias Tsitsimpis
        ssh = paramiko.SSHClient()
363 cee3ee9b Ilias Tsitsimpis
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
364 cee3ee9b Ilias Tsitsimpis
        try:
365 cee3ee9b Ilias Tsitsimpis
            ssh.connect(hostip, username=username, password=password)
366 16c7f032 Ilias Tsitsimpis
        except paramiko.SSHException as err:
367 16c7f032 Ilias Tsitsimpis
            if err.args[0] == "Error reading SSH protocol banner":
368 449a1c40 Ilias Tsitsimpis
                raise Retry()
369 449a1c40 Ilias Tsitsimpis
            else:
370 449a1c40 Ilias Tsitsimpis
                raise
371 449a1c40 Ilias Tsitsimpis
        _, stdout, _ = ssh.exec_command(command)
372 cee3ee9b Ilias Tsitsimpis
        status = stdout.channel.recv_exit_status()
373 cee3ee9b Ilias Tsitsimpis
        output = stdout.readlines()
374 cee3ee9b Ilias Tsitsimpis
        ssh.close()
375 cee3ee9b Ilias Tsitsimpis
        return output, status
376 cee3ee9b Ilias Tsitsimpis
377 cee3ee9b Ilias Tsitsimpis
    def _insist_get_hostname_over_ssh(self, hostip, username, password):
378 cee3ee9b Ilias Tsitsimpis
        """Connect to server using ssh and get it's hostname"""
379 cee3ee9b Ilias Tsitsimpis
        def check_fun():
380 cee3ee9b Ilias Tsitsimpis
            """Get hostname"""
381 cee3ee9b Ilias Tsitsimpis
            try:
382 cee3ee9b Ilias Tsitsimpis
                lines, status = self._ssh_execute(
383 cee3ee9b Ilias Tsitsimpis
                    hostip, username, password, "hostname")
384 cee3ee9b Ilias Tsitsimpis
                self.assertEqual(status, 0)
385 cee3ee9b Ilias Tsitsimpis
                self.assertEqual(len(lines), 1)
386 cee3ee9b Ilias Tsitsimpis
                # Remove new line
387 cee3ee9b Ilias Tsitsimpis
                return lines[0].strip('\n')
388 cee3ee9b Ilias Tsitsimpis
            except AssertionError:
389 cee3ee9b Ilias Tsitsimpis
                raise Retry()
390 cee3ee9b Ilias Tsitsimpis
        opmsg = "Connecting to server using ssh and get it's hostname"
391 cee3ee9b Ilias Tsitsimpis
        self.info(opmsg)
392 cee3ee9b Ilias Tsitsimpis
        hostname = self._try_until_timeout_expires(opmsg, check_fun)
393 cee3ee9b Ilias Tsitsimpis
        self.info("Server's hostname is %s", hostname)
394 cee3ee9b Ilias Tsitsimpis
        return hostname
395 cee3ee9b Ilias Tsitsimpis
396 cee3ee9b Ilias Tsitsimpis
    # Too many arguments. pylint: disable-msg=R0913
397 cee3ee9b Ilias Tsitsimpis
    def _check_file_through_ssh(self, hostip, username, password,
398 cee3ee9b Ilias Tsitsimpis
                                remotepath, content):
399 cee3ee9b Ilias Tsitsimpis
        """Fetch file from server and compare contents"""
400 cee3ee9b Ilias Tsitsimpis
        self.info("Fetching file %s from remote server", remotepath)
401 cee3ee9b Ilias Tsitsimpis
        transport = paramiko.Transport((hostip, 22))
402 cee3ee9b Ilias Tsitsimpis
        transport.connect(username=username, password=password)
403 cee3ee9b Ilias Tsitsimpis
        with tempfile.NamedTemporaryFile() as ftmp:
404 cee3ee9b Ilias Tsitsimpis
            sftp = paramiko.SFTPClient.from_transport(transport)
405 cee3ee9b Ilias Tsitsimpis
            sftp.get(remotepath, ftmp.name)
406 cee3ee9b Ilias Tsitsimpis
            sftp.close()
407 cee3ee9b Ilias Tsitsimpis
            transport.close()
408 cee3ee9b Ilias Tsitsimpis
            self.info("Comparing file contents")
409 cee3ee9b Ilias Tsitsimpis
            remote_content = base64.b64encode(ftmp.read())
410 cee3ee9b Ilias Tsitsimpis
            self.assertEqual(content, remote_content)
411 cee3ee9b Ilias Tsitsimpis
412 60a80953 Ilias Tsitsimpis
    # ----------------------------------
413 60a80953 Ilias Tsitsimpis
    # Networks
414 60a80953 Ilias Tsitsimpis
    def _create_network(self, cidr="10.0.1.0/28", dhcp=True):
415 3e5bbd85 Ilias Tsitsimpis
        """Create a new private network"""
416 60a80953 Ilias Tsitsimpis
        name = self.run_id
417 60a80953 Ilias Tsitsimpis
        network = self.clients.network.create_network(
418 60a80953 Ilias Tsitsimpis
            "MAC_FILTERED", name=name, shared=False)
419 3e5bbd85 Ilias Tsitsimpis
        self.info("Network with id %s created", network['id'])
420 60a80953 Ilias Tsitsimpis
        subnet = self.clients.network.create_subnet(
421 60a80953 Ilias Tsitsimpis
            network['id'], cidr=cidr, enable_dhcp=dhcp)
422 60a80953 Ilias Tsitsimpis
        self.info("Subnet with id %s created", subnet['id'])
423 3e5bbd85 Ilias Tsitsimpis
424 3e5bbd85 Ilias Tsitsimpis
        # Verify quotas
425 3e5bbd85 Ilias Tsitsimpis
        self._check_quotas(network=+1)
426 3e5bbd85 Ilias Tsitsimpis
427 3e5bbd85 Ilias Tsitsimpis
        #Test if the right name is assigned
428 3e5bbd85 Ilias Tsitsimpis
        self.assertEqual(network['name'], name)
429 3e5bbd85 Ilias Tsitsimpis
430 3e5bbd85 Ilias Tsitsimpis
        return network
431 3e5bbd85 Ilias Tsitsimpis
432 60a80953 Ilias Tsitsimpis
    def _delete_networks(self, networks, error=False):
433 60a80953 Ilias Tsitsimpis
        """Delete a network"""
434 60a80953 Ilias Tsitsimpis
        for net in networks:
435 60a80953 Ilias Tsitsimpis
            self.info("Deleting network with id %s", net['id'])
436 60a80953 Ilias Tsitsimpis
            self.clients.network.delete_network(net['id'])
437 60a80953 Ilias Tsitsimpis
438 60a80953 Ilias Tsitsimpis
        if error:
439 60a80953 Ilias Tsitsimpis
            curr_states = ["ACTIVE", "SNF:DRAINED", "ERROR"]
440 60a80953 Ilias Tsitsimpis
        else:
441 60a80953 Ilias Tsitsimpis
            curr_states = ["ACTIVE", "SNF:DRAINED"]
442 60a80953 Ilias Tsitsimpis
        for net in networks:
443 60a80953 Ilias Tsitsimpis
            self._insist_on_network_transition(net, curr_states, "DELETED")
444 60a80953 Ilias Tsitsimpis
445 60a80953 Ilias Tsitsimpis
        # Networks no longer in network list
446 60a80953 Ilias Tsitsimpis
        new_networks = [n['id'] for n in self._get_list_of_networks()]
447 60a80953 Ilias Tsitsimpis
        for net in networks:
448 60a80953 Ilias Tsitsimpis
            self.info("Verifying that network with id %s is no longer in "
449 60a80953 Ilias Tsitsimpis
                      "network list", net['id'])
450 60a80953 Ilias Tsitsimpis
            self.assertNotIn(net['id'], new_networks)
451 60a80953 Ilias Tsitsimpis
452 60a80953 Ilias Tsitsimpis
        # Verify quotas
453 60a80953 Ilias Tsitsimpis
        self._check_quotas(network=-len(networks))
454 60a80953 Ilias Tsitsimpis
455 60a80953 Ilias Tsitsimpis
    def _get_public_network(self, networks=None):
456 60a80953 Ilias Tsitsimpis
        """Get the public network"""
457 60a80953 Ilias Tsitsimpis
        if networks is None:
458 60a80953 Ilias Tsitsimpis
            networks = self._get_list_of_networks(detail=True)
459 60a80953 Ilias Tsitsimpis
        self.info("Getting the public network")
460 60a80953 Ilias Tsitsimpis
        for net in networks:
461 60a80953 Ilias Tsitsimpis
            if net['SNF:floating_ip_pool'] and net['public']:
462 60a80953 Ilias Tsitsimpis
                return net
463 60a80953 Ilias Tsitsimpis
        self.fail("Could not find a public network to use")
464 60a80953 Ilias Tsitsimpis
465 60a80953 Ilias Tsitsimpis
    def _create_floating_ip(self):
466 60a80953 Ilias Tsitsimpis
        """Create a new floating ip"""
467 60a80953 Ilias Tsitsimpis
        pub_net = self._get_public_network()
468 60a80953 Ilias Tsitsimpis
        self.info("Creating a new floating ip for network with id %s",
469 60a80953 Ilias Tsitsimpis
                  pub_net['id'])
470 60a80953 Ilias Tsitsimpis
        fip = self.clients.network.create_floatingip(pub_net['id'])
471 60a80953 Ilias Tsitsimpis
        # Verify that floating ip has been created
472 60a80953 Ilias Tsitsimpis
        fips = self.clients.network.list_floatingips()
473 60a80953 Ilias Tsitsimpis
        fips = [f['id'] for f in fips]
474 60a80953 Ilias Tsitsimpis
        self.assertIn(fip['id'], fips)
475 60a80953 Ilias Tsitsimpis
        # Verify quotas
476 60a80953 Ilias Tsitsimpis
        self._check_quotas(ip=+1)
477 60a80953 Ilias Tsitsimpis
        # Check that IP is IPv4
478 60a80953 Ilias Tsitsimpis
        self.assertEquals(IPy.IP(fip['floating_ip_address']).version(), 4)
479 60a80953 Ilias Tsitsimpis
480 60a80953 Ilias Tsitsimpis
        self.info("Floating IP %s with id %s created",
481 60a80953 Ilias Tsitsimpis
                  fip['floating_ip_address'], fip['id'])
482 60a80953 Ilias Tsitsimpis
        return fip
483 60a80953 Ilias Tsitsimpis
484 60a80953 Ilias Tsitsimpis
    def _create_port(self, network_id, device_id=None, floating_ip=None):
485 60a80953 Ilias Tsitsimpis
        """Create a new port attached to the a specific network"""
486 60a80953 Ilias Tsitsimpis
        self.info("Creating a new port to network with id %s", network_id)
487 60a80953 Ilias Tsitsimpis
        if floating_ip is not None:
488 60a80953 Ilias Tsitsimpis
            fixed_ips = [{'ip_address': floating_ip['floating_ip_address']}]
489 60a80953 Ilias Tsitsimpis
        else:
490 60a80953 Ilias Tsitsimpis
            fixed_ips = None
491 60a80953 Ilias Tsitsimpis
        port = self.clients.network.create_port(network_id,
492 60a80953 Ilias Tsitsimpis
                                                device_id=device_id,
493 60a80953 Ilias Tsitsimpis
                                                fixed_ips=fixed_ips)
494 60a80953 Ilias Tsitsimpis
        # Verify that port created
495 60a80953 Ilias Tsitsimpis
        ports = self.clients.network.list_ports()
496 60a80953 Ilias Tsitsimpis
        ports = [p['id'] for p in ports]
497 60a80953 Ilias Tsitsimpis
        self.assertIn(port['id'], ports)
498 60a80953 Ilias Tsitsimpis
        # Insist on creation
499 60a80953 Ilias Tsitsimpis
        if device_id is None:
500 60a80953 Ilias Tsitsimpis
            self._insist_on_port_transition(port, ["BUILD"], "DOWN")
501 60a80953 Ilias Tsitsimpis
        else:
502 60a80953 Ilias Tsitsimpis
            self._insist_on_port_transition(port, ["BUILD", "DOWN"], "ACTIVE")
503 60a80953 Ilias Tsitsimpis
504 60a80953 Ilias Tsitsimpis
        self.info("Port with id %s created", port['id'])
505 60a80953 Ilias Tsitsimpis
        return port
506 60a80953 Ilias Tsitsimpis
507 60a80953 Ilias Tsitsimpis
    def _insist_on_port_transition(self, port, curr_statuses, new_status):
508 60a80953 Ilias Tsitsimpis
        """Insist on port transiting from curr_statuses to new_status"""
509 60a80953 Ilias Tsitsimpis
        def check_fun():
510 60a80953 Ilias Tsitsimpis
            """Check port status"""
511 60a80953 Ilias Tsitsimpis
            portd = self.clients.network.get_port_details(port['id'])
512 60a80953 Ilias Tsitsimpis
            if portd['status'] in curr_statuses:
513 60a80953 Ilias Tsitsimpis
                raise Retry()
514 60a80953 Ilias Tsitsimpis
            elif portd['status'] == new_status:
515 60a80953 Ilias Tsitsimpis
                return
516 60a80953 Ilias Tsitsimpis
            else:
517 60a80953 Ilias Tsitsimpis
                msg = "Port %s went to unexpected status %s"
518 60a80953 Ilias Tsitsimpis
                self.fail(msg % (portd['id'], portd['status']))
519 60a80953 Ilias Tsitsimpis
        opmsg = "Waiting for port %s to become %s"
520 60a80953 Ilias Tsitsimpis
        self.info(opmsg, port['id'], new_status)
521 60a80953 Ilias Tsitsimpis
        opmsg = opmsg % (port['id'], new_status)
522 60a80953 Ilias Tsitsimpis
        self._try_until_timeout_expires(opmsg, check_fun)
523 60a80953 Ilias Tsitsimpis
524 60a80953 Ilias Tsitsimpis
    def _insist_on_port_deletion(self, portid):
525 60a80953 Ilias Tsitsimpis
        """Insist on port deletion"""
526 60a80953 Ilias Tsitsimpis
        def check_fun():
527 60a80953 Ilias Tsitsimpis
            """Check port details"""
528 60a80953 Ilias Tsitsimpis
            try:
529 60a80953 Ilias Tsitsimpis
                self.clients.network.get_port_details(portid)
530 60a80953 Ilias Tsitsimpis
            except ClientError as err:
531 60a80953 Ilias Tsitsimpis
                if err.status != 404:
532 60a80953 Ilias Tsitsimpis
                    raise
533 60a80953 Ilias Tsitsimpis
            else:
534 60a80953 Ilias Tsitsimpis
                raise Retry()
535 60a80953 Ilias Tsitsimpis
        opmsg = "Waiting for port %s to be deleted"
536 60a80953 Ilias Tsitsimpis
        self.info(opmsg, portid)
537 60a80953 Ilias Tsitsimpis
        opmsg = opmsg % portid
538 60a80953 Ilias Tsitsimpis
        self._try_until_timeout_expires(opmsg, check_fun)
539 60a80953 Ilias Tsitsimpis
540 60a80953 Ilias Tsitsimpis
    def _disconnect_from_network(self, server, network=None):
541 60a80953 Ilias Tsitsimpis
        """Disconnnect server from network"""
542 60a80953 Ilias Tsitsimpis
        if network is None:
543 60a80953 Ilias Tsitsimpis
            # Disconnect from public network
544 60a80953 Ilias Tsitsimpis
            network = self._get_public_network()
545 60a80953 Ilias Tsitsimpis
546 60a80953 Ilias Tsitsimpis
        lports = self.clients.network.list_ports()
547 60a80953 Ilias Tsitsimpis
        ports = []
548 60a80953 Ilias Tsitsimpis
        for port in lports:
549 60a80953 Ilias Tsitsimpis
            dport = self.clients.network.get_port_details(port['id'])
550 60a80953 Ilias Tsitsimpis
            if str(dport['network_id']) == str(network['id']) \
551 60a80953 Ilias Tsitsimpis
                    and str(dport['device_id']) == str(server['id']):
552 60a80953 Ilias Tsitsimpis
                ports.append(dport)
553 60a80953 Ilias Tsitsimpis
554 60a80953 Ilias Tsitsimpis
        # Find floating IPs attached to these ports
555 60a80953 Ilias Tsitsimpis
        ports_id = [p['id'] for p in ports]
556 60a80953 Ilias Tsitsimpis
        fips = [f for f in self.clients.network.list_floatingips()
557 60a80953 Ilias Tsitsimpis
                if str(f['port_id']) in ports_id]
558 60a80953 Ilias Tsitsimpis
559 60a80953 Ilias Tsitsimpis
        # First destroy the ports
560 60a80953 Ilias Tsitsimpis
        for port in ports:
561 60a80953 Ilias Tsitsimpis
            self.info("Destroying port with id %s", port['id'])
562 60a80953 Ilias Tsitsimpis
            self.clients.network.delete_port(port['id'])
563 60a80953 Ilias Tsitsimpis
            self._insist_on_port_deletion(port['id'])
564 60a80953 Ilias Tsitsimpis
565 60a80953 Ilias Tsitsimpis
        # Then delete the floating IPs
566 17bb60a5 Ilias Tsitsimpis
        self._delete_floating_ips(fips)
567 17bb60a5 Ilias Tsitsimpis
568 17bb60a5 Ilias Tsitsimpis
    def _delete_floating_ips(self, fips):
569 17bb60a5 Ilias Tsitsimpis
        """Delete floating ips"""
570 60a80953 Ilias Tsitsimpis
        for fip in fips:
571 60a80953 Ilias Tsitsimpis
            self.info("Destroying floating IP %s with id %s",
572 60a80953 Ilias Tsitsimpis
                      fip['floating_ip_address'], fip['id'])
573 60a80953 Ilias Tsitsimpis
            self.clients.network.delete_floatingip(fip['id'])
574 60a80953 Ilias Tsitsimpis
575 60a80953 Ilias Tsitsimpis
        # Check that floating IPs have been deleted
576 60a80953 Ilias Tsitsimpis
        list_ips = [f['id'] for f in self.clients.network.list_floatingips()]
577 60a80953 Ilias Tsitsimpis
        for fip in fips:
578 60a80953 Ilias Tsitsimpis
            self.assertNotIn(fip['id'], list_ips)
579 60a80953 Ilias Tsitsimpis
        # Verify quotas
580 60a80953 Ilias Tsitsimpis
        self._check_quotas(ip=-len(fips))
581 60a80953 Ilias Tsitsimpis
582 d246be88 Ilias Tsitsimpis
583 d246be88 Ilias Tsitsimpis
class Retry(Exception):
584 d246be88 Ilias Tsitsimpis
    """Retry the action
585 d246be88 Ilias Tsitsimpis

586 d246be88 Ilias Tsitsimpis
    This is used by _try_unit_timeout_expires method.
587 d246be88 Ilias Tsitsimpis

588 d246be88 Ilias Tsitsimpis
    """