Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8.6 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 socket
43
import random
44
import subprocess
45

    
46
from synnefo_tools.burnin.common import BurninTests
47

    
48

    
49
# Too many public methods. pylint: disable-msg=R0904
50
class CycladesTests(BurninTests):
51
    """Extends the BurninTests class for Cyclades"""
52
    def _try_until_timeout_expires(self, opmsg, check_fun):
53
        """Try to perform an action until timeout expires"""
54
        assert callable(check_fun), "Not a function"
55

    
56
        action_timeout = self.action_timeout
57
        action_warning = self.action_warning
58
        if action_warning > action_timeout:
59
            action_warning = action_timeout
60

    
61
        start_time = time.time()
62
        while (start_time + action_warning) > time.time():
63
            try:
64
                return check_fun()
65
            except Retry:
66
                time.sleep(self.query_interval)
67
        self.warning("Operation `%s' is taking too long", opmsg)
68
        while (start_time + action_timeout) > time.time():
69
            try:
70
                return check_fun()
71
            except Retry:
72
                time.sleep(self.query_interval)
73
        self.error("Operation `%s' timed out", opmsg)
74
        self.fail("time out")
75

    
76
    def _get_list_of_servers(self, detail=False):
77
        """Get (detailed) list of servers"""
78
        if detail:
79
            self.info("Getting detailed list of servers")
80
        else:
81
            self.info("Getting simple list of servers")
82
        return self.clients.cyclades.list_servers(detail=detail)
83

    
84
    def _get_server_details(self, server):
85
        """Get details for a server"""
86
        self.info("Getting details for server %s with id %s",
87
                  server['name'], server['id'])
88
        return self.clients.cyclades.get_server_details(server['id'])
89

    
90
    def _create_server(self, name, image, flavor):
91
        """Create a new server"""
92
        self.info("Creating a server with name %s", name)
93
        self.info("Using image %s with id %s", image['name'], image['id'])
94
        self.info("Using flavor %s with id %s", flavor['name'], flavor['id'])
95
        server = self.clients.cyclades.create_server(
96
            name, flavor['id'], image['id'])
97

    
98
        self.info("Server id: %s", server['id'])
99
        self.info("Server password: %s", server['adminPass'])
100

    
101
        self.assertEqual(server['name'], name)
102
        self.assertEqual(server['flavor']['id'], flavor['id'])
103
        self.assertEqual(server['image']['id'], image['id'])
104
        self.assertEqual(server['status'], "BUILD")
105

    
106
        return server
107

    
108
    def _get_connection_username(self, server):
109
        """Determine the username to use to connect to the server"""
110
        users = server['metadata'].get("users", None)
111
        ret_user = None
112
        if users is not None:
113
            user_list = users.split()
114
            if "root" in user_list:
115
                ret_user = "root"
116
            else:
117
                ret_user = random.choice(user_list)
118
        else:
119
            # Return the login name for connections based on the server OS
120
            self.info("Could not find `users' metadata in server. Let's guess")
121
            os_value = server['metadata'].get("os")
122
            if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
123
                ret_user = "user"
124
            elif os_value in ("windows", "windows_alpha1"):
125
                ret_user = "Administrator"
126
            else:
127
                ret_user = "root"
128

    
129
        self.assertIsNotNone(ret_user)
130
        self.info("User's login name: %s", ret_user)
131
        return ret_user
132

    
133
    def _insist_on_server_transition(self, server, curr_status, new_status):
134
        """Insist on server transiting from curr_status to new_status"""
135
        def check_fun():
136
            """Check server status"""
137
            srv = self.clients.cyclades.get_server_details(server['id'])
138
            if srv['status'] == curr_status:
139
                raise Retry()
140
            elif srv['status'] == new_status:
141
                return
142
            else:
143
                msg = "Server %s went to unexpected status %s"
144
                self.error(msg, server['name'], srv['status'])
145
                self.fail(msg % (server['name'], srv['status']))
146
        opmsg = "Waiting for server %s to transit from %s to %s"
147
        self.info(opmsg, server['name'], curr_status, new_status)
148
        opmsg = opmsg % (server['name'], curr_status, new_status)
149
        self._try_until_timeout_expires(opmsg, check_fun)
150

    
151
    def _insist_on_tcp_connection(self, family, host, port):
152
        """Insist on tcp connection"""
153
        def check_fun():
154
            """Get a connected socket from the specified family to host:port"""
155
            sock = None
156
            for res in socket.getaddrinfo(host, port, family,
157
                                          socket.SOCK_STREAM, 0,
158
                                          socket.AI_PASSIVE):
159
                fam, socktype, proto, _, saddr = res
160
                try:
161
                    sock = socket.socket(fam, socktype, proto)
162
                except socket.error:
163
                    sock = None
164
                    continue
165
                try:
166
                    sock.connect(saddr)
167
                except socket.error:
168
                    sock.close()
169
                    sock = None
170
                    continue
171
            if sock is None:
172
                raise Retry
173
            return sock
174
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
175
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
176
        opmsg = "Connecting over %s to %s:%s"
177
        self.info(opmsg, familystr.get(family, "Unknown"), host, port)
178
        opmsg = opmsg % (familystr.get(family, "Unknown"), host, port)
179
        return self._try_until_timeout_expires(opmsg, check_fun)
180

    
181
    def _get_ip(self, server, version):
182
        """Get the public IP of a server from the detailed server info"""
183
        assert version in (4, 6)
184

    
185
        nics = server['attachments']
186
        public_addrs = None
187
        for nic in nics:
188
            net_id = nic['network_id']
189
            if self.clients.cyclades.get_network_details(net_id)['public']:
190
                public_addrs = nic['ipv' + str(version)]
191

    
192
        self.assertIsNotNone(public_addrs)
193
        msg = "Servers %s public IPv%s is %s"
194
        self.info(msg, server['name'], version, public_addrs)
195
        return public_addrs
196

    
197
    def _insist_on_ping(self, ip_addr, version):
198
        """Test server responds to a single IPv4 of IPv6 ping"""
199
        def check_fun():
200
            """Ping to server"""
201
            assert version in (4, 6)
202
            cmd = ("ping%s -c 3 -w 20 %s" %
203
                   ("6" if version == 6 else "", ip_addr))
204
            ping = subprocess.Popen(
205
                cmd, shell=True, stdout=subprocess.PIPE,
206
                stderr=subprocess.PIPE)
207
            ping.communicate()
208
            ret = ping.wait()
209
            if ret != 0:
210
                raise Retry
211
        opmsg = "Sent IPv%s ping requests to %s"
212
        self.info(opmsg, version, ip_addr)
213
        opmsg = opmsg % (version, ip_addr)
214
        self._try_until_timeout_expires(opmsg, check_fun)
215

    
216

    
217
class Retry(Exception):
218
    """Retry the action
219

220
    This is used by _try_unit_timeout_expires method.
221

222
    """