burnin.py 64.6 KB
Newer Older
1
#!/usr/bin/env python
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

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

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

38
import __main__
39
40
41
42
43
import datetime
import inspect
import logging
import os
import paramiko
44
import prctl
45
import subprocess
46
import signal
47
48
49
import socket
import sys
import time
50
from base64 import b64encode
51
from IPy import IP
52
from multiprocessing import Process, Queue
53
from random import choice
John Giannelos's avatar
John Giannelos committed
54
from optparse import OptionParser, OptionValueError
55

John Giannelos's avatar
John Giannelos committed
56
57
from kamaki.clients.compute import ComputeClient
from kamaki.clients.cyclades import CycladesClient
58
from kamaki.clients.image import ImageClient
John Giannelos's avatar
John Giannelos committed
59
from kamaki.clients import ClientError
John Giannelos's avatar
John Giannelos committed
60

John Giannelos's avatar
John Giannelos committed
61
from fabric.api import *
62

63
from vncauthproxy.d3des import generate_response as d3des_generate_response
64
65
66
67
68

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


74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class BurninTestResult(unittest.TextTestResult):
    def addSuccess(self, test):
        super(BurninTestResult, self).addSuccess(test)
        if self.showAll:
            if test.result_dict:
                run_details = test.result_dict

                self.stream.write("\n")
                for i in run_details:
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
                self.stream.write("\n")

        elif self.dots:
            self.stream.write('.')
            self.stream.flush() 
            
    def addError(self, test, err):
        super(BurninTestResult, self).addError(test, err)
        if self.showAll:
            self.stream.writeln("ERROR")

            run_details = test.result_dict

            self.stream.write("\n")
            for i in run_details:
                self.stream.write("%s : %s \n" % (i, run_details[i]))
            self.stream.write("\n")

        elif self.dots:
            self.stream.write('E')
            self.stream.flush()

    def addFailure(self, test, err):
        super(BurninTestResult, self).addFailure(test, err)
        if self.showAll:
            self.stream.writeln("FAIL")

            run_details = test.result_dict

            self.stream.write("\n")
            for i in run_details:
                self.stream.write("%s : %s \n" % (i, run_details[i]))
            self.stream.write("\n")

        elif self.dots:
            self.stream.write('F')
            self.stream.flush()



124
125
API = None
TOKEN = None
John Giannelos's avatar
John Giannelos committed
126
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
127
128
DEFAULT_PLANKTON = "https://cyclades.okeanos.grnet.gr/plankton"
DEFAULT_PLANKTON_USER = "images@okeanos.grnet.gr"
129

130
# A unique id identifying this test run
131
132
133
TEST_RUN_ID = datetime.datetime.strftime(datetime.datetime.now(),
                                         "%Y%m%d%H%M%S")
SNF_TEST_PREFIX = "snf-test-"
134

John Giannelos's avatar
John Giannelos committed
135
136
137
138
139
140
141
142
143
red = '\x1b[31m'
yellow = '\x1b[33m'
green = '\x1b[32m'
normal = '\x1b[0m'


class burninFormatter(logging.Formatter):

    err_fmt = red + "ERROR: %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
144
    dbg_fmt = green + "* %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    info_fmt = "%(msg)s"

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

    def format(self, record):

        format_orig = self._fmt

        # Replace the original format with one customized by logging level
        if record.levelno == 10:    # DEBUG
            self._fmt = burninFormatter.dbg_fmt

        elif record.levelno == 20:  # INFO
            self._fmt = burninFormatter.info_fmt

        elif record.levelno == 40:  # ERROR
            self._fmt = burninFormatter.err_fmt

        result = logging.Formatter.format(self, record)
        self._fmt = format_orig

        return result


170
log = logging.getLogger("burnin")
John Giannelos's avatar
John Giannelos committed
171
172
173
174
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(burninFormatter())
log.addHandler(handler)
175

176

177
class UnauthorizedTestCase(unittest.TestCase):
178
179
180
181
182

    @classmethod
    def setUpClass(cls):
        cls.result_dict = dict()

183
184
    def test_unauthorized_access(self):
        """Test access without a valid token fails"""
185
186
        log.info("Authentication test")

187
        falseToken = '12345'
188
        c = ComputeClient(API, falseToken)
189

190
191
        with self.assertRaises(ClientError) as cm:
            c.list_servers()
John Giannelos's avatar
John Giannelos committed
192
            self.assertEqual(cm.exception.status, 401)
193
194
195
196
197
198
199
200


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

John Giannelos's avatar
John Giannelos committed
202
        cls.client = ComputeClient(API, TOKEN)
203
204
205
        cls.plankton = ImageClient(PLANKTON, TOKEN)
        cls.images = cls.plankton.list_public()
        cls.dimages = cls.plankton.list_public(detail=True)
206
        cls.result_dict = dict()
207
208
209

    def test_001_list_images(self):
        """Test image list actually returns images"""
John Giannelos's avatar
John Giannelos committed
210
        self.assertGreater(len(self.images), 0)
211

212
213
    def test_002_list_images_detailed(self):
        """Test detailed image list is the same length as list"""
John Giannelos's avatar
John Giannelos committed
214
        self.assertEqual(len(self.dimages), len(self.images))
215

216
217
218
219
220
221
222
    def test_003_same_image_names(self):
        """Test detailed and simple image list contain same names"""
        names = sorted(map(lambda x: x["name"], self.images))
        dnames = sorted(map(lambda x: x["name"], self.dimages))
        self.assertEqual(names, dnames)

    def test_004_unique_image_names(self):
223
224
225
226
        """Test system images have unique names"""
        sys_images = filter(lambda x: x['owner'] == PLANKTON_USER,
                            self.dimages)
        names = sorted(map(lambda x: x["name"], sys_images))
227
228
229
230
        self.assertEqual(sorted(list(set(names))), names)

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
231
        keys = frozenset(["osfamily", "root_partition"])
John Giannelos's avatar
John Giannelos committed
232
233
        details = self.client.list_images(detail=True)
        for i in details:
234
235
236
237
238
239
240
241
242
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))


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

John Giannelos's avatar
John Giannelos committed
244
        cls.client = ComputeClient(API, TOKEN)
245
246
        cls.flavors = cls.client.list_flavors()
        cls.dflavors = cls.client.list_flavors(detail=True)
247
        cls.result_dict = dict()
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285

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

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

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

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

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

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

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


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

John Giannelos's avatar
John Giannelos committed
287
        cls.client = ComputeClient(API, TOKEN)
288
289
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)
290
        cls.result_dict = dict()
291

292
293
294
    # def test_001_list_servers(self):
    #     """Test server list actually returns servers"""
    #     self.assertGreater(len(self.servers), 0)
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313

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

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


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

    @classmethod
    def setUpClass(cls):
        """Initialize a kamaki instance"""
314
        log.info("Spawning server for image `%s'" %cls.imagename)
John Giannelos's avatar
John Giannelos committed
315
316
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
317
        cls.result_dict = dict()
318
319

    def _get_ipv4(self, server):
320
        """Get the public IPv4 of a server from the detailed server info"""
321

322
323
324
325
326
327
328
329
330
        public_addrs = filter(lambda x: x["id"] == "public",
                              server["addresses"]["values"])
        self.assertEqual(len(public_addrs), 1)
        ipv4_addrs = filter(lambda x: x["version"] == 4,
                            public_addrs[0]["values"])
        self.assertEqual(len(ipv4_addrs), 1)
        return ipv4_addrs[0]["addr"]

    def _get_ipv6(self, server):
331
        """Get the public IPv6 of a server from the detailed server info"""
332
333
334
335
336
337
338
339
        public_addrs = filter(lambda x: x["id"] == "public",
                              server["addresses"]["values"])
        self.assertEqual(len(public_addrs), 1)
        ipv6_addrs = filter(lambda x: x["version"] == 6,
                            public_addrs[0]["values"])
        self.assertEqual(len(ipv6_addrs), 1)
        return ipv6_addrs[0]["addr"]

340
341
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
342
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
343
            return "user"
344
        elif os in ("windows", "windows_alpha1"):
345
            return "Administrator"
346
        else:
347
            return "root"
348
349
350
351

    def _verify_server_status(self, current_status, new_status):
        """Verify a server has switched to a specified status"""
        server = self.client.get_server_details(self.serverid)
352
353
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
        self.assertEquals(server["status"], new_status)

    def _get_connected_tcp_socket(self, family, host, port):
        """Get a connected socket from the specified family to host:port"""
        sock = None
        for res in \
            socket.getaddrinfo(host, port, family, socket.SOCK_STREAM, 0,
                               socket.AI_PASSIVE):
            af, socktype, proto, canonname, sa = res
            try:
                sock = socket.socket(af, socktype, proto)
            except socket.error as msg:
                sock = None
                continue
            try:
                sock.connect(sa)
            except socket.error as msg:
                sock.close()
                sock = None
                continue
        self.assertIsNotNone(sock)
        return sock

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

386
387
388
389
390
391
392
393
394
395
    def _get_hostname_over_ssh(self, hostip, username, password):
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        try:
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
        stdin, stdout, stderr = ssh.exec_command("hostname")
        lines = stdout.readlines()
        self.assertEqual(len(lines), 1)
396
        return lines[0]
397
398
399
400

    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
                                   opmsg, callable, *args, **kwargs):
        if warn_timeout == fail_timeout:
401
402
403
404
            warn_timeout = fail_timeout + 1
        warn_tmout = time.time() + warn_timeout
        fail_tmout = time.time() + fail_timeout
        while True:
405
            self.assertLess(time.time(), fail_tmout,
406
                            "operation `%s' timed out" % opmsg)
407
            if time.time() > warn_tmout:
408
409
                log.warning("Server %d: `%s' operation `%s' not done yet",
                            self.serverid, self.servername, opmsg)
410
            try:
411
                log.info("%s... " % opmsg)
412
413
414
                return callable(*args, **kwargs)
            except AssertionError:
                pass
415
416
            time.sleep(self.query_interval)

417
    def _insist_on_tcp_connection(self, family, host, port):
418
419
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
420
421
422
423
424
425
426
427
428
429
        msg = "connect over %s to %s:%s" % \
              (familystr.get(family, "Unknown"), host, port)
        sock = self._try_until_timeout_expires(
                self.action_timeout, self.action_timeout,
                msg, self._get_connected_tcp_socket,
                family, host, port)
        return sock

    def _insist_on_status_transition(self, current_status, new_status,
                                    fail_timeout, warn_timeout=None):
430
431
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
432
433
434
435
436
        if warn_timeout is None:
            warn_timeout = fail_timeout
        self._try_until_timeout_expires(warn_timeout, fail_timeout,
                                        msg, self._verify_server_status,
                                        current_status, new_status)
437
438
439
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
440
441

    def _insist_on_ssh_hostname(self, hostip, username, password):
442
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
443
444
445
446
447
448
449
        hostname = self._try_until_timeout_expires(
                self.action_timeout, self.action_timeout,
                msg, self._get_hostname_over_ssh,
                hostip, username, password)

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

451
452
453
454
    def _check_file_through_ssh(self, hostip, username, password,
                                remotepath, content):
        msg = "Trying file injection through SSH to %s, as %s/%s" % \
            (hostip, username, password)
455
        log.info(msg)
456
457
458
459
460
461
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
462

463
464
465
466
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
467
        sftp = paramiko.SFTPClient.from_transport(transport)
468
        sftp.get(remotepath, localpath)
469
470
471
        sftp.close()
        transport.close()

472
473
474
        f = open(localpath)
        remote_content = b64encode(f.read())

475
        # Check if files are the same
476
        return (remote_content == content)
477

478
479
480
481
482
483
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
484
485

        log.info("Submit new server request")
486
487
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
488

489
490
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
491
492
493
494
495
496
497
498
        self.assertEqual(server["name"], self.servername)
        self.assertEqual(server["flavorRef"], self.flavorid)
        self.assertEqual(server["imageRef"], self.imageid)
        self.assertEqual(server["status"], "BUILD")

        # Update class attributes to reflect data on building server
        cls = type(self)
        cls.serverid = server["id"]
499
        cls.username = None
500
501
        cls.passwd = server["adminPass"]

502
503
504
        self.result_dict["Server ID"] = str(server["id"])
        self.result_dict["Password"] = str(server["adminPass"])

505
506
    def test_002a_server_is_building_in_list(self):
        """Test server is in BUILD state, in server list"""
507
508
        log.info("Server in BUILD state in server list")

509
510
        self.result_dict.clear()

511
512
513
514
515
516
517
518
519
520
521
        servers = self.client.list_servers(detail=True)
        servers = filter(lambda x: x["name"] == self.servername, servers)
        self.assertEqual(len(servers), 1)
        server = servers[0]
        self.assertEqual(server["name"], self.servername)
        self.assertEqual(server["flavorRef"], self.flavorid)
        self.assertEqual(server["imageRef"], self.imageid)
        self.assertEqual(server["status"], "BUILD")

    def test_002b_server_is_building_in_details(self):
        """Test server is in BUILD state, in details"""
522
523
524

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

525
526
527
528
529
530
531
        server = self.client.get_server_details(self.serverid)
        self.assertEqual(server["name"], self.servername)
        self.assertEqual(server["flavorRef"], self.flavorid)
        self.assertEqual(server["imageRef"], self.imageid)
        self.assertEqual(server["status"], "BUILD")

    def test_002c_set_server_metadata(self):
532
533
534

        log.info("Creating server metadata")

535
        image = self.client.get_image_details(self.imageid)
536
        os = image["metadata"]["values"]["os"]
537
        users = image["metadata"]["values"].get("users", None)
538
        self.client.update_server_metadata(self.serverid, OS=os)
John Giannelos's avatar
John Giannelos committed
539

540
        userlist = users.split()
541

542
543
544
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
545
546
547
548

        if "root" in userlist:
            cls.username = "root"
        elif users == None:
549
            cls.username = self._connect_loginname(os)
550
551
552
        else:
            cls.username = choice(userlist)

553
        self.assertIsNotNone(cls.username)
554
555
556

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
557
558
559

        log.info("Verifying image metadata")

560
561
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
562

John Giannelos's avatar
John Giannelos committed
563
        self.assertEqual(servermeta["OS"], imagemeta["os"])
564
565
566

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
567
568
569

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

570
        self._insist_on_status_transition("BUILD", "ACTIVE",
571
572
                                         self.build_fail, self.build_warning)

573
574
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
575

576
577
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
578

579
580
581
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
582
        sock = self._insist_on_tcp_connection(socket.AF_INET,
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
                                        console["host"], console["port"])

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

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

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

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

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

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

611
612
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
613

614
        log.info("Validate server's IPv4")
615

616

617
618
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
619
620
621
622

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

623
624
        self.assertEquals(IP(ipv4).version(), 4)

625
626
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
627
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
628

629
        log.info("Validate server's IPv6")
630

631
632
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
633
634
635
636

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

637
        self.assertEquals(IP(ipv6).version(), 6)
638
639
640

    def test_006_server_responds_to_ping_IPv4(self):
        """Test server responds to ping on IPv4 address"""
641
642

        log.info("Testing if server responds to pings in IPv4")
643
        self.result_dict.clear()
644

645
        server = self.client.get_server_details(self.serverid)
646
647
648
        ip = self._get_ipv4(server)
        self._try_until_timeout_expires(self.action_timeout,
                                        self.action_timeout,
649
650
                                        "PING IPv4 to %s" % ip,
                                        self._ping_once,
651
                                        False, ip)
652

653
654
    def test_007_server_responds_to_ping_IPv6(self):
        """Test server responds to ping on IPv6 address"""
655
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
656
657
        log.info("Testing if server responds to pings in IPv6")

658
659
660
661
662
663
664
        server = self.client.get_server_details(self.serverid)
        ip = self._get_ipv6(server)
        self._try_until_timeout_expires(self.action_timeout,
                                        self.action_timeout,
                                        "PING IPv6 to %s" % ip,
                                        self._ping_once,
                                        True, ip)
665
666
667

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
668
669
670

        log.info("Shutting down server")

671
        self.cyclades.shutdown_server(self.serverid)
672
673
674

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
675
676

        log.info("Waiting until server becomes STOPPED")
677
678
        self._insist_on_status_transition("ACTIVE", "STOPPED",
                                         self.action_timeout,
679
680
681
682
                                         self.action_timeout)

    def test_010_submit_start_request(self):
        """Test submit start server request"""
683
684
685

        log.info("Starting server")

686
        self.cyclades.start_server(self.serverid)
687
688
689

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
690
691

        log.info("Waiting until server becomes ACTIVE")
692
693
        self._insist_on_status_transition("STOPPED", "ACTIVE",
                                         self.action_timeout,
694
695
696
697
                                         self.action_timeout)

    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
698
699
700

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

701
702
703
704
        self.test_006_server_responds_to_ping_IPv4()

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

706
        self._skipIf(self.is_windows, "only valid for Linux servers")
707
        server = self.client.get_server_details(self.serverid)
708
709
        self._insist_on_ssh_hostname(self._get_ipv4(server),
                                     self.username, self.passwd)
710

711
712
713
    def test_013_ssh_to_server_IPv6(self):
        """Test SSH to server public IPv6 works, verify hostname"""
        self._skipIf(self.is_windows, "only valid for Linux servers")
714
715
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

716
717
718
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
719
720
721

    def test_014_rdp_to_server_IPv4(self):
        "Test RDP connection to server public IPv4 works"""
722
        self._skipIf(not self.is_windows, "only valid for Windows servers")
723
724
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
725
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
726
727
728

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

732
733
734
    def test_015_rdp_to_server_IPv6(self):
        "Test RDP connection to server public IPv6 works"""
        self._skipIf(not self.is_windows, "only valid for Windows servers")
735
736
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

737
738
739
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
740

741
742
743
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        sock.close()
744
745
746

    def test_016_personality_is_enforced(self):
        """Test file injection for personality enforcement"""
John Giannelos's avatar
John Giannelos committed
747
748
        self._skipIf(self.is_windows, "only implemented for Linux servers")
        self._skipIf(self.personality == None, "No personality file selected")
749
750
751

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

752
753
        server = self.client.get_server_details(self.serverid)

754
        for inj_file in self.personality:
755
756
757
758
759
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
                                                       inj_file['owner'],
                                                       self.passwd,
                                                       inj_file['path'],
                                                       inj_file['contents'])
760
            self.assertTrue(equal_files)
761

762
763
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
764
765
766

        log.info("Deleting server")

767
768
769
770
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
771
772
773

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

774
775
776
777
778
779
        self._insist_on_status_transition("ACTIVE", "DELETED",
                                         self.action_timeout,
                                         self.action_timeout)

    def test_019_server_no_longer_in_server_list(self):
        """Test server is no longer in server list"""
780
781
782

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

783
        servers = self.client.list_servers()
784
785
786
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


787
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
788
    """ Testing networking in cyclades """
789

790
    @classmethod
John Giannelos's avatar
John Giannelos committed
791
792
    def setUpClass(cls):
        "Initialize kamaki, get list of current networks"
John Giannelos's avatar
John Giannelos committed
793
794
795

        cls.client = CycladesClient(API, TOKEN)
        cls.compute = ComputeClient(API, TOKEN)
796

797
798
799
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
                                          TEST_RUN_ID,
                                          cls.imagename)
800
801
802
803
804

        #Dictionary initialization for the vms credentials
        cls.serverid = dict()
        cls.username = dict()
        cls.password = dict()
John Giannelos's avatar
John Giannelos committed
805
        cls.is_windows = cls.imagename.lower().find("windows") >= 0
806

807
808
        cls.result_dict = dict()

809
810
811
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)
812

813
814
815
816
817
818
819
820
821
822
    def _get_ipv4(self, server):
        """Get the public IPv4 of a server from the detailed server info"""

        public_addrs = filter(lambda x: x["id"] == "public",
                              server["addresses"]["values"])
        self.assertEqual(len(public_addrs), 1)
        ipv4_addrs = filter(lambda x: x["version"] == 4,
                            public_addrs[0]["values"])
        self.assertEqual(len(ipv4_addrs), 1)
        return ipv4_addrs[0]["addr"]
823

824
825
826
827
828
829
830
831
832
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
            return "user"
        elif os in ("windows", "windows_alpha1"):
            return "Administrator"
        else:
            return "root"

John Giannelos's avatar
John Giannelos committed
833
    def _ping_once(self, ip):
834

John Giannelos's avatar
John Giannelos committed
835
836
837
838
839
840
        """Test server responds to a single IPv4 or IPv6 ping"""
        cmd = "ping -c 2 -w 3 %s" % (ip)
        ping = subprocess.Popen(cmd, shell=True,
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        (stdout, stderr) = ping.communicate()
        ret = ping.wait()
841

John Giannelos's avatar
John Giannelos committed
842
843
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
844
    def test_00001a_submit_create_server_A(self):
845
        """Test submit create server request"""
846
847
848

        log.info("Creating test server A")

849
        serverA = self.client.create_server(self.servername, self.flavorid,
850
                                            self.imageid, personality=None)
851

John Giannelos's avatar
John Giannelos committed
852
853
854
855
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
856
857

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
858
859
860
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
861

862
863
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
864

865
866
867
        self.result_dict["Server A ID"] = str(serverA["id"])
        self.result_dict["Server A password"] = serverA["adminPass"]
        
John Giannelos's avatar
John Giannelos committed
868
    def test_00001b_serverA_becomes_active(self):
869
        """Test server becomes ACTIVE"""
870

871
        log.info("Waiting until test server A becomes ACTIVE")
872
        self.result_dict.clear()
873

874
        fail_tmout = time.time() + self.action_timeout
875
876
877
878
879
880
881
882
883
884
885
886
887
        while True:
            d = self.client.get_server_details(self.serverid['A'])
            status = d['status']
            if status == 'ACTIVE':
                active = True
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(active)

John Giannelos's avatar
John Giannelos committed
888
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
889
        """Test submit create server request"""
890

891
        log.info("Creating test server B")
892
        
John Giannelos's avatar
John Giannelos committed
893
        serverB = self.client.create_server(self.servername, self.flavorid,
894
895
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
896
897
898
899
900
901
902
903
904
905
        self.assertEqual(serverB["name"], self.servername)
        self.assertEqual(serverB["flavorRef"], self.flavorid)
        self.assertEqual(serverB["imageRef"], self.imageid)
        self.assertEqual(serverB["status"], "BUILD")

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

906
907
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
908

909
910
911
912
        self.result_dict.clear()
        self.result_dict["Server B ID"] = str(serverB["id"])
        self.result_dict["Server B password"] = serverB["adminPass"]

John Giannelos's avatar
John Giannelos committed
913
    def test_00002b_serverB_becomes_active(self):
914
915
        """Test server becomes ACTIVE"""

916
        log.info("Waiting until test server B becomes ACTIVE")
917
        self.result_dict.clear()
918

919
        fail_tmout = time.time() + self.action_timeout
920
921
922
923
924
925
926
927
928
929
930
931
        while True:
            d = self.client.get_server_details(self.serverid['B'])
            status = d['status']
            if status == 'ACTIVE':
                active = True
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(active)
John Giannelos's avatar
John Giannelos committed
932

933
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
934
        """Test submit create network request"""
935
936

        log.info("Submit new network request")
937
938
        self.result_dict.clear()
                
939
        name = SNF_TEST_PREFIX + TEST_RUN_ID
940
        previous_num = len(self.client.list_networks())
941
        network = self.client.create_network(name)
942

943
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
944
        self.assertEqual(network['name'], name)
945

946
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
947
948
        cls = type(self)
        cls.networkid = network['id']
949
950
951
952
        networks = self.client.list_networks()

        #Test if new network is created
        self.assertTrue(len(networks) > previous_num)
953

954
955
        self.result_dict["Private network ID"] = str(network['id'])

956
    def test_002_connect_to_network(self):
957
        """Test connect VMs to network"""
958

959
        log.info("Connect VMs to private network")
960
        self.result_dict.clear()
961

962
963
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
964

965
        #Insist on connecting until action timeout
966
        fail_tmout = time.time() + self.action_timeout
967

968
        while True:
John Giannelos's avatar
John Giannelos committed
969
            connected = (self.client.get_network_details(self.networkid))
970
            connections = connected['servers']['values']
971
972
            if (self.serverid['A'] in connections) \
                    and (self.serverid['B'] in connections):
973
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
974
975
                break
            elif time.time() > fail_tmout:
976
977
978
979
980
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(conn_exists)
981

John Giannelos's avatar
John Giannelos committed
982
    def test_002a_reboot(self):
983
984
985
986
        """Rebooting server A"""

        log.info("Rebooting server A")

987
988
        self.client.shutdown_server(self.serverid['A'])

989
        fail_tmout = time.time() + self.action_timeout
990
991
992
993
994
995
996
997
998
999
1000
1001
        while True:
            d = self.client.get_server_details(self.serverid['A'])
            status = d['status']
            if status == 'STOPPED':
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

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

1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
        while True:
            d = self.client.get_server_details(self.serverid['A'])
            status = d['status']
            if status == 'ACTIVE':
                active = True
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)
1012

1013
        self.assertTrue(active)
John Giannelos's avatar
John Giannelos committed
1014

1015
    def test_002b_ping_server_A(self):
John Giannelos's avatar
John Giannelos committed
1016
        "Test if server A responds to IPv4 pings"
1017

John Giannelos's avatar
John Giannelos committed
1018
        log.info("Testing if server A responds to IPv4 pings ")
1019
        self.result_dict.clear()
1020

John Giannelos's avatar
John Giannelos committed
1021
1022
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
1023

1024
        fail_tmout = time.time() + self.action_timeout
1025

John Giannelos's avatar
John Giannelos committed
1026
        s = False
1027
1028
        
        self.result_dict["Server A public IP"] = str(ip)
John Giannelos's avatar
John Giannelos committed
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042

        while True:

            if self._ping_once(ip):
                s = True
                break

            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)

            else:
                time.sleep(self.query_interval)

        self.assertTrue(s)
1043
1044

    def test_002c_reboot(self):
1045
1046
1047
        """Reboot server B"""

        log.info("Rebooting server B")
1048
        self.result_dict.clear()
1049

1050
1051
        self.client.shutdown_server(self.serverid['B'])

1052
        fail_tmout = time.time() + self.action_timeout
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
        while True:
            d = self.client.get_server_details(self.serverid['B'])
            status = d['status']
            if status == 'STOPPED':
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

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

1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
        while True:
            d = self.client.get_server_details(self.serverid['B'])
            status = d['status']
            if status == 'ACTIVE':
                active = True
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)
1075

1076
        self.assertTrue(active)
1077

John Giannelos's avatar
John Giannelos committed
1078
    def test_002d_ping_server_B(self):
John Giannelos's avatar
John Giannelos committed
1079
        """Test if server B responds to IPv4 pings"""
1080

John Giannelos's avatar
John Giannelos committed
1081
        log.info("Testing if server B responds to IPv4 pings")
1082
1083
        self.result_dict.clear()
        
John Giannelos's avatar
John Giannelos committed
1084
1085
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
1086

1087
        fail_tmout = time.time() + self.action_timeout
1088

John Giannelos's avatar
John Giannelos committed
1089
1090
        s = False

1091
1092
        self.result_dict["Server B public IP"] = str(ip)

John Giannelos's avatar
John Giannelos committed
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
        while True:
            if self._ping_once(ip):
                s = True
                break

            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)

            else:
                time.sleep(self.query_interval)

        self.assertTrue(s)

    def test_003a_setup_interface_A(self):
1107
1108
        """Set up eth1 for server A"""

1109
1110
        self._skipIf(self.is_windows, "only valid for Linux servers")

1111
        log.info("Setting up interface eth1 for server A")
1112
        self.result_dict.clear()
1113

1114
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
1115
        image = self.client.get_image_details(self.imageid)