Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin.py @ 139d3a0b

History | View | Annotate | Download (70.1 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
# This class gets replicated into actual TestCases dynamically
330
class SpawnServerTestCase(unittest.TestCase):
331
    """Test scenario for server of the specified image"""
332

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

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

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

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

    
351
        self.assertTrue(public_addrs is not None)
352

    
353
        return public_addrs
354

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

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

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

    
365
        self.assertTrue(public_addrs is not None)
366

    
367
        return public_addrs
368

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
533
        self.result_dict.clear()
534

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

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

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

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

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

    
555
    def test_002c_set_server_metadata(self):
556

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

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

    
564
        userlist = users.split()
565

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

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

    
577
        self.assertIsNotNone(cls.username)
578

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
706
        log.info("Starting server")
707

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

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

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

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

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

    
722
        self.test_006_server_responds_to_ping_IPv4()
723

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
786
        log.info("Deleting server")
787

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

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

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

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

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

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

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

    
806

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

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

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

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

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

    
827
        cls.result_dict = dict()
828

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

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

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

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

    
843
        self.assertTrue(public_addrs is not None)
844

    
845
        return public_addrs
846

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

    
856
    def _ping_once(self, ip):
857

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

    
865
        return (ret == 0)
866

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

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

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

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

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

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

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

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

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

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

    
909
        self.assertTrue(active)
910

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

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

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

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

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

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

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

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

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

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

    
954
        self.assertTrue(active)
955

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

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

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

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

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

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

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

    
988
        self.assertTrue(connected)
989

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

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

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

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

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

    
1004
        while True:
1005

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

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

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

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

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

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

    
1039
        self.assertTrue(conn_exists)
1040

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

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

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

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

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

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

    
1072
        self.assertTrue(active)
1073

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

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

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

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

    
1085
        s = False
1086

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

    
1089
        while True:
1090

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

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

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

    
1101
        self.assertTrue(s)
1102

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

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

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

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

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

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

    
1135
        self.assertTrue(active)
1136

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

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

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

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

    
1148
        s = False
1149

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

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

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

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

    
1163
        self.assertTrue(s)
1164

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

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

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

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

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

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

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

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

    
1195
        self.assertEquals(status, 0)
1196

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

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

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

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

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

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

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

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

    
1226
        self.assertEquals(status, 0)
1227

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

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

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

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

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

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

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

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

    
1257
        exists = False
1258

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

    
1262
        self.assertTrue(exists)
1263

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

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

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

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

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

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

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

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

    
1307
        self.assertFalse(conn_exists)
1308

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

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

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

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

    
1318
        while True:
1319

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

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

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

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

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

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

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

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

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

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

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

    
1362
        self.assertTrue(deleted)
1363

    
1364

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

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

    
1380
        prctl.set_pdeathsig(signal.SIGHUP)
1381

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1447

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

    
1451
    for case in cases:
1452

    
1453
        test = case.__name__
1454

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

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

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

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

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

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

    
1496

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

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

1507
    """
1508

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

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

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

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

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

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

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

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

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

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

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

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

    
1551

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

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

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

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

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

    
1571

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

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

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

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

    
1585

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

    
1590
    c = ComputeClient(API, TOKEN)
1591

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

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

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

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

    
1629

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

    
1632
    c = CycladesClient(API, TOKEN)
1633

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

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

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

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

    
1671

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

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

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

    
1684

    
1685
def parse_arguments(args):
1686

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

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

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

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

    
1803
    # -----------------------
1804
    # Verify arguments
1805

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

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

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

    
1836
    return (opts, args)
1837

    
1838

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

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

1850
    """
1851

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

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

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

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

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

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

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

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

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

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

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

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

    
1967

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