Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin.py @ 2a410f76

History | View | Annotate | Download (70.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 sys
49
import time
50
from base64 import b64encode
51
from IPy import IP
52
from multiprocessing import Process, Queue
53
from random import choice
54
from optparse import OptionParser, OptionValueError
55

    
56
from kamaki.clients.compute import ComputeClient
57
from kamaki.clients.cyclades import CycladesClient
58
from kamaki.clients.image import ImageClient
59
from kamaki.clients import ClientError
60

    
61
from vncauthproxy.d3des import generate_response as d3des_generate_response
62

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

    
71
# --------------------------------------------------------------------
72
# Global Variables
73
API = None
74
TOKEN = None
75
PLANKTON = None
76
PLANKTON_USER = None
77
NO_IPV6 = None
78
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
79
DEFAULT_PLANKTON = "https://cyclades.okeanos.grnet.gr/plankton"
80
DEFAULT_PLANKTON_USER = "images@okeanos.grnet.gr"
81
NOFAILFAST = None
82
VERBOSE = None
83

    
84
# A unique id identifying this test run
85
TEST_RUN_ID = datetime.datetime.strftime(datetime.datetime.now(),
86
                                         "%Y%m%d%H%M%S")
87
SNF_TEST_PREFIX = "snf-test-"
88

    
89
red = '\x1b[31m'
90
yellow = '\x1b[33m'
91
green = '\x1b[32m'
92
normal = '\x1b[0m'
93

    
94

    
95
# --------------------------------------------------------------------
96
# Global functions
97
def _ssh_execute(hostip, username, password, command):
98
    """Execute a command via ssh"""
99
    ssh = paramiko.SSHClient()
100
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
101
    try:
102
        ssh.connect(hostip, username=username, password=password)
103
    except socket.error:
104
        raise AssertionError
105
    try:
106
        stdin, stdout, stderr = ssh.exec_command(command)
107
    except paramiko.SSHException:
108
        raise AssertionError
109
    status = stdout.channel.recv_exit_status()
110
    output = stdout.readlines()
111
    ssh.close()
112
    return output, status
113

    
114

    
115
# --------------------------------------------------------------------
116
# BurninTestReulst class
117
class BurninTestResult(unittest.TextTestResult):
118
    def addSuccess(self, test):
119
        super(BurninTestResult, self).addSuccess(test)
120
        if self.showAll:
121
            if hasattr(test, 'result_dict'):
122
                run_details = test.result_dict
123

    
124
                self.stream.write("\n")
125
                for i in run_details:
126
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
127
                self.stream.write("\n")
128

    
129
        elif self.dots:
130
            self.stream.write('.')
131
            self.stream.flush()
132

    
133
    def addError(self, test, err):
134
        super(BurninTestResult, self).addError(test, err)
135
        if self.showAll:
136
            self.stream.writeln("ERROR")
137
            if hasattr(test, 'result_dict'):
138
                run_details = test.result_dict
139

    
140
                self.stream.write("\n")
141
                for i in run_details:
142
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
143
                self.stream.write("\n")
144

    
145
        elif self.dots:
146
            self.stream.write('E')
147
            self.stream.flush()
148

    
149
    def addFailure(self, test, err):
150
        super(BurninTestResult, self).addFailure(test, err)
151
        if self.showAll:
152
            self.stream.writeln("FAIL")
153
            if hasattr(test, 'result_dict'):
154
                run_details = test.result_dict
155

    
156
                self.stream.write("\n")
157
                for i in run_details:
158
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
159
                self.stream.write("\n")
160

    
161
        elif self.dots:
162
            self.stream.write('F')
163
            self.stream.flush()
164

    
165

    
166
# --------------------------------------------------------------------
167
# Format Results
168
class burninFormatter(logging.Formatter):
169
    err_fmt = red + "ERROR: %(msg)s" + normal
170
    dbg_fmt = green + "* %(msg)s" + normal
171
    info_fmt = "%(msg)s"
172

    
173
    def __init__(self, fmt="%(levelno)s: %(msg)s"):
174
        logging.Formatter.__init__(self, fmt)
175

    
176
    def format(self, record):
177
        format_orig = self._fmt
178
        # Replace the original format with one customized by logging level
179
        if record.levelno == 10:    # DEBUG
180
            self._fmt = burninFormatter.dbg_fmt
181
        elif record.levelno == 20:  # INFO
182
            self._fmt = burninFormatter.info_fmt
183
        elif record.levelno == 40:  # ERROR
184
            self._fmt = burninFormatter.err_fmt
185
        result = logging.Formatter.format(self, record)
186
        self._fmt = format_orig
187
        return result
188

    
189
log = logging.getLogger("burnin")
190
log.setLevel(logging.DEBUG)
191
handler = logging.StreamHandler()
192
handler.setFormatter(burninFormatter())
193
log.addHandler(handler)
194

    
195

    
196
# --------------------------------------------------------------------
197
# UnauthorizedTestCase class
198
class UnauthorizedTestCase(unittest.TestCase):
199
    """Test unauthorized access"""
200
    @classmethod
201
    def setUpClass(cls):
202
        cls.result_dict = dict()
203

    
204
    def test_unauthorized_access(self):
205
        """Test access without a valid token fails"""
206
        log.info("Authentication test")
207
        falseToken = '12345'
208
        c = ComputeClient(API, falseToken)
209

    
210
        with self.assertRaises(ClientError) as cm:
211
            c.list_servers()
212
            self.assertEqual(cm.exception.status, 401)
213

    
214

    
215
# --------------------------------------------------------------------
216
# ImagesTestCase class
217
class ImagesTestCase(unittest.TestCase):
218
    """Test image lists for consistency"""
219
    @classmethod
220
    def setUpClass(cls):
221
        """Initialize kamaki, get (detailed) list of images"""
222
        log.info("Getting simple and detailed list of images")
223
        cls.client = ComputeClient(API, TOKEN)
224
        cls.plankton = ImageClient(PLANKTON, TOKEN)
225
        cls.images = cls.plankton.list_public()
226
        cls.dimages = cls.plankton.list_public(detail=True)
227
        cls.result_dict = dict()
228

    
229
    def test_001_list_images(self):
230
        """Test image list actually returns images"""
231
        self.assertGreater(len(self.images), 0)
232

    
233
    def test_002_list_images_detailed(self):
234
        """Test detailed image list is the same length as list"""
235
        self.assertEqual(len(self.dimages), len(self.images))
236

    
237
    def test_003_same_image_names(self):
238
        """Test detailed and simple image list contain same names"""
239
        names = sorted(map(lambda x: x["name"], self.images))
240
        dnames = sorted(map(lambda x: x["name"], self.dimages))
241
        self.assertEqual(names, dnames)
242

    
243
    def test_004_unique_image_names(self):
244
        """Test system images have unique names"""
245
        sys_images = filter(lambda x: x['owner'] == PLANKTON_USER,
246
                            self.dimages)
247
        names = sorted(map(lambda x: x["name"], sys_images))
248
        self.assertEqual(sorted(list(set(names))), names)
249

    
250
    def test_005_image_metadata(self):
251
        """Test every image has specific metadata defined"""
252
        keys = frozenset(["osfamily", "root_partition"])
253
        details = self.client.list_images(detail=True)
254
        for i in details:
255
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
256

    
257

    
258
# --------------------------------------------------------------------
259
# FlavorsTestCase class
260
class FlavorsTestCase(unittest.TestCase):
261
    """Test flavor lists for consistency"""
262
    @classmethod
263
    def setUpClass(cls):
264
        """Initialize kamaki, get (detailed) list of flavors"""
265
        log.info("Getting simple and detailed list of flavors")
266
        cls.client = ComputeClient(API, TOKEN)
267
        cls.flavors = cls.client.list_flavors()
268
        cls.dflavors = cls.client.list_flavors(detail=True)
269
        cls.result_dict = dict()
270

    
271
    def test_001_list_flavors(self):
272
        """Test flavor list actually returns flavors"""
273
        self.assertGreater(len(self.flavors), 0)
274

    
275
    def test_002_list_flavors_detailed(self):
276
        """Test detailed flavor list is the same length as list"""
277
        self.assertEquals(len(self.dflavors), len(self.flavors))
278

    
279
    def test_003_same_flavor_names(self):
280
        """Test detailed and simple flavor list contain same names"""
281
        names = sorted(map(lambda x: x["name"], self.flavors))
282
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
283
        self.assertEqual(names, dnames)
284

    
285
    def test_004_unique_flavor_names(self):
286
        """Test flavors have unique names"""
287
        names = sorted(map(lambda x: x["name"], self.flavors))
288
        self.assertEqual(sorted(list(set(names))), names)
289

    
290
    def test_005_well_formed_flavor_names(self):
291
        """Test flavors have names of the form CxxRyyDzz
292
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
293
        """
294
        for f in self.dflavors:
295
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
296
                             f["name"],
297
                             "Flavor %s does not match its specs." % f["name"])
298

    
299

    
300
# --------------------------------------------------------------------
301
# ServersTestCase class
302
class ServersTestCase(unittest.TestCase):
303
    """Test server lists for consistency"""
304
    @classmethod
305
    def setUpClass(cls):
306
        """Initialize kamaki, get (detailed) list of servers"""
307
        log.info("Getting simple and detailed list of servers")
308

    
309
        cls.client = ComputeClient(API, TOKEN)
310
        cls.servers = cls.client.list_servers()
311
        cls.dservers = cls.client.list_servers(detail=True)
312
        cls.result_dict = dict()
313

    
314
    def test_001_list_servers(self):
315
        """Test server list actually returns servers"""
316
        self.assertGreater(len(self.servers), 0)
317

    
318
    def test_002_list_servers_detailed(self):
319
        """Test detailed server list is the same length as list"""
320
        self.assertEqual(len(self.dservers), len(self.servers))
321

    
322
    def test_003_same_server_names(self):
323
        """Test detailed and simple flavor list contain same names"""
324
        names = sorted(map(lambda x: x["name"], self.servers))
325
        dnames = sorted(map(lambda x: x["name"], self.dservers))
326
        self.assertEqual(names, dnames)
327

    
328

    
329
# --------------------------------------------------------------------
330
# This class gets replicated into actual TestCases dynamically
331
class SpawnServerTestCase(unittest.TestCase):
332
    """Test scenario for server of the specified image"""
333

    
334
    @classmethod
335
    def setUpClass(cls):
336
        """Initialize a kamaki instance"""
337
        log.info("Spawning server for image `%s'" % cls.imagename)
338
        cls.client = ComputeClient(API, TOKEN)
339
        cls.cyclades = CycladesClient(API, TOKEN)
340
        cls.result_dict = dict()
341

    
342
    def _get_ipv4(self, server):
343
        """Get the public IPv4 of a server from the detailed server info"""
344

    
345
        nics = server["attachments"]["values"]
346

    
347
        for nic in nics:
348
            net_id = nic["network_id"]
349
            if self.cyclades.get_network_details(net_id)["public"]:
350
                public_addrs = nic["ipv4"]
351

    
352
        self.assertTrue(public_addrs is not None)
353

    
354
        return public_addrs
355

    
356
    def _get_ipv6(self, server):
357
        """Get the public IPv6 of a server from the detailed server info"""
358

    
359
        nics = server["attachments"]["values"]
360

    
361
        for nic in nics:
362
            net_id = nic["network_id"]
363
            if self.cyclades.get_network_details(net_id)["public"]:
364
                public_addrs = nic["ipv6"]
365

    
366
        self.assertTrue(public_addrs is not None)
367

    
368
        return public_addrs
369

    
370
    def _connect_loginname(self, os_value):
371
        """Return the login name for connections based on the server OS"""
372
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
373
            return "user"
374
        elif os_value in ("windows", "windows_alpha1"):
375
            return "Administrator"
376
        else:
377
            return "root"
378

    
379
    def _verify_server_status(self, current_status, new_status):
380
        """Verify a server has switched to a specified status"""
381
        server = self.client.get_server_details(self.serverid)
382
        if server["status"] not in (current_status, new_status):
383
            return None  # Do not raise exception, return so the test fails
384
        self.assertEquals(server["status"], new_status)
385

    
386
    def _get_connected_tcp_socket(self, family, host, port):
387
        """Get a connected socket from the specified family to host:port"""
388
        sock = None
389
        for res in \
390
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
391
                               socket.AI_PASSIVE):
392
            af, socktype, proto, canonname, sa = res
393
            try:
394
                sock = socket.socket(af, socktype, proto)
395
            except socket.error:
396
                sock = None
397
                continue
398
            try:
399
                sock.connect(sa)
400
            except socket.error:
401
                sock.close()
402
                sock = None
403
                continue
404
        self.assertIsNotNone(sock)
405
        return sock
406

    
407
    def _ping_once(self, ipv6, ip):
408
        """Test server responds to a single IPv4 or IPv6 ping"""
409
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
410
        ping = subprocess.Popen(cmd, shell=True,
411
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
412
        (stdout, stderr) = ping.communicate()
413
        ret = ping.wait()
414
        self.assertEquals(ret, 0)
415

    
416
    def _get_hostname_over_ssh(self, hostip, username, password):
417
        lines, status = _ssh_execute(
418
            hostip, username, password, "hostname")
419
        self.assertEqual(len(lines), 1)
420
        return lines[0]
421

    
422
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
423
                                   opmsg, callable, *args, **kwargs):
424
        if warn_timeout == fail_timeout:
425
            warn_timeout = fail_timeout + 1
426
        warn_tmout = time.time() + warn_timeout
427
        fail_tmout = time.time() + fail_timeout
428
        while True:
429
            self.assertLess(time.time(), fail_tmout,
430
                            "operation `%s' timed out" % opmsg)
431
            if time.time() > warn_tmout:
432
                log.warning("Server %d: `%s' operation `%s' not done yet",
433
                            self.serverid, self.servername, opmsg)
434
            try:
435
                log.info("%s... " % opmsg)
436
                return callable(*args, **kwargs)
437
            except AssertionError:
438
                pass
439
            time.sleep(self.query_interval)
440

    
441
    def _insist_on_tcp_connection(self, family, host, port):
442
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
443
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
444
        msg = "connect over %s to %s:%s" % \
445
              (familystr.get(family, "Unknown"), host, port)
446
        sock = self._try_until_timeout_expires(
447
            self.action_timeout, self.action_timeout,
448
            msg, self._get_connected_tcp_socket,
449
            family, host, port)
450
        return sock
451

    
452
    def _insist_on_status_transition(self, current_status, new_status,
453
                                     fail_timeout, warn_timeout=None):
454
        msg = "Server %d: `%s', waiting for %s -> %s" % \
455
              (self.serverid, self.servername, current_status, new_status)
456
        if warn_timeout is None:
457
            warn_timeout = fail_timeout
458
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
459
                                        msg, self._verify_server_status,
460
                                        current_status, new_status)
461
        # Ensure the status is actually the expected one
462
        server = self.client.get_server_details(self.serverid)
463
        self.assertEquals(server["status"], new_status)
464

    
465
    def _insist_on_ssh_hostname(self, hostip, username, password):
466
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
467
        hostname = self._try_until_timeout_expires(
468
            self.action_timeout, self.action_timeout,
469
            msg, self._get_hostname_over_ssh,
470
            hostip, username, password)
471

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

    
475
    def _check_file_through_ssh(self, hostip, username, password,
476
                                remotepath, content):
477
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
478
            (hostip, username, password)
479
        log.info(msg)
480
        try:
481
            ssh = paramiko.SSHClient()
482
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
483
            ssh.connect(hostip, username=username, password=password)
484
            ssh.close()
485
        except socket.error:
486
            raise AssertionError
487

    
488
        transport = paramiko.Transport((hostip, 22))
489
        transport.connect(username=username, password=password)
490

    
491
        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
492
        sftp = paramiko.SFTPClient.from_transport(transport)
493
        sftp.get(remotepath, localpath)
494
        sftp.close()
495
        transport.close()
496

    
497
        f = open(localpath)
498
        remote_content = b64encode(f.read())
499

    
500
        # Check if files are the same
501
        return (remote_content == content)
502

    
503
    def _skipIf(self, condition, msg):
504
        if condition:
505
            self.skipTest(msg)
506

    
507
    def test_001_submit_create_server(self):
508
        """Test submit create server request"""
509

    
510
        log.info("Submit new server request")
511
        server = self.client.create_server(self.servername, self.flavorid,
512
                                           self.imageid, self.personality)
513

    
514
        log.info("Server id: " + str(server["id"]))
515
        log.info("Server password: " + server["adminPass"])
516
        self.assertEqual(server["name"], self.servername)
517
        self.assertEqual(server["flavorRef"], self.flavorid)
518
        self.assertEqual(server["imageRef"], self.imageid)
519
        self.assertEqual(server["status"], "BUILD")
520

    
521
        # Update class attributes to reflect data on building server
522
        cls = type(self)
523
        cls.serverid = server["id"]
524
        cls.username = None
525
        cls.passwd = server["adminPass"]
526

    
527
        self.result_dict["Server ID"] = str(server["id"])
528
        self.result_dict["Password"] = str(server["adminPass"])
529

    
530
    def test_002a_server_is_building_in_list(self):
531
        """Test server is in BUILD state, in server list"""
532
        log.info("Server in BUILD state in server list")
533

    
534
        self.result_dict.clear()
535

    
536
        servers = self.client.list_servers(detail=True)
537
        servers = filter(lambda x: x["name"] == self.servername, servers)
538

    
539
        server = servers[0]
540
        self.assertEqual(server["name"], self.servername)
541
        self.assertEqual(server["flavorRef"], self.flavorid)
542
        self.assertEqual(server["imageRef"], self.imageid)
543
        self.assertEqual(server["status"], "BUILD")
544

    
545
    def test_002b_server_is_building_in_details(self):
546
        """Test server is in BUILD state, in details"""
547

    
548
        log.info("Server in BUILD state in details")
549

    
550
        server = self.client.get_server_details(self.serverid)
551
        self.assertEqual(server["name"], self.servername)
552
        self.assertEqual(server["flavorRef"], self.flavorid)
553
        self.assertEqual(server["imageRef"], self.imageid)
554
        self.assertEqual(server["status"], "BUILD")
555

    
556
    def test_002c_set_server_metadata(self):
557

    
558
        log.info("Creating server metadata")
559

    
560
        image = self.client.get_image_details(self.imageid)
561
        os_value = image["metadata"]["values"]["os"]
562
        users = image["metadata"]["values"].get("users", None)
563
        self.client.update_server_metadata(self.serverid, OS=os_value)
564

    
565
        userlist = users.split()
566

    
567
        # Determine the username to use for future connections
568
        # to this host
569
        cls = type(self)
570

    
571
        if "root" in userlist:
572
            cls.username = "root"
573
        elif users is None:
574
            cls.username = self._connect_loginname(os_value)
575
        else:
576
            cls.username = choice(userlist)
577

    
578
        self.assertIsNotNone(cls.username)
579

    
580
    def test_002d_verify_server_metadata(self):
581
        """Test server metadata keys are set based on image metadata"""
582

    
583
        log.info("Verifying image metadata")
584

    
585
        servermeta = self.client.get_server_metadata(self.serverid)
586
        imagemeta = self.client.get_image_metadata(self.imageid)
587

    
588
        self.assertEqual(servermeta["OS"], imagemeta["os"])
589

    
590
    def test_003_server_becomes_active(self):
591
        """Test server becomes ACTIVE"""
592

    
593
        log.info("Waiting for server to become ACTIVE")
594

    
595
        self._insist_on_status_transition(
596
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
597

    
598
    def test_003a_get_server_oob_console(self):
599
        """Test getting OOB server console over VNC
600

601
        Implementation of RFB protocol follows
602
        http://www.realvnc.com/docs/rfbproto.pdf.
603

604
        """
605
        console = self.cyclades.get_server_console(self.serverid)
606
        self.assertEquals(console['type'], "vnc")
607
        sock = self._insist_on_tcp_connection(
608
            socket.AF_INET, console["host"], console["port"])
609

    
610
        # Step 1. ProtocolVersion message (par. 6.1.1)
611
        version = sock.recv(1024)
612
        self.assertEquals(version, 'RFB 003.008\n')
613
        sock.send(version)
614

    
615
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
616
        sec = sock.recv(1024)
617
        self.assertEquals(list(sec), ['\x01', '\x02'])
618

    
619
        # Step 3. Request VNC Authentication (par 6.1.2)
620
        sock.send('\x02')
621

    
622
        # Step 4. Receive Challenge (par 6.2.2)
623
        challenge = sock.recv(1024)
624
        self.assertEquals(len(challenge), 16)
625

    
626
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
627
        response = d3des_generate_response(
628
            (console["password"] + '\0' * 8)[:8], challenge)
629
        sock.send(response)
630

    
631
        # Step 6. SecurityResult (par 6.1.3)
632
        result = sock.recv(4)
633
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
634
        sock.close()
635

    
636
    def test_004_server_has_ipv4(self):
637
        """Test active server has a valid IPv4 address"""
638

    
639
        log.info("Validate server's IPv4")
640

    
641
        server = self.client.get_server_details(self.serverid)
642
        ipv4 = self._get_ipv4(server)
643

    
644
        self.result_dict.clear()
645
        self.result_dict["IPv4"] = str(ipv4)
646

    
647
        self.assertEquals(IP(ipv4).version(), 4)
648

    
649
    def test_005_server_has_ipv6(self):
650
        """Test active server has a valid IPv6 address"""
651
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
652

    
653
        log.info("Validate server's IPv6")
654

    
655
        server = self.client.get_server_details(self.serverid)
656
        ipv6 = self._get_ipv6(server)
657

    
658
        self.result_dict.clear()
659
        self.result_dict["IPv6"] = str(ipv6)
660

    
661
        self.assertEquals(IP(ipv6).version(), 6)
662

    
663
    def test_006_server_responds_to_ping_IPv4(self):
664
        """Test server responds to ping on IPv4 address"""
665

    
666
        log.info("Testing if server responds to pings in IPv4")
667
        self.result_dict.clear()
668

    
669
        server = self.client.get_server_details(self.serverid)
670
        ip = self._get_ipv4(server)
671
        self._try_until_timeout_expires(self.action_timeout,
672
                                        self.action_timeout,
673
                                        "PING IPv4 to %s" % ip,
674
                                        self._ping_once,
675
                                        False, ip)
676

    
677
    def test_007_server_responds_to_ping_IPv6(self):
678
        """Test server responds to ping on IPv6 address"""
679
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
680
        log.info("Testing if server responds to pings in IPv6")
681

    
682
        server = self.client.get_server_details(self.serverid)
683
        ip = self._get_ipv6(server)
684
        self._try_until_timeout_expires(self.action_timeout,
685
                                        self.action_timeout,
686
                                        "PING IPv6 to %s" % ip,
687
                                        self._ping_once,
688
                                        True, ip)
689

    
690
    def test_008_submit_shutdown_request(self):
691
        """Test submit request to shutdown server"""
692

    
693
        log.info("Shutting down server")
694

    
695
        self.cyclades.shutdown_server(self.serverid)
696

    
697
    def test_009_server_becomes_stopped(self):
698
        """Test server becomes STOPPED"""
699

    
700
        log.info("Waiting until server becomes STOPPED")
701
        self._insist_on_status_transition(
702
            "ACTIVE", "STOPPED", self.action_timeout, self.action_timeout)
703

    
704
    def test_010_submit_start_request(self):
705
        """Test submit start server request"""
706

    
707
        log.info("Starting server")
708

    
709
        self.cyclades.start_server(self.serverid)
710

    
711
    def test_011_server_becomes_active(self):
712
        """Test server becomes ACTIVE again"""
713

    
714
        log.info("Waiting until server becomes ACTIVE")
715
        self._insist_on_status_transition(
716
            "STOPPED", "ACTIVE", self.action_timeout, self.action_timeout)
717

    
718
    def test_011a_server_responds_to_ping_IPv4(self):
719
        """Test server OS is actually up and running again"""
720

    
721
        log.info("Testing if server is actually up and running")
722

    
723
        self.test_006_server_responds_to_ping_IPv4()
724

    
725
    def test_012_ssh_to_server_IPv4(self):
726
        """Test SSH to server public IPv4 works, verify hostname"""
727

    
728
        self._skipIf(self.is_windows, "only valid for Linux servers")
729
        server = self.client.get_server_details(self.serverid)
730
        self._insist_on_ssh_hostname(self._get_ipv4(server),
731
                                     self.username, self.passwd)
732

    
733
    def test_013_ssh_to_server_IPv6(self):
734
        """Test SSH to server public IPv6 works, verify hostname"""
735
        self._skipIf(self.is_windows, "only valid for Linux servers")
736
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
737

    
738
        server = self.client.get_server_details(self.serverid)
739
        self._insist_on_ssh_hostname(self._get_ipv6(server),
740
                                     self.username, self.passwd)
741

    
742
    def test_014_rdp_to_server_IPv4(self):
743
        "Test RDP connection to server public IPv4 works"""
744
        self._skipIf(not self.is_windows, "only valid for Windows servers")
745
        server = self.client.get_server_details(self.serverid)
746
        ipv4 = self._get_ipv4(server)
747
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
748

    
749
        # No actual RDP processing done. We assume the RDP server is there
750
        # if the connection to the RDP port is successful.
751
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
752
        sock.close()
753

    
754
    def test_015_rdp_to_server_IPv6(self):
755
        "Test RDP connection to server public IPv6 works"""
756
        self._skipIf(not self.is_windows, "only valid for Windows servers")
757
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
758

    
759
        server = self.client.get_server_details(self.serverid)
760
        ipv6 = self._get_ipv6(server)
761
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
762

    
763
        # No actual RDP processing done. We assume the RDP server is there
764
        # if the connection to the RDP port is successful.
765
        sock.close()
766

    
767
    def test_016_personality_is_enforced(self):
768
        """Test file injection for personality enforcement"""
769
        self._skipIf(self.is_windows, "only implemented for Linux servers")
770
        self._skipIf(self.personality is None, "No personality file selected")
771

    
772
        log.info("Trying to inject file for personality enforcement")
773

    
774
        server = self.client.get_server_details(self.serverid)
775

    
776
        for inj_file in self.personality:
777
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
778
                                                       inj_file['owner'],
779
                                                       self.passwd,
780
                                                       inj_file['path'],
781
                                                       inj_file['contents'])
782
            self.assertTrue(equal_files)
783

    
784
    def test_017_submit_delete_request(self):
785
        """Test submit request to delete server"""
786

    
787
        log.info("Deleting server")
788

    
789
        self.client.delete_server(self.serverid)
790

    
791
    def test_018_server_becomes_deleted(self):
792
        """Test server becomes DELETED"""
793

    
794
        log.info("Testing if server becomes DELETED")
795

    
796
        self._insist_on_status_transition(
797
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
798

    
799
    def test_019_server_no_longer_in_server_list(self):
800
        """Test server is no longer in server list"""
801

    
802
        log.info("Test if server is no longer listed")
803

    
804
        servers = self.client.list_servers()
805
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
806

    
807

    
808
class NetworkTestCase(unittest.TestCase):
809
    """ Testing networking in cyclades """
810

    
811
    @classmethod
812
    def setUpClass(cls):
813
        "Initialize kamaki, get list of current networks"
814

    
815
        cls.client = CycladesClient(API, TOKEN)
816
        cls.compute = ComputeClient(API, TOKEN)
817

    
818
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
819
                                          TEST_RUN_ID,
820
                                          cls.imagename)
821

    
822
        #Dictionary initialization for the vms credentials
823
        cls.serverid = dict()
824
        cls.username = dict()
825
        cls.password = dict()
826
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
827

    
828
        cls.result_dict = dict()
829

    
830
    def _skipIf(self, condition, msg):
831
        if condition:
832
            self.skipTest(msg)
833

    
834
    def _get_ipv4(self, server):
835
        """Get the public IPv4 of a server from the detailed server info"""
836

    
837
        nics = server["attachments"]["values"]
838

    
839
        for nic in nics:
840
            net_id = nic["network_id"]
841
            if self.client.get_network_details(net_id)["public"]:
842
                public_addrs = nic["ipv4"]
843

    
844
        self.assertTrue(public_addrs is not None)
845

    
846
        return public_addrs
847

    
848
    def _connect_loginname(self, os_value):
849
        """Return the login name for connections based on the server OS"""
850
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
851
            return "user"
852
        elif os_value in ("windows", "windows_alpha1"):
853
            return "Administrator"
854
        else:
855
            return "root"
856

    
857
    def _ping_once(self, ip):
858

    
859
        """Test server responds to a single IPv4 or IPv6 ping"""
860
        cmd = "ping -c 2 -w 3 %s" % (ip)
861
        ping = subprocess.Popen(cmd, shell=True,
862
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
863
        (stdout, stderr) = ping.communicate()
864
        ret = ping.wait()
865

    
866
        return (ret == 0)
867

    
868
    def test_00001a_submit_create_server_A(self):
869
        """Test submit create server request"""
870

    
871
        log.info("Creating test server A")
872

    
873
        serverA = self.client.create_server(self.servername, self.flavorid,
874
                                            self.imageid, personality=None)
875

    
876
        self.assertEqual(serverA["name"], self.servername)
877
        self.assertEqual(serverA["flavorRef"], self.flavorid)
878
        self.assertEqual(serverA["imageRef"], self.imageid)
879
        self.assertEqual(serverA["status"], "BUILD")
880

    
881
        # Update class attributes to reflect data on building server
882
        self.serverid['A'] = serverA["id"]
883
        self.username['A'] = None
884
        self.password['A'] = serverA["adminPass"]
885

    
886
        log.info("Server A id:" + str(serverA["id"]))
887
        log.info("Server password " + (self.password['A']))
888

    
889
        self.result_dict["Server A ID"] = str(serverA["id"])
890
        self.result_dict["Server A password"] = serverA["adminPass"]
891

    
892
    def test_00001b_serverA_becomes_active(self):
893
        """Test server becomes ACTIVE"""
894

    
895
        log.info("Waiting until test server A becomes ACTIVE")
896
        self.result_dict.clear()
897

    
898
        fail_tmout = time.time() + self.action_timeout
899
        while True:
900
            d = self.client.get_server_details(self.serverid['A'])
901
            status = d['status']
902
            if status == 'ACTIVE':
903
                active = True
904
                break
905
            elif time.time() > fail_tmout:
906
                self.assertLess(time.time(), fail_tmout)
907
            else:
908
                time.sleep(self.query_interval)
909

    
910
        self.assertTrue(active)
911

    
912
    def test_00002a_submit_create_server_B(self):
913
        """Test submit create server request"""
914

    
915
        log.info("Creating test server B")
916

    
917
        serverB = self.client.create_server(self.servername, self.flavorid,
918
                                            self.imageid, personality=None)
919

    
920
        self.assertEqual(serverB["name"], self.servername)
921
        self.assertEqual(serverB["flavorRef"], self.flavorid)
922
        self.assertEqual(serverB["imageRef"], self.imageid)
923
        self.assertEqual(serverB["status"], "BUILD")
924

    
925
        # Update class attributes to reflect data on building server
926
        self.serverid['B'] = serverB["id"]
927
        self.username['B'] = None
928
        self.password['B'] = serverB["adminPass"]
929

    
930
        log.info("Server B id: " + str(serverB["id"]))
931
        log.info("Password " + (self.password['B']))
932

    
933
        self.result_dict.clear()
934
        self.result_dict["Server B ID"] = str(serverB["id"])
935
        self.result_dict["Server B password"] = serverB["adminPass"]
936

    
937
    def test_00002b_serverB_becomes_active(self):
938
        """Test server becomes ACTIVE"""
939

    
940
        log.info("Waiting until test server B becomes ACTIVE")
941
        self.result_dict.clear()
942

    
943
        fail_tmout = time.time() + self.action_timeout
944
        while True:
945
            d = self.client.get_server_details(self.serverid['B'])
946
            status = d['status']
947
            if status == 'ACTIVE':
948
                active = True
949
                break
950
            elif time.time() > fail_tmout:
951
                self.assertLess(time.time(), fail_tmout)
952
            else:
953
                time.sleep(self.query_interval)
954

    
955
        self.assertTrue(active)
956

    
957
    def test_001_create_network(self):
958
        """Test submit create network request"""
959

    
960
        log.info("Submit new network request")
961
        self.result_dict.clear()
962

    
963
        name = SNF_TEST_PREFIX + TEST_RUN_ID
964
        #previous_num = len(self.client.list_networks())
965
        network = self.client.create_network(name, cidr='10.0.0.1/28')
966

    
967
        #Test if right name is assigned
968
        self.assertEqual(network['name'], name)
969

    
970
        # Update class attributes
971
        cls = type(self)
972
        cls.networkid = network['id']
973
        #networks = self.client.list_networks()
974

    
975
        fail_tmout = time.time() + self.action_timeout
976

    
977
        #Test if new network is created
978
        while True:
979
            d = self.client.get_network_details(network['id'])
980
            if d['status'] == 'ACTIVE':
981
                connected = True
982
                break
983
            elif time.time() > fail_tmout:
984
                self.assertLess(time.time(), fail_tmout)
985
            else:
986
                log.info("Waiting for network to become ACTIVE")
987
                time.sleep(self.query_interval)
988

    
989
        self.assertTrue(connected)
990

    
991
        self.result_dict["Private network ID"] = str(network['id'])
992

    
993
    def test_002_connect_to_network(self):
994
        """Test connect VMs to network"""
995

    
996
        log.info("Connect VMs to private network")
997
        self.result_dict.clear()
998

    
999
        self.client.connect_server(self.serverid['A'], self.networkid)
1000
        self.client.connect_server(self.serverid['B'], self.networkid)
1001

    
1002
        #Insist on connecting until action timeout
1003
        fail_tmout = time.time() + self.action_timeout
1004

    
1005
        while True:
1006

    
1007
            netsA = [x['network_id']
1008
                     for x in self.client.get_server_details(
1009
                         self.serverid['A'])['attachments']['values']]
1010
            netsB = [x['network_id']
1011
                     for x in self.client.get_server_details(
1012
                         self.serverid['B'])['attachments']['values']]
1013

    
1014
            if (self.networkid in netsA) and (self.networkid in netsB):
1015
                conn_exists = True
1016
                break
1017
            elif time.time() > fail_tmout:
1018
                self.assertLess(time.time(), fail_tmout)
1019
            else:
1020
                time.sleep(self.query_interval)
1021

    
1022
        #Adding private IPs to class attributes
1023
        cls = type(self)
1024
        cls.priv_ip = dict()
1025

    
1026
        nicsA = self.client.get_server_details(
1027
            self.serverid['A'])['attachments']['values']
1028
        nicsB = self.client.get_server_details(
1029
            self.serverid['B'])['attachments']['values']
1030

    
1031
        if conn_exists:
1032
            for nic in nicsA:
1033
                if nic["network_id"] == self.networkid:
1034
                    cls.priv_ip["A"] = nic["ipv4"]
1035

    
1036
            for nic in nicsB:
1037
                if nic["network_id"] == self.networkid:
1038
                    cls.priv_ip["B"] = nic["ipv4"]
1039

    
1040
        self.assertTrue(conn_exists)
1041

    
1042
    def test_002a_reboot(self):
1043
        """Rebooting server A"""
1044

    
1045
        log.info("Rebooting server A")
1046

    
1047
        self.client.shutdown_server(self.serverid['A'])
1048

    
1049
        fail_tmout = time.time() + self.action_timeout
1050
        while True:
1051
            d = self.client.get_server_details(self.serverid['A'])
1052
            status = d['status']
1053
            if status == 'STOPPED':
1054
                break
1055
            elif time.time() > fail_tmout:
1056
                self.assertLess(time.time(), fail_tmout)
1057
            else:
1058
                time.sleep(self.query_interval)
1059

    
1060
        self.client.start_server(self.serverid['A'])
1061

    
1062
        while True:
1063
            d = self.client.get_server_details(self.serverid['A'])
1064
            status = d['status']
1065
            if status == 'ACTIVE':
1066
                active = True
1067
                break
1068
            elif time.time() > fail_tmout:
1069
                self.assertLess(time.time(), fail_tmout)
1070
            else:
1071
                time.sleep(self.query_interval)
1072

    
1073
        self.assertTrue(active)
1074

    
1075
    def test_002b_ping_server_A(self):
1076
        "Test if server A responds to IPv4 pings"
1077

    
1078
        log.info("Testing if server A responds to IPv4 pings ")
1079
        self.result_dict.clear()
1080

    
1081
        server = self.client.get_server_details(self.serverid['A'])
1082
        ip = self._get_ipv4(server)
1083

    
1084
        fail_tmout = time.time() + self.action_timeout
1085

    
1086
        s = False
1087

    
1088
        self.result_dict["Server A public IP"] = str(ip)
1089

    
1090
        while True:
1091

    
1092
            if self._ping_once(ip):
1093
                s = True
1094
                break
1095

    
1096
            elif time.time() > fail_tmout:
1097
                self.assertLess(time.time(), fail_tmout)
1098

    
1099
            else:
1100
                time.sleep(self.query_interval)
1101

    
1102
        self.assertTrue(s)
1103

    
1104
    def test_002c_reboot(self):
1105
        """Reboot server B"""
1106

    
1107
        log.info("Rebooting server B")
1108
        self.result_dict.clear()
1109

    
1110
        self.client.shutdown_server(self.serverid['B'])
1111

    
1112
        fail_tmout = time.time() + self.action_timeout
1113
        while True:
1114
            d = self.client.get_server_details(self.serverid['B'])
1115
            status = d['status']
1116
            if status == 'STOPPED':
1117
                break
1118
            elif time.time() > fail_tmout:
1119
                self.assertLess(time.time(), fail_tmout)
1120
            else:
1121
                time.sleep(self.query_interval)
1122

    
1123
        self.client.start_server(self.serverid['B'])
1124

    
1125
        while True:
1126
            d = self.client.get_server_details(self.serverid['B'])
1127
            status = d['status']
1128
            if status == 'ACTIVE':
1129
                active = True
1130
                break
1131
            elif time.time() > fail_tmout:
1132
                self.assertLess(time.time(), fail_tmout)
1133
            else:
1134
                time.sleep(self.query_interval)
1135

    
1136
        self.assertTrue(active)
1137

    
1138
    def test_002d_ping_server_B(self):
1139
        """Test if server B responds to IPv4 pings"""
1140

    
1141
        log.info("Testing if server B responds to IPv4 pings")
1142
        self.result_dict.clear()
1143

    
1144
        server = self.client.get_server_details(self.serverid['B'])
1145
        ip = self._get_ipv4(server)
1146

    
1147
        fail_tmout = time.time() + self.action_timeout
1148

    
1149
        s = False
1150

    
1151
        self.result_dict["Server B public IP"] = str(ip)
1152

    
1153
        while True:
1154
            if self._ping_once(ip):
1155
                s = True
1156
                break
1157

    
1158
            elif time.time() > fail_tmout:
1159
                self.assertLess(time.time(), fail_tmout)
1160

    
1161
            else:
1162
                time.sleep(self.query_interval)
1163

    
1164
        self.assertTrue(s)
1165

    
1166
    def test_003a_setup_interface_A(self):
1167
        """Set up eth1 for server A"""
1168

    
1169
        self._skipIf(self.is_windows, "only valid for Linux servers")
1170

    
1171
        log.info("Setting up interface eth1 for server A")
1172
        self.result_dict.clear()
1173

    
1174
        server = self.client.get_server_details(self.serverid['A'])
1175
        image = self.client.get_image_details(self.imageid)
1176
        os_value = image['metadata']['values']['os']
1177

    
1178
        users = image["metadata"]["values"].get("users", None)
1179
        userlist = users.split()
1180

    
1181
        if "root" in userlist:
1182
            loginname = "root"
1183
        elif users is None:
1184
            loginname = self._connect_loginname(os_value)
1185
        else:
1186
            loginname = choice(userlist)
1187

    
1188
        hostip = self._get_ipv4(server)
1189
        myPass = self.password['A']
1190

    
1191
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1192
        command = "ifconfig eth1 %s" % self.priv_ip["A"]
1193
        output, status = _ssh_execute(
1194
            hostip, loginname, myPass, command)
1195

    
1196
        self.assertEquals(status, 0)
1197

    
1198
    def test_003b_setup_interface_B(self):
1199
        """Setup eth1 for server B"""
1200

    
1201
        self._skipIf(self.is_windows, "only valid for Linux servers")
1202

    
1203
        log.info("Setting up interface eth1 for server B")
1204

    
1205
        server = self.client.get_server_details(self.serverid['B'])
1206
        image = self.client.get_image_details(self.imageid)
1207
        os_value = image['metadata']['values']['os']
1208

    
1209
        users = image["metadata"]["values"].get("users", None)
1210
        userlist = users.split()
1211

    
1212
        if "root" in userlist:
1213
            loginname = "root"
1214
        elif users is None:
1215
            loginname = self._connect_loginname(os_value)
1216
        else:
1217
            loginname = choice(userlist)
1218

    
1219
        hostip = self._get_ipv4(server)
1220
        myPass = self.password['B']
1221

    
1222
        log.info("SSH in server B as %s/%s" % (loginname, myPass))
1223
        command = "ifconfig eth1 %s" % self.priv_ip["B"]
1224
        output, status = _ssh_execute(
1225
            hostip, loginname, myPass, command)
1226

    
1227
        self.assertEquals(status, 0)
1228

    
1229
    def test_003c_test_connection_exists(self):
1230
        """Ping server B from server A to test if connection exists"""
1231

    
1232
        self._skipIf(self.is_windows, "only valid for Linux servers")
1233

    
1234
        log.info("Testing if server A is actually connected to server B")
1235

    
1236
        server = self.client.get_server_details(self.serverid['A'])
1237
        image = self.client.get_image_details(self.imageid)
1238
        os_value = image['metadata']['values']['os']
1239
        hostip = self._get_ipv4(server)
1240

    
1241
        users = image["metadata"]["values"].get("users", None)
1242
        userlist = users.split()
1243

    
1244
        if "root" in userlist:
1245
            loginname = "root"
1246
        elif users is None:
1247
            loginname = self._connect_loginname(os_value)
1248
        else:
1249
            loginname = choice(userlist)
1250

    
1251
        myPass = self.password['A']
1252

    
1253
        cmd = "if ping -c 2 -w 3 %s >/dev/null; \
1254
               then echo \'True\'; fi;" % self.priv_ip["B"]
1255
        lines, status = _ssh_execute(
1256
            hostip, loginname, myPass, cmd)
1257

    
1258
        exists = False
1259

    
1260
        if 'True\n' in lines:
1261
            exists = True
1262

    
1263
        self.assertTrue(exists)
1264

    
1265
    def test_004_disconnect_from_network(self):
1266
        "Disconnecting server A and B from network"
1267

    
1268
        log.info("Disconnecting servers from private network")
1269

    
1270
        prev_state = self.client.get_network_details(self.networkid)
1271
        prev_nics = prev_state['attachments']['values']
1272
        #prev_conn = len(prev_nics)
1273

    
1274
        nicsA = [x['id']
1275
                 for x in self.client.get_server_details(
1276
                     self.serverid['A'])['attachments']['values']]
1277
        nicsB = [x['id']
1278
                 for x in self.client.get_server_details(
1279
                     self.serverid['B'])['attachments']['values']]
1280

    
1281
        for nic in prev_nics:
1282
            if nic in nicsA:
1283
                self.client.disconnect_server(self.serverid['A'], nic)
1284
            if nic in nicsB:
1285
                self.client.disconnect_server(self.serverid['B'], nic)
1286

    
1287
        #Insist on deleting until action timeout
1288
        fail_tmout = time.time() + self.action_timeout
1289

    
1290
        while True:
1291
            netsA = [x['network_id']
1292
                     for x in self.client.get_server_details(
1293
                         self.serverid['A'])['attachments']['values']]
1294
            netsB = [x['network_id']
1295
                     for x in self.client.get_server_details(
1296
                         self.serverid['B'])['attachments']['values']]
1297

    
1298
            #connected = (self.client.get_network_details(self.networkid))
1299
            #connections = connected['attachments']['values']
1300
            if (self.networkid not in netsA) and (self.networkid not in netsB):
1301
                conn_exists = False
1302
                break
1303
            elif time.time() > fail_tmout:
1304
                self.assertLess(time.time(), fail_tmout)
1305
            else:
1306
                time.sleep(self.query_interval)
1307

    
1308
        self.assertFalse(conn_exists)
1309

    
1310
    def test_005_destroy_network(self):
1311
        """Test submit delete network request"""
1312

    
1313
        log.info("Submitting delete network request")
1314

    
1315
        self.client.delete_network(self.networkid)
1316

    
1317
        fail_tmout = time.time() + self.action_timeout
1318

    
1319
        while True:
1320

    
1321
            curr_net = []
1322
            networks = self.client.list_networks()
1323

    
1324
            for net in networks:
1325
                curr_net.append(net['id'])
1326

    
1327
            if self.networkid not in curr_net:
1328
                self.assertTrue(self.networkid not in curr_net)
1329
                break
1330

    
1331
            elif time.time() > fail_tmout:
1332
                self.assertLess(time.time(), fail_tmout)
1333

    
1334
            else:
1335
                time.sleep(self.query_interval)
1336

    
1337
    def test_006_cleanup_servers(self):
1338
        """Cleanup servers created for this test"""
1339

    
1340
        log.info("Delete servers created for this test")
1341

    
1342
        self.compute.delete_server(self.serverid['A'])
1343
        self.compute.delete_server(self.serverid['B'])
1344

    
1345
        fail_tmout = time.time() + self.action_timeout
1346

    
1347
        #Ensure server gets deleted
1348
        status = dict()
1349

    
1350
        while True:
1351
            details = self.compute.get_server_details(self.serverid['A'])
1352
            status['A'] = details['status']
1353
            details = self.compute.get_server_details(self.serverid['B'])
1354
            status['B'] = details['status']
1355
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1356
                deleted = True
1357
                break
1358
            elif time.time() > fail_tmout:
1359
                self.assertLess(time.time(), fail_tmout)
1360
            else:
1361
                time.sleep(self.query_interval)
1362

    
1363
        self.assertTrue(deleted)
1364

    
1365

    
1366
class TestRunnerProcess(Process):
1367
    """A distinct process used to execute part of the tests in parallel"""
1368
    def __init__(self, **kw):
1369
        Process.__init__(self, **kw)
1370
        kwargs = kw["kwargs"]
1371
        self.testq = kwargs["testq"]
1372
        self.worker_folder = kwargs["worker_folder"]
1373

    
1374
    def run(self):
1375
        # Make sure this test runner process dies with the parent
1376
        # and is not left behind.
1377
        #
1378
        # WARNING: This uses the prctl(2) call and is
1379
        # Linux-specific.
1380

    
1381
        prctl.set_pdeathsig(signal.SIGHUP)
1382

    
1383
        multi = logging.getLogger("multiprocess")
1384

    
1385
        while True:
1386
            multi.debug("I am process %d, GETting from queue is %s" %
1387
                        (os.getpid(), self.testq))
1388
            msg = self.testq.get()
1389

    
1390
            multi.debug("Dequeued msg: %s" % msg)
1391

    
1392
            if msg == "TEST_RUNNER_TERMINATE":
1393
                raise SystemExit
1394

    
1395
            elif issubclass(msg, unittest.TestCase):
1396
                # Assemble a TestSuite, and run it
1397

    
1398
                log_file = os.path.join(self.worker_folder, 'details_' +
1399
                                        (msg.__name__) + "_" +
1400
                                        TEST_RUN_ID + '.log')
1401

    
1402
                fail_file = os.path.join(self.worker_folder, 'failed_' +
1403
                                         (msg.__name__) + "_" +
1404
                                         TEST_RUN_ID + '.log')
1405
                error_file = os.path.join(self.worker_folder, 'error_' +
1406
                                          (msg.__name__) + "_" +
1407
                                          TEST_RUN_ID + '.log')
1408

    
1409
                f = open(log_file, 'w')
1410
                fail = open(fail_file, 'w')
1411
                error = open(error_file, 'w')
1412

    
1413
                log.info(yellow + '* Starting testcase: %s' % msg + normal)
1414

    
1415
                runner = unittest.TextTestRunner(
1416
                    f, verbosity=2, failfast=True,
1417
                    resultclass=BurninTestResult)
1418
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
1419
                result = runner.run(suite)
1420

    
1421
                for res in result.errors:
1422
                    log.error("snf-burnin encountered an error in "
1423
                              "testcase: %s" % msg)
1424
                    log.error("See log for details")
1425
                    error.write(str(res[0]) + '\n')
1426
                    error.write(str(res[0].shortDescription()) + '\n')
1427
                    error.write('\n')
1428

    
1429
                for res in result.failures:
1430
                    log.error("snf-burnin failed in testcase: %s" % msg)
1431
                    log.error("See log for details")
1432
                    fail.write(str(res[0]) + '\n')
1433
                    fail.write(str(res[0].shortDescription()) + '\n')
1434
                    fail.write('\n')
1435
                    if not NOFAILFAST:
1436
                        sys.exit()
1437

    
1438
                if (len(result.failures) == 0) and (len(result.errors) == 0):
1439
                    log.debug("Passed testcase: %s" % msg)
1440

    
1441
                f.close()
1442
                fail.close()
1443
                error.close()
1444

    
1445
            else:
1446
                raise Exception("Cannot handle msg: %s" % msg)
1447

    
1448

    
1449
def _run_cases_in_series(cases, image_folder):
1450
    """Run instances of TestCase in series"""
1451

    
1452
    for case in cases:
1453

    
1454
        test = case.__name__
1455

    
1456
        log.info(yellow + '* Starting testcase: %s' % test + normal)
1457
        log_file = os.path.join(image_folder, 'details_' +
1458
                                (case.__name__) + "_" +
1459
                                TEST_RUN_ID + '.log')
1460
        fail_file = os.path.join(image_folder, 'failed_' +
1461
                                 (case.__name__) + "_" +
1462
                                 TEST_RUN_ID + '.log')
1463
        error_file = os.path.join(image_folder, 'error_' +
1464
                                  (case.__name__) + "_" +
1465
                                  TEST_RUN_ID + '.log')
1466

    
1467
        f = open(log_file, "w")
1468
        fail = open(fail_file, "w")
1469
        error = open(error_file, "w")
1470

    
1471
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1472
        runner = unittest.TextTestRunner(
1473
            f, verbosity=2, failfast=True,
1474
            resultclass=BurninTestResult)
1475
        result = runner.run(suite)
1476

    
1477
        for res in result.errors:
1478
            log.error("snf-burnin encountered an error in "
1479
                      "testcase: %s" % test)
1480
            log.error("See log for details")
1481
            error.write(str(res[0]) + '\n')
1482
            error.write(str(res[0].shortDescription()) + '\n')
1483
            error.write('\n')
1484

    
1485
        for res in result.failures:
1486
            log.error("snf-burnin failed in testcase: %s" % test)
1487
            log.error("See log for details")
1488
            fail.write(str(res[0]) + '\n')
1489
            fail.write(str(res[0].shortDescription()) + '\n')
1490
            fail.write('\n')
1491
            if not NOFAILFAST:
1492
                sys.exit()
1493

    
1494
        if (len(result.failures) == 0) and (len(result.errors) == 0):
1495
            log.debug("Passed testcase: %s" % test)
1496

    
1497

    
1498
def _run_cases_in_parallel(cases, fanout, image_folder):
1499
    """Run instances of TestCase in parallel, in a number of distinct processes
1500

1501
    The cases iterable specifies the TestCases to be executed in parallel,
1502
    by test runners running in distinct processes.
1503
    The fanout parameter specifies the number of processes to spawn,
1504
    and defaults to 1.
1505
    The runner argument specifies the test runner class to use inside each
1506
    runner process.
1507

1508
    """
1509

    
1510
    multi = logging.getLogger("multiprocess")
1511
    handler = logging.StreamHandler()
1512
    multi.addHandler(handler)
1513

    
1514
    if VERBOSE:
1515
        multi.setLevel(logging.DEBUG)
1516
    else:
1517
        multi.setLevel(logging.INFO)
1518

    
1519
    testq = []
1520
    worker_folder = []
1521
    runners = []
1522

    
1523
    for i in xrange(0, fanout):
1524
        testq.append(Queue())
1525
        worker_folder.append(os.path.join(image_folder, 'process'+str(i)))
1526
        os.mkdir(worker_folder[i])
1527

    
1528
    for i in xrange(0, fanout):
1529
        kwargs = dict(testq=testq[i], worker_folder=worker_folder[i])
1530
        runners.append(TestRunnerProcess(kwargs=kwargs))
1531

    
1532
    multi.debug("Spawning %d test runner processes" % len(runners))
1533

    
1534
    for p in runners:
1535
        p.start()
1536

    
1537
    # Enqueue test cases
1538
    for i in xrange(0, fanout):
1539
        map(testq[i].put, cases)
1540
        testq[i].put("TEST_RUNNER_TERMINATE")
1541

    
1542
    multi.debug("Spawned %d test runners, PIDs are %s" %
1543
                (len(runners), [p.pid for p in runners]))
1544

    
1545
    multi.debug("Joining %d processes" % len(runners))
1546

    
1547
    for p in runners:
1548
        p.join()
1549

    
1550
    multi.debug("Done joining %d processes" % len(runners))
1551

    
1552

    
1553
def _spawn_server_test_case(**kwargs):
1554
    """Construct a new unit test case class from SpawnServerTestCase"""
1555

    
1556
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1557
    cls = type(name, (SpawnServerTestCase,), kwargs)
1558

    
1559
    # Patch extra parameters into test names by manipulating method docstrings
1560
    for (mname, m) in \
1561
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1562
        if hasattr(m, __doc__):
1563
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1564

    
1565
    # Make sure the class can be pickled, by listing it among
1566
    # the attributes of __main__. A PicklingError is raised otherwise.
1567

    
1568
    thismodule = sys.modules[__name__]
1569
    setattr(thismodule, name, cls)
1570
    return cls
1571

    
1572

    
1573
def _spawn_network_test_case(**kwargs):
1574
    """Construct a new unit test case class from NetworkTestCase"""
1575

    
1576
    name = "NetworkTestCase" + TEST_RUN_ID
1577
    cls = type(name, (NetworkTestCase,), kwargs)
1578

    
1579
    # Make sure the class can be pickled, by listing it among
1580
    # the attributes of __main__. A PicklingError is raised otherwise.
1581

    
1582
    thismodule = sys.modules[__name__]
1583
    setattr(thismodule, name, cls)
1584
    return cls
1585

    
1586

    
1587
# --------------------------------------------------------------------
1588
# Clean up servers/networks functions
1589
def cleanup_servers(timeout, query_interval, delete_stale=False):
1590

    
1591
    c = ComputeClient(API, TOKEN)
1592

    
1593
    servers = c.list_servers()
1594
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1595

    
1596
    if len(stale) == 0:
1597
        return
1598

    
1599
    # Show staled servers
1600
    print >>sys.stderr, yellow + \
1601
        "Found these stale servers from previous runs:" + \
1602
        normal
1603
    print >>sys.stderr, "    " + \
1604
        "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1605

    
1606
    # Delete staled servers
1607
    if delete_stale:
1608
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1609
        fail_tmout = time.time() + timeout
1610
        for s in stale:
1611
            c.delete_server(s["id"])
1612
        # Wait for all servers to be deleted
1613
        while True:
1614
            servers = c.list_servers()
1615
            stale = [s for s in servers
1616
                     if s["name"].startswith(SNF_TEST_PREFIX)]
1617
            if len(stale) == 0:
1618
                print >> sys.stderr, green + "    ...done" + normal
1619
                break
1620
            elif time.time() > fail_tmout:
1621
                print >> sys.stderr, red + \
1622
                    "Not all stale servers deleted. Action timed out." + \
1623
                    normal
1624
                sys.exit(1)
1625
            else:
1626
                time.sleep(query_interval)
1627
    else:
1628
        print >> sys.stderr, "Use --delete-stale to delete them."
1629

    
1630

    
1631
def cleanup_networks(action_timeout, query_interval, delete_stale=False):
1632

    
1633
    c = CycladesClient(API, TOKEN)
1634

    
1635
    networks = c.list_networks()
1636
    stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1637

    
1638
    if len(stale) == 0:
1639
        return
1640

    
1641
    # Show staled networks
1642
    print >> sys.stderr, yellow + \
1643
        "Found these stale networks from previous runs:" + \
1644
        normal
1645
    print "    " + \
1646
        "\n    ".join(["%s: %s" % (str(n["id"]), n["name"]) for n in stale])
1647

    
1648
    # Delete staled networks
1649
    if delete_stale:
1650
        print >> sys.stderr, "Deleting %d stale networks:" % len(stale)
1651
        fail_tmout = time.time() + action_timeout
1652
        for n in stale:
1653
            c.delete_network(n["id"])
1654
        # Wait for all networks to be deleted
1655
        while True:
1656
            networks = c.list_networks()
1657
            stale = [n for n in networks
1658
                     if n["name"].startswith(SNF_TEST_PREFIX)]
1659
            if len(stale) == 0:
1660
                print >> sys.stderr, green + "    ...done" + normal
1661
                break
1662
            elif time.time() > fail_tmout:
1663
                print >> sys.stderr, red + \
1664
                    "Not all stale networks deleted. Action timed out." + \
1665
                    normal
1666
                sys.exit(1)
1667
            else:
1668
                time.sleep(query_interval)
1669
    else:
1670
        print >> sys.stderr, "Use --delete-stale to delete them."
1671

    
1672

    
1673
# --------------------------------------------------------------------
1674
# Parse arguments functions
1675
def parse_comma(option, opt, value, parser):
1676
    tests = set(['all', 'auth', 'images', 'flavors',
1677
                 'servers', 'server_spawn', 'network_spawn'])
1678
    parse_input = value.split(',')
1679

    
1680
    if not (set(parse_input)).issubset(tests):
1681
        raise OptionValueError("The selected set of tests is invalid")
1682

    
1683
    setattr(parser.values, option.dest, value.split(','))
1684

    
1685

    
1686
def parse_arguments(args):
1687

    
1688
    kw = {}
1689
    kw["usage"] = "%prog [options]"
1690
    kw["description"] = \
1691
        "%prog runs a number of test scenarios on a " \
1692
        "Synnefo deployment."
1693

    
1694
    parser = OptionParser(**kw)
1695
    parser.disable_interspersed_args()
1696

    
1697
    parser.add_option("--api",
1698
                      action="store", type="string", dest="api",
1699
                      help="The API URI to use to reach the Synnefo API",
1700
                      default=DEFAULT_API)
1701
    parser.add_option("--plankton",
1702
                      action="store", type="string", dest="plankton",
1703
                      help="The API URI to use to reach the Plankton API",
1704
                      default=DEFAULT_PLANKTON)
1705
    parser.add_option("--plankton-user",
1706
                      action="store", type="string", dest="plankton_user",
1707
                      help="Owner of system images",
1708
                      default=DEFAULT_PLANKTON_USER)
1709
    parser.add_option("--token",
1710
                      action="store", type="string", dest="token",
1711
                      help="The token to use for authentication to the API")
1712
    parser.add_option("--nofailfast",
1713
                      action="store_true", dest="nofailfast",
1714
                      help="Do not fail immediately if one of the tests "
1715
                           "fails (EXPERIMENTAL)",
1716
                      default=False)
1717
    parser.add_option("--no-ipv6",
1718
                      action="store_true", dest="no_ipv6",
1719
                      help="Disables ipv6 related tests",
1720
                      default=False)
1721
    parser.add_option("--action-timeout",
1722
                      action="store", type="int", dest="action_timeout",
1723
                      metavar="TIMEOUT",
1724
                      help="Wait SECONDS seconds for a server action to "
1725
                           "complete, then the test is considered failed",
1726
                      default=100)
1727
    parser.add_option("--build-warning",
1728
                      action="store", type="int", dest="build_warning",
1729
                      metavar="TIMEOUT",
1730
                      help="Warn if TIMEOUT seconds have passed and a "
1731
                           "build operation is still pending",
1732
                      default=600)
1733
    parser.add_option("--build-fail",
1734
                      action="store", type="int", dest="build_fail",
1735
                      metavar="BUILD_TIMEOUT",
1736
                      help="Fail the test if TIMEOUT seconds have passed "
1737
                           "and a build operation is still incomplete",
1738
                      default=900)
1739
    parser.add_option("--query-interval",
1740
                      action="store", type="int", dest="query_interval",
1741
                      metavar="INTERVAL",
1742
                      help="Query server status when requests are pending "
1743
                           "every INTERVAL seconds",
1744
                      default=3)
1745
    parser.add_option("--fanout",
1746
                      action="store", type="int", dest="fanout",
1747
                      metavar="COUNT",
1748
                      help="Spawn up to COUNT child processes to execute "
1749
                           "in parallel, essentially have up to COUNT "
1750
                           "server build requests outstanding (EXPERIMENTAL)",
1751
                      default=1)
1752
    parser.add_option("--force-flavor",
1753
                      action="store", type="int", dest="force_flavorid",
1754
                      metavar="FLAVOR ID",
1755
                      help="Force all server creations to use the specified "
1756
                           "FLAVOR ID instead of a randomly chosen one, "
1757
                           "useful if disk space is scarce",
1758
                      default=None)
1759
    parser.add_option("--image-id",
1760
                      action="store", type="string", dest="force_imageid",
1761
                      metavar="IMAGE ID",
1762
                      help="Test the specified image id, use 'all' to test "
1763
                           "all available images (mandatory argument)",
1764
                      default=None)
1765
    parser.add_option("--show-stale",
1766
                      action="store_true", dest="show_stale",
1767
                      help="Show stale servers from previous runs, whose "
1768
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1769
                      default=False)
1770
    parser.add_option("--delete-stale",
1771
                      action="store_true", dest="delete_stale",
1772
                      help="Delete stale servers from previous runs, whose "
1773
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1774
                      default=False)
1775
    parser.add_option("--force-personality",
1776
                      action="store", type="string", dest="personality_path",
1777
                      help="Force a personality file injection.\
1778
                            File path required. ",
1779
                      default=None)
1780
    parser.add_option("--log-folder",
1781
                      action="store", type="string", dest="log_folder",
1782
                      help="Define the absolute path where the output \
1783
                            log is stored. ",
1784
                      default="/var/log/burnin/")
1785
    parser.add_option("--verbose", "-V",
1786
                      action="store_true", dest="verbose",
1787
                      help="Print detailed output about multiple "
1788
                           "processes spawning",
1789
                      default=False)
1790
    parser.add_option("--set-tests",
1791
                      action="callback",
1792
                      dest="tests",
1793
                      type="string",
1794
                      help='Set comma seperated tests for this run. \
1795
                            Available tests: auth, images, flavors, \
1796
                                             servers, server_spawn, \
1797
                                             network_spawn. \
1798
                            Default = all',
1799
                      default='all',
1800
                      callback=parse_comma)
1801

    
1802
    (opts, args) = parser.parse_args(args)
1803

    
1804
    # -----------------------
1805
    # Verify arguments
1806

    
1807
    # `delete_stale' implies `show_stale'
1808
    if opts.delete_stale:
1809
        opts.show_stale = True
1810

    
1811
    # `image-id' is mandatory
1812
    if not opts.show_stale:
1813
        if not opts.force_imageid:
1814
            print >>sys.stderr, red + \
1815
                "The --image-id argument is mandatory.\n" + \
1816
                normal
1817
            parser.print_help()
1818
            sys.exit(1)
1819
        if opts.force_imageid != 'all':
1820
            try:
1821
                opts.force_imageid = str(opts.force_imageid)
1822
            except ValueError:
1823
                print >>sys.stderr, red + \
1824
                    "Invalid value specified for" + \
1825
                    "--image-id. Use a valid id, or `all'." + \
1826
                    normal
1827
                sys.exit(1)
1828

    
1829
    # `token' is mandatory
1830
    if not opts.token:
1831
        print >>sys.stderr, red + \
1832
            "The --token argument is mandatory.\n" + \
1833
            normal
1834
        parser.print_help()
1835
        sys.exit(1)
1836

    
1837
    return (opts, args)
1838

    
1839

    
1840
# --------------------------------------------------------------------
1841
# Burnin main function
1842
def main():
1843
    """Assemble test cases into a test suite, and run it
1844

1845
    IMPORTANT: Tests have dependencies and have to be run in the specified
1846
    order inside a single test case. They communicate through attributes of the
1847
    corresponding TestCase class (shared fixtures). Distinct subclasses of
1848
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
1849
    test runner processes.
1850

1851
    """
1852

    
1853
    # Parse arguments using `optparse'
1854
    (opts, args) = parse_arguments(sys.argv[1:])
1855

    
1856
    # Some global variables
1857
    global API, TOKEN, PLANKTON, PLANKTON_USER, NO_IPV6, VERBOSE, NOFAILFAST
1858
    API = opts.api
1859
    TOKEN = opts.token
1860
    PLANKTON = opts.plankton
1861
    PLANKTON_USER = opts.plankton_user
1862
    NO_IPV6 = opts.no_ipv6
1863
    VERBOSE = opts.verbose
1864
    NOFAILFAST = opts.nofailfast
1865

    
1866
    # If `show_stale', cleanup stale servers
1867
    # from previous runs and exit
1868
    if opts.show_stale:
1869
        # We must clean the servers first
1870
        cleanup_servers(opts.action_timeout, opts.query_interval,
1871
                        delete_stale=opts.delete_stale)
1872
        cleanup_networks(opts.action_timeout, opts.query_interval,
1873
                         delete_stale=opts.delete_stale)
1874
        return 0
1875

    
1876
    # Initialize a kamaki instance, get flavors, images
1877
    c = ComputeClient(API, TOKEN)
1878
    DIMAGES = c.list_images(detail=True)
1879
    DFLAVORS = c.list_flavors(detail=True)
1880

    
1881
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
1882
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
1883
    #unittest.main(verbosity=2, catchbreak=True)
1884

    
1885
    # Get a list of images we are going to test
1886
    if opts.force_imageid == 'all':
1887
        test_images = DIMAGES
1888
    else:
1889
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1890

    
1891
    # Create output (logging) folder
1892
    if not os.path.exists(opts.log_folder):
1893
        os.mkdir(opts.log_folder)
1894
    test_folder = os.path.join(opts.log_folder, TEST_RUN_ID)
1895
    os.mkdir(test_folder)
1896

    
1897
    for image in test_images:
1898
        imageid = str(image["id"])
1899
        imagename = image["name"]
1900
        # Choose a flavor (given from user or random)
1901
        if opts.force_flavorid:
1902
            flavorid = opts.force_flavorid
1903
        else:
1904
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
1905
        # Personality dictionary for file injection test
1906
        if opts.personality_path is not None:
1907
            f = open(opts.personality_path)
1908
            content = b64encode(f.read())
1909
            personality = []
1910
            st = os.stat(opts.personality_path)
1911
            personality.append({
1912
                'path': '/root/test_inj_file',
1913
                'owner': 'root',
1914
                'group': 'root',
1915
                'mode': 0x7777 & st.st_mode,
1916
                'contents': content})
1917
        else:
1918
            personality = None
1919
        # Give a name to our test servers
1920
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
1921
        is_windows = imagename.lower().find("windows") >= 0
1922

    
1923
        # Create Server TestCases
1924
        ServerTestCase = _spawn_server_test_case(
1925
            imageid=imageid,
1926
            flavorid=flavorid,
1927
            imagename=imagename,
1928
            personality=personality,
1929
            servername=servername,
1930
            is_windows=is_windows,
1931
            action_timeout=opts.action_timeout,
1932
            build_warning=opts.build_warning,
1933
            build_fail=opts.build_fail,
1934
            query_interval=opts.query_interval)
1935
        # Create Network TestCases
1936
        NetworkTestCase = _spawn_network_test_case(
1937
            action_timeout=opts.action_timeout,
1938
            imageid=imageid,
1939
            flavorid=flavorid,
1940
            imagename=imagename,
1941
            query_interval=opts.query_interval)
1942

    
1943
        # Choose the tests we are going to run
1944
        test_dict = {'auth': UnauthorizedTestCase,
1945
                     'images': ImagesTestCase,
1946
                     'flavors': FlavorsTestCase,
1947
                     'servers': ServersTestCase,
1948
                     'server_spawn': ServerTestCase,
1949
                     'network_spawn': NetworkTestCase}
1950
        seq_cases = []
1951
        if 'all' in opts.tests:
1952
            seq_cases = [UnauthorizedTestCase, ImagesTestCase, FlavorsTestCase,
1953
                         ServersTestCase, ServerTestCase, NetworkTestCase]
1954
        else:
1955
            for test in opts.tests:
1956
                seq_cases.append(test_dict[test])
1957

    
1958
        # Folder for each image
1959
        image_folder = os.path.join(test_folder, imageid)
1960
        os.mkdir(image_folder)
1961

    
1962
        # Run each test
1963
        if opts.fanout > 1:
1964
            _run_cases_in_parallel(seq_cases, opts.fanout, image_folder)
1965
        else:
1966
            _run_cases_in_series(seq_cases, image_folder)
1967

    
1968

    
1969
# --------------------------------------------------------------------
1970
# Call main
1971
if __name__ == "__main__":
1972
    sys.exit(main())