Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin.py @ 11779b6c

History | View | Annotate | Download (73.8 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 os.path
44
import paramiko
45
import prctl
46
import subprocess
47
import signal
48
import socket
49
import sys
50
import time
51
import tempfile
52
from base64 import b64encode
53
from IPy import IP
54
from multiprocessing import Process, Queue
55
from random import choice, randint
56
from optparse import OptionParser, OptionValueError
57

    
58
from kamaki.clients.compute import ComputeClient
59
from kamaki.clients.cyclades import CycladesClient
60
from kamaki.clients.image import ImageClient
61
from kamaki.clients.pithos import PithosClient
62
from kamaki.clients import ClientError
63

    
64
from vncauthproxy.d3des import generate_response as d3des_generate_response
65

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

    
74
# --------------------------------------------------------------------
75
# Global Variables
76
API = None
77
TOKEN = None
78
PLANKTON = None
79
PLANKTON_USER = None
80
PITHOS = None
81
PITHOS_USER = None
82
NO_IPV6 = None
83
DEFAULT_PLANKTON_USER = "images@okeanos.grnet.gr"
84
NOFAILFAST = None
85
VERBOSE = None
86

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

    
92
red = '\x1b[31m'
93
yellow = '\x1b[33m'
94
green = '\x1b[32m'
95
normal = '\x1b[0m'
96

    
97

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

    
117

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

    
127
                self.stream.write("\n")
128
                for i in run_details:
129
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
130
                self.stream.write("\n")
131

    
132
        elif self.dots:
133
            self.stream.write('.')
134
            self.stream.flush()
135

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

    
143
                self.stream.write("\n")
144
                for i in run_details:
145
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
146
                self.stream.write("\n")
147

    
148
        elif self.dots:
149
            self.stream.write('E')
150
            self.stream.flush()
151

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

    
159
                self.stream.write("\n")
160
                for i in run_details:
161
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
162
                self.stream.write("\n")
163

    
164
        elif self.dots:
165
            self.stream.write('F')
166
            self.stream.flush()
167

    
168

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

    
176
    def __init__(self, fmt="%(levelno)s: %(msg)s"):
177
        logging.Formatter.__init__(self, fmt)
178

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

    
192
log = logging.getLogger("burnin")
193
log.setLevel(logging.DEBUG)
194
handler = logging.StreamHandler()
195
handler.setFormatter(burninFormatter())
196
log.addHandler(handler)
197

    
198

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

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

    
213
        with self.assertRaises(ClientError) as cm:
214
            c.list_servers()
215
            self.assertEqual(cm.exception.status, 401)
216

    
217

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

    
232
    def test_001_list_images(self):
233
        """Test image list actually returns images"""
234
        self.assertGreater(len(self.images), 0)
235

    
236
    def test_002_list_images_detailed(self):
237
        """Test detailed image list is the same length as list"""
238
        self.assertEqual(len(self.dimages), len(self.images))
239

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

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

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

    
260

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

    
274
    def test_001_list_flavors(self):
275
        """Test flavor list actually returns flavors"""
276
        self.assertGreater(len(self.flavors), 0)
277

    
278
    def test_002_list_flavors_detailed(self):
279
        """Test detailed flavor list is the same length as list"""
280
        self.assertEquals(len(self.dflavors), len(self.flavors))
281

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

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

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

    
302

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

    
312
        cls.client = ComputeClient(API, TOKEN)
313
        cls.servers = cls.client.list_servers()
314
        cls.dservers = cls.client.list_servers(detail=True)
315
        cls.result_dict = dict()
316

    
317
    # def test_001_list_servers(self):
318
    #     """Test server list actually returns servers"""
319
    #     self.assertGreater(len(self.servers), 0)
320

    
321
    def test_002_list_servers_detailed(self):
322
        """Test detailed server list is the same length as list"""
323
        self.assertEqual(len(self.dservers), len(self.servers))
324

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

    
331

    
332
# --------------------------------------------------------------------
333
# Pithos Test Cases
334
class PithosTestCase(unittest.TestCase):
335
    """Test pithos functionality"""
336
    @classmethod
337
    def setUpClass(cls):
338
        """Initialize kamaki, get list of containers"""
339
        log.info("Getting list of containers")
340

    
341
        cls.client = PithosClient(PITHOS, TOKEN, PITHOS_USER)
342
        cls.containers = cls.client.list_containers()
343
        cls.result_dict = dict()
344

    
345
    def test_001_list_containers(self):
346
        """Test container list actually returns containers"""
347
        self.assertGreater(len(self.containers), 0)
348

    
349
    def test_002_unique_containers(self):
350
        """Test if containers have unique names"""
351
        names = [n['name'] for n in self.containers]
352
        names = sorted(names)
353
        self.assertEqual(sorted(list(set(names))), names)
354

    
355
    def test_003_create_container(self):
356
        """Test create a container"""
357
        rand_num = randint(1000, 9999)
358
        rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
359
        names = [n['name'] for n in self.containers]
360
        while rand_name in names:
361
            rand_num = randint(1000, 9999)
362
            rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
363
        # Create container
364
        self.client.container = rand_name
365
        self.client.container_put()
366
        # Get list of containers
367
        new_containers = self.client.list_containers()
368
        new_container_names = [n['name'] for n in new_containers]
369
        self.assertIn(rand_name, new_container_names)
370

    
371
    def test_004_upload(self):
372
        """Test uploading something to pithos+"""
373
        # Create a tmp file
374
        with tempfile.TemporaryFile() as f:
375
            f.write("This is a temp file")
376
            f.seek(0, 0)
377
            # Where to save file
378
            self.client.upload_object("test.txt", f)
379

    
380
    def test_005_download(self):
381
        """Test download something from pithos+"""
382
        # Create tmp directory to save file
383
        tmp_dir = tempfile.mkdtemp()
384
        tmp_file = os.path.join(tmp_dir, "test.txt")
385
        with open(tmp_file, "wb+") as f:
386
            self.client.download_object("test.txt", f)
387
            # Read file
388
            f.seek(0, 0)
389
            content = f.read()
390
        # Remove files
391
        os.unlink(tmp_file)
392
        os.rmdir(tmp_dir)
393
        # Compare results
394
        self.assertEqual(content, "This is a temp file")
395

    
396
    def test_006_remove(self):
397
        """Test removing files and containers"""
398
        cont_name = self.client.container
399
        self.client.del_object("test.txt")
400
        self.client.purge_container()
401
        # List containers
402
        containers = self.client.list_containers()
403
        cont_names = [n['name'] for n in containers]
404
        self.assertNotIn(cont_name, cont_names)
405

    
406

    
407
# --------------------------------------------------------------------
408
# This class gets replicated into actual TestCases dynamically
409
class SpawnServerTestCase(unittest.TestCase):
410
    """Test scenario for server of the specified image"""
411

    
412
    @classmethod
413
    def setUpClass(cls):
414
        """Initialize a kamaki instance"""
415
        log.info("Spawning server for image `%s'" % cls.imagename)
416
        cls.client = ComputeClient(API, TOKEN)
417
        cls.cyclades = CycladesClient(API, TOKEN)
418
        cls.result_dict = dict()
419

    
420
    def _get_ipv4(self, server):
421
        """Get the public IPv4 of a server from the detailed server info"""
422

    
423
        nics = server["attachments"]["values"]
424

    
425
        for nic in nics:
426
            net_id = nic["network_id"]
427
            if self.cyclades.get_network_details(net_id)["public"]:
428
                public_addrs = nic["ipv4"]
429

    
430
        self.assertTrue(public_addrs is not None)
431

    
432
        return public_addrs
433

    
434
    def _get_ipv6(self, server):
435
        """Get the public IPv6 of a server from the detailed server info"""
436

    
437
        nics = server["attachments"]["values"]
438

    
439
        for nic in nics:
440
            net_id = nic["network_id"]
441
            if self.cyclades.get_network_details(net_id)["public"]:
442
                public_addrs = nic["ipv6"]
443

    
444
        self.assertTrue(public_addrs is not None)
445

    
446
        return public_addrs
447

    
448
    def _connect_loginname(self, os_value):
449
        """Return the login name for connections based on the server OS"""
450
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
451
            return "user"
452
        elif os_value in ("windows", "windows_alpha1"):
453
            return "Administrator"
454
        else:
455
            return "root"
456

    
457
    def _verify_server_status(self, current_status, new_status):
458
        """Verify a server has switched to a specified status"""
459
        server = self.client.get_server_details(self.serverid)
460
        if server["status"] not in (current_status, new_status):
461
            return None  # Do not raise exception, return so the test fails
462
        self.assertEquals(server["status"], new_status)
463

    
464
    def _get_connected_tcp_socket(self, family, host, port):
465
        """Get a connected socket from the specified family to host:port"""
466
        sock = None
467
        for res in \
468
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
469
                               socket.AI_PASSIVE):
470
            af, socktype, proto, canonname, sa = res
471
            try:
472
                sock = socket.socket(af, socktype, proto)
473
            except socket.error:
474
                sock = None
475
                continue
476
            try:
477
                sock.connect(sa)
478
            except socket.error:
479
                sock.close()
480
                sock = None
481
                continue
482
        self.assertIsNotNone(sock)
483
        return sock
484

    
485
    def _ping_once(self, ipv6, ip):
486
        """Test server responds to a single IPv4 or IPv6 ping"""
487
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
488
        ping = subprocess.Popen(cmd, shell=True,
489
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
490
        (stdout, stderr) = ping.communicate()
491
        ret = ping.wait()
492
        self.assertEquals(ret, 0)
493

    
494
    def _get_hostname_over_ssh(self, hostip, username, password):
495
        lines, status = _ssh_execute(
496
            hostip, username, password, "hostname")
497
        self.assertEqual(len(lines), 1)
498
        return lines[0]
499

    
500
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
501
                                   opmsg, callable, *args, **kwargs):
502
        if warn_timeout == fail_timeout:
503
            warn_timeout = fail_timeout + 1
504
        warn_tmout = time.time() + warn_timeout
505
        fail_tmout = time.time() + fail_timeout
506
        while True:
507
            self.assertLess(time.time(), fail_tmout,
508
                            "operation `%s' timed out" % opmsg)
509
            if time.time() > warn_tmout:
510
                log.warning("Server %d: `%s' operation `%s' not done yet",
511
                            self.serverid, self.servername, opmsg)
512
            try:
513
                log.info("%s... " % opmsg)
514
                return callable(*args, **kwargs)
515
            except AssertionError:
516
                pass
517
            time.sleep(self.query_interval)
518

    
519
    def _insist_on_tcp_connection(self, family, host, port):
520
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
521
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
522
        msg = "connect over %s to %s:%s" % \
523
              (familystr.get(family, "Unknown"), host, port)
524
        sock = self._try_until_timeout_expires(
525
            self.action_timeout, self.action_timeout,
526
            msg, self._get_connected_tcp_socket,
527
            family, host, port)
528
        return sock
529

    
530
    def _insist_on_status_transition(self, current_status, new_status,
531
                                     fail_timeout, warn_timeout=None):
532
        msg = "Server %d: `%s', waiting for %s -> %s" % \
533
              (self.serverid, self.servername, current_status, new_status)
534
        if warn_timeout is None:
535
            warn_timeout = fail_timeout
536
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
537
                                        msg, self._verify_server_status,
538
                                        current_status, new_status)
539
        # Ensure the status is actually the expected one
540
        server = self.client.get_server_details(self.serverid)
541
        self.assertEquals(server["status"], new_status)
542

    
543
    def _insist_on_ssh_hostname(self, hostip, username, password):
544
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
545
        hostname = self._try_until_timeout_expires(
546
            self.action_timeout, self.action_timeout,
547
            msg, self._get_hostname_over_ssh,
548
            hostip, username, password)
549

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

    
553
    def _check_file_through_ssh(self, hostip, username, password,
554
                                remotepath, content):
555
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
556
            (hostip, username, password)
557
        log.info(msg)
558
        try:
559
            ssh = paramiko.SSHClient()
560
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
561
            ssh.connect(hostip, username=username, password=password)
562
            ssh.close()
563
        except socket.error:
564
            raise AssertionError
565

    
566
        transport = paramiko.Transport((hostip, 22))
567
        transport.connect(username=username, password=password)
568

    
569
        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
570
        sftp = paramiko.SFTPClient.from_transport(transport)
571
        sftp.get(remotepath, localpath)
572
        sftp.close()
573
        transport.close()
574

    
575
        f = open(localpath)
576
        remote_content = b64encode(f.read())
577

    
578
        # Check if files are the same
579
        return (remote_content == content)
580

    
581
    def _skipIf(self, condition, msg):
582
        if condition:
583
            self.skipTest(msg)
584

    
585
    def test_001_submit_create_server(self):
586
        """Test submit create server request"""
587

    
588
        log.info("Submit new server request")
589
        server = self.client.create_server(self.servername, self.flavorid,
590
                                           self.imageid, self.personality)
591

    
592
        log.info("Server id: " + str(server["id"]))
593
        log.info("Server password: " + server["adminPass"])
594
        self.assertEqual(server["name"], self.servername)
595
        self.assertEqual(server["flavorRef"], self.flavorid)
596
        self.assertEqual(server["imageRef"], self.imageid)
597
        self.assertEqual(server["status"], "BUILD")
598

    
599
        # Update class attributes to reflect data on building server
600
        cls = type(self)
601
        cls.serverid = server["id"]
602
        cls.username = None
603
        cls.passwd = server["adminPass"]
604

    
605
        self.result_dict["Server ID"] = str(server["id"])
606
        self.result_dict["Password"] = str(server["adminPass"])
607

    
608
    def test_002a_server_is_building_in_list(self):
609
        """Test server is in BUILD state, in server list"""
610
        log.info("Server in BUILD state in server list")
611

    
612
        self.result_dict.clear()
613

    
614
        servers = self.client.list_servers(detail=True)
615
        servers = filter(lambda x: x["name"] == self.servername, servers)
616

    
617
        server = servers[0]
618
        self.assertEqual(server["name"], self.servername)
619
        self.assertEqual(server["flavorRef"], self.flavorid)
620
        self.assertEqual(server["imageRef"], self.imageid)
621
        self.assertEqual(server["status"], "BUILD")
622

    
623
    def test_002b_server_is_building_in_details(self):
624
        """Test server is in BUILD state, in details"""
625

    
626
        log.info("Server in BUILD state in details")
627

    
628
        server = self.client.get_server_details(self.serverid)
629
        self.assertEqual(server["name"], self.servername)
630
        self.assertEqual(server["flavorRef"], self.flavorid)
631
        self.assertEqual(server["imageRef"], self.imageid)
632
        self.assertEqual(server["status"], "BUILD")
633

    
634
    def test_002c_set_server_metadata(self):
635

    
636
        log.info("Creating server metadata")
637

    
638
        image = self.client.get_image_details(self.imageid)
639
        os_value = image["metadata"]["values"]["os"]
640
        users = image["metadata"]["values"].get("users", None)
641
        self.client.update_server_metadata(self.serverid, OS=os_value)
642

    
643
        userlist = users.split()
644

    
645
        # Determine the username to use for future connections
646
        # to this host
647
        cls = type(self)
648

    
649
        if "root" in userlist:
650
            cls.username = "root"
651
        elif users is None:
652
            cls.username = self._connect_loginname(os_value)
653
        else:
654
            cls.username = choice(userlist)
655

    
656
        self.assertIsNotNone(cls.username)
657

    
658
    def test_002d_verify_server_metadata(self):
659
        """Test server metadata keys are set based on image metadata"""
660

    
661
        log.info("Verifying image metadata")
662

    
663
        servermeta = self.client.get_server_metadata(self.serverid)
664
        imagemeta = self.client.get_image_metadata(self.imageid)
665

    
666
        self.assertEqual(servermeta["OS"], imagemeta["os"])
667

    
668
    def test_003_server_becomes_active(self):
669
        """Test server becomes ACTIVE"""
670

    
671
        log.info("Waiting for server to become ACTIVE")
672

    
673
        self._insist_on_status_transition(
674
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
675

    
676
    def test_003a_get_server_oob_console(self):
677
        """Test getting OOB server console over VNC
678

679
        Implementation of RFB protocol follows
680
        http://www.realvnc.com/docs/rfbproto.pdf.
681

682
        """
683
        console = self.cyclades.get_server_console(self.serverid)
684
        self.assertEquals(console['type'], "vnc")
685
        sock = self._insist_on_tcp_connection(
686
            socket.AF_INET, console["host"], console["port"])
687

    
688
        # Step 1. ProtocolVersion message (par. 6.1.1)
689
        version = sock.recv(1024)
690
        self.assertEquals(version, 'RFB 003.008\n')
691
        sock.send(version)
692

    
693
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
694
        sec = sock.recv(1024)
695
        self.assertEquals(list(sec), ['\x01', '\x02'])
696

    
697
        # Step 3. Request VNC Authentication (par 6.1.2)
698
        sock.send('\x02')
699

    
700
        # Step 4. Receive Challenge (par 6.2.2)
701
        challenge = sock.recv(1024)
702
        self.assertEquals(len(challenge), 16)
703

    
704
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
705
        response = d3des_generate_response(
706
            (console["password"] + '\0' * 8)[:8], challenge)
707
        sock.send(response)
708

    
709
        # Step 6. SecurityResult (par 6.1.3)
710
        result = sock.recv(4)
711
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
712
        sock.close()
713

    
714
    def test_004_server_has_ipv4(self):
715
        """Test active server has a valid IPv4 address"""
716

    
717
        log.info("Validate server's IPv4")
718

    
719
        server = self.client.get_server_details(self.serverid)
720
        ipv4 = self._get_ipv4(server)
721

    
722
        self.result_dict.clear()
723
        self.result_dict["IPv4"] = str(ipv4)
724

    
725
        self.assertEquals(IP(ipv4).version(), 4)
726

    
727
    def test_005_server_has_ipv6(self):
728
        """Test active server has a valid IPv6 address"""
729
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
730

    
731
        log.info("Validate server's IPv6")
732

    
733
        server = self.client.get_server_details(self.serverid)
734
        ipv6 = self._get_ipv6(server)
735

    
736
        self.result_dict.clear()
737
        self.result_dict["IPv6"] = str(ipv6)
738

    
739
        self.assertEquals(IP(ipv6).version(), 6)
740

    
741
    def test_006_server_responds_to_ping_IPv4(self):
742
        """Test server responds to ping on IPv4 address"""
743

    
744
        log.info("Testing if server responds to pings in IPv4")
745
        self.result_dict.clear()
746

    
747
        server = self.client.get_server_details(self.serverid)
748
        ip = self._get_ipv4(server)
749
        self._try_until_timeout_expires(self.action_timeout,
750
                                        self.action_timeout,
751
                                        "PING IPv4 to %s" % ip,
752
                                        self._ping_once,
753
                                        False, ip)
754

    
755
    def test_007_server_responds_to_ping_IPv6(self):
756
        """Test server responds to ping on IPv6 address"""
757
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
758
        log.info("Testing if server responds to pings in IPv6")
759

    
760
        server = self.client.get_server_details(self.serverid)
761
        ip = self._get_ipv6(server)
762
        self._try_until_timeout_expires(self.action_timeout,
763
                                        self.action_timeout,
764
                                        "PING IPv6 to %s" % ip,
765
                                        self._ping_once,
766
                                        True, ip)
767

    
768
    def test_008_submit_shutdown_request(self):
769
        """Test submit request to shutdown server"""
770

    
771
        log.info("Shutting down server")
772

    
773
        self.cyclades.shutdown_server(self.serverid)
774

    
775
    def test_009_server_becomes_stopped(self):
776
        """Test server becomes STOPPED"""
777

    
778
        log.info("Waiting until server becomes STOPPED")
779
        self._insist_on_status_transition(
780
            "ACTIVE", "STOPPED", self.action_timeout, self.action_timeout)
781

    
782
    def test_010_submit_start_request(self):
783
        """Test submit start server request"""
784

    
785
        log.info("Starting server")
786

    
787
        self.cyclades.start_server(self.serverid)
788

    
789
    def test_011_server_becomes_active(self):
790
        """Test server becomes ACTIVE again"""
791

    
792
        log.info("Waiting until server becomes ACTIVE")
793
        self._insist_on_status_transition(
794
            "STOPPED", "ACTIVE", self.action_timeout, self.action_timeout)
795

    
796
    def test_011a_server_responds_to_ping_IPv4(self):
797
        """Test server OS is actually up and running again"""
798

    
799
        log.info("Testing if server is actually up and running")
800

    
801
        self.test_006_server_responds_to_ping_IPv4()
802

    
803
    def test_012_ssh_to_server_IPv4(self):
804
        """Test SSH to server public IPv4 works, verify hostname"""
805

    
806
        self._skipIf(self.is_windows, "only valid for Linux servers")
807
        server = self.client.get_server_details(self.serverid)
808
        self._insist_on_ssh_hostname(self._get_ipv4(server),
809
                                     self.username, self.passwd)
810

    
811
    def test_013_ssh_to_server_IPv6(self):
812
        """Test SSH to server public IPv6 works, verify hostname"""
813
        self._skipIf(self.is_windows, "only valid for Linux servers")
814
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
815

    
816
        server = self.client.get_server_details(self.serverid)
817
        self._insist_on_ssh_hostname(self._get_ipv6(server),
818
                                     self.username, self.passwd)
819

    
820
    def test_014_rdp_to_server_IPv4(self):
821
        "Test RDP connection to server public IPv4 works"""
822
        self._skipIf(not self.is_windows, "only valid for Windows servers")
823
        server = self.client.get_server_details(self.serverid)
824
        ipv4 = self._get_ipv4(server)
825
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
826

    
827
        # No actual RDP processing done. We assume the RDP server is there
828
        # if the connection to the RDP port is successful.
829
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
830
        sock.close()
831

    
832
    def test_015_rdp_to_server_IPv6(self):
833
        "Test RDP connection to server public IPv6 works"""
834
        self._skipIf(not self.is_windows, "only valid for Windows servers")
835
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
836

    
837
        server = self.client.get_server_details(self.serverid)
838
        ipv6 = self._get_ipv6(server)
839
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
840

    
841
        # No actual RDP processing done. We assume the RDP server is there
842
        # if the connection to the RDP port is successful.
843
        sock.close()
844

    
845
    def test_016_personality_is_enforced(self):
846
        """Test file injection for personality enforcement"""
847
        self._skipIf(self.is_windows, "only implemented for Linux servers")
848
        self._skipIf(self.personality is None, "No personality file selected")
849

    
850
        log.info("Trying to inject file for personality enforcement")
851

    
852
        server = self.client.get_server_details(self.serverid)
853

    
854
        for inj_file in self.personality:
855
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
856
                                                       inj_file['owner'],
857
                                                       self.passwd,
858
                                                       inj_file['path'],
859
                                                       inj_file['contents'])
860
            self.assertTrue(equal_files)
861

    
862
    def test_017_submit_delete_request(self):
863
        """Test submit request to delete server"""
864

    
865
        log.info("Deleting server")
866

    
867
        self.client.delete_server(self.serverid)
868

    
869
    def test_018_server_becomes_deleted(self):
870
        """Test server becomes DELETED"""
871

    
872
        log.info("Testing if server becomes DELETED")
873

    
874
        self._insist_on_status_transition(
875
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
876

    
877
    def test_019_server_no_longer_in_server_list(self):
878
        """Test server is no longer in server list"""
879

    
880
        log.info("Test if server is no longer listed")
881

    
882
        servers = self.client.list_servers()
883
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
884

    
885

    
886
class NetworkTestCase(unittest.TestCase):
887
    """ Testing networking in cyclades """
888

    
889
    @classmethod
890
    def setUpClass(cls):
891
        "Initialize kamaki, get list of current networks"
892

    
893
        cls.client = CycladesClient(API, TOKEN)
894
        cls.compute = ComputeClient(API, TOKEN)
895

    
896
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
897
                                          TEST_RUN_ID,
898
                                          cls.imagename)
899

    
900
        #Dictionary initialization for the vms credentials
901
        cls.serverid = dict()
902
        cls.username = dict()
903
        cls.password = dict()
904
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
905

    
906
        cls.result_dict = dict()
907

    
908
    def _skipIf(self, condition, msg):
909
        if condition:
910
            self.skipTest(msg)
911

    
912
    def _get_ipv4(self, server):
913
        """Get the public IPv4 of a server from the detailed server info"""
914

    
915
        nics = server["attachments"]["values"]
916

    
917
        for nic in nics:
918
            net_id = nic["network_id"]
919
            if self.client.get_network_details(net_id)["public"]:
920
                public_addrs = nic["ipv4"]
921

    
922
        self.assertTrue(public_addrs is not None)
923

    
924
        return public_addrs
925

    
926
    def _connect_loginname(self, os_value):
927
        """Return the login name for connections based on the server OS"""
928
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
929
            return "user"
930
        elif os_value in ("windows", "windows_alpha1"):
931
            return "Administrator"
932
        else:
933
            return "root"
934

    
935
    def _ping_once(self, ip):
936

    
937
        """Test server responds to a single IPv4 or IPv6 ping"""
938
        cmd = "ping -c 2 -w 3 %s" % (ip)
939
        ping = subprocess.Popen(cmd, shell=True,
940
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
941
        (stdout, stderr) = ping.communicate()
942
        ret = ping.wait()
943

    
944
        return (ret == 0)
945

    
946
    def test_00001a_submit_create_server_A(self):
947
        """Test submit create server request"""
948

    
949
        log.info("Creating test server A")
950

    
951
        serverA = self.client.create_server(self.servername, self.flavorid,
952
                                            self.imageid, personality=None)
953

    
954
        self.assertEqual(serverA["name"], self.servername)
955
        self.assertEqual(serverA["flavorRef"], self.flavorid)
956
        self.assertEqual(serverA["imageRef"], self.imageid)
957
        self.assertEqual(serverA["status"], "BUILD")
958

    
959
        # Update class attributes to reflect data on building server
960
        self.serverid['A'] = serverA["id"]
961
        self.username['A'] = None
962
        self.password['A'] = serverA["adminPass"]
963

    
964
        log.info("Server A id:" + str(serverA["id"]))
965
        log.info("Server password " + (self.password['A']))
966

    
967
        self.result_dict["Server A ID"] = str(serverA["id"])
968
        self.result_dict["Server A password"] = serverA["adminPass"]
969

    
970
    def test_00001b_serverA_becomes_active(self):
971
        """Test server becomes ACTIVE"""
972

    
973
        log.info("Waiting until test server A becomes ACTIVE")
974
        self.result_dict.clear()
975

    
976
        fail_tmout = time.time() + self.action_timeout
977
        while True:
978
            d = self.client.get_server_details(self.serverid['A'])
979
            status = d['status']
980
            if status == 'ACTIVE':
981
                active = True
982
                break
983
            elif time.time() > fail_tmout:
984
                self.assertLess(time.time(), fail_tmout)
985
            else:
986
                time.sleep(self.query_interval)
987

    
988
        self.assertTrue(active)
989

    
990
    def test_00002a_submit_create_server_B(self):
991
        """Test submit create server request"""
992

    
993
        log.info("Creating test server B")
994

    
995
        serverB = self.client.create_server(self.servername, self.flavorid,
996
                                            self.imageid, personality=None)
997

    
998
        self.assertEqual(serverB["name"], self.servername)
999
        self.assertEqual(serverB["flavorRef"], self.flavorid)
1000
        self.assertEqual(serverB["imageRef"], self.imageid)
1001
        self.assertEqual(serverB["status"], "BUILD")
1002

    
1003
        # Update class attributes to reflect data on building server
1004
        self.serverid['B'] = serverB["id"]
1005
        self.username['B'] = None
1006
        self.password['B'] = serverB["adminPass"]
1007

    
1008
        log.info("Server B id: " + str(serverB["id"]))
1009
        log.info("Password " + (self.password['B']))
1010

    
1011
        self.result_dict.clear()
1012
        self.result_dict["Server B ID"] = str(serverB["id"])
1013
        self.result_dict["Server B password"] = serverB["adminPass"]
1014

    
1015
    def test_00002b_serverB_becomes_active(self):
1016
        """Test server becomes ACTIVE"""
1017

    
1018
        log.info("Waiting until test server B becomes ACTIVE")
1019
        self.result_dict.clear()
1020

    
1021
        fail_tmout = time.time() + self.action_timeout
1022
        while True:
1023
            d = self.client.get_server_details(self.serverid['B'])
1024
            status = d['status']
1025
            if status == 'ACTIVE':
1026
                active = True
1027
                break
1028
            elif time.time() > fail_tmout:
1029
                self.assertLess(time.time(), fail_tmout)
1030
            else:
1031
                time.sleep(self.query_interval)
1032

    
1033
        self.assertTrue(active)
1034

    
1035
    def test_001_create_network(self):
1036
        """Test submit create network request"""
1037

    
1038
        log.info("Submit new network request")
1039
        self.result_dict.clear()
1040

    
1041
        name = SNF_TEST_PREFIX + TEST_RUN_ID
1042
        #previous_num = len(self.client.list_networks())
1043
        network = self.client.create_network(name, cidr='10.0.0.1/28')
1044

    
1045
        #Test if right name is assigned
1046
        self.assertEqual(network['name'], name)
1047

    
1048
        # Update class attributes
1049
        cls = type(self)
1050
        cls.networkid = network['id']
1051
        #networks = self.client.list_networks()
1052

    
1053
        fail_tmout = time.time() + self.action_timeout
1054

    
1055
        #Test if new network is created
1056
        while True:
1057
            d = self.client.get_network_details(network['id'])
1058
            if d['status'] == 'ACTIVE':
1059
                connected = True
1060
                break
1061
            elif time.time() > fail_tmout:
1062
                self.assertLess(time.time(), fail_tmout)
1063
            else:
1064
                log.info("Waiting for network to become ACTIVE")
1065
                time.sleep(self.query_interval)
1066

    
1067
        self.assertTrue(connected)
1068

    
1069
        self.result_dict["Private network ID"] = str(network['id'])
1070

    
1071
    def test_002_connect_to_network(self):
1072
        """Test connect VMs to network"""
1073

    
1074
        log.info("Connect VMs to private network")
1075
        self.result_dict.clear()
1076

    
1077
        self.client.connect_server(self.serverid['A'], self.networkid)
1078
        self.client.connect_server(self.serverid['B'], self.networkid)
1079

    
1080
        #Insist on connecting until action timeout
1081
        fail_tmout = time.time() + self.action_timeout
1082

    
1083
        while True:
1084

    
1085
            netsA = [x['network_id']
1086
                     for x in self.client.get_server_details(
1087
                         self.serverid['A'])['attachments']['values']]
1088
            netsB = [x['network_id']
1089
                     for x in self.client.get_server_details(
1090
                         self.serverid['B'])['attachments']['values']]
1091

    
1092
            if (self.networkid in netsA) and (self.networkid in netsB):
1093
                conn_exists = True
1094
                break
1095
            elif time.time() > fail_tmout:
1096
                self.assertLess(time.time(), fail_tmout)
1097
            else:
1098
                time.sleep(self.query_interval)
1099

    
1100
        #Adding private IPs to class attributes
1101
        cls = type(self)
1102
        cls.priv_ip = dict()
1103

    
1104
        nicsA = self.client.get_server_details(
1105
            self.serverid['A'])['attachments']['values']
1106
        nicsB = self.client.get_server_details(
1107
            self.serverid['B'])['attachments']['values']
1108

    
1109
        if conn_exists:
1110
            for nic in nicsA:
1111
                if nic["network_id"] == self.networkid:
1112
                    cls.priv_ip["A"] = nic["ipv4"]
1113

    
1114
            for nic in nicsB:
1115
                if nic["network_id"] == self.networkid:
1116
                    cls.priv_ip["B"] = nic["ipv4"]
1117

    
1118
        self.assertTrue(conn_exists)
1119

    
1120
    def test_002a_reboot(self):
1121
        """Rebooting server A"""
1122

    
1123
        log.info("Rebooting server A")
1124

    
1125
        self.client.shutdown_server(self.serverid['A'])
1126

    
1127
        fail_tmout = time.time() + self.action_timeout
1128
        while True:
1129
            d = self.client.get_server_details(self.serverid['A'])
1130
            status = d['status']
1131
            if status == 'STOPPED':
1132
                break
1133
            elif time.time() > fail_tmout:
1134
                self.assertLess(time.time(), fail_tmout)
1135
            else:
1136
                time.sleep(self.query_interval)
1137

    
1138
        self.client.start_server(self.serverid['A'])
1139

    
1140
        while True:
1141
            d = self.client.get_server_details(self.serverid['A'])
1142
            status = d['status']
1143
            if status == 'ACTIVE':
1144
                active = True
1145
                break
1146
            elif time.time() > fail_tmout:
1147
                self.assertLess(time.time(), fail_tmout)
1148
            else:
1149
                time.sleep(self.query_interval)
1150

    
1151
        self.assertTrue(active)
1152

    
1153
    def test_002b_ping_server_A(self):
1154
        "Test if server A responds to IPv4 pings"
1155

    
1156
        log.info("Testing if server A responds to IPv4 pings ")
1157
        self.result_dict.clear()
1158

    
1159
        server = self.client.get_server_details(self.serverid['A'])
1160
        ip = self._get_ipv4(server)
1161

    
1162
        fail_tmout = time.time() + self.action_timeout
1163

    
1164
        s = False
1165

    
1166
        self.result_dict["Server A public IP"] = str(ip)
1167

    
1168
        while True:
1169

    
1170
            if self._ping_once(ip):
1171
                s = True
1172
                break
1173

    
1174
            elif time.time() > fail_tmout:
1175
                self.assertLess(time.time(), fail_tmout)
1176

    
1177
            else:
1178
                time.sleep(self.query_interval)
1179

    
1180
        self.assertTrue(s)
1181

    
1182
    def test_002c_reboot(self):
1183
        """Reboot server B"""
1184

    
1185
        log.info("Rebooting server B")
1186
        self.result_dict.clear()
1187

    
1188
        self.client.shutdown_server(self.serverid['B'])
1189

    
1190
        fail_tmout = time.time() + self.action_timeout
1191
        while True:
1192
            d = self.client.get_server_details(self.serverid['B'])
1193
            status = d['status']
1194
            if status == 'STOPPED':
1195
                break
1196
            elif time.time() > fail_tmout:
1197
                self.assertLess(time.time(), fail_tmout)
1198
            else:
1199
                time.sleep(self.query_interval)
1200

    
1201
        self.client.start_server(self.serverid['B'])
1202

    
1203
        while True:
1204
            d = self.client.get_server_details(self.serverid['B'])
1205
            status = d['status']
1206
            if status == 'ACTIVE':
1207
                active = True
1208
                break
1209
            elif time.time() > fail_tmout:
1210
                self.assertLess(time.time(), fail_tmout)
1211
            else:
1212
                time.sleep(self.query_interval)
1213

    
1214
        self.assertTrue(active)
1215

    
1216
    def test_002d_ping_server_B(self):
1217
        """Test if server B responds to IPv4 pings"""
1218

    
1219
        log.info("Testing if server B responds to IPv4 pings")
1220
        self.result_dict.clear()
1221

    
1222
        server = self.client.get_server_details(self.serverid['B'])
1223
        ip = self._get_ipv4(server)
1224

    
1225
        fail_tmout = time.time() + self.action_timeout
1226

    
1227
        s = False
1228

    
1229
        self.result_dict["Server B public IP"] = str(ip)
1230

    
1231
        while True:
1232
            if self._ping_once(ip):
1233
                s = True
1234
                break
1235

    
1236
            elif time.time() > fail_tmout:
1237
                self.assertLess(time.time(), fail_tmout)
1238

    
1239
            else:
1240
                time.sleep(self.query_interval)
1241

    
1242
        self.assertTrue(s)
1243

    
1244
    def test_003a_setup_interface_A(self):
1245
        """Set up eth1 for server A"""
1246

    
1247
        self._skipIf(self.is_windows, "only valid for Linux servers")
1248

    
1249
        log.info("Setting up interface eth1 for server A")
1250
        self.result_dict.clear()
1251

    
1252
        server = self.client.get_server_details(self.serverid['A'])
1253
        image = self.client.get_image_details(self.imageid)
1254
        os_value = image['metadata']['values']['os']
1255

    
1256
        users = image["metadata"]["values"].get("users", None)
1257
        userlist = users.split()
1258

    
1259
        if "root" in userlist:
1260
            loginname = "root"
1261
        elif users is None:
1262
            loginname = self._connect_loginname(os_value)
1263
        else:
1264
            loginname = choice(userlist)
1265

    
1266
        hostip = self._get_ipv4(server)
1267
        myPass = self.password['A']
1268

    
1269
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1270
        command = "ifconfig eth1 %s" % self.priv_ip["A"]
1271
        output, status = _ssh_execute(
1272
            hostip, loginname, myPass, command)
1273

    
1274
        self.assertEquals(status, 0)
1275

    
1276
    def test_003b_setup_interface_B(self):
1277
        """Setup eth1 for server B"""
1278

    
1279
        self._skipIf(self.is_windows, "only valid for Linux servers")
1280

    
1281
        log.info("Setting up interface eth1 for server B")
1282

    
1283
        server = self.client.get_server_details(self.serverid['B'])
1284
        image = self.client.get_image_details(self.imageid)
1285
        os_value = image['metadata']['values']['os']
1286

    
1287
        users = image["metadata"]["values"].get("users", None)
1288
        userlist = users.split()
1289

    
1290
        if "root" in userlist:
1291
            loginname = "root"
1292
        elif users is None:
1293
            loginname = self._connect_loginname(os_value)
1294
        else:
1295
            loginname = choice(userlist)
1296

    
1297
        hostip = self._get_ipv4(server)
1298
        myPass = self.password['B']
1299

    
1300
        log.info("SSH in server B as %s/%s" % (loginname, myPass))
1301
        command = "ifconfig eth1 %s" % self.priv_ip["B"]
1302
        output, status = _ssh_execute(
1303
            hostip, loginname, myPass, command)
1304

    
1305
        self.assertEquals(status, 0)
1306

    
1307
    def test_003c_test_connection_exists(self):
1308
        """Ping server B from server A to test if connection exists"""
1309

    
1310
        self._skipIf(self.is_windows, "only valid for Linux servers")
1311

    
1312
        log.info("Testing if server A is actually connected to server B")
1313

    
1314
        server = self.client.get_server_details(self.serverid['A'])
1315
        image = self.client.get_image_details(self.imageid)
1316
        os_value = image['metadata']['values']['os']
1317
        hostip = self._get_ipv4(server)
1318

    
1319
        users = image["metadata"]["values"].get("users", None)
1320
        userlist = users.split()
1321

    
1322
        if "root" in userlist:
1323
            loginname = "root"
1324
        elif users is None:
1325
            loginname = self._connect_loginname(os_value)
1326
        else:
1327
            loginname = choice(userlist)
1328

    
1329
        myPass = self.password['A']
1330

    
1331
        cmd = "if ping -c 2 -w 3 %s >/dev/null; \
1332
               then echo \'True\'; fi;" % self.priv_ip["B"]
1333
        lines, status = _ssh_execute(
1334
            hostip, loginname, myPass, cmd)
1335

    
1336
        exists = False
1337

    
1338
        if 'True\n' in lines:
1339
            exists = True
1340

    
1341
        self.assertTrue(exists)
1342

    
1343
    def test_004_disconnect_from_network(self):
1344
        "Disconnecting server A and B from network"
1345

    
1346
        log.info("Disconnecting servers from private network")
1347

    
1348
        prev_state = self.client.get_network_details(self.networkid)
1349
        prev_nics = prev_state['attachments']['values']
1350
        #prev_conn = len(prev_nics)
1351

    
1352
        nicsA = [x['id']
1353
                 for x in self.client.get_server_details(
1354
                     self.serverid['A'])['attachments']['values']]
1355
        nicsB = [x['id']
1356
                 for x in self.client.get_server_details(
1357
                     self.serverid['B'])['attachments']['values']]
1358

    
1359
        for nic in prev_nics:
1360
            if nic in nicsA:
1361
                self.client.disconnect_server(self.serverid['A'], nic)
1362
            if nic in nicsB:
1363
                self.client.disconnect_server(self.serverid['B'], nic)
1364

    
1365
        #Insist on deleting until action timeout
1366
        fail_tmout = time.time() + self.action_timeout
1367

    
1368
        while True:
1369
            netsA = [x['network_id']
1370
                     for x in self.client.get_server_details(
1371
                         self.serverid['A'])['attachments']['values']]
1372
            netsB = [x['network_id']
1373
                     for x in self.client.get_server_details(
1374
                         self.serverid['B'])['attachments']['values']]
1375

    
1376
            #connected = (self.client.get_network_details(self.networkid))
1377
            #connections = connected['attachments']['values']
1378
            if (self.networkid not in netsA) and (self.networkid not in netsB):
1379
                conn_exists = False
1380
                break
1381
            elif time.time() > fail_tmout:
1382
                self.assertLess(time.time(), fail_tmout)
1383
            else:
1384
                time.sleep(self.query_interval)
1385

    
1386
        self.assertFalse(conn_exists)
1387

    
1388
    def test_005_destroy_network(self):
1389
        """Test submit delete network request"""
1390

    
1391
        log.info("Submitting delete network request")
1392

    
1393
        self.client.delete_network(self.networkid)
1394

    
1395
        fail_tmout = time.time() + self.action_timeout
1396

    
1397
        while True:
1398

    
1399
            curr_net = []
1400
            networks = self.client.list_networks()
1401

    
1402
            for net in networks:
1403
                curr_net.append(net['id'])
1404

    
1405
            if self.networkid not in curr_net:
1406
                self.assertTrue(self.networkid not in curr_net)
1407
                break
1408

    
1409
            elif time.time() > fail_tmout:
1410
                self.assertLess(time.time(), fail_tmout)
1411

    
1412
            else:
1413
                time.sleep(self.query_interval)
1414

    
1415
    def test_006_cleanup_servers(self):
1416
        """Cleanup servers created for this test"""
1417

    
1418
        log.info("Delete servers created for this test")
1419

    
1420
        self.compute.delete_server(self.serverid['A'])
1421
        self.compute.delete_server(self.serverid['B'])
1422

    
1423
        fail_tmout = time.time() + self.action_timeout
1424

    
1425
        #Ensure server gets deleted
1426
        status = dict()
1427

    
1428
        while True:
1429
            details = self.compute.get_server_details(self.serverid['A'])
1430
            status['A'] = details['status']
1431
            details = self.compute.get_server_details(self.serverid['B'])
1432
            status['B'] = details['status']
1433
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1434
                deleted = True
1435
                break
1436
            elif time.time() > fail_tmout:
1437
                self.assertLess(time.time(), fail_tmout)
1438
            else:
1439
                time.sleep(self.query_interval)
1440

    
1441
        self.assertTrue(deleted)
1442

    
1443

    
1444
class TestRunnerProcess(Process):
1445
    """A distinct process used to execute part of the tests in parallel"""
1446
    def __init__(self, **kw):
1447
        Process.__init__(self, **kw)
1448
        kwargs = kw["kwargs"]
1449
        self.testq = kwargs["testq"]
1450
        self.worker_folder = kwargs["worker_folder"]
1451

    
1452
    def run(self):
1453
        # Make sure this test runner process dies with the parent
1454
        # and is not left behind.
1455
        #
1456
        # WARNING: This uses the prctl(2) call and is
1457
        # Linux-specific.
1458

    
1459
        prctl.set_pdeathsig(signal.SIGHUP)
1460

    
1461
        multi = logging.getLogger("multiprocess")
1462

    
1463
        while True:
1464
            multi.debug("I am process %d, GETting from queue is %s" %
1465
                        (os.getpid(), self.testq))
1466
            msg = self.testq.get()
1467

    
1468
            multi.debug("Dequeued msg: %s" % msg)
1469

    
1470
            if msg == "TEST_RUNNER_TERMINATE":
1471
                raise SystemExit
1472

    
1473
            elif issubclass(msg, unittest.TestCase):
1474
                # Assemble a TestSuite, and run it
1475

    
1476
                log_file = os.path.join(self.worker_folder, 'details_' +
1477
                                        (msg.__name__) + "_" +
1478
                                        TEST_RUN_ID + '.log')
1479

    
1480
                fail_file = os.path.join(self.worker_folder, 'failed_' +
1481
                                         (msg.__name__) + "_" +
1482
                                         TEST_RUN_ID + '.log')
1483
                error_file = os.path.join(self.worker_folder, 'error_' +
1484
                                          (msg.__name__) + "_" +
1485
                                          TEST_RUN_ID + '.log')
1486

    
1487
                f = open(log_file, 'w')
1488
                fail = open(fail_file, 'w')
1489
                error = open(error_file, 'w')
1490

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

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

    
1499
                for res in result.errors:
1500
                    log.error("snf-burnin encountered an error in "
1501
                              "testcase: %s" % msg)
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" % msg)
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 not NOFAILFAST:
1514
                        sys.exit()
1515

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

    
1519
                f.close()
1520
                fail.close()
1521
                error.close()
1522

    
1523
            else:
1524
                raise Exception("Cannot handle msg: %s" % msg)
1525

    
1526

    
1527
def _run_cases_in_series(cases, image_folder):
1528
    """Run instances of TestCase in series"""
1529

    
1530
    for case in cases:
1531

    
1532
        test = case.__name__
1533

    
1534
        log.info(yellow + '* Starting testcase: %s' % test + normal)
1535
        log_file = os.path.join(image_folder, 'details_' +
1536
                                (case.__name__) + "_" +
1537
                                TEST_RUN_ID + '.log')
1538
        fail_file = os.path.join(image_folder, 'failed_' +
1539
                                 (case.__name__) + "_" +
1540
                                 TEST_RUN_ID + '.log')
1541
        error_file = os.path.join(image_folder, 'error_' +
1542
                                  (case.__name__) + "_" +
1543
                                  TEST_RUN_ID + '.log')
1544

    
1545
        f = open(log_file, "w")
1546
        fail = open(fail_file, "w")
1547
        error = open(error_file, "w")
1548

    
1549
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1550
        runner = unittest.TextTestRunner(
1551
            f, verbosity=2, failfast=True,
1552
            resultclass=BurninTestResult)
1553
        result = runner.run(suite)
1554

    
1555
        for res in result.errors:
1556
            log.error("snf-burnin encountered an error in "
1557
                      "testcase: %s" % test)
1558
            log.error("See log for details")
1559
            error.write(str(res[0]) + '\n')
1560
            error.write(str(res[0].shortDescription()) + '\n')
1561
            error.write('\n')
1562

    
1563
        for res in result.failures:
1564
            log.error("snf-burnin failed in testcase: %s" % test)
1565
            log.error("See log for details")
1566
            fail.write(str(res[0]) + '\n')
1567
            fail.write(str(res[0].shortDescription()) + '\n')
1568
            fail.write('\n')
1569
            if not NOFAILFAST:
1570
                sys.exit()
1571

    
1572
        if (len(result.failures) == 0) and (len(result.errors) == 0):
1573
            log.debug("Passed testcase: %s" % test)
1574

    
1575

    
1576
def _run_cases_in_parallel(cases, fanout, image_folder):
1577
    """Run instances of TestCase in parallel, in a number of distinct processes
1578

1579
    The cases iterable specifies the TestCases to be executed in parallel,
1580
    by test runners running in distinct processes.
1581
    The fanout parameter specifies the number of processes to spawn,
1582
    and defaults to 1.
1583
    The runner argument specifies the test runner class to use inside each
1584
    runner process.
1585

1586
    """
1587

    
1588
    multi = logging.getLogger("multiprocess")
1589
    handler = logging.StreamHandler()
1590
    multi.addHandler(handler)
1591

    
1592
    if VERBOSE:
1593
        multi.setLevel(logging.DEBUG)
1594
    else:
1595
        multi.setLevel(logging.INFO)
1596

    
1597
    testq = []
1598
    worker_folder = []
1599
    runners = []
1600

    
1601
    for i in xrange(0, fanout):
1602
        testq.append(Queue())
1603
        worker_folder.append(os.path.join(image_folder, 'process'+str(i)))
1604
        os.mkdir(worker_folder[i])
1605

    
1606
    for i in xrange(0, fanout):
1607
        kwargs = dict(testq=testq[i], worker_folder=worker_folder[i])
1608
        runners.append(TestRunnerProcess(kwargs=kwargs))
1609

    
1610
    multi.debug("Spawning %d test runner processes" % len(runners))
1611

    
1612
    for p in runners:
1613
        p.start()
1614

    
1615
    # Enqueue test cases
1616
    for i in xrange(0, fanout):
1617
        map(testq[i].put, cases)
1618
        testq[i].put("TEST_RUNNER_TERMINATE")
1619

    
1620
    multi.debug("Spawned %d test runners, PIDs are %s" %
1621
                (len(runners), [p.pid for p in runners]))
1622

    
1623
    multi.debug("Joining %d processes" % len(runners))
1624

    
1625
    for p in runners:
1626
        p.join()
1627

    
1628
    multi.debug("Done joining %d processes" % len(runners))
1629

    
1630

    
1631
def _spawn_server_test_case(**kwargs):
1632
    """Construct a new unit test case class from SpawnServerTestCase"""
1633

    
1634
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1635
    cls = type(name, (SpawnServerTestCase,), kwargs)
1636

    
1637
    # Patch extra parameters into test names by manipulating method docstrings
1638
    for (mname, m) in \
1639
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1640
        if hasattr(m, __doc__):
1641
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1642

    
1643
    # Make sure the class can be pickled, by listing it among
1644
    # the attributes of __main__. A PicklingError is raised otherwise.
1645

    
1646
    thismodule = sys.modules[__name__]
1647
    setattr(thismodule, name, cls)
1648
    return cls
1649

    
1650

    
1651
def _spawn_network_test_case(**kwargs):
1652
    """Construct a new unit test case class from NetworkTestCase"""
1653

    
1654
    name = "NetworkTestCase" + TEST_RUN_ID
1655
    cls = type(name, (NetworkTestCase,), kwargs)
1656

    
1657
    # Make sure the class can be pickled, by listing it among
1658
    # the attributes of __main__. A PicklingError is raised otherwise.
1659

    
1660
    thismodule = sys.modules[__name__]
1661
    setattr(thismodule, name, cls)
1662
    return cls
1663

    
1664

    
1665
# --------------------------------------------------------------------
1666
# Clean up servers/networks functions
1667
def cleanup_servers(timeout, query_interval, delete_stale=False):
1668

    
1669
    c = ComputeClient(API, TOKEN)
1670

    
1671
    servers = c.list_servers()
1672
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1673

    
1674
    if len(stale) == 0:
1675
        return
1676

    
1677
    # Show staled servers
1678
    print >>sys.stderr, yellow + \
1679
        "Found these stale servers from previous runs:" + \
1680
        normal
1681
    print >>sys.stderr, "    " + \
1682
        "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1683

    
1684
    # Delete staled servers
1685
    if delete_stale:
1686
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1687
        fail_tmout = time.time() + timeout
1688
        for s in stale:
1689
            c.delete_server(s["id"])
1690
        # Wait for all servers to be deleted
1691
        while True:
1692
            servers = c.list_servers()
1693
            stale = [s for s in servers
1694
                     if s["name"].startswith(SNF_TEST_PREFIX)]
1695
            if len(stale) == 0:
1696
                print >> sys.stderr, green + "    ...done" + normal
1697
                break
1698
            elif time.time() > fail_tmout:
1699
                print >> sys.stderr, red + \
1700
                    "Not all stale servers deleted. Action timed out." + \
1701
                    normal
1702
                sys.exit(1)
1703
            else:
1704
                time.sleep(query_interval)
1705
    else:
1706
        print >> sys.stderr, "Use --delete-stale to delete them."
1707

    
1708

    
1709
def cleanup_networks(action_timeout, query_interval, delete_stale=False):
1710

    
1711
    c = CycladesClient(API, TOKEN)
1712

    
1713
    networks = c.list_networks()
1714
    stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1715

    
1716
    if len(stale) == 0:
1717
        return
1718

    
1719
    # Show staled networks
1720
    print >> sys.stderr, yellow + \
1721
        "Found these stale networks from previous runs:" + \
1722
        normal
1723
    print "    " + \
1724
        "\n    ".join(["%s: %s" % (str(n["id"]), n["name"]) for n in stale])
1725

    
1726
    # Delete staled networks
1727
    if delete_stale:
1728
        print >> sys.stderr, "Deleting %d stale networks:" % len(stale)
1729
        fail_tmout = time.time() + action_timeout
1730
        for n in stale:
1731
            c.delete_network(n["id"])
1732
        # Wait for all networks to be deleted
1733
        while True:
1734
            networks = c.list_networks()
1735
            stale = [n for n in networks
1736
                     if n["name"].startswith(SNF_TEST_PREFIX)]
1737
            if len(stale) == 0:
1738
                print >> sys.stderr, green + "    ...done" + normal
1739
                break
1740
            elif time.time() > fail_tmout:
1741
                print >> sys.stderr, red + \
1742
                    "Not all stale networks deleted. Action timed out." + \
1743
                    normal
1744
                sys.exit(1)
1745
            else:
1746
                time.sleep(query_interval)
1747
    else:
1748
        print >> sys.stderr, "Use --delete-stale to delete them."
1749

    
1750

    
1751
# --------------------------------------------------------------------
1752
# Parse arguments functions
1753
def parse_comma(option, opt, value, parser):
1754
    tests = set(['all', 'auth', 'images', 'flavors',
1755
                 'pithos', 'servers', 'server_spawn',
1756
                 'network_spawn'])
1757
    parse_input = value.split(',')
1758

    
1759
    if not (set(parse_input)).issubset(tests):
1760
        raise OptionValueError("The selected set of tests is invalid")
1761

    
1762
    setattr(parser.values, option.dest, value.split(','))
1763

    
1764

    
1765
def parse_arguments(args):
1766

    
1767
    kw = {}
1768
    kw["usage"] = "%prog [options]"
1769
    kw["description"] = \
1770
        "%prog runs a number of test scenarios on a " \
1771
        "Synnefo deployment."
1772

    
1773
    parser = OptionParser(**kw)
1774
    parser.disable_interspersed_args()
1775

    
1776
    parser.add_option("--api",
1777
                      action="store", type="string", dest="api",
1778
                      help="The API URI to use to reach the Synnefo API",
1779
                      default=None)
1780
    parser.add_option("--plankton",
1781
                      action="store", type="string", dest="plankton",
1782
                      help="The API URI to use to reach the Plankton API",
1783
                      default=None)
1784
    parser.add_option("--plankton-user",
1785
                      action="store", type="string", dest="plankton_user",
1786
                      help="Owner of system images",
1787
                      default=DEFAULT_PLANKTON_USER)
1788
    parser.add_option("--pithos",
1789
                      action="store", type="string", dest="pithos",
1790
                      help="The API URI to use to reach the Pithos API",
1791
                      default=None)
1792
    parser.add_option("--pithos_user",
1793
                      action="store", type="string", dest="pithos_user",
1794
                      help="Owner of the pithos account",
1795
                      default=None)
1796
    parser.add_option("--token",
1797
                      action="store", type="string", dest="token",
1798
                      help="The token to use for authentication to the API")
1799
    parser.add_option("--nofailfast",
1800
                      action="store_true", dest="nofailfast",
1801
                      help="Do not fail immediately if one of the tests "
1802
                           "fails (EXPERIMENTAL)",
1803
                      default=False)
1804
    parser.add_option("--no-ipv6",
1805
                      action="store_true", dest="no_ipv6",
1806
                      help="Disables ipv6 related tests",
1807
                      default=False)
1808
    parser.add_option("--action-timeout",
1809
                      action="store", type="int", dest="action_timeout",
1810
                      metavar="TIMEOUT",
1811
                      help="Wait SECONDS seconds for a server action to "
1812
                           "complete, then the test is considered failed",
1813
                      default=100)
1814
    parser.add_option("--build-warning",
1815
                      action="store", type="int", dest="build_warning",
1816
                      metavar="TIMEOUT",
1817
                      help="Warn if TIMEOUT seconds have passed and a "
1818
                           "build operation is still pending",
1819
                      default=600)
1820
    parser.add_option("--build-fail",
1821
                      action="store", type="int", dest="build_fail",
1822
                      metavar="BUILD_TIMEOUT",
1823
                      help="Fail the test if TIMEOUT seconds have passed "
1824
                           "and a build operation is still incomplete",
1825
                      default=900)
1826
    parser.add_option("--query-interval",
1827
                      action="store", type="int", dest="query_interval",
1828
                      metavar="INTERVAL",
1829
                      help="Query server status when requests are pending "
1830
                           "every INTERVAL seconds",
1831
                      default=3)
1832
    parser.add_option("--fanout",
1833
                      action="store", type="int", dest="fanout",
1834
                      metavar="COUNT",
1835
                      help="Spawn up to COUNT child processes to execute "
1836
                           "in parallel, essentially have up to COUNT "
1837
                           "server build requests outstanding (EXPERIMENTAL)",
1838
                      default=1)
1839
    parser.add_option("--force-flavor",
1840
                      action="store", type="int", dest="force_flavorid",
1841
                      metavar="FLAVOR ID",
1842
                      help="Force all server creations to use the specified "
1843
                           "FLAVOR ID instead of a randomly chosen one, "
1844
                           "useful if disk space is scarce",
1845
                      default=None)
1846
    parser.add_option("--image-id",
1847
                      action="store", type="string", dest="force_imageid",
1848
                      metavar="IMAGE ID",
1849
                      help="Test the specified image id, use 'all' to test "
1850
                           "all available images (mandatory argument)",
1851
                      default=None)
1852
    parser.add_option("--show-stale",
1853
                      action="store_true", dest="show_stale",
1854
                      help="Show stale servers from previous runs, whose "
1855
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1856
                      default=False)
1857
    parser.add_option("--delete-stale",
1858
                      action="store_true", dest="delete_stale",
1859
                      help="Delete stale servers from previous runs, whose "
1860
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1861
                      default=False)
1862
    parser.add_option("--force-personality",
1863
                      action="store", type="string", dest="personality_path",
1864
                      help="Force a personality file injection.\
1865
                            File path required. ",
1866
                      default=None)
1867
    parser.add_option("--log-folder",
1868
                      action="store", type="string", dest="log_folder",
1869
                      help="Define the absolute path where the output \
1870
                            log is stored. ",
1871
                      default="/var/log/burnin/")
1872
    parser.add_option("--verbose", "-V",
1873
                      action="store_true", dest="verbose",
1874
                      help="Print detailed output about multiple "
1875
                           "processes spawning",
1876
                      default=False)
1877
    parser.add_option("--set-tests",
1878
                      action="callback",
1879
                      dest="tests",
1880
                      type="string",
1881
                      help='Set comma seperated tests for this run. \
1882
                            Available tests: auth, images, flavors, \
1883
                                             servers, server_spawn, \
1884
                                             network_spawn, pithos. \
1885
                            Default = all',
1886
                      default='all',
1887
                      callback=parse_comma)
1888

    
1889
    (opts, args) = parser.parse_args(args)
1890

    
1891
    # -----------------------
1892
    # Verify arguments
1893

    
1894
    # `delete_stale' implies `show_stale'
1895
    if opts.delete_stale:
1896
        opts.show_stale = True
1897

    
1898
    # `token' is mandatory
1899
    _mandatory_argument(opts.token, "--token")
1900
    # `api' is mandatory
1901
    _mandatory_argument(opts.api, "--api")
1902

    
1903
    if not opts.show_stale:
1904
        # `image-id' is mandatory
1905
        _mandatory_argument(opts.force_imageid, "--image_id")
1906
        if opts.force_imageid != 'all':
1907
            try:
1908
                opts.force_imageid = str(opts.force_imageid)
1909
            except ValueError:
1910
                print >>sys.stderr, red + \
1911
                    "Invalid value specified for" + \
1912
                    "--image-id. Use a valid id, or `all'." + \
1913
                    normal
1914
                sys.exit(1)
1915
        # `pithos' is mandatory
1916
        _mandatory_argument(opts.pithos, "--pithos")
1917
        # `pithos_user' is mandatory
1918
        _mandatory_argument(opts.pithos_user, "--pithos_user")
1919
        # `plankton' is mandatory
1920
        _mandatory_argument(opts.plankton, "--plankton")
1921

    
1922
    return (opts, args)
1923

    
1924

    
1925
def _mandatory_argument(Arg, Str):
1926
    if not Arg:
1927
        print >>sys.stderr, red + \
1928
            "The " + Str + " argument is mandatory.\n" + \
1929
            normal
1930
        sys.exit(1)
1931

    
1932

    
1933
# --------------------------------------------------------------------
1934
# Burnin main function
1935
def main():
1936
    """Assemble test cases into a test suite, and run it
1937

1938
    IMPORTANT: Tests have dependencies and have to be run in the specified
1939
    order inside a single test case. They communicate through attributes of the
1940
    corresponding TestCase class (shared fixtures). Distinct subclasses of
1941
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
1942
    test runner processes.
1943

1944
    """
1945

    
1946
    # Parse arguments using `optparse'
1947
    (opts, args) = parse_arguments(sys.argv[1:])
1948

    
1949
    # Some global variables
1950
    global API, TOKEN, PLANKTON, PLANKTON_USER
1951
    global PITHOS, PITHOS_USER, NO_IPV6, VERBOSE, NOFAILFAST
1952
    API = opts.api
1953
    TOKEN = opts.token
1954
    PLANKTON = opts.plankton
1955
    PLANKTON_USER = opts.plankton_user
1956
    PITHOS = opts.pithos
1957
    PITHOS_USER = opts.pithos_user
1958
    NO_IPV6 = opts.no_ipv6
1959
    VERBOSE = opts.verbose
1960
    NOFAILFAST = opts.nofailfast
1961

    
1962
    # If `show_stale', cleanup stale servers
1963
    # from previous runs and exit
1964
    if opts.show_stale:
1965
        # We must clean the servers first
1966
        cleanup_servers(opts.action_timeout, opts.query_interval,
1967
                        delete_stale=opts.delete_stale)
1968
        cleanup_networks(opts.action_timeout, opts.query_interval,
1969
                         delete_stale=opts.delete_stale)
1970
        return 0
1971

    
1972
    # Initialize a kamaki instance, get flavors, images
1973
    c = ComputeClient(API, TOKEN)
1974
    DIMAGES = c.list_images(detail=True)
1975
    DFLAVORS = c.list_flavors(detail=True)
1976

    
1977
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
1978
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
1979
    #unittest.main(verbosity=2, catchbreak=True)
1980

    
1981
    # Get a list of images we are going to test
1982
    if opts.force_imageid == 'all':
1983
        test_images = DIMAGES
1984
    else:
1985
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1986

    
1987
    # Create output (logging) folder
1988
    if not os.path.exists(opts.log_folder):
1989
        os.mkdir(opts.log_folder)
1990
    test_folder = os.path.join(opts.log_folder, TEST_RUN_ID)
1991
    os.mkdir(test_folder)
1992

    
1993
    for image in test_images:
1994
        imageid = str(image["id"])
1995
        imagename = image["name"]
1996
        # Choose a flavor (given from user or random)
1997
        if opts.force_flavorid:
1998
            flavorid = opts.force_flavorid
1999
        else:
2000
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
2001
        # Personality dictionary for file injection test
2002
        if opts.personality_path is not None:
2003
            f = open(opts.personality_path)
2004
            content = b64encode(f.read())
2005
            personality = []
2006
            st = os.stat(opts.personality_path)
2007
            personality.append({
2008
                'path': '/root/test_inj_file',
2009
                'owner': 'root',
2010
                'group': 'root',
2011
                'mode': 0x7777 & st.st_mode,
2012
                'contents': content})
2013
        else:
2014
            personality = None
2015
        # Give a name to our test servers
2016
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
2017
        is_windows = imagename.lower().find("windows") >= 0
2018

    
2019
        # Create Server TestCases
2020
        ServerTestCase = _spawn_server_test_case(
2021
            imageid=imageid,
2022
            flavorid=flavorid,
2023
            imagename=imagename,
2024
            personality=personality,
2025
            servername=servername,
2026
            is_windows=is_windows,
2027
            action_timeout=opts.action_timeout,
2028
            build_warning=opts.build_warning,
2029
            build_fail=opts.build_fail,
2030
            query_interval=opts.query_interval)
2031
        # Create Network TestCases
2032
        NetworkTestCase = _spawn_network_test_case(
2033
            action_timeout=opts.action_timeout,
2034
            imageid=imageid,
2035
            flavorid=flavorid,
2036
            imagename=imagename,
2037
            query_interval=opts.query_interval)
2038

    
2039
        # Choose the tests we are going to run
2040
        test_dict = {'auth': UnauthorizedTestCase,
2041
                     'images': ImagesTestCase,
2042
                     'flavors': FlavorsTestCase,
2043
                     'servers': ServersTestCase,
2044
                     'pithos': PithosTestCase,
2045
                     'server_spawn': ServerTestCase,
2046
                     'network_spawn': NetworkTestCase}
2047
        seq_cases = []
2048
        if 'all' in opts.tests:
2049
            seq_cases = [UnauthorizedTestCase, ImagesTestCase,
2050
                         FlavorsTestCase, ServersTestCase,
2051
                         PithosTestCase, ServerTestCase,
2052
                         NetworkTestCase]
2053
        else:
2054
            for test in opts.tests:
2055
                seq_cases.append(test_dict[test])
2056

    
2057
        # Folder for each image
2058
        image_folder = os.path.join(test_folder, imageid)
2059
        os.mkdir(image_folder)
2060

    
2061
        # Run each test
2062
        if opts.fanout > 1:
2063
            _run_cases_in_parallel(seq_cases, opts.fanout, image_folder)
2064
        else:
2065
            _run_cases_in_series(seq_cases, image_folder)
2066

    
2067

    
2068
# --------------------------------------------------------------------
2069
# Call main
2070
if __name__ == "__main__":
2071
    sys.exit(main())