Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin.py @ 3e4c5c32

History | View | Annotate | Download (69.4 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 fabric.api import *
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
class BurninTestResult(unittest.TextTestResult):
75
    def addSuccess(self, test):
76
        super(BurninTestResult, self).addSuccess(test)
77
        if self.showAll:
78
            if hasattr(test, 'result_dict'):
79
                run_details = test.result_dict
80

    
81
                self.stream.write("\n")
82
                for i in run_details:
83
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
84
                self.stream.write("\n")
85

    
86
        elif self.dots:
87
            self.stream.write('.')
88
            self.stream.flush() 
89
            
90
    def addError(self, test, err):
91
        super(BurninTestResult, self).addError(test, err)
92
        if self.showAll:
93
            self.stream.writeln("ERROR")
94
            if hasattr(test, 'result_dict'):
95
                run_details = test.result_dict
96

    
97
                self.stream.write("\n")
98
                for i in run_details:
99
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
100
                self.stream.write("\n")
101

    
102
        elif self.dots:
103
            self.stream.write('E')
104
            self.stream.flush()
105

    
106
    def addFailure(self, test, err):
107
        super(BurninTestResult, self).addFailure(test, err)
108
        if self.showAll:
109
            self.stream.writeln("FAIL")
110
            if hasattr(test, 'result_dict'):
111
                run_details = test.result_dict
112

    
113
                self.stream.write("\n")
114
                for i in run_details:
115
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
116
                self.stream.write("\n")
117

    
118
        elif self.dots:
119
            self.stream.write('F')
120
            self.stream.flush()
121

    
122

    
123

    
124
API = None
125
TOKEN = None
126
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
127
DEFAULT_PLANKTON = "https://cyclades.okeanos.grnet.gr/plankton"
128
DEFAULT_PLANKTON_USER = "images@okeanos.grnet.gr"
129

    
130
# A unique id identifying this test run
131
TEST_RUN_ID = datetime.datetime.strftime(datetime.datetime.now(),
132
                                         "%Y%m%d%H%M%S")
133
SNF_TEST_PREFIX = "snf-test-"
134

    
135
red = '\x1b[31m'
136
yellow = '\x1b[33m'
137
green = '\x1b[32m'
138
normal = '\x1b[0m'
139

    
140
class burninFormatter(logging.Formatter):
141

    
142
    err_fmt = red + "ERROR: %(msg)s" + normal
143
    dbg_fmt = green + "* %(msg)s" + normal
144
    info_fmt = "%(msg)s"
145

    
146
    def __init__(self, fmt="%(levelno)s: %(msg)s"):
147
        logging.Formatter.__init__(self, fmt)
148

    
149
    def format(self, record):
150

    
151
        format_orig = self._fmt
152

    
153
        # Replace the original format with one customized by logging level
154
        if record.levelno == 10:    # DEBUG
155
            self._fmt = burninFormatter.dbg_fmt
156

    
157
        elif record.levelno == 20:  # INFO
158
            self._fmt = burninFormatter.info_fmt
159

    
160
        elif record.levelno == 40:  # ERROR
161
            self._fmt = burninFormatter.err_fmt
162

    
163
        result = logging.Formatter.format(self, record)
164
        self._fmt = format_orig
165

    
166
        return result
167

    
168
log = logging.getLogger("burnin")
169
log.setLevel(logging.DEBUG)
170
handler = logging.StreamHandler()
171
handler.setFormatter(burninFormatter())
172
log.addHandler(handler)
173

    
174

    
175
class UnauthorizedTestCase(unittest.TestCase):
176

    
177
    @classmethod
178
    def setUpClass(cls):
179
        cls.result_dict = dict()
180

    
181
    def test_unauthorized_access(self):
182
        """Test access without a valid token fails"""
183
        log.info("Authentication test")
184

    
185
        falseToken = '12345'
186
        c = ComputeClient(API, falseToken)
187

    
188
        with self.assertRaises(ClientError) as cm:
189
            c.list_servers()
190
            self.assertEqual(cm.exception.status, 401)
191

    
192

    
193
class ImagesTestCase(unittest.TestCase):
194
    """Test image lists for consistency"""
195
    @classmethod
196
    def setUpClass(cls):
197
        """Initialize kamaki, get (detailed) list of images"""
198
        log.info("Getting simple and detailed list of images")
199

    
200
        cls.client = ComputeClient(API, TOKEN)
201
        cls.plankton = ImageClient(PLANKTON, TOKEN)
202
        cls.images = cls.plankton.list_public()
203
        cls.dimages = cls.plankton.list_public(detail=True)
204
        cls.result_dict = dict()
205

    
206
    def test_001_list_images(self):
207
        """Test image list actually returns images"""
208
        self.assertGreater(len(self.images), 0)
209

    
210
    def test_002_list_images_detailed(self):
211
        """Test detailed image list is the same length as list"""
212
        self.assertEqual(len(self.dimages), len(self.images))
213

    
214
    def test_003_same_image_names(self):
215
        """Test detailed and simple image list contain same names"""
216
        names = sorted(map(lambda x: x["name"], self.images))
217
        dnames = sorted(map(lambda x: x["name"], self.dimages))
218
        self.assertEqual(names, dnames)
219

    
220
    def test_004_unique_image_names(self):
221
        """Test system images have unique names"""
222
        sys_images = filter(lambda x: x['owner'] == PLANKTON_USER,
223
                            self.dimages)
224
        names = sorted(map(lambda x: x["name"], sys_images))
225
        self.assertEqual(sorted(list(set(names))), names)
226

    
227
    def test_005_image_metadata(self):
228
        """Test every image has specific metadata defined"""
229
        keys = frozenset(["osfamily", "root_partition"])
230
        details = self.client.list_images(detail=True)
231
        for i in details:
232
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
233

    
234

    
235
class FlavorsTestCase(unittest.TestCase):
236
    """Test flavor lists for consistency"""
237
    @classmethod
238
    def setUpClass(cls):
239
        """Initialize kamaki, get (detailed) list of flavors"""
240
        log.info("Getting simple and detailed list of flavors")
241

    
242
        cls.client = ComputeClient(API, TOKEN)
243
        cls.flavors = cls.client.list_flavors()
244
        cls.dflavors = cls.client.list_flavors(detail=True)
245
        cls.result_dict = dict()
246

    
247
    def test_001_list_flavors(self):
248
        """Test flavor list actually returns flavors"""
249
        self.assertGreater(len(self.flavors), 0)
250

    
251
    def test_002_list_flavors_detailed(self):
252
        """Test detailed flavor list is the same length as list"""
253
        self.assertEquals(len(self.dflavors), len(self.flavors))
254

    
255
    def test_003_same_flavor_names(self):
256
        """Test detailed and simple flavor list contain same names"""
257
        names = sorted(map(lambda x: x["name"], self.flavors))
258
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
259
        self.assertEqual(names, dnames)
260

    
261
    def test_004_unique_flavor_names(self):
262
        """Test flavors have unique names"""
263
        names = sorted(map(lambda x: x["name"], self.flavors))
264
        self.assertEqual(sorted(list(set(names))), names)
265

    
266
    def test_005_well_formed_flavor_names(self):
267
        """Test flavors have names of the form CxxRyyDzz
268

269
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
270

271
        """
272
        for f in self.dflavors:
273
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
274
                             f["name"],
275
                             "Flavor %s does not match its specs." % f["name"])
276

    
277

    
278
class ServersTestCase(unittest.TestCase):
279
    """Test server lists for consistency"""
280
    @classmethod
281
    def setUpClass(cls):
282
        """Initialize kamaki, get (detailed) list of servers"""
283
        log.info("Getting simple and detailed list of servers")
284

    
285
        cls.client = ComputeClient(API, TOKEN)
286
        cls.servers = cls.client.list_servers()
287
        cls.dservers = cls.client.list_servers(detail=True)
288
        cls.result_dict = dict()
289

    
290
    # def test_001_list_servers(self):
291
    #     """Test server list actually returns servers"""
292
    #     self.assertGreater(len(self.servers), 0)
293

    
294
    def test_002_list_servers_detailed(self):
295
        """Test detailed server list is the same length as list"""
296
        self.assertEqual(len(self.dservers), len(self.servers))
297

    
298
    def test_003_same_server_names(self):
299
        """Test detailed and simple flavor list contain same names"""
300
        names = sorted(map(lambda x: x["name"], self.servers))
301
        dnames = sorted(map(lambda x: x["name"], self.dservers))
302
        self.assertEqual(names, dnames)
303

    
304

    
305
# This class gets replicated into actual TestCases dynamically
306
class SpawnServerTestCase(unittest.TestCase):
307
    """Test scenario for server of the specified image"""
308

    
309
    @classmethod
310
    def setUpClass(cls):
311
        """Initialize a kamaki instance"""
312
        log.info("Spawning server for image `%s'" %cls.imagename)
313
        cls.client = ComputeClient(API, TOKEN)
314
        cls.cyclades = CycladesClient(API, TOKEN)
315
        cls.result_dict = dict()
316

    
317
    def _get_ipv4(self, server):
318
        """Get the public IPv4 of a server from the detailed server info"""
319

    
320
        nics = server["attachments"]["values"]
321

    
322
        for nic in nics:
323
            net_id = nic["network_id"]
324
            if self.cyclades.get_network_details(net_id)["public"] == True:
325
                public_addrs = nic["ipv4"]
326
        
327
        self.assertTrue(public_addrs != None)
328

    
329
        return public_addrs
330

    
331
    def _get_ipv6(self, server):
332
        """Get the public IPv6 of a server from the detailed server info"""
333

    
334
        nics = server["attachments"]["values"]
335

    
336
        for nic in nics:
337
            net_id = nic["network_id"]
338
            if self.cyclades.get_network_details(net_id)["public"] == True:
339
                public_addrs = nic["ipv6"]
340
        
341
        self.assertTrue(public_addrs != None)
342

    
343
        return public_addrs
344

    
345

    
346
    def _connect_loginname(self, os):
347
        """Return the login name for connections based on the server OS"""
348
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
349
            return "user"
350
        elif os in ("windows", "windows_alpha1"):
351
            return "Administrator"
352
        else:
353
            return "root"
354

    
355
    def _verify_server_status(self, current_status, new_status):
356
        """Verify a server has switched to a specified status"""
357
        server = self.client.get_server_details(self.serverid)
358
        if server["status"] not in (current_status, new_status):
359
            return None  # Do not raise exception, return so the test fails
360
        self.assertEquals(server["status"], new_status)
361

    
362
    def _get_connected_tcp_socket(self, family, host, port):
363
        """Get a connected socket from the specified family to host:port"""
364
        sock = None
365
        for res in \
366
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
367
                               socket.AI_PASSIVE):
368
            af, socktype, proto, canonname, sa = res
369
            try:
370
                sock = socket.socket(af, socktype, proto)
371
            except socket.error as msg:
372
                sock = None
373
                continue
374
            try:
375
                sock.connect(sa)
376
            except socket.error as msg:
377
                sock.close()
378
                sock = None
379
                continue
380
        self.assertIsNotNone(sock)
381
        return sock
382

    
383
    def _ping_once(self, ipv6, ip):
384
        """Test server responds to a single IPv4 or IPv6 ping"""
385
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
386
        ping = subprocess.Popen(cmd, shell=True,
387
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
388
        (stdout, stderr) = ping.communicate()
389
        ret = ping.wait()
390
        self.assertEquals(ret, 0)
391

    
392
    def _get_hostname_over_ssh(self, hostip, username, password):
393
        ssh = paramiko.SSHClient()
394
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
395
        try:
396
            ssh.connect(hostip, username=username, password=password)
397
        except socket.error:
398
            raise AssertionError
399
        stdin, stdout, stderr = ssh.exec_command("hostname")
400
        lines = stdout.readlines()
401
        self.assertEqual(len(lines), 1)
402
        return lines[0]
403

    
404
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
405
                                   opmsg, callable, *args, **kwargs):
406
        if warn_timeout == fail_timeout:
407
            warn_timeout = fail_timeout + 1
408
        warn_tmout = time.time() + warn_timeout
409
        fail_tmout = time.time() + fail_timeout
410
        while True:
411
            self.assertLess(time.time(), fail_tmout,
412
                            "operation `%s' timed out" % opmsg)
413
            if time.time() > warn_tmout:
414
                log.warning("Server %d: `%s' operation `%s' not done yet",
415
                            self.serverid, self.servername, opmsg)
416
            try:
417
                log.info("%s... " % opmsg)
418
                return callable(*args, **kwargs)
419
            except AssertionError:
420
                pass
421
            time.sleep(self.query_interval)
422

    
423
    def _insist_on_tcp_connection(self, family, host, port):
424
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
425
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
426
        msg = "connect over %s to %s:%s" % \
427
              (familystr.get(family, "Unknown"), host, port)
428
        sock = self._try_until_timeout_expires(
429
                self.action_timeout, self.action_timeout,
430
                msg, self._get_connected_tcp_socket,
431
                family, host, port)
432
        return sock
433

    
434
    def _insist_on_status_transition(self, current_status, new_status,
435
                                    fail_timeout, warn_timeout=None):
436
        msg = "Server %d: `%s', waiting for %s -> %s" % \
437
              (self.serverid, self.servername, current_status, new_status)
438
        if warn_timeout is None:
439
            warn_timeout = fail_timeout
440
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
441
                                        msg, self._verify_server_status,
442
                                        current_status, new_status)
443
        # Ensure the status is actually the expected one
444
        server = self.client.get_server_details(self.serverid)
445
        self.assertEquals(server["status"], new_status)
446

    
447
    def _insist_on_ssh_hostname(self, hostip, username, password):
448
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
449
        hostname = self._try_until_timeout_expires(
450
                self.action_timeout, self.action_timeout,
451
                msg, self._get_hostname_over_ssh,
452
                hostip, username, password)
453

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

    
457
    def _check_file_through_ssh(self, hostip, username, password,
458
                                remotepath, content):
459
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
460
            (hostip, username, password)
461
        log.info(msg)
462
        try:
463
            ssh = paramiko.SSHClient()
464
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
465
            ssh.connect(hostip, username=username, password=password)
466
        except socket.error:
467
            raise AssertionError
468

    
469
        transport = paramiko.Transport((hostip, 22))
470
        transport.connect(username=username, password=password)
471

    
472
        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
473
        sftp = paramiko.SFTPClient.from_transport(transport)
474
        sftp.get(remotepath, localpath)
475
        sftp.close()
476
        transport.close()
477

    
478
        f = open(localpath)
479
        remote_content = b64encode(f.read())
480

    
481
        # Check if files are the same
482
        return (remote_content == content)
483

    
484
    def _skipIf(self, condition, msg):
485
        if condition:
486
            self.skipTest(msg)
487

    
488
    def test_001_submit_create_server(self):
489
        """Test submit create server request"""
490

    
491
        log.info("Submit new server request")
492
        server = self.client.create_server(self.servername, self.flavorid,
493
                                           self.imageid, self.personality)
494

    
495
        log.info("Server id: " + str(server["id"]))
496
        log.info("Server password: " + server["adminPass"])
497
        self.assertEqual(server["name"], self.servername)
498
        self.assertEqual(server["flavorRef"], self.flavorid)
499
        self.assertEqual(server["imageRef"], self.imageid)
500
        self.assertEqual(server["status"], "BUILD")
501

    
502
        # Update class attributes to reflect data on building server
503
        cls = type(self)
504
        cls.serverid = server["id"]
505
        cls.username = None
506
        cls.passwd = server["adminPass"]
507

    
508
        self.result_dict["Server ID"] = str(server["id"])
509
        self.result_dict["Password"] = str(server["adminPass"])
510

    
511
    def test_002a_server_is_building_in_list(self):
512
        """Test server is in BUILD state, in server list"""
513
        log.info("Server in BUILD state in server list")
514

    
515
        self.result_dict.clear()
516

    
517
        servers = self.client.list_servers(detail=True)
518
        servers = filter(lambda x: x["name"] == self.servername, servers)
519

    
520
        server = servers[0]
521
        self.assertEqual(server["name"], self.servername)
522
        self.assertEqual(server["flavorRef"], self.flavorid)
523
        self.assertEqual(server["imageRef"], self.imageid)
524
        self.assertEqual(server["status"], "BUILD")
525

    
526
    def test_002b_server_is_building_in_details(self):
527
        """Test server is in BUILD state, in details"""
528

    
529
        log.info("Server in BUILD state in details")
530

    
531
        server = self.client.get_server_details(self.serverid)
532
        self.assertEqual(server["name"], self.servername)
533
        self.assertEqual(server["flavorRef"], self.flavorid)
534
        self.assertEqual(server["imageRef"], self.imageid)
535
        self.assertEqual(server["status"], "BUILD")
536

    
537
    def test_002c_set_server_metadata(self):
538

    
539
        log.info("Creating server metadata")
540

    
541
        image = self.client.get_image_details(self.imageid)
542
        os = image["metadata"]["values"]["os"]
543
        users = image["metadata"]["values"].get("users", None)
544
        self.client.update_server_metadata(self.serverid, OS=os)
545

    
546
        userlist = users.split()
547

    
548
        # Determine the username to use for future connections
549
        # to this host
550
        cls = type(self)
551

    
552
        if "root" in userlist:
553
            cls.username = "root"
554
        elif users == None:
555
            cls.username = self._connect_loginname(os)
556
        else:
557
            cls.username = choice(userlist)
558

    
559
        self.assertIsNotNone(cls.username)
560

    
561
    def test_002d_verify_server_metadata(self):
562
        """Test server metadata keys are set based on image metadata"""
563

    
564
        log.info("Verifying image metadata")
565

    
566
        servermeta = self.client.get_server_metadata(self.serverid)
567
        imagemeta = self.client.get_image_metadata(self.imageid)
568

    
569
        self.assertEqual(servermeta["OS"], imagemeta["os"])
570

    
571
    def test_003_server_becomes_active(self):
572
        """Test server becomes ACTIVE"""
573

    
574
        log.info("Waiting for server to become ACTIVE")
575

    
576
        self._insist_on_status_transition("BUILD", "ACTIVE",
577
                                         self.build_fail, self.build_warning)
578

    
579
    def test_003a_get_server_oob_console(self):
580
        """Test getting OOB server console over VNC
581

582
        Implementation of RFB protocol follows
583
        http://www.realvnc.com/docs/rfbproto.pdf.
584

585
        """
586
        console = self.cyclades.get_server_console(self.serverid)
587
        self.assertEquals(console['type'], "vnc")
588
        sock = self._insist_on_tcp_connection(socket.AF_INET,
589
                                        console["host"], console["port"])
590

    
591
        # Step 1. ProtocolVersion message (par. 6.1.1)
592
        version = sock.recv(1024)
593
        self.assertEquals(version, 'RFB 003.008\n')
594
        sock.send(version)
595

    
596
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
597
        sec = sock.recv(1024)
598
        self.assertEquals(list(sec), ['\x01', '\x02'])
599

    
600
        # Step 3. Request VNC Authentication (par 6.1.2)
601
        sock.send('\x02')
602

    
603
        # Step 4. Receive Challenge (par 6.2.2)
604
        challenge = sock.recv(1024)
605
        self.assertEquals(len(challenge), 16)
606

    
607
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
608
        response = d3des_generate_response(
609
            (console["password"] + '\0' * 8)[:8], challenge)
610
        sock.send(response)
611

    
612
        # Step 6. SecurityResult (par 6.1.3)
613
        result = sock.recv(4)
614
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
615
        sock.close()
616

    
617
    def test_004_server_has_ipv4(self):
618
        """Test active server has a valid IPv4 address"""
619

    
620
        log.info("Validate server's IPv4")
621

    
622

    
623
        server = self.client.get_server_details(self.serverid)
624
        ipv4 = self._get_ipv4(server)
625

    
626
        self.result_dict.clear()
627
        self.result_dict["IPv4"] = str(ipv4)
628

    
629
        self.assertEquals(IP(ipv4).version(), 4)
630

    
631
    def test_005_server_has_ipv6(self):
632
        """Test active server has a valid IPv6 address"""
633
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
634

    
635
        log.info("Validate server's IPv6")
636

    
637
        server = self.client.get_server_details(self.serverid)
638
        ipv6 = self._get_ipv6(server)
639

    
640
        self.result_dict.clear()
641
        self.result_dict["IPv6"] = str(ipv6)
642

    
643
        self.assertEquals(IP(ipv6).version(), 6)
644

    
645
    def test_006_server_responds_to_ping_IPv4(self):
646
        """Test server responds to ping on IPv4 address"""
647

    
648
        log.info("Testing if server responds to pings in IPv4")
649
        self.result_dict.clear()
650

    
651
        server = self.client.get_server_details(self.serverid)
652
        ip = self._get_ipv4(server)
653
        self._try_until_timeout_expires(self.action_timeout,
654
                                        self.action_timeout,
655
                                        "PING IPv4 to %s" % ip,
656
                                        self._ping_once,
657
                                        False, ip)
658

    
659
    def test_007_server_responds_to_ping_IPv6(self):
660
        """Test server responds to ping on IPv6 address"""
661
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
662
        log.info("Testing if server responds to pings in IPv6")
663

    
664
        server = self.client.get_server_details(self.serverid)
665
        ip = self._get_ipv6(server)
666
        self._try_until_timeout_expires(self.action_timeout,
667
                                        self.action_timeout,
668
                                        "PING IPv6 to %s" % ip,
669
                                        self._ping_once,
670
                                        True, ip)
671

    
672
    def test_008_submit_shutdown_request(self):
673
        """Test submit request to shutdown server"""
674

    
675
        log.info("Shutting down server")
676

    
677
        self.cyclades.shutdown_server(self.serverid)
678

    
679
    def test_009_server_becomes_stopped(self):
680
        """Test server becomes STOPPED"""
681

    
682
        log.info("Waiting until server becomes STOPPED")
683
        self._insist_on_status_transition("ACTIVE", "STOPPED",
684
                                         self.action_timeout,
685
                                         self.action_timeout)
686

    
687
    def test_010_submit_start_request(self):
688
        """Test submit start server request"""
689

    
690
        log.info("Starting server")
691

    
692
        self.cyclades.start_server(self.serverid)
693

    
694
    def test_011_server_becomes_active(self):
695
        """Test server becomes ACTIVE again"""
696

    
697
        log.info("Waiting until server becomes ACTIVE")
698
        self._insist_on_status_transition("STOPPED", "ACTIVE",
699
                                         self.action_timeout,
700
                                         self.action_timeout)
701

    
702
    def test_011a_server_responds_to_ping_IPv4(self):
703
        """Test server OS is actually up and running again"""
704

    
705
        log.info("Testing if server is actually up and running")
706

    
707
        self.test_006_server_responds_to_ping_IPv4()
708

    
709
    def test_012_ssh_to_server_IPv4(self):
710
        """Test SSH to server public IPv4 works, verify hostname"""
711

    
712
        self._skipIf(self.is_windows, "only valid for Linux servers")
713
        server = self.client.get_server_details(self.serverid)
714
        self._insist_on_ssh_hostname(self._get_ipv4(server),
715
                                     self.username, self.passwd)
716

    
717
    def test_013_ssh_to_server_IPv6(self):
718
        """Test SSH to server public IPv6 works, verify hostname"""
719
        self._skipIf(self.is_windows, "only valid for Linux servers")
720
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
721

    
722
        server = self.client.get_server_details(self.serverid)
723
        self._insist_on_ssh_hostname(self._get_ipv6(server),
724
                                     self.username, self.passwd)
725

    
726
    def test_014_rdp_to_server_IPv4(self):
727
        "Test RDP connection to server public IPv4 works"""
728
        self._skipIf(not self.is_windows, "only valid for Windows servers")
729
        server = self.client.get_server_details(self.serverid)
730
        ipv4 = self._get_ipv4(server)
731
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
732

    
733
        # No actual RDP processing done. We assume the RDP server is there
734
        # if the connection to the RDP port is successful.
735
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
736
        sock.close()
737

    
738
    def test_015_rdp_to_server_IPv6(self):
739
        "Test RDP connection to server public IPv6 works"""
740
        self._skipIf(not self.is_windows, "only valid for Windows servers")
741
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
742

    
743
        server = self.client.get_server_details(self.serverid)
744
        ipv6 = self._get_ipv6(server)
745
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
746

    
747
        # No actual RDP processing done. We assume the RDP server is there
748
        # if the connection to the RDP port is successful.
749
        sock.close()
750

    
751
    def test_016_personality_is_enforced(self):
752
        """Test file injection for personality enforcement"""
753
        self._skipIf(self.is_windows, "only implemented for Linux servers")
754
        self._skipIf(self.personality == None, "No personality file selected")
755

    
756
        log.info("Trying to inject file for personality enforcement")
757

    
758
        server = self.client.get_server_details(self.serverid)
759

    
760
        for inj_file in self.personality:
761
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
762
                                                       inj_file['owner'],
763
                                                       self.passwd,
764
                                                       inj_file['path'],
765
                                                       inj_file['contents'])
766
            self.assertTrue(equal_files)
767

    
768
    def test_017_submit_delete_request(self):
769
        """Test submit request to delete server"""
770

    
771
        log.info("Deleting server")
772

    
773
        self.client.delete_server(self.serverid)
774

    
775
    def test_018_server_becomes_deleted(self):
776
        """Test server becomes DELETED"""
777

    
778
        log.info("Testing if server becomes DELETED")
779

    
780
        self._insist_on_status_transition("ACTIVE", "DELETED",
781
                                         self.action_timeout,
782
                                         self.action_timeout)
783

    
784
    def test_019_server_no_longer_in_server_list(self):
785
        """Test server is no longer in server list"""
786

    
787
        log.info("Test if server is no longer listed")
788

    
789
        servers = self.client.list_servers()
790
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
791

    
792

    
793
class NetworkTestCase(unittest.TestCase):
794
    """ Testing networking in cyclades """
795

    
796
    @classmethod
797
    def setUpClass(cls):
798
        "Initialize kamaki, get list of current networks"
799

    
800
        cls.client = CycladesClient(API, TOKEN)
801
        cls.compute = ComputeClient(API, TOKEN)
802

    
803
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
804
                                          TEST_RUN_ID,
805
                                          cls.imagename)
806

    
807
        #Dictionary initialization for the vms credentials
808
        cls.serverid = dict()
809
        cls.username = dict()
810
        cls.password = dict()
811
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
812

    
813
        cls.result_dict = dict()
814

    
815
    def _skipIf(self, condition, msg):
816
        if condition:
817
            self.skipTest(msg)
818

    
819
    def _get_ipv4(self, server):
820
        """Get the public IPv4 of a server from the detailed server info"""
821

    
822
        nics = server["attachments"]["values"]
823

    
824
        for nic in nics:
825
            net_id = nic["network_id"]
826
            if self.client.get_network_details(net_id)["public"] == True:
827
                public_addrs = nic["ipv4"]
828
        
829
        self.assertTrue(public_addrs != None)
830

    
831
        return public_addrs
832

    
833

    
834
    def _connect_loginname(self, os):
835
        """Return the login name for connections based on the server OS"""
836
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
837
            return "user"
838
        elif os in ("windows", "windows_alpha1"):
839
            return "Administrator"
840
        else:
841
            return "root"
842

    
843
    def _ping_once(self, ip):
844

    
845
        """Test server responds to a single IPv4 or IPv6 ping"""
846
        cmd = "ping -c 2 -w 3 %s" % (ip)
847
        ping = subprocess.Popen(cmd, shell=True,
848
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
849
        (stdout, stderr) = ping.communicate()
850
        ret = ping.wait()
851

    
852
        return (ret == 0)
853

    
854
    def test_00001a_submit_create_server_A(self):
855
        """Test submit create server request"""
856

    
857
        log.info("Creating test server A")
858

    
859
        serverA = self.client.create_server(self.servername, self.flavorid,
860
                                            self.imageid, personality=None)
861

    
862
        self.assertEqual(serverA["name"], self.servername)
863
        self.assertEqual(serverA["flavorRef"], self.flavorid)
864
        self.assertEqual(serverA["imageRef"], self.imageid)
865
        self.assertEqual(serverA["status"], "BUILD")
866

    
867
        # Update class attributes to reflect data on building server
868
        self.serverid['A'] = serverA["id"]
869
        self.username['A'] = None
870
        self.password['A'] = serverA["adminPass"]
871

    
872
        log.info("Server A id:" + str(serverA["id"]))
873
        log.info("Server password " + (self.password['A']))
874

    
875
        self.result_dict["Server A ID"] = str(serverA["id"])
876
        self.result_dict["Server A password"] = serverA["adminPass"]
877
        
878
    def test_00001b_serverA_becomes_active(self):
879
        """Test server becomes ACTIVE"""
880

    
881
        log.info("Waiting until test server A becomes ACTIVE")
882
        self.result_dict.clear()
883

    
884
        fail_tmout = time.time() + self.action_timeout
885
        while True:
886
            d = self.client.get_server_details(self.serverid['A'])
887
            status = d['status']
888
            if status == 'ACTIVE':
889
                active = True
890
                break
891
            elif time.time() > fail_tmout:
892
                self.assertLess(time.time(), fail_tmout)
893
            else:
894
                time.sleep(self.query_interval)
895

    
896
        self.assertTrue(active)
897

    
898
    def test_00002a_submit_create_server_B(self):
899
        """Test submit create server request"""
900

    
901
        log.info("Creating test server B")
902
        
903
        serverB = self.client.create_server(self.servername, self.flavorid,
904
                                            self.imageid, personality=None)
905

    
906
        self.assertEqual(serverB["name"], self.servername)
907
        self.assertEqual(serverB["flavorRef"], self.flavorid)
908
        self.assertEqual(serverB["imageRef"], self.imageid)
909
        self.assertEqual(serverB["status"], "BUILD")
910

    
911
        # Update class attributes to reflect data on building server
912
        self.serverid['B'] = serverB["id"]
913
        self.username['B'] = None
914
        self.password['B'] = serverB["adminPass"]
915

    
916
        log.info("Server B id: " + str(serverB["id"]))
917
        log.info("Password " + (self.password['B']))
918

    
919
        self.result_dict.clear()
920
        self.result_dict["Server B ID"] = str(serverB["id"])
921
        self.result_dict["Server B password"] = serverB["adminPass"]
922

    
923
    def test_00002b_serverB_becomes_active(self):
924
        """Test server becomes ACTIVE"""
925

    
926
        log.info("Waiting until test server B becomes ACTIVE")
927
        self.result_dict.clear()
928

    
929
        fail_tmout = time.time() + self.action_timeout
930
        while True:
931
            d = self.client.get_server_details(self.serverid['B'])
932
            status = d['status']
933
            if status == 'ACTIVE':
934
                active = True
935
                break
936
            elif time.time() > fail_tmout:
937
                self.assertLess(time.time(), fail_tmout)
938
            else:
939
                time.sleep(self.query_interval)
940

    
941
        self.assertTrue(active)
942

    
943
    def test_001_create_network(self):
944
        """Test submit create network request"""
945

    
946
        log.info("Submit new network request")
947
        self.result_dict.clear()
948
                
949
        name = SNF_TEST_PREFIX + TEST_RUN_ID
950
        previous_num = len(self.client.list_networks())
951
        network = self.client.create_network(name,cidr='10.0.0.1/28')
952

    
953
        #Test if right name is assigned
954
        self.assertEqual(network['name'], name)
955

    
956
        # Update class attributes
957
        cls = type(self)
958
        cls.networkid = network['id']
959
        networks = self.client.list_networks()
960

    
961
        fail_tmout = time.time() + self.action_timeout
962

    
963
        #Test if new network is created
964
        while True:
965
            d = self.client.get_network_details(network['id'])
966
            if d['status'] == 'ACTIVE':
967
                connected = True
968
                break
969
            elif time.time() > fail_tmout:
970
                self.assertLess(time.time(), fail_tmout)
971
            else:
972
                log.info("Waiting for network to become ACTIVE")
973
                time.sleep(self.query_interval)
974

    
975
        self.assertTrue(connected)
976

    
977
        self.result_dict["Private network ID"] = str(network['id'])
978

    
979
    def test_002_connect_to_network(self):
980
        """Test connect VMs to network"""
981

    
982
        log.info("Connect VMs to private network")
983
        self.result_dict.clear()
984

    
985
        self.client.connect_server(self.serverid['A'], self.networkid)
986
        self.client.connect_server(self.serverid['B'], self.networkid)
987

    
988
        #Insist on connecting until action timeout
989
        fail_tmout = time.time() + self.action_timeout
990

    
991
        while True:
992

    
993
            netsA = [x['network_id'] for x in self.client.get_server_details(self.serverid['A'])['attachments']['values']]
994
            netsB = [x['network_id'] for x in self.client.get_server_details(self.serverid['B'])['attachments']['values']]
995

    
996
            if (self.networkid in netsA) and (self.networkid in netsB):
997
                conn_exists = True
998
                break
999
            elif time.time() > fail_tmout:
1000
                self.assertLess(time.time(), fail_tmout)
1001
            else:
1002
                time.sleep(self.query_interval)
1003
                
1004
        #Adding private IPs to class attributes
1005
        cls = type(self)
1006
        cls.priv_ip = dict()
1007

    
1008
        nicsA = self.client.get_server_details(self.serverid['A'])['attachments']['values']
1009
        nicsB = self.client.get_server_details(self.serverid['B'])['attachments']['values']
1010

    
1011
        if conn_exists:
1012
            for nic in nicsA:
1013
                if nic["network_id"] == self.networkid:
1014
                    cls.priv_ip["A"] = nic["ipv4"]
1015

    
1016
            for nic in nicsB:
1017
                if nic["network_id"] == self.networkid:
1018
                    cls.priv_ip["B"] = nic["ipv4"]
1019

    
1020
        self.assertTrue(conn_exists)
1021

    
1022
    def test_002a_reboot(self):
1023
        """Rebooting server A"""
1024

    
1025
        log.info("Rebooting server A")
1026

    
1027
        self.client.shutdown_server(self.serverid['A'])
1028

    
1029
        fail_tmout = time.time() + self.action_timeout
1030
        while True:
1031
            d = self.client.get_server_details(self.serverid['A'])
1032
            status = d['status']
1033
            if status == 'STOPPED':
1034
                break
1035
            elif time.time() > fail_tmout:
1036
                self.assertLess(time.time(), fail_tmout)
1037
            else:
1038
                time.sleep(self.query_interval)
1039

    
1040
        self.client.start_server(self.serverid['A'])
1041

    
1042
        while True:
1043
            d = self.client.get_server_details(self.serverid['A'])
1044
            status = d['status']
1045
            if status == 'ACTIVE':
1046
                active = True
1047
                break
1048
            elif time.time() > fail_tmout:
1049
                self.assertLess(time.time(), fail_tmout)
1050
            else:
1051
                time.sleep(self.query_interval)
1052

    
1053
        self.assertTrue(active)
1054

    
1055
    def test_002b_ping_server_A(self):
1056
        "Test if server A responds to IPv4 pings"
1057

    
1058
        log.info("Testing if server A responds to IPv4 pings ")
1059
        self.result_dict.clear()
1060

    
1061
        server = self.client.get_server_details(self.serverid['A'])
1062
        ip = self._get_ipv4(server)
1063

    
1064
        fail_tmout = time.time() + self.action_timeout
1065

    
1066
        s = False
1067
        
1068
        self.result_dict["Server A public IP"] = str(ip)
1069

    
1070
        while True:
1071

    
1072
            if self._ping_once(ip):
1073
                s = True
1074
                break
1075

    
1076
            elif time.time() > fail_tmout:
1077
                self.assertLess(time.time(), fail_tmout)
1078

    
1079
            else:
1080
                time.sleep(self.query_interval)
1081

    
1082
        self.assertTrue(s)
1083

    
1084
    def test_002c_reboot(self):
1085
        """Reboot server B"""
1086

    
1087
        log.info("Rebooting server B")
1088
        self.result_dict.clear()
1089

    
1090
        self.client.shutdown_server(self.serverid['B'])
1091

    
1092
        fail_tmout = time.time() + self.action_timeout
1093
        while True:
1094
            d = self.client.get_server_details(self.serverid['B'])
1095
            status = d['status']
1096
            if status == 'STOPPED':
1097
                break
1098
            elif time.time() > fail_tmout:
1099
                self.assertLess(time.time(), fail_tmout)
1100
            else:
1101
                time.sleep(self.query_interval)
1102

    
1103
        self.client.start_server(self.serverid['B'])
1104

    
1105
        while True:
1106
            d = self.client.get_server_details(self.serverid['B'])
1107
            status = d['status']
1108
            if status == 'ACTIVE':
1109
                active = True
1110
                break
1111
            elif time.time() > fail_tmout:
1112
                self.assertLess(time.time(), fail_tmout)
1113
            else:
1114
                time.sleep(self.query_interval)
1115

    
1116
        self.assertTrue(active)
1117

    
1118
    def test_002d_ping_server_B(self):
1119
        """Test if server B responds to IPv4 pings"""
1120

    
1121
        log.info("Testing if server B responds to IPv4 pings")
1122
        self.result_dict.clear()
1123
        
1124
        server = self.client.get_server_details(self.serverid['B'])
1125
        ip = self._get_ipv4(server)
1126

    
1127
        fail_tmout = time.time() + self.action_timeout
1128

    
1129
        s = False
1130

    
1131
        self.result_dict["Server B public IP"] = str(ip)
1132

    
1133
        while True:
1134
            if self._ping_once(ip):
1135
                s = True
1136
                break
1137

    
1138
            elif time.time() > fail_tmout:
1139
                self.assertLess(time.time(), fail_tmout)
1140

    
1141
            else:
1142
                time.sleep(self.query_interval)
1143

    
1144
        self.assertTrue(s)
1145

    
1146
    def test_003a_setup_interface_A(self):
1147
        """Set up eth1 for server A"""
1148

    
1149
        self._skipIf(self.is_windows, "only valid for Linux servers")
1150

    
1151
        log.info("Setting up interface eth1 for server A")
1152
        self.result_dict.clear()
1153

    
1154
        server = self.client.get_server_details(self.serverid['A'])
1155
        image = self.client.get_image_details(self.imageid)
1156
        os = image['metadata']['values']['os']
1157

    
1158
        users = image["metadata"]["values"].get("users", None)
1159
        userlist = users.split()
1160

    
1161
        if "root" in userlist:
1162
            loginname = "root"
1163
        elif users == None:
1164
            loginname = self._connect_loginname(os)
1165
        else:
1166
            loginname = choice(userlist)
1167

    
1168
        hostip = self._get_ipv4(server)
1169
        myPass = self.password['A']
1170

    
1171
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1172

    
1173
        res = False
1174

    
1175
        if loginname != "root":
1176
            with settings(
1177
                hide('warnings', 'running'),
1178
                warn_only=True,
1179
                host_string=hostip,
1180
                user=loginname, password=myPass
1181
                ):
1182

    
1183
                if len(sudo('ifconfig eth1 %s' % self.priv_ip["A"])) == 0:
1184
                    res = True
1185

    
1186
        else:
1187
            with settings(
1188
                hide('warnings', 'running'),
1189
                warn_only=True,
1190
                host_string=hostip,
1191
                user=loginname, password=myPass
1192
                ):
1193

    
1194
                if len(run('ifconfig eth1 %s' % self.priv_ip["A"])) == 0:
1195
                    res = True
1196

    
1197
        self.assertTrue(res)
1198

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

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

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

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

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

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

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

    
1223
        log.info("SSH in server B as %s/%s" % (loginname, myPass))
1224

    
1225
        res = False
1226

    
1227
        if loginname != "root":
1228
            with settings(
1229
                hide('warnings', 'running'),
1230
                warn_only=True,
1231
                host_string=hostip,
1232
                user=loginname, password=myPass
1233
                ):
1234

    
1235
                if len(sudo('ifconfig eth1 %s' % self.priv_ip["B"])) == 0:
1236
                    res = True
1237

    
1238
        else:
1239
            with settings(
1240
                hide('warnings', 'running'),
1241
                warn_only=True,
1242
                host_string=hostip,
1243
                user=loginname, password=myPass
1244
                ):
1245

    
1246
                if len(run('ifconfig eth1 %s' % self.priv_ip["B"])) == 0:
1247
                    res = True
1248

    
1249
        self.assertTrue(res)
1250

    
1251
    def test_003c_test_connection_exists(self):
1252
        """Ping server B from server A to test if connection exists"""
1253

    
1254
        self._skipIf(self.is_windows, "only valid for Linux servers")
1255

    
1256
        log.info("Testing if server A is actually connected to server B")
1257

    
1258
        server = self.client.get_server_details(self.serverid['A'])
1259
        image = self.client.get_image_details(self.imageid)
1260
        os = image['metadata']['values']['os']
1261
        hostip = self._get_ipv4(server)
1262

    
1263
        users = image["metadata"]["values"].get("users", None)
1264
        userlist = users.split()
1265

    
1266
        if "root" in userlist:
1267
            loginname = "root"
1268
        elif users == None:
1269
            loginname = self._connect_loginname(os)
1270
        else:
1271
            loginname = choice(userlist)
1272

    
1273
        myPass = self.password['A']
1274

    
1275
        try:
1276
            ssh = paramiko.SSHClient()
1277
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
1278
            ssh.connect(hostip, username=loginname, password=myPass)
1279
        except socket.error:
1280
            raise AssertionError
1281

    
1282
        cmd = "if ping -c 2 -w 3 %s >/dev/null; \
1283
               then echo \'True\'; fi;" % self.priv_ip["B"]
1284
        stdin, stdout, stderr = ssh.exec_command(cmd)
1285
        lines = stdout.readlines()
1286

    
1287
        exists = False
1288

    
1289
        if 'True\n' in lines:
1290
            exists = True
1291

    
1292
        self.assertTrue(exists)
1293

    
1294
    def test_004_disconnect_from_network(self):
1295
        "Disconnecting server A and B from network"
1296

    
1297
        log.info("Disconnecting servers from private network")
1298

    
1299
        prev_state = self.client.get_network_details(self.networkid)
1300
        prev_nics = prev_state['attachments']['values']
1301
        prev_conn = len(prev_nics)
1302

    
1303
        nicsA=[x['id'] for x in self.client.get_server_details(self.serverid['A'])['attachments']['values']]
1304
        nicsB=[x['id'] for x in self.client.get_server_details(self.serverid['B'])['attachments']['values']]
1305

    
1306
        for nic in prev_nics:
1307
            if nic in nicsA:
1308
                self.client.disconnect_server(self.serverid['A'], nic)
1309
            if nic in nicsB:
1310
                self.client.disconnect_server(self.serverid['B'], nic)
1311

    
1312

    
1313
        #Insist on deleting until action timeout
1314
        fail_tmout = time.time() + self.action_timeout
1315

    
1316
        while True:
1317

    
1318
            netsA=[x['network_id'] for x in self.client.get_server_details(self.serverid['A'])['attachments']['values']]
1319
            netsB=[x['network_id'] for x in self.client.get_server_details(self.serverid['B'])['attachments']['values']]
1320

    
1321

    
1322
            connected = (self.client.get_network_details(self.networkid))
1323
            connections = connected['attachments']['values']
1324
            if (self.networkid not in netsA) and (self.networkid not in netsB):
1325
                conn_exists = False
1326
                break
1327
            elif time.time() > fail_tmout:
1328
                self.assertLess(time.time(), fail_tmout)
1329
            else:
1330
                time.sleep(self.query_interval)
1331

    
1332
        self.assertFalse(conn_exists)
1333

    
1334
    def test_005_destroy_network(self):
1335
        """Test submit delete network request"""
1336

    
1337
        log.info("Submitting delete network request")
1338

    
1339
        self.client.delete_network(self.networkid)
1340

    
1341
        fail_tmout = time.time() + self.action_timeout
1342

    
1343
        while True:
1344

    
1345
            curr_net = []
1346
            networks = self.client.list_networks()
1347

    
1348
            for net in networks:
1349
                curr_net.append(net['id'])
1350

    
1351
            if self.networkid not in curr_net:
1352
                self.assertTrue(self.networkid not in curr_net)
1353
                break
1354

    
1355
            elif time.time() > fail_tmout:
1356
                self.assertLess(time.time(), fail_tmout)
1357

    
1358
            else:
1359
                time.sleep(self.query_interval)
1360

    
1361

    
1362

    
1363
    def test_006_cleanup_servers(self):
1364
        """Cleanup servers created for this test"""
1365

    
1366
        log.info("Delete servers created for this test")
1367

    
1368
        self.compute.delete_server(self.serverid['A'])
1369
        self.compute.delete_server(self.serverid['B'])
1370

    
1371
        fail_tmout = time.time() + self.action_timeout
1372

    
1373
        #Ensure server gets deleted
1374
        status = dict()
1375

    
1376
        while True:
1377
            details = self.compute.get_server_details(self.serverid['A'])
1378
            status['A'] = details['status']
1379
            details = self.compute.get_server_details(self.serverid['B'])
1380
            status['B'] = details['status']
1381
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1382
                deleted = True
1383
                break
1384
            elif time.time() > fail_tmout:
1385
                self.assertLess(time.time(), fail_tmout)
1386
            else:
1387
                time.sleep(self.query_interval)
1388

    
1389
        self.assertTrue(deleted)
1390

    
1391

    
1392
class TestRunnerProcess(Process):
1393
    """A distinct process used to execute part of the tests in parallel"""
1394
    def __init__(self, **kw):
1395
        Process.__init__(self, **kw)
1396
        kwargs = kw["kwargs"]
1397
        self.testq = kwargs["testq"]
1398
        self.worker_folder = kwargs["worker_folder"]
1399

    
1400
    def run(self):
1401
        # Make sure this test runner process dies with the parent
1402
        # and is not left behind.
1403
        #
1404
        # WARNING: This uses the prctl(2) call and is
1405
        # Linux-specific.
1406

    
1407
        prctl.set_pdeathsig(signal.SIGHUP)
1408

    
1409
        multi = logging.getLogger("multiprocess")
1410

    
1411
        while True:
1412
            multi.debug("I am process %d, GETting from queue is %s" %
1413
                     (os.getpid(), self.testq))
1414
            msg = self.testq.get()
1415

    
1416
            multi.debug("Dequeued msg: %s" % msg)
1417

    
1418
            if msg == "TEST_RUNNER_TERMINATE":
1419
                raise SystemExit
1420

    
1421
            elif issubclass(msg, unittest.TestCase):
1422
                # Assemble a TestSuite, and run it
1423

    
1424
                log_file = os.path.join(self.worker_folder, 'details_' +
1425
                                        (msg.__name__) + "_" +
1426
                                        TEST_RUN_ID + '.log')
1427

    
1428
                fail_file = os.path.join(self.worker_folder, 'failed_' +
1429
                                         (msg.__name__) + "_" +
1430
                                         TEST_RUN_ID + '.log')
1431
                error_file = os.path.join(self.worker_folder, 'error_' +
1432
                                          (msg.__name__) + "_" +
1433
                                          TEST_RUN_ID + '.log')
1434

    
1435
                f = open(log_file, 'w')
1436
                fail = open(fail_file,'w')
1437
                error = open(error_file, 'w')
1438

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

    
1441
                runner = unittest.TextTestRunner(f, verbosity=2, failfast = True, resultclass=BurninTestResult)
1442
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
1443
                result = runner.run(suite)
1444

    
1445
                for res in result.errors:
1446
                    log.error("snf-burnin encountered an error in " \
1447
                                  "testcase: %s" %msg)
1448
                    log.error("See log for details")
1449
                    error.write(str(res[0]) + '\n')
1450
                    error.write(str(res[0].shortDescription()) + '\n')
1451
                    error.write('\n')
1452

    
1453
                for res in result.failures:
1454
                    log.error("snf-burnin failed in testcase: %s" %msg)
1455
                    log.error("See log for details")
1456
                    fail.write(str(res[0]) + '\n')
1457
                    fail.write(str(res[0].shortDescription()) + '\n')
1458
                    fail.write('\n')
1459
                    if NOFAILFAST == False:
1460
                        sys.exit()
1461

    
1462
                if (len(result.failures) == 0) and (len(result.errors) == 0):
1463
                    log.debug("Passed testcase: %s" %msg)
1464

    
1465
                f.close()
1466
                fail.close()
1467
                error.close()
1468

    
1469

    
1470
            else:
1471
                raise Exception("Cannot handle msg: %s" % msg)
1472

    
1473
def _run_cases_in_series(cases,image_folder):
1474
    """Run instances of TestCase in series"""
1475

    
1476
    for case in cases:
1477

    
1478
        test = case.__name__
1479

    
1480
        log.info(yellow + '* Starting testcase: %s' %test + normal)
1481
        log_file = os.path.join(image_folder, 'details_' +
1482
                                (case.__name__) + "_" +
1483
                                TEST_RUN_ID + '.log')
1484
        fail_file = os.path.join(image_folder, 'failed_' +
1485
                                 (case.__name__) + "_" +
1486
                                 TEST_RUN_ID + '.log')
1487
        error_file = os.path.join(image_folder, 'error_' +
1488
                                  (case.__name__) + "_" +
1489
                                  TEST_RUN_ID + '.log')
1490

    
1491
        f = open(log_file, "w")
1492
        fail = open(fail_file, "w")
1493
        error = open(error_file, "w")
1494

    
1495
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1496
        runner = unittest.TextTestRunner(f, verbosity=2, failfast=True, resultclass=BurninTestResult)
1497
        result = runner.run(suite)
1498

    
1499
        for res in result.errors:
1500
            log.error("snf-burnin encountered an error in " \
1501
                          "testcase: %s" %test)
1502
            log.error("See log for details")
1503
            error.write(str(res[0]) + '\n')
1504
            error.write(str(res[0].shortDescription()) + '\n')
1505
            error.write('\n')
1506

    
1507
        for res in result.failures:
1508
            log.error("snf-burnin failed in testcase: %s" %test)
1509
            log.error("See log for details")
1510
            fail.write(str(res[0]) + '\n')
1511
            fail.write(str(res[0].shortDescription()) + '\n')
1512
            fail.write('\n')
1513
            if NOFAILFAST == False:
1514
                sys.exit()
1515

    
1516
        if (len(result.failures) == 0) and (len(result.errors) == 0):
1517
                log.debug("Passed testcase: %s" %test)
1518

    
1519
    
1520

    
1521
def _run_cases_in_parallel(cases, fanout, image_folder):
1522
    """Run instances of TestCase in parallel, in a number of distinct processes
1523

1524
    The cases iterable specifies the TestCases to be executed in parallel,
1525
    by test runners running in distinct processes.
1526
    The fanout parameter specifies the number of processes to spawn,
1527
    and defaults to 1.
1528
    The runner argument specifies the test runner class to use inside each
1529
    runner process.
1530

1531
    """
1532

    
1533
    multi = logging.getLogger("multiprocess")
1534
    handler = logging.StreamHandler()
1535
    multi.addHandler(handler)
1536

    
1537
    if VERBOSE:
1538
        multi.setLevel(logging.DEBUG)
1539
    else:
1540
        multi.setLevel(logging.INFO)
1541

    
1542
    testq = []
1543
    worker_folder = []
1544
    runners = []
1545

    
1546
    for i in xrange(0,fanout):
1547
        testq.append(Queue())
1548
        worker_folder.append(os.path.join(image_folder, 'process'+str(i)))
1549
        os.mkdir(worker_folder[i])
1550

    
1551
    for i in xrange(0, fanout):
1552
        kwargs = dict(testq=testq[i], worker_folder=worker_folder[i])
1553
        runners.append(TestRunnerProcess(kwargs=kwargs))
1554

    
1555
    multi.debug("Spawning %d test runner processes" %len(runners))
1556

    
1557
    for p in runners:
1558
        p.start()
1559

    
1560
    # Enqueue test cases
1561
    for i in xrange(0, fanout):
1562
        map(testq[i].put, cases)
1563
        testq[i].put("TEST_RUNNER_TERMINATE")
1564

    
1565
    multi.debug("Spawned %d test runners, PIDs are %s" %
1566
              (len(runners), [p.pid for p in runners]))
1567

    
1568
    multi.debug("Joining %d processes" % len(runners))
1569

    
1570
    for p in runners:
1571
        p.join()
1572

    
1573
    multi.debug("Done joining %d processes" % len(runners))
1574

    
1575

    
1576
def _spawn_server_test_case(**kwargs):
1577
    """Construct a new unit test case class from SpawnServerTestCase"""
1578

    
1579
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1580
    cls = type(name, (SpawnServerTestCase,), kwargs)
1581

    
1582
    # Patch extra parameters into test names by manipulating method docstrings
1583
    for (mname, m) in \
1584
        inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1585
        if hasattr(m, __doc__):
1586
            m.__func__.__doc__ = "[%s] %s" % (imagename, m.__doc__)
1587

    
1588
    # Make sure the class can be pickled, by listing it among
1589
    # the attributes of __main__. A PicklingError is raised otherwise.
1590

    
1591
    thismodule = sys.modules[__name__]
1592
    setattr(thismodule, name, cls)
1593
    return cls
1594

    
1595

    
1596
def _spawn_network_test_case(**kwargs):
1597
    """Construct a new unit test case class from NetworkTestCase"""
1598

    
1599
    name = "NetworkTestCase" + TEST_RUN_ID
1600
    cls = type(name, (NetworkTestCase,), kwargs)
1601

    
1602
    # Make sure the class can be pickled, by listing it among
1603
    # the attributes of __main__. A PicklingError is raised otherwise.
1604

    
1605
    thismodule = sys.modules[__name__]
1606
    setattr(thismodule, name, cls)
1607
    return cls
1608

    
1609

    
1610
def cleanup_servers(timeout, query_interval, delete_stale=False):
1611

    
1612
    c = ComputeClient(API, TOKEN)
1613

    
1614
    servers = c.list_servers()
1615
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1616

    
1617
    if len(stale) == 0:
1618
        return
1619

    
1620
    print >> sys.stderr, yellow + "Found these stale servers from previous runs:" + normal
1621
    print "    " + \
1622
          "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1623

    
1624
    if delete_stale:
1625
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1626

    
1627
        fail_tmout = time.time() + timeout
1628

    
1629

    
1630
        for s in stale:
1631
            c.delete_server(s["id"])
1632

    
1633
        
1634
        while True:
1635
            servers = c.list_servers()
1636
            stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1637

    
1638
            if len(stale)==0:
1639
                print >> sys.stderr, green + "    ...done" + normal
1640
                break
1641

    
1642
            elif time.time() > fail_tmout:
1643
                print >> sys.stderr, red + "Not all stale servers deleted. Action timed out." + normal
1644
                return 
1645
            else:
1646
                time.sleep(query_interval)
1647
                
1648
    else:
1649
        print >> sys.stderr, "Use --delete-stale to delete them."
1650

    
1651

    
1652
def cleanup_networks(action_timeout, query_interval, delete_stale=False):
1653

    
1654
    c = CycladesClient(API, TOKEN)
1655

    
1656
    networks = c.list_networks()
1657
    stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1658

    
1659
    if len(stale) == 0:
1660
        return
1661

    
1662
    fail_tmout = time.time() + action_timeout
1663
    while True:
1664
        servers = c.list_servers()
1665
        staleServers = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1666
        if len(staleServers) == 0:
1667
            break
1668
        elif time.time() > fail_tmout:
1669
            log.error("Stale servers not deleted from previous run")
1670
            sys.exit()
1671
        else:
1672
            time.sleep(query_interval)
1673

    
1674
    print >> sys.stderr, yellow + "Found these stale networks from previous runs:" + normal
1675
    print "    " + \
1676
          "\n    ".join(["%s: %s" % (str(n["id"]), n["name"]) for n in stale])
1677

    
1678
    if delete_stale:
1679
        print >> sys.stderr, "Deleting %d stale networks:" % len(stale)
1680

    
1681
        fail_tmout = time.time() + action_timeout
1682
        
1683
        for n in stale:
1684
            c.delete_network(n["id"])
1685

    
1686

    
1687
        while True:
1688
            networks = c.list_networks()
1689
            stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1690

    
1691
            if len(stale)==0:
1692
                print >> sys.stderr, green + "    ...done" + normal
1693
                break
1694

    
1695
            elif time.time() > fail_tmout:
1696
                print >> sys.stderr, red + "Not all stale networks deleted. Action timed out." + normal
1697
                return 
1698
            else:
1699
                time.sleep(query_interval)
1700

    
1701
    else:
1702
        print >> sys.stderr, "Use --delete-stale to delete them."
1703

    
1704

    
1705
def parse_comma(option, opt, value, parser):
1706
    tests = set(['all', 'auth', 'images', 'flavors',
1707
               'servers', 'server_spawn', 'network_spawn'])
1708
    parse_input = value.split(',')
1709

    
1710
    if not (set(parse_input)).issubset(tests):
1711
        raise OptionValueError("The selected set of tests is invalid")
1712

    
1713
    setattr(parser.values, option.dest, value.split(','))
1714

    
1715

    
1716
def parse_arguments(args):
1717

    
1718
    kw = {}
1719
    kw["usage"] = "%prog [options]"
1720
    kw["description"] = \
1721
        "%prog runs a number of test scenarios on a " \
1722
        "Synnefo deployment."
1723

    
1724
    parser = OptionParser(**kw)
1725
    parser.disable_interspersed_args()
1726

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

    
1831
    (opts, args) = parser.parse_args(args)
1832

    
1833
    # Verify arguments
1834
    if opts.delete_stale:
1835
        opts.show_stale = True
1836

    
1837
    if not opts.show_stale:
1838
        if not opts.force_imageid:
1839
            print >>sys.stderr, red + "The --image-id argument " \
1840
                                       "is mandatory.\n" + normal
1841
            parser.print_help()
1842
            sys.exit(1)
1843

    
1844
        if not opts.token:
1845
            print >>sys.stderr, red + "The --token argument is " \
1846
                                      "mandatory.\n" + normal
1847
            parser.print_help()
1848
            sys.exit(1)
1849

    
1850
        if opts.force_imageid != 'all':
1851
            try:
1852
                opts.force_imageid = str(opts.force_imageid)
1853
            except ValueError:
1854
                print >>sys.stderr, red + "Invalid value specified for" \
1855
                    "--image-id. Use a valid id, or `all'." + normal
1856
                sys.exit(1)
1857

    
1858
    return (opts, args)
1859

    
1860

    
1861
def main():
1862
    """Assemble test cases into a test suite, and run it
1863

1864
    IMPORTANT: Tests have dependencies and have to be run in the specified
1865
    order inside a single test case. They communicate through attributes of the
1866
    corresponding TestCase class (shared fixtures). Distinct subclasses of
1867
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
1868
    test runner processes.
1869

1870
    """
1871

    
1872
    (opts, args) = parse_arguments(sys.argv[1:])
1873

    
1874
    global API, TOKEN, PLANKTON, PLANKTON_USER, NO_IPV6, VERBOSE, NOFAILFAST
1875
    API = opts.api
1876
    TOKEN = opts.token
1877
    PLANKTON = opts.plankton
1878
    PLANKTON_USER = opts.plankton_user
1879
    NO_IPV6 = opts.no_ipv6
1880
    VERBOSE = opts.verbose
1881
    NOFAILFAST = opts.nofailfast
1882

    
1883
    # Cleanup stale servers from previous runs
1884
    if opts.show_stale:
1885
        cleanup_servers(opts.action_timeout, opts.query_interval, delete_stale=opts.delete_stale)
1886
        cleanup_networks(opts.action_timeout, opts.query_interval, delete_stale=opts.delete_stale)
1887

    
1888
        return 0
1889

    
1890
    # Initialize a kamaki instance, get flavors, images
1891
    c = ComputeClient(API, TOKEN)
1892

    
1893
    DIMAGES = c.list_images(detail=True)
1894
    DFLAVORS = c.list_flavors(detail=True)
1895

    
1896
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
1897
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
1898
    #unittest.main(verbosity=2, catchbreak=True)
1899

    
1900
    if opts.force_imageid == 'all':
1901
        test_images = DIMAGES
1902
    else:
1903
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1904

    
1905
    #New folder for log per image
1906
    if not os.path.exists(opts.log_folder):
1907
        os.mkdir(opts.log_folder)
1908

    
1909
    test_folder = os.path.join(opts.log_folder, TEST_RUN_ID)
1910
    os.mkdir(test_folder)
1911

    
1912
    for image in test_images:
1913

    
1914
        imageid = str(image["id"])
1915

    
1916
        if opts.force_flavorid:
1917
            flavorid = opts.force_flavorid
1918
        else:
1919
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
1920

    
1921
        imagename = image["name"]
1922

    
1923
        #Personality dictionary for file injection test
1924
        if opts.personality_path != None:
1925
            f = open(opts.personality_path)
1926
            content = b64encode(f.read())
1927
            personality = []
1928
            st = os.stat(opts.personality_path)
1929
            personality.append({
1930
                    'path': '/root/test_inj_file',
1931
                    'owner': 'root',
1932
                    'group': 'root',
1933
                    'mode': 0x7777 & st.st_mode,
1934
                    'contents': content
1935
                    })
1936
        else:
1937
            personality = None
1938

    
1939
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
1940
        is_windows = imagename.lower().find("windows") >= 0
1941

    
1942
        ServerTestCase = _spawn_server_test_case(
1943
            imageid=imageid,
1944
            flavorid=flavorid,
1945
            imagename=imagename,
1946
            personality=personality,
1947
            servername=servername,
1948
            is_windows=is_windows,
1949
            action_timeout=opts.action_timeout,
1950
            build_warning=opts.build_warning,
1951
            build_fail=opts.build_fail,
1952
            query_interval=opts.query_interval,
1953
            )
1954

    
1955

    
1956
        NetworkTestCase = _spawn_network_test_case(
1957
            action_timeout=opts.action_timeout,
1958
            imageid=imageid,
1959
            flavorid=flavorid,
1960
            imagename=imagename,
1961
            query_interval=opts.query_interval,
1962
            )
1963

    
1964
        test_dict = {'auth': UnauthorizedTestCase,
1965
                     'images': ImagesTestCase,
1966
                     'flavors': FlavorsTestCase,
1967
                     'servers': ServersTestCase,
1968
                     'server_spawn': ServerTestCase,
1969
                     'network_spawn': NetworkTestCase}
1970

    
1971
        seq_cases = []
1972
        if 'all' in opts.tests:
1973
            seq_cases = [UnauthorizedTestCase, ImagesTestCase, FlavorsTestCase,
1974
                         ServersTestCase, ServerTestCase, NetworkTestCase]
1975
        else:
1976
            for test in opts.tests:
1977
                seq_cases.append(test_dict[test])
1978

    
1979
        #folder for each image
1980
        image_folder = os.path.join(test_folder, imageid)
1981
        os.mkdir(image_folder)
1982

    
1983
        if opts.fanout>1:
1984
            _run_cases_in_parallel(seq_cases, opts.fanout, image_folder)
1985
        else:
1986
            _run_cases_in_series(seq_cases,image_folder)
1987

    
1988
if __name__ == "__main__":
1989
    sys.exit(main())