burnin.py 70 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
class BurninTestResult(unittest.TextTestResult):
    def addSuccess(self, test):
        super(BurninTestResult, self).addSuccess(test)
        if self.showAll:
John Giannelos's avatar
John Giannelos committed
78
            if hasattr(test, 'result_dict'):
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
                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")
John Giannelos's avatar
John Giannelos committed
94
95
            if hasattr(test, 'result_dict'):
                run_details = test.result_dict
96

John Giannelos's avatar
John Giannelos committed
97
98
99
100
                self.stream.write("\n")
                for i in run_details:
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
                self.stream.write("\n")
101
102
103
104
105
106
107
108
109

        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")
John Giannelos's avatar
John Giannelos committed
110
111
            if hasattr(test, 'result_dict'):
                run_details = test.result_dict
112

John Giannelos's avatar
John Giannelos committed
113
114
115
116
                self.stream.write("\n")
                for i in run_details:
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
                self.stream.write("\n")
117
118
119
120
121
122
123

        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
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
143
    dbg_fmt = green + "* %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    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

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

174

175
class UnauthorizedTestCase(unittest.TestCase):
176
177
178
179
180

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

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

185
        falseToken = '12345'
186
        c = ComputeClient(API, falseToken)
187

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


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")
199

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

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

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

214
215
216
217
218
219
220
    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):
221
222
223
224
        """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))
225
226
227
228
        self.assertEqual(sorted(list(set(names))), names)

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
229
        keys = frozenset(["osfamily", "root_partition"])
John Giannelos's avatar
John Giannelos committed
230
231
        details = self.client.list_images(detail=True)
        for i in details:
232
233
234
235
236
237
238
239
240
            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")
241

John Giannelos's avatar
John Giannelos committed
242
        cls.client = ComputeClient(API, TOKEN)
243
244
        cls.flavors = cls.client.list_flavors()
        cls.dflavors = cls.client.list_flavors(detail=True)
245
        cls.result_dict = dict()
246
247
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

    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")
284

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

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

    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"""
312
        log.info("Spawning server for image `%s'" %cls.imagename)
John Giannelos's avatar
John Giannelos committed
313
314
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
315
        cls.result_dict = dict()
316
317

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

320
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
321

322
323
324
325
326
327
        for nic in nics:
            net_id = nic["network_id"]
            if self.cyclades.get_network_details(net_id)["public"] == True:
                public_addrs = nic["ipv4"]
        
        self.assertTrue(public_addrs != None)
John Giannelos's avatar
John Giannelos committed
328

329
        return public_addrs
330
331

    def _get_ipv6(self, server):
332
        """Get the public IPv6 of a server from the detailed server info"""
John Giannelos's avatar
John Giannelos committed
333

334
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
335

336
337
338
339
340
341
        for nic in nics:
            net_id = nic["network_id"]
            if self.cyclades.get_network_details(net_id)["public"] == True:
                public_addrs = nic["ipv6"]
        
        self.assertTrue(public_addrs != None)
John Giannelos's avatar
John Giannelos committed
342

343
        return public_addrs
John Giannelos's avatar
John Giannelos committed
344

345

346
347
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
348
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
349
            return "user"
350
        elif os in ("windows", "windows_alpha1"):
351
            return "Administrator"
352
        else:
353
            return "root"
354
355
356
357

    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)
358
359
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
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
385
386
387
388
389
390
        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)
391

392
393
394
395
396
397
398
399
400
401
    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)
402
        return lines[0]
403
404
405
406

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

423
    def _insist_on_tcp_connection(self, family, host, port):
424
425
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
426
427
428
429
430
431
432
433
434
435
        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):
436
437
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
438
439
440
441
442
        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)
443
444
445
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
446
447

    def _insist_on_ssh_hostname(self, hostip, username, password):
448
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
449
450
451
452
453
454
455
        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))
456

457
458
459
460
    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)
461
        log.info(msg)
462
463
464
465
466
467
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
468

469
470
471
472
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
473
        sftp = paramiko.SFTPClient.from_transport(transport)
474
        sftp.get(remotepath, localpath)
475
476
477
        sftp.close()
        transport.close()

478
479
480
        f = open(localpath)
        remote_content = b64encode(f.read())

481
        # Check if files are the same
482
        return (remote_content == content)
483

484
485
486
487
488
489
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
490
491

        log.info("Submit new server request")
492
493
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
494

495
496
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
497
498
499
500
501
502
503
504
        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"]
505
        cls.username = None
506
507
        cls.passwd = server["adminPass"]

508
509
510
        self.result_dict["Server ID"] = str(server["id"])
        self.result_dict["Password"] = str(server["adminPass"])

511
512
    def test_002a_server_is_building_in_list(self):
        """Test server is in BUILD state, in server list"""
513
514
        log.info("Server in BUILD state in server list")

515
516
        self.result_dict.clear()

517
518
        servers = self.client.list_servers(detail=True)
        servers = filter(lambda x: x["name"] == self.servername, servers)
John Giannelos's avatar
John Giannelos committed
519

520
521
522
523
524
525
526
527
        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"""
528
529
530

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

531
532
533
534
535
536
537
        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):
538
539
540

        log.info("Creating server metadata")

541
        image = self.client.get_image_details(self.imageid)
542
        os = image["metadata"]["values"]["os"]
543
        users = image["metadata"]["values"].get("users", None)
544
        self.client.update_server_metadata(self.serverid, OS=os)
John Giannelos's avatar
John Giannelos committed
545

546
        userlist = users.split()
547

548
549
550
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
551
552
553
554

        if "root" in userlist:
            cls.username = "root"
        elif users == None:
555
            cls.username = self._connect_loginname(os)
556
557
558
        else:
            cls.username = choice(userlist)

559
        self.assertIsNotNone(cls.username)
560
561
562

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
563
564
565

        log.info("Verifying image metadata")

566
567
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
568

John Giannelos's avatar
John Giannelos committed
569
        self.assertEqual(servermeta["OS"], imagemeta["os"])
570
571
572

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
573
574
575

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

576
        self._insist_on_status_transition("BUILD", "ACTIVE",
577
578
                                         self.build_fail, self.build_warning)

John Giannelos's avatar
John Giannelos committed
579
580
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
581

John Giannelos's avatar
John Giannelos committed
582
583
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
584

John Giannelos's avatar
John Giannelos committed
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
610
611
612
613
614
615
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
        sock = self._insist_on_tcp_connection(socket.AF_INET,
                                        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()
616

617
618
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
619

620
        log.info("Validate server's IPv4")
621

622

623
624
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
625
626
627
628

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

629
630
        self.assertEquals(IP(ipv4).version(), 4)

631
632
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
633
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
634

635
        log.info("Validate server's IPv6")
636

637
638
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
639
640
641
642

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

643
        self.assertEquals(IP(ipv6).version(), 6)
644

John Giannelos's avatar
John Giannelos committed
645
646
647
648
    def test_006_server_responds_to_ping_IPv4(self):
        """Test server responds to ping on IPv4 address"""

        log.info("Testing if server responds to pings in IPv4")
649
        self.result_dict.clear()
John Giannelos's avatar
John Giannelos committed
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670

        server = self.client.get_server_details(self.serverid)
        ip = self._get_ipv4(server)
        self._try_until_timeout_expires(self.action_timeout,
                                        self.action_timeout,
                                        "PING IPv4 to %s" % ip,
                                        self._ping_once,
                                        False, ip)

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

        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)
671
672
673

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
674
675
676

        log.info("Shutting down server")

677
        self.cyclades.shutdown_server(self.serverid)
678
679
680

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
681
682

        log.info("Waiting until server becomes STOPPED")
683
684
        self._insist_on_status_transition("ACTIVE", "STOPPED",
                                         self.action_timeout,
685
686
687
688
                                         self.action_timeout)

    def test_010_submit_start_request(self):
        """Test submit start server request"""
689
690
691

        log.info("Starting server")

692
        self.cyclades.start_server(self.serverid)
693
694
695

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
696
697

        log.info("Waiting until server becomes ACTIVE")
698
699
        self._insist_on_status_transition("STOPPED", "ACTIVE",
                                         self.action_timeout,
700
701
                                         self.action_timeout)

John Giannelos's avatar
John Giannelos committed
702
703
    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
704

John Giannelos's avatar
John Giannelos committed
705
        log.info("Testing if server is actually up and running")
706

John Giannelos's avatar
John Giannelos committed
707
        self.test_006_server_responds_to_ping_IPv4()
708

John Giannelos's avatar
John Giannelos committed
709
710
    def test_012_ssh_to_server_IPv4(self):
        """Test SSH to server public IPv4 works, verify hostname"""
711

John Giannelos's avatar
John Giannelos committed
712
713
714
715
        self._skipIf(self.is_windows, "only valid for Linux servers")
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv4(server),
                                     self.username, self.passwd)
716

John Giannelos's avatar
John Giannelos committed
717
718
719
720
    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")
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
721

John Giannelos's avatar
John Giannelos committed
722
723
724
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
725

John Giannelos's avatar
John Giannelos committed
726
727
728
729
730
731
    def test_014_rdp_to_server_IPv4(self):
        "Test RDP connection to server public IPv4 works"""
        self._skipIf(not self.is_windows, "only valid for Windows servers")
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
732

John Giannelos's avatar
John Giannelos committed
733
734
735
736
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        # FIXME: Use rdesktop, analyze exit code? see manpage [costasd]
        sock.close()
737

John Giannelos's avatar
John Giannelos committed
738
739
740
741
    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")
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
742

John Giannelos's avatar
John Giannelos committed
743
744
745
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
746

John Giannelos's avatar
John Giannelos committed
747
748
749
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        sock.close()
750

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

John Giannelos's avatar
John Giannelos committed
756
        log.info("Trying to inject file for personality enforcement")
757

John Giannelos's avatar
John Giannelos committed
758
        server = self.client.get_server_details(self.serverid)
759

John Giannelos's avatar
John Giannelos committed
760
761
762
763
764
765
766
        for inj_file in self.personality:
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
                                                       inj_file['owner'],
                                                       self.passwd,
                                                       inj_file['path'],
                                                       inj_file['contents'])
            self.assertTrue(equal_files)
767

768
769
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
770
771
772

        log.info("Deleting server")

773
774
775
776
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
777
778
779

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

780
781
782
783
784
785
        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"""
786
787
788

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

789
        servers = self.client.list_servers()
790
791
792
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


793
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
794
    """ Testing networking in cyclades """
795

796
    @classmethod
John Giannelos's avatar
John Giannelos committed
797
798
    def setUpClass(cls):
        "Initialize kamaki, get list of current networks"
John Giannelos's avatar
John Giannelos committed
799
800
801

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

803
804
805
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
                                          TEST_RUN_ID,
                                          cls.imagename)
806
807
808
809
810

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

813
814
        cls.result_dict = dict()

815
816
817
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)
818

819
820
821
    def _get_ipv4(self, server):
        """Get the public IPv4 of a server from the detailed server info"""

822
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
823

824
825
826
827
828
829
        for nic in nics:
            net_id = nic["network_id"]
            if self.client.get_network_details(net_id)["public"] == True:
                public_addrs = nic["ipv4"]
        
        self.assertTrue(public_addrs != None)
John Giannelos's avatar
John Giannelos committed
830

831
        return public_addrs
John Giannelos's avatar
John Giannelos committed
832

833

834
835
836
837
838
839
840
841
842
    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
843
    def _ping_once(self, ip):
844

John Giannelos's avatar
John Giannelos committed
845
846
847
848
849
850
        """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()
851

John Giannelos's avatar
John Giannelos committed
852
853
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
854
    def test_00001a_submit_create_server_A(self):
855
        """Test submit create server request"""
856
857
858

        log.info("Creating test server A")

859
        serverA = self.client.create_server(self.servername, self.flavorid,
860
                                            self.imageid, personality=None)
861

John Giannelos's avatar
John Giannelos committed
862
863
864
865
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
866
867

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
868
869
870
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
871

872
873
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
874

875
876
877
        self.result_dict["Server A ID"] = str(serverA["id"])
        self.result_dict["Server A password"] = serverA["adminPass"]
        
John Giannelos's avatar
John Giannelos committed
878
    def test_00001b_serverA_becomes_active(self):
879
        """Test server becomes ACTIVE"""
880

881
        log.info("Waiting until test server A becomes ACTIVE")
882
        self.result_dict.clear()
883

884
        fail_tmout = time.time() + self.action_timeout
885
886
887
888
889
890
891
892
893
894
895
896
897
        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
898
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
899
        """Test submit create server request"""
900

901
        log.info("Creating test server B")
902
        
John Giannelos's avatar
John Giannelos committed
903
        serverB = self.client.create_server(self.servername, self.flavorid,
904
905
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
906
907
908
909
910
911
912
913
914
915
        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"]

916
917
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
918

919
920
921
922
        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
923
    def test_00002b_serverB_becomes_active(self):
924
925
        """Test server becomes ACTIVE"""

926
        log.info("Waiting until test server B becomes ACTIVE")
927
        self.result_dict.clear()
928

929
        fail_tmout = time.time() + self.action_timeout
930
931
932
933
934
935
936
937
938
939
940
941
        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
942

943
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
944
        """Test submit create network request"""
945
946

        log.info("Submit new network request")
947
948
        self.result_dict.clear()
                
949
        name = SNF_TEST_PREFIX + TEST_RUN_ID
950
        previous_num = len(self.client.list_networks())
John Giannelos's avatar
John Giannelos committed
951
        network = self.client.create_network(name,cidr='10.0.0.1/28')
952

953
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
954
        self.assertEqual(network['name'], name)
955

956
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
957
958
        cls = type(self)
        cls.networkid = network['id']
959
960
        networks = self.client.list_networks()

John Giannelos's avatar
John Giannelos committed
961
962
        fail_tmout = time.time() + self.action_timeout

963
        #Test if new network is created
John Giannelos's avatar
John Giannelos committed
964
965
966
967
968
969
970
971
972
973
974
975
        while True:
            d = self.client.get_network_details(network['id'])
            if d['status'] == 'ACTIVE':
                connected = True
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                log.info("Waiting for network to become ACTIVE")
                time.sleep(self.query_interval)

        self.assertTrue(connected)
976

977
978
        self.result_dict["Private network ID"] = str(network['id'])

979
    def test_002_connect_to_network(self):
980
        """Test connect VMs to network"""
981

982
        log.info("Connect VMs to private network")
983
        self.result_dict.clear()
984

985
986
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
987

988
        #Insist on connecting until action timeout
989
        fail_tmout = time.time() + self.action_timeout
990

991
        while True:
John Giannelos's avatar
John Giannelos committed
992

John Giannelos's avatar
John Giannelos committed
993
994
            netsA = [x['network_id'] for x in self.client.get_server_details(self.serverid['A'])['attachments']['values']]
            netsB = [x['network_id'] for x in self.client.get_server_details(self.serverid['B'])['attachments']['values']]
John Giannelos's avatar
John Giannelos committed
995
996

            if (self.networkid in netsA) and (self.networkid in netsB):
997
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
998
999
                break
            elif time.time() > fail_tmout:
1000
1001
1002
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)
John Giannelos's avatar
John Giannelos committed
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
                
        #Adding private IPs to class attributes
        cls = type(self)
        cls.priv_ip = dict()

        nicsA = self.client.get_server_details(self.serverid['A'])['attachments']['values']
        nicsB = self.client.get_server_details(self.serverid['B'])['attachments']['values']

        if conn_exists:
            for nic in nicsA:
                if nic["network_id"] == self.networkid:
                    cls.priv_ip["A"] = nic["ipv4"]

            for nic in nicsB:
                if nic["network_id"] == self.networkid:
                    cls.priv_ip["B"] = nic["ipv4"]
1019
1020

        self.assertTrue(conn_exists)
1021

John Giannelos's avatar
John Giannelos committed
1022
    def test_002a_reboot(self):
1023
1024
1025
1026
        """Rebooting server A"""

        log.info("Rebooting server A")

1027
1028
        self.client.shutdown_server(self.serverid['A'])

1029
        fail_tmout = time.time() + self.action_timeout
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
        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'])

1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
        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)
1052

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

John Giannelos's avatar
John Giannelos committed
1055
1056
    def test_002b_ping_server_A(self):
        "Test if server A responds to IPv4 pings"
1057

John Giannelos's avatar
John Giannelos committed
1058
        log.info("Testing if server A responds to IPv4 pings ")
1059
        self.result_dict.clear()
1060

John Giannelos's avatar
John Giannelos committed
1061
1062
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
1063

John Giannelos's avatar
John Giannelos committed
1064
        fail_tmout = time.time() + self.action_timeout
1065

John Giannelos's avatar
John Giannelos committed
1066
        s = False
1067
1068
        
        self.result_dict["Server A public IP"] = str(ip)
John Giannelos's avatar
John Giannelos committed
1069

John Giannelos's avatar
John Giannelos committed
1070
        while True:
John Giannelos's avatar
John Giannelos committed
1071

John Giannelos's avatar
John Giannelos committed
1072
1073
1074
            if self._ping_once(ip):
                s = True
                break
John Giannelos's avatar
John Giannelos committed
1075

John Giannelos's avatar
John Giannelos committed
1076
1077
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
John Giannelos's avatar
John Giannelos committed
1078

John Giannelos's avatar
John Giannelos committed
1079
1080
            else:
                time.sleep(self.query_interval)
John Giannelos's avatar
John Giannelos committed
1081

John Giannelos's avatar
John Giannelos committed
1082
        self.assertTrue(s)
1083
1084

    def test_002c_reboot(self):
1085
1086
1087
        """Reboot server B"""

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