Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (76.6 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
"""Perform integration testing on a running Synnefo deployment"""
37

    
38
#import __main__
39
import datetime
40
import inspect
41
import logging
42
import os
43
import os.path
44
import paramiko
45
import prctl
46
import subprocess
47
import signal
48
import socket
49
import sys
50
import time
51
import tempfile
52
from base64 import b64encode
53
from IPy import IP
54
from multiprocessing import Process, Queue
55
from random import choice, randint
56
from optparse import OptionParser, OptionValueError
57

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

    
64
from vncauthproxy.d3des import generate_response as d3des_generate_response
65

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

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

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

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

    
97

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

    
117

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

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

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

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

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

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

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

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

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

    
168

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

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

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

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

    
198

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

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

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

    
217

    
218
# --------------------------------------------------------------------
219
# This class gest replicated into Images TestCases dynamically
220
class ImagesTestCase(unittest.TestCase):
221
    """Test image lists for consistency"""
222
    @classmethod
223
    def setUpClass(cls):
224
        """Initialize kamaki, get (detailed) list of images"""
225
        log.info("Getting simple and detailed list of images")
226
        cls.client = ComputeClient(API, TOKEN)
227
        cls.plankton = ImageClient(PLANKTON, TOKEN)
228
        cls.images = cls.plankton.list_public()
229
        cls.dimages = cls.plankton.list_public(detail=True)
230
        cls.result_dict = dict()
231
        # Create temp directory and store it inside our class
232
        # XXX: In my machine /tmp has not enough space
233
        #      so use current directory to be sure.
234
        cls.temp_dir = tempfile.mkdtemp(dir=os.getcwd())
235
        cls.temp_image_name = \
236
            SNF_TEST_PREFIX + cls.imageid + ".diskdump"
237

    
238
    def test_001_list_images(self):
239
        """Test image list actually returns images"""
240
        self.assertGreater(len(self.images), 0)
241

    
242
    def test_002_list_images_detailed(self):
243
        """Test detailed image list is the same length as list"""
244
        self.assertEqual(len(self.dimages), len(self.images))
245

    
246
    def test_003_same_image_names(self):
247
        """Test detailed and simple image list contain same names"""
248
        names = sorted(map(lambda x: x["name"], self.images))
249
        dnames = sorted(map(lambda x: x["name"], self.dimages))
250
        self.assertEqual(names, dnames)
251

    
252
    def test_004_unique_image_names(self):
253
        """Test system images have unique names"""
254
        sys_images = filter(lambda x: x['owner'] == PLANKTON_USER,
255
                            self.dimages)
256
        names = sorted(map(lambda x: x["name"], sys_images))
257
        self.assertEqual(sorted(list(set(names))), names)
258

    
259
    def test_005_image_metadata(self):
260
        """Test every image has specific metadata defined"""
261
        keys = frozenset(["osfamily", "root_partition"])
262
        details = self.client.list_images(detail=True)
263
        for i in details:
264
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
265

    
266
    def test_006_download_image(self):
267
        """Download image from pithos+"""
268
        # Get image location
269
        image = filter(
270
            lambda x: x['id'] == self.imageid, self.dimages)[0]
271
        image_location = \
272
            image['location'].replace("://", " ").replace("/", " ").split()
273
        log.info("Download image, with owner %s\n\tcontainer %s, and name %s"
274
                 % (image_location[1], image_location[2], image_location[3]))
275
        pithos_client = PithosClient(PITHOS, TOKEN, image_location[1])
276
        pithos_client.container = image_location[2]
277
        temp_file = os.path.join(self.temp_dir, self.temp_image_name)
278
        with open(temp_file, "wb+") as f:
279
            pithos_client.download_object(image_location[3], f)
280

    
281
    def test_007_upload_image(self):
282
        """Upload and register image"""
283
        temp_file = os.path.join(self.temp_dir, self.temp_image_name)
284
        log.info("Upload image to pithos+")
285
        # Create container `images'
286
        pithos_client = PithosClient(PITHOS, TOKEN, PITHOS_USER)
287
        pithos_client.container = "images"
288
        pithos_client.container_put()
289
        with open(temp_file, "rb+") as f:
290
            pithos_client.upload_object(self.temp_image_name, f)
291
        log.info("Register image to plankton")
292
        location = "pithos://" + PITHOS_USER + \
293
            "/images/" + self.temp_image_name
294
        params = {'is_public': True}
295
        properties = {'OSFAMILY': "linux", 'ROOT_PARTITION': 1}
296
        self.plankton.register(self.temp_image_name, location,
297
                               params, properties)
298

    
299

    
300
# --------------------------------------------------------------------
301
# FlavorsTestCase class
302
class FlavorsTestCase(unittest.TestCase):
303
    """Test flavor lists for consistency"""
304
    @classmethod
305
    def setUpClass(cls):
306
        """Initialize kamaki, get (detailed) list of flavors"""
307
        log.info("Getting simple and detailed list of flavors")
308
        cls.client = ComputeClient(API, TOKEN)
309
        cls.flavors = cls.client.list_flavors()
310
        cls.dflavors = cls.client.list_flavors(detail=True)
311
        cls.result_dict = dict()
312

    
313
    def test_001_list_flavors(self):
314
        """Test flavor list actually returns flavors"""
315
        self.assertGreater(len(self.flavors), 0)
316

    
317
    def test_002_list_flavors_detailed(self):
318
        """Test detailed flavor list is the same length as list"""
319
        self.assertEquals(len(self.dflavors), len(self.flavors))
320

    
321
    def test_003_same_flavor_names(self):
322
        """Test detailed and simple flavor list contain same names"""
323
        names = sorted(map(lambda x: x["name"], self.flavors))
324
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
325
        self.assertEqual(names, dnames)
326

    
327
    def test_004_unique_flavor_names(self):
328
        """Test flavors have unique names"""
329
        names = sorted(map(lambda x: x["name"], self.flavors))
330
        self.assertEqual(sorted(list(set(names))), names)
331

    
332
    def test_005_well_formed_flavor_names(self):
333
        """Test flavors have names of the form CxxRyyDzz
334
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
335
        """
336
        for f in self.dflavors:
337
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
338
                             f["name"],
339
                             "Flavor %s does not match its specs." % f["name"])
340

    
341

    
342
# --------------------------------------------------------------------
343
# ServersTestCase class
344
class ServersTestCase(unittest.TestCase):
345
    """Test server lists for consistency"""
346
    @classmethod
347
    def setUpClass(cls):
348
        """Initialize kamaki, get (detailed) list of servers"""
349
        log.info("Getting simple and detailed list of servers")
350

    
351
        cls.client = ComputeClient(API, TOKEN)
352
        cls.servers = cls.client.list_servers()
353
        cls.dservers = cls.client.list_servers(detail=True)
354
        cls.result_dict = dict()
355

    
356
    # def test_001_list_servers(self):
357
    #     """Test server list actually returns servers"""
358
    #     self.assertGreater(len(self.servers), 0)
359

    
360
    def test_002_list_servers_detailed(self):
361
        """Test detailed server list is the same length as list"""
362
        self.assertEqual(len(self.dservers), len(self.servers))
363

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

    
370

    
371
# --------------------------------------------------------------------
372
# Pithos Test Cases
373
class PithosTestCase(unittest.TestCase):
374
    """Test pithos functionality"""
375
    @classmethod
376
    def setUpClass(cls):
377
        """Initialize kamaki, get list of containers"""
378
        log.info("Getting list of containers")
379

    
380
        cls.client = PithosClient(PITHOS, TOKEN, PITHOS_USER)
381
        cls.containers = cls.client.list_containers()
382
        cls.result_dict = dict()
383

    
384
    def test_001_list_containers(self):
385
        """Test container list actually returns containers"""
386
        self.assertGreater(len(self.containers), 0)
387

    
388
    def test_002_unique_containers(self):
389
        """Test if containers have unique names"""
390
        names = [n['name'] for n in self.containers]
391
        names = sorted(names)
392
        self.assertEqual(sorted(list(set(names))), names)
393

    
394
    def test_003_create_container(self):
395
        """Test create a container"""
396
        rand_num = randint(1000, 9999)
397
        rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
398
        names = [n['name'] for n in self.containers]
399
        while rand_name in names:
400
            rand_num = randint(1000, 9999)
401
            rand_name = "%s%s" % (SNF_TEST_PREFIX, rand_num)
402
        # Create container
403
        self.client.container = rand_name
404
        self.client.container_put()
405
        # Get list of containers
406
        new_containers = self.client.list_containers()
407
        new_container_names = [n['name'] for n in new_containers]
408
        self.assertIn(rand_name, new_container_names)
409

    
410
    def test_004_upload(self):
411
        """Test uploading something to pithos+"""
412
        # Create a tmp file
413
        with tempfile.TemporaryFile() as f:
414
            f.write("This is a temp file")
415
            f.seek(0, 0)
416
            # Where to save file
417
            self.client.upload_object("test.txt", f)
418

    
419
    def test_005_download(self):
420
        """Test download something from pithos+"""
421
        # Create tmp directory to save file
422
        tmp_dir = tempfile.mkdtemp()
423
        tmp_file = os.path.join(tmp_dir, "test.txt")
424
        with open(tmp_file, "wb+") as f:
425
            self.client.download_object("test.txt", f)
426
            # Read file
427
            f.seek(0, 0)
428
            content = f.read()
429
        # Remove files
430
        os.unlink(tmp_file)
431
        os.rmdir(tmp_dir)
432
        # Compare results
433
        self.assertEqual(content, "This is a temp file")
434

    
435
    def test_006_remove(self):
436
        """Test removing files and containers"""
437
        cont_name = self.client.container
438
        self.client.del_object("test.txt")
439
        self.client.purge_container()
440
        # List containers
441
        containers = self.client.list_containers()
442
        cont_names = [n['name'] for n in containers]
443
        self.assertNotIn(cont_name, cont_names)
444

    
445

    
446
# --------------------------------------------------------------------
447
# This class gets replicated into actual TestCases dynamically
448
class SpawnServerTestCase(unittest.TestCase):
449
    """Test scenario for server of the specified image"""
450
    @classmethod
451
    def setUpClass(cls):
452
        """Initialize a kamaki instance"""
453
        log.info("Spawning server for image `%s'" % cls.imagename)
454
        cls.client = ComputeClient(API, TOKEN)
455
        cls.cyclades = CycladesClient(API, TOKEN)
456
        cls.result_dict = dict()
457

    
458
    def _get_ipv4(self, server):
459
        """Get the public IPv4 of a server from the detailed server info"""
460

    
461
        nics = server["attachments"]["values"]
462

    
463
        for nic in nics:
464
            net_id = nic["network_id"]
465
            if self.cyclades.get_network_details(net_id)["public"]:
466
                public_addrs = nic["ipv4"]
467

    
468
        self.assertTrue(public_addrs is not None)
469

    
470
        return public_addrs
471

    
472
    def _get_ipv6(self, server):
473
        """Get the public IPv6 of a server from the detailed server info"""
474

    
475
        nics = server["attachments"]["values"]
476

    
477
        for nic in nics:
478
            net_id = nic["network_id"]
479
            if self.cyclades.get_network_details(net_id)["public"]:
480
                public_addrs = nic["ipv6"]
481

    
482
        self.assertTrue(public_addrs is not None)
483

    
484
        return public_addrs
485

    
486
    def _connect_loginname(self, os_value):
487
        """Return the login name for connections based on the server OS"""
488
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
489
            return "user"
490
        elif os_value in ("windows", "windows_alpha1"):
491
            return "Administrator"
492
        else:
493
            return "root"
494

    
495
    def _verify_server_status(self, current_status, new_status):
496
        """Verify a server has switched to a specified status"""
497
        server = self.client.get_server_details(self.serverid)
498
        if server["status"] not in (current_status, new_status):
499
            return None  # Do not raise exception, return so the test fails
500
        self.assertEquals(server["status"], new_status)
501

    
502
    def _get_connected_tcp_socket(self, family, host, port):
503
        """Get a connected socket from the specified family to host:port"""
504
        sock = None
505
        for res in \
506
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
507
                               socket.AI_PASSIVE):
508
            af, socktype, proto, canonname, sa = res
509
            try:
510
                sock = socket.socket(af, socktype, proto)
511
            except socket.error:
512
                sock = None
513
                continue
514
            try:
515
                sock.connect(sa)
516
            except socket.error:
517
                sock.close()
518
                sock = None
519
                continue
520
        self.assertIsNotNone(sock)
521
        return sock
522

    
523
    def _ping_once(self, ipv6, ip):
524
        """Test server responds to a single IPv4 or IPv6 ping"""
525
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
526
        ping = subprocess.Popen(cmd, shell=True,
527
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
528
        (stdout, stderr) = ping.communicate()
529
        ret = ping.wait()
530
        self.assertEquals(ret, 0)
531

    
532
    def _get_hostname_over_ssh(self, hostip, username, password):
533
        lines, status = _ssh_execute(
534
            hostip, username, password, "hostname")
535
        self.assertEqual(len(lines), 1)
536
        return lines[0]
537

    
538
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
539
                                   opmsg, callable, *args, **kwargs):
540
        if warn_timeout == fail_timeout:
541
            warn_timeout = fail_timeout + 1
542
        warn_tmout = time.time() + warn_timeout
543
        fail_tmout = time.time() + fail_timeout
544
        while True:
545
            self.assertLess(time.time(), fail_tmout,
546
                            "operation `%s' timed out" % opmsg)
547
            if time.time() > warn_tmout:
548
                log.warning("Server %d: `%s' operation `%s' not done yet",
549
                            self.serverid, self.servername, opmsg)
550
            try:
551
                log.info("%s... " % opmsg)
552
                return callable(*args, **kwargs)
553
            except AssertionError:
554
                pass
555
            time.sleep(self.query_interval)
556

    
557
    def _insist_on_tcp_connection(self, family, host, port):
558
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
559
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
560
        msg = "connect over %s to %s:%s" % \
561
              (familystr.get(family, "Unknown"), host, port)
562
        sock = self._try_until_timeout_expires(
563
            self.action_timeout, self.action_timeout,
564
            msg, self._get_connected_tcp_socket,
565
            family, host, port)
566
        return sock
567

    
568
    def _insist_on_status_transition(self, current_status, new_status,
569
                                     fail_timeout, warn_timeout=None):
570
        msg = "Server %d: `%s', waiting for %s -> %s" % \
571
              (self.serverid, self.servername, current_status, new_status)
572
        if warn_timeout is None:
573
            warn_timeout = fail_timeout
574
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
575
                                        msg, self._verify_server_status,
576
                                        current_status, new_status)
577
        # Ensure the status is actually the expected one
578
        server = self.client.get_server_details(self.serverid)
579
        self.assertEquals(server["status"], new_status)
580

    
581
    def _insist_on_ssh_hostname(self, hostip, username, password):
582
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
583
        hostname = self._try_until_timeout_expires(
584
            self.action_timeout, self.action_timeout,
585
            msg, self._get_hostname_over_ssh,
586
            hostip, username, password)
587

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

    
591
    def _check_file_through_ssh(self, hostip, username, password,
592
                                remotepath, content):
593
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
594
            (hostip, username, password)
595
        log.info(msg)
596
        try:
597
            ssh = paramiko.SSHClient()
598
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
599
            ssh.connect(hostip, username=username, password=password)
600
            ssh.close()
601
        except socket.error:
602
            raise AssertionError
603

    
604
        transport = paramiko.Transport((hostip, 22))
605
        transport.connect(username=username, password=password)
606

    
607
        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
608
        sftp = paramiko.SFTPClient.from_transport(transport)
609
        sftp.get(remotepath, localpath)
610
        sftp.close()
611
        transport.close()
612

    
613
        f = open(localpath)
614
        remote_content = b64encode(f.read())
615

    
616
        # Check if files are the same
617
        return (remote_content == content)
618

    
619
    def _skipIf(self, condition, msg):
620
        if condition:
621
            self.skipTest(msg)
622

    
623
    def test_001_submit_create_server(self):
624
        """Test submit create server request"""
625

    
626
        log.info("Submit new server request")
627
        server = self.client.create_server(self.servername, self.flavorid,
628
                                           self.imageid, self.personality)
629

    
630
        log.info("Server id: " + str(server["id"]))
631
        log.info("Server password: " + server["adminPass"])
632
        self.assertEqual(server["name"], self.servername)
633
        self.assertEqual(server["flavorRef"], self.flavorid)
634
        self.assertEqual(server["imageRef"], self.imageid)
635
        self.assertEqual(server["status"], "BUILD")
636

    
637
        # Update class attributes to reflect data on building server
638
        cls = type(self)
639
        cls.serverid = server["id"]
640
        cls.username = None
641
        cls.passwd = server["adminPass"]
642

    
643
        self.result_dict["Server ID"] = str(server["id"])
644
        self.result_dict["Password"] = str(server["adminPass"])
645

    
646
    def test_002a_server_is_building_in_list(self):
647
        """Test server is in BUILD state, in server list"""
648
        log.info("Server in BUILD state in server list")
649

    
650
        self.result_dict.clear()
651

    
652
        servers = self.client.list_servers(detail=True)
653
        servers = filter(lambda x: x["name"] == self.servername, servers)
654

    
655
        server = servers[0]
656
        self.assertEqual(server["name"], self.servername)
657
        self.assertEqual(server["flavorRef"], self.flavorid)
658
        self.assertEqual(server["imageRef"], self.imageid)
659
        self.assertEqual(server["status"], "BUILD")
660

    
661
    def test_002b_server_is_building_in_details(self):
662
        """Test server is in BUILD state, in details"""
663

    
664
        log.info("Server in BUILD state in details")
665

    
666
        server = self.client.get_server_details(self.serverid)
667
        self.assertEqual(server["name"], self.servername)
668
        self.assertEqual(server["flavorRef"], self.flavorid)
669
        self.assertEqual(server["imageRef"], self.imageid)
670
        self.assertEqual(server["status"], "BUILD")
671

    
672
    def test_002c_set_server_metadata(self):
673

    
674
        log.info("Creating server metadata")
675

    
676
        image = self.client.get_image_details(self.imageid)
677
        os_value = image["metadata"]["values"]["os"]
678
        users = image["metadata"]["values"].get("users", None)
679
        self.client.update_server_metadata(self.serverid, OS=os_value)
680

    
681
        userlist = users.split()
682

    
683
        # Determine the username to use for future connections
684
        # to this host
685
        cls = type(self)
686

    
687
        if "root" in userlist:
688
            cls.username = "root"
689
        elif users is None:
690
            cls.username = self._connect_loginname(os_value)
691
        else:
692
            cls.username = choice(userlist)
693

    
694
        self.assertIsNotNone(cls.username)
695

    
696
    def test_002d_verify_server_metadata(self):
697
        """Test server metadata keys are set based on image metadata"""
698

    
699
        log.info("Verifying image metadata")
700

    
701
        servermeta = self.client.get_server_metadata(self.serverid)
702
        imagemeta = self.client.get_image_metadata(self.imageid)
703

    
704
        self.assertEqual(servermeta["OS"], imagemeta["os"])
705

    
706
    def test_003_server_becomes_active(self):
707
        """Test server becomes ACTIVE"""
708

    
709
        log.info("Waiting for server to become ACTIVE")
710

    
711
        self._insist_on_status_transition(
712
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
713

    
714
    def test_003a_get_server_oob_console(self):
715
        """Test getting OOB server console over VNC
716

717
        Implementation of RFB protocol follows
718
        http://www.realvnc.com/docs/rfbproto.pdf.
719

720
        """
721
        console = self.cyclades.get_server_console(self.serverid)
722
        self.assertEquals(console['type'], "vnc")
723
        sock = self._insist_on_tcp_connection(
724
            socket.AF_INET, console["host"], console["port"])
725

    
726
        # Step 1. ProtocolVersion message (par. 6.1.1)
727
        version = sock.recv(1024)
728
        self.assertEquals(version, 'RFB 003.008\n')
729
        sock.send(version)
730

    
731
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
732
        sec = sock.recv(1024)
733
        self.assertEquals(list(sec), ['\x01', '\x02'])
734

    
735
        # Step 3. Request VNC Authentication (par 6.1.2)
736
        sock.send('\x02')
737

    
738
        # Step 4. Receive Challenge (par 6.2.2)
739
        challenge = sock.recv(1024)
740
        self.assertEquals(len(challenge), 16)
741

    
742
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
743
        response = d3des_generate_response(
744
            (console["password"] + '\0' * 8)[:8], challenge)
745
        sock.send(response)
746

    
747
        # Step 6. SecurityResult (par 6.1.3)
748
        result = sock.recv(4)
749
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
750
        sock.close()
751

    
752
    def test_004_server_has_ipv4(self):
753
        """Test active server has a valid IPv4 address"""
754

    
755
        log.info("Validate server's IPv4")
756

    
757
        server = self.client.get_server_details(self.serverid)
758
        ipv4 = self._get_ipv4(server)
759

    
760
        self.result_dict.clear()
761
        self.result_dict["IPv4"] = str(ipv4)
762

    
763
        self.assertEquals(IP(ipv4).version(), 4)
764

    
765
    def test_005_server_has_ipv6(self):
766
        """Test active server has a valid IPv6 address"""
767
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
768

    
769
        log.info("Validate server's IPv6")
770

    
771
        server = self.client.get_server_details(self.serverid)
772
        ipv6 = self._get_ipv6(server)
773

    
774
        self.result_dict.clear()
775
        self.result_dict["IPv6"] = str(ipv6)
776

    
777
        self.assertEquals(IP(ipv6).version(), 6)
778

    
779
    def test_006_server_responds_to_ping_IPv4(self):
780
        """Test server responds to ping on IPv4 address"""
781

    
782
        log.info("Testing if server responds to pings in IPv4")
783
        self.result_dict.clear()
784

    
785
        server = self.client.get_server_details(self.serverid)
786
        ip = self._get_ipv4(server)
787
        self._try_until_timeout_expires(self.action_timeout,
788
                                        self.action_timeout,
789
                                        "PING IPv4 to %s" % ip,
790
                                        self._ping_once,
791
                                        False, ip)
792

    
793
    def test_007_server_responds_to_ping_IPv6(self):
794
        """Test server responds to ping on IPv6 address"""
795
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
796
        log.info("Testing if server responds to pings in IPv6")
797

    
798
        server = self.client.get_server_details(self.serverid)
799
        ip = self._get_ipv6(server)
800
        self._try_until_timeout_expires(self.action_timeout,
801
                                        self.action_timeout,
802
                                        "PING IPv6 to %s" % ip,
803
                                        self._ping_once,
804
                                        True, ip)
805

    
806
    def test_008_submit_shutdown_request(self):
807
        """Test submit request to shutdown server"""
808

    
809
        log.info("Shutting down server")
810

    
811
        self.cyclades.shutdown_server(self.serverid)
812

    
813
    def test_009_server_becomes_stopped(self):
814
        """Test server becomes STOPPED"""
815

    
816
        log.info("Waiting until server becomes STOPPED")
817
        self._insist_on_status_transition(
818
            "ACTIVE", "STOPPED", self.action_timeout, self.action_timeout)
819

    
820
    def test_010_submit_start_request(self):
821
        """Test submit start server request"""
822

    
823
        log.info("Starting server")
824

    
825
        self.cyclades.start_server(self.serverid)
826

    
827
    def test_011_server_becomes_active(self):
828
        """Test server becomes ACTIVE again"""
829

    
830
        log.info("Waiting until server becomes ACTIVE")
831
        self._insist_on_status_transition(
832
            "STOPPED", "ACTIVE", self.action_timeout, self.action_timeout)
833

    
834
    def test_011a_server_responds_to_ping_IPv4(self):
835
        """Test server OS is actually up and running again"""
836

    
837
        log.info("Testing if server is actually up and running")
838

    
839
        self.test_006_server_responds_to_ping_IPv4()
840

    
841
    def test_012_ssh_to_server_IPv4(self):
842
        """Test SSH to server public IPv4 works, verify hostname"""
843

    
844
        self._skipIf(self.is_windows, "only valid for Linux servers")
845
        server = self.client.get_server_details(self.serverid)
846
        self._insist_on_ssh_hostname(self._get_ipv4(server),
847
                                     self.username, self.passwd)
848

    
849
    def test_013_ssh_to_server_IPv6(self):
850
        """Test SSH to server public IPv6 works, verify hostname"""
851
        self._skipIf(self.is_windows, "only valid for Linux servers")
852
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
853

    
854
        server = self.client.get_server_details(self.serverid)
855
        self._insist_on_ssh_hostname(self._get_ipv6(server),
856
                                     self.username, self.passwd)
857

    
858
    def test_014_rdp_to_server_IPv4(self):
859
        "Test RDP connection to server public IPv4 works"""
860
        self._skipIf(not self.is_windows, "only valid for Windows servers")
861
        server = self.client.get_server_details(self.serverid)
862
        ipv4 = self._get_ipv4(server)
863
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
864

    
865
        # No actual RDP processing done. We assume the RDP server is there
866
        # if the connection to the RDP port is successful.
867
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
868
        sock.close()
869

    
870
    def test_015_rdp_to_server_IPv6(self):
871
        "Test RDP connection to server public IPv6 works"""
872
        self._skipIf(not self.is_windows, "only valid for Windows servers")
873
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
874

    
875
        server = self.client.get_server_details(self.serverid)
876
        ipv6 = self._get_ipv6(server)
877
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
878

    
879
        # No actual RDP processing done. We assume the RDP server is there
880
        # if the connection to the RDP port is successful.
881
        sock.close()
882

    
883
    def test_016_personality_is_enforced(self):
884
        """Test file injection for personality enforcement"""
885
        self._skipIf(self.is_windows, "only implemented for Linux servers")
886
        self._skipIf(self.personality is None, "No personality file selected")
887

    
888
        log.info("Trying to inject file for personality enforcement")
889

    
890
        server = self.client.get_server_details(self.serverid)
891

    
892
        for inj_file in self.personality:
893
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
894
                                                       inj_file['owner'],
895
                                                       self.passwd,
896
                                                       inj_file['path'],
897
                                                       inj_file['contents'])
898
            self.assertTrue(equal_files)
899

    
900
    def test_017_submit_delete_request(self):
901
        """Test submit request to delete server"""
902

    
903
        log.info("Deleting server")
904

    
905
        self.client.delete_server(self.serverid)
906

    
907
    def test_018_server_becomes_deleted(self):
908
        """Test server becomes DELETED"""
909

    
910
        log.info("Testing if server becomes DELETED")
911

    
912
        self._insist_on_status_transition(
913
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
914

    
915
    def test_019_server_no_longer_in_server_list(self):
916
        """Test server is no longer in server list"""
917

    
918
        log.info("Test if server is no longer listed")
919

    
920
        servers = self.client.list_servers()
921
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
922

    
923

    
924
class NetworkTestCase(unittest.TestCase):
925
    """ Testing networking in cyclades """
926

    
927
    @classmethod
928
    def setUpClass(cls):
929
        "Initialize kamaki, get list of current networks"
930

    
931
        cls.client = CycladesClient(API, TOKEN)
932
        cls.compute = ComputeClient(API, TOKEN)
933

    
934
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
935
                                          TEST_RUN_ID,
936
                                          cls.imagename)
937

    
938
        #Dictionary initialization for the vms credentials
939
        cls.serverid = dict()
940
        cls.username = dict()
941
        cls.password = dict()
942
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
943

    
944
        cls.result_dict = dict()
945

    
946
    def _skipIf(self, condition, msg):
947
        if condition:
948
            self.skipTest(msg)
949

    
950
    def _get_ipv4(self, server):
951
        """Get the public IPv4 of a server from the detailed server info"""
952

    
953
        nics = server["attachments"]["values"]
954

    
955
        for nic in nics:
956
            net_id = nic["network_id"]
957
            if self.client.get_network_details(net_id)["public"]:
958
                public_addrs = nic["ipv4"]
959

    
960
        self.assertTrue(public_addrs is not None)
961

    
962
        return public_addrs
963

    
964
    def _connect_loginname(self, os_value):
965
        """Return the login name for connections based on the server OS"""
966
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
967
            return "user"
968
        elif os_value in ("windows", "windows_alpha1"):
969
            return "Administrator"
970
        else:
971
            return "root"
972

    
973
    def _ping_once(self, ip):
974

    
975
        """Test server responds to a single IPv4 or IPv6 ping"""
976
        cmd = "ping -c 2 -w 3 %s" % (ip)
977
        ping = subprocess.Popen(cmd, shell=True,
978
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
979
        (stdout, stderr) = ping.communicate()
980
        ret = ping.wait()
981

    
982
        return (ret == 0)
983

    
984
    def test_00001a_submit_create_server_A(self):
985
        """Test submit create server request"""
986

    
987
        log.info("Creating test server A")
988

    
989
        serverA = self.client.create_server(self.servername, self.flavorid,
990
                                            self.imageid, personality=None)
991

    
992
        self.assertEqual(serverA["name"], self.servername)
993
        self.assertEqual(serverA["flavorRef"], self.flavorid)
994
        self.assertEqual(serverA["imageRef"], self.imageid)
995
        self.assertEqual(serverA["status"], "BUILD")
996

    
997
        # Update class attributes to reflect data on building server
998
        self.serverid['A'] = serverA["id"]
999
        self.username['A'] = None
1000
        self.password['A'] = serverA["adminPass"]
1001

    
1002
        log.info("Server A id:" + str(serverA["id"]))
1003
        log.info("Server password " + (self.password['A']))
1004

    
1005
        self.result_dict["Server A ID"] = str(serverA["id"])
1006
        self.result_dict["Server A password"] = serverA["adminPass"]
1007

    
1008
    def test_00001b_serverA_becomes_active(self):
1009
        """Test server becomes ACTIVE"""
1010

    
1011
        log.info("Waiting until test server A becomes ACTIVE")
1012
        self.result_dict.clear()
1013

    
1014
        fail_tmout = time.time() + self.action_timeout
1015
        while True:
1016
            d = self.client.get_server_details(self.serverid['A'])
1017
            status = d['status']
1018
            if status == 'ACTIVE':
1019
                active = True
1020
                break
1021
            elif time.time() > fail_tmout:
1022
                self.assertLess(time.time(), fail_tmout)
1023
            else:
1024
                time.sleep(self.query_interval)
1025

    
1026
        self.assertTrue(active)
1027

    
1028
    def test_00002a_submit_create_server_B(self):
1029
        """Test submit create server request"""
1030

    
1031
        log.info("Creating test server B")
1032

    
1033
        serverB = self.client.create_server(self.servername, self.flavorid,
1034
                                            self.imageid, personality=None)
1035

    
1036
        self.assertEqual(serverB["name"], self.servername)
1037
        self.assertEqual(serverB["flavorRef"], self.flavorid)
1038
        self.assertEqual(serverB["imageRef"], self.imageid)
1039
        self.assertEqual(serverB["status"], "BUILD")
1040

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

    
1046
        log.info("Server B id: " + str(serverB["id"]))
1047
        log.info("Password " + (self.password['B']))
1048

    
1049
        self.result_dict.clear()
1050
        self.result_dict["Server B ID"] = str(serverB["id"])
1051
        self.result_dict["Server B password"] = serverB["adminPass"]
1052

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

    
1056
        log.info("Waiting until test server B 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['B'])
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_001_create_network(self):
1074
        """Test submit create network request"""
1075

    
1076
        log.info("Submit new network request")
1077
        self.result_dict.clear()
1078

    
1079
        name = SNF_TEST_PREFIX + TEST_RUN_ID
1080
        #previous_num = len(self.client.list_networks())
1081
        network = self.client.create_network(name, cidr='10.0.0.1/28')
1082

    
1083
        #Test if right name is assigned
1084
        self.assertEqual(network['name'], name)
1085

    
1086
        # Update class attributes
1087
        cls = type(self)
1088
        cls.networkid = network['id']
1089
        #networks = self.client.list_networks()
1090

    
1091
        fail_tmout = time.time() + self.action_timeout
1092

    
1093
        #Test if new network is created
1094
        while True:
1095
            d = self.client.get_network_details(network['id'])
1096
            if d['status'] == 'ACTIVE':
1097
                connected = True
1098
                break
1099
            elif time.time() > fail_tmout:
1100
                self.assertLess(time.time(), fail_tmout)
1101
            else:
1102
                log.info("Waiting for network to become ACTIVE")
1103
                time.sleep(self.query_interval)
1104

    
1105
        self.assertTrue(connected)
1106

    
1107
        self.result_dict["Private network ID"] = str(network['id'])
1108

    
1109
    def test_002_connect_to_network(self):
1110
        """Test connect VMs to network"""
1111

    
1112
        log.info("Connect VMs to private network")
1113
        self.result_dict.clear()
1114

    
1115
        self.client.connect_server(self.serverid['A'], self.networkid)
1116
        self.client.connect_server(self.serverid['B'], self.networkid)
1117

    
1118
        #Insist on connecting until action timeout
1119
        fail_tmout = time.time() + self.action_timeout
1120

    
1121
        while True:
1122

    
1123
            netsA = [x['network_id']
1124
                     for x in self.client.get_server_details(
1125
                         self.serverid['A'])['attachments']['values']]
1126
            netsB = [x['network_id']
1127
                     for x in self.client.get_server_details(
1128
                         self.serverid['B'])['attachments']['values']]
1129

    
1130
            if (self.networkid in netsA) and (self.networkid in netsB):
1131
                conn_exists = True
1132
                break
1133
            elif time.time() > fail_tmout:
1134
                self.assertLess(time.time(), fail_tmout)
1135
            else:
1136
                time.sleep(self.query_interval)
1137

    
1138
        #Adding private IPs to class attributes
1139
        cls = type(self)
1140
        cls.priv_ip = dict()
1141

    
1142
        nicsA = self.client.get_server_details(
1143
            self.serverid['A'])['attachments']['values']
1144
        nicsB = self.client.get_server_details(
1145
            self.serverid['B'])['attachments']['values']
1146

    
1147
        if conn_exists:
1148
            for nic in nicsA:
1149
                if nic["network_id"] == self.networkid:
1150
                    cls.priv_ip["A"] = nic["ipv4"]
1151

    
1152
            for nic in nicsB:
1153
                if nic["network_id"] == self.networkid:
1154
                    cls.priv_ip["B"] = nic["ipv4"]
1155

    
1156
        self.assertTrue(conn_exists)
1157

    
1158
    def test_002a_reboot(self):
1159
        """Rebooting server A"""
1160

    
1161
        log.info("Rebooting server A")
1162

    
1163
        self.client.shutdown_server(self.serverid['A'])
1164

    
1165
        fail_tmout = time.time() + self.action_timeout
1166
        while True:
1167
            d = self.client.get_server_details(self.serverid['A'])
1168
            status = d['status']
1169
            if status == 'STOPPED':
1170
                break
1171
            elif time.time() > fail_tmout:
1172
                self.assertLess(time.time(), fail_tmout)
1173
            else:
1174
                time.sleep(self.query_interval)
1175

    
1176
        self.client.start_server(self.serverid['A'])
1177

    
1178
        while True:
1179
            d = self.client.get_server_details(self.serverid['A'])
1180
            status = d['status']
1181
            if status == 'ACTIVE':
1182
                active = True
1183
                break
1184
            elif time.time() > fail_tmout:
1185
                self.assertLess(time.time(), fail_tmout)
1186
            else:
1187
                time.sleep(self.query_interval)
1188

    
1189
        self.assertTrue(active)
1190

    
1191
    def test_002b_ping_server_A(self):
1192
        "Test if server A responds to IPv4 pings"
1193

    
1194
        log.info("Testing if server A responds to IPv4 pings ")
1195
        self.result_dict.clear()
1196

    
1197
        server = self.client.get_server_details(self.serverid['A'])
1198
        ip = self._get_ipv4(server)
1199

    
1200
        fail_tmout = time.time() + self.action_timeout
1201

    
1202
        s = False
1203

    
1204
        self.result_dict["Server A public IP"] = str(ip)
1205

    
1206
        while True:
1207

    
1208
            if self._ping_once(ip):
1209
                s = True
1210
                break
1211

    
1212
            elif time.time() > fail_tmout:
1213
                self.assertLess(time.time(), fail_tmout)
1214

    
1215
            else:
1216
                time.sleep(self.query_interval)
1217

    
1218
        self.assertTrue(s)
1219

    
1220
    def test_002c_reboot(self):
1221
        """Reboot server B"""
1222

    
1223
        log.info("Rebooting server B")
1224
        self.result_dict.clear()
1225

    
1226
        self.client.shutdown_server(self.serverid['B'])
1227

    
1228
        fail_tmout = time.time() + self.action_timeout
1229
        while True:
1230
            d = self.client.get_server_details(self.serverid['B'])
1231
            status = d['status']
1232
            if status == 'STOPPED':
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.client.start_server(self.serverid['B'])
1240

    
1241
        while True:
1242
            d = self.client.get_server_details(self.serverid['B'])
1243
            status = d['status']
1244
            if status == 'ACTIVE':
1245
                active = True
1246
                break
1247
            elif time.time() > fail_tmout:
1248
                self.assertLess(time.time(), fail_tmout)
1249
            else:
1250
                time.sleep(self.query_interval)
1251

    
1252
        self.assertTrue(active)
1253

    
1254
    def test_002d_ping_server_B(self):
1255
        """Test if server B responds to IPv4 pings"""
1256

    
1257
        log.info("Testing if server B responds to IPv4 pings")
1258
        self.result_dict.clear()
1259

    
1260
        server = self.client.get_server_details(self.serverid['B'])
1261
        ip = self._get_ipv4(server)
1262

    
1263
        fail_tmout = time.time() + self.action_timeout
1264

    
1265
        s = False
1266

    
1267
        self.result_dict["Server B public IP"] = str(ip)
1268

    
1269
        while True:
1270
            if self._ping_once(ip):
1271
                s = True
1272
                break
1273

    
1274
            elif time.time() > fail_tmout:
1275
                self.assertLess(time.time(), fail_tmout)
1276

    
1277
            else:
1278
                time.sleep(self.query_interval)
1279

    
1280
        self.assertTrue(s)
1281

    
1282
    def test_003a_setup_interface_A(self):
1283
        """Set up eth1 for server A"""
1284

    
1285
        self._skipIf(self.is_windows, "only valid for Linux servers")
1286

    
1287
        log.info("Setting up interface eth1 for server A")
1288
        self.result_dict.clear()
1289

    
1290
        server = self.client.get_server_details(self.serverid['A'])
1291
        image = self.client.get_image_details(self.imageid)
1292
        os_value = image['metadata']['values']['os']
1293

    
1294
        users = image["metadata"]["values"].get("users", None)
1295
        userlist = users.split()
1296

    
1297
        if "root" in userlist:
1298
            loginname = "root"
1299
        elif users is None:
1300
            loginname = self._connect_loginname(os_value)
1301
        else:
1302
            loginname = choice(userlist)
1303

    
1304
        hostip = self._get_ipv4(server)
1305
        myPass = self.password['A']
1306

    
1307
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1308
        command = "ifconfig eth1 %s" % self.priv_ip["A"]
1309
        output, status = _ssh_execute(
1310
            hostip, loginname, myPass, command)
1311

    
1312
        self.assertEquals(status, 0)
1313

    
1314
    def test_003b_setup_interface_B(self):
1315
        """Setup eth1 for server B"""
1316

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

    
1319
        log.info("Setting up interface eth1 for server B")
1320

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

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

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

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

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

    
1343
        self.assertEquals(status, 0)
1344

    
1345
    def test_003c_test_connection_exists(self):
1346
        """Ping server B from server A to test if connection exists"""
1347

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

    
1350
        log.info("Testing if server A is actually connected to server B")
1351

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

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

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

    
1367
        myPass = self.password['A']
1368

    
1369
        cmd = "if ping -c 2 -w 3 %s >/dev/null; \
1370
               then echo \'True\'; fi;" % self.priv_ip["B"]
1371
        lines, status = _ssh_execute(
1372
            hostip, loginname, myPass, cmd)
1373

    
1374
        exists = False
1375

    
1376
        if 'True\n' in lines:
1377
            exists = True
1378

    
1379
        self.assertTrue(exists)
1380

    
1381
    def test_004_disconnect_from_network(self):
1382
        "Disconnecting server A and B from network"
1383

    
1384
        log.info("Disconnecting servers from private network")
1385

    
1386
        prev_state = self.client.get_network_details(self.networkid)
1387
        prev_nics = prev_state['attachments']['values']
1388
        #prev_conn = len(prev_nics)
1389

    
1390
        nicsA = [x['id']
1391
                 for x in self.client.get_server_details(
1392
                     self.serverid['A'])['attachments']['values']]
1393
        nicsB = [x['id']
1394
                 for x in self.client.get_server_details(
1395
                     self.serverid['B'])['attachments']['values']]
1396

    
1397
        for nic in prev_nics:
1398
            if nic in nicsA:
1399
                self.client.disconnect_server(self.serverid['A'], nic)
1400
            if nic in nicsB:
1401
                self.client.disconnect_server(self.serverid['B'], nic)
1402

    
1403
        #Insist on deleting until action timeout
1404
        fail_tmout = time.time() + self.action_timeout
1405

    
1406
        while True:
1407
            netsA = [x['network_id']
1408
                     for x in self.client.get_server_details(
1409
                         self.serverid['A'])['attachments']['values']]
1410
            netsB = [x['network_id']
1411
                     for x in self.client.get_server_details(
1412
                         self.serverid['B'])['attachments']['values']]
1413

    
1414
            #connected = (self.client.get_network_details(self.networkid))
1415
            #connections = connected['attachments']['values']
1416
            if (self.networkid not in netsA) and (self.networkid not in netsB):
1417
                conn_exists = False
1418
                break
1419
            elif time.time() > fail_tmout:
1420
                self.assertLess(time.time(), fail_tmout)
1421
            else:
1422
                time.sleep(self.query_interval)
1423

    
1424
        self.assertFalse(conn_exists)
1425

    
1426
    def test_005_destroy_network(self):
1427
        """Test submit delete network request"""
1428

    
1429
        log.info("Submitting delete network request")
1430

    
1431
        self.client.delete_network(self.networkid)
1432

    
1433
        fail_tmout = time.time() + self.action_timeout
1434

    
1435
        while True:
1436

    
1437
            curr_net = []
1438
            networks = self.client.list_networks()
1439

    
1440
            for net in networks:
1441
                curr_net.append(net['id'])
1442

    
1443
            if self.networkid not in curr_net:
1444
                self.assertTrue(self.networkid not in curr_net)
1445
                break
1446

    
1447
            elif time.time() > fail_tmout:
1448
                self.assertLess(time.time(), fail_tmout)
1449

    
1450
            else:
1451
                time.sleep(self.query_interval)
1452

    
1453
    def test_006_cleanup_servers(self):
1454
        """Cleanup servers created for this test"""
1455

    
1456
        log.info("Delete servers created for this test")
1457

    
1458
        self.compute.delete_server(self.serverid['A'])
1459
        self.compute.delete_server(self.serverid['B'])
1460

    
1461
        fail_tmout = time.time() + self.action_timeout
1462

    
1463
        #Ensure server gets deleted
1464
        status = dict()
1465

    
1466
        while True:
1467
            details = self.compute.get_server_details(self.serverid['A'])
1468
            status['A'] = details['status']
1469
            details = self.compute.get_server_details(self.serverid['B'])
1470
            status['B'] = details['status']
1471
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1472
                deleted = True
1473
                break
1474
            elif time.time() > fail_tmout:
1475
                self.assertLess(time.time(), fail_tmout)
1476
            else:
1477
                time.sleep(self.query_interval)
1478

    
1479
        self.assertTrue(deleted)
1480

    
1481

    
1482
class TestRunnerProcess(Process):
1483
    """A distinct process used to execute part of the tests in parallel"""
1484
    def __init__(self, **kw):
1485
        Process.__init__(self, **kw)
1486
        kwargs = kw["kwargs"]
1487
        self.testq = kwargs["testq"]
1488
        self.worker_folder = kwargs["worker_folder"]
1489

    
1490
    def run(self):
1491
        # Make sure this test runner process dies with the parent
1492
        # and is not left behind.
1493
        #
1494
        # WARNING: This uses the prctl(2) call and is
1495
        # Linux-specific.
1496

    
1497
        prctl.set_pdeathsig(signal.SIGHUP)
1498

    
1499
        multi = logging.getLogger("multiprocess")
1500

    
1501
        while True:
1502
            multi.debug("I am process %d, GETting from queue is %s" %
1503
                        (os.getpid(), self.testq))
1504
            msg = self.testq.get()
1505

    
1506
            multi.debug("Dequeued msg: %s" % msg)
1507

    
1508
            if msg == "TEST_RUNNER_TERMINATE":
1509
                raise SystemExit
1510

    
1511
            elif issubclass(msg, unittest.TestCase):
1512
                # Assemble a TestSuite, and run it
1513

    
1514
                log_file = os.path.join(self.worker_folder, 'details_' +
1515
                                        (msg.__name__) + "_" +
1516
                                        TEST_RUN_ID + '.log')
1517

    
1518
                fail_file = os.path.join(self.worker_folder, 'failed_' +
1519
                                         (msg.__name__) + "_" +
1520
                                         TEST_RUN_ID + '.log')
1521
                error_file = os.path.join(self.worker_folder, 'error_' +
1522
                                          (msg.__name__) + "_" +
1523
                                          TEST_RUN_ID + '.log')
1524

    
1525
                f = open(log_file, 'w')
1526
                fail = open(fail_file, 'w')
1527
                error = open(error_file, 'w')
1528

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

    
1531
                runner = unittest.TextTestRunner(
1532
                    f, verbosity=2, failfast=True,
1533
                    resultclass=BurninTestResult)
1534
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
1535
                result = runner.run(suite)
1536

    
1537
                for res in result.errors:
1538
                    log.error("snf-burnin encountered an error in "
1539
                              "testcase: %s" % msg)
1540
                    log.error("See log for details")
1541
                    error.write(str(res[0]) + '\n')
1542
                    error.write(str(res[0].shortDescription()) + '\n')
1543
                    error.write('\n')
1544

    
1545
                for res in result.failures:
1546
                    log.error("snf-burnin failed in testcase: %s" % msg)
1547
                    log.error("See log for details")
1548
                    fail.write(str(res[0]) + '\n')
1549
                    fail.write(str(res[0].shortDescription()) + '\n')
1550
                    fail.write('\n')
1551
                    if not NOFAILFAST:
1552
                        sys.exit()
1553

    
1554
                if (len(result.failures) == 0) and (len(result.errors) == 0):
1555
                    log.debug("Passed testcase: %s" % msg)
1556

    
1557
                f.close()
1558
                fail.close()
1559
                error.close()
1560

    
1561
            else:
1562
                raise Exception("Cannot handle msg: %s" % msg)
1563

    
1564

    
1565
def _run_cases_in_series(cases, image_folder):
1566
    """Run instances of TestCase in series"""
1567

    
1568
    for case in cases:
1569

    
1570
        test = case.__name__
1571

    
1572
        log.info(yellow + '* Starting testcase: %s' % test + normal)
1573
        log_file = os.path.join(image_folder, 'details_' +
1574
                                (case.__name__) + "_" +
1575
                                TEST_RUN_ID + '.log')
1576
        fail_file = os.path.join(image_folder, 'failed_' +
1577
                                 (case.__name__) + "_" +
1578
                                 TEST_RUN_ID + '.log')
1579
        error_file = os.path.join(image_folder, 'error_' +
1580
                                  (case.__name__) + "_" +
1581
                                  TEST_RUN_ID + '.log')
1582

    
1583
        f = open(log_file, "w")
1584
        fail = open(fail_file, "w")
1585
        error = open(error_file, "w")
1586

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

    
1593
        for res in result.errors:
1594
            log.error("snf-burnin encountered an error in "
1595
                      "testcase: %s" % test)
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" % test)
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" % test)
1612

    
1613

    
1614
def _run_cases_in_parallel(cases, fanout, image_folder):
1615
    """Run instances of TestCase in parallel, in a number of distinct processes
1616

1617
    The cases iterable specifies the TestCases to be executed in parallel,
1618
    by test runners running in distinct processes.
1619
    The fanout parameter specifies the number of processes to spawn,
1620
    and defaults to 1.
1621
    The runner argument specifies the test runner class to use inside each
1622
    runner process.
1623

1624
    """
1625

    
1626
    multi = logging.getLogger("multiprocess")
1627
    handler = logging.StreamHandler()
1628
    multi.addHandler(handler)
1629

    
1630
    if VERBOSE:
1631
        multi.setLevel(logging.DEBUG)
1632
    else:
1633
        multi.setLevel(logging.INFO)
1634

    
1635
    testq = []
1636
    worker_folder = []
1637
    runners = []
1638

    
1639
    for i in xrange(0, fanout):
1640
        testq.append(Queue())
1641
        worker_folder.append(os.path.join(image_folder, 'process'+str(i)))
1642
        os.mkdir(worker_folder[i])
1643

    
1644
    for i in xrange(0, fanout):
1645
        kwargs = dict(testq=testq[i], worker_folder=worker_folder[i])
1646
        runners.append(TestRunnerProcess(kwargs=kwargs))
1647

    
1648
    multi.debug("Spawning %d test runner processes" % len(runners))
1649

    
1650
    for p in runners:
1651
        p.start()
1652

    
1653
    # Enqueue test cases
1654
    for i in xrange(0, fanout):
1655
        map(testq[i].put, cases)
1656
        testq[i].put("TEST_RUNNER_TERMINATE")
1657

    
1658
    multi.debug("Spawned %d test runners, PIDs are %s" %
1659
                (len(runners), [p.pid for p in runners]))
1660

    
1661
    multi.debug("Joining %d processes" % len(runners))
1662

    
1663
    for p in runners:
1664
        p.join()
1665

    
1666
    multi.debug("Done joining %d processes" % len(runners))
1667

    
1668

    
1669
def _images_test_case(**kwargs):
1670
    """Construct a new unit test case class from ImagesTestCase"""
1671
    name = "ImagesTestCase_%s" % kwargs["imageid"]
1672
    cls = type(name, (ImagesTestCase,), kwargs)
1673

    
1674
    #Patch extra parameters into test names by manipulating method docstrings
1675
    for (mname, m) in \
1676
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1677
        if hasattr(m, __doc__):
1678
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1679

    
1680
    # Make sure the class can be pickled, by listing it among
1681
    # the attributes of __main__. A PicklingError is raised otherwise.
1682
    thismodule = sys.modules[__name__]
1683
    setattr(thismodule, name, cls)
1684
    return cls
1685

    
1686

    
1687
def _spawn_server_test_case(**kwargs):
1688
    """Construct a new unit test case class from SpawnServerTestCase"""
1689

    
1690
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1691
    cls = type(name, (SpawnServerTestCase,), kwargs)
1692

    
1693
    # Patch extra parameters into test names by manipulating method docstrings
1694
    for (mname, m) in \
1695
            inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1696
        if hasattr(m, __doc__):
1697
            m.__func__.__doc__ = "[%s] %s" % (cls.imagename, m.__doc__)
1698

    
1699
    # Make sure the class can be pickled, by listing it among
1700
    # the attributes of __main__. A PicklingError is raised otherwise.
1701

    
1702
    thismodule = sys.modules[__name__]
1703
    setattr(thismodule, name, cls)
1704
    return cls
1705

    
1706

    
1707
def _spawn_network_test_case(**kwargs):
1708
    """Construct a new unit test case class from NetworkTestCase"""
1709

    
1710
    name = "NetworkTestCase" + TEST_RUN_ID
1711
    cls = type(name, (NetworkTestCase,), kwargs)
1712

    
1713
    # Make sure the class can be pickled, by listing it among
1714
    # the attributes of __main__. A PicklingError is raised otherwise.
1715

    
1716
    thismodule = sys.modules[__name__]
1717
    setattr(thismodule, name, cls)
1718
    return cls
1719

    
1720

    
1721
# --------------------------------------------------------------------
1722
# Clean up servers/networks functions
1723
def cleanup_servers(timeout, query_interval, delete_stale=False):
1724

    
1725
    c = ComputeClient(API, TOKEN)
1726

    
1727
    servers = c.list_servers()
1728
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1729

    
1730
    if len(stale) == 0:
1731
        return
1732

    
1733
    # Show staled servers
1734
    print >>sys.stderr, yellow + \
1735
        "Found these stale servers from previous runs:" + \
1736
        normal
1737
    print >>sys.stderr, "    " + \
1738
        "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1739

    
1740
    # Delete staled servers
1741
    if delete_stale:
1742
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1743
        fail_tmout = time.time() + timeout
1744
        for s in stale:
1745
            c.delete_server(s["id"])
1746
        # Wait for all servers to be deleted
1747
        while True:
1748
            servers = c.list_servers()
1749
            stale = [s for s in servers
1750
                     if s["name"].startswith(SNF_TEST_PREFIX)]
1751
            if len(stale) == 0:
1752
                print >> sys.stderr, green + "    ...done" + normal
1753
                break
1754
            elif time.time() > fail_tmout:
1755
                print >> sys.stderr, red + \
1756
                    "Not all stale servers deleted. Action timed out." + \
1757
                    normal
1758
                sys.exit(1)
1759
            else:
1760
                time.sleep(query_interval)
1761
    else:
1762
        print >> sys.stderr, "Use --delete-stale to delete them."
1763

    
1764

    
1765
def cleanup_networks(action_timeout, query_interval, delete_stale=False):
1766

    
1767
    c = CycladesClient(API, TOKEN)
1768

    
1769
    networks = c.list_networks()
1770
    stale = [n for n in networks if n["name"].startswith(SNF_TEST_PREFIX)]
1771

    
1772
    if len(stale) == 0:
1773
        return
1774

    
1775
    # Show staled networks
1776
    print >> sys.stderr, yellow + \
1777
        "Found these stale networks from previous runs:" + \
1778
        normal
1779
    print "    " + \
1780
        "\n    ".join(["%s: %s" % (str(n["id"]), n["name"]) for n in stale])
1781

    
1782
    # Delete staled networks
1783
    if delete_stale:
1784
        print >> sys.stderr, "Deleting %d stale networks:" % len(stale)
1785
        fail_tmout = time.time() + action_timeout
1786
        for n in stale:
1787
            c.delete_network(n["id"])
1788
        # Wait for all networks to be deleted
1789
        while True:
1790
            networks = c.list_networks()
1791
            stale = [n for n in networks
1792
                     if n["name"].startswith(SNF_TEST_PREFIX)]
1793
            if len(stale) == 0:
1794
                print >> sys.stderr, green + "    ...done" + normal
1795
                break
1796
            elif time.time() > fail_tmout:
1797
                print >> sys.stderr, red + \
1798
                    "Not all stale networks deleted. Action timed out." + \
1799
                    normal
1800
                sys.exit(1)
1801
            else:
1802
                time.sleep(query_interval)
1803
    else:
1804
        print >> sys.stderr, "Use --delete-stale to delete them."
1805

    
1806

    
1807
# --------------------------------------------------------------------
1808
# Parse arguments functions
1809
def parse_comma(option, opt, value, parser):
1810
    tests = set(['all', 'auth', 'images', 'flavors',
1811
                 'pithos', 'servers', 'server_spawn',
1812
                 'network_spawn'])
1813
    parse_input = value.split(',')
1814

    
1815
    if not (set(parse_input)).issubset(tests):
1816
        raise OptionValueError("The selected set of tests is invalid")
1817

    
1818
    setattr(parser.values, option.dest, value.split(','))
1819

    
1820

    
1821
def parse_arguments(args):
1822

    
1823
    kw = {}
1824
    kw["usage"] = "%prog [options]"
1825
    kw["description"] = \
1826
        "%prog runs a number of test scenarios on a " \
1827
        "Synnefo deployment."
1828

    
1829
    parser = OptionParser(**kw)
1830
    parser.disable_interspersed_args()
1831

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

    
1945
    (opts, args) = parser.parse_args(args)
1946

    
1947
    # -----------------------
1948
    # Verify arguments
1949

    
1950
    # `delete_stale' implies `show_stale'
1951
    if opts.delete_stale:
1952
        opts.show_stale = True
1953

    
1954
    # `token' is mandatory
1955
    _mandatory_argument(opts.token, "--token")
1956
    # `api' is mandatory
1957
    _mandatory_argument(opts.api, "--api")
1958

    
1959
    if not opts.show_stale:
1960
        # `image-id' is mandatory
1961
        _mandatory_argument(opts.force_imageid, "--image_id")
1962
        if opts.force_imageid != 'all':
1963
            try:
1964
                opts.force_imageid = str(opts.force_imageid)
1965
            except ValueError:
1966
                print >>sys.stderr, red + \
1967
                    "Invalid value specified for" + \
1968
                    "--image-id. Use a valid id, or `all'." + \
1969
                    normal
1970
                sys.exit(1)
1971
        # `pithos' is mandatory
1972
        _mandatory_argument(opts.pithos, "--pithos")
1973
        # `pithos_user' is mandatory
1974
        _mandatory_argument(opts.pithos_user, "--pithos_user")
1975
        # `plankton' is mandatory
1976
        _mandatory_argument(opts.plankton, "--plankton")
1977

    
1978
    return (opts, args)
1979

    
1980

    
1981
def _mandatory_argument(Arg, Str):
1982
    if not Arg:
1983
        print >>sys.stderr, red + \
1984
            "The " + Str + " argument is mandatory.\n" + \
1985
            normal
1986
        sys.exit(1)
1987

    
1988

    
1989
# --------------------------------------------------------------------
1990
# Burnin main function
1991
def main():
1992
    """Assemble test cases into a test suite, and run it
1993

1994
    IMPORTANT: Tests have dependencies and have to be run in the specified
1995
    order inside a single test case. They communicate through attributes of the
1996
    corresponding TestCase class (shared fixtures). Distinct subclasses of
1997
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
1998
    test runner processes.
1999

2000
    """
2001

    
2002
    # Parse arguments using `optparse'
2003
    (opts, args) = parse_arguments(sys.argv[1:])
2004

    
2005
    # Some global variables
2006
    global API, TOKEN, PLANKTON, PLANKTON_USER
2007
    global PITHOS, PITHOS_USER, NO_IPV6, VERBOSE, NOFAILFAST
2008
    API = opts.api
2009
    TOKEN = opts.token
2010
    PLANKTON = opts.plankton
2011
    PLANKTON_USER = opts.plankton_user
2012
    PITHOS = opts.pithos
2013
    PITHOS_USER = opts.pithos_user
2014
    NO_IPV6 = opts.no_ipv6
2015
    VERBOSE = opts.verbose
2016
    NOFAILFAST = opts.nofailfast
2017

    
2018
    # If `show_stale', cleanup stale servers
2019
    # from previous runs and exit
2020
    if opts.show_stale:
2021
        # We must clean the servers first
2022
        cleanup_servers(opts.action_timeout, opts.query_interval,
2023
                        delete_stale=opts.delete_stale)
2024
        cleanup_networks(opts.action_timeout, opts.query_interval,
2025
                         delete_stale=opts.delete_stale)
2026
        return 0
2027

    
2028
    # Initialize a kamaki instance, get flavors, images
2029
    c = ComputeClient(API, TOKEN)
2030
    DIMAGES = c.list_images(detail=True)
2031
    DFLAVORS = c.list_flavors(detail=True)
2032

    
2033
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
2034
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
2035
    #unittest.main(verbosity=2, catchbreak=True)
2036

    
2037
    # Get a list of images we are going to test
2038
    if opts.force_imageid == 'all':
2039
        test_images = DIMAGES
2040
    else:
2041
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
2042

    
2043
    # Create output (logging) folder
2044
    if not os.path.exists(opts.log_folder):
2045
        os.mkdir(opts.log_folder)
2046
    test_folder = os.path.join(opts.log_folder, TEST_RUN_ID)
2047
    os.mkdir(test_folder)
2048

    
2049
    for image in test_images:
2050
        imageid = str(image["id"])
2051
        imagename = image["name"]
2052
        # Choose a flavor (given from user or random)
2053
        if opts.force_flavorid:
2054
            flavorid = opts.force_flavorid
2055
        else:
2056
            flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
2057
        # Personality dictionary for file injection test
2058
        if opts.personality_path is not None:
2059
            f = open(opts.personality_path)
2060
            content = b64encode(f.read())
2061
            personality = []
2062
            st = os.stat(opts.personality_path)
2063
            personality.append({
2064
                'path': '/root/test_inj_file',
2065
                'owner': 'root',
2066
                'group': 'root',
2067
                'mode': 0x7777 & st.st_mode,
2068
                'contents': content})
2069
        else:
2070
            personality = None
2071
        # Give a name to our test servers
2072
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
2073
        is_windows = imagename.lower().find("windows") >= 0
2074

    
2075
        # Create Server TestCases
2076
        ServerTestCase = _spawn_server_test_case(
2077
            imageid=imageid,
2078
            flavorid=flavorid,
2079
            imagename=imagename,
2080
            personality=personality,
2081
            servername=servername,
2082
            is_windows=is_windows,
2083
            action_timeout=opts.action_timeout,
2084
            build_warning=opts.build_warning,
2085
            build_fail=opts.build_fail,
2086
            query_interval=opts.query_interval)
2087
        # Create Network TestCases
2088
        NetworkTestCase = _spawn_network_test_case(
2089
            action_timeout=opts.action_timeout,
2090
            imageid=imageid,
2091
            flavorid=flavorid,
2092
            imagename=imagename,
2093
            query_interval=opts.query_interval)
2094
        # Create Images TestCase
2095
        CImagesTestCase = _images_test_case(
2096
            action_timeout=opts.action_timeout,
2097
            imageid=imageid,
2098
            flavorid=flavorid,
2099
            imagename=imagename,
2100
            query_interval=opts.query_interval)
2101

    
2102
        # Choose the tests we are going to run
2103
        test_dict = {'auth': UnauthorizedTestCase,
2104
                     'images': CImagesTestCase,
2105
                     'flavors': FlavorsTestCase,
2106
                     'servers': ServersTestCase,
2107
                     'pithos': PithosTestCase,
2108
                     'server_spawn': ServerTestCase,
2109
                     'network_spawn': NetworkTestCase}
2110
        seq_cases = []
2111
        if 'all' in opts.tests:
2112
            seq_cases = [UnauthorizedTestCase, ImagesTestCase,
2113
                         FlavorsTestCase, ServersTestCase,
2114
                         PithosTestCase, ServerTestCase,
2115
                         NetworkTestCase]
2116
        else:
2117
            for test in opts.tests:
2118
                seq_cases.append(test_dict[test])
2119

    
2120
        # Folder for each image
2121
        image_folder = os.path.join(test_folder, imageid)
2122
        os.mkdir(image_folder)
2123

    
2124
        # Run each test
2125
        if opts.fanout > 1:
2126
            _run_cases_in_parallel(seq_cases, opts.fanout, image_folder)
2127
        else:
2128
            _run_cases_in_series(seq_cases, image_folder)
2129

    
2130

    
2131
# --------------------------------------------------------------------
2132
# Call main
2133
if __name__ == "__main__":
2134
    sys.exit(main())