Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin.py @ bcb7c357

History | View | Annotate | Download (78.5 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.astakos import AstakosClient
63
from kamaki.clients import ClientError
64

    
65
from vncauthproxy.d3des import generate_response as d3des_generate_response
66

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

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

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

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

    
98

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

    
118

    
119
def _get_user_id():
120
    """Authenticate to astakos and get unique users id"""
121
    astakos = AstakosClient(ASTAKOS, TOKEN)
122
    authenticate = astakos.authenticate()
123
    if 'uuid' in authenticate:
124
        return authenticate['uuid']
125
    else:
126
        return authenticate['uniq']
127

    
128

    
129
# --------------------------------------------------------------------
130
# BurninTestReulst class
131
class BurninTestResult(unittest.TextTestResult):
132
    def addSuccess(self, test):
133
        super(BurninTestResult, self).addSuccess(test)
134
        if self.showAll:
135
            if hasattr(test, 'result_dict'):
136
                run_details = test.result_dict
137

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

    
143
        elif self.dots:
144
            self.stream.write('.')
145
            self.stream.flush()
146

    
147
    def addError(self, test, err):
148
        super(BurninTestResult, self).addError(test, err)
149
        if self.showAll:
150
            self.stream.writeln("ERROR")
151
            if hasattr(test, 'result_dict'):
152
                run_details = test.result_dict
153

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

    
159
        elif self.dots:
160
            self.stream.write('E')
161
            self.stream.flush()
162

    
163
    def addFailure(self, test, err):
164
        super(BurninTestResult, self).addFailure(test, err)
165
        if self.showAll:
166
            self.stream.writeln("FAIL")
167
            if hasattr(test, 'result_dict'):
168
                run_details = test.result_dict
169

    
170
                self.stream.write("\n")
171
                for i in run_details:
172
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
173
                self.stream.write("\n")
174

    
175
        elif self.dots:
176
            self.stream.write('F')
177
            self.stream.flush()
178

    
179

    
180
# --------------------------------------------------------------------
181
# Format Results
182
class burninFormatter(logging.Formatter):
183
    err_fmt = red + "ERROR: %(msg)s" + normal
184
    dbg_fmt = green + "* %(msg)s" + normal
185
    info_fmt = "%(msg)s"
186

    
187
    def __init__(self, fmt="%(levelno)s: %(msg)s"):
188
        logging.Formatter.__init__(self, fmt)
189

    
190
    def format(self, record):
191
        format_orig = self._fmt
192
        # Replace the original format with one customized by logging level
193
        if record.levelno == 10:    # DEBUG
194
            self._fmt = burninFormatter.dbg_fmt
195
        elif record.levelno == 20:  # INFO
196
            self._fmt = burninFormatter.info_fmt
197
        elif record.levelno == 40:  # ERROR
198
            self._fmt = burninFormatter.err_fmt
199
        result = logging.Formatter.format(self, record)
200
        self._fmt = format_orig
201
        return result
202

    
203
log = logging.getLogger("burnin")
204
log.setLevel(logging.DEBUG)
205
handler = logging.StreamHandler()
206
handler.setFormatter(burninFormatter())
207
log.addHandler(handler)
208

    
209

    
210
# --------------------------------------------------------------------
211
# UnauthorizedTestCase class
212
class UnauthorizedTestCase(unittest.TestCase):
213
    """Test unauthorized access"""
214
    @classmethod
215
    def setUpClass(cls):
216
        cls.result_dict = dict()
217

    
218
    def test_unauthorized_access(self):
219
        """Test access without a valid token fails"""
220
        log.info("Authentication test")
221
        falseToken = '12345'
222
        c = ComputeClient(API, falseToken)
223

    
224
        with self.assertRaises(ClientError) as cm:
225
            c.list_servers()
226
            self.assertEqual(cm.exception.status, 401)
227

    
228

    
229
# --------------------------------------------------------------------
230
# This class gest replicated into Images TestCases dynamically
231
class ImagesTestCase(unittest.TestCase):
232
    """Test image lists for consistency"""
233
    @classmethod
234
    def setUpClass(cls):
235
        """Initialize kamaki, get (detailed) list of images"""
236
        log.info("Getting simple and detailed list of images")
237
        cls.client = ComputeClient(API, TOKEN)
238
        cls.plankton = ImageClient(PLANKTON, TOKEN)
239
        cls.images = cls.plankton.list_public()
240
        cls.dimages = cls.plankton.list_public(detail=True)
241
        cls.result_dict = dict()
242
        # Get uniq user id
243
        cls.uuid = _get_user_id()
244
        log.info("Uniq user id = %s" % cls.uuid)
245
        # Create temp directory and store it inside our class
246
        # XXX: In my machine /tmp has not enough space
247
        #      so use current directory to be sure.
248
        cls.temp_dir = tempfile.mkdtemp(dir=os.getcwd())
249
        cls.temp_image_name = \
250
            SNF_TEST_PREFIX + cls.imageid + ".diskdump"
251

    
252
    @classmethod
253
    def tearDownClass(cls):
254
        """Remove local files"""
255
        try:
256
            temp_file = os.path.join(cls.temp_dir, cls.temp_image_name)
257
            os.unlink(temp_file)
258
        except:
259
            pass
260
        try:
261
            os.rmdir(cls.temp_dir)
262
        except:
263
            pass
264

    
265
    def test_001_list_images(self):
266
        """Test image list actually returns images"""
267
        self.assertGreater(len(self.images), 0)
268

    
269
    def test_002_list_images_detailed(self):
270
        """Test detailed image list is the same length as list"""
271
        self.assertEqual(len(self.dimages), len(self.images))
272

    
273
    def test_003_same_image_names(self):
274
        """Test detailed and simple image list contain same names"""
275
        names = sorted(map(lambda x: x["name"], self.images))
276
        dnames = sorted(map(lambda x: x["name"], self.dimages))
277
        self.assertEqual(names, dnames)
278

    
279
    def test_004_unique_image_names(self):
280
        """Test system images have unique names"""
281
        sys_images = filter(lambda x: x['owner'] == PLANKTON_USER,
282
                            self.dimages)
283
        names = sorted(map(lambda x: x["name"], sys_images))
284
        self.assertEqual(sorted(list(set(names))), names)
285

    
286
    def test_005_image_metadata(self):
287
        """Test every image has specific metadata defined"""
288
        keys = frozenset(["osfamily", "root_partition"])
289
        details = self.client.list_images(detail=True)
290
        for i in details:
291
            self.assertTrue(keys.issubset(i["metadata"].keys()))
292

    
293
    def test_006_download_image(self):
294
        """Download image from pithos+"""
295
        # Get image location
296
        image = filter(
297
            lambda x: x['id'] == self.imageid, self.dimages)[0]
298
        image_location = \
299
            image['location'].replace("://", " ").replace("/", " ").split()
300
        log.info("Download image, with owner %s\n\tcontainer %s, and name %s"
301
                 % (image_location[1], image_location[2], image_location[3]))
302
        pithos_client = PithosClient(PITHOS, TOKEN, image_location[1])
303
        pithos_client.container = image_location[2]
304
        temp_file = os.path.join(self.temp_dir, self.temp_image_name)
305
        with open(temp_file, "wb+") as f:
306
            pithos_client.download_object(image_location[3], f)
307

    
308
    def test_007_upload_image(self):
309
        """Upload and register image"""
310
        temp_file = os.path.join(self.temp_dir, self.temp_image_name)
311
        log.info("Upload image to pithos+")
312
        # Create container `images'
313
        pithos_client = PithosClient(PITHOS, TOKEN, self.uuid)
314
        pithos_client.container = "images"
315
        pithos_client.container_put()
316
        with open(temp_file, "rb+") as f:
317
            pithos_client.upload_object(self.temp_image_name, f)
318
        log.info("Register image to plankton")
319
        location = "pithos://" + self.uuid + \
320
            "/images/" + self.temp_image_name
321
        params = {'is_public': True}
322
        properties = {'OSFAMILY': "linux", 'ROOT_PARTITION': 1}
323
        self.plankton.register(self.temp_image_name, location,
324
                               params, properties)
325
        # Get image id
326
        details = self.plankton.list_public(detail=True)
327
        detail = filter(lambda x: x['location'] == location, details)
328
        self.assertEqual(len(detail), 1)
329
        cls = type(self)
330
        cls.temp_image_id = detail[0]['id']
331
        log.info("Image registered with id %s" % detail[0]['id'])
332

    
333
    def test_008_cleanup_image(self):
334
        """Cleanup image test"""
335
        log.info("Cleanup image test")
336
        # Remove image from pithos+
337
        pithos_client = PithosClient(PITHOS, TOKEN, self.uuid)
338
        pithos_client.container = "images"
339
        pithos_client.del_object(self.temp_image_name)
340

    
341

    
342
# --------------------------------------------------------------------
343
# FlavorsTestCase class
344
class FlavorsTestCase(unittest.TestCase):
345
    """Test flavor lists for consistency"""
346
    @classmethod
347
    def setUpClass(cls):
348
        """Initialize kamaki, get (detailed) list of flavors"""
349
        log.info("Getting simple and detailed list of flavors")
350
        cls.client = ComputeClient(API, TOKEN)
351
        cls.flavors = cls.client.list_flavors()
352
        cls.dflavors = cls.client.list_flavors(detail=True)
353
        cls.result_dict = dict()
354

    
355
    def test_001_list_flavors(self):
356
        """Test flavor list actually returns flavors"""
357
        self.assertGreater(len(self.flavors), 0)
358

    
359
    def test_002_list_flavors_detailed(self):
360
        """Test detailed flavor list is the same length as list"""
361
        self.assertEquals(len(self.dflavors), len(self.flavors))
362

    
363
    def test_003_same_flavor_names(self):
364
        """Test detailed and simple flavor list contain same names"""
365
        names = sorted(map(lambda x: x["name"], self.flavors))
366
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
367
        self.assertEqual(names, dnames)
368

    
369
    def test_004_unique_flavor_names(self):
370
        """Test flavors have unique names"""
371
        names = sorted(map(lambda x: x["name"], self.flavors))
372
        self.assertEqual(sorted(list(set(names))), names)
373

    
374
    def test_005_well_formed_flavor_names(self):
375
        """Test flavors have names of the form CxxRyyDzz
376
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
377
        """
378
        for f in self.dflavors:
379
            flavor = (f["cpu"], f["ram"], f["disk"], f["SNF:disk_template"])
380
            self.assertEqual("C%dR%dD%d%s" % flavor,
381
                             f["name"],
382
                             "Flavor %s does not match its specs." % f["name"])
383

    
384

    
385
# --------------------------------------------------------------------
386
# ServersTestCase class
387
class ServersTestCase(unittest.TestCase):
388
    """Test server lists for consistency"""
389
    @classmethod
390
    def setUpClass(cls):
391
        """Initialize kamaki, get (detailed) list of servers"""
392
        log.info("Getting simple and detailed list of servers")
393

    
394
        cls.client = ComputeClient(API, TOKEN)
395
        cls.servers = cls.client.list_servers()
396
        cls.dservers = cls.client.list_servers(detail=True)
397
        cls.result_dict = dict()
398

    
399
    # def test_001_list_servers(self):
400
    #     """Test server list actually returns servers"""
401
    #     self.assertGreater(len(self.servers), 0)
402

    
403
    def test_002_list_servers_detailed(self):
404
        """Test detailed server list is the same length as list"""
405
        self.assertEqual(len(self.dservers), len(self.servers))
406

    
407
    def test_003_same_server_names(self):
408
        """Test detailed and simple flavor list contain same names"""
409
        names = sorted(map(lambda x: x["name"], self.servers))
410
        dnames = sorted(map(lambda x: x["name"], self.dservers))
411
        self.assertEqual(names, dnames)
412

    
413

    
414
# --------------------------------------------------------------------
415
# Pithos Test Cases
416
class PithosTestCase(unittest.TestCase):
417
    """Test pithos functionality"""
418
    @classmethod
419
    def setUpClass(cls):
420
        """Initialize kamaki, get list of containers"""
421
        # Get uniq user id
422
        cls.uuid = _get_user_id()
423
        log.info("Uniq user id = %s" % cls.uuid)
424
        log.info("Getting list of containers")
425
        cls.client = PithosClient(PITHOS, TOKEN, cls.uuid)
426
        cls.containers = cls.client.list_containers()
427
        cls.result_dict = dict()
428

    
429
    def test_001_list_containers(self):
430
        """Test container list actually returns containers"""
431
        self.assertGreater(len(self.containers), 0)
432

    
433
    def test_002_unique_containers(self):
434
        """Test if containers have unique names"""
435
        names = [n['name'] for n in self.containers]
436
        names = sorted(names)
437
        self.assertEqual(sorted(list(set(names))), names)
438

    
439
    def test_003_create_container(self):
440
        """Test create a container"""
441
        rand_num = randint(1000, 9999)
442
        rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
443
        names = [n['name'] for n in self.containers]
444
        while rand_name in names:
445
            rand_num = randint(1000, 9999)
446
            rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
447
        # Create container
448
        self.client.container = rand_name
449
        self.client.container_put()
450
        # Get list of containers
451
        new_containers = self.client.list_containers()
452
        new_container_names = [n['name'] for n in new_containers]
453
        self.assertIn(rand_name, new_container_names)
454

    
455
    def test_004_upload(self):
456
        """Test uploading something to pithos+"""
457
        # Create a tmp file
458
        with tempfile.TemporaryFile() as f:
459
            f.write("This is a temp file")
460
            f.seek(0, 0)
461
            # Where to save file
462
            self.client.upload_object("test.txt", f)
463

    
464
    def test_005_download(self):
465
        """Test download something from pithos+"""
466
        # Create tmp directory to save file
467
        tmp_dir = tempfile.mkdtemp()
468
        tmp_file = os.path.join(tmp_dir, "test.txt")
469
        with open(tmp_file, "wb+") as f:
470
            self.client.download_object("test.txt", f)
471
            # Read file
472
            f.seek(0, 0)
473
            content = f.read()
474
        # Remove files
475
        os.unlink(tmp_file)
476
        os.rmdir(tmp_dir)
477
        # Compare results
478
        self.assertEqual(content, "This is a temp file")
479

    
480
    def test_006_remove(self):
481
        """Test removing files and containers"""
482
        cont_name = self.client.container
483
        self.client.del_object("test.txt")
484
        self.client.purge_container()
485
        # List containers
486
        containers = self.client.list_containers()
487
        cont_names = [n['name'] for n in containers]
488
        self.assertNotIn(cont_name, cont_names)
489

    
490

    
491
# --------------------------------------------------------------------
492
# This class gets replicated into actual TestCases dynamically
493
class SpawnServerTestCase(unittest.TestCase):
494
    """Test scenario for server of the specified image"""
495
    @classmethod
496
    def setUpClass(cls):
497
        """Initialize a kamaki instance"""
498
        log.info("Spawning server for image `%s'" % cls.imagename)
499
        cls.client = ComputeClient(API, TOKEN)
500
        cls.cyclades = CycladesClient(API, TOKEN)
501
        cls.result_dict = dict()
502

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

    
506
        nics = server["attachments"]
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["ipv4"]
512

    
513
        self.assertTrue(public_addrs is not None)
514

    
515
        return public_addrs
516

    
517
    def _get_ipv6(self, server):
518
        """Get the public IPv6 of a server from the detailed server info"""
519

    
520
        nics = server["attachments"]
521

    
522
        for nic in nics:
523
            net_id = nic["network_id"]
524
            if self.cyclades.get_network_details(net_id)["public"]:
525
                public_addrs = nic["ipv6"]
526

    
527
        self.assertTrue(public_addrs is not None)
528

    
529
        return public_addrs
530

    
531
    def _connect_loginname(self, os_value):
532
        """Return the login name for connections based on the server OS"""
533
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
534
            return "user"
535
        elif os_value in ("windows", "windows_alpha1"):
536
            return "Administrator"
537
        else:
538
            return "root"
539

    
540
    def _verify_server_status(self, current_status, new_status):
541
        """Verify a server has switched to a specified status"""
542
        server = self.client.get_server_details(self.serverid)
543
        if server["status"] not in (current_status, new_status):
544
            return None  # Do not raise exception, return so the test fails
545
        self.assertEquals(server["status"], new_status)
546

    
547
    def _get_connected_tcp_socket(self, family, host, port):
548
        """Get a connected socket from the specified family to host:port"""
549
        sock = None
550
        for res in \
551
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
552
                               socket.AI_PASSIVE):
553
            af, socktype, proto, canonname, sa = res
554
            try:
555
                sock = socket.socket(af, socktype, proto)
556
            except socket.error:
557
                sock = None
558
                continue
559
            try:
560
                sock.connect(sa)
561
            except socket.error:
562
                sock.close()
563
                sock = None
564
                continue
565
        self.assertIsNotNone(sock)
566
        return sock
567

    
568
    def _ping_once(self, ipv6, ip):
569
        """Test server responds to a single IPv4 or IPv6 ping"""
570
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
571
        ping = subprocess.Popen(cmd, shell=True,
572
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
573
        (stdout, stderr) = ping.communicate()
574
        ret = ping.wait()
575
        self.assertEquals(ret, 0)
576

    
577
    def _get_hostname_over_ssh(self, hostip, username, password):
578
        lines, status = _ssh_execute(
579
            hostip, username, password, "hostname")
580
        self.assertEqual(len(lines), 1)
581
        return lines[0]
582

    
583
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
584
                                   opmsg, callable, *args, **kwargs):
585
        if warn_timeout == fail_timeout:
586
            warn_timeout = fail_timeout + 1
587
        warn_tmout = time.time() + warn_timeout
588
        fail_tmout = time.time() + fail_timeout
589
        while True:
590
            self.assertLess(time.time(), fail_tmout,
591
                            "operation `%s' timed out" % opmsg)
592
            if time.time() > warn_tmout:
593
                log.warning("Server %d: `%s' operation `%s' not done yet",
594
                            self.serverid, self.servername, opmsg)
595
            try:
596
                log.info("%s... " % opmsg)
597
                return callable(*args, **kwargs)
598
            except AssertionError:
599
                pass
600
            time.sleep(self.query_interval)
601

    
602
    def _insist_on_tcp_connection(self, family, host, port):
603
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
604
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
605
        msg = "connect over %s to %s:%s" % \
606
              (familystr.get(family, "Unknown"), host, port)
607
        sock = self._try_until_timeout_expires(
608
            self.action_timeout, self.action_timeout,
609
            msg, self._get_connected_tcp_socket,
610
            family, host, port)
611
        return sock
612

    
613
    def _insist_on_status_transition(self, current_status, new_status,
614
                                     fail_timeout, warn_timeout=None):
615
        msg = "Server %d: `%s', waiting for %s -> %s" % \
616
              (self.serverid, self.servername, current_status, new_status)
617
        if warn_timeout is None:
618
            warn_timeout = fail_timeout
619
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
620
                                        msg, self._verify_server_status,
621
                                        current_status, new_status)
622
        # Ensure the status is actually the expected one
623
        server = self.client.get_server_details(self.serverid)
624
        self.assertEquals(server["status"], new_status)
625

    
626
    def _insist_on_ssh_hostname(self, hostip, username, password):
627
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
628
        hostname = self._try_until_timeout_expires(
629
            self.action_timeout, self.action_timeout,
630
            msg, self._get_hostname_over_ssh,
631
            hostip, username, password)
632

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

    
636
    def _check_file_through_ssh(self, hostip, username, password,
637
                                remotepath, content):
638
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
639
            (hostip, username, password)
640
        log.info(msg)
641
        try:
642
            ssh = paramiko.SSHClient()
643
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
644
            ssh.connect(hostip, username=username, password=password)
645
            ssh.close()
646
        except socket.error:
647
            raise AssertionError
648

    
649
        transport = paramiko.Transport((hostip, 22))
650
        transport.connect(username=username, password=password)
651

    
652
        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
653
        sftp = paramiko.SFTPClient.from_transport(transport)
654
        sftp.get(remotepath, localpath)
655
        sftp.close()
656
        transport.close()
657

    
658
        f = open(localpath)
659
        remote_content = b64encode(f.read())
660

    
661
        # Check if files are the same
662
        return (remote_content == content)
663

    
664
    def _skipIf(self, condition, msg):
665
        if condition:
666
            self.skipTest(msg)
667

    
668
    def test_001_submit_create_server(self):
669
        """Test submit create server request"""
670

    
671
        log.info("Submit new server request")
672
        server = self.client.create_server(self.servername, self.flavorid,
673
                                           self.imageid, self.personality)
674

    
675
        log.info("Server id: " + str(server["id"]))
676
        log.info("Server password: " + server["adminPass"])
677
        self.assertEqual(server["name"], self.servername)
678
        self.assertEqual(server["flavor"], self.flavorid)
679
        self.assertEqual(server["image"], self.imageid)
680
        self.assertEqual(server["status"], "BUILD")
681

    
682
        # Update class attributes to reflect data on building server
683
        cls = type(self)
684
        cls.serverid = server["id"]
685
        cls.username = None
686
        cls.passwd = server["adminPass"]
687

    
688
        self.result_dict["Server ID"] = str(server["id"])
689
        self.result_dict["Password"] = str(server["adminPass"])
690

    
691
    def test_002a_server_is_building_in_list(self):
692
        """Test server is in BUILD state, in server list"""
693
        log.info("Server in BUILD state in server list")
694

    
695
        self.result_dict.clear()
696

    
697
        servers = self.client.list_servers(detail=True)
698
        servers = filter(lambda x: x["name"] == self.servername, servers)
699

    
700
        server = servers[0]
701
        self.assertEqual(server["name"], self.servername)
702
        self.assertEqual(server["flavor"], self.flavorid)
703
        self.assertEqual(server["image"], self.imageid)
704
        self.assertEqual(server["status"], "BUILD")
705

    
706
    def test_002b_server_is_building_in_details(self):
707
        """Test server is in BUILD state, in details"""
708

    
709
        log.info("Server in BUILD state in details")
710

    
711
        server = self.client.get_server_details(self.serverid)
712
        self.assertEqual(server["name"], self.servername)
713
        self.assertEqual(server["flavor"], self.flavorid)
714
        self.assertEqual(server["image"], self.imageid)
715
        self.assertEqual(server["status"], "BUILD")
716

    
717
    def test_002c_set_server_metadata(self):
718

    
719
        log.info("Creating server metadata")
720

    
721
        image = self.client.get_image_details(self.imageid)
722
        os_value = image["metadata"]["os"]
723
        users = image["metadata"].get("users", None)
724
        self.client.update_server_metadata(self.serverid, OS=os_value)
725

    
726
        userlist = users.split()
727

    
728
        # Determine the username to use for future connections
729
        # to this host
730
        cls = type(self)
731

    
732
        if "root" in userlist:
733
            cls.username = "root"
734
        elif users is None:
735
            cls.username = self._connect_loginname(os_value)
736
        else:
737
            cls.username = choice(userlist)
738

    
739
        self.assertIsNotNone(cls.username)
740

    
741
    def test_002d_verify_server_metadata(self):
742
        """Test server metadata keys are set based on image metadata"""
743

    
744
        log.info("Verifying image metadata")
745

    
746
        servermeta = self.client.get_server_metadata(self.serverid)
747
        imagemeta = self.client.get_image_metadata(self.imageid)
748

    
749
        self.assertEqual(servermeta["OS"], imagemeta["os"])
750

    
751
    def test_003_server_becomes_active(self):
752
        """Test server becomes ACTIVE"""
753

    
754
        log.info("Waiting for server to become ACTIVE")
755

    
756
        self._insist_on_status_transition(
757
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
758

    
759
    def test_003a_get_server_oob_console(self):
760
        """Test getting OOB server console over VNC
761

762
        Implementation of RFB protocol follows
763
        http://www.realvnc.com/docs/rfbproto.pdf.
764

765
        """
766
        console = self.cyclades.get_server_console(self.serverid)
767
        self.assertEquals(console['type'], "vnc")
768
        sock = self._insist_on_tcp_connection(
769
            socket.AF_INET, console["host"], console["port"])
770

    
771
        # Step 1. ProtocolVersion message (par. 6.1.1)
772
        version = sock.recv(1024)
773
        self.assertEquals(version, 'RFB 003.008\n')
774
        sock.send(version)
775

    
776
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
777
        sec = sock.recv(1024)
778
        self.assertEquals(list(sec), ['\x01', '\x02'])
779

    
780
        # Step 3. Request VNC Authentication (par 6.1.2)
781
        sock.send('\x02')
782

    
783
        # Step 4. Receive Challenge (par 6.2.2)
784
        challenge = sock.recv(1024)
785
        self.assertEquals(len(challenge), 16)
786

    
787
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
788
        response = d3des_generate_response(
789
            (console["password"] + '\0' * 8)[:8], challenge)
790
        sock.send(response)
791

    
792
        # Step 6. SecurityResult (par 6.1.3)
793
        result = sock.recv(4)
794
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
795
        sock.close()
796

    
797
    def test_004_server_has_ipv4(self):
798
        """Test active server has a valid IPv4 address"""
799

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

    
802
        server = self.client.get_server_details(self.serverid)
803
        ipv4 = self._get_ipv4(server)
804

    
805
        self.result_dict.clear()
806
        self.result_dict["IPv4"] = str(ipv4)
807

    
808
        self.assertEquals(IP(ipv4).version(), 4)
809

    
810
    def test_005_server_has_ipv6(self):
811
        """Test active server has a valid IPv6 address"""
812
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
813

    
814
        log.info("Validate server's IPv6")
815

    
816
        server = self.client.get_server_details(self.serverid)
817
        ipv6 = self._get_ipv6(server)
818

    
819
        self.result_dict.clear()
820
        self.result_dict["IPv6"] = str(ipv6)
821

    
822
        self.assertEquals(IP(ipv6).version(), 6)
823

    
824
    def test_006_server_responds_to_ping_IPv4(self):
825
        """Test server responds to ping on IPv4 address"""
826

    
827
        log.info("Testing if server responds to pings in IPv4")
828
        self.result_dict.clear()
829

    
830
        server = self.client.get_server_details(self.serverid)
831
        ip = self._get_ipv4(server)
832
        self._try_until_timeout_expires(self.action_timeout,
833
                                        self.action_timeout,
834
                                        "PING IPv4 to %s" % ip,
835
                                        self._ping_once,
836
                                        False, ip)
837

    
838
    def test_007_server_responds_to_ping_IPv6(self):
839
        """Test server responds to ping on IPv6 address"""
840
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
841
        log.info("Testing if server responds to pings in IPv6")
842

    
843
        server = self.client.get_server_details(self.serverid)
844
        ip = self._get_ipv6(server)
845
        self._try_until_timeout_expires(self.action_timeout,
846
                                        self.action_timeout,
847
                                        "PING IPv6 to %s" % ip,
848
                                        self._ping_once,
849
                                        True, ip)
850

    
851
    def test_008_submit_shutdown_request(self):
852
        """Test submit request to shutdown server"""
853

    
854
        log.info("Shutting down server")
855

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

    
858
    def test_009_server_becomes_stopped(self):
859
        """Test server becomes STOPPED"""
860

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

    
865
    def test_010_submit_start_request(self):
866
        """Test submit start server request"""
867

    
868
        log.info("Starting server")
869

    
870
        self.cyclades.start_server(self.serverid)
871

    
872
    def test_011_server_becomes_active(self):
873
        """Test server becomes ACTIVE again"""
874

    
875
        log.info("Waiting until server becomes ACTIVE")
876
        self._insist_on_status_transition(
877
            "STOPPED", "ACTIVE", self.action_timeout, self.action_timeout)
878

    
879
    def test_011a_server_responds_to_ping_IPv4(self):
880
        """Test server OS is actually up and running again"""
881

    
882
        log.info("Testing if server is actually up and running")
883

    
884
        self.test_006_server_responds_to_ping_IPv4()
885

    
886
    def test_012_ssh_to_server_IPv4(self):
887
        """Test SSH to server public IPv4 works, verify hostname"""
888

    
889
        self._skipIf(self.is_windows, "only valid for Linux servers")
890
        server = self.client.get_server_details(self.serverid)
891
        self._insist_on_ssh_hostname(self._get_ipv4(server),
892
                                     self.username, self.passwd)
893

    
894
    def test_013_ssh_to_server_IPv6(self):
895
        """Test SSH to server public IPv6 works, verify hostname"""
896
        self._skipIf(self.is_windows, "only valid for Linux servers")
897
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
898

    
899
        server = self.client.get_server_details(self.serverid)
900
        self._insist_on_ssh_hostname(self._get_ipv6(server),
901
                                     self.username, self.passwd)
902

    
903
    def test_014_rdp_to_server_IPv4(self):
904
        "Test RDP connection to server public IPv4 works"""
905
        self._skipIf(not self.is_windows, "only valid for Windows servers")
906
        server = self.client.get_server_details(self.serverid)
907
        ipv4 = self._get_ipv4(server)
908
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 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
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
913
        sock.close()
914

    
915
    def test_015_rdp_to_server_IPv6(self):
916
        "Test RDP connection to server public IPv6 works"""
917
        self._skipIf(not self.is_windows, "only valid for Windows servers")
918
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
919

    
920
        server = self.client.get_server_details(self.serverid)
921
        ipv6 = self._get_ipv6(server)
922
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
923

    
924
        # No actual RDP processing done. We assume the RDP server is there
925
        # if the connection to the RDP port is successful.
926
        sock.close()
927

    
928
    def test_016_personality_is_enforced(self):
929
        """Test file injection for personality enforcement"""
930
        self._skipIf(self.is_windows, "only implemented for Linux servers")
931
        self._skipIf(self.personality is None, "No personality file selected")
932

    
933
        log.info("Trying to inject file for personality enforcement")
934

    
935
        server = self.client.get_server_details(self.serverid)
936

    
937
        for inj_file in self.personality:
938
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
939
                                                       inj_file['owner'],
940
                                                       self.passwd,
941
                                                       inj_file['path'],
942
                                                       inj_file['contents'])
943
            self.assertTrue(equal_files)
944

    
945
    def test_017_submit_delete_request(self):
946
        """Test submit request to delete server"""
947

    
948
        log.info("Deleting server")
949

    
950
        self.client.delete_server(self.serverid)
951

    
952
    def test_018_server_becomes_deleted(self):
953
        """Test server becomes DELETED"""
954

    
955
        log.info("Testing if server becomes DELETED")
956

    
957
        self._insist_on_status_transition(
958
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
959

    
960
    def test_019_server_no_longer_in_server_list(self):
961
        """Test server is no longer in server list"""
962

    
963
        log.info("Test if server is no longer listed")
964

    
965
        servers = self.client.list_servers()
966
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
967

    
968

    
969
class NetworkTestCase(unittest.TestCase):
970
    """ Testing networking in cyclades """
971

    
972
    @classmethod
973
    def setUpClass(cls):
974
        "Initialize kamaki, get list of current networks"
975

    
976
        cls.client = CycladesClient(API, TOKEN)
977
        cls.compute = ComputeClient(API, TOKEN)
978

    
979
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
980
                                          TEST_RUN_ID,
981
                                          cls.imagename)
982

    
983
        #Dictionary initialization for the vms credentials
984
        cls.serverid = dict()
985
        cls.username = dict()
986
        cls.password = dict()
987
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
988

    
989
        cls.result_dict = dict()
990

    
991
    def _skipIf(self, condition, msg):
992
        if condition:
993
            self.skipTest(msg)
994

    
995
    def _get_ipv4(self, server):
996
        """Get the public IPv4 of a server from the detailed server info"""
997

    
998
        nics = server["attachments"]
999

    
1000
        for nic in nics:
1001
            net_id = nic["network_id"]
1002
            if self.client.get_network_details(net_id)["public"]:
1003
                public_addrs = nic["ipv4"]
1004

    
1005
        self.assertTrue(public_addrs is not None)
1006

    
1007
        return public_addrs
1008

    
1009
    def _connect_loginname(self, os_value):
1010
        """Return the login name for connections based on the server OS"""
1011
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
1012
            return "user"
1013
        elif os_value in ("windows", "windows_alpha1"):
1014
            return "Administrator"
1015
        else:
1016
            return "root"
1017

    
1018
    def _ping_once(self, ip):
1019

    
1020
        """Test server responds to a single IPv4 or IPv6 ping"""
1021
        cmd = "ping -c 2 -w 3 %s" % (ip)
1022
        ping = subprocess.Popen(cmd, shell=True,
1023
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1024
        (stdout, stderr) = ping.communicate()
1025
        ret = ping.wait()
1026

    
1027
        return (ret == 0)
1028

    
1029
    def test_00001a_submit_create_server_A(self):
1030
        """Test submit create server request"""
1031

    
1032
        log.info("Creating test server A")
1033

    
1034
        serverA = self.client.create_server(self.servername, self.flavorid,
1035
                                            self.imageid, personality=None)
1036

    
1037
        self.assertEqual(serverA["name"], self.servername)
1038
        self.assertEqual(serverA["flavor"], self.flavorid)
1039
        self.assertEqual(serverA["image"], self.imageid)
1040
        self.assertEqual(serverA["status"], "BUILD")
1041

    
1042
        # Update class attributes to reflect data on building server
1043
        self.serverid['A'] = serverA["id"]
1044
        self.username['A'] = None
1045
        self.password['A'] = serverA["adminPass"]
1046

    
1047
        log.info("Server A id:" + str(serverA["id"]))
1048
        log.info("Server password " + (self.password['A']))
1049

    
1050
        self.result_dict["Server A ID"] = str(serverA["id"])
1051
        self.result_dict["Server A password"] = serverA["adminPass"]
1052

    
1053
    def test_00001b_serverA_becomes_active(self):
1054
        """Test server becomes ACTIVE"""
1055

    
1056
        log.info("Waiting until test server A becomes ACTIVE")
1057
        self.result_dict.clear()
1058

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

    
1071
        self.assertTrue(active)
1072

    
1073
    def test_00002a_submit_create_server_B(self):
1074
        """Test submit create server request"""
1075

    
1076
        log.info("Creating test server B")
1077

    
1078
        serverB = self.client.create_server(self.servername, self.flavorid,
1079
                                            self.imageid, personality=None)
1080

    
1081
        self.assertEqual(serverB["name"], self.servername)
1082
        self.assertEqual(serverB["flavor"], self.flavorid)
1083
        self.assertEqual(serverB["image"], self.imageid)
1084
        self.assertEqual(serverB["status"], "BUILD")
1085

    
1086
        # Update class attributes to reflect data on building server
1087
        self.serverid['B'] = serverB["id"]
1088
        self.username['B'] = None
1089
        self.password['B'] = serverB["adminPass"]
1090

    
1091
        log.info("Server B id: " + str(serverB["id"]))
1092
        log.info("Password " + (self.password['B']))
1093

    
1094
        self.result_dict.clear()
1095
        self.result_dict["Server B ID"] = str(serverB["id"])
1096
        self.result_dict["Server B password"] = serverB["adminPass"]
1097

    
1098
    def test_00002b_serverB_becomes_active(self):
1099
        """Test server becomes ACTIVE"""
1100

    
1101
        log.info("Waiting until test server B becomes ACTIVE")
1102
        self.result_dict.clear()
1103

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

    
1116
        self.assertTrue(active)
1117

    
1118
    def test_001_create_network(self):
1119
        """Test submit create network request"""
1120

    
1121
        log.info("Submit new network request")
1122
        self.result_dict.clear()
1123

    
1124
        name = SNF_TEST_PREFIX + TEST_RUN_ID
1125
        #previous_num = len(self.client.list_networks())
1126
        network = self.client.create_network(name, cidr='10.0.1.0/28',
1127
                                             dhcp=True)
1128

    
1129
        #Test if right name is assigned
1130
        self.assertEqual(network['name'], name)
1131

    
1132
        # Update class attributes
1133
        cls = type(self)
1134
        cls.networkid = network['id']
1135
        #networks = self.client.list_networks()
1136

    
1137
        fail_tmout = time.time() + self.action_timeout
1138

    
1139
        #Test if new network is created
1140
        while True:
1141
            d = self.client.get_network_details(network['id'])
1142
            if d['status'] == 'ACTIVE':
1143
                connected = True
1144
                break
1145
            elif time.time() > fail_tmout:
1146
                self.assertLess(time.time(), fail_tmout)
1147
            else:
1148
                log.info("Waiting for network to become ACTIVE")
1149
                time.sleep(self.query_interval)
1150

    
1151
        self.assertTrue(connected)
1152

    
1153
        self.result_dict["Private network ID"] = str(network['id'])
1154

    
1155
    def test_002_connect_to_network(self):
1156
        """Test connect VMs to network"""
1157

    
1158
        log.info("Connect VMs to private network")
1159
        self.result_dict.clear()
1160

    
1161
        self.client.connect_server(self.serverid['A'], self.networkid)
1162
        self.client.connect_server(self.serverid['B'], self.networkid)
1163

    
1164
        #Insist on connecting until action timeout
1165
        fail_tmout = time.time() + self.action_timeout
1166

    
1167
        while True:
1168

    
1169
            netsA = [x['network_id']
1170
                     for x in self.client.get_server_details(
1171
                         self.serverid['A'])['attachments']]
1172
            netsB = [x['network_id']
1173
                     for x in self.client.get_server_details(
1174
                         self.serverid['B'])['attachments']]
1175

    
1176
            if (self.networkid in netsA) and (self.networkid in netsB):
1177
                conn_exists = True
1178
                break
1179
            elif time.time() > fail_tmout:
1180
                self.assertLess(time.time(), fail_tmout)
1181
            else:
1182
                time.sleep(self.query_interval)
1183

    
1184
        #Adding private IPs to class attributes
1185
        cls = type(self)
1186
        cls.priv_ip = dict()
1187

    
1188
        nicsA = self.client.get_server_details(
1189
            self.serverid['A'])['attachments']
1190
        nicsB = self.client.get_server_details(
1191
            self.serverid['B'])['attachments']
1192

    
1193
        if conn_exists:
1194
            for nic in nicsA:
1195
                if nic["network_id"] == self.networkid:
1196
                    cls.priv_ip["A"] = nic["ipv4"]
1197
            self.result_dict["Server A private IP"] = str(cls.priv_ip["A"])
1198

    
1199
            for nic in nicsB:
1200
                if nic["network_id"] == self.networkid:
1201
                    cls.priv_ip["B"] = nic["ipv4"]
1202
            self.result_dict["Server B private IP"] = str(cls.priv_ip["B"])
1203

    
1204
        self.assertTrue(conn_exists)
1205
        self.assertIsNot(cls.priv_ip["A"], None)
1206
        self.assertIsNot(cls.priv_ip["B"], None)
1207

    
1208
    def test_002a_reboot(self):
1209
        """Rebooting server A"""
1210

    
1211
        log.info("Rebooting server A")
1212

    
1213
        self.client.shutdown_server(self.serverid['A'])
1214

    
1215
        fail_tmout = time.time() + self.action_timeout
1216
        while True:
1217
            d = self.client.get_server_details(self.serverid['A'])
1218
            status = d['status']
1219
            if status == 'STOPPED':
1220
                break
1221
            elif time.time() > fail_tmout:
1222
                self.assertLess(time.time(), fail_tmout)
1223
            else:
1224
                time.sleep(self.query_interval)
1225

    
1226
        self.client.start_server(self.serverid['A'])
1227

    
1228
        while True:
1229
            d = self.client.get_server_details(self.serverid['A'])
1230
            status = d['status']
1231
            if status == 'ACTIVE':
1232
                active = True
1233
                break
1234
            elif time.time() > fail_tmout:
1235
                self.assertLess(time.time(), fail_tmout)
1236
            else:
1237
                time.sleep(self.query_interval)
1238

    
1239
        self.assertTrue(active)
1240

    
1241
    def test_002b_ping_server_A(self):
1242
        "Test if server A responds to IPv4 pings"
1243

    
1244
        log.info("Testing if server A responds to IPv4 pings ")
1245
        self.result_dict.clear()
1246

    
1247
        server = self.client.get_server_details(self.serverid['A'])
1248
        ip = self._get_ipv4(server)
1249

    
1250
        fail_tmout = time.time() + self.action_timeout
1251

    
1252
        s = False
1253

    
1254
        self.result_dict["Server A public IP"] = str(ip)
1255

    
1256
        while True:
1257

    
1258
            if self._ping_once(ip):
1259
                s = True
1260
                break
1261

    
1262
            elif time.time() > fail_tmout:
1263
                self.assertLess(time.time(), fail_tmout)
1264

    
1265
            else:
1266
                time.sleep(self.query_interval)
1267

    
1268
        self.assertTrue(s)
1269

    
1270
    def test_002c_reboot(self):
1271
        """Reboot server B"""
1272

    
1273
        log.info("Rebooting server B")
1274
        self.result_dict.clear()
1275

    
1276
        self.client.shutdown_server(self.serverid['B'])
1277

    
1278
        fail_tmout = time.time() + self.action_timeout
1279
        while True:
1280
            d = self.client.get_server_details(self.serverid['B'])
1281
            status = d['status']
1282
            if status == 'STOPPED':
1283
                break
1284
            elif time.time() > fail_tmout:
1285
                self.assertLess(time.time(), fail_tmout)
1286
            else:
1287
                time.sleep(self.query_interval)
1288

    
1289
        self.client.start_server(self.serverid['B'])
1290

    
1291
        while True:
1292
            d = self.client.get_server_details(self.serverid['B'])
1293
            status = d['status']
1294
            if status == 'ACTIVE':
1295
                active = True
1296
                break
1297
            elif time.time() > fail_tmout:
1298
                self.assertLess(time.time(), fail_tmout)
1299
            else:
1300
                time.sleep(self.query_interval)
1301

    
1302
        self.assertTrue(active)
1303

    
1304
    def test_002d_ping_server_B(self):
1305
        """Test if server B responds to IPv4 pings"""
1306

    
1307
        log.info("Testing if server B responds to IPv4 pings")
1308
        self.result_dict.clear()
1309

    
1310
        server = self.client.get_server_details(self.serverid['B'])
1311
        ip = self._get_ipv4(server)
1312

    
1313
        fail_tmout = time.time() + self.action_timeout
1314

    
1315
        s = False
1316

    
1317
        self.result_dict["Server B public IP"] = str(ip)
1318

    
1319
        while True:
1320
            if self._ping_once(ip):
1321
                s = True
1322
                break
1323

    
1324
            elif time.time() > fail_tmout:
1325
                self.assertLess(time.time(), fail_tmout)
1326

    
1327
            else:
1328
                time.sleep(self.query_interval)
1329

    
1330
        self.assertTrue(s)
1331

    
1332
    def test_003a_setup_interface_A(self):
1333
        """Setup eth1 for server A"""
1334

    
1335
        self._skipIf(self.is_windows, "only valid for Linux servers")
1336

    
1337
        log.info("Setting up interface eth1 for server A")
1338
        self.result_dict.clear()
1339

    
1340
        server = self.client.get_server_details(self.serverid['A'])
1341
        image = self.client.get_image_details(self.imageid)
1342
        os_value = image['metadata']['os']
1343

    
1344
        users = image["metadata"].get("users", None)
1345
        userlist = users.split()
1346

    
1347
        if "root" in userlist:
1348
            loginname = "root"
1349
        elif users is None:
1350
            loginname = self._connect_loginname(os_value)
1351
        else:
1352
            loginname = choice(userlist)
1353

    
1354
        hostip = self._get_ipv4(server)
1355
        myPass = self.password['A']
1356

    
1357
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1358
        command = "ifconfig eth1 %s && ifconfig eth1 | " \
1359
                  "grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'" \
1360
                  % self.priv_ip["A"]
1361
        output, status = _ssh_execute(
1362
            hostip, loginname, myPass, command)
1363

    
1364
        self.assertEquals(status, 0)
1365
        self.assertEquals(output[0].strip(), self.priv_ip["A"])
1366

    
1367
    def test_003b_setup_interface_B(self):
1368
        """Setup eth1 for server B"""
1369

    
1370
        self._skipIf(self.is_windows, "only valid for Linux servers")
1371

    
1372
        log.info("Setting up interface eth1 for server B")
1373

    
1374
        server = self.client.get_server_details(self.serverid['B'])
1375
        image = self.client.get_image_details(self.imageid)
1376
        os_value = image['metadata']['os']
1377

    
1378
        users = image["metadata"].get("users", None)
1379
        userlist = users.split()
1380

    
1381
        if "root" in userlist:
1382
            loginname = "root"
1383
        elif users is None:
1384
            loginname = self._connect_loginname(os_value)
1385
        else:
1386
            loginname = choice(userlist)
1387

    
1388
        hostip = self._get_ipv4(server)
1389
        myPass = self.password['B']
1390

    
1391
        log.info("SSH in server B as %s/%s" % (loginname, myPass))
1392
        command = "ifconfig eth1 %s && ifconfig eth1 | " \
1393
                  "grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'" \
1394
                  % self.priv_ip["B"]
1395
        output, status = _ssh_execute(
1396
            hostip, loginname, myPass, command)
1397

    
1398
        self.assertEquals(status, 0)
1399
        self.assertEquals(output[0].strip(), self.priv_ip["B"])
1400

    
1401
    def test_003c_test_connection_exists(self):
1402
        """Ping server B from server A to test if connection exists"""
1403

    
1404
        self._skipIf(self.is_windows, "only valid for Linux servers")
1405

    
1406
        log.info("Testing if server A is actually connected to server B")
1407

    
1408
        server = self.client.get_server_details(self.serverid['A'])
1409
        image = self.client.get_image_details(self.imageid)
1410
        os_value = image['metadata']['os']
1411
        hostip = self._get_ipv4(server)
1412

    
1413
        users = image["metadata"].get("users", None)
1414
        userlist = users.split()
1415

    
1416
        if "root" in userlist:
1417
            loginname = "root"
1418
        elif users is None:
1419
            loginname = self._connect_loginname(os_value)
1420
        else:
1421
            loginname = choice(userlist)
1422

    
1423
        myPass = self.password['A']
1424

    
1425
        cmd = "if ping -c 2 -w 3 %s >/dev/null; \
1426
               then echo \'True\'; fi;" % self.priv_ip["B"]
1427
        lines, status = _ssh_execute(
1428
            hostip, loginname, myPass, cmd)
1429

    
1430
        exists = False
1431

    
1432
        if 'True\n' in lines:
1433
            exists = True
1434

    
1435
        self.assertTrue(exists)
1436

    
1437
    def test_004_disconnect_from_network(self):
1438
        "Disconnecting server A and B from network"
1439

    
1440
        log.info("Disconnecting servers from private network")
1441

    
1442
        prev_state = self.client.get_network_details(self.networkid)
1443
        prev_nics = prev_state['attachments']
1444
        #prev_conn = len(prev_nics)
1445

    
1446
        nicsA = [x['id']
1447
                 for x in self.client.get_server_details(
1448
                     self.serverid['A'])['attachments']]
1449
        nicsB = [x['id']
1450
                 for x in self.client.get_server_details(
1451
                     self.serverid['B'])['attachments']]
1452

    
1453
        for nic in prev_nics:
1454
            if nic in nicsA:
1455
                self.client.disconnect_server(self.serverid['A'], nic)
1456
            if nic in nicsB:
1457
                self.client.disconnect_server(self.serverid['B'], nic)
1458

    
1459
        #Insist on deleting until action timeout
1460
        fail_tmout = time.time() + self.action_timeout
1461

    
1462
        while True:
1463
            netsA = [x['network_id']
1464
                     for x in self.client.get_server_details(
1465
                         self.serverid['A'])['attachments']]
1466
            netsB = [x['network_id']
1467
                     for x in self.client.get_server_details(
1468
                         self.serverid['B'])['attachments']]
1469

    
1470
            #connected = (self.client.get_network_details(self.networkid))
1471
            #connections = connected['attachments']
1472
            if (self.networkid not in netsA) and (self.networkid not in netsB):
1473
                conn_exists = False
1474
                break
1475
            elif time.time() > fail_tmout:
1476
                self.assertLess(time.time(), fail_tmout)
1477
            else:
1478
                time.sleep(self.query_interval)
1479

    
1480
        self.assertFalse(conn_exists)
1481

    
1482
    def test_005_destroy_network(self):
1483
        """Test submit delete network request"""
1484

    
1485
        log.info("Submitting delete network request")
1486

    
1487
        self.client.delete_network(self.networkid)
1488

    
1489
        fail_tmout = time.time() + self.action_timeout
1490

    
1491
        while True:
1492

    
1493
            curr_net = []
1494
            networks = self.client.list_networks()
1495

    
1496
            for net in networks:
1497
                curr_net.append(net['id'])
1498

    
1499
            if self.networkid not in curr_net:
1500
                self.assertTrue(self.networkid not in curr_net)
1501
                break
1502

    
1503
            elif time.time() > fail_tmout:
1504
                self.assertLess(time.time(), fail_tmout)
1505

    
1506
            else:
1507
                time.sleep(self.query_interval)
1508

    
1509
    def test_006_cleanup_servers(self):
1510
        """Cleanup servers created for this test"""
1511

    
1512
        log.info("Delete servers created for this test")
1513

    
1514
        self.compute.delete_server(self.serverid['A'])
1515
        self.compute.delete_server(self.serverid['B'])
1516

    
1517
        fail_tmout = time.time() + self.action_timeout
1518

    
1519
        #Ensure server gets deleted
1520
        status = dict()
1521

    
1522
        while True:
1523
            details = self.compute.get_server_details(self.serverid['A'])
1524
            status['A'] = details['status']
1525
            details = self.compute.get_server_details(self.serverid['B'])
1526
            status['B'] = details['status']
1527
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1528
                deleted = True
1529
                break
1530
            elif time.time() > fail_tmout:
1531
                self.assertLess(time.time(), fail_tmout)
1532
            else:
1533
                time.sleep(self.query_interval)
1534

    
1535
        self.assertTrue(deleted)
1536

    
1537

    
1538
class TestRunnerProcess(Process):
1539
    """A distinct process used to execute part of the tests in parallel"""
1540
    def __init__(self, **kw):
1541
        Process.__init__(self, **kw)
1542
        kwargs = kw["kwargs"]
1543
        self.testq = kwargs["testq"]
1544
        self.worker_folder = kwargs["worker_folder"]
1545

    
1546
    def run(self):
1547
        # Make sure this test runner process dies with the parent
1548
        # and is not left behind.
1549
        #
1550
        # WARNING: This uses the prctl(2) call and is
1551
        # Linux-specific.
1552

    
1553
        prctl.set_pdeathsig(signal.SIGHUP)
1554

    
1555
        multi = logging.getLogger("multiprocess")
1556

    
1557
        while True:
1558
            multi.debug("I am process %d, GETting from queue is %s" %
1559
                        (os.getpid(), self.testq))
1560
            msg = self.testq.get()
1561

    
1562
            multi.debug("Dequeued msg: %s" % msg)
1563

    
1564
            if msg == "TEST_RUNNER_TERMINATE":
1565
                raise SystemExit
1566

    
1567
            elif issubclass(msg, unittest.TestCase):
1568
                # Assemble a TestSuite, and run it
1569

    
1570
                log_file = os.path.join(self.worker_folder, 'details_' +
1571
                                        (msg.__name__) + "_" +
1572
                                        TEST_RUN_ID + '.log')
1573

    
1574
                fail_file = os.path.join(self.worker_folder, 'failed_' +
1575
                                         (msg.__name__) + "_" +
1576
                                         TEST_RUN_ID + '.log')
1577
                error_file = os.path.join(self.worker_folder, 'error_' +
1578
                                          (msg.__name__) + "_" +
1579
                                          TEST_RUN_ID + '.log')
1580

    
1581
                f = open(log_file, 'w')
1582
                fail = open(fail_file, 'w')
1583
                error = open(error_file, 'w')
1584

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

    
1587
                runner = unittest.TextTestRunner(
1588
                    f, verbosity=2, failfast=True,
1589
                    resultclass=BurninTestResult)
1590
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
1591
                result = runner.run(suite)
1592

    
1593
                for res in result.errors:
1594
                    log.error("snf-burnin encountered an error in "
1595
                              "testcase: %s" % msg)
1596
                    log.error("See log for details")
1597
                    error.write(str(res[0]) + '\n')
1598
                    error.write(str(res[0].shortDescription()) + '\n')
1599
                    error.write('\n')
1600

    
1601
                for res in result.failures:
1602
                    log.error("snf-burnin failed in testcase: %s" % msg)
1603
                    log.error("See log for details")
1604
                    fail.write(str(res[0]) + '\n')
1605
                    fail.write(str(res[0].shortDescription()) + '\n')
1606
                    fail.write('\n')
1607
                    if not NOFAILFAST:
1608
                        sys.exit()
1609

    
1610
                if (len(result.failures) == 0) and (len(result.errors) == 0):
1611
                    log.debug("Passed testcase: %s" % msg)
1612

    
1613
                f.close()
1614
                fail.close()
1615
                error.close()
1616

    
1617
            else:
1618
                raise Exception("Cannot handle msg: %s" % msg)
1619

    
1620

    
1621
def _run_cases_in_series(cases, image_folder):
1622
    """Run instances of TestCase in series"""
1623

    
1624
    for case in cases:
1625

    
1626
        test = case.__name__
1627

    
1628
        log.info(yellow + '* Starting testcase: %s' % test + normal)
1629
        log_file = os.path.join(image_folder, 'details_' +
1630
                                (case.__name__) + "_" +
1631
                                TEST_RUN_ID + '.log')
1632
        fail_file = os.path.join(image_folder, 'failed_' +
1633
                                 (case.__name__) + "_" +
1634
                                 TEST_RUN_ID + '.log')
1635
        error_file = os.path.join(image_folder, 'error_' +
1636
                                  (case.__name__) + "_" +
1637
                                  TEST_RUN_ID + '.log')
1638

    
1639
        f = open(log_file, "w")
1640
        fail = open(fail_file, "w")
1641
        error = open(error_file, "w")
1642

    
1643
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1644
        runner = unittest.TextTestRunner(
1645
            f, verbosity=2, failfast=True,
1646
            resultclass=BurninTestResult)
1647
        result = runner.run(suite)
1648

    
1649
        for res in result.errors:
1650
            log.error("snf-burnin encountered an error in "
1651
                      "testcase: %s" % test)
1652
            log.error("See log for details")
1653
            error.write(str(res[0]) + '\n')
1654
            error.write(str(res[0].shortDescription()) + '\n')
1655
            error.write('\n')
1656

    
1657
        for res in result.failures:
1658
            log.error("snf-burnin failed in testcase: %s" % test)
1659
            log.error("See log for details")
1660
            fail.write(str(res[0]) + '\n')
1661
            fail.write(str(res[0].shortDescription()) + '\n')
1662
            fail.write('\n')
1663
            if not NOFAILFAST:
1664
                sys.exit()
1665

    
1666
        if (len(result.failures) == 0) and (len(result.errors) == 0):
1667
            log.debug("Passed testcase: %s" % test)
1668

    
1669

    
1670
def _run_cases_in_parallel(cases, fanout, image_folder):
1671
    """Run instances of TestCase in parallel, in a number of distinct processes
1672

1673
    The cases iterable specifies the TestCases to be executed in parallel,
1674
    by test runners running in distinct processes.
1675
    The fanout parameter specifies the number of processes to spawn,
1676
    and defaults to 1.
1677
    The runner argument specifies the test runner class to use inside each
1678
    runner process.
1679

1680
    """
1681

    
1682
    multi = logging.getLogger("multiprocess")
1683
    handler = logging.StreamHandler()
1684
    multi.addHandler(handler)
1685

    
1686
    if VERBOSE:
1687
        multi.setLevel(logging.DEBUG)
1688
    else:
1689
        multi.setLevel(logging.INFO)
1690

    
1691
    testq = []
1692
    worker_folder = []
1693
    runners = []
1694

    
1695
    for i in xrange(0, fanout):
1696
        testq.append(Queue())
1697
        worker_folder.append(os.path.join(image_folder, 'process'+str(i)))
1698
        os.mkdir(worker_folder[i])
1699

    
1700
    for i in xrange(0, fanout):
1701
        kwargs = dict(testq=testq[i], worker_folder=worker_folder[i])
1702
        runners.append(TestRunnerProcess(kwargs=kwargs))
1703

    
1704
    multi.debug("Spawning %d test runner processes" % len(runners))
1705

    
1706
    for p in runners:
1707
        p.start()
1708

    
1709
    # Enqueue test cases
1710
    for i in xrange(0, fanout):
1711
        map(testq[i].put, cases)
1712
        testq[i].put("TEST_RUNNER_TERMINATE")
1713

    
1714
    multi.debug("Spawned %d test runners, PIDs are %s" %
1715
                (len(runners), [p.pid for p in runners]))
1716

    
1717
    multi.debug("Joining %d processes" % len(runners))
1718

    
1719
    for p in runners:
1720
        p.join()
1721

    
1722
    multi.debug("Done joining %d processes" % len(runners))
1723

    
1724

    
1725
def _images_test_case(**kwargs):
1726
    """Construct a new unit test case class from ImagesTestCase"""
1727
    name = "ImagesTestCase_%s" % kwargs["imageid"]
1728
    cls = type(name, (ImagesTestCase,), kwargs)
1729

    
1730
    #Patch extra parameters into test names by manipulating method docstrings
1731
    for (mname, m) in \
1732
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1733
        if hasattr(m, __doc__):
1734
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1735

    
1736
    # Make sure the class can be pickled, by listing it among
1737
    # the attributes of __main__. A PicklingError is raised otherwise.
1738
    thismodule = sys.modules[__name__]
1739
    setattr(thismodule, name, cls)
1740
    return cls
1741

    
1742

    
1743
def _spawn_server_test_case(**kwargs):
1744
    """Construct a new unit test case class from SpawnServerTestCase"""
1745

    
1746
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1747
    cls = type(name, (SpawnServerTestCase,), kwargs)
1748

    
1749
    # Patch extra parameters into test names by manipulating method docstrings
1750
    for (mname, m) in \
1751
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1752
        if hasattr(m, __doc__):
1753
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1754

    
1755
    # Make sure the class can be pickled, by listing it among
1756
    # the attributes of __main__. A PicklingError is raised otherwise.
1757

    
1758
    thismodule = sys.modules[__name__]
1759
    setattr(thismodule, name, cls)
1760
    return cls
1761

    
1762

    
1763
def _spawn_network_test_case(**kwargs):
1764
    """Construct a new unit test case class from NetworkTestCase"""
1765

    
1766
    name = "NetworkTestCase" + TEST_RUN_ID
1767
    cls = type(name, (NetworkTestCase,), kwargs)
1768

    
1769
    # Make sure the class can be pickled, by listing it among
1770
    # the attributes of __main__. A PicklingError is raised otherwise.
1771

    
1772
    thismodule = sys.modules[__name__]
1773
    setattr(thismodule, name, cls)
1774
    return cls
1775

    
1776

    
1777
# --------------------------------------------------------------------
1778
# Clean up servers/networks functions
1779
def cleanup_servers(timeout, query_interval, delete_stale=False):
1780

    
1781
    c = ComputeClient(API, TOKEN)
1782

    
1783
    servers = c.list_servers()
1784
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1785

    
1786
    if len(stale) == 0:
1787
        return
1788

    
1789
    # Show staled servers
1790
    print >>sys.stderr, yellow + \
1791
        "Found these stale servers from previous runs:" + \
1792
        normal
1793
    print >>sys.stderr, "    " + \
1794
        "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1795

    
1796
    # Delete staled servers
1797
    if delete_stale:
1798
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1799
        fail_tmout = time.time() + timeout
1800
        for s in stale:
1801
            c.delete_server(s["id"])
1802
        # Wait for all servers to be deleted
1803
        while True:
1804
            servers = c.list_servers()
1805
            stale = [s for s in servers
1806
                     if s["name"].startswith(SNF_TEST_PREFIX)]
1807
            if len(stale) == 0:
1808
                print >> sys.stderr, green + "    ...done" + normal
1809
                break
1810
            elif time.time() > fail_tmout:
1811
                print >> sys.stderr, red + \
1812
                    "Not all stale servers deleted. Action timed out." + \
1813
                    normal
1814
                sys.exit(1)
1815
            else:
1816
                time.sleep(query_interval)
1817
    else:
1818
        print >> sys.stderr, "Use --delete-stale to delete them."
1819

    
1820

    
1821
def cleanup_networks(action_timeout, query_interval, delete_stale=False):
1822

    
1823
    c = CycladesClient(API, TOKEN)
1824

    
1825
    networks = c.list_networks()
1826
    stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1827

    
1828
    if len(stale) == 0:
1829
        return
1830

    
1831
    # Show staled networks
1832
    print >> sys.stderr, yellow + \
1833
        "Found these stale networks from previous runs:" + \
1834
        normal
1835
    print "    " + \
1836
        "\n    ".join(["%s: %s" % (str(n["id"]), n["name"]) for n in stale])
1837

    
1838
    # Delete staled networks
1839
    if delete_stale:
1840
        print >> sys.stderr, "Deleting %d stale networks:" % len(stale)
1841
        fail_tmout = time.time() + action_timeout
1842
        for n in stale:
1843
            c.delete_network(n["id"])
1844
        # Wait for all networks to be deleted
1845
        while True:
1846
            networks = c.list_networks()
1847
            stale = [n for n in networks
1848
                     if n["name"].startswith(SNF_TEST_PREFIX)]
1849
            if len(stale) == 0:
1850
                print >> sys.stderr, green + "    ...done" + normal
1851
                break
1852
            elif time.time() > fail_tmout:
1853
                print >> sys.stderr, red + \
1854
                    "Not all stale networks deleted. Action timed out." + \
1855
                    normal
1856
                sys.exit(1)
1857
            else:
1858
                time.sleep(query_interval)
1859
    else:
1860
        print >> sys.stderr, "Use --delete-stale to delete them."
1861

    
1862

    
1863
# --------------------------------------------------------------------
1864
# Parse arguments functions
1865
def parse_comma(option, opt, value, parser):
1866
    tests = set(['all', 'auth', 'images', 'flavors',
1867
                 'pithos', 'servers', 'server_spawn',
1868
                 'network_spawn'])
1869
    parse_input = value.split(',')
1870

    
1871
    if not (set(parse_input)).issubset(tests):
1872
        raise OptionValueError("The selected set of tests is invalid")
1873

    
1874
    setattr(parser.values, option.dest, value.split(','))
1875

    
1876

    
1877
def parse_arguments(args):
1878

    
1879
    kw = {}
1880
    kw["usage"] = "%prog [options]"
1881
    kw["description"] = \
1882
        "%prog runs a number of test scenarios on a " \
1883
        "Synnefo deployment."
1884

    
1885
    parser = OptionParser(**kw)
1886
    parser.disable_interspersed_args()
1887

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

    
2001
    (opts, args) = parser.parse_args(args)
2002

    
2003
    # -----------------------
2004
    # Verify arguments
2005

    
2006
    # `delete_stale' implies `show_stale'
2007
    if opts.delete_stale:
2008
        opts.show_stale = True
2009

    
2010
    # `token' is mandatory
2011
    _mandatory_argument(opts.token, "--token")
2012
    # `api' is mandatory
2013
    _mandatory_argument(opts.api, "--api")
2014

    
2015
    if not opts.show_stale:
2016
        # `image-id' is mandatory
2017
        _mandatory_argument(opts.force_imageid, "--image_id")
2018
        if opts.force_imageid != 'all':
2019
            try:
2020
                opts.force_imageid = str(opts.force_imageid)
2021
            except ValueError:
2022
                print >>sys.stderr, red + \
2023
                    "Invalid value specified for" + \
2024
                    "--image-id. Use a valid id, or `all'." + \
2025
                    normal
2026
                sys.exit(1)
2027
        # `pithos' is mandatory
2028
        _mandatory_argument(opts.pithos, "--pithos")
2029
        # `astakos' is mandatory
2030
        _mandatory_argument(opts.astakos, "--astakos")
2031
        # `plankton' is mandatory
2032
        _mandatory_argument(opts.plankton, "--plankton")
2033

    
2034
    return (opts, args)
2035

    
2036

    
2037
def _mandatory_argument(Arg, Str):
2038
    if not Arg:
2039
        print >>sys.stderr, red + \
2040
            "The " + Str + " argument is mandatory.\n" + \
2041
            normal
2042
        sys.exit(1)
2043

    
2044

    
2045
# --------------------------------------------------------------------
2046
# Burnin main function
2047
def main():
2048
    """Assemble test cases into a test suite, and run it
2049

2050
    IMPORTANT: Tests have dependencies and have to be run in the specified
2051
    order inside a single test case. They communicate through attributes of the
2052
    corresponding TestCase class (shared fixtures). Distinct subclasses of
2053
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
2054
    test runner processes.
2055

2056
    """
2057

    
2058
    # Parse arguments using `optparse'
2059
    (opts, args) = parse_arguments(sys.argv[1:])
2060

    
2061
    # Some global variables
2062
    global API, TOKEN, PLANKTON, PLANKTON_USER
2063
    global PITHOS, ASTAKOS, NO_IPV6, VERBOSE, NOFAILFAST
2064
    API = opts.api
2065
    TOKEN = opts.token
2066
    PLANKTON = opts.plankton
2067
    PLANKTON_USER = opts.plankton_user
2068
    PITHOS = opts.pithos
2069
    ASTAKOS = opts.astakos
2070
    NO_IPV6 = opts.no_ipv6
2071
    VERBOSE = opts.verbose
2072
    NOFAILFAST = opts.nofailfast
2073

    
2074
    # If `show_stale', cleanup stale servers
2075
    # from previous runs and exit
2076
    if opts.show_stale:
2077
        # We must clean the servers first
2078
        cleanup_servers(opts.action_timeout, opts.query_interval,
2079
                        delete_stale=opts.delete_stale)
2080
        cleanup_networks(opts.action_timeout, opts.query_interval,
2081
                         delete_stale=opts.delete_stale)
2082
        return 0
2083

    
2084
    # Initialize a kamaki instance, get flavors, images
2085
    c = ComputeClient(API, TOKEN)
2086
    DIMAGES = c.list_images(detail=True)
2087
    DFLAVORS = c.list_flavors(detail=True)
2088

    
2089
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
2090
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
2091
    #unittest.main(verbosity=2, catchbreak=True)
2092

    
2093
    # Get a list of images we are going to test
2094
    if opts.force_imageid == 'all':
2095
        test_images = DIMAGES
2096
    else:
2097
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
2098

    
2099
    # Create output (logging) folder
2100
    if not os.path.exists(opts.log_folder):
2101
        os.mkdir(opts.log_folder)
2102
    test_folder = os.path.join(opts.log_folder, TEST_RUN_ID)
2103
    os.mkdir(test_folder)
2104

    
2105
    for image in test_images:
2106
        imageid = str(image["id"])
2107
        imagename = image["name"]
2108
        # Choose a flavor (given from user or random)
2109
        if opts.force_flavorid:
2110
            flavorid = opts.force_flavorid
2111
        else:
2112
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
2113
        # Personality dictionary for file injection test
2114
        if opts.personality_path is not None:
2115
            f = open(opts.personality_path)
2116
            content = b64encode(f.read())
2117
            personality = []
2118
            st = os.stat(opts.personality_path)
2119
            personality.append({
2120
                'path': '/root/test_inj_file',
2121
                'owner': 'root',
2122
                'group': 'root',
2123
                'mode': 0x7777 & st.st_mode,
2124
                'contents': content})
2125
        else:
2126
            personality = None
2127
        # Give a name to our test servers
2128
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
2129
        is_windows = imagename.lower().find("windows") >= 0
2130

    
2131
        # Create Server TestCases
2132
        ServerTestCase = _spawn_server_test_case(
2133
            imageid=imageid,
2134
            flavorid=flavorid,
2135
            imagename=imagename,
2136
            personality=personality,
2137
            servername=servername,
2138
            is_windows=is_windows,
2139
            action_timeout=opts.action_timeout,
2140
            build_warning=opts.build_warning,
2141
            build_fail=opts.build_fail,
2142
            query_interval=opts.query_interval)
2143
        # Create Network TestCases
2144
        NetworkTestCase = _spawn_network_test_case(
2145
            action_timeout=opts.action_timeout,
2146
            imageid=imageid,
2147
            flavorid=flavorid,
2148
            imagename=imagename,
2149
            query_interval=opts.query_interval)
2150
        # Create Images TestCase
2151
        CImagesTestCase = _images_test_case(
2152
            action_timeout=opts.action_timeout,
2153
            imageid=imageid,
2154
            flavorid=flavorid,
2155
            imagename=imagename,
2156
            query_interval=opts.query_interval)
2157

    
2158
        # Choose the tests we are going to run
2159
        test_dict = {'auth': UnauthorizedTestCase,
2160
                     'images': CImagesTestCase,
2161
                     'flavors': FlavorsTestCase,
2162
                     'servers': ServersTestCase,
2163
                     'pithos': PithosTestCase,
2164
                     'server_spawn': ServerTestCase,
2165
                     'network_spawn': NetworkTestCase}
2166
        seq_cases = []
2167
        if 'all' in opts.tests:
2168
            seq_cases = [UnauthorizedTestCase, CImagesTestCase,
2169
                         FlavorsTestCase, ServersTestCase,
2170
                         PithosTestCase, ServerTestCase,
2171
                         NetworkTestCase]
2172
        else:
2173
            for test in opts.tests:
2174
                seq_cases.append(test_dict[test])
2175

    
2176
        # Folder for each image
2177
        image_folder = os.path.join(test_folder, imageid)
2178
        os.mkdir(image_folder)
2179

    
2180
        # Run each test
2181
        if opts.fanout > 1:
2182
            _run_cases_in_parallel(seq_cases, opts.fanout, image_folder)
2183
        else:
2184
            _run_cases_in_series(seq_cases, image_folder)
2185

    
2186

    
2187
# --------------------------------------------------------------------
2188
# Call main
2189
if __name__ == "__main__":
2190
    sys.exit(main())