Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / tools / burnin.py @ 1b40b5e3

History | View | Annotate | Download (48.2 kB)

1
#!/usr/bin/env python
2

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

    
36
"""Perform integration testing on a running Synnefo deployment"""
37

    
38
import __main__
39
import datetime
40
import inspect
41
import logging
42
import os
43
import paramiko
44
import prctl
45
import subprocess
46
import signal
47
import socket
48
import struct
49
import sys
50
import time
51
import hashlib
52
from base64 import b64encode
53
from pwd import getpwuid
54
from grp import getgrgid
55
from IPy import IP
56
from multiprocessing import Process, Queue
57
from random import choice
58

    
59
from kamaki.clients.compute import ComputeClient
60
from kamaki.clients.cyclades import CycladesClient
61

    
62

    
63
from vncauthproxy.d3des import generate_response as d3des_generate_response
64

    
65
# Use backported unittest functionality if Python < 2.7
66
try:
67
    import unittest2 as unittest
68
except ImportError:
69
    if sys.version_info < (2, 7):
70
        raise Exception("The unittest2 package is required for Python < 2.7")
71
    import unittest
72

    
73

    
74
API = None
75
TOKEN = None
76
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
77

    
78
# A unique id identifying this test run
79
TEST_RUN_ID = datetime.datetime.strftime(datetime.datetime.now(),
80
                                         "%Y%m%d%H%M%S")
81
SNF_TEST_PREFIX = "snf-test-"
82

    
83
# Setup logging (FIXME - verigak)
84
logging.basicConfig(format="%(message)s")
85
log = logging.getLogger("burnin")
86
log.setLevel(logging.INFO)
87

    
88
class UnauthorizedTestCase(unittest.TestCase):
89
    def test_unauthorized_access(self):
90
        """Test access without a valid token fails"""
91
        falseToken = '12345'
92
        c=ComputeClient(API, falseToken)
93

    
94
        with self.assertRaises(ClientError) as cm:
95
            c.list_servers()
96
        self.assertEqual(cm.exception.status, 401)
97

    
98

    
99
class ImagesTestCase(unittest.TestCase):
100
    """Test image lists for consistency"""
101
    @classmethod
102
    def setUpClass(cls):
103
        """Initialize kamaki, get (detailed) list of images"""
104
        log.info("Getting simple and detailed list of images")
105

    
106
        cls.client = ComputeClient(API, TOKEN)
107
        cls.images = cls.client.list_images()
108
        cls.dimages = cls.client.list_images(detail=True)
109

    
110
    def test_001_list_images(self):
111
        """Test image list actually returns images"""
112
        self.assertGreater(len(self.images), 0)
113

    
114
    def test_002_list_images_detailed(self):
115
        """Test detailed image list is the same length as list"""
116
        self.assertEqual(len(self.dimages), len(self.images))
117

    
118
    def test_003_same_image_names(self):
119
        """Test detailed and simple image list contain same names"""
120
        names = sorted(map(lambda x: x["name"], self.images))
121
        dnames = sorted(map(lambda x: x["name"], self.dimages))
122
        self.assertEqual(names, dnames)
123

    
124
    def test_004_unique_image_names(self):
125
        """Test images have unique names"""
126
        names = sorted(map(lambda x: x["name"], self.images))
127
        self.assertEqual(sorted(list(set(names))), names)
128

    
129
    def test_005_image_metadata(self):
130
        """Test every image has specific metadata defined"""
131
        keys = frozenset(["os", "description", "size"])
132
        for i in self.dimages:
133
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
134

    
135

    
136
class FlavorsTestCase(unittest.TestCase):
137
    """Test flavor lists for consistency"""
138
    @classmethod
139
    def setUpClass(cls):
140
        """Initialize kamaki, get (detailed) list of flavors"""
141
        log.info("Getting simple and detailed list of flavors")
142

    
143
        cls.client = ComputeClient(API, TOKEN)
144
        cls.flavors = cls.client.list_flavors()
145
        cls.dflavors = cls.client.list_flavors(detail=True)
146

    
147
    def test_001_list_flavors(self):
148
        """Test flavor list actually returns flavors"""
149
        self.assertGreater(len(self.flavors), 0)
150

    
151
    def test_002_list_flavors_detailed(self):
152
        """Test detailed flavor list is the same length as list"""
153
        self.assertEquals(len(self.dflavors), len(self.flavors))
154

    
155
    def test_003_same_flavor_names(self):
156
        """Test detailed and simple flavor list contain same names"""
157
        names = sorted(map(lambda x: x["name"], self.flavors))
158
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
159
        self.assertEqual(names, dnames)
160

    
161
    def test_004_unique_flavor_names(self):
162
        """Test flavors have unique names"""
163
        names = sorted(map(lambda x: x["name"], self.flavors))
164
        self.assertEqual(sorted(list(set(names))), names)
165

    
166
    def test_005_well_formed_flavor_names(self):
167
        """Test flavors have names of the form CxxRyyDzz
168

169
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
170

171
        """
172
        for f in self.dflavors:
173
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
174
                             f["name"],
175
                             "Flavor %s does not match its specs." % f["name"])
176

    
177

    
178
class ServersTestCase(unittest.TestCase):
179
    """Test server lists for consistency"""
180
    @classmethod
181
    def setUpClass(cls):
182
        """Initialize kamaki, get (detailed) list of servers"""
183
        log.info("Getting simple and detailed list of servers")
184

    
185
        cls.client = ComputeClient(API, TOKEN)
186
        cls.servers = cls.client.list_servers()
187
        cls.dservers = cls.client.list_servers(detail=True)
188

    
189
    def test_001_list_servers(self):
190
        """Test server list actually returns servers"""
191
        self.assertGreater(len(self.servers), 0)
192

    
193
    def test_002_list_servers_detailed(self):
194
        """Test detailed server list is the same length as list"""
195
        self.assertEqual(len(self.dservers), len(self.servers))
196

    
197
    def test_003_same_server_names(self):
198
        """Test detailed and simple flavor list contain same names"""
199
        names = sorted(map(lambda x: x["name"], self.servers))
200
        dnames = sorted(map(lambda x: x["name"], self.dservers))
201
        self.assertEqual(names, dnames)
202

    
203

    
204
# This class gets replicated into actual TestCases dynamically
205
class SpawnServerTestCase(unittest.TestCase):
206
    """Test scenario for server of the specified image"""
207

    
208
    @classmethod
209
    def setUpClass(cls):
210
        """Initialize a kamaki instance"""
211
        log.info("Spawning server for image `%s'", cls.imagename)
212

    
213
        cls.client = ComputeClient(API, TOKEN)
214
        cls.cyclades = CycladesClient(API, TOKEN)
215

    
216
    def _get_ipv4(self, server):
217
        """Get the public IPv4 of a server from the detailed server info"""
218

    
219
        public_addrs = filter(lambda x: x["id"] == "public",
220
                              server["addresses"]["values"])
221
        self.assertEqual(len(public_addrs), 1)
222
        ipv4_addrs = filter(lambda x: x["version"] == 4,
223
                            public_addrs[0]["values"])
224
        self.assertEqual(len(ipv4_addrs), 1)
225
        return ipv4_addrs[0]["addr"]
226

    
227
    def _get_ipv6(self, server):
228
        """Get the public IPv6 of a server from the detailed server info"""
229
        public_addrs = filter(lambda x: x["id"] == "public",
230
                              server["addresses"]["values"])
231
        self.assertEqual(len(public_addrs), 1)
232
        ipv6_addrs = filter(lambda x: x["version"] == 6,
233
                            public_addrs[0]["values"])
234
        self.assertEqual(len(ipv6_addrs), 1)
235
        return ipv6_addrs[0]["addr"]
236

    
237
    def _connect_loginname(self, os):
238
        """Return the login name for connections based on the server OS"""
239
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
240
            return "user"
241
        elif os in ("windows", "windows_alpha1"):
242
            return "Administrator"
243
        else:
244
            return "root"
245

    
246
    def _verify_server_status(self, current_status, new_status):
247
        """Verify a server has switched to a specified status"""
248
        server = self.client.get_server_details(self.serverid)
249
        if server["status"] not in (current_status, new_status):
250
            return None  # Do not raise exception, return so the test fails
251
        self.assertEquals(server["status"], new_status)
252

    
253
    def _get_connected_tcp_socket(self, family, host, port):
254
        """Get a connected socket from the specified family to host:port"""
255
        sock = None
256
        for res in \
257
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
258
                               socket.AI_PASSIVE):
259
            af, socktype, proto, canonname, sa = res
260
            try:
261
                sock = socket.socket(af, socktype, proto)
262
            except socket.error as msg:
263
                sock = None
264
                continue
265
            try:
266
                sock.connect(sa)
267
            except socket.error as msg:
268
                sock.close()
269
                sock = None
270
                continue
271
        self.assertIsNotNone(sock)
272
        return sock
273

    
274
    def _ping_once(self, ipv6, ip):
275
        """Test server responds to a single IPv4 or IPv6 ping"""
276
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
277
        ping = subprocess.Popen(cmd, shell=True,
278
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
279
        (stdout, stderr) = ping.communicate()
280
        ret = ping.wait()
281
        self.assertEquals(ret, 0)
282

    
283
    def _get_hostname_over_ssh(self, hostip, username, password):
284
        ssh = paramiko.SSHClient()
285
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
286
        try:
287
            ssh.connect(hostip, username=username, password=password)
288
        except socket.error:
289
            raise AssertionError
290
        stdin, stdout, stderr = ssh.exec_command("hostname")
291
        lines = stdout.readlines()
292
        self.assertEqual(len(lines), 1)
293
        return lines[0]
294

    
295
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
296
                                   opmsg, callable, *args, **kwargs):
297
        if warn_timeout == fail_timeout:
298
            warn_timeout = fail_timeout + 1
299
        warn_tmout = time.time() + warn_timeout
300
        fail_tmout = time.time() + fail_timeout
301
        while True:
302
            self.assertLess(time.time(), fail_tmout,
303
                            "operation `%s' timed out" % opmsg)
304
            if time.time() > warn_tmout:
305
                log.warning("Server %d: `%s' operation `%s' not done yet",
306
                            self.serverid, self.servername, opmsg)
307
            try:
308
                log.info("%s... " % opmsg)
309
                return callable(*args, **kwargs)
310
            except AssertionError:
311
                pass
312
            time.sleep(self.query_interval)
313

    
314
    def _insist_on_tcp_connection(self, family, host, port):
315
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
316
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
317
        msg = "connect over %s to %s:%s" % \
318
              (familystr.get(family, "Unknown"), host, port)
319
        sock = self._try_until_timeout_expires(
320
                self.action_timeout, self.action_timeout,
321
                msg, self._get_connected_tcp_socket,
322
                family, host, port)
323
        return sock
324

    
325
    def _insist_on_status_transition(self, current_status, new_status,
326
                                    fail_timeout, warn_timeout=None):
327
        msg = "Server %d: `%s', waiting for %s -> %s" % \
328
              (self.serverid, self.servername, current_status, new_status)
329
        if warn_timeout is None:
330
            warn_timeout = fail_timeout
331
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
332
                                        msg, self._verify_server_status,
333
                                        current_status, new_status)
334
        # Ensure the status is actually the expected one
335
        server = self.client.get_server_details(self.serverid)
336
        self.assertEquals(server["status"], new_status)
337

    
338
    def _insist_on_ssh_hostname(self, hostip, username, password):
339
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
340
        hostname = self._try_until_timeout_expires(
341
                self.action_timeout, self.action_timeout,
342
                msg, self._get_hostname_over_ssh,
343
                hostip, username, password)
344

    
345
        # The hostname must be of the form 'prefix-id'
346
        self.assertTrue(hostname.endswith("-%d\n" % self.serverid))
347

    
348
    def _check_file_through_ssh(self, hostip, username, password, remotepath, content):
349
        msg = "Trying file injection through SSH to %s, as %s/%s" % (hostip, username, password)
350
        log.info(msg)
351
        try:
352
            ssh = paramiko.SSHClient()
353
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
354
            ssh.connect(hostip, username=username, password=password)
355
        except socket.error:
356
            raise AssertionError
357
        
358
        transport = paramiko.Transport((hostip,22))
359
        transport.connect(username = username, password = password)
360

    
361
        localpath = '/tmp/'+SNF_TEST_PREFIX+'injection'
362
        sftp = paramiko.SFTPClient.from_transport(transport)
363
        sftp.get(remotepath, localpath)
364
        
365
        sftp.close()
366
        transport.close()
367

    
368
        f = open(localpath)
369
        remote_content = b64encode(f.read())
370

    
371
        # Check if files are the same
372
        return (remote_content == content)
373

    
374
    def _skipIf(self, condition, msg):
375
        if condition:
376
            self.skipTest(msg)
377

    
378
    def test_001_submit_create_server(self):
379
        """Test submit create server request"""
380
        server = self.client.create_server(self.servername, self.flavorid,
381
                                           self.imageid, self.personality)
382

    
383
        self.assertEqual(server["name"], self.servername)
384
        self.assertEqual(server["flavorRef"], self.flavorid)
385
        self.assertEqual(server["imageRef"], self.imageid)
386
        self.assertEqual(server["status"], "BUILD")
387

    
388
        # Update class attributes to reflect data on building server
389
        cls = type(self)
390
        cls.serverid = server["id"]
391
        cls.username = None
392
        cls.passwd = server["adminPass"]
393

    
394
    def test_002a_server_is_building_in_list(self):
395
        """Test server is in BUILD state, in server list"""
396
        servers = self.client.list_servers(detail=True)
397
        servers = filter(lambda x: x["name"] == self.servername, servers)
398
        self.assertEqual(len(servers), 1)
399
        server = servers[0]
400
        self.assertEqual(server["name"], self.servername)
401
        self.assertEqual(server["flavorRef"], self.flavorid)
402
        self.assertEqual(server["imageRef"], self.imageid)
403
        self.assertEqual(server["status"], "BUILD")
404

    
405
    def test_002b_server_is_building_in_details(self):
406
        """Test server is in BUILD state, in details"""
407
        server = self.client.get_server_details(self.serverid)
408
        self.assertEqual(server["name"], self.servername)
409
        self.assertEqual(server["flavorRef"], self.flavorid)
410
        self.assertEqual(server["imageRef"], self.imageid)
411
        self.assertEqual(server["status"], "BUILD")
412

    
413
    def test_002c_set_server_metadata(self):
414
        image = self.client.get_image_details(self.imageid)
415
        os = image["metadata"]["values"]["os"]
416
        loginname = image["metadata"]["values"].get("users", None)
417
        self.client.update_server_metadata(self.serverid, OS=os)
418

    
419
        # Determine the username to use for future connections
420
        # to this host
421
        cls = type(self)
422
        cls.username = loginname
423
        if not cls.username:
424
            cls.username = self._connect_loginname(os)
425
        self.assertIsNotNone(cls.username)
426

    
427
    def test_002d_verify_server_metadata(self):
428
        """Test server metadata keys are set based on image metadata"""
429
        servermeta = self.client.get_server_metadata(self.serverid)
430
        imagemeta = self.client.get_image_metadata(self.imageid)
431
        self.assertEqual(servermeta["OS"], imagemeta["os"])
432

    
433
    def test_003_server_becomes_active(self):
434
        """Test server becomes ACTIVE"""
435
        self._insist_on_status_transition("BUILD", "ACTIVE",
436
                                         self.build_fail, self.build_warning)
437

    
438
    def test_003a_get_server_oob_console(self):
439
        """Test getting OOB server console over VNC
440

441
        Implementation of RFB protocol follows
442
        http://www.realvnc.com/docs/rfbproto.pdf.
443

444
        """
445
        
446
        console = self.cyclades.get_server_console(self.serverid)
447
        self.assertEquals(console['type'], "vnc")
448
        sock = self._insist_on_tcp_connection(socket.AF_UNSPEC,
449
                                        console["host"], console["port"])
450

    
451
        # Step 1. ProtocolVersion message (par. 6.1.1)
452
        version = sock.recv(1024)
453
        self.assertEquals(version, 'RFB 003.008\n')
454
        sock.send(version)
455

    
456
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
457
        sec = sock.recv(1024)
458
        self.assertEquals(list(sec), ['\x01', '\x02'])
459

    
460
        # Step 3. Request VNC Authentication (par 6.1.2)
461
        sock.send('\x02')
462

    
463
        # Step 4. Receive Challenge (par 6.2.2)
464
        challenge = sock.recv(1024)
465
        self.assertEquals(len(challenge), 16)
466

    
467
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
468
        response = d3des_generate_response(
469
            (console["password"] + '\0' * 8)[:8], challenge)
470
        sock.send(response)
471

    
472
        # Step 6. SecurityResult (par 6.1.3)
473
        result = sock.recv(4)
474
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
475
        sock.close()
476
        
477
    def test_004_server_has_ipv4(self):
478
        """Test active server has a valid IPv4 address"""
479
        server = self.client.get_server_details(self.serverid)
480
        ipv4 = self._get_ipv4(server)
481
        self.assertEquals(IP(ipv4).version(), 4)
482

    
483
    def test_005_server_has_ipv6(self):
484
        """Test active server has a valid IPv6 address"""
485
        server = self.client.get_server_details(self.serverid)
486
        ipv6 = self._get_ipv6(server)
487
        self.assertEquals(IP(ipv6).version(), 6)
488

    
489
    def test_006_server_responds_to_ping_IPv4(self):
490
        """Test server responds to ping on IPv4 address"""
491
        server = self.client.get_server_details(self.serverid)
492
        ip = self._get_ipv4(server)
493
        self._try_until_timeout_expires(self.action_timeout,
494
                                        self.action_timeout,
495
                                        "PING IPv4 to %s" % ip,
496
                                        self._ping_once,
497
                                        False, ip)
498

    
499
    def test_007_server_responds_to_ping_IPv6(self):
500
        """Test server responds to ping on IPv6 address"""
501
        server = self.client.get_server_details(self.serverid)
502
        ip = self._get_ipv6(server)
503
        self._try_until_timeout_expires(self.action_timeout,
504
                                        self.action_timeout,
505
                                        "PING IPv6 to %s" % ip,
506
                                        self._ping_once,
507
                                        True, ip)
508

    
509
    def test_008_submit_shutdown_request(self):
510
        """Test submit request to shutdown server"""
511
        self.cyclades.shutdown_server(self.serverid)
512

    
513
    def test_009_server_becomes_stopped(self):
514
        """Test server becomes STOPPED"""
515
        self._insist_on_status_transition("ACTIVE", "STOPPED",
516
                                         self.action_timeout,
517
                                         self.action_timeout)
518

    
519
    def test_010_submit_start_request(self):
520
        """Test submit start server request"""
521
        self.cyclades.start_server(self.serverid)
522

    
523
    def test_011_server_becomes_active(self):
524
        """Test server becomes ACTIVE again"""
525
        self._insist_on_status_transition("STOPPED", "ACTIVE",
526
                                         self.action_timeout,
527
                                         self.action_timeout)
528

    
529
    def test_011a_server_responds_to_ping_IPv4(self):
530
        """Test server OS is actually up and running again"""
531
        self.test_006_server_responds_to_ping_IPv4()
532

    
533
    def test_012_ssh_to_server_IPv4(self):
534
        """Test SSH to server public IPv4 works, verify hostname"""
535
        self._skipIf(self.is_windows, "only valid for Linux servers")
536
        server = self.client.get_server_details(self.serverid)
537
        self._insist_on_ssh_hostname(self._get_ipv4(server),
538
                                     self.username, self.passwd)
539

    
540
    def test_013_ssh_to_server_IPv6(self):
541
        """Test SSH to server public IPv6 works, verify hostname"""
542
        self._skipIf(self.is_windows, "only valid for Linux servers")
543
        server = self.client.get_server_details(self.serverid)
544
        self._insist_on_ssh_hostname(self._get_ipv6(server),
545
                                     self.username, self.passwd)
546

    
547
    def test_014_rdp_to_server_IPv4(self):
548
        "Test RDP connection to server public IPv4 works"""
549
        self._skipIf(not self.is_windows, "only valid for Windows servers")
550
        server = self.client.get_server_details(self.serverid)
551
        ipv4 = self._get_ipv4(server)
552
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
553

    
554
        # No actual RDP processing done. We assume the RDP server is there
555
        # if the connection to the RDP port is successful.
556
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
557
        sock.close()
558

    
559
    def test_015_rdp_to_server_IPv6(self):
560
        "Test RDP connection to server public IPv6 works"""
561
        self._skipIf(not self.is_windows, "only valid for Windows servers")
562
        server = self.client.get_server_details(self.serverid)
563
        ipv6 = self._get_ipv6(server)
564
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
565

    
566
        # No actual RDP processing done. We assume the RDP server is there
567
        # if the connection to the RDP port is successful.
568
        sock.close()
569

    
570
    def test_016_personality_is_enforced(self):
571
        """Test file injection for personality enforcement"""
572
        self._skipIf(self.is_windows, "only implemented for Linux servers")
573
        self._skipIf(self.personality == None, "No personality file selected")
574

    
575
        server = self.client.get_server_details(self.serverid)
576

    
577
        for inj_file in self.personality:
578
            equal_files = self._check_file_through_ssh(self._get_ipv4(server), inj_file['owner'], 
579
                                                       self.passwd, inj_file['path'], inj_file['contents'])
580
            self.assertTrue(equal_files)
581
        
582

    
583
    def test_017_submit_delete_request(self):
584
        """Test submit request to delete server"""
585
        self.client.delete_server(self.serverid)
586

    
587
    def test_018_server_becomes_deleted(self):
588
        """Test server becomes DELETED"""
589
        self._insist_on_status_transition("ACTIVE", "DELETED",
590
                                         self.action_timeout,
591
                                         self.action_timeout)
592

    
593
    def test_019_server_no_longer_in_server_list(self):
594
        """Test server is no longer in server list"""
595
        servers = self.client.list_servers()
596
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
597

    
598

    
599
class NetworkTestCase(unittest.TestCase):
600
    """ Testing networking in cyclades """
601
  
602
    @classmethod
603
    def setUpClass(cls):
604
        "Initialize kamaki, get list of current networks"
605

    
606
        cls.client = CycladesClient(API, TOKEN)
607
        cls.compute = ComputeClient(API, TOKEN)
608

    
609
        images = cls.compute.list_images(detail = True)
610
        flavors = cls.compute.list_flavors(detail = True)
611

    
612
        cls.imageid = choice([im['id'] for im in images if not im['name'].lower().find("windows") >= 0])
613
        cls.flavorid = choice([f['id'] for f in flavors if f['disk'] >= 20])
614

    
615
        for image in images:
616
            if image['id'] == cls.imageid:
617
                imagename = image['name']
618

    
619
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
620

    
621
        #Dictionary initialization for the vms credentials
622
        cls.serverid = dict()
623
        cls.username = dict()
624
        cls.password = dict()
625

    
626

    
627
    def _get_ipv4(self, server):
628
    
629
        """Get the public IPv4 of a server from the detailed server info"""
630

    
631
        public_addrs = filter(lambda x: x["id"] == "public",
632
                              server["addresses"]["values"])
633
        self.assertEqual(len(public_addrs), 1)
634
        ipv4_addrs = filter(lambda x: x["version"] == 4,
635
                            public_addrs[0]["values"])
636
        self.assertEqual(len(ipv4_addrs), 1)
637
        return ipv4_addrs[0]["addr"]
638

    
639

    
640
    def _connect_loginname(self, os):
641
        """Return the login name for connections based on the server OS"""
642
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
643
            return "user"
644
        elif os in ("windows", "windows_alpha1"):
645
            return "Administrator"
646
        else:
647
            return "root"
648

    
649

    
650
    def test_0001_submit_create_server_A(self):
651
        """Test submit create server request"""
652
        serverA = self.client.create_server(self.servername, self.flavorid,
653
                                           self.imageid, personality=None)
654

    
655
        self.assertEqual(server["name"], self.servername)
656
        self.assertEqual(server["flavorRef"], self.flavorid)
657
        self.assertEqual(server["imageRef"], self.imageid)
658
        self.assertEqual(server["status"], "BUILD")
659

    
660
        # Update class attributes to reflect data on building server
661
        cls = type(self)
662
        cls.serverid['A'] = serverA["id"]
663
        cls.username['A'] = None
664
        cls.password['A'] = serverA["adminPass"]
665

    
666
    
667
    def test_0001_submit_create_server_B(self):
668
        """Test submit create server request"""
669
        serverB = self.client.create_server(self.servername, self.flavorid,
670
                                           self.imageid, personality=None)
671

    
672
        self.assertEqual(server["name"], self.servername)
673
        self.assertEqual(server["flavorRef"], self.flavorid)
674
        self.assertEqual(server["imageRef"], self.imageid)
675
        self.assertEqual(server["status"], "BUILD")
676

    
677
        # Update class attributes to reflect data on building server
678
        cls = type(self)
679
        cls.serverid['B'] = serverB["id"]
680
        cls.username['B'] = None
681
        cls.password['B'] = serverB["adminPass"]
682

    
683
    def test_0001_serverA_becomes_active(self):
684
        """Test server becomes ACTIVE"""
685

    
686
        fail_tmout = time.time()+self.action_timeout
687
        while True:
688
            d = self.client.get_server_details(self.serverid['A'])
689
            status = d['status']
690
            if status == 'ACTIVE':
691
                active = True
692
                break
693
            elif time.time() > fail_tmout:
694
                self.assertLess(time.time(), fail_tmout)
695
            else:
696
                time.sleep(self.query_interval)
697

    
698
        self.assertTrue(active)
699

    
700
    def test_0001_serverB_becomes_active(self):
701
        """Test server becomes ACTIVE"""
702

    
703
        fail_tmout = time.time()+self.action_timeout
704
        while True:
705
            d = self.client.get_server_details(self.serverid['B'])
706
            status = d['status']
707
            if status == 'ACTIVE':
708
                active = True
709
                break
710
            elif time.time() > fail_tmout:
711
                self.assertLess(time.time(), fail_tmout)
712
            else:
713
                time.sleep(self.query_interval)
714

    
715
        self.assertTrue(active)
716

    
717

    
718
    def test_001_create_network(self):
719
        """Test submit create network request"""
720
        name = SNF_TEST_PREFIX+TEST_RUN_ID
721
        previous_num = len(self.client.list_networks())
722
        network =  self.client.create_network(name)        
723
       
724
        #Test if right name is assigned
725
        self.assertEqual(network['name'], name)
726
        
727
        # Update class attributes
728
        cls = type(self)
729
        cls.networkid = network['id']
730
        networks = self.client.list_networks()
731

    
732
        #Test if new network is created
733
        self.assertTrue(len(networks) > previous_num)
734
        
735
    
736
    def test_002_connect_to_network(self):
737
        """Test connect VM to network"""
738

    
739
        self.client.connect_server(self.serverid['A'], self.networkid)
740
        self.client.connect_server(self.serverid['B'], self.networkid)
741
                
742
        #Insist on connecting until action timeout
743
        fail_tmout = time.time()+self.action_timeout
744

    
745
        while True:
746
            connected = (self.client.get_network_details(self.networkid))
747
            connections = connected['servers']['values']
748
            if (self.serverid['A'] in connections) and (self.serverid['B'] in connections):
749
                conn_exists = True
750
                break
751
            elif time.time() > fail_tmout:
752
                self.assertLess(time.time(), fail_tmout)
753
            else:
754
                time.sleep(self.query_interval)
755

    
756
        self.assertTrue(conn_exists)
757
            
758

    
759
    def test_002a_setup_interface_A(self):
760

    
761
        server = self.client.get_server_details(self.serverid['A'])
762
        image = self.client.get_image_details(self.serverid['A'])
763
        os = image['metadata']['values']['os']
764
        loginname = image["metadata"]["values"].get("users", None)
765
        hostip = self._get_ipv4(server) 
766
        
767
        if not loginname:
768
            loginname = self._connect_loginname(os)
769

    
770
        try:
771
            ssh = paramiko.SSHClient()
772
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
773
            ssh.connect(hostip, username = loginname, password = self.password['A'])
774
        except socket.error:
775
            raise AssertionError
776

    
777
        stdin, stdout, stderr = ssh.exec_command("ifconfig eth1 %s up"%("192.168.0.42"))
778
        lines = stdout.readlines()
779

    
780
        self.assertEqual(len(lines), 0)
781
        
782

    
783
    def test_002b_setup_interface_B(self):
784

    
785
        server = self.client.get_server_details(self.serverid['B'])
786
        image = self.client.get_image_details(self.serverid['B'])
787
        os = image['metadata']['values']['os']
788
        loginname = image["metadata"]["values"].get("users", None)
789
        hostip = self._get_ipv4(server) 
790
        
791
        if not loginname:
792
            loginname = self._connect_loginname(os)
793

    
794
        try:
795
            ssh = paramiko.SSHClient()
796
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
797
            ssh.connect(hostip, username = loginname, password = self.password['B'])
798
        except socket.error:
799
            raise AssertionError
800

    
801
        stdin, stdout, stderr = ssh.exec_command("ifconfig eth1 %s up"%("192.168.0.43"))
802
        lines = stdout.readlines()
803

    
804
        self.assertEqual(len(lines), 0)
805

    
806

    
807

    
808
    def test_002a_test_connection_exists(self):
809
        """Ping serverB from serverA to test if connection exists"""
810

    
811
        server = self.client.get_server_details(self.serverid['A'])
812
        image = self.client.get_image_details(self.serverid['A'])
813
        os = image['metadata']['values']['os']
814
        loginname = image["metadata"]["values"].get("users", None)
815
        
816
        hostip = self._get_ipv4(server)
817
        
818
        if not loginname:
819
            loginname = self._connect_loginname(os)
820

    
821
        try:
822
            ssh = paramiko.SSHClient()
823
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
824
            ssh.connect(hostip, username = loginname, password = self.password['A'])
825
        except socket.error:
826
            raise AssertionError
827

    
828
        cmd = "if ping -c 2 -w 3 %s >/dev/null; then echo \"True\"; fi;" % ("192.168.0.43")
829
        stdin, stdout, stderr = ssh.exec_command(cmd)
830
        lines = stdout.readlines()
831

    
832
        for i in lines:
833
            if i=='True\n':
834
                exists = True
835

    
836
        self.assertTrue(exists)
837

    
838

    
839

    
840
    def test_003_disconnect_from_network(self):
841
        prev_state = self.client.get_network_details(self.networkid)
842
        prev_conn = len(prev_state['servers']['values'])
843

    
844
        self.client.disconnect_server(self.serverid['A'], self.networkid)
845
        self.client.disconnect_server(self.serverid['B'], self.networkid)
846

    
847
        #Insist on deleting until action timeout
848
        fail_tmout = time.time()+self.action_timeout
849

    
850
        while True:
851
            connected = (self.client.get_network_details(self.networkid))
852
            connections = connected['servers']['values']
853
            if (self.serverid['A'] not in connections) and (self.serverid['B'] not in connections):
854
                conn_exists = False
855
                break
856
            elif time.time() > fail_tmout:
857
                self.assertLess(time.time(), fail_tmout)
858
            else:
859
                time.sleep(self.query_interval)
860

    
861
        self.assertFalse(conn_exists)
862

    
863
    def test_004_destroy_network(self):
864
        """Test submit delete network request"""
865
        self.client.delete_network(self.networkid)        
866
        networks = self.client.list_networks()
867

    
868
        curr_net = []
869
        for net in networks:
870
            curr_net.append(net['id'])
871

    
872
        self.assertTrue(self.networkid not in curr_net)
873
        
874
    def test_005_cleanup_servers(self):
875
        """Cleanup servers created for this test"""
876
        self.compute.delete_server(self.serverid['A'])
877
        self.compute.delete_server(self.serverid['B'])
878

    
879
        fail_tmout = time.time()+self.action_timeout
880

    
881
        #Ensure server gets deleted
882
        status = dict() 
883

    
884
        while True:
885
            details = self.compute.get_server_details(self.serverid['A'])
886
            status['A'] = details['status']
887
            details = self.compute.get_server_details(self.serverid['B'])
888
            status['B'] = details['status']
889
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
890
                deleted = True
891
                break
892
            elif time.time() > fail_tmout: 
893
                self.assertLess(time.time(), fail_tmout)
894
            else:
895
                time.sleep(self.query_interval)
896

    
897
        self.assertTrue(deleted)
898

    
899
class TestRunnerProcess(Process):
900
    """A distinct process used to execute part of the tests in parallel"""
901
    def __init__(self, **kw):
902
        Process.__init__(self, **kw)
903
        kwargs = kw["kwargs"]
904
        self.testq = kwargs["testq"]
905
        self.runner = kwargs["runner"]
906

    
907
    def run(self):
908
        # Make sure this test runner process dies with the parent
909
        # and is not left behind.
910
        #
911
        # WARNING: This uses the prctl(2) call and is
912
        # Linux-specific.
913
        prctl.set_pdeathsig(signal.SIGHUP)
914

    
915
        while True:
916
            log.debug("I am process %d, GETting from queue is %s",
917
                     os.getpid(), self.testq)
918
            msg = self.testq.get()
919
            log.debug("Dequeued msg: %s", msg)
920

    
921
            if msg == "TEST_RUNNER_TERMINATE":
922
                raise SystemExit
923
            elif issubclass(msg, unittest.TestCase):
924
                # Assemble a TestSuite, and run it
925
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
926
                self.runner.run(suite)
927
            else:
928
                raise Exception("Cannot handle msg: %s" % msg)
929

    
930

    
931

    
932
def _run_cases_in_parallel(cases, fanout=1, runner=None):
933
    """Run instances of TestCase in parallel, in a number of distinct processes
934

935
    The cases iterable specifies the TestCases to be executed in parallel,
936
    by test runners running in distinct processes.
937
    The fanout parameter specifies the number of processes to spawn,
938
    and defaults to 1.
939
    The runner argument specifies the test runner class to use inside each
940
    runner process.
941

942
    """
943
    if runner is None:
944
        runner = unittest.TextTestRunner(verbosity=2, failfast=True)
945

    
946
    # testq: The master process enqueues TestCase objects into this queue,
947
    #        test runner processes pick them up for execution, in parallel.
948
    testq = Queue()
949
    runners = []
950
    for i in xrange(0, fanout):
951
        kwargs = dict(testq=testq, runner=runner)
952
        runners.append(TestRunnerProcess(kwargs=kwargs))
953

    
954
    log.info("Spawning %d test runner processes", len(runners))
955
    for p in runners:
956
        p.start()
957
    log.debug("Spawned %d test runners, PIDs are %s",
958
              len(runners), [p.pid for p in runners])
959

    
960
    # Enqueue test cases
961
    map(testq.put, cases)
962
    map(testq.put, ["TEST_RUNNER_TERMINATE"] * len(runners))
963

    
964
    log.debug("Joining %d processes", len(runners))
965
    for p in runners:
966
        p.join()
967
    log.debug("Done joining %d processes", len(runners))
968

    
969

    
970
def _spawn_server_test_case(**kwargs):
971
    """Construct a new unit test case class from SpawnServerTestCase"""
972

    
973
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
974
    cls = type(name, (SpawnServerTestCase,), kwargs)
975

    
976
    # Patch extra parameters into test names by manipulating method docstrings
977
    for (mname, m) in \
978
        inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
979
            if hasattr(m, __doc__):
980
                m.__func__.__doc__ = "[%s] %s" % (imagename, m.__doc__)
981

    
982
    # Make sure the class can be pickled, by listing it among
983
    # the attributes of __main__. A PicklingError is raised otherwise.
984
    setattr(__main__, name, cls)
985
    return cls 
986

    
987
def _spawn_network_test_case(**kwargs):
988
    """Construct a new unit test case class from NetworkTestCase"""
989

    
990
    name = "NetworkTestCase"+TEST_RUN_ID
991
    cls = type(name, (NetworkTestCase,), kwargs)
992

    
993
    # Make sure the class can be pickled, by listing it among
994
    # the attributes of __main__. A PicklingError is raised otherwise.
995
    setattr(__main__, name, cls)
996
    return cls 
997

    
998

    
999
def cleanup_servers(delete_stale=False):
1000

    
1001
    c = ComputeClient(API, TOKEN)
1002

    
1003
    servers = c.list_servers()
1004
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1005

    
1006
    if len(stale) == 0:
1007
        return
1008

    
1009
    print >> sys.stderr, "Found these stale servers from previous runs:"
1010
    print "    " + \
1011
          "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1012

    
1013
    if delete_stale:
1014
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1015
        for server in stale:
1016
            c.delete_server(server["id"])
1017
        print >> sys.stderr, "    ...done"
1018
    else:
1019
        print >> sys.stderr, "Use --delete-stale to delete them."
1020

    
1021

    
1022
def parse_arguments(args):
1023
    from optparse import OptionParser
1024

    
1025
    kw = {}
1026
    kw["usage"] = "%prog [options]"
1027
    kw["description"] = \
1028
        "%prog runs a number of test scenarios on a " \
1029
        "Synnefo deployment."
1030

    
1031
    parser = OptionParser(**kw)
1032
    parser.disable_interspersed_args()
1033
    parser.add_option("--api",
1034
                      action="store", type="string", dest="api",
1035
                      help="The API URI to use to reach the Synnefo API",
1036
                      default=DEFAULT_API)
1037
    parser.add_option("--token",
1038
                      action="store", type="string", dest="token",
1039
                      help="The token to use for authentication to the API")
1040
    parser.add_option("--nofailfast",
1041
                      action="store_true", dest="nofailfast",
1042
                      help="Do not fail immediately if one of the tests " \
1043
                           "fails (EXPERIMENTAL)",
1044
                      default=False)
1045
    parser.add_option("--action-timeout",
1046
                      action="store", type="int", dest="action_timeout",
1047
                      metavar="TIMEOUT",
1048
                      help="Wait SECONDS seconds for a server action to " \
1049
                           "complete, then the test is considered failed",
1050
                      default=100)
1051
    parser.add_option("--build-warning",
1052
                      action="store", type="int", dest="build_warning",
1053
                      metavar="TIMEOUT",
1054
                      help="Warn if TIMEOUT seconds have passed and a " \
1055
                           "build operation is still pending",
1056
                      default=600)
1057
    parser.add_option("--build-fail",
1058
                      action="store", type="int", dest="build_fail",
1059
                      metavar="BUILD_TIMEOUT",
1060
                      help="Fail the test if TIMEOUT seconds have passed " \
1061
                           "and a build operation is still incomplete",
1062
                      default=900)
1063
    parser.add_option("--query-interval",
1064
                      action="store", type="int", dest="query_interval",
1065
                      metavar="INTERVAL",
1066
                      help="Query server status when requests are pending " \
1067
                           "every INTERVAL seconds",
1068
                      default=3)
1069
    parser.add_option("--fanout",
1070
                      action="store", type="int", dest="fanout",
1071
                      metavar="COUNT",
1072
                      help="Spawn up to COUNT child processes to execute " \
1073
                           "in parallel, essentially have up to COUNT " \
1074
                           "server build requests outstanding (EXPERIMENTAL)",
1075
                      default=1)
1076
    parser.add_option("--force-flavor",
1077
                      action="store", type="int", dest="force_flavorid",
1078
                      metavar="FLAVOR ID",
1079
                      help="Force all server creations to use the specified "\
1080
                           "FLAVOR ID instead of a randomly chosen one, " \
1081
                           "useful if disk space is scarce",
1082
                      default=None)
1083
    parser.add_option("--image-id",
1084
                      action="store", type="string", dest="force_imageid",
1085
                      metavar="IMAGE ID",
1086
                      help="Test the specified image id, use 'all' to test " \
1087
                           "all available images (mandatory argument)",
1088
                      default=None)
1089
    parser.add_option("--show-stale",
1090
                      action="store_true", dest="show_stale",
1091
                      help="Show stale servers from previous runs, whose "\
1092
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1093
                      default=False)
1094
    parser.add_option("--delete-stale",
1095
                      action="store_true", dest="delete_stale",
1096
                      help="Delete stale servers from previous runs, whose "\
1097
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1098
                      default=False)
1099
    parser.add_option("--force-personality",
1100
                      action="store", type="string", dest="personality_path",
1101
                      help="Force a personality file injection. File path required. ",
1102
                      default=None)
1103
    
1104

    
1105
    # FIXME: Change the default for build-fanout to 10
1106
    # FIXME: Allow the user to specify a specific set of Images to test
1107

    
1108
    (opts, args) = parser.parse_args(args)
1109

    
1110
    # Verify arguments
1111
    if opts.delete_stale:
1112
        opts.show_stale = True
1113

    
1114
    if not opts.show_stale:
1115
        if not opts.force_imageid:
1116
            print >>sys.stderr, "The --image-id argument is mandatory."
1117
            parser.print_help()
1118
            sys.exit(1)
1119

    
1120
        if opts.force_imageid != 'all':
1121
            try:
1122
                opts.force_imageid = str(opts.force_imageid)
1123
            except ValueError:
1124
                print >>sys.stderr, "Invalid value specified for --image-id." \
1125
                                    "Use a valid id, or `all'."
1126
                sys.exit(1)
1127

    
1128
    return (opts, args)
1129

    
1130

    
1131
def main():
1132
    """Assemble test cases into a test suite, and run it
1133

1134
    IMPORTANT: Tests have dependencies and have to be run in the specified
1135
    order inside a single test case. They communicate through attributes of the
1136
    corresponding TestCase class (shared fixtures). Distinct subclasses of
1137
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
1138
    test runner processes.
1139

1140
    """
1141
    (opts, args) = parse_arguments(sys.argv[1:])
1142

    
1143
    global API, TOKEN
1144
    API = opts.api
1145
    TOKEN = opts.token
1146

    
1147
    # Cleanup stale servers from previous runs
1148
    if opts.show_stale:
1149
        cleanup_servers(delete_stale=opts.delete_stale)
1150
        return 0
1151

    
1152
    # Initialize a kamaki instance, get flavors, images
1153

    
1154
    c = ComputeClient(API, TOKEN)
1155

    
1156
    DIMAGES = c.list_images(detail=True)
1157
    DFLAVORS = c.list_flavors(detail=True)
1158

    
1159
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
1160
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
1161
    #unittest.main(verbosity=2, catchbreak=True)
1162

    
1163
    if opts.force_imageid == 'all':
1164
        test_images = DIMAGES
1165
    else:
1166
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1167

    
1168
    for image in test_images:
1169
        imageid = str(image["id"])
1170
        flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
1171
        imagename = image["name"]
1172
        
1173
        #Personality dictionary for file injection test
1174
        if opts.personality_path != None:
1175
            f = open(opts.personality_path)
1176
            content = b64encode(f.read())
1177
            personality = []
1178
            st = os.stat(opts.personality_path)
1179
            personality.append({
1180
                    'path': '/root/test_inj_file',
1181
                    'owner': 'root',
1182
                    'group': 'root',
1183
                    'mode': 0x7777 & st.st_mode,
1184
                    'contents': content
1185
                    })
1186
        else:
1187
            personality = None
1188

    
1189
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
1190
        is_windows = imagename.lower().find("windows") >= 0
1191
        
1192
    ServerTestCase = _spawn_server_test_case(imageid=imageid, flavorid=flavorid,
1193
                                             imagename=imagename,
1194
                                             personality=personality,
1195
                                             servername=servername,
1196
                                             is_windows=is_windows,
1197
                                             action_timeout=opts.action_timeout,
1198
                                             build_warning=opts.build_warning,
1199
                                             build_fail=opts.build_fail,
1200
                                             query_interval=opts.query_interval,
1201
                                             )
1202

    
1203

    
1204
    #Running all the testcases sequentially
1205
    
1206
    #To run all cases
1207
    #seq_cases = [UnauthorizedTestCase, FlavorsTestCase, ImagesTestCase, ServerTestCase, NetworkTestCase]
1208
    
1209
    newNetworkTestCase = _spawn_network_test_case(action_timeout = opts.action_timeout,
1210
                                                  query_interval = opts.query_interval)    
1211
    seq_cases = [newNetworkTestCase]
1212

    
1213
    for case in seq_cases:
1214
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1215
        unittest.TextTestRunner(verbosity=2).run(suite)
1216
        
1217
    
1218

    
1219
    # # The Following cases run sequentially
1220
    # seq_cases = [UnauthorizedTestCase, FlavorsTestCase, ImagesTestCase]
1221
    # _run_cases_in_parallel(seq_cases, fanout=3, runner=runner)
1222

    
1223
    # # The following cases run in parallel
1224
    # par_cases = []
1225

    
1226
    # if opts.force_imageid == 'all':
1227
    #     test_images = DIMAGES
1228
    # else:
1229
    #     test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1230

    
1231
    # for image in test_images:
1232
    #     imageid = image["id"]
1233
    #     imagename = image["name"]
1234
    #     if opts.force_flavorid:
1235
    #         flavorid = opts.force_flavorid
1236
    #     else:
1237
    #         flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
1238
    #     personality = None   # FIXME
1239
    #     servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
1240
    #     is_windows = imagename.lower().find("windows") >= 0
1241
    #     case = _spawn_server_test_case(imageid=str(imageid), flavorid=flavorid,
1242
    #                                    imagename=imagename,
1243
    #                                    personality=personality,
1244
    #                                    servername=servername,
1245
    #                                    is_windows=is_windows,
1246
    #                                    action_timeout=opts.action_timeout,
1247
    #                                    build_warning=opts.build_warning,
1248
    #                                    build_fail=opts.build_fail,
1249
    #                                    query_interval=opts.query_interval)
1250
    #     par_cases.append(case)
1251

    
1252
    # _run_cases_in_parallel(par_cases, fanout=opts.fanout, runner=runner)
1253

    
1254
if __name__ == "__main__":
1255
    sys.exit(main())