Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin.py @ 4d72f9ab

History | View | Annotate | Download (77.6 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
# This class gest replicated into Images TestCases dynamically
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
        # Create temp directory and store it inside our class
232
        # XXX: In my machine /tmp has not enough space
233
        #      so use current directory to be sure.
234
        cls.temp_dir = tempfile.mkdtemp(dir=os.getcwd())
235
        cls.temp_image_name = \
236
            SNF_TEST_PREFIX + cls.imageid + ".diskdump"
237

    
238
    @classmethod
239
    def tearDownClass(cls):
240
        """Remove local files"""
241
        try:
242
            temp_file = os.path.join(cls.temp_dir, cls.temp_image_name)
243
            os.unlink(temp_file)
244
        except:
245
            pass
246
        try:
247
            os.rmdir(cls.temp_dir)
248
        except:
249
            pass
250

    
251
    def test_001_list_images(self):
252
        """Test image list actually returns images"""
253
        self.assertGreater(len(self.images), 0)
254

    
255
    def test_002_list_images_detailed(self):
256
        """Test detailed image list is the same length as list"""
257
        self.assertEqual(len(self.dimages), len(self.images))
258

    
259
    def test_003_same_image_names(self):
260
        """Test detailed and simple image list contain same names"""
261
        names = sorted(map(lambda x: x["name"], self.images))
262
        dnames = sorted(map(lambda x: x["name"], self.dimages))
263
        self.assertEqual(names, dnames)
264

    
265
    def test_004_unique_image_names(self):
266
        """Test system images have unique names"""
267
        sys_images = filter(lambda x: x['owner'] == PLANKTON_USER,
268
                            self.dimages)
269
        names = sorted(map(lambda x: x["name"], sys_images))
270
        self.assertEqual(sorted(list(set(names))), names)
271

    
272
    def test_005_image_metadata(self):
273
        """Test every image has specific metadata defined"""
274
        keys = frozenset(["osfamily", "root_partition"])
275
        details = self.client.list_images(detail=True)
276
        for i in details:
277
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
278

    
279
    def test_006_download_image(self):
280
        """Download image from pithos+"""
281
        # Get image location
282
        image = filter(
283
            lambda x: x['id'] == self.imageid, self.dimages)[0]
284
        image_location = \
285
            image['location'].replace("://", " ").replace("/", " ").split()
286
        log.info("Download image, with owner %s\n\tcontainer %s, and name %s"
287
                 % (image_location[1], image_location[2], image_location[3]))
288
        pithos_client = PithosClient(PITHOS, TOKEN, image_location[1])
289
        pithos_client.container = image_location[2]
290
        temp_file = os.path.join(self.temp_dir, self.temp_image_name)
291
        with open(temp_file, "wb+") as f:
292
            pithos_client.download_object(image_location[3], f)
293

    
294
    def test_007_upload_image(self):
295
        """Upload and register image"""
296
        temp_file = os.path.join(self.temp_dir, self.temp_image_name)
297
        log.info("Upload image to pithos+")
298
        # Create container `images'
299
        pithos_client = PithosClient(PITHOS, TOKEN, PITHOS_USER)
300
        pithos_client.container = "images"
301
        pithos_client.container_put()
302
        with open(temp_file, "rb+") as f:
303
            pithos_client.upload_object(self.temp_image_name, f)
304
        log.info("Register image to plankton")
305
        location = "pithos://" + PITHOS_USER + \
306
            "/images/" + self.temp_image_name
307
        params = {'is_public': True}
308
        properties = {'OSFAMILY': "linux", 'ROOT_PARTITION': 1}
309
        self.plankton.register(self.temp_image_name, location,
310
                               params, properties)
311
        # Get image id
312
        details = self.plankton.list_public(detail=True)
313
        detail = filter(
314
            lambda x: x['owner'] == PITHOS_USER and
315
            x['name'] == self.temp_image_name,
316
            details)
317
        self.assertEqual(len(detail), 1)
318
        cls = type(self)
319
        cls.temp_image_id = detail[0]['id']
320
        log.info("Image registered with id %s" % detail[0]['id'])
321

    
322
    def test_008_cleanup_image(self):
323
        """Cleanup image test"""
324
        log.info("Cleanup image test")
325
        # Remove image from pithos+
326
        pithos_client = PithosClient(PITHOS, TOKEN, PITHOS_USER)
327
        pithos_client.container = "images"
328
        pithos_client.del_object(self.temp_image_name)
329

    
330

    
331
# --------------------------------------------------------------------
332
# FlavorsTestCase class
333
class FlavorsTestCase(unittest.TestCase):
334
    """Test flavor lists for consistency"""
335
    @classmethod
336
    def setUpClass(cls):
337
        """Initialize kamaki, get (detailed) list of flavors"""
338
        log.info("Getting simple and detailed list of flavors")
339
        cls.client = ComputeClient(API, TOKEN)
340
        cls.flavors = cls.client.list_flavors()
341
        cls.dflavors = cls.client.list_flavors(detail=True)
342
        cls.result_dict = dict()
343

    
344
    def test_001_list_flavors(self):
345
        """Test flavor list actually returns flavors"""
346
        self.assertGreater(len(self.flavors), 0)
347

    
348
    def test_002_list_flavors_detailed(self):
349
        """Test detailed flavor list is the same length as list"""
350
        self.assertEquals(len(self.dflavors), len(self.flavors))
351

    
352
    def test_003_same_flavor_names(self):
353
        """Test detailed and simple flavor list contain same names"""
354
        names = sorted(map(lambda x: x["name"], self.flavors))
355
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
356
        self.assertEqual(names, dnames)
357

    
358
    def test_004_unique_flavor_names(self):
359
        """Test flavors have unique names"""
360
        names = sorted(map(lambda x: x["name"], self.flavors))
361
        self.assertEqual(sorted(list(set(names))), names)
362

    
363
    def test_005_well_formed_flavor_names(self):
364
        """Test flavors have names of the form CxxRyyDzz
365
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
366
        """
367
        for f in self.dflavors:
368
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
369
                             f["name"],
370
                             "Flavor %s does not match its specs." % f["name"])
371

    
372

    
373
# --------------------------------------------------------------------
374
# ServersTestCase class
375
class ServersTestCase(unittest.TestCase):
376
    """Test server lists for consistency"""
377
    @classmethod
378
    def setUpClass(cls):
379
        """Initialize kamaki, get (detailed) list of servers"""
380
        log.info("Getting simple and detailed list of servers")
381

    
382
        cls.client = ComputeClient(API, TOKEN)
383
        cls.servers = cls.client.list_servers()
384
        cls.dservers = cls.client.list_servers(detail=True)
385
        cls.result_dict = dict()
386

    
387
    # def test_001_list_servers(self):
388
    #     """Test server list actually returns servers"""
389
    #     self.assertGreater(len(self.servers), 0)
390

    
391
    def test_002_list_servers_detailed(self):
392
        """Test detailed server list is the same length as list"""
393
        self.assertEqual(len(self.dservers), len(self.servers))
394

    
395
    def test_003_same_server_names(self):
396
        """Test detailed and simple flavor list contain same names"""
397
        names = sorted(map(lambda x: x["name"], self.servers))
398
        dnames = sorted(map(lambda x: x["name"], self.dservers))
399
        self.assertEqual(names, dnames)
400

    
401

    
402
# --------------------------------------------------------------------
403
# Pithos Test Cases
404
class PithosTestCase(unittest.TestCase):
405
    """Test pithos functionality"""
406
    @classmethod
407
    def setUpClass(cls):
408
        """Initialize kamaki, get list of containers"""
409
        log.info("Getting list of containers")
410

    
411
        cls.client = PithosClient(PITHOS, TOKEN, PITHOS_USER)
412
        cls.containers = cls.client.list_containers()
413
        cls.result_dict = dict()
414

    
415
    def test_001_list_containers(self):
416
        """Test container list actually returns containers"""
417
        self.assertGreater(len(self.containers), 0)
418

    
419
    def test_002_unique_containers(self):
420
        """Test if containers have unique names"""
421
        names = [n['name'] for n in self.containers]
422
        names = sorted(names)
423
        self.assertEqual(sorted(list(set(names))), names)
424

    
425
    def test_003_create_container(self):
426
        """Test create a container"""
427
        rand_num = randint(1000, 9999)
428
        rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
429
        names = [n['name'] for n in self.containers]
430
        while rand_name in names:
431
            rand_num = randint(1000, 9999)
432
            rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
433
        # Create container
434
        self.client.container = rand_name
435
        self.client.container_put()
436
        # Get list of containers
437
        new_containers = self.client.list_containers()
438
        new_container_names = [n['name'] for n in new_containers]
439
        self.assertIn(rand_name, new_container_names)
440

    
441
    def test_004_upload(self):
442
        """Test uploading something to pithos+"""
443
        # Create a tmp file
444
        with tempfile.TemporaryFile() as f:
445
            f.write("This is a temp file")
446
            f.seek(0, 0)
447
            # Where to save file
448
            self.client.upload_object("test.txt", f)
449

    
450
    def test_005_download(self):
451
        """Test download something from pithos+"""
452
        # Create tmp directory to save file
453
        tmp_dir = tempfile.mkdtemp()
454
        tmp_file = os.path.join(tmp_dir, "test.txt")
455
        with open(tmp_file, "wb+") as f:
456
            self.client.download_object("test.txt", f)
457
            # Read file
458
            f.seek(0, 0)
459
            content = f.read()
460
        # Remove files
461
        os.unlink(tmp_file)
462
        os.rmdir(tmp_dir)
463
        # Compare results
464
        self.assertEqual(content, "This is a temp file")
465

    
466
    def test_006_remove(self):
467
        """Test removing files and containers"""
468
        cont_name = self.client.container
469
        self.client.del_object("test.txt")
470
        self.client.purge_container()
471
        # List containers
472
        containers = self.client.list_containers()
473
        cont_names = [n['name'] for n in containers]
474
        self.assertNotIn(cont_name, cont_names)
475

    
476

    
477
# --------------------------------------------------------------------
478
# This class gets replicated into actual TestCases dynamically
479
class SpawnServerTestCase(unittest.TestCase):
480
    """Test scenario for server of the specified image"""
481
    @classmethod
482
    def setUpClass(cls):
483
        """Initialize a kamaki instance"""
484
        log.info("Spawning server for image `%s'" % cls.imagename)
485
        cls.client = ComputeClient(API, TOKEN)
486
        cls.cyclades = CycladesClient(API, TOKEN)
487
        cls.result_dict = dict()
488

    
489
    def _get_ipv4(self, server):
490
        """Get the public IPv4 of a server from the detailed server info"""
491

    
492
        nics = server["attachments"]["values"]
493

    
494
        for nic in nics:
495
            net_id = nic["network_id"]
496
            if self.cyclades.get_network_details(net_id)["public"]:
497
                public_addrs = nic["ipv4"]
498

    
499
        self.assertTrue(public_addrs is not None)
500

    
501
        return public_addrs
502

    
503
    def _get_ipv6(self, server):
504
        """Get the public IPv6 of a server from the detailed server info"""
505

    
506
        nics = server["attachments"]["values"]
507

    
508
        for nic in nics:
509
            net_id = nic["network_id"]
510
            if self.cyclades.get_network_details(net_id)["public"]:
511
                public_addrs = nic["ipv6"]
512

    
513
        self.assertTrue(public_addrs is not None)
514

    
515
        return public_addrs
516

    
517
    def _connect_loginname(self, os_value):
518
        """Return the login name for connections based on the server OS"""
519
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
520
            return "user"
521
        elif os_value in ("windows", "windows_alpha1"):
522
            return "Administrator"
523
        else:
524
            return "root"
525

    
526
    def _verify_server_status(self, current_status, new_status):
527
        """Verify a server has switched to a specified status"""
528
        server = self.client.get_server_details(self.serverid)
529
        if server["status"] not in (current_status, new_status):
530
            return None  # Do not raise exception, return so the test fails
531
        self.assertEquals(server["status"], new_status)
532

    
533
    def _get_connected_tcp_socket(self, family, host, port):
534
        """Get a connected socket from the specified family to host:port"""
535
        sock = None
536
        for res in \
537
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
538
                               socket.AI_PASSIVE):
539
            af, socktype, proto, canonname, sa = res
540
            try:
541
                sock = socket.socket(af, socktype, proto)
542
            except socket.error:
543
                sock = None
544
                continue
545
            try:
546
                sock.connect(sa)
547
            except socket.error:
548
                sock.close()
549
                sock = None
550
                continue
551
        self.assertIsNotNone(sock)
552
        return sock
553

    
554
    def _ping_once(self, ipv6, ip):
555
        """Test server responds to a single IPv4 or IPv6 ping"""
556
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
557
        ping = subprocess.Popen(cmd, shell=True,
558
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
559
        (stdout, stderr) = ping.communicate()
560
        ret = ping.wait()
561
        self.assertEquals(ret, 0)
562

    
563
    def _get_hostname_over_ssh(self, hostip, username, password):
564
        lines, status = _ssh_execute(
565
            hostip, username, password, "hostname")
566
        self.assertEqual(len(lines), 1)
567
        return lines[0]
568

    
569
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
570
                                   opmsg, callable, *args, **kwargs):
571
        if warn_timeout == fail_timeout:
572
            warn_timeout = fail_timeout + 1
573
        warn_tmout = time.time() + warn_timeout
574
        fail_tmout = time.time() + fail_timeout
575
        while True:
576
            self.assertLess(time.time(), fail_tmout,
577
                            "operation `%s' timed out" % opmsg)
578
            if time.time() > warn_tmout:
579
                log.warning("Server %d: `%s' operation `%s' not done yet",
580
                            self.serverid, self.servername, opmsg)
581
            try:
582
                log.info("%s... " % opmsg)
583
                return callable(*args, **kwargs)
584
            except AssertionError:
585
                pass
586
            time.sleep(self.query_interval)
587

    
588
    def _insist_on_tcp_connection(self, family, host, port):
589
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
590
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
591
        msg = "connect over %s to %s:%s" % \
592
              (familystr.get(family, "Unknown"), host, port)
593
        sock = self._try_until_timeout_expires(
594
            self.action_timeout, self.action_timeout,
595
            msg, self._get_connected_tcp_socket,
596
            family, host, port)
597
        return sock
598

    
599
    def _insist_on_status_transition(self, current_status, new_status,
600
                                     fail_timeout, warn_timeout=None):
601
        msg = "Server %d: `%s', waiting for %s -> %s" % \
602
              (self.serverid, self.servername, current_status, new_status)
603
        if warn_timeout is None:
604
            warn_timeout = fail_timeout
605
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
606
                                        msg, self._verify_server_status,
607
                                        current_status, new_status)
608
        # Ensure the status is actually the expected one
609
        server = self.client.get_server_details(self.serverid)
610
        self.assertEquals(server["status"], new_status)
611

    
612
    def _insist_on_ssh_hostname(self, hostip, username, password):
613
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
614
        hostname = self._try_until_timeout_expires(
615
            self.action_timeout, self.action_timeout,
616
            msg, self._get_hostname_over_ssh,
617
            hostip, username, password)
618

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

    
622
    def _check_file_through_ssh(self, hostip, username, password,
623
                                remotepath, content):
624
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
625
            (hostip, username, password)
626
        log.info(msg)
627
        try:
628
            ssh = paramiko.SSHClient()
629
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
630
            ssh.connect(hostip, username=username, password=password)
631
            ssh.close()
632
        except socket.error:
633
            raise AssertionError
634

    
635
        transport = paramiko.Transport((hostip, 22))
636
        transport.connect(username=username, password=password)
637

    
638
        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
639
        sftp = paramiko.SFTPClient.from_transport(transport)
640
        sftp.get(remotepath, localpath)
641
        sftp.close()
642
        transport.close()
643

    
644
        f = open(localpath)
645
        remote_content = b64encode(f.read())
646

    
647
        # Check if files are the same
648
        return (remote_content == content)
649

    
650
    def _skipIf(self, condition, msg):
651
        if condition:
652
            self.skipTest(msg)
653

    
654
    def test_001_submit_create_server(self):
655
        """Test submit create server request"""
656

    
657
        log.info("Submit new server request")
658
        server = self.client.create_server(self.servername, self.flavorid,
659
                                           self.imageid, self.personality)
660

    
661
        log.info("Server id: " + str(server["id"]))
662
        log.info("Server password: " + server["adminPass"])
663
        self.assertEqual(server["name"], self.servername)
664
        self.assertEqual(server["flavorRef"], self.flavorid)
665
        self.assertEqual(server["imageRef"], self.imageid)
666
        self.assertEqual(server["status"], "BUILD")
667

    
668
        # Update class attributes to reflect data on building server
669
        cls = type(self)
670
        cls.serverid = server["id"]
671
        cls.username = None
672
        cls.passwd = server["adminPass"]
673

    
674
        self.result_dict["Server ID"] = str(server["id"])
675
        self.result_dict["Password"] = str(server["adminPass"])
676

    
677
    def test_002a_server_is_building_in_list(self):
678
        """Test server is in BUILD state, in server list"""
679
        log.info("Server in BUILD state in server list")
680

    
681
        self.result_dict.clear()
682

    
683
        servers = self.client.list_servers(detail=True)
684
        servers = filter(lambda x: x["name"] == self.servername, servers)
685

    
686
        server = servers[0]
687
        self.assertEqual(server["name"], self.servername)
688
        self.assertEqual(server["flavorRef"], self.flavorid)
689
        self.assertEqual(server["imageRef"], self.imageid)
690
        self.assertEqual(server["status"], "BUILD")
691

    
692
    def test_002b_server_is_building_in_details(self):
693
        """Test server is in BUILD state, in details"""
694

    
695
        log.info("Server in BUILD state in details")
696

    
697
        server = self.client.get_server_details(self.serverid)
698
        self.assertEqual(server["name"], self.servername)
699
        self.assertEqual(server["flavorRef"], self.flavorid)
700
        self.assertEqual(server["imageRef"], self.imageid)
701
        self.assertEqual(server["status"], "BUILD")
702

    
703
    def test_002c_set_server_metadata(self):
704

    
705
        log.info("Creating server metadata")
706

    
707
        image = self.client.get_image_details(self.imageid)
708
        os_value = image["metadata"]["values"]["os"]
709
        users = image["metadata"]["values"].get("users", None)
710
        self.client.update_server_metadata(self.serverid, OS=os_value)
711

    
712
        userlist = users.split()
713

    
714
        # Determine the username to use for future connections
715
        # to this host
716
        cls = type(self)
717

    
718
        if "root" in userlist:
719
            cls.username = "root"
720
        elif users is None:
721
            cls.username = self._connect_loginname(os_value)
722
        else:
723
            cls.username = choice(userlist)
724

    
725
        self.assertIsNotNone(cls.username)
726

    
727
    def test_002d_verify_server_metadata(self):
728
        """Test server metadata keys are set based on image metadata"""
729

    
730
        log.info("Verifying image metadata")
731

    
732
        servermeta = self.client.get_server_metadata(self.serverid)
733
        imagemeta = self.client.get_image_metadata(self.imageid)
734

    
735
        self.assertEqual(servermeta["OS"], imagemeta["os"])
736

    
737
    def test_003_server_becomes_active(self):
738
        """Test server becomes ACTIVE"""
739

    
740
        log.info("Waiting for server to become ACTIVE")
741

    
742
        self._insist_on_status_transition(
743
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
744

    
745
    def test_003a_get_server_oob_console(self):
746
        """Test getting OOB server console over VNC
747

748
        Implementation of RFB protocol follows
749
        http://www.realvnc.com/docs/rfbproto.pdf.
750

751
        """
752
        console = self.cyclades.get_server_console(self.serverid)
753
        self.assertEquals(console['type'], "vnc")
754
        sock = self._insist_on_tcp_connection(
755
            socket.AF_INET, console["host"], console["port"])
756

    
757
        # Step 1. ProtocolVersion message (par. 6.1.1)
758
        version = sock.recv(1024)
759
        self.assertEquals(version, 'RFB 003.008\n')
760
        sock.send(version)
761

    
762
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
763
        sec = sock.recv(1024)
764
        self.assertEquals(list(sec), ['\x01', '\x02'])
765

    
766
        # Step 3. Request VNC Authentication (par 6.1.2)
767
        sock.send('\x02')
768

    
769
        # Step 4. Receive Challenge (par 6.2.2)
770
        challenge = sock.recv(1024)
771
        self.assertEquals(len(challenge), 16)
772

    
773
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
774
        response = d3des_generate_response(
775
            (console["password"] + '\0' * 8)[:8], challenge)
776
        sock.send(response)
777

    
778
        # Step 6. SecurityResult (par 6.1.3)
779
        result = sock.recv(4)
780
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
781
        sock.close()
782

    
783
    def test_004_server_has_ipv4(self):
784
        """Test active server has a valid IPv4 address"""
785

    
786
        log.info("Validate server's IPv4")
787

    
788
        server = self.client.get_server_details(self.serverid)
789
        ipv4 = self._get_ipv4(server)
790

    
791
        self.result_dict.clear()
792
        self.result_dict["IPv4"] = str(ipv4)
793

    
794
        self.assertEquals(IP(ipv4).version(), 4)
795

    
796
    def test_005_server_has_ipv6(self):
797
        """Test active server has a valid IPv6 address"""
798
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
799

    
800
        log.info("Validate server's IPv6")
801

    
802
        server = self.client.get_server_details(self.serverid)
803
        ipv6 = self._get_ipv6(server)
804

    
805
        self.result_dict.clear()
806
        self.result_dict["IPv6"] = str(ipv6)
807

    
808
        self.assertEquals(IP(ipv6).version(), 6)
809

    
810
    def test_006_server_responds_to_ping_IPv4(self):
811
        """Test server responds to ping on IPv4 address"""
812

    
813
        log.info("Testing if server responds to pings in IPv4")
814
        self.result_dict.clear()
815

    
816
        server = self.client.get_server_details(self.serverid)
817
        ip = self._get_ipv4(server)
818
        self._try_until_timeout_expires(self.action_timeout,
819
                                        self.action_timeout,
820
                                        "PING IPv4 to %s" % ip,
821
                                        self._ping_once,
822
                                        False, ip)
823

    
824
    def test_007_server_responds_to_ping_IPv6(self):
825
        """Test server responds to ping on IPv6 address"""
826
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
827
        log.info("Testing if server responds to pings in IPv6")
828

    
829
        server = self.client.get_server_details(self.serverid)
830
        ip = self._get_ipv6(server)
831
        self._try_until_timeout_expires(self.action_timeout,
832
                                        self.action_timeout,
833
                                        "PING IPv6 to %s" % ip,
834
                                        self._ping_once,
835
                                        True, ip)
836

    
837
    def test_008_submit_shutdown_request(self):
838
        """Test submit request to shutdown server"""
839

    
840
        log.info("Shutting down server")
841

    
842
        self.cyclades.shutdown_server(self.serverid)
843

    
844
    def test_009_server_becomes_stopped(self):
845
        """Test server becomes STOPPED"""
846

    
847
        log.info("Waiting until server becomes STOPPED")
848
        self._insist_on_status_transition(
849
            "ACTIVE", "STOPPED", self.action_timeout, self.action_timeout)
850

    
851
    def test_010_submit_start_request(self):
852
        """Test submit start server request"""
853

    
854
        log.info("Starting server")
855

    
856
        self.cyclades.start_server(self.serverid)
857

    
858
    def test_011_server_becomes_active(self):
859
        """Test server becomes ACTIVE again"""
860

    
861
        log.info("Waiting until server becomes ACTIVE")
862
        self._insist_on_status_transition(
863
            "STOPPED", "ACTIVE", self.action_timeout, self.action_timeout)
864

    
865
    def test_011a_server_responds_to_ping_IPv4(self):
866
        """Test server OS is actually up and running again"""
867

    
868
        log.info("Testing if server is actually up and running")
869

    
870
        self.test_006_server_responds_to_ping_IPv4()
871

    
872
    def test_012_ssh_to_server_IPv4(self):
873
        """Test SSH to server public IPv4 works, verify hostname"""
874

    
875
        self._skipIf(self.is_windows, "only valid for Linux servers")
876
        server = self.client.get_server_details(self.serverid)
877
        self._insist_on_ssh_hostname(self._get_ipv4(server),
878
                                     self.username, self.passwd)
879

    
880
    def test_013_ssh_to_server_IPv6(self):
881
        """Test SSH to server public IPv6 works, verify hostname"""
882
        self._skipIf(self.is_windows, "only valid for Linux servers")
883
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
884

    
885
        server = self.client.get_server_details(self.serverid)
886
        self._insist_on_ssh_hostname(self._get_ipv6(server),
887
                                     self.username, self.passwd)
888

    
889
    def test_014_rdp_to_server_IPv4(self):
890
        "Test RDP connection to server public IPv4 works"""
891
        self._skipIf(not self.is_windows, "only valid for Windows servers")
892
        server = self.client.get_server_details(self.serverid)
893
        ipv4 = self._get_ipv4(server)
894
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
895

    
896
        # No actual RDP processing done. We assume the RDP server is there
897
        # if the connection to the RDP port is successful.
898
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
899
        sock.close()
900

    
901
    def test_015_rdp_to_server_IPv6(self):
902
        "Test RDP connection to server public IPv6 works"""
903
        self._skipIf(not self.is_windows, "only valid for Windows servers")
904
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
905

    
906
        server = self.client.get_server_details(self.serverid)
907
        ipv6 = self._get_ipv6(server)
908
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
909

    
910
        # No actual RDP processing done. We assume the RDP server is there
911
        # if the connection to the RDP port is successful.
912
        sock.close()
913

    
914
    def test_016_personality_is_enforced(self):
915
        """Test file injection for personality enforcement"""
916
        self._skipIf(self.is_windows, "only implemented for Linux servers")
917
        self._skipIf(self.personality is None, "No personality file selected")
918

    
919
        log.info("Trying to inject file for personality enforcement")
920

    
921
        server = self.client.get_server_details(self.serverid)
922

    
923
        for inj_file in self.personality:
924
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
925
                                                       inj_file['owner'],
926
                                                       self.passwd,
927
                                                       inj_file['path'],
928
                                                       inj_file['contents'])
929
            self.assertTrue(equal_files)
930

    
931
    def test_017_submit_delete_request(self):
932
        """Test submit request to delete server"""
933

    
934
        log.info("Deleting server")
935

    
936
        self.client.delete_server(self.serverid)
937

    
938
    def test_018_server_becomes_deleted(self):
939
        """Test server becomes DELETED"""
940

    
941
        log.info("Testing if server becomes DELETED")
942

    
943
        self._insist_on_status_transition(
944
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
945

    
946
    def test_019_server_no_longer_in_server_list(self):
947
        """Test server is no longer in server list"""
948

    
949
        log.info("Test if server is no longer listed")
950

    
951
        servers = self.client.list_servers()
952
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
953

    
954

    
955
class NetworkTestCase(unittest.TestCase):
956
    """ Testing networking in cyclades """
957

    
958
    @classmethod
959
    def setUpClass(cls):
960
        "Initialize kamaki, get list of current networks"
961

    
962
        cls.client = CycladesClient(API, TOKEN)
963
        cls.compute = ComputeClient(API, TOKEN)
964

    
965
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
966
                                          TEST_RUN_ID,
967
                                          cls.imagename)
968

    
969
        #Dictionary initialization for the vms credentials
970
        cls.serverid = dict()
971
        cls.username = dict()
972
        cls.password = dict()
973
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
974

    
975
        cls.result_dict = dict()
976

    
977
    def _skipIf(self, condition, msg):
978
        if condition:
979
            self.skipTest(msg)
980

    
981
    def _get_ipv4(self, server):
982
        """Get the public IPv4 of a server from the detailed server info"""
983

    
984
        nics = server["attachments"]["values"]
985

    
986
        for nic in nics:
987
            net_id = nic["network_id"]
988
            if self.client.get_network_details(net_id)["public"]:
989
                public_addrs = nic["ipv4"]
990

    
991
        self.assertTrue(public_addrs is not None)
992

    
993
        return public_addrs
994

    
995
    def _connect_loginname(self, os_value):
996
        """Return the login name for connections based on the server OS"""
997
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
998
            return "user"
999
        elif os_value in ("windows", "windows_alpha1"):
1000
            return "Administrator"
1001
        else:
1002
            return "root"
1003

    
1004
    def _ping_once(self, ip):
1005

    
1006
        """Test server responds to a single IPv4 or IPv6 ping"""
1007
        cmd = "ping -c 2 -w 3 %s" % (ip)
1008
        ping = subprocess.Popen(cmd, shell=True,
1009
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1010
        (stdout, stderr) = ping.communicate()
1011
        ret = ping.wait()
1012

    
1013
        return (ret == 0)
1014

    
1015
    def test_00001a_submit_create_server_A(self):
1016
        """Test submit create server request"""
1017

    
1018
        log.info("Creating test server A")
1019

    
1020
        serverA = self.client.create_server(self.servername, self.flavorid,
1021
                                            self.imageid, personality=None)
1022

    
1023
        self.assertEqual(serverA["name"], self.servername)
1024
        self.assertEqual(serverA["flavorRef"], self.flavorid)
1025
        self.assertEqual(serverA["imageRef"], self.imageid)
1026
        self.assertEqual(serverA["status"], "BUILD")
1027

    
1028
        # Update class attributes to reflect data on building server
1029
        self.serverid['A'] = serverA["id"]
1030
        self.username['A'] = None
1031
        self.password['A'] = serverA["adminPass"]
1032

    
1033
        log.info("Server A id:" + str(serverA["id"]))
1034
        log.info("Server password " + (self.password['A']))
1035

    
1036
        self.result_dict["Server A ID"] = str(serverA["id"])
1037
        self.result_dict["Server A password"] = serverA["adminPass"]
1038

    
1039
    def test_00001b_serverA_becomes_active(self):
1040
        """Test server becomes ACTIVE"""
1041

    
1042
        log.info("Waiting until test server A becomes ACTIVE")
1043
        self.result_dict.clear()
1044

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

    
1057
        self.assertTrue(active)
1058

    
1059
    def test_00002a_submit_create_server_B(self):
1060
        """Test submit create server request"""
1061

    
1062
        log.info("Creating test server B")
1063

    
1064
        serverB = self.client.create_server(self.servername, self.flavorid,
1065
                                            self.imageid, personality=None)
1066

    
1067
        self.assertEqual(serverB["name"], self.servername)
1068
        self.assertEqual(serverB["flavorRef"], self.flavorid)
1069
        self.assertEqual(serverB["imageRef"], self.imageid)
1070
        self.assertEqual(serverB["status"], "BUILD")
1071

    
1072
        # Update class attributes to reflect data on building server
1073
        self.serverid['B'] = serverB["id"]
1074
        self.username['B'] = None
1075
        self.password['B'] = serverB["adminPass"]
1076

    
1077
        log.info("Server B id: " + str(serverB["id"]))
1078
        log.info("Password " + (self.password['B']))
1079

    
1080
        self.result_dict.clear()
1081
        self.result_dict["Server B ID"] = str(serverB["id"])
1082
        self.result_dict["Server B password"] = serverB["adminPass"]
1083

    
1084
    def test_00002b_serverB_becomes_active(self):
1085
        """Test server becomes ACTIVE"""
1086

    
1087
        log.info("Waiting until test server B becomes ACTIVE")
1088
        self.result_dict.clear()
1089

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

    
1102
        self.assertTrue(active)
1103

    
1104
    def test_001_create_network(self):
1105
        """Test submit create network request"""
1106

    
1107
        log.info("Submit new network request")
1108
        self.result_dict.clear()
1109

    
1110
        name = SNF_TEST_PREFIX + TEST_RUN_ID
1111
        #previous_num = len(self.client.list_networks())
1112
        network = self.client.create_network(name, cidr='10.0.0.1/28')
1113

    
1114
        #Test if right name is assigned
1115
        self.assertEqual(network['name'], name)
1116

    
1117
        # Update class attributes
1118
        cls = type(self)
1119
        cls.networkid = network['id']
1120
        #networks = self.client.list_networks()
1121

    
1122
        fail_tmout = time.time() + self.action_timeout
1123

    
1124
        #Test if new network is created
1125
        while True:
1126
            d = self.client.get_network_details(network['id'])
1127
            if d['status'] == 'ACTIVE':
1128
                connected = True
1129
                break
1130
            elif time.time() > fail_tmout:
1131
                self.assertLess(time.time(), fail_tmout)
1132
            else:
1133
                log.info("Waiting for network to become ACTIVE")
1134
                time.sleep(self.query_interval)
1135

    
1136
        self.assertTrue(connected)
1137

    
1138
        self.result_dict["Private network ID"] = str(network['id'])
1139

    
1140
    def test_002_connect_to_network(self):
1141
        """Test connect VMs to network"""
1142

    
1143
        log.info("Connect VMs to private network")
1144
        self.result_dict.clear()
1145

    
1146
        self.client.connect_server(self.serverid['A'], self.networkid)
1147
        self.client.connect_server(self.serverid['B'], self.networkid)
1148

    
1149
        #Insist on connecting until action timeout
1150
        fail_tmout = time.time() + self.action_timeout
1151

    
1152
        while True:
1153

    
1154
            netsA = [x['network_id']
1155
                     for x in self.client.get_server_details(
1156
                         self.serverid['A'])['attachments']['values']]
1157
            netsB = [x['network_id']
1158
                     for x in self.client.get_server_details(
1159
                         self.serverid['B'])['attachments']['values']]
1160

    
1161
            if (self.networkid in netsA) and (self.networkid in netsB):
1162
                conn_exists = True
1163
                break
1164
            elif time.time() > fail_tmout:
1165
                self.assertLess(time.time(), fail_tmout)
1166
            else:
1167
                time.sleep(self.query_interval)
1168

    
1169
        #Adding private IPs to class attributes
1170
        cls = type(self)
1171
        cls.priv_ip = dict()
1172

    
1173
        nicsA = self.client.get_server_details(
1174
            self.serverid['A'])['attachments']['values']
1175
        nicsB = self.client.get_server_details(
1176
            self.serverid['B'])['attachments']['values']
1177

    
1178
        if conn_exists:
1179
            for nic in nicsA:
1180
                if nic["network_id"] == self.networkid:
1181
                    cls.priv_ip["A"] = nic["ipv4"]
1182

    
1183
            for nic in nicsB:
1184
                if nic["network_id"] == self.networkid:
1185
                    cls.priv_ip["B"] = nic["ipv4"]
1186

    
1187
        self.assertTrue(conn_exists)
1188

    
1189
    def test_002a_reboot(self):
1190
        """Rebooting server A"""
1191

    
1192
        log.info("Rebooting server A")
1193

    
1194
        self.client.shutdown_server(self.serverid['A'])
1195

    
1196
        fail_tmout = time.time() + self.action_timeout
1197
        while True:
1198
            d = self.client.get_server_details(self.serverid['A'])
1199
            status = d['status']
1200
            if status == 'STOPPED':
1201
                break
1202
            elif time.time() > fail_tmout:
1203
                self.assertLess(time.time(), fail_tmout)
1204
            else:
1205
                time.sleep(self.query_interval)
1206

    
1207
        self.client.start_server(self.serverid['A'])
1208

    
1209
        while True:
1210
            d = self.client.get_server_details(self.serverid['A'])
1211
            status = d['status']
1212
            if status == 'ACTIVE':
1213
                active = True
1214
                break
1215
            elif time.time() > fail_tmout:
1216
                self.assertLess(time.time(), fail_tmout)
1217
            else:
1218
                time.sleep(self.query_interval)
1219

    
1220
        self.assertTrue(active)
1221

    
1222
    def test_002b_ping_server_A(self):
1223
        "Test if server A responds to IPv4 pings"
1224

    
1225
        log.info("Testing if server A responds to IPv4 pings ")
1226
        self.result_dict.clear()
1227

    
1228
        server = self.client.get_server_details(self.serverid['A'])
1229
        ip = self._get_ipv4(server)
1230

    
1231
        fail_tmout = time.time() + self.action_timeout
1232

    
1233
        s = False
1234

    
1235
        self.result_dict["Server A public IP"] = str(ip)
1236

    
1237
        while True:
1238

    
1239
            if self._ping_once(ip):
1240
                s = True
1241
                break
1242

    
1243
            elif time.time() > fail_tmout:
1244
                self.assertLess(time.time(), fail_tmout)
1245

    
1246
            else:
1247
                time.sleep(self.query_interval)
1248

    
1249
        self.assertTrue(s)
1250

    
1251
    def test_002c_reboot(self):
1252
        """Reboot server B"""
1253

    
1254
        log.info("Rebooting server B")
1255
        self.result_dict.clear()
1256

    
1257
        self.client.shutdown_server(self.serverid['B'])
1258

    
1259
        fail_tmout = time.time() + self.action_timeout
1260
        while True:
1261
            d = self.client.get_server_details(self.serverid['B'])
1262
            status = d['status']
1263
            if status == 'STOPPED':
1264
                break
1265
            elif time.time() > fail_tmout:
1266
                self.assertLess(time.time(), fail_tmout)
1267
            else:
1268
                time.sleep(self.query_interval)
1269

    
1270
        self.client.start_server(self.serverid['B'])
1271

    
1272
        while True:
1273
            d = self.client.get_server_details(self.serverid['B'])
1274
            status = d['status']
1275
            if status == 'ACTIVE':
1276
                active = True
1277
                break
1278
            elif time.time() > fail_tmout:
1279
                self.assertLess(time.time(), fail_tmout)
1280
            else:
1281
                time.sleep(self.query_interval)
1282

    
1283
        self.assertTrue(active)
1284

    
1285
    def test_002d_ping_server_B(self):
1286
        """Test if server B responds to IPv4 pings"""
1287

    
1288
        log.info("Testing if server B responds to IPv4 pings")
1289
        self.result_dict.clear()
1290

    
1291
        server = self.client.get_server_details(self.serverid['B'])
1292
        ip = self._get_ipv4(server)
1293

    
1294
        fail_tmout = time.time() + self.action_timeout
1295

    
1296
        s = False
1297

    
1298
        self.result_dict["Server B public IP"] = str(ip)
1299

    
1300
        while True:
1301
            if self._ping_once(ip):
1302
                s = True
1303
                break
1304

    
1305
            elif time.time() > fail_tmout:
1306
                self.assertLess(time.time(), fail_tmout)
1307

    
1308
            else:
1309
                time.sleep(self.query_interval)
1310

    
1311
        self.assertTrue(s)
1312

    
1313
    def test_003a_setup_interface_A(self):
1314
        """Set up eth1 for server A"""
1315

    
1316
        self._skipIf(self.is_windows, "only valid for Linux servers")
1317

    
1318
        log.info("Setting up interface eth1 for server A")
1319
        self.result_dict.clear()
1320

    
1321
        server = self.client.get_server_details(self.serverid['A'])
1322
        image = self.client.get_image_details(self.imageid)
1323
        os_value = image['metadata']['values']['os']
1324

    
1325
        users = image["metadata"]["values"].get("users", None)
1326
        userlist = users.split()
1327

    
1328
        if "root" in userlist:
1329
            loginname = "root"
1330
        elif users is None:
1331
            loginname = self._connect_loginname(os_value)
1332
        else:
1333
            loginname = choice(userlist)
1334

    
1335
        hostip = self._get_ipv4(server)
1336
        myPass = self.password['A']
1337

    
1338
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1339
        command = "ifconfig eth1 %s" % self.priv_ip["A"]
1340
        output, status = _ssh_execute(
1341
            hostip, loginname, myPass, command)
1342

    
1343
        self.assertEquals(status, 0)
1344

    
1345
    def test_003b_setup_interface_B(self):
1346
        """Setup eth1 for server B"""
1347

    
1348
        self._skipIf(self.is_windows, "only valid for Linux servers")
1349

    
1350
        log.info("Setting up interface eth1 for server B")
1351

    
1352
        server = self.client.get_server_details(self.serverid['B'])
1353
        image = self.client.get_image_details(self.imageid)
1354
        os_value = image['metadata']['values']['os']
1355

    
1356
        users = image["metadata"]["values"].get("users", None)
1357
        userlist = users.split()
1358

    
1359
        if "root" in userlist:
1360
            loginname = "root"
1361
        elif users is None:
1362
            loginname = self._connect_loginname(os_value)
1363
        else:
1364
            loginname = choice(userlist)
1365

    
1366
        hostip = self._get_ipv4(server)
1367
        myPass = self.password['B']
1368

    
1369
        log.info("SSH in server B as %s/%s" % (loginname, myPass))
1370
        command = "ifconfig eth1 %s" % self.priv_ip["B"]
1371
        output, status = _ssh_execute(
1372
            hostip, loginname, myPass, command)
1373

    
1374
        self.assertEquals(status, 0)
1375

    
1376
    def test_003c_test_connection_exists(self):
1377
        """Ping server B from server A to test if connection exists"""
1378

    
1379
        self._skipIf(self.is_windows, "only valid for Linux servers")
1380

    
1381
        log.info("Testing if server A is actually connected to server B")
1382

    
1383
        server = self.client.get_server_details(self.serverid['A'])
1384
        image = self.client.get_image_details(self.imageid)
1385
        os_value = image['metadata']['values']['os']
1386
        hostip = self._get_ipv4(server)
1387

    
1388
        users = image["metadata"]["values"].get("users", None)
1389
        userlist = users.split()
1390

    
1391
        if "root" in userlist:
1392
            loginname = "root"
1393
        elif users is None:
1394
            loginname = self._connect_loginname(os_value)
1395
        else:
1396
            loginname = choice(userlist)
1397

    
1398
        myPass = self.password['A']
1399

    
1400
        cmd = "if ping -c 2 -w 3 %s >/dev/null; \
1401
               then echo \'True\'; fi;" % self.priv_ip["B"]
1402
        lines, status = _ssh_execute(
1403
            hostip, loginname, myPass, cmd)
1404

    
1405
        exists = False
1406

    
1407
        if 'True\n' in lines:
1408
            exists = True
1409

    
1410
        self.assertTrue(exists)
1411

    
1412
    def test_004_disconnect_from_network(self):
1413
        "Disconnecting server A and B from network"
1414

    
1415
        log.info("Disconnecting servers from private network")
1416

    
1417
        prev_state = self.client.get_network_details(self.networkid)
1418
        prev_nics = prev_state['attachments']['values']
1419
        #prev_conn = len(prev_nics)
1420

    
1421
        nicsA = [x['id']
1422
                 for x in self.client.get_server_details(
1423
                     self.serverid['A'])['attachments']['values']]
1424
        nicsB = [x['id']
1425
                 for x in self.client.get_server_details(
1426
                     self.serverid['B'])['attachments']['values']]
1427

    
1428
        for nic in prev_nics:
1429
            if nic in nicsA:
1430
                self.client.disconnect_server(self.serverid['A'], nic)
1431
            if nic in nicsB:
1432
                self.client.disconnect_server(self.serverid['B'], nic)
1433

    
1434
        #Insist on deleting until action timeout
1435
        fail_tmout = time.time() + self.action_timeout
1436

    
1437
        while True:
1438
            netsA = [x['network_id']
1439
                     for x in self.client.get_server_details(
1440
                         self.serverid['A'])['attachments']['values']]
1441
            netsB = [x['network_id']
1442
                     for x in self.client.get_server_details(
1443
                         self.serverid['B'])['attachments']['values']]
1444

    
1445
            #connected = (self.client.get_network_details(self.networkid))
1446
            #connections = connected['attachments']['values']
1447
            if (self.networkid not in netsA) and (self.networkid not in netsB):
1448
                conn_exists = False
1449
                break
1450
            elif time.time() > fail_tmout:
1451
                self.assertLess(time.time(), fail_tmout)
1452
            else:
1453
                time.sleep(self.query_interval)
1454

    
1455
        self.assertFalse(conn_exists)
1456

    
1457
    def test_005_destroy_network(self):
1458
        """Test submit delete network request"""
1459

    
1460
        log.info("Submitting delete network request")
1461

    
1462
        self.client.delete_network(self.networkid)
1463

    
1464
        fail_tmout = time.time() + self.action_timeout
1465

    
1466
        while True:
1467

    
1468
            curr_net = []
1469
            networks = self.client.list_networks()
1470

    
1471
            for net in networks:
1472
                curr_net.append(net['id'])
1473

    
1474
            if self.networkid not in curr_net:
1475
                self.assertTrue(self.networkid not in curr_net)
1476
                break
1477

    
1478
            elif time.time() > fail_tmout:
1479
                self.assertLess(time.time(), fail_tmout)
1480

    
1481
            else:
1482
                time.sleep(self.query_interval)
1483

    
1484
    def test_006_cleanup_servers(self):
1485
        """Cleanup servers created for this test"""
1486

    
1487
        log.info("Delete servers created for this test")
1488

    
1489
        self.compute.delete_server(self.serverid['A'])
1490
        self.compute.delete_server(self.serverid['B'])
1491

    
1492
        fail_tmout = time.time() + self.action_timeout
1493

    
1494
        #Ensure server gets deleted
1495
        status = dict()
1496

    
1497
        while True:
1498
            details = self.compute.get_server_details(self.serverid['A'])
1499
            status['A'] = details['status']
1500
            details = self.compute.get_server_details(self.serverid['B'])
1501
            status['B'] = details['status']
1502
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1503
                deleted = True
1504
                break
1505
            elif time.time() > fail_tmout:
1506
                self.assertLess(time.time(), fail_tmout)
1507
            else:
1508
                time.sleep(self.query_interval)
1509

    
1510
        self.assertTrue(deleted)
1511

    
1512

    
1513
class TestRunnerProcess(Process):
1514
    """A distinct process used to execute part of the tests in parallel"""
1515
    def __init__(self, **kw):
1516
        Process.__init__(self, **kw)
1517
        kwargs = kw["kwargs"]
1518
        self.testq = kwargs["testq"]
1519
        self.worker_folder = kwargs["worker_folder"]
1520

    
1521
    def run(self):
1522
        # Make sure this test runner process dies with the parent
1523
        # and is not left behind.
1524
        #
1525
        # WARNING: This uses the prctl(2) call and is
1526
        # Linux-specific.
1527

    
1528
        prctl.set_pdeathsig(signal.SIGHUP)
1529

    
1530
        multi = logging.getLogger("multiprocess")
1531

    
1532
        while True:
1533
            multi.debug("I am process %d, GETting from queue is %s" %
1534
                        (os.getpid(), self.testq))
1535
            msg = self.testq.get()
1536

    
1537
            multi.debug("Dequeued msg: %s" % msg)
1538

    
1539
            if msg == "TEST_RUNNER_TERMINATE":
1540
                raise SystemExit
1541

    
1542
            elif issubclass(msg, unittest.TestCase):
1543
                # Assemble a TestSuite, and run it
1544

    
1545
                log_file = os.path.join(self.worker_folder, 'details_' +
1546
                                        (msg.__name__) + "_" +
1547
                                        TEST_RUN_ID + '.log')
1548

    
1549
                fail_file = os.path.join(self.worker_folder, 'failed_' +
1550
                                         (msg.__name__) + "_" +
1551
                                         TEST_RUN_ID + '.log')
1552
                error_file = os.path.join(self.worker_folder, 'error_' +
1553
                                          (msg.__name__) + "_" +
1554
                                          TEST_RUN_ID + '.log')
1555

    
1556
                f = open(log_file, 'w')
1557
                fail = open(fail_file, 'w')
1558
                error = open(error_file, 'w')
1559

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

    
1562
                runner = unittest.TextTestRunner(
1563
                    f, verbosity=2, failfast=True,
1564
                    resultclass=BurninTestResult)
1565
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
1566
                result = runner.run(suite)
1567

    
1568
                for res in result.errors:
1569
                    log.error("snf-burnin encountered an error in "
1570
                              "testcase: %s" % msg)
1571
                    log.error("See log for details")
1572
                    error.write(str(res[0]) + '\n')
1573
                    error.write(str(res[0].shortDescription()) + '\n')
1574
                    error.write('\n')
1575

    
1576
                for res in result.failures:
1577
                    log.error("snf-burnin failed in testcase: %s" % msg)
1578
                    log.error("See log for details")
1579
                    fail.write(str(res[0]) + '\n')
1580
                    fail.write(str(res[0].shortDescription()) + '\n')
1581
                    fail.write('\n')
1582
                    if not NOFAILFAST:
1583
                        sys.exit()
1584

    
1585
                if (len(result.failures) == 0) and (len(result.errors) == 0):
1586
                    log.debug("Passed testcase: %s" % msg)
1587

    
1588
                f.close()
1589
                fail.close()
1590
                error.close()
1591

    
1592
            else:
1593
                raise Exception("Cannot handle msg: %s" % msg)
1594

    
1595

    
1596
def _run_cases_in_series(cases, image_folder):
1597
    """Run instances of TestCase in series"""
1598

    
1599
    for case in cases:
1600

    
1601
        test = case.__name__
1602

    
1603
        log.info(yellow + '* Starting testcase: %s' % test + normal)
1604
        log_file = os.path.join(image_folder, 'details_' +
1605
                                (case.__name__) + "_" +
1606
                                TEST_RUN_ID + '.log')
1607
        fail_file = os.path.join(image_folder, 'failed_' +
1608
                                 (case.__name__) + "_" +
1609
                                 TEST_RUN_ID + '.log')
1610
        error_file = os.path.join(image_folder, 'error_' +
1611
                                  (case.__name__) + "_" +
1612
                                  TEST_RUN_ID + '.log')
1613

    
1614
        f = open(log_file, "w")
1615
        fail = open(fail_file, "w")
1616
        error = open(error_file, "w")
1617

    
1618
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1619
        runner = unittest.TextTestRunner(
1620
            f, verbosity=2, failfast=True,
1621
            resultclass=BurninTestResult)
1622
        result = runner.run(suite)
1623

    
1624
        for res in result.errors:
1625
            log.error("snf-burnin encountered an error in "
1626
                      "testcase: %s" % test)
1627
            log.error("See log for details")
1628
            error.write(str(res[0]) + '\n')
1629
            error.write(str(res[0].shortDescription()) + '\n')
1630
            error.write('\n')
1631

    
1632
        for res in result.failures:
1633
            log.error("snf-burnin failed in testcase: %s" % test)
1634
            log.error("See log for details")
1635
            fail.write(str(res[0]) + '\n')
1636
            fail.write(str(res[0].shortDescription()) + '\n')
1637
            fail.write('\n')
1638
            if not NOFAILFAST:
1639
                sys.exit()
1640

    
1641
        if (len(result.failures) == 0) and (len(result.errors) == 0):
1642
            log.debug("Passed testcase: %s" % test)
1643

    
1644

    
1645
def _run_cases_in_parallel(cases, fanout, image_folder):
1646
    """Run instances of TestCase in parallel, in a number of distinct processes
1647

1648
    The cases iterable specifies the TestCases to be executed in parallel,
1649
    by test runners running in distinct processes.
1650
    The fanout parameter specifies the number of processes to spawn,
1651
    and defaults to 1.
1652
    The runner argument specifies the test runner class to use inside each
1653
    runner process.
1654

1655
    """
1656

    
1657
    multi = logging.getLogger("multiprocess")
1658
    handler = logging.StreamHandler()
1659
    multi.addHandler(handler)
1660

    
1661
    if VERBOSE:
1662
        multi.setLevel(logging.DEBUG)
1663
    else:
1664
        multi.setLevel(logging.INFO)
1665

    
1666
    testq = []
1667
    worker_folder = []
1668
    runners = []
1669

    
1670
    for i in xrange(0, fanout):
1671
        testq.append(Queue())
1672
        worker_folder.append(os.path.join(image_folder, 'process'+str(i)))
1673
        os.mkdir(worker_folder[i])
1674

    
1675
    for i in xrange(0, fanout):
1676
        kwargs = dict(testq=testq[i], worker_folder=worker_folder[i])
1677
        runners.append(TestRunnerProcess(kwargs=kwargs))
1678

    
1679
    multi.debug("Spawning %d test runner processes" % len(runners))
1680

    
1681
    for p in runners:
1682
        p.start()
1683

    
1684
    # Enqueue test cases
1685
    for i in xrange(0, fanout):
1686
        map(testq[i].put, cases)
1687
        testq[i].put("TEST_RUNNER_TERMINATE")
1688

    
1689
    multi.debug("Spawned %d test runners, PIDs are %s" %
1690
                (len(runners), [p.pid for p in runners]))
1691

    
1692
    multi.debug("Joining %d processes" % len(runners))
1693

    
1694
    for p in runners:
1695
        p.join()
1696

    
1697
    multi.debug("Done joining %d processes" % len(runners))
1698

    
1699

    
1700
def _images_test_case(**kwargs):
1701
    """Construct a new unit test case class from ImagesTestCase"""
1702
    name = "ImagesTestCase_%s" % kwargs["imageid"]
1703
    cls = type(name, (ImagesTestCase,), kwargs)
1704

    
1705
    #Patch extra parameters into test names by manipulating method docstrings
1706
    for (mname, m) in \
1707
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1708
        if hasattr(m, __doc__):
1709
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1710

    
1711
    # Make sure the class can be pickled, by listing it among
1712
    # the attributes of __main__. A PicklingError is raised otherwise.
1713
    thismodule = sys.modules[__name__]
1714
    setattr(thismodule, name, cls)
1715
    return cls
1716

    
1717

    
1718
def _spawn_server_test_case(**kwargs):
1719
    """Construct a new unit test case class from SpawnServerTestCase"""
1720

    
1721
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1722
    cls = type(name, (SpawnServerTestCase,), kwargs)
1723

    
1724
    # Patch extra parameters into test names by manipulating method docstrings
1725
    for (mname, m) in \
1726
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1727
        if hasattr(m, __doc__):
1728
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1729

    
1730
    # Make sure the class can be pickled, by listing it among
1731
    # the attributes of __main__. A PicklingError is raised otherwise.
1732

    
1733
    thismodule = sys.modules[__name__]
1734
    setattr(thismodule, name, cls)
1735
    return cls
1736

    
1737

    
1738
def _spawn_network_test_case(**kwargs):
1739
    """Construct a new unit test case class from NetworkTestCase"""
1740

    
1741
    name = "NetworkTestCase" + TEST_RUN_ID
1742
    cls = type(name, (NetworkTestCase,), kwargs)
1743

    
1744
    # Make sure the class can be pickled, by listing it among
1745
    # the attributes of __main__. A PicklingError is raised otherwise.
1746

    
1747
    thismodule = sys.modules[__name__]
1748
    setattr(thismodule, name, cls)
1749
    return cls
1750

    
1751

    
1752
# --------------------------------------------------------------------
1753
# Clean up servers/networks functions
1754
def cleanup_servers(timeout, query_interval, delete_stale=False):
1755

    
1756
    c = ComputeClient(API, TOKEN)
1757

    
1758
    servers = c.list_servers()
1759
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1760

    
1761
    if len(stale) == 0:
1762
        return
1763

    
1764
    # Show staled servers
1765
    print >>sys.stderr, yellow + \
1766
        "Found these stale servers from previous runs:" + \
1767
        normal
1768
    print >>sys.stderr, "    " + \
1769
        "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1770

    
1771
    # Delete staled servers
1772
    if delete_stale:
1773
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1774
        fail_tmout = time.time() + timeout
1775
        for s in stale:
1776
            c.delete_server(s["id"])
1777
        # Wait for all servers to be deleted
1778
        while True:
1779
            servers = c.list_servers()
1780
            stale = [s for s in servers
1781
                     if s["name"].startswith(SNF_TEST_PREFIX)]
1782
            if len(stale) == 0:
1783
                print >> sys.stderr, green + "    ...done" + normal
1784
                break
1785
            elif time.time() > fail_tmout:
1786
                print >> sys.stderr, red + \
1787
                    "Not all stale servers deleted. Action timed out." + \
1788
                    normal
1789
                sys.exit(1)
1790
            else:
1791
                time.sleep(query_interval)
1792
    else:
1793
        print >> sys.stderr, "Use --delete-stale to delete them."
1794

    
1795

    
1796
def cleanup_networks(action_timeout, query_interval, delete_stale=False):
1797

    
1798
    c = CycladesClient(API, TOKEN)
1799

    
1800
    networks = c.list_networks()
1801
    stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1802

    
1803
    if len(stale) == 0:
1804
        return
1805

    
1806
    # Show staled networks
1807
    print >> sys.stderr, yellow + \
1808
        "Found these stale networks from previous runs:" + \
1809
        normal
1810
    print "    " + \
1811
        "\n    ".join(["%s: %s" % (str(n["id"]), n["name"]) for n in stale])
1812

    
1813
    # Delete staled networks
1814
    if delete_stale:
1815
        print >> sys.stderr, "Deleting %d stale networks:" % len(stale)
1816
        fail_tmout = time.time() + action_timeout
1817
        for n in stale:
1818
            c.delete_network(n["id"])
1819
        # Wait for all networks to be deleted
1820
        while True:
1821
            networks = c.list_networks()
1822
            stale = [n for n in networks
1823
                     if n["name"].startswith(SNF_TEST_PREFIX)]
1824
            if len(stale) == 0:
1825
                print >> sys.stderr, green + "    ...done" + normal
1826
                break
1827
            elif time.time() > fail_tmout:
1828
                print >> sys.stderr, red + \
1829
                    "Not all stale networks deleted. Action timed out." + \
1830
                    normal
1831
                sys.exit(1)
1832
            else:
1833
                time.sleep(query_interval)
1834
    else:
1835
        print >> sys.stderr, "Use --delete-stale to delete them."
1836

    
1837

    
1838
# --------------------------------------------------------------------
1839
# Parse arguments functions
1840
def parse_comma(option, opt, value, parser):
1841
    tests = set(['all', 'auth', 'images', 'flavors',
1842
                 'pithos', 'servers', 'server_spawn',
1843
                 'network_spawn'])
1844
    parse_input = value.split(',')
1845

    
1846
    if not (set(parse_input)).issubset(tests):
1847
        raise OptionValueError("The selected set of tests is invalid")
1848

    
1849
    setattr(parser.values, option.dest, value.split(','))
1850

    
1851

    
1852
def parse_arguments(args):
1853

    
1854
    kw = {}
1855
    kw["usage"] = "%prog [options]"
1856
    kw["description"] = \
1857
        "%prog runs a number of test scenarios on a " \
1858
        "Synnefo deployment."
1859

    
1860
    parser = OptionParser(**kw)
1861
    parser.disable_interspersed_args()
1862

    
1863
    parser.add_option("--api",
1864
                      action="store", type="string", dest="api",
1865
                      help="The API URI to use to reach the Synnefo API",
1866
                      default=None)
1867
    parser.add_option("--plankton",
1868
                      action="store", type="string", dest="plankton",
1869
                      help="The API URI to use to reach the Plankton API",
1870
                      default=None)
1871
    parser.add_option("--plankton-user",
1872
                      action="store", type="string", dest="plankton_user",
1873
                      help="Owner of system images",
1874
                      default=DEFAULT_PLANKTON_USER)
1875
    parser.add_option("--pithos",
1876
                      action="store", type="string", dest="pithos",
1877
                      help="The API URI to use to reach the Pithos API",
1878
                      default=None)
1879
    parser.add_option("--pithos_user",
1880
                      action="store", type="string", dest="pithos_user",
1881
                      help="Owner of the pithos account",
1882
                      default=None)
1883
    parser.add_option("--token",
1884
                      action="store", type="string", dest="token",
1885
                      help="The token to use for authentication to the API")
1886
    parser.add_option("--nofailfast",
1887
                      action="store_true", dest="nofailfast",
1888
                      help="Do not fail immediately if one of the tests "
1889
                           "fails (EXPERIMENTAL)",
1890
                      default=False)
1891
    parser.add_option("--no-ipv6",
1892
                      action="store_true", dest="no_ipv6",
1893
                      help="Disables ipv6 related tests",
1894
                      default=False)
1895
    parser.add_option("--action-timeout",
1896
                      action="store", type="int", dest="action_timeout",
1897
                      metavar="TIMEOUT",
1898
                      help="Wait SECONDS seconds for a server action to "
1899
                           "complete, then the test is considered failed",
1900
                      default=100)
1901
    parser.add_option("--build-warning",
1902
                      action="store", type="int", dest="build_warning",
1903
                      metavar="TIMEOUT",
1904
                      help="Warn if TIMEOUT seconds have passed and a "
1905
                           "build operation is still pending",
1906
                      default=600)
1907
    parser.add_option("--build-fail",
1908
                      action="store", type="int", dest="build_fail",
1909
                      metavar="BUILD_TIMEOUT",
1910
                      help="Fail the test if TIMEOUT seconds have passed "
1911
                           "and a build operation is still incomplete",
1912
                      default=900)
1913
    parser.add_option("--query-interval",
1914
                      action="store", type="int", dest="query_interval",
1915
                      metavar="INTERVAL",
1916
                      help="Query server status when requests are pending "
1917
                           "every INTERVAL seconds",
1918
                      default=3)
1919
    parser.add_option("--fanout",
1920
                      action="store", type="int", dest="fanout",
1921
                      metavar="COUNT",
1922
                      help="Spawn up to COUNT child processes to execute "
1923
                           "in parallel, essentially have up to COUNT "
1924
                           "server build requests outstanding (EXPERIMENTAL)",
1925
                      default=1)
1926
    parser.add_option("--force-flavor",
1927
                      action="store", type="int", dest="force_flavorid",
1928
                      metavar="FLAVOR ID",
1929
                      help="Force all server creations to use the specified "
1930
                           "FLAVOR ID instead of a randomly chosen one, "
1931
                           "useful if disk space is scarce",
1932
                      default=None)
1933
    parser.add_option("--image-id",
1934
                      action="store", type="string", dest="force_imageid",
1935
                      metavar="IMAGE ID",
1936
                      help="Test the specified image id, use 'all' to test "
1937
                           "all available images (mandatory argument)",
1938
                      default=None)
1939
    parser.add_option("--show-stale",
1940
                      action="store_true", dest="show_stale",
1941
                      help="Show stale servers from previous runs, whose "
1942
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1943
                      default=False)
1944
    parser.add_option("--delete-stale",
1945
                      action="store_true", dest="delete_stale",
1946
                      help="Delete stale servers from previous runs, whose "
1947
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1948
                      default=False)
1949
    parser.add_option("--force-personality",
1950
                      action="store", type="string", dest="personality_path",
1951
                      help="Force a personality file injection.\
1952
                            File path required. ",
1953
                      default=None)
1954
    parser.add_option("--log-folder",
1955
                      action="store", type="string", dest="log_folder",
1956
                      help="Define the absolute path where the output \
1957
                            log is stored. ",
1958
                      default="/var/log/burnin/")
1959
    parser.add_option("--verbose", "-V",
1960
                      action="store_true", dest="verbose",
1961
                      help="Print detailed output about multiple "
1962
                           "processes spawning",
1963
                      default=False)
1964
    parser.add_option("--set-tests",
1965
                      action="callback",
1966
                      dest="tests",
1967
                      type="string",
1968
                      help='Set comma seperated tests for this run. \
1969
                            Available tests: auth, images, flavors, \
1970
                                             servers, server_spawn, \
1971
                                             network_spawn, pithos. \
1972
                            Default = all',
1973
                      default='all',
1974
                      callback=parse_comma)
1975

    
1976
    (opts, args) = parser.parse_args(args)
1977

    
1978
    # -----------------------
1979
    # Verify arguments
1980

    
1981
    # `delete_stale' implies `show_stale'
1982
    if opts.delete_stale:
1983
        opts.show_stale = True
1984

    
1985
    # `token' is mandatory
1986
    _mandatory_argument(opts.token, "--token")
1987
    # `api' is mandatory
1988
    _mandatory_argument(opts.api, "--api")
1989

    
1990
    if not opts.show_stale:
1991
        # `image-id' is mandatory
1992
        _mandatory_argument(opts.force_imageid, "--image_id")
1993
        if opts.force_imageid != 'all':
1994
            try:
1995
                opts.force_imageid = str(opts.force_imageid)
1996
            except ValueError:
1997
                print >>sys.stderr, red + \
1998
                    "Invalid value specified for" + \
1999
                    "--image-id. Use a valid id, or `all'." + \
2000
                    normal
2001
                sys.exit(1)
2002
        # `pithos' is mandatory
2003
        _mandatory_argument(opts.pithos, "--pithos")
2004
        # `pithos_user' is mandatory
2005
        _mandatory_argument(opts.pithos_user, "--pithos_user")
2006
        # `plankton' is mandatory
2007
        _mandatory_argument(opts.plankton, "--plankton")
2008

    
2009
    return (opts, args)
2010

    
2011

    
2012
def _mandatory_argument(Arg, Str):
2013
    if not Arg:
2014
        print >>sys.stderr, red + \
2015
            "The " + Str + " argument is mandatory.\n" + \
2016
            normal
2017
        sys.exit(1)
2018

    
2019

    
2020
# --------------------------------------------------------------------
2021
# Burnin main function
2022
def main():
2023
    """Assemble test cases into a test suite, and run it
2024

2025
    IMPORTANT: Tests have dependencies and have to be run in the specified
2026
    order inside a single test case. They communicate through attributes of the
2027
    corresponding TestCase class (shared fixtures). Distinct subclasses of
2028
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
2029
    test runner processes.
2030

2031
    """
2032

    
2033
    # Parse arguments using `optparse'
2034
    (opts, args) = parse_arguments(sys.argv[1:])
2035

    
2036
    # Some global variables
2037
    global API, TOKEN, PLANKTON, PLANKTON_USER
2038
    global PITHOS, PITHOS_USER, NO_IPV6, VERBOSE, NOFAILFAST
2039
    API = opts.api
2040
    TOKEN = opts.token
2041
    PLANKTON = opts.plankton
2042
    PLANKTON_USER = opts.plankton_user
2043
    PITHOS = opts.pithos
2044
    PITHOS_USER = opts.pithos_user
2045
    NO_IPV6 = opts.no_ipv6
2046
    VERBOSE = opts.verbose
2047
    NOFAILFAST = opts.nofailfast
2048

    
2049
    # If `show_stale', cleanup stale servers
2050
    # from previous runs and exit
2051
    if opts.show_stale:
2052
        # We must clean the servers first
2053
        cleanup_servers(opts.action_timeout, opts.query_interval,
2054
                        delete_stale=opts.delete_stale)
2055
        cleanup_networks(opts.action_timeout, opts.query_interval,
2056
                         delete_stale=opts.delete_stale)
2057
        return 0
2058

    
2059
    # Initialize a kamaki instance, get flavors, images
2060
    c = ComputeClient(API, TOKEN)
2061
    DIMAGES = c.list_images(detail=True)
2062
    DFLAVORS = c.list_flavors(detail=True)
2063

    
2064
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
2065
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
2066
    #unittest.main(verbosity=2, catchbreak=True)
2067

    
2068
    # Get a list of images we are going to test
2069
    if opts.force_imageid == 'all':
2070
        test_images = DIMAGES
2071
    else:
2072
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
2073

    
2074
    # Create output (logging) folder
2075
    if not os.path.exists(opts.log_folder):
2076
        os.mkdir(opts.log_folder)
2077
    test_folder = os.path.join(opts.log_folder, TEST_RUN_ID)
2078
    os.mkdir(test_folder)
2079

    
2080
    for image in test_images:
2081
        imageid = str(image["id"])
2082
        imagename = image["name"]
2083
        # Choose a flavor (given from user or random)
2084
        if opts.force_flavorid:
2085
            flavorid = opts.force_flavorid
2086
        else:
2087
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
2088
        # Personality dictionary for file injection test
2089
        if opts.personality_path is not None:
2090
            f = open(opts.personality_path)
2091
            content = b64encode(f.read())
2092
            personality = []
2093
            st = os.stat(opts.personality_path)
2094
            personality.append({
2095
                'path': '/root/test_inj_file',
2096
                'owner': 'root',
2097
                'group': 'root',
2098
                'mode': 0x7777 & st.st_mode,
2099
                'contents': content})
2100
        else:
2101
            personality = None
2102
        # Give a name to our test servers
2103
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
2104
        is_windows = imagename.lower().find("windows") >= 0
2105

    
2106
        # Create Server TestCases
2107
        ServerTestCase = _spawn_server_test_case(
2108
            imageid=imageid,
2109
            flavorid=flavorid,
2110
            imagename=imagename,
2111
            personality=personality,
2112
            servername=servername,
2113
            is_windows=is_windows,
2114
            action_timeout=opts.action_timeout,
2115
            build_warning=opts.build_warning,
2116
            build_fail=opts.build_fail,
2117
            query_interval=opts.query_interval)
2118
        # Create Network TestCases
2119
        NetworkTestCase = _spawn_network_test_case(
2120
            action_timeout=opts.action_timeout,
2121
            imageid=imageid,
2122
            flavorid=flavorid,
2123
            imagename=imagename,
2124
            query_interval=opts.query_interval)
2125
        # Create Images TestCase
2126
        CImagesTestCase = _images_test_case(
2127
            action_timeout=opts.action_timeout,
2128
            imageid=imageid,
2129
            flavorid=flavorid,
2130
            imagename=imagename,
2131
            query_interval=opts.query_interval)
2132

    
2133
        # Choose the tests we are going to run
2134
        test_dict = {'auth': UnauthorizedTestCase,
2135
                     'images': CImagesTestCase,
2136
                     'flavors': FlavorsTestCase,
2137
                     'servers': ServersTestCase,
2138
                     'pithos': PithosTestCase,
2139
                     'server_spawn': ServerTestCase,
2140
                     'network_spawn': NetworkTestCase}
2141
        seq_cases = []
2142
        if 'all' in opts.tests:
2143
            seq_cases = [UnauthorizedTestCase, CImagesTestCase,
2144
                         FlavorsTestCase, ServersTestCase,
2145
                         PithosTestCase, ServerTestCase,
2146
                         NetworkTestCase]
2147
        else:
2148
            for test in opts.tests:
2149
                seq_cases.append(test_dict[test])
2150

    
2151
        # Folder for each image
2152
        image_folder = os.path.join(test_folder, imageid)
2153
        os.mkdir(image_folder)
2154

    
2155
        # Run each test
2156
        if opts.fanout > 1:
2157
            _run_cases_in_parallel(seq_cases, opts.fanout, image_folder)
2158
        else:
2159
            _run_cases_in_series(seq_cases, image_folder)
2160

    
2161

    
2162
# --------------------------------------------------------------------
2163
# Call main
2164
if __name__ == "__main__":
2165
    sys.exit(main())