Statistics
| Branch: | Tag: | Revision:

root / snf-tools / burnin.py @ 00f87624

History | View | Annotate | Download (32.8 kB)

1 5a140b23 Vangelis Koukis
#!/usr/bin/env python
2 5a140b23 Vangelis Koukis
3 5a140b23 Vangelis Koukis
# Copyright 2011 GRNET S.A. All rights reserved.
4 5a140b23 Vangelis Koukis
#
5 5a140b23 Vangelis Koukis
# Redistribution and use in source and binary forms, with or
6 5a140b23 Vangelis Koukis
# without modification, are permitted provided that the following
7 5a140b23 Vangelis Koukis
# conditions are met:
8 5a140b23 Vangelis Koukis
#
9 5a140b23 Vangelis Koukis
#   1. Redistributions of source code must retain the above
10 5a140b23 Vangelis Koukis
#      copyright notice, this list of conditions and the following
11 5a140b23 Vangelis Koukis
#      disclaimer.
12 5a140b23 Vangelis Koukis
#
13 5a140b23 Vangelis Koukis
#   2. Redistributions in binary form must reproduce the above
14 5a140b23 Vangelis Koukis
#      copyright notice, this list of conditions and the following
15 5a140b23 Vangelis Koukis
#      disclaimer in the documentation and/or other materials
16 5a140b23 Vangelis Koukis
#      provided with the distribution.
17 5a140b23 Vangelis Koukis
#
18 5a140b23 Vangelis Koukis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 5a140b23 Vangelis Koukis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 5a140b23 Vangelis Koukis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 5a140b23 Vangelis Koukis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 5a140b23 Vangelis Koukis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 5a140b23 Vangelis Koukis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 5a140b23 Vangelis Koukis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 5a140b23 Vangelis Koukis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 5a140b23 Vangelis Koukis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 5a140b23 Vangelis Koukis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 5a140b23 Vangelis Koukis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 5a140b23 Vangelis Koukis
# POSSIBILITY OF SUCH DAMAGE.
30 5a140b23 Vangelis Koukis
#
31 5a140b23 Vangelis Koukis
# The views and conclusions contained in the software and
32 5a140b23 Vangelis Koukis
# documentation are those of the authors and should not be
33 5a140b23 Vangelis Koukis
# interpreted as representing official policies, either expressed
34 5a140b23 Vangelis Koukis
# or implied, of GRNET S.A.
35 5a140b23 Vangelis Koukis
36 5a140b23 Vangelis Koukis
"""Perform integration testing on a running Synnefo deployment"""
37 5a140b23 Vangelis Koukis
38 21bbbc9b Vangelis Koukis
import __main__
39 5a140b23 Vangelis Koukis
import datetime
40 5a140b23 Vangelis Koukis
import inspect
41 5a140b23 Vangelis Koukis
import logging
42 5a140b23 Vangelis Koukis
import os
43 5a140b23 Vangelis Koukis
import paramiko
44 21bbbc9b Vangelis Koukis
import prctl
45 5a140b23 Vangelis Koukis
import subprocess
46 21bbbc9b Vangelis Koukis
import signal
47 5a140b23 Vangelis Koukis
import socket
48 bc14ba88 Vangelis Koukis
import struct
49 5a140b23 Vangelis Koukis
import sys
50 5a140b23 Vangelis Koukis
import time
51 5a140b23 Vangelis Koukis
52 5a140b23 Vangelis Koukis
from IPy import IP
53 21bbbc9b Vangelis Koukis
from multiprocessing import Process, Queue
54 bc14ba88 Vangelis Koukis
from random import choice
55 21bbbc9b Vangelis Koukis
56 5a140b23 Vangelis Koukis
from kamaki.client import Client, ClientError
57 bc14ba88 Vangelis Koukis
from vncauthproxy.d3des import generate_response as d3des_generate_response
58 5a140b23 Vangelis Koukis
59 5a140b23 Vangelis Koukis
# Use backported unittest functionality if Python < 2.7
60 5a140b23 Vangelis Koukis
try:
61 5a140b23 Vangelis Koukis
    import unittest2 as unittest
62 5a140b23 Vangelis Koukis
except ImportError:
63 bc14ba88 Vangelis Koukis
    if sys.version_info < (2, 7):
64 bc14ba88 Vangelis Koukis
        raise Exception("The unittest2 package is required for Python < 2.7")
65 5a140b23 Vangelis Koukis
    import unittest
66 5a140b23 Vangelis Koukis
67 5a140b23 Vangelis Koukis
68 21bbbc9b Vangelis Koukis
API = None
69 21bbbc9b Vangelis Koukis
TOKEN = None
70 21bbbc9b Vangelis Koukis
DEFAULT_API = "http://dev67.dev.grnet.gr:8000/api/v1.1"
71 21bbbc9b Vangelis Koukis
DEFAULT_TOKEN = "46e427d657b20defe352804f0eb6f8a2"
72 5a140b23 Vangelis Koukis
# A unique id identifying this test run
73 21bbbc9b Vangelis Koukis
TEST_RUN_ID = datetime.datetime.strftime(datetime.datetime.now(),
74 21bbbc9b Vangelis Koukis
                                         "%Y%m%d%H%M%S")
75 21bbbc9b Vangelis Koukis
SNF_TEST_PREFIX = "snf-test-"
76 5a140b23 Vangelis Koukis
77 5a140b23 Vangelis Koukis
# Setup logging (FIXME - verigak)
78 5a140b23 Vangelis Koukis
logging.basicConfig(format="%(message)s")
79 00f87624 Vangelis Koukis
log = logging.getLogger("burnin")
80 5a140b23 Vangelis Koukis
log.setLevel(logging.INFO)
81 5a140b23 Vangelis Koukis
82 5a140b23 Vangelis Koukis
83 5a140b23 Vangelis Koukis
class UnauthorizedTestCase(unittest.TestCase):
84 5a140b23 Vangelis Koukis
    def test_unauthorized_access(self):
85 5a140b23 Vangelis Koukis
        """Test access without a valid token fails"""
86 5a140b23 Vangelis Koukis
        c = Client(API, "123")
87 5a140b23 Vangelis Koukis
        with self.assertRaises(ClientError) as cm:
88 5a140b23 Vangelis Koukis
            c.list_servers()
89 5a140b23 Vangelis Koukis
        self.assertEqual(cm.exception.status, 401)
90 5a140b23 Vangelis Koukis
91 5a140b23 Vangelis Koukis
92 5a140b23 Vangelis Koukis
class ImagesTestCase(unittest.TestCase):
93 5a140b23 Vangelis Koukis
    """Test image lists for consistency"""
94 5a140b23 Vangelis Koukis
    @classmethod
95 5a140b23 Vangelis Koukis
    def setUpClass(cls):
96 5a140b23 Vangelis Koukis
        """Initialize kamaki, get (detailed) list of images"""
97 5a140b23 Vangelis Koukis
        log.info("Getting simple and detailed list of images")
98 5a140b23 Vangelis Koukis
        cls.client = Client(API, TOKEN)
99 5a140b23 Vangelis Koukis
        cls.images = cls.client.list_images()
100 5a140b23 Vangelis Koukis
        cls.dimages = cls.client.list_images(detail=True)
101 5a140b23 Vangelis Koukis
102 5a140b23 Vangelis Koukis
    def test_001_list_images(self):
103 5a140b23 Vangelis Koukis
        """Test image list actually returns images"""
104 5a140b23 Vangelis Koukis
        self.assertGreater(len(self.images), 0)
105 5a140b23 Vangelis Koukis
106 5a140b23 Vangelis Koukis
    def test_002_list_images_detailed(self):
107 5a140b23 Vangelis Koukis
        """Test detailed image list is the same length as list"""
108 5a140b23 Vangelis Koukis
        self.assertEqual(len(self.dimages), len(self.images))
109 5a140b23 Vangelis Koukis
110 5a140b23 Vangelis Koukis
    def test_003_same_image_names(self):
111 5a140b23 Vangelis Koukis
        """Test detailed and simple image list contain same names"""
112 5a140b23 Vangelis Koukis
        names = sorted(map(lambda x: x["name"], self.images))
113 5a140b23 Vangelis Koukis
        dnames = sorted(map(lambda x: x["name"], self.dimages))
114 5a140b23 Vangelis Koukis
        self.assertEqual(names, dnames)
115 5a140b23 Vangelis Koukis
116 5a140b23 Vangelis Koukis
    def test_004_unique_image_names(self):
117 5a140b23 Vangelis Koukis
        """Test images have unique names"""
118 5a140b23 Vangelis Koukis
        names = sorted(map(lambda x: x["name"], self.images))
119 5a140b23 Vangelis Koukis
        self.assertEqual(sorted(list(set(names))), names)
120 5a140b23 Vangelis Koukis
121 5a140b23 Vangelis Koukis
    def test_005_image_metadata(self):
122 5a140b23 Vangelis Koukis
        """Test every image has specific metadata defined"""
123 5a140b23 Vangelis Koukis
        keys = frozenset(["OS", "description", "size"])
124 5a140b23 Vangelis Koukis
        for i in self.dimages:
125 5a140b23 Vangelis Koukis
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
126 5a140b23 Vangelis Koukis
127 5a140b23 Vangelis Koukis
128 5a140b23 Vangelis Koukis
class FlavorsTestCase(unittest.TestCase):
129 5a140b23 Vangelis Koukis
    """Test flavor lists for consistency"""
130 5a140b23 Vangelis Koukis
    @classmethod
131 5a140b23 Vangelis Koukis
    def setUpClass(cls):
132 5a140b23 Vangelis Koukis
        """Initialize kamaki, get (detailed) list of flavors"""
133 5a140b23 Vangelis Koukis
        log.info("Getting simple and detailed list of flavors")
134 5a140b23 Vangelis Koukis
        cls.client = Client(API, TOKEN)
135 5a140b23 Vangelis Koukis
        cls.flavors = cls.client.list_flavors()
136 5a140b23 Vangelis Koukis
        cls.dflavors = cls.client.list_flavors(detail=True)
137 5a140b23 Vangelis Koukis
138 5a140b23 Vangelis Koukis
    def test_001_list_flavors(self):
139 5a140b23 Vangelis Koukis
        """Test flavor list actually returns flavors"""
140 5a140b23 Vangelis Koukis
        self.assertGreater(len(self.flavors), 0)
141 5a140b23 Vangelis Koukis
142 5a140b23 Vangelis Koukis
    def test_002_list_flavors_detailed(self):
143 5a140b23 Vangelis Koukis
        """Test detailed flavor list is the same length as list"""
144 5a140b23 Vangelis Koukis
        self.assertEquals(len(self.dflavors), len(self.flavors))
145 5a140b23 Vangelis Koukis
146 5a140b23 Vangelis Koukis
    def test_003_same_flavor_names(self):
147 5a140b23 Vangelis Koukis
        """Test detailed and simple flavor list contain same names"""
148 5a140b23 Vangelis Koukis
        names = sorted(map(lambda x: x["name"], self.flavors))
149 5a140b23 Vangelis Koukis
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
150 5a140b23 Vangelis Koukis
        self.assertEqual(names, dnames)
151 5a140b23 Vangelis Koukis
152 5a140b23 Vangelis Koukis
    def test_004_unique_flavor_names(self):
153 5a140b23 Vangelis Koukis
        """Test flavors have unique names"""
154 5a140b23 Vangelis Koukis
        names = sorted(map(lambda x: x["name"], self.flavors))
155 5a140b23 Vangelis Koukis
        self.assertEqual(sorted(list(set(names))), names)
156 5a140b23 Vangelis Koukis
157 5a140b23 Vangelis Koukis
    def test_005_well_formed_flavor_names(self):
158 5a140b23 Vangelis Koukis
        """Test flavors have names of the form CxxRyyDzz
159 5a140b23 Vangelis Koukis

160 5a140b23 Vangelis Koukis
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
161 5a140b23 Vangelis Koukis

162 5a140b23 Vangelis Koukis
        """
163 5a140b23 Vangelis Koukis
        for f in self.dflavors:
164 5a140b23 Vangelis Koukis
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
165 5a140b23 Vangelis Koukis
                             f["name"],
166 5a140b23 Vangelis Koukis
                             "Flavor %s does not match its specs." % f["name"])
167 5a140b23 Vangelis Koukis
168 5a140b23 Vangelis Koukis
169 5a140b23 Vangelis Koukis
class ServersTestCase(unittest.TestCase):
170 5a140b23 Vangelis Koukis
    """Test server lists for consistency"""
171 5a140b23 Vangelis Koukis
    @classmethod
172 5a140b23 Vangelis Koukis
    def setUpClass(cls):
173 5a140b23 Vangelis Koukis
        """Initialize kamaki, get (detailed) list of servers"""
174 5a140b23 Vangelis Koukis
        log.info("Getting simple and detailed list of servers")
175 5a140b23 Vangelis Koukis
        cls.client = Client(API, TOKEN)
176 5a140b23 Vangelis Koukis
        cls.servers = cls.client.list_servers()
177 5a140b23 Vangelis Koukis
        cls.dservers = cls.client.list_servers(detail=True)
178 5a140b23 Vangelis Koukis
179 5a140b23 Vangelis Koukis
    def test_001_list_servers(self):
180 5a140b23 Vangelis Koukis
        """Test server list actually returns servers"""
181 5a140b23 Vangelis Koukis
        self.assertGreater(len(self.servers), 0)
182 5a140b23 Vangelis Koukis
183 5a140b23 Vangelis Koukis
    def test_002_list_servers_detailed(self):
184 5a140b23 Vangelis Koukis
        """Test detailed server list is the same length as list"""
185 5a140b23 Vangelis Koukis
        self.assertEqual(len(self.dservers), len(self.servers))
186 5a140b23 Vangelis Koukis
187 5a140b23 Vangelis Koukis
    def test_003_same_server_names(self):
188 5a140b23 Vangelis Koukis
        """Test detailed and simple flavor list contain same names"""
189 5a140b23 Vangelis Koukis
        names = sorted(map(lambda x: x["name"], self.servers))
190 5a140b23 Vangelis Koukis
        dnames = sorted(map(lambda x: x["name"], self.dservers))
191 5a140b23 Vangelis Koukis
        self.assertEqual(names, dnames)
192 5a140b23 Vangelis Koukis
193 5a140b23 Vangelis Koukis
194 5a140b23 Vangelis Koukis
# This class gets replicated into actual TestCases dynamically
195 5a140b23 Vangelis Koukis
class SpawnServerTestCase(unittest.TestCase):
196 5a140b23 Vangelis Koukis
    """Test scenario for server of the specified image"""
197 5a140b23 Vangelis Koukis
198 5a140b23 Vangelis Koukis
    @classmethod
199 5a140b23 Vangelis Koukis
    def setUpClass(cls):
200 5a140b23 Vangelis Koukis
        """Initialize a kamaki instance"""
201 bc14ba88 Vangelis Koukis
        log.info("Spawning server for image `%s'", cls.imagename)
202 5a140b23 Vangelis Koukis
        cls.client = Client(API, TOKEN)
203 5a140b23 Vangelis Koukis
204 5a140b23 Vangelis Koukis
    def _get_ipv4(self, server):
205 bc14ba88 Vangelis Koukis
        """Get the public IPv4 of a server from the detailed server info"""
206 5a140b23 Vangelis Koukis
        public_addrs = filter(lambda x: x["id"] == "public",
207 5a140b23 Vangelis Koukis
                              server["addresses"]["values"])
208 5a140b23 Vangelis Koukis
        self.assertEqual(len(public_addrs), 1)
209 5a140b23 Vangelis Koukis
        ipv4_addrs = filter(lambda x: x["version"] == 4,
210 5a140b23 Vangelis Koukis
                            public_addrs[0]["values"])
211 5a140b23 Vangelis Koukis
        self.assertEqual(len(ipv4_addrs), 1)
212 5a140b23 Vangelis Koukis
        return ipv4_addrs[0]["addr"]
213 5a140b23 Vangelis Koukis
214 5a140b23 Vangelis Koukis
    def _get_ipv6(self, server):
215 bc14ba88 Vangelis Koukis
        """Get the public IPv6 of a server from the detailed server info"""
216 5a140b23 Vangelis Koukis
        public_addrs = filter(lambda x: x["id"] == "public",
217 5a140b23 Vangelis Koukis
                              server["addresses"]["values"])
218 5a140b23 Vangelis Koukis
        self.assertEqual(len(public_addrs), 1)
219 5a140b23 Vangelis Koukis
        ipv6_addrs = filter(lambda x: x["version"] == 6,
220 5a140b23 Vangelis Koukis
                            public_addrs[0]["values"])
221 5a140b23 Vangelis Koukis
        self.assertEqual(len(ipv6_addrs), 1)
222 5a140b23 Vangelis Koukis
        return ipv6_addrs[0]["addr"]
223 5a140b23 Vangelis Koukis
224 bc14ba88 Vangelis Koukis
    def _connect_loginname(self, os):
225 bc14ba88 Vangelis Koukis
        """Return the login name for connections based on the server OS"""
226 21bbbc9b Vangelis Koukis
        if os in ("ubuntu", "kubuntu", "fedora"):
227 21bbbc9b Vangelis Koukis
            return "user"
228 21bbbc9b Vangelis Koukis
        elif os == "windows":
229 21bbbc9b Vangelis Koukis
            return "Administrator"
230 bc14ba88 Vangelis Koukis
        else:
231 21bbbc9b Vangelis Koukis
            return "root"
232 bc14ba88 Vangelis Koukis
233 bc14ba88 Vangelis Koukis
    def _verify_server_status(self, current_status, new_status):
234 bc14ba88 Vangelis Koukis
        """Verify a server has switched to a specified status"""
235 bc14ba88 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
236 21bbbc9b Vangelis Koukis
        if server["status"] not in (current_status, new_status):
237 21bbbc9b Vangelis Koukis
            return None  # Do not raise exception, return so the test fails
238 bc14ba88 Vangelis Koukis
        self.assertEquals(server["status"], new_status)
239 bc14ba88 Vangelis Koukis
240 bc14ba88 Vangelis Koukis
    def _get_connected_tcp_socket(self, family, host, port):
241 bc14ba88 Vangelis Koukis
        """Get a connected socket from the specified family to host:port"""
242 bc14ba88 Vangelis Koukis
        sock = None
243 bc14ba88 Vangelis Koukis
        for res in \
244 bc14ba88 Vangelis Koukis
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
245 bc14ba88 Vangelis Koukis
                               socket.AI_PASSIVE):
246 bc14ba88 Vangelis Koukis
            af, socktype, proto, canonname, sa = res
247 bc14ba88 Vangelis Koukis
            try:
248 bc14ba88 Vangelis Koukis
                sock = socket.socket(af, socktype, proto)
249 bc14ba88 Vangelis Koukis
            except socket.error as msg:
250 bc14ba88 Vangelis Koukis
                sock = None
251 bc14ba88 Vangelis Koukis
                continue
252 bc14ba88 Vangelis Koukis
            try:
253 bc14ba88 Vangelis Koukis
                sock.connect(sa)
254 bc14ba88 Vangelis Koukis
            except socket.error as msg:
255 bc14ba88 Vangelis Koukis
                sock.close()
256 bc14ba88 Vangelis Koukis
                sock = None
257 bc14ba88 Vangelis Koukis
                continue
258 bc14ba88 Vangelis Koukis
        self.assertIsNotNone(sock)
259 bc14ba88 Vangelis Koukis
        return sock
260 bc14ba88 Vangelis Koukis
261 bc14ba88 Vangelis Koukis
    def _ping_once(self, ipv6, ip):
262 bc14ba88 Vangelis Koukis
        """Test server responds to a single IPv4 or IPv6 ping"""
263 bc14ba88 Vangelis Koukis
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
264 bc14ba88 Vangelis Koukis
        ping = subprocess.Popen(cmd, shell=True,
265 bc14ba88 Vangelis Koukis
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
266 bc14ba88 Vangelis Koukis
        (stdout, stderr) = ping.communicate()
267 bc14ba88 Vangelis Koukis
        ret = ping.wait()
268 bc14ba88 Vangelis Koukis
        self.assertEquals(ret, 0)
269 5a140b23 Vangelis Koukis
270 bc14ba88 Vangelis Koukis
    def _get_hostname_over_ssh(self, hostip, username, password):
271 bc14ba88 Vangelis Koukis
        ssh = paramiko.SSHClient()
272 bc14ba88 Vangelis Koukis
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
273 bc14ba88 Vangelis Koukis
        try:
274 bc14ba88 Vangelis Koukis
            ssh.connect(hostip, username=username, password=password)
275 bc14ba88 Vangelis Koukis
        except socket.error:
276 bc14ba88 Vangelis Koukis
            raise AssertionError
277 bc14ba88 Vangelis Koukis
        stdin, stdout, stderr = ssh.exec_command("hostname")
278 bc14ba88 Vangelis Koukis
        lines = stdout.readlines()
279 bc14ba88 Vangelis Koukis
        self.assertEqual(len(lines), 1)
280 4fdd25ab Vangelis Koukis
        return lines[0]
281 bc14ba88 Vangelis Koukis
282 bc14ba88 Vangelis Koukis
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
283 bc14ba88 Vangelis Koukis
                                   opmsg, callable, *args, **kwargs):
284 bc14ba88 Vangelis Koukis
        if warn_timeout == fail_timeout:
285 5a140b23 Vangelis Koukis
            warn_timeout = fail_timeout + 1
286 5a140b23 Vangelis Koukis
        warn_tmout = time.time() + warn_timeout
287 5a140b23 Vangelis Koukis
        fail_tmout = time.time() + fail_timeout
288 5a140b23 Vangelis Koukis
        while True:
289 4fdd25ab Vangelis Koukis
            self.assertLess(time.time(), fail_tmout,
290 21bbbc9b Vangelis Koukis
                            "operation `%s' timed out" % opmsg)
291 5a140b23 Vangelis Koukis
            if time.time() > warn_tmout:
292 4fdd25ab Vangelis Koukis
                log.warning("Server %d: `%s' operation `%s' not done yet",
293 4fdd25ab Vangelis Koukis
                            self.serverid, self.servername, opmsg)
294 bc14ba88 Vangelis Koukis
            try:
295 4fdd25ab Vangelis Koukis
                log.info("%s... " % opmsg)
296 bc14ba88 Vangelis Koukis
                return callable(*args, **kwargs)
297 bc14ba88 Vangelis Koukis
            except AssertionError:
298 bc14ba88 Vangelis Koukis
                pass
299 5a140b23 Vangelis Koukis
            time.sleep(self.query_interval)
300 5a140b23 Vangelis Koukis
301 bc14ba88 Vangelis Koukis
    def _insist_on_tcp_connection(self, family, host, port):
302 21bbbc9b Vangelis Koukis
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
303 21bbbc9b Vangelis Koukis
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
304 bc14ba88 Vangelis Koukis
        msg = "connect over %s to %s:%s" % \
305 bc14ba88 Vangelis Koukis
              (familystr.get(family, "Unknown"), host, port)
306 bc14ba88 Vangelis Koukis
        sock = self._try_until_timeout_expires(
307 bc14ba88 Vangelis Koukis
                self.action_timeout, self.action_timeout,
308 bc14ba88 Vangelis Koukis
                msg, self._get_connected_tcp_socket,
309 bc14ba88 Vangelis Koukis
                family, host, port)
310 bc14ba88 Vangelis Koukis
        return sock
311 bc14ba88 Vangelis Koukis
312 bc14ba88 Vangelis Koukis
    def _insist_on_status_transition(self, current_status, new_status,
313 bc14ba88 Vangelis Koukis
                                    fail_timeout, warn_timeout=None):
314 4fdd25ab Vangelis Koukis
        msg = "Server %d: `%s', waiting for %s -> %s" % \
315 4fdd25ab Vangelis Koukis
              (self.serverid, self.servername, current_status, new_status)
316 bc14ba88 Vangelis Koukis
        if warn_timeout is None:
317 bc14ba88 Vangelis Koukis
            warn_timeout = fail_timeout
318 bc14ba88 Vangelis Koukis
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
319 bc14ba88 Vangelis Koukis
                                        msg, self._verify_server_status,
320 bc14ba88 Vangelis Koukis
                                        current_status, new_status)
321 21bbbc9b Vangelis Koukis
        # Ensure the status is actually the expected one
322 21bbbc9b Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
323 21bbbc9b Vangelis Koukis
        self.assertEquals(server["status"], new_status)
324 bc14ba88 Vangelis Koukis
325 bc14ba88 Vangelis Koukis
    def _insist_on_ssh_hostname(self, hostip, username, password):
326 4fdd25ab Vangelis Koukis
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
327 bc14ba88 Vangelis Koukis
        hostname = self._try_until_timeout_expires(
328 bc14ba88 Vangelis Koukis
                self.action_timeout, self.action_timeout,
329 bc14ba88 Vangelis Koukis
                msg, self._get_hostname_over_ssh,
330 bc14ba88 Vangelis Koukis
                hostip, username, password)
331 bc14ba88 Vangelis Koukis
332 bc14ba88 Vangelis Koukis
        # The hostname must be of the form 'prefix-id'
333 bc14ba88 Vangelis Koukis
        self.assertTrue(hostname.endswith("-%d\n" % self.serverid))
334 5a140b23 Vangelis Koukis
335 5a140b23 Vangelis Koukis
    def _skipIf(self, condition, msg):
336 5a140b23 Vangelis Koukis
        if condition:
337 5a140b23 Vangelis Koukis
            self.skipTest(msg)
338 5a140b23 Vangelis Koukis
339 5a140b23 Vangelis Koukis
    def test_001_submit_create_server(self):
340 5a140b23 Vangelis Koukis
        """Test submit create server request"""
341 5a140b23 Vangelis Koukis
        server = self.client.create_server(self.servername, self.flavorid,
342 5a140b23 Vangelis Koukis
                                           self.imageid, self.personality)
343 5a140b23 Vangelis Koukis
        self.assertEqual(server["name"], self.servername)
344 5a140b23 Vangelis Koukis
        self.assertEqual(server["flavorRef"], self.flavorid)
345 5a140b23 Vangelis Koukis
        self.assertEqual(server["imageRef"], self.imageid)
346 5a140b23 Vangelis Koukis
        self.assertEqual(server["status"], "BUILD")
347 5a140b23 Vangelis Koukis
348 5a140b23 Vangelis Koukis
        # Update class attributes to reflect data on building server
349 5a140b23 Vangelis Koukis
        cls = type(self)
350 5a140b23 Vangelis Koukis
        cls.serverid = server["id"]
351 bc14ba88 Vangelis Koukis
        cls.username = None
352 5a140b23 Vangelis Koukis
        cls.passwd = server["adminPass"]
353 5a140b23 Vangelis Koukis
354 5a140b23 Vangelis Koukis
    def test_002a_server_is_building_in_list(self):
355 5a140b23 Vangelis Koukis
        """Test server is in BUILD state, in server list"""
356 5a140b23 Vangelis Koukis
        servers = self.client.list_servers(detail=True)
357 5a140b23 Vangelis Koukis
        servers = filter(lambda x: x["name"] == self.servername, servers)
358 5a140b23 Vangelis Koukis
        self.assertEqual(len(servers), 1)
359 5a140b23 Vangelis Koukis
        server = servers[0]
360 5a140b23 Vangelis Koukis
        self.assertEqual(server["name"], self.servername)
361 5a140b23 Vangelis Koukis
        self.assertEqual(server["flavorRef"], self.flavorid)
362 5a140b23 Vangelis Koukis
        self.assertEqual(server["imageRef"], self.imageid)
363 5a140b23 Vangelis Koukis
        self.assertEqual(server["status"], "BUILD")
364 5a140b23 Vangelis Koukis
365 5a140b23 Vangelis Koukis
    def test_002b_server_is_building_in_details(self):
366 5a140b23 Vangelis Koukis
        """Test server is in BUILD state, in details"""
367 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
368 5a140b23 Vangelis Koukis
        self.assertEqual(server["name"], self.servername)
369 5a140b23 Vangelis Koukis
        self.assertEqual(server["flavorRef"], self.flavorid)
370 5a140b23 Vangelis Koukis
        self.assertEqual(server["imageRef"], self.imageid)
371 5a140b23 Vangelis Koukis
        self.assertEqual(server["status"], "BUILD")
372 5a140b23 Vangelis Koukis
373 5a140b23 Vangelis Koukis
    def test_002c_set_server_metadata(self):
374 5a140b23 Vangelis Koukis
        image = self.client.get_image_details(self.imageid)
375 bc14ba88 Vangelis Koukis
        os = image["metadata"]["values"]["OS"]
376 bc14ba88 Vangelis Koukis
        loginname = image["metadata"]["values"].get("loginname", None)
377 bc14ba88 Vangelis Koukis
        self.client.update_server_metadata(self.serverid, OS=os)
378 bc14ba88 Vangelis Koukis
379 bc14ba88 Vangelis Koukis
        # Determine the username to use for future connections
380 bc14ba88 Vangelis Koukis
        # to this host
381 bc14ba88 Vangelis Koukis
        cls = type(self)
382 bc14ba88 Vangelis Koukis
        cls.username = loginname
383 bc14ba88 Vangelis Koukis
        if not cls.username:
384 bc14ba88 Vangelis Koukis
            cls.username = self._connect_loginname(os)
385 bc14ba88 Vangelis Koukis
        self.assertIsNotNone(cls.username)
386 5a140b23 Vangelis Koukis
387 5a140b23 Vangelis Koukis
    def test_002d_verify_server_metadata(self):
388 5a140b23 Vangelis Koukis
        """Test server metadata keys are set based on image metadata"""
389 5a140b23 Vangelis Koukis
        servermeta = self.client.get_server_metadata(self.serverid)
390 5a140b23 Vangelis Koukis
        imagemeta = self.client.get_image_metadata(self.imageid)
391 5a140b23 Vangelis Koukis
        self.assertEqual(servermeta["OS"], imagemeta["OS"])
392 5a140b23 Vangelis Koukis
393 5a140b23 Vangelis Koukis
    def test_003_server_becomes_active(self):
394 5a140b23 Vangelis Koukis
        """Test server becomes ACTIVE"""
395 bc14ba88 Vangelis Koukis
        self._insist_on_status_transition("BUILD", "ACTIVE",
396 5a140b23 Vangelis Koukis
                                         self.build_fail, self.build_warning)
397 5a140b23 Vangelis Koukis
398 5a140b23 Vangelis Koukis
    def test_003a_get_server_oob_console(self):
399 bc14ba88 Vangelis Koukis
        """Test getting OOB server console over VNC
400 bc14ba88 Vangelis Koukis

401 bc14ba88 Vangelis Koukis
        Implementation of RFB protocol follows
402 bc14ba88 Vangelis Koukis
        http://www.realvnc.com/docs/rfbproto.pdf.
403 bc14ba88 Vangelis Koukis

404 bc14ba88 Vangelis Koukis
        """
405 5a140b23 Vangelis Koukis
        console = self.client.get_server_console(self.serverid)
406 5a140b23 Vangelis Koukis
        self.assertEquals(console['type'], "vnc")
407 bc14ba88 Vangelis Koukis
        sock = self._insist_on_tcp_connection(socket.AF_UNSPEC,
408 5a140b23 Vangelis Koukis
                                        console["host"], console["port"])
409 bc14ba88 Vangelis Koukis
410 bc14ba88 Vangelis Koukis
        # Step 1. ProtocolVersion message (par. 6.1.1)
411 5a140b23 Vangelis Koukis
        version = sock.recv(1024)
412 bc14ba88 Vangelis Koukis
        self.assertEquals(version, 'RFB 003.008\n')
413 bc14ba88 Vangelis Koukis
        sock.send(version)
414 bc14ba88 Vangelis Koukis
415 bc14ba88 Vangelis Koukis
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
416 bc14ba88 Vangelis Koukis
        sec = sock.recv(1024)
417 bc14ba88 Vangelis Koukis
        self.assertEquals(list(sec), ['\x01', '\x02'])
418 bc14ba88 Vangelis Koukis
419 bc14ba88 Vangelis Koukis
        # Step 3. Request VNC Authentication (par 6.1.2)
420 bc14ba88 Vangelis Koukis
        sock.send('\x02')
421 bc14ba88 Vangelis Koukis
422 bc14ba88 Vangelis Koukis
        # Step 4. Receive Challenge (par 6.2.2)
423 bc14ba88 Vangelis Koukis
        challenge = sock.recv(1024)
424 bc14ba88 Vangelis Koukis
        self.assertEquals(len(challenge), 16)
425 bc14ba88 Vangelis Koukis
426 bc14ba88 Vangelis Koukis
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
427 bc14ba88 Vangelis Koukis
        response = d3des_generate_response(
428 bc14ba88 Vangelis Koukis
            (console["password"] + '\0' * 8)[:8], challenge)
429 bc14ba88 Vangelis Koukis
        sock.send(response)
430 bc14ba88 Vangelis Koukis
431 bc14ba88 Vangelis Koukis
        # Step 6. SecurityResult (par 6.1.3)
432 bc14ba88 Vangelis Koukis
        result = sock.recv(4)
433 bc14ba88 Vangelis Koukis
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
434 5a140b23 Vangelis Koukis
        sock.close()
435 5a140b23 Vangelis Koukis
436 5a140b23 Vangelis Koukis
    def test_004_server_has_ipv4(self):
437 5a140b23 Vangelis Koukis
        """Test active server has a valid IPv4 address"""
438 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
439 5a140b23 Vangelis Koukis
        ipv4 = self._get_ipv4(server)
440 5a140b23 Vangelis Koukis
        self.assertEquals(IP(ipv4).version(), 4)
441 5a140b23 Vangelis Koukis
442 5a140b23 Vangelis Koukis
    def test_005_server_has_ipv6(self):
443 5a140b23 Vangelis Koukis
        """Test active server has a valid IPv6 address"""
444 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
445 5a140b23 Vangelis Koukis
        ipv6 = self._get_ipv6(server)
446 5a140b23 Vangelis Koukis
        self.assertEquals(IP(ipv6).version(), 6)
447 5a140b23 Vangelis Koukis
448 5a140b23 Vangelis Koukis
    def test_006_server_responds_to_ping_IPv4(self):
449 5a140b23 Vangelis Koukis
        """Test server responds to ping on IPv4 address"""
450 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
451 bc14ba88 Vangelis Koukis
        ip = self._get_ipv4(server)
452 bc14ba88 Vangelis Koukis
        self._try_until_timeout_expires(self.action_timeout,
453 bc14ba88 Vangelis Koukis
                                        self.action_timeout,
454 4fdd25ab Vangelis Koukis
                                        "PING IPv4 to %s" % ip,
455 4fdd25ab Vangelis Koukis
                                        self._ping_once,
456 bc14ba88 Vangelis Koukis
                                        False, ip)
457 5a140b23 Vangelis Koukis
458 5a140b23 Vangelis Koukis
    def test_007_server_responds_to_ping_IPv6(self):
459 5a140b23 Vangelis Koukis
        """Test server responds to ping on IPv6 address"""
460 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
461 bc14ba88 Vangelis Koukis
        ip = self._get_ipv6(server)
462 bc14ba88 Vangelis Koukis
        self._try_until_timeout_expires(self.action_timeout,
463 bc14ba88 Vangelis Koukis
                                        self.action_timeout,
464 4fdd25ab Vangelis Koukis
                                        "PING IPv6 to %s" % ip,
465 4fdd25ab Vangelis Koukis
                                        self._ping_once,
466 bc14ba88 Vangelis Koukis
                                        True, ip)
467 5a140b23 Vangelis Koukis
468 5a140b23 Vangelis Koukis
    def test_008_submit_shutdown_request(self):
469 5a140b23 Vangelis Koukis
        """Test submit request to shutdown server"""
470 5a140b23 Vangelis Koukis
        self.client.shutdown_server(self.serverid)
471 5a140b23 Vangelis Koukis
472 5a140b23 Vangelis Koukis
    def test_009_server_becomes_stopped(self):
473 5a140b23 Vangelis Koukis
        """Test server becomes STOPPED"""
474 bc14ba88 Vangelis Koukis
        self._insist_on_status_transition("ACTIVE", "STOPPED",
475 bc14ba88 Vangelis Koukis
                                         self.action_timeout,
476 5a140b23 Vangelis Koukis
                                         self.action_timeout)
477 5a140b23 Vangelis Koukis
478 5a140b23 Vangelis Koukis
    def test_010_submit_start_request(self):
479 5a140b23 Vangelis Koukis
        """Test submit start server request"""
480 5a140b23 Vangelis Koukis
        self.client.start_server(self.serverid)
481 5a140b23 Vangelis Koukis
482 5a140b23 Vangelis Koukis
    def test_011_server_becomes_active(self):
483 5a140b23 Vangelis Koukis
        """Test server becomes ACTIVE again"""
484 bc14ba88 Vangelis Koukis
        self._insist_on_status_transition("STOPPED", "ACTIVE",
485 bc14ba88 Vangelis Koukis
                                         self.action_timeout,
486 5a140b23 Vangelis Koukis
                                         self.action_timeout)
487 5a140b23 Vangelis Koukis
488 5a140b23 Vangelis Koukis
    def test_011a_server_responds_to_ping_IPv4(self):
489 5a140b23 Vangelis Koukis
        """Test server OS is actually up and running again"""
490 5a140b23 Vangelis Koukis
        self.test_006_server_responds_to_ping_IPv4()
491 5a140b23 Vangelis Koukis
492 5a140b23 Vangelis Koukis
    def test_012_ssh_to_server_IPv4(self):
493 5a140b23 Vangelis Koukis
        """Test SSH to server public IPv4 works, verify hostname"""
494 bc14ba88 Vangelis Koukis
        self._skipIf(self.is_windows, "only valid for Linux servers")
495 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
496 bc14ba88 Vangelis Koukis
        self._insist_on_ssh_hostname(self._get_ipv4(server),
497 bc14ba88 Vangelis Koukis
                                     self.username, self.passwd)
498 5a140b23 Vangelis Koukis
499 5a140b23 Vangelis Koukis
    def test_013_ssh_to_server_IPv6(self):
500 5a140b23 Vangelis Koukis
        """Test SSH to server public IPv6 works, verify hostname"""
501 bc14ba88 Vangelis Koukis
        self._skipIf(self.is_windows, "only valid for Linux servers")
502 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
503 bc14ba88 Vangelis Koukis
        self._insist_on_ssh_hostname(self._get_ipv6(server),
504 bc14ba88 Vangelis Koukis
                                     self.username, self.passwd)
505 5a140b23 Vangelis Koukis
506 5a140b23 Vangelis Koukis
    def test_014_rdp_to_server_IPv4(self):
507 5a140b23 Vangelis Koukis
        "Test RDP connection to server public IPv4 works"""
508 bc14ba88 Vangelis Koukis
        self._skipIf(not self.is_windows, "only valid for Windows servers")
509 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
510 5a140b23 Vangelis Koukis
        ipv4 = self._get_ipv4(server)
511 bc14ba88 Vangelis Koukis
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
512 5a140b23 Vangelis Koukis
513 5a140b23 Vangelis Koukis
        # No actual RDP processing done. We assume the RDP server is there
514 5a140b23 Vangelis Koukis
        # if the connection to the RDP port is successful.
515 5a140b23 Vangelis Koukis
        sock.close()
516 5a140b23 Vangelis Koukis
517 5a140b23 Vangelis Koukis
    def test_015_rdp_to_server_IPv6(self):
518 5a140b23 Vangelis Koukis
        "Test RDP connection to server public IPv6 works"""
519 bc14ba88 Vangelis Koukis
        self._skipIf(not self.is_windows, "only valid for Windows servers")
520 5a140b23 Vangelis Koukis
        server = self.client.get_server_details(self.serverid)
521 5a140b23 Vangelis Koukis
        ipv6 = self._get_ipv6(server)
522 5a140b23 Vangelis Koukis
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
523 5a140b23 Vangelis Koukis
524 5a140b23 Vangelis Koukis
        # No actual RDP processing done. We assume the RDP server is there
525 5a140b23 Vangelis Koukis
        # if the connection to the RDP port is successful.
526 5a140b23 Vangelis Koukis
        sock.close()
527 5a140b23 Vangelis Koukis
528 5a140b23 Vangelis Koukis
    def test_016_personality_is_enforced(self):
529 5a140b23 Vangelis Koukis
        """Test file injection for personality enforcement"""
530 bc14ba88 Vangelis Koukis
        self._skipIf(self.is_windows, "only implemented for Linux servers")
531 5a140b23 Vangelis Koukis
        self.assertTrue(False, "test not implemented, will fail")
532 5a140b23 Vangelis Koukis
533 4fdd25ab Vangelis Koukis
    def test_017_submit_delete_request(self):
534 4fdd25ab Vangelis Koukis
        """Test submit request to delete server"""
535 4fdd25ab Vangelis Koukis
        self.client.delete_server(self.serverid)
536 4fdd25ab Vangelis Koukis
537 4fdd25ab Vangelis Koukis
    def test_018_server_becomes_deleted(self):
538 4fdd25ab Vangelis Koukis
        """Test server becomes DELETED"""
539 4fdd25ab Vangelis Koukis
        self._insist_on_status_transition("ACTIVE", "DELETED",
540 4fdd25ab Vangelis Koukis
                                         self.action_timeout,
541 4fdd25ab Vangelis Koukis
                                         self.action_timeout)
542 4fdd25ab Vangelis Koukis
543 4fdd25ab Vangelis Koukis
    def test_019_server_no_longer_in_server_list(self):
544 4fdd25ab Vangelis Koukis
        """Test server is no longer in server list"""
545 4fdd25ab Vangelis Koukis
        servers = self.client.list_servers()
546 21bbbc9b Vangelis Koukis
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
547 21bbbc9b Vangelis Koukis
548 21bbbc9b Vangelis Koukis
549 21bbbc9b Vangelis Koukis
class TestRunnerProcess(Process):
550 21bbbc9b Vangelis Koukis
    """A distinct process used to execute part of the tests in parallel"""
551 21bbbc9b Vangelis Koukis
    def __init__(self, **kw):
552 21bbbc9b Vangelis Koukis
        Process.__init__(self, **kw)
553 21bbbc9b Vangelis Koukis
        kwargs = kw["kwargs"]
554 21bbbc9b Vangelis Koukis
        self.testq = kwargs["testq"]
555 21bbbc9b Vangelis Koukis
        self.runner = kwargs["runner"]
556 21bbbc9b Vangelis Koukis
557 21bbbc9b Vangelis Koukis
    def run(self):
558 21bbbc9b Vangelis Koukis
        # Make sure this test runner process dies with the parent
559 21bbbc9b Vangelis Koukis
        # and is not left behind.
560 21bbbc9b Vangelis Koukis
        #
561 21bbbc9b Vangelis Koukis
        # WARNING: This uses the prctl(2) call and is
562 21bbbc9b Vangelis Koukis
        # Linux-specific.
563 21bbbc9b Vangelis Koukis
        prctl.set_pdeathsig(signal.SIGHUP)
564 21bbbc9b Vangelis Koukis
565 21bbbc9b Vangelis Koukis
        while True:
566 21bbbc9b Vangelis Koukis
            log.debug("I am process %d, GETting from queue is %s",
567 21bbbc9b Vangelis Koukis
                     os.getpid(), self.testq)
568 21bbbc9b Vangelis Koukis
            msg = self.testq.get()
569 21bbbc9b Vangelis Koukis
            log.debug("Dequeued msg: %s", msg)
570 21bbbc9b Vangelis Koukis
571 21bbbc9b Vangelis Koukis
            if msg == "TEST_RUNNER_TERMINATE":
572 21bbbc9b Vangelis Koukis
                raise SystemExit
573 21bbbc9b Vangelis Koukis
            elif issubclass(msg, unittest.TestCase):
574 21bbbc9b Vangelis Koukis
                # Assemble a TestSuite, and run it
575 21bbbc9b Vangelis Koukis
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
576 21bbbc9b Vangelis Koukis
                self.runner.run(suite)
577 21bbbc9b Vangelis Koukis
            else:
578 21bbbc9b Vangelis Koukis
                raise Exception("Cannot handle msg: %s" % msg)
579 21bbbc9b Vangelis Koukis
580 21bbbc9b Vangelis Koukis
581 21bbbc9b Vangelis Koukis
def _run_cases_in_parallel(cases, fanout=1, runner=None):
582 21bbbc9b Vangelis Koukis
    """Run instances of TestCase in parallel, in a number of distinct processes
583 21bbbc9b Vangelis Koukis

584 21bbbc9b Vangelis Koukis
    The cases iterable specifies the TestCases to be executed in parallel,
585 21bbbc9b Vangelis Koukis
    by test runners running in distinct processes.
586 21bbbc9b Vangelis Koukis
    The fanout parameter specifies the number of processes to spawn,
587 21bbbc9b Vangelis Koukis
    and defaults to 1.
588 21bbbc9b Vangelis Koukis
    The runner argument specifies the test runner class to use inside each
589 21bbbc9b Vangelis Koukis
    runner process.
590 21bbbc9b Vangelis Koukis

591 21bbbc9b Vangelis Koukis
    """
592 21bbbc9b Vangelis Koukis
    if runner is None:
593 00f87624 Vangelis Koukis
        runner = unittest.TextTestRunner(verbosity=2, failfast=True)
594 21bbbc9b Vangelis Koukis
595 21bbbc9b Vangelis Koukis
    # testq: The master process enqueues TestCase objects into this queue,
596 21bbbc9b Vangelis Koukis
    #        test runner processes pick them up for execution, in parallel.
597 21bbbc9b Vangelis Koukis
    testq = Queue()
598 21bbbc9b Vangelis Koukis
    runners = []
599 21bbbc9b Vangelis Koukis
    for i in xrange(0, fanout):
600 21bbbc9b Vangelis Koukis
        kwargs = dict(testq=testq, runner=runner)
601 21bbbc9b Vangelis Koukis
        runners.append(TestRunnerProcess(kwargs=kwargs))
602 21bbbc9b Vangelis Koukis
603 21bbbc9b Vangelis Koukis
    log.info("Spawning %d test runner processes", len(runners))
604 21bbbc9b Vangelis Koukis
    for p in runners:
605 21bbbc9b Vangelis Koukis
        p.start()
606 21bbbc9b Vangelis Koukis
    log.debug("Spawned %d test runners, PIDs are %s",
607 21bbbc9b Vangelis Koukis
              len(runners), [p.pid for p in runners])
608 21bbbc9b Vangelis Koukis
609 21bbbc9b Vangelis Koukis
    # Enqueue test cases
610 21bbbc9b Vangelis Koukis
    map(testq.put, cases)
611 21bbbc9b Vangelis Koukis
    map(testq.put, ["TEST_RUNNER_TERMINATE"] * len(runners))
612 21bbbc9b Vangelis Koukis
613 21bbbc9b Vangelis Koukis
    log.debug("Joining %d processes", len(runners))
614 21bbbc9b Vangelis Koukis
    for p in runners:
615 21bbbc9b Vangelis Koukis
        p.join()
616 21bbbc9b Vangelis Koukis
    log.debug("Done joining %d processes", len(runners))
617 4fdd25ab Vangelis Koukis
618 5a140b23 Vangelis Koukis
619 5a140b23 Vangelis Koukis
def _spawn_server_test_case(**kwargs):
620 5a140b23 Vangelis Koukis
    """Construct a new unit test case class from SpawnServerTestCase"""
621 5a140b23 Vangelis Koukis
622 21bbbc9b Vangelis Koukis
    name = "SpawnServerTestCase_%d" % kwargs["imageid"]
623 5a140b23 Vangelis Koukis
    cls = type(name, (SpawnServerTestCase,), kwargs)
624 5a140b23 Vangelis Koukis
625 5a140b23 Vangelis Koukis
    # Patch extra parameters into test names by manipulating method docstrings
626 5a140b23 Vangelis Koukis
    for (mname, m) in \
627 5a140b23 Vangelis Koukis
        inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
628 5a140b23 Vangelis Koukis
            if hasattr(m, __doc__):
629 5a140b23 Vangelis Koukis
                m.__func__.__doc__ = "[%s] %s" % (imagename, m.__doc__)
630 e72bcf60 Vangelis Koukis
631 e72bcf60 Vangelis Koukis
    # Make sure the class can be pickled, by listing it among
632 e72bcf60 Vangelis Koukis
    # the attributes of __main__. A PicklingError is raised otherwise.
633 e72bcf60 Vangelis Koukis
    setattr(__main__, name, cls)
634 5a140b23 Vangelis Koukis
    return cls
635 5a140b23 Vangelis Koukis
636 5a140b23 Vangelis Koukis
637 5a140b23 Vangelis Koukis
def cleanup_servers(delete_stale=False):
638 5a140b23 Vangelis Koukis
    c = Client(API, TOKEN)
639 5a140b23 Vangelis Koukis
    servers = c.list_servers()
640 5a140b23 Vangelis Koukis
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
641 5a140b23 Vangelis Koukis
642 4fdd25ab Vangelis Koukis
    if len(stale) == 0:
643 4fdd25ab Vangelis Koukis
        return
644 4fdd25ab Vangelis Koukis
645 5a140b23 Vangelis Koukis
    print >> sys.stderr, "Found these stale servers from previous runs:"
646 5a140b23 Vangelis Koukis
    print "    " + \
647 21bbbc9b Vangelis Koukis
          "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
648 5a140b23 Vangelis Koukis
649 5a140b23 Vangelis Koukis
    if delete_stale:
650 5a140b23 Vangelis Koukis
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
651 5a140b23 Vangelis Koukis
        for server in stale:
652 21bbbc9b Vangelis Koukis
            c.delete_server(server["id"])
653 5a140b23 Vangelis Koukis
        print >> sys.stderr, "    ...done"
654 5a140b23 Vangelis Koukis
    else:
655 5a140b23 Vangelis Koukis
        print >> sys.stderr, "Use --delete-stale to delete them."
656 5a140b23 Vangelis Koukis
657 5a140b23 Vangelis Koukis
658 5a140b23 Vangelis Koukis
def parse_arguments(args):
659 5a140b23 Vangelis Koukis
    from optparse import OptionParser
660 5a140b23 Vangelis Koukis
661 5a140b23 Vangelis Koukis
    kw = {}
662 5a140b23 Vangelis Koukis
    kw["usage"] = "%prog [options]"
663 5a140b23 Vangelis Koukis
    kw["description"] = \
664 5a140b23 Vangelis Koukis
        "%prog runs a number of test scenarios on a " \
665 5a140b23 Vangelis Koukis
        "Synnefo deployment."
666 5a140b23 Vangelis Koukis
667 5a140b23 Vangelis Koukis
    parser = OptionParser(**kw)
668 5a140b23 Vangelis Koukis
    parser.disable_interspersed_args()
669 21bbbc9b Vangelis Koukis
    parser.add_option("--api",
670 21bbbc9b Vangelis Koukis
                      action="store", type="string", dest="api",
671 21bbbc9b Vangelis Koukis
                      help="The API URI to use to reach the Synnefo API",
672 21bbbc9b Vangelis Koukis
                      default=DEFAULT_API)
673 21bbbc9b Vangelis Koukis
    parser.add_option("--token",
674 21bbbc9b Vangelis Koukis
                      action="store", type="string", dest="token",
675 21bbbc9b Vangelis Koukis
                      help="The token to use for authentication to the API",
676 21bbbc9b Vangelis Koukis
                      default=DEFAULT_TOKEN)
677 00f87624 Vangelis Koukis
    parser.add_option("--nofailfast",
678 00f87624 Vangelis Koukis
                      action="store_true", dest="nofailfast",
679 00f87624 Vangelis Koukis
                      help="Do not fail immediately if one of the tests " \
680 00f87624 Vangelis Koukis
                           "fails (EXPERIMENTAL)",
681 bc14ba88 Vangelis Koukis
                      default=False)
682 5a140b23 Vangelis Koukis
    parser.add_option("--action-timeout",
683 5a140b23 Vangelis Koukis
                      action="store", type="int", dest="action_timeout",
684 5a140b23 Vangelis Koukis
                      metavar="TIMEOUT",
685 5a140b23 Vangelis Koukis
                      help="Wait SECONDS seconds for a server action to " \
686 5a140b23 Vangelis Koukis
                           "complete, then the test is considered failed",
687 5a140b23 Vangelis Koukis
                      default=20)
688 5a140b23 Vangelis Koukis
    parser.add_option("--build-warning",
689 5a140b23 Vangelis Koukis
                      action="store", type="int", dest="build_warning",
690 5a140b23 Vangelis Koukis
                      metavar="TIMEOUT",
691 5a140b23 Vangelis Koukis
                      help="Warn if TIMEOUT seconds have passed and a " \
692 5a140b23 Vangelis Koukis
                           "build operation is still pending",
693 5a140b23 Vangelis Koukis
                      default=600)
694 5a140b23 Vangelis Koukis
    parser.add_option("--build-fail",
695 5a140b23 Vangelis Koukis
                      action="store", type="int", dest="build_fail",
696 5a140b23 Vangelis Koukis
                      metavar="BUILD_TIMEOUT",
697 5a140b23 Vangelis Koukis
                      help="Fail the test if TIMEOUT seconds have passed " \
698 5a140b23 Vangelis Koukis
                           "and a build operation is still incomplete",
699 5a140b23 Vangelis Koukis
                      default=900)
700 5a140b23 Vangelis Koukis
    parser.add_option("--query-interval",
701 5a140b23 Vangelis Koukis
                      action="store", type="int", dest="query_interval",
702 5a140b23 Vangelis Koukis
                      metavar="INTERVAL",
703 5a140b23 Vangelis Koukis
                      help="Query server status when requests are pending " \
704 5a140b23 Vangelis Koukis
                           "every INTERVAL seconds",
705 5a140b23 Vangelis Koukis
                      default=3)
706 21bbbc9b Vangelis Koukis
    parser.add_option("--fanout",
707 21bbbc9b Vangelis Koukis
                      action="store", type="int", dest="fanout",
708 5a140b23 Vangelis Koukis
                      metavar="COUNT",
709 21bbbc9b Vangelis Koukis
                      help="Spawn up to COUNT child processes to execute " \
710 21bbbc9b Vangelis Koukis
                           "in parallel, essentially have up to COUNT " \
711 00f87624 Vangelis Koukis
                           "server build requests outstanding (EXPERIMENTAL)",
712 5a140b23 Vangelis Koukis
                      default=1)
713 5a140b23 Vangelis Koukis
    parser.add_option("--force-flavor",
714 5a140b23 Vangelis Koukis
                      action="store", type="int", dest="force_flavorid",
715 5a140b23 Vangelis Koukis
                      metavar="FLAVOR ID",
716 5a140b23 Vangelis Koukis
                      help="Force all server creations to use the specified "\
717 5a140b23 Vangelis Koukis
                           "FLAVOR ID instead of a randomly chosen one, " \
718 5a140b23 Vangelis Koukis
                           "useful if disk space is scarce",
719 00f87624 Vangelis Koukis
                      default=None)
720 00f87624 Vangelis Koukis
    parser.add_option("--force-image",
721 00f87624 Vangelis Koukis
                      action="store", type="int", dest="force_imageid",
722 00f87624 Vangelis Koukis
                      metavar="IMAGE ID",
723 00f87624 Vangelis Koukis
                      help="Instead of testing all available images, test " \
724 00f87624 Vangelis Koukis
                           "only the specified IMAGE ID",
725 00f87624 Vangelis Koukis
                      default=None)
726 5a140b23 Vangelis Koukis
    parser.add_option("--show-stale",
727 5a140b23 Vangelis Koukis
                      action="store_true", dest="show_stale",
728 5a140b23 Vangelis Koukis
                      help="Show stale servers from previous runs, whose "\
729 21bbbc9b Vangelis Koukis
                           "name starts with `%s'" % SNF_TEST_PREFIX,
730 5a140b23 Vangelis Koukis
                      default=False)
731 5a140b23 Vangelis Koukis
    parser.add_option("--delete-stale",
732 5a140b23 Vangelis Koukis
                      action="store_true", dest="delete_stale",
733 5a140b23 Vangelis Koukis
                      help="Delete stale servers from previous runs, whose "\
734 21bbbc9b Vangelis Koukis
                           "name starts with `%s'" % SNF_TEST_PREFIX,
735 5a140b23 Vangelis Koukis
                      default=False)
736 5a140b23 Vangelis Koukis
737 5a140b23 Vangelis Koukis
    # FIXME: Change the default for build-fanout to 10
738 5a140b23 Vangelis Koukis
    # FIXME: Allow the user to specify a specific set of Images to test
739 5a140b23 Vangelis Koukis
740 5a140b23 Vangelis Koukis
    (opts, args) = parser.parse_args(args)
741 5a140b23 Vangelis Koukis
742 5a140b23 Vangelis Koukis
    # Verify arguments
743 5a140b23 Vangelis Koukis
    if opts.delete_stale:
744 5a140b23 Vangelis Koukis
        opts.show_stale = True
745 5a140b23 Vangelis Koukis
746 5a140b23 Vangelis Koukis
    return (opts, args)
747 5a140b23 Vangelis Koukis
748 5a140b23 Vangelis Koukis
749 5a140b23 Vangelis Koukis
def main():
750 5a140b23 Vangelis Koukis
    """Assemble test cases into a test suite, and run it
751 5a140b23 Vangelis Koukis

752 5a140b23 Vangelis Koukis
    IMPORTANT: Tests have dependencies and have to be run in the specified
753 5a140b23 Vangelis Koukis
    order inside a single test case. They communicate through attributes of the
754 21bbbc9b Vangelis Koukis
    corresponding TestCase class (shared fixtures). Distinct subclasses of
755 21bbbc9b Vangelis Koukis
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
756 21bbbc9b Vangelis Koukis
    test runner processes.
757 5a140b23 Vangelis Koukis

758 5a140b23 Vangelis Koukis
    """
759 5a140b23 Vangelis Koukis
    (opts, args) = parse_arguments(sys.argv[1:])
760 5a140b23 Vangelis Koukis
761 21bbbc9b Vangelis Koukis
    global API, TOKEN
762 21bbbc9b Vangelis Koukis
    API = opts.api
763 21bbbc9b Vangelis Koukis
    TOKEN = opts.token
764 21bbbc9b Vangelis Koukis
765 5a140b23 Vangelis Koukis
    # Cleanup stale servers from previous runs
766 5a140b23 Vangelis Koukis
    if opts.show_stale:
767 5a140b23 Vangelis Koukis
        cleanup_servers(delete_stale=opts.delete_stale)
768 5a140b23 Vangelis Koukis
        return 0
769 5a140b23 Vangelis Koukis
770 5a140b23 Vangelis Koukis
    # Initialize a kamaki instance, get flavors, images
771 5a140b23 Vangelis Koukis
    c = Client(API, TOKEN)
772 5a140b23 Vangelis Koukis
    DIMAGES = c.list_images(detail=True)
773 5a140b23 Vangelis Koukis
    DFLAVORS = c.list_flavors(detail=True)
774 5a140b23 Vangelis Koukis
775 21bbbc9b Vangelis Koukis
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
776 21bbbc9b Vangelis Koukis
    # FIXME: Network testing? Create, destroy, connect, ping, disconnect VMs?
777 5a140b23 Vangelis Koukis
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
778 5a140b23 Vangelis Koukis
    #unittest.main(verbosity=2, catchbreak=True)
779 5a140b23 Vangelis Koukis
780 00f87624 Vangelis Koukis
    runner = unittest.TextTestRunner(verbosity=2, failfast=not opts.nofailfast)
781 21bbbc9b Vangelis Koukis
    # The following cases run sequentially
782 21bbbc9b Vangelis Koukis
    seq_cases = [UnauthorizedTestCase, FlavorsTestCase, ImagesTestCase]
783 21bbbc9b Vangelis Koukis
    _run_cases_in_parallel(seq_cases, fanout=3, runner=runner)
784 21bbbc9b Vangelis Koukis
785 21bbbc9b Vangelis Koukis
    # The following cases run in parallel
786 21bbbc9b Vangelis Koukis
    par_cases = []
787 5a140b23 Vangelis Koukis
788 00f87624 Vangelis Koukis
    if opts.force_imageid:
789 00f87624 Vangelis Koukis
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
790 00f87624 Vangelis Koukis
    else:
791 00f87624 Vangelis Koukis
        test_images = DIMAGES
792 00f87624 Vangelis Koukis
793 00f87624 Vangelis Koukis
    for image in test_images:
794 21bbbc9b Vangelis Koukis
        imageid = image["id"]
795 21bbbc9b Vangelis Koukis
        imagename = image["name"]
796 21bbbc9b Vangelis Koukis
        if opts.force_flavorid:
797 21bbbc9b Vangelis Koukis
            flavorid = opts.force_flavorid
798 21bbbc9b Vangelis Koukis
        else:
799 21bbbc9b Vangelis Koukis
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
800 21bbbc9b Vangelis Koukis
        personality = None   # FIXME
801 21bbbc9b Vangelis Koukis
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
802 21bbbc9b Vangelis Koukis
        is_windows = imagename.lower().find("windows") >= 0
803 21bbbc9b Vangelis Koukis
        case = _spawn_server_test_case(imageid=imageid, flavorid=flavorid,
804 21bbbc9b Vangelis Koukis
                                       imagename=imagename,
805 21bbbc9b Vangelis Koukis
                                       personality=personality,
806 21bbbc9b Vangelis Koukis
                                       servername=servername,
807 21bbbc9b Vangelis Koukis
                                       is_windows=is_windows,
808 21bbbc9b Vangelis Koukis
                                       action_timeout=opts.action_timeout,
809 21bbbc9b Vangelis Koukis
                                       build_warning=opts.build_warning,
810 21bbbc9b Vangelis Koukis
                                       build_fail=opts.build_fail,
811 21bbbc9b Vangelis Koukis
                                       query_interval=opts.query_interval)
812 21bbbc9b Vangelis Koukis
        par_cases.append(case)
813 21bbbc9b Vangelis Koukis
814 21bbbc9b Vangelis Koukis
    _run_cases_in_parallel(par_cases, fanout=opts.fanout, runner=runner)
815 5a140b23 Vangelis Koukis
816 5a140b23 Vangelis Koukis
if __name__ == "__main__":
817 5a140b23 Vangelis Koukis
    sys.exit(main())