Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / tools / burnin.py @ 4573ea07

History | View | Annotate | Download (52.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 paramiko
44
import prctl
45
import subprocess
46
import signal
47
import socket
48
import struct
49
import sys
50
import time
51
import hashlib
52
from base64 import b64encode
53
from pwd import getpwuid
54
from grp import getgrgid
55
from IPy import IP
56
from multiprocessing import Process, Queue
57
from random import choice
58

    
59
from kamaki.clients.compute import ComputeClient
60
from kamaki.clients.cyclades import CycladesClient
61

    
62
from fabric.api import *
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
API = None
76
TOKEN = None
77
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
78

    
79
# A unique id identifying this test run
80
TEST_RUN_ID = datetime.datetime.strftime(datetime.datetime.now(),
81
                                         "%Y%m%d%H%M%S")
82
SNF_TEST_PREFIX = "snf-test-"
83

    
84
# Setup logging (FIXME - verigak)
85
logging.basicConfig(format="%(message)s")
86
log = logging.getLogger("burnin")
87
log.setLevel(logging.INFO)
88

    
89
class UnauthorizedTestCase(unittest.TestCase):
90
    def test_unauthorized_access(self):
91
        """Test access without a valid token fails"""
92
        falseToken = '12345'
93
        c=ComputeClient(API, falseToken)
94

    
95
        with self.assertRaises(ClientError) as cm:
96
            c.list_servers()
97
        self.assertEqual(cm.exception.status, 401)
98

    
99

    
100
class ImagesTestCase(unittest.TestCase):
101
    """Test image lists for consistency"""
102
    @classmethod
103
    def setUpClass(cls):
104
        """Initialize kamaki, get (detailed) list of images"""
105
        log.info("Getting simple and detailed list of images")
106

    
107
        cls.client = ComputeClient(API, TOKEN)
108
        cls.images = cls.client.list_images()
109
        cls.dimages = cls.client.list_images(detail=True)
110

    
111
    def test_001_list_images(self):
112
        """Test image list actually returns images"""
113
        self.assertGreater(len(self.images), 0)
114

    
115
    def test_002_list_images_detailed(self):
116
        """Test detailed image list is the same length as list"""
117
        self.assertEqual(len(self.dimages), len(self.images))
118

    
119
    def test_003_same_image_names(self):
120
        """Test detailed and simple image list contain same names"""
121
        names = sorted(map(lambda x: x["name"], self.images))
122
        dnames = sorted(map(lambda x: x["name"], self.dimages))
123
        self.assertEqual(names, dnames)
124

    
125
    def test_004_unique_image_names(self):
126
        """Test images have unique names"""
127
        names = sorted(map(lambda x: x["name"], self.images))
128
        self.assertEqual(sorted(list(set(names))), names)
129

    
130
    def test_005_image_metadata(self):
131
        """Test every image has specific metadata defined"""
132
        keys = frozenset(["os", "description", "size"])
133
        for i in self.dimages:
134
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))
135

    
136

    
137
class FlavorsTestCase(unittest.TestCase):
138
    """Test flavor lists for consistency"""
139
    @classmethod
140
    def setUpClass(cls):
141
        """Initialize kamaki, get (detailed) list of flavors"""
142
        log.info("Getting simple and detailed list of flavors")
143

    
144
        cls.client = ComputeClient(API, TOKEN)
145
        cls.flavors = cls.client.list_flavors()
146
        cls.dflavors = cls.client.list_flavors(detail=True)
147

    
148
    def test_001_list_flavors(self):
149
        """Test flavor list actually returns flavors"""
150
        self.assertGreater(len(self.flavors), 0)
151

    
152
    def test_002_list_flavors_detailed(self):
153
        """Test detailed flavor list is the same length as list"""
154
        self.assertEquals(len(self.dflavors), len(self.flavors))
155

    
156
    def test_003_same_flavor_names(self):
157
        """Test detailed and simple flavor list contain same names"""
158
        names = sorted(map(lambda x: x["name"], self.flavors))
159
        dnames = sorted(map(lambda x: x["name"], self.dflavors))
160
        self.assertEqual(names, dnames)
161

    
162
    def test_004_unique_flavor_names(self):
163
        """Test flavors have unique names"""
164
        names = sorted(map(lambda x: x["name"], self.flavors))
165
        self.assertEqual(sorted(list(set(names))), names)
166

    
167
    def test_005_well_formed_flavor_names(self):
168
        """Test flavors have names of the form CxxRyyDzz
169

170
        Where xx is vCPU count, yy is RAM in MiB, zz is Disk in GiB
171

172
        """
173
        for f in self.dflavors:
174
            self.assertEqual("C%dR%dD%d" % (f["cpu"], f["ram"], f["disk"]),
175
                             f["name"],
176
                             "Flavor %s does not match its specs." % f["name"])
177

    
178

    
179
class ServersTestCase(unittest.TestCase):
180
    """Test server lists for consistency"""
181
    @classmethod
182
    def setUpClass(cls):
183
        """Initialize kamaki, get (detailed) list of servers"""
184
        log.info("Getting simple and detailed list of servers")
185

    
186
        cls.client = ComputeClient(API, TOKEN)
187
        cls.servers = cls.client.list_servers()
188
        cls.dservers = cls.client.list_servers(detail=True)
189

    
190
    def test_001_list_servers(self):
191
        """Test server list actually returns servers"""
192
        self.assertGreater(len(self.servers), 0)
193

    
194
    def test_002_list_servers_detailed(self):
195
        """Test detailed server list is the same length as list"""
196
        self.assertEqual(len(self.dservers), len(self.servers))
197

    
198
    def test_003_same_server_names(self):
199
        """Test detailed and simple flavor list contain same names"""
200
        names = sorted(map(lambda x: x["name"], self.servers))
201
        dnames = sorted(map(lambda x: x["name"], self.dservers))
202
        self.assertEqual(names, dnames)
203

    
204

    
205
# This class gets replicated into actual TestCases dynamically
206
class SpawnServerTestCase(unittest.TestCase):
207
    """Test scenario for server of the specified image"""
208

    
209
    @classmethod
210
    def setUpClass(cls):
211
        """Initialize a kamaki instance"""
212
        log.info("Spawning server for image `%s'", cls.imagename)
213

    
214
        cls.client = ComputeClient(API, TOKEN)
215
        cls.cyclades = CycladesClient(API, TOKEN)
216

    
217
    def _get_ipv4(self, server):
218
        """Get the public IPv4 of a server from the detailed server info"""
219

    
220
        public_addrs = filter(lambda x: x["id"] == "public",
221
                              server["addresses"]["values"])
222
        self.assertEqual(len(public_addrs), 1)
223
        ipv4_addrs = filter(lambda x: x["version"] == 4,
224
                            public_addrs[0]["values"])
225
        self.assertEqual(len(ipv4_addrs), 1)
226
        return ipv4_addrs[0]["addr"]
227

    
228
    def _get_ipv6(self, server):
229
        """Get the public IPv6 of a server from the detailed server info"""
230
        public_addrs = filter(lambda x: x["id"] == "public",
231
                              server["addresses"]["values"])
232
        self.assertEqual(len(public_addrs), 1)
233
        ipv6_addrs = filter(lambda x: x["version"] == 6,
234
                            public_addrs[0]["values"])
235
        self.assertEqual(len(ipv6_addrs), 1)
236
        return ipv6_addrs[0]["addr"]
237

    
238
    def _connect_loginname(self, os):
239
        """Return the login name for connections based on the server OS"""
240
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
241
            return "user"
242
        elif os in ("windows", "windows_alpha1"):
243
            return "Administrator"
244
        else:
245
            return "root"
246

    
247
    def _verify_server_status(self, current_status, new_status):
248
        """Verify a server has switched to a specified status"""
249
        server = self.client.get_server_details(self.serverid)
250
        if server["status"] not in (current_status, new_status):
251
            return None  # Do not raise exception, return so the test fails
252
        self.assertEquals(server["status"], new_status)
253

    
254
    def _get_connected_tcp_socket(self, family, host, port):
255
        """Get a connected socket from the specified family to host:port"""
256
        sock = None
257
        for res in \
258
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
259
                               socket.AI_PASSIVE):
260
            af, socktype, proto, canonname, sa = res
261
            try:
262
                sock = socket.socket(af, socktype, proto)
263
            except socket.error as msg:
264
                sock = None
265
                continue
266
            try:
267
                sock.connect(sa)
268
            except socket.error as msg:
269
                sock.close()
270
                sock = None
271
                continue
272
        self.assertIsNotNone(sock)
273
        return sock
274

    
275
    def _ping_once(self, ipv6, ip):
276
        """Test server responds to a single IPv4 or IPv6 ping"""
277
        cmd = "ping%s -c 2 -w 3 %s" % ("6" if ipv6 else "", ip)
278
        ping = subprocess.Popen(cmd, shell=True,
279
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
280
        (stdout, stderr) = ping.communicate()
281
        ret = ping.wait()
282
        self.assertEquals(ret, 0)
283

    
284
    def _get_hostname_over_ssh(self, hostip, username, password):
285
        ssh = paramiko.SSHClient()
286
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
287
        try:
288
            ssh.connect(hostip, username=username, password=password)
289
        except socket.error:
290
            raise AssertionError
291
        stdin, stdout, stderr = ssh.exec_command("hostname")
292
        lines = stdout.readlines()
293
        self.assertEqual(len(lines), 1)
294
        return lines[0]
295

    
296
    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
297
                                   opmsg, callable, *args, **kwargs):
298
        if warn_timeout == fail_timeout:
299
            warn_timeout = fail_timeout + 1
300
        warn_tmout = time.time() + warn_timeout
301
        fail_tmout = time.time() + fail_timeout
302
        while True:
303
            self.assertLess(time.time(), fail_tmout,
304
                            "operation `%s' timed out" % opmsg)
305
            if time.time() > warn_tmout:
306
                log.warning("Server %d: `%s' operation `%s' not done yet",
307
                            self.serverid, self.servername, opmsg)
308
            try:
309
                log.info("%s... " % opmsg)
310
                return callable(*args, **kwargs)
311
            except AssertionError:
312
                pass
313
            time.sleep(self.query_interval)
314

    
315
    def _insist_on_tcp_connection(self, family, host, port):
316
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
317
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
318
        msg = "connect over %s to %s:%s" % \
319
              (familystr.get(family, "Unknown"), host, port)
320
        sock = self._try_until_timeout_expires(
321
                self.action_timeout, self.action_timeout,
322
                msg, self._get_connected_tcp_socket,
323
                family, host, port)
324
        return sock
325

    
326
    def _insist_on_status_transition(self, current_status, new_status,
327
                                    fail_timeout, warn_timeout=None):
328
        msg = "Server %d: `%s', waiting for %s -> %s" % \
329
              (self.serverid, self.servername, current_status, new_status)
330
        if warn_timeout is None:
331
            warn_timeout = fail_timeout
332
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
333
                                        msg, self._verify_server_status,
334
                                        current_status, new_status)
335
        # Ensure the status is actually the expected one
336
        server = self.client.get_server_details(self.serverid)
337
        self.assertEquals(server["status"], new_status)
338

    
339
    def _insist_on_ssh_hostname(self, hostip, username, password):
340
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
341
        hostname = self._try_until_timeout_expires(
342
                self.action_timeout, self.action_timeout,
343
                msg, self._get_hostname_over_ssh,
344
                hostip, username, password)
345

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

    
349
    def _check_file_through_ssh(self, hostip, username, password, remotepath, content):
350
        msg = "Trying file injection through SSH to %s, as %s/%s" % (hostip, username, password)
351
        log.info(msg)
352
        try:
353
            ssh = paramiko.SSHClient()
354
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
355
            ssh.connect(hostip, username=username, password=password)
356
        except socket.error:
357
            raise AssertionError
358
        
359
        transport = paramiko.Transport((hostip,22))
360
        transport.connect(username = username, password = password)
361

    
362
        localpath = '/tmp/'+SNF_TEST_PREFIX+'injection'
363
        sftp = paramiko.SFTPClient.from_transport(transport)
364
        sftp.get(remotepath, localpath)
365
        
366
        sftp.close()
367
        transport.close()
368

    
369
        f = open(localpath)
370
        remote_content = b64encode(f.read())
371

    
372
        # Check if files are the same
373
        return (remote_content == content)
374

    
375
    def _skipIf(self, condition, msg):
376
        if condition:
377
            self.skipTest(msg)
378

    
379
    def test_001_submit_create_server(self):
380
        """Test submit create server request"""
381
        server = self.client.create_server(self.servername, self.flavorid,
382
                                           self.imageid, self.personality)
383

    
384
        self.assertEqual(server["name"], self.servername)
385
        self.assertEqual(server["flavorRef"], self.flavorid)
386
        self.assertEqual(server["imageRef"], self.imageid)
387
        self.assertEqual(server["status"], "BUILD")
388

    
389
        # Update class attributes to reflect data on building server
390
        cls = type(self)
391
        cls.serverid = server["id"]
392
        cls.username = None
393
        cls.passwd = server["adminPass"]
394

    
395
    def test_002a_server_is_building_in_list(self):
396
        """Test server is in BUILD state, in server list"""
397
        servers = self.client.list_servers(detail=True)
398
        servers = filter(lambda x: x["name"] == self.servername, servers)
399
        self.assertEqual(len(servers), 1)
400
        server = servers[0]
401
        self.assertEqual(server["name"], self.servername)
402
        self.assertEqual(server["flavorRef"], self.flavorid)
403
        self.assertEqual(server["imageRef"], self.imageid)
404
        self.assertEqual(server["status"], "BUILD")
405

    
406
    def test_002b_server_is_building_in_details(self):
407
        """Test server is in BUILD state, in details"""
408
        server = self.client.get_server_details(self.serverid)
409
        self.assertEqual(server["name"], self.servername)
410
        self.assertEqual(server["flavorRef"], self.flavorid)
411
        self.assertEqual(server["imageRef"], self.imageid)
412
        self.assertEqual(server["status"], "BUILD")
413

    
414
    def test_002c_set_server_metadata(self):
415
        image = self.client.get_image_details(self.imageid)
416
        os = image["metadata"]["values"]["os"]
417
        loginname = image["metadata"]["values"].get("users", None)
418
        self.client.update_server_metadata(self.serverid, OS=os)
419

    
420
        # Determine the username to use for future connections
421
        # to this host
422
        cls = type(self)
423
        cls.username = loginname
424
        if not cls.username:
425
            cls.username = self._connect_loginname(os)
426
        self.assertIsNotNone(cls.username)
427

    
428
    def test_002d_verify_server_metadata(self):
429
        """Test server metadata keys are set based on image metadata"""
430
        servermeta = self.client.get_server_metadata(self.serverid)
431
        imagemeta = self.client.get_image_metadata(self.imageid)
432
        self.assertEqual(servermeta["OS"], imagemeta["os"])
433

    
434
    def test_003_server_becomes_active(self):
435
        """Test server becomes ACTIVE"""
436
        self._insist_on_status_transition("BUILD", "ACTIVE",
437
                                         self.build_fail, self.build_warning)
438

    
439
    def test_003a_get_server_oob_console(self):
440
        """Test getting OOB server console over VNC
441

442
        Implementation of RFB protocol follows
443
        http://www.realvnc.com/docs/rfbproto.pdf.
444

445
        """
446
        
447
        console = self.cyclades.get_server_console(self.serverid)
448
        self.assertEquals(console['type'], "vnc")
449
        sock = self._insist_on_tcp_connection(socket.AF_UNSPEC,
450
                                        console["host"], console["port"])
451

    
452
        # Step 1. ProtocolVersion message (par. 6.1.1)
453
        version = sock.recv(1024)
454
        self.assertEquals(version, 'RFB 003.008\n')
455
        sock.send(version)
456

    
457
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
458
        sec = sock.recv(1024)
459
        self.assertEquals(list(sec), ['\x01', '\x02'])
460

    
461
        # Step 3. Request VNC Authentication (par 6.1.2)
462
        sock.send('\x02')
463

    
464
        # Step 4. Receive Challenge (par 6.2.2)
465
        challenge = sock.recv(1024)
466
        self.assertEquals(len(challenge), 16)
467

    
468
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
469
        response = d3des_generate_response(
470
            (console["password"] + '\0' * 8)[:8], challenge)
471
        sock.send(response)
472

    
473
        # Step 6. SecurityResult (par 6.1.3)
474
        result = sock.recv(4)
475
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
476
        sock.close()
477
        
478
    def test_004_server_has_ipv4(self):
479
        """Test active server has a valid IPv4 address"""
480
        server = self.client.get_server_details(self.serverid)
481
        ipv4 = self._get_ipv4(server)
482
        self.assertEquals(IP(ipv4).version(), 4)
483

    
484
    def test_005_server_has_ipv6(self):
485
        """Test active server has a valid IPv6 address"""
486
        server = self.client.get_server_details(self.serverid)
487
        ipv6 = self._get_ipv6(server)
488
        self.assertEquals(IP(ipv6).version(), 6)
489

    
490
    def test_006_server_responds_to_ping_IPv4(self):
491
        """Test server responds to ping on IPv4 address"""
492
        server = self.client.get_server_details(self.serverid)
493
        ip = self._get_ipv4(server)
494
        self._try_until_timeout_expires(self.action_timeout,
495
                                        self.action_timeout,
496
                                        "PING IPv4 to %s" % ip,
497
                                        self._ping_once,
498
                                        False, ip)
499

    
500
    def test_007_server_responds_to_ping_IPv6(self):
501
        """Test server responds to ping on IPv6 address"""
502
        server = self.client.get_server_details(self.serverid)
503
        ip = self._get_ipv6(server)
504
        self._try_until_timeout_expires(self.action_timeout,
505
                                        self.action_timeout,
506
                                        "PING IPv6 to %s" % ip,
507
                                        self._ping_once,
508
                                        True, ip)
509

    
510
    def test_008_submit_shutdown_request(self):
511
        """Test submit request to shutdown server"""
512
        self.cyclades.shutdown_server(self.serverid)
513

    
514
    def test_009_server_becomes_stopped(self):
515
        """Test server becomes STOPPED"""
516
        self._insist_on_status_transition("ACTIVE", "STOPPED",
517
                                         self.action_timeout,
518
                                         self.action_timeout)
519

    
520
    def test_010_submit_start_request(self):
521
        """Test submit start server request"""
522
        self.cyclades.start_server(self.serverid)
523

    
524
    def test_011_server_becomes_active(self):
525
        """Test server becomes ACTIVE again"""
526
        self._insist_on_status_transition("STOPPED", "ACTIVE",
527
                                         self.action_timeout,
528
                                         self.action_timeout)
529

    
530
    def test_011a_server_responds_to_ping_IPv4(self):
531
        """Test server OS is actually up and running again"""
532
        self.test_006_server_responds_to_ping_IPv4()
533

    
534
    def test_012_ssh_to_server_IPv4(self):
535
        """Test SSH to server public IPv4 works, verify hostname"""
536
        self._skipIf(self.is_windows, "only valid for Linux servers")
537
        server = self.client.get_server_details(self.serverid)
538
        self._insist_on_ssh_hostname(self._get_ipv4(server),
539
                                     self.username, self.passwd)
540

    
541
    def test_013_ssh_to_server_IPv6(self):
542
        """Test SSH to server public IPv6 works, verify hostname"""
543
        self._skipIf(self.is_windows, "only valid for Linux servers")
544
        server = self.client.get_server_details(self.serverid)
545
        self._insist_on_ssh_hostname(self._get_ipv6(server),
546
                                     self.username, self.passwd)
547

    
548
    def test_014_rdp_to_server_IPv4(self):
549
        "Test RDP connection to server public IPv4 works"""
550
        self._skipIf(not self.is_windows, "only valid for Windows servers")
551
        server = self.client.get_server_details(self.serverid)
552
        ipv4 = self._get_ipv4(server)
553
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
554

    
555
        # No actual RDP processing done. We assume the RDP server is there
556
        # if the connection to the RDP port is successful.
557
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
558
        sock.close()
559

    
560
    def test_015_rdp_to_server_IPv6(self):
561
        "Test RDP connection to server public IPv6 works"""
562
        self._skipIf(not self.is_windows, "only valid for Windows servers")
563
        server = self.client.get_server_details(self.serverid)
564
        ipv6 = self._get_ipv6(server)
565
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
566

    
567
        # No actual RDP processing done. We assume the RDP server is there
568
        # if the connection to the RDP port is successful.
569
        sock.close()
570

    
571
    def test_016_personality_is_enforced(self):
572
        """Test file injection for personality enforcement"""
573
        self._skipIf(self.is_windows, "only implemented for Linux servers")
574
        self._skipIf(self.personality == None, "No personality file selected")
575

    
576
        server = self.client.get_server_details(self.serverid)
577

    
578
        for inj_file in self.personality:
579
            equal_files = self._check_file_through_ssh(self._get_ipv4(server), inj_file['owner'], 
580
                                                       self.passwd, inj_file['path'], inj_file['contents'])
581
            self.assertTrue(equal_files)
582
        
583

    
584
    def test_017_submit_delete_request(self):
585
        """Test submit request to delete server"""
586
        self.client.delete_server(self.serverid)
587

    
588
    def test_018_server_becomes_deleted(self):
589
        """Test server becomes DELETED"""
590
        self._insist_on_status_transition("ACTIVE", "DELETED",
591
                                         self.action_timeout,
592
                                         self.action_timeout)
593

    
594
    def test_019_server_no_longer_in_server_list(self):
595
        """Test server is no longer in server list"""
596
        servers = self.client.list_servers()
597
        self.assertNotIn(self.serverid, [s["id"] for s in servers])
598

    
599

    
600
class NetworkTestCase(unittest.TestCase):
601
    """ Testing networking in cyclades """
602
  
603
    @classmethod
604
    def setUpClass(cls):
605
        "Initialize kamaki, get list of current networks"
606

    
607
        cls.client = CycladesClient(API, TOKEN)
608
        cls.compute = ComputeClient(API, TOKEN)
609

    
610
        images = cls.compute.list_images(detail = True)
611
        flavors = cls.compute.list_flavors(detail = True)
612

    
613
        cls.imageid = choice([im['id'] for im in images if not im['name'].lower().find("windows") >= 0])
614
        cls.flavorid = choice([f['id'] for f in flavors if f['disk'] >= 20])
615

    
616
        for image in images:
617
            if image['id'] == cls.imageid:
618
                imagename = image['name']
619

    
620
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
621

    
622
        #Dictionary initialization for the vms credentials
623
        cls.serverid = dict()
624
        cls.username = dict()
625
        cls.password = dict()
626

    
627
    def _get_ipv4(self, server):
628
    
629
        """Get the public IPv4 of a server from the detailed server info"""
630

    
631
        public_addrs = filter(lambda x: x["id"] == "public",
632
                              server["addresses"]["values"])
633
        self.assertEqual(len(public_addrs), 1)
634
        ipv4_addrs = filter(lambda x: x["version"] == 4,
635
                            public_addrs[0]["values"])
636
        self.assertEqual(len(ipv4_addrs), 1)
637
        return ipv4_addrs[0]["addr"]
638
    
639

    
640
    def _connect_loginname(self, os):
641
        """Return the login name for connections based on the server OS"""
642
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
643
            return "user"
644
        elif os in ("windows", "windows_alpha1"):
645
            return "Administrator"
646
        else:
647
            return "root"
648

    
649
    def _ping_once(self, ip):
650
    
651
        """Test server responds to a single IPv4 or IPv6 ping"""
652
        cmd = "ping -c 2 -w 3 %s" % (ip)
653
        ping = subprocess.Popen(cmd, shell=True,
654
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
655
        (stdout, stderr) = ping.communicate()
656
        ret = ping.wait()
657
        
658
        return (ret == 0)
659

    
660

    
661
    def test_00001a_submit_create_server_A(self):
662
        """Test submit create server request"""
663
        serverA = self.client.create_server(self.servername, self.flavorid,
664
                                           self.imageid, personality=None)
665

    
666
        self.assertEqual(serverA["name"], self.servername)
667
        self.assertEqual(serverA["flavorRef"], self.flavorid)
668
        self.assertEqual(serverA["imageRef"], self.imageid)
669
        self.assertEqual(serverA["status"], "BUILD")
670

    
671
        # Update class attributes to reflect data on building server
672
        self.serverid['A'] = serverA["id"]
673
        self.username['A'] = None
674
        self.password['A'] = serverA["adminPass"]
675

    
676

    
677
    def test_00001b_serverA_becomes_active(self):
678
        """Test server becomes ACTIVE"""
679

    
680
        fail_tmout = time.time()+self.action_timeout
681
        while True:
682
            d = self.client.get_server_details(self.serverid['A'])
683
            status = d['status']
684
            if status == 'ACTIVE':
685
                active = True
686
                break
687
            elif time.time() > fail_tmout:
688
                self.assertLess(time.time(), fail_tmout)
689
            else:
690
                time.sleep(self.query_interval)
691

    
692
        self.assertTrue(active)
693

    
694
        
695
    
696
    def test_00002a_submit_create_server_B(self):
697
        """Test submit create server request"""
698
        serverB = self.client.create_server(self.servername, self.flavorid,
699
                                           self.imageid, personality=None)
700

    
701
        self.assertEqual(serverB["name"], self.servername)
702
        self.assertEqual(serverB["flavorRef"], self.flavorid)
703
        self.assertEqual(serverB["imageRef"], self.imageid)
704
        self.assertEqual(serverB["status"], "BUILD")
705

    
706
        # Update class attributes to reflect data on building server
707
        self.serverid['B'] = serverB["id"]
708
        self.username['B'] = None
709
        self.password['B'] = serverB["adminPass"]
710

    
711

    
712
    def test_00002b_serverB_becomes_active(self):
713
        """Test server becomes ACTIVE"""
714

    
715
        fail_tmout = time.time()+self.action_timeout
716
        while True:
717
            d = self.client.get_server_details(self.serverid['B'])
718
            status = d['status']
719
            if status == 'ACTIVE':
720
                active = True
721
                break
722
            elif time.time() > fail_tmout:
723
                self.assertLess(time.time(), fail_tmout)
724
            else:
725
                time.sleep(self.query_interval)
726

    
727
        self.assertTrue(active)
728

    
729

    
730
    def test_001_create_network(self):
731
        """Test submit create network request"""
732
        name = SNF_TEST_PREFIX+TEST_RUN_ID
733
        previous_num = len(self.client.list_networks())
734
        network =  self.client.create_network(name)        
735
       
736
        #Test if right name is assigned
737
        self.assertEqual(network['name'], name)
738
        
739
        # Update class attributes
740
        cls = type(self)
741
        cls.networkid = network['id']
742
        networks = self.client.list_networks()
743

    
744
        #Test if new network is created
745
        self.assertTrue(len(networks) > previous_num)
746
        
747
    
748
    def test_002_connect_to_network(self):
749
        """Test connect VMs to network"""
750

    
751
        self.client.connect_server(self.serverid['A'], self.networkid)
752
        self.client.connect_server(self.serverid['B'], self.networkid)
753
                
754
        #Insist on connecting until action timeout
755
        fail_tmout = time.time()+self.action_timeout
756

    
757
        while True:
758
            connected = (self.client.get_network_details(self.networkid))
759
            connections = connected['servers']['values']
760
            if (self.serverid['A'] in connections) and (self.serverid['B'] in connections):
761
                conn_exists = True
762
                break
763
            elif time.time() > fail_tmout:
764
                self.assertLess(time.time(), fail_tmout)
765
            else:
766
                time.sleep(self.query_interval)
767

    
768
        self.assertTrue(conn_exists)
769

    
770
    
771
    def test_002a_reboot(self):
772
        "Reboot server A"
773
        self.client.reboot_server(self.serverid['A']) 
774
       
775
        fail_tmout = time.time()+self.action_timeout
776
        while True:
777
            d = self.client.get_server_details(self.serverid['A'])
778
            status = d['status']
779
            if status == 'ACTIVE':
780
                active = True
781
                break
782
            elif time.time() > fail_tmout:
783
                self.assertLess(time.time(), fail_tmout)
784
            else:
785
                time.sleep(self.query_interval)
786
               
787
        self.assertTrue(active)
788

    
789

    
790
    def test_002b_ping_server_A(self):
791
        "Test if server A is pingable"
792

    
793
        server = self.client.get_server_details(self.serverid['A'])
794
        ip = self._get_ipv4(server)
795
        
796
        fail_tmout = time.time()+self.action_timeout
797
        
798
        s = False
799

    
800
        while True:
801

    
802
            if self._ping_once(ip):
803
                s = True
804
                break
805

    
806
            elif time.time() > fail_tmout:
807
                self.assertLess(time.time(), fail_tmout)
808

    
809
            else:
810
                time.sleep(self.query_interval)
811

    
812
        self.assertTrue(s)
813

    
814

    
815
    def test_002c_reboot(self):
816
        "Reboot server B"
817
        self.client.reboot_server(self.serverid['B']) 
818
        
819
        fail_tmout = time.time()+self.action_timeout
820
        while True:
821
            d = self.client.get_server_details(self.serverid['B'])
822
            status = d['status']
823
            if status == 'ACTIVE':
824
                active = True
825
                break
826
            elif time.time() > fail_tmout:
827
                self.assertLess(time.time(), fail_tmout)
828
            else:
829
                time.sleep(self.query_interval)
830
                
831
        self.assertTrue(active)
832
        
833

    
834
    def test_002d_ping_server_B(self):
835
        "Test if server B is pingable"
836

    
837
        server = self.client.get_server_details(self.serverid['B'])
838
        ip = self._get_ipv4(server)
839
        
840
        fail_tmout = time.time()+self.action_timeout
841
        
842
        s = False
843

    
844
        while True:
845

    
846
            if self._ping_once(ip):
847
                s = True
848
                break
849

    
850
            elif time.time() > fail_tmout:
851
                self.assertLess(time.time(), fail_tmout)
852

    
853
            else:
854
                time.sleep(self.query_interval)
855

    
856
        self.assertTrue(s)
857

    
858

    
859
    def test_003a_setup_interface_A(self):
860
        "Set up eth1 for server A"
861
        
862
        server = self.client.get_server_details(self.serverid['A'])
863
        image = self.client.get_image_details(self.imageid)
864
        os = image['metadata']['values']['os']
865
        
866
        users = image["metadata"]["values"].get("users", None)
867
        userlist = users.split()
868

    
869
        if "root" in userlist:
870
            loginname = "root"
871
        elif users == None:
872
            loginname = self._connect_loginname(os)
873
        else:
874
            loginname = choice(userlist)
875

    
876
        hostip = self._get_ipv4(server) 
877
        myPass = self.password['A']
878

    
879
        ssh = paramiko.SSHClient()
880
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
881

    
882
        try:
883

    
884
            log.info("Username: " + loginname) 
885
            log.info("Password " + myPass)
886
            ssh.connect(hostip, username = loginname, password = myPass)
887

    
888
        except socket.error:
889
            raise AssertionError
890
        
891
        res = False
892

    
893
        if loginname != "root":
894
            with settings(
895
                hide('warnings', 'running', 'stdout', 'stderr'),
896
                warn_only=True, 
897
                host_string = hostip, 
898
                user = loginname, password = myPass
899
                ):
900

    
901
                if run('ifconfig eth1 192.168.0.12') : 
902
                    res = True
903
            
904
        else :
905
            stdin, stdout, stderr = ssh.exec_command("ifconfig eth1 %s up"%("192.168.0.12"))
906
            lines = stdout.readlines()
907

    
908
            if len(lines)==0:
909
                res = True
910

    
911
        self.assertTrue(res)
912

    
913

    
914
    def test_003b_setup_interface_B(self):
915
        "Setup eth1 for server B"
916

    
917
        server = self.client.get_server_details(self.serverid['B'])
918
        image = self.client.get_image_details(self.imageid)
919
        os = image['metadata']['values']['os']
920
        
921
        users = image["metadata"]["values"].get("users", None)
922
        userlist = users.split()
923

    
924
        if "root" in userlist:
925
            loginname = "root"
926
        elif users == None:
927
            loginname = self._connect_loginname(os)
928
        else:
929
            loginname = choice(userlist)
930

    
931
        hostip = self._get_ipv4(server)
932
        myPass = self.password['B']
933

    
934
        ssh = paramiko.SSHClient()
935
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
936

    
937
        try:
938
            log.info("Username: " + loginname)
939
            log.info("Password " + myPass)
940
            ssh.connect(hostip, username = loginname, password = myPass)
941
        except socket.error:
942
            raise AssertionError
943

    
944
        res = False
945

    
946
        if loginname != "root":
947
            with settings(
948
                hide('warnings', 'running', 'stdout', 'stderr'),
949
                warn_only=True, 
950
                host_string = hostip, 
951
                user = loginname, password = myPass
952
                ):
953
                
954
                if run('ifconfig eth1 192.168.0.13'):
955
                    res = True
956
            
957
        else :
958
            stdin, stdout, stderr = ssh.exec_command("ifconfig eth1 %s up"%("192.168.0.13"))
959
            lines = stdout.readlines()
960
            
961
            if len(lines) == 0:
962
                res = True
963

    
964
        self.assertTrue(res)
965
            
966

    
967
    def test_003c_test_connection_exists(self):
968
        """Ping server B from server A to test if connection exists"""
969

    
970
        server = self.client.get_server_details(self.serverid['A'])
971
        image = self.client.get_image_details(self.imageid)
972
        os = image['metadata']['values']['os']
973
        hostip = self._get_ipv4(server)
974

    
975
        users = image["metadata"]["values"].get("users", None)
976
        userlist = users.split()
977

    
978
        if "root" in userlist:
979
            loginname = "root"
980
        elif users == None:
981
            loginname = self._connect_loginname(os)
982
        else:
983
            loginname = choice(userlist)
984

    
985
        myPass = self.password['A']
986

    
987
        try:
988
            ssh = paramiko.SSHClient()
989
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
990
            ssh.connect(hostip, username = loginname, password = myPass)
991
        except socket.error:
992
            raise AssertionError
993

    
994
        cmd = "if ping -c 2 -w 3 192.168.0.13 >/dev/null; then echo \'True\'; fi;"
995
        stdin, stdout, stderr = ssh.exec_command(cmd)
996
        lines = stdout.readlines()
997

    
998
        exists = False
999

    
1000
        if 'True\n' in lines:
1001
            exists = True
1002

    
1003
        self.assertTrue(exists)
1004

    
1005

    
1006
    def test_004_disconnect_from_network(self):
1007
        "Disconnecting server A and B from network"
1008

    
1009
        prev_state = self.client.get_network_details(self.networkid)
1010
        prev_conn = len(prev_state['servers']['values'])
1011

    
1012
        self.client.disconnect_server(self.serverid['A'], self.networkid)
1013
        self.client.disconnect_server(self.serverid['B'], self.networkid)
1014

    
1015
        #Insist on deleting until action timeout
1016
        fail_tmout = time.time()+self.action_timeout
1017

    
1018
        while True:
1019
            connected = (self.client.get_network_details(self.networkid))
1020
            connections = connected['servers']['values']
1021
            if (self.serverid['A'] not in connections) and (self.serverid['B'] not in connections):
1022
                conn_exists = False
1023
                break
1024
            elif time.time() > fail_tmout:
1025
                self.assertLess(time.time(), fail_tmout)
1026
            else:
1027
                time.sleep(self.query_interval)
1028

    
1029
        self.assertFalse(conn_exists)
1030

    
1031
    def test_005_destroy_network(self):
1032
        """Test submit delete network request"""
1033
        self.client.delete_network(self.networkid)        
1034
        networks = self.client.list_networks()
1035

    
1036
        curr_net = []
1037
        for net in networks:
1038
            curr_net.append(net['id'])
1039

    
1040
        self.assertTrue(self.networkid not in curr_net)
1041
        
1042
    def test_006_cleanup_servers(self):
1043
        """Cleanup servers created for this test"""
1044
        self.compute.delete_server(self.serverid['A'])
1045
        self.compute.delete_server(self.serverid['B'])
1046

    
1047
        fail_tmout = time.time()+self.action_timeout
1048

    
1049
        #Ensure server gets deleted
1050
        status = dict() 
1051

    
1052
        while True:
1053
            details = self.compute.get_server_details(self.serverid['A'])
1054
            status['A'] = details['status']
1055
            details = self.compute.get_server_details(self.serverid['B'])
1056
            status['B'] = details['status']
1057
            if (status['A'] == 'DELETED') and (status['B'] == 'DELETED'):
1058
                deleted = True
1059
                break
1060
            elif time.time() > fail_tmout: 
1061
                self.assertLess(time.time(), fail_tmout)
1062
            else:
1063
                time.sleep(self.query_interval)
1064
                
1065
        self.assertTrue(deleted)
1066

    
1067

    
1068
class TestRunnerProcess(Process):
1069
    """A distinct process used to execute part of the tests in parallel"""
1070
    def __init__(self, **kw):
1071
        Process.__init__(self, **kw)
1072
        kwargs = kw["kwargs"]
1073
        self.testq = kwargs["testq"]
1074
        self.runner = kwargs["runner"]
1075

    
1076
    def run(self):
1077
        # Make sure this test runner process dies with the parent
1078
        # and is not left behind.
1079
        #
1080
        # WARNING: This uses the prctl(2) call and is
1081
        # Linux-specific.
1082
        prctl.set_pdeathsig(signal.SIGHUP)
1083

    
1084
        while True:
1085
            log.debug("I am process %d, GETting from queue is %s",
1086
                     os.getpid(), self.testq)
1087
            msg = self.testq.get()
1088
            log.debug("Dequeued msg: %s", msg)
1089

    
1090
            if msg == "TEST_RUNNER_TERMINATE":
1091
                raise SystemExit
1092
            elif issubclass(msg, unittest.TestCase):
1093
                # Assemble a TestSuite, and run it
1094
                suite = unittest.TestLoader().loadTestsFromTestCase(msg)
1095
                self.runner.run(suite)
1096
            else:
1097
                raise Exception("Cannot handle msg: %s" % msg)
1098

    
1099

    
1100

    
1101
def _run_cases_in_parallel(cases, fanout=1, runner=None):
1102
    """Run instances of TestCase in parallel, in a number of distinct processes
1103

1104
    The cases iterable specifies the TestCases to be executed in parallel,
1105
    by test runners running in distinct processes.
1106
    The fanout parameter specifies the number of processes to spawn,
1107
    and defaults to 1.
1108
    The runner argument specifies the test runner class to use inside each
1109
    runner process.
1110

1111
    """
1112
    if runner is None:
1113
        runner = unittest.TextTestRunner(verbosity=2, failfast=True)
1114

    
1115
    # testq: The master process enqueues TestCase objects into this queue,
1116
    #        test runner processes pick them up for execution, in parallel.
1117
    testq = Queue()
1118
    runners = []
1119
    for i in xrange(0, fanout):
1120
        kwargs = dict(testq=testq, runner=runner)
1121
        runners.append(TestRunnerProcess(kwargs=kwargs))
1122

    
1123
    log.info("Spawning %d test runner processes", len(runners))
1124
    for p in runners:
1125
        p.start()
1126
    log.debug("Spawned %d test runners, PIDs are %s",
1127
              len(runners), [p.pid for p in runners])
1128

    
1129
    # Enqueue test cases
1130
    map(testq.put, cases)
1131
    map(testq.put, ["TEST_RUNNER_TERMINATE"] * len(runners))
1132

    
1133
    log.debug("Joining %d processes", len(runners))
1134
    for p in runners:
1135
        p.join()
1136
    log.debug("Done joining %d processes", len(runners))
1137

    
1138

    
1139
def _spawn_server_test_case(**kwargs):
1140
    """Construct a new unit test case class from SpawnServerTestCase"""
1141

    
1142
    name = "SpawnServerTestCase_%s" % kwargs["imageid"]
1143
    cls = type(name, (SpawnServerTestCase,), kwargs)
1144

    
1145
    # Patch extra parameters into test names by manipulating method docstrings
1146
    for (mname, m) in \
1147
        inspect.getmembers(cls, lambda x: inspect.ismethod(x)):
1148
            if hasattr(m, __doc__):
1149
                m.__func__.__doc__ = "[%s] %s" % (imagename, m.__doc__)
1150

    
1151
    # Make sure the class can be pickled, by listing it among
1152
    # the attributes of __main__. A PicklingError is raised otherwise.
1153
    setattr(__main__, name, cls)
1154
    return cls 
1155

    
1156
def _spawn_network_test_case(**kwargs):
1157
    """Construct a new unit test case class from NetworkTestCase"""
1158

    
1159
    name = "NetworkTestCase"+TEST_RUN_ID
1160
    cls = type(name, (NetworkTestCase,), kwargs)
1161

    
1162
    # Make sure the class can be pickled, by listing it among
1163
    # the attributes of __main__. A PicklingError is raised otherwise.
1164
    setattr(__main__, name, cls)
1165
    return cls 
1166

    
1167

    
1168
def cleanup_servers(delete_stale=False):
1169

    
1170
    c = ComputeClient(API, TOKEN)
1171

    
1172
    servers = c.list_servers()
1173
    stale = [s for s in servers if s["name"].startswith(SNF_TEST_PREFIX)]
1174

    
1175
    if len(stale) == 0:
1176
        return
1177

    
1178
    print >> sys.stderr, "Found these stale servers from previous runs:"
1179
    print "    " + \
1180
          "\n    ".join(["%d: %s" % (s["id"], s["name"]) for s in stale])
1181

    
1182
    if delete_stale:
1183
        print >> sys.stderr, "Deleting %d stale servers:" % len(stale)
1184
        for server in stale:
1185
            c.delete_server(server["id"])
1186
        print >> sys.stderr, "    ...done"
1187
    else:
1188
        print >> sys.stderr, "Use --delete-stale to delete them."
1189

    
1190

    
1191
def parse_arguments(args):
1192
    from optparse import OptionParser
1193

    
1194
    kw = {}
1195
    kw["usage"] = "%prog [options]"
1196
    kw["description"] = \
1197
        "%prog runs a number of test scenarios on a " \
1198
        "Synnefo deployment."
1199

    
1200
    parser = OptionParser(**kw)
1201
    parser.disable_interspersed_args()
1202
    parser.add_option("--api",
1203
                      action="store", type="string", dest="api",
1204
                      help="The API URI to use to reach the Synnefo API",
1205
                      default=DEFAULT_API)
1206
    parser.add_option("--token",
1207
                      action="store", type="string", dest="token",
1208
                      help="The token to use for authentication to the API")
1209
    parser.add_option("--nofailfast",
1210
                      action="store_true", dest="nofailfast",
1211
                      help="Do not fail immediately if one of the tests " \
1212
                           "fails (EXPERIMENTAL)",
1213
                      default=False)
1214
    parser.add_option("--action-timeout",
1215
                      action="store", type="int", dest="action_timeout",
1216
                      metavar="TIMEOUT",
1217
                      help="Wait SECONDS seconds for a server action to " \
1218
                           "complete, then the test is considered failed",
1219
                      default=100)
1220
    parser.add_option("--build-warning",
1221
                      action="store", type="int", dest="build_warning",
1222
                      metavar="TIMEOUT",
1223
                      help="Warn if TIMEOUT seconds have passed and a " \
1224
                           "build operation is still pending",
1225
                      default=600)
1226
    parser.add_option("--build-fail",
1227
                      action="store", type="int", dest="build_fail",
1228
                      metavar="BUILD_TIMEOUT",
1229
                      help="Fail the test if TIMEOUT seconds have passed " \
1230
                           "and a build operation is still incomplete",
1231
                      default=900)
1232
    parser.add_option("--query-interval",
1233
                      action="store", type="int", dest="query_interval",
1234
                      metavar="INTERVAL",
1235
                      help="Query server status when requests are pending " \
1236
                           "every INTERVAL seconds",
1237
                      default=3)
1238
    parser.add_option("--fanout",
1239
                      action="store", type="int", dest="fanout",
1240
                      metavar="COUNT",
1241
                      help="Spawn up to COUNT child processes to execute " \
1242
                           "in parallel, essentially have up to COUNT " \
1243
                           "server build requests outstanding (EXPERIMENTAL)",
1244
                      default=1)
1245
    parser.add_option("--force-flavor",
1246
                      action="store", type="int", dest="force_flavorid",
1247
                      metavar="FLAVOR ID",
1248
                      help="Force all server creations to use the specified "\
1249
                           "FLAVOR ID instead of a randomly chosen one, " \
1250
                           "useful if disk space is scarce",
1251
                      default=None)
1252
    parser.add_option("--image-id",
1253
                      action="store", type="string", dest="force_imageid",
1254
                      metavar="IMAGE ID",
1255
                      help="Test the specified image id, use 'all' to test " \
1256
                           "all available images (mandatory argument)",
1257
                      default=None)
1258
    parser.add_option("--show-stale",
1259
                      action="store_true", dest="show_stale",
1260
                      help="Show stale servers from previous runs, whose "\
1261
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1262
                      default=False)
1263
    parser.add_option("--delete-stale",
1264
                      action="store_true", dest="delete_stale",
1265
                      help="Delete stale servers from previous runs, whose "\
1266
                           "name starts with `%s'" % SNF_TEST_PREFIX,
1267
                      default=False)
1268
    parser.add_option("--force-personality",
1269
                      action="store", type="string", dest="personality_path",
1270
                      help="Force a personality file injection. File path required. ",
1271
                      default=None)
1272
    
1273

    
1274
    # FIXME: Change the default for build-fanout to 10
1275
    # FIXME: Allow the user to specify a specific set of Images to test
1276

    
1277
    (opts, args) = parser.parse_args(args)
1278

    
1279
    # Verify arguments
1280
    if opts.delete_stale:
1281
        opts.show_stale = True
1282

    
1283
    if not opts.show_stale:
1284
        if not opts.force_imageid:
1285
            print >>sys.stderr, "The --image-id argument is mandatory."
1286
            parser.print_help()
1287
            sys.exit(1)
1288

    
1289
        if opts.force_imageid != 'all':
1290
            try:
1291
                opts.force_imageid = str(opts.force_imageid)
1292
            except ValueError:
1293
                print >>sys.stderr, "Invalid value specified for --image-id." \
1294
                                    "Use a valid id, or `all'."
1295
                sys.exit(1)
1296

    
1297
    return (opts, args)
1298

    
1299

    
1300
def main():
1301
    """Assemble test cases into a test suite, and run it
1302

1303
    IMPORTANT: Tests have dependencies and have to be run in the specified
1304
    order inside a single test case. They communicate through attributes of the
1305
    corresponding TestCase class (shared fixtures). Distinct subclasses of
1306
    TestCase MAY SHARE NO DATA, since they are run in parallel, in distinct
1307
    test runner processes.
1308

1309
    """
1310
    (opts, args) = parse_arguments(sys.argv[1:])
1311

    
1312
    global API, TOKEN
1313
    API = opts.api
1314
    TOKEN = opts.token
1315

    
1316
    # Cleanup stale servers from previous runs
1317
    if opts.show_stale:
1318
        cleanup_servers(delete_stale=opts.delete_stale)
1319
        return 0
1320

    
1321
    # Initialize a kamaki instance, get flavors, images
1322

    
1323
    c = ComputeClient(API, TOKEN)
1324

    
1325
    DIMAGES = c.list_images(detail=True)
1326
    DFLAVORS = c.list_flavors(detail=True)
1327

    
1328
    # FIXME: logging, log, LOG PID, TEST_RUN_ID, arguments
1329
    # Run them: FIXME: In parallel, FAILEARLY, catchbreak?
1330
    #unittest.main(verbosity=2, catchbreak=True)
1331

    
1332
    if opts.force_imageid == 'all':
1333
        test_images = DIMAGES
1334
    else:
1335
        test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1336

    
1337
    for image in test_images:
1338
        imageid = str(image["id"])
1339
        flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
1340
        imagename = image["name"]
1341
        
1342
        #Personality dictionary for file injection test
1343
        if opts.personality_path != None:
1344
            f = open(opts.personality_path)
1345
            content = b64encode(f.read())
1346
            personality = []
1347
            st = os.stat(opts.personality_path)
1348
            personality.append({
1349
                    'path': '/root/test_inj_file',
1350
                    'owner': 'root',
1351
                    'group': 'root',
1352
                    'mode': 0x7777 & st.st_mode,
1353
                    'contents': content
1354
                    })
1355
        else:
1356
            personality = None
1357

    
1358
        servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
1359
        is_windows = imagename.lower().find("windows") >= 0
1360
        
1361
    ServerTestCase = _spawn_server_test_case(imageid=imageid, flavorid=flavorid,
1362
                                             imagename=imagename,
1363
                                             personality=personality,
1364
                                             servername=servername,
1365
                                             is_windows=is_windows,
1366
                                             action_timeout=opts.action_timeout,
1367
                                             build_warning=opts.build_warning,
1368
                                             build_fail=opts.build_fail,
1369
                                             query_interval=opts.query_interval,
1370
                                             )
1371

    
1372

    
1373
    #Running all the testcases sequentially
1374
    
1375
    #To run all cases
1376
    #seq_cases = [UnauthorizedTestCase, FlavorsTestCase, ImagesTestCase, ServerTestCase, NetworkTestCase]
1377
    
1378
    newNetworkTestCase = _spawn_network_test_case(action_timeout = opts.action_timeout,
1379
                                                  query_interval = opts.query_interval)    
1380
    seq_cases = [ServerTestCase]
1381

    
1382
    for case in seq_cases:
1383
        suite = unittest.TestLoader().loadTestsFromTestCase(case)
1384
        unittest.TextTestRunner(verbosity=2).run(suite)
1385
        
1386
    
1387

    
1388
    # # The Following cases run sequentially
1389
    # seq_cases = [UnauthorizedTestCase, FlavorsTestCase, ImagesTestCase]
1390
    # _run_cases_in_parallel(seq_cases, fanout=3, runner=runner)
1391

    
1392
    # # The following cases run in parallel
1393
    # par_cases = []
1394

    
1395
    # if opts.force_imageid == 'all':
1396
    #     test_images = DIMAGES
1397
    # else:
1398
    #     test_images = filter(lambda x: x["id"] == opts.force_imageid, DIMAGES)
1399

    
1400
    # for image in test_images:
1401
    #     imageid = image["id"]
1402
    #     imagename = image["name"]
1403
    #     if opts.force_flavorid:
1404
    #         flavorid = opts.force_flavorid
1405
    #     else:
1406
    #         flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20])
1407
    #     personality = None   # FIXME
1408
    #     servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename)
1409
    #     is_windows = imagename.lower().find("windows") >= 0
1410
    #     case = _spawn_server_test_case(imageid=str(imageid), flavorid=flavorid,
1411
    #                                    imagename=imagename,
1412
    #                                    personality=personality,
1413
    #                                    servername=servername,
1414
    #                                    is_windows=is_windows,
1415
    #                                    action_timeout=opts.action_timeout,
1416
    #                                    build_warning=opts.build_warning,
1417
    #                                    build_fail=opts.build_fail,
1418
    #                                    query_interval=opts.query_interval)
1419
    #     par_cases.append(case)
1420

    
1421
    # _run_cases_in_parallel(par_cases, fanout=opts.fanout, runner=runner)
1422

    
1423
if __name__ == "__main__":
1424
    sys.exit(main())