burnin.py 70.2 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"""

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
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

61
from vncauthproxy.d3des import generate_response as d3des_generate_response
62
63
64
65
66

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# --------------------------------------------------------------------
# Global Variables
API = None
TOKEN = None
PLANKTON = None
PLANKTON_USER = None
NO_IPV6 = None
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
DEFAULT_PLANKTON = "https://cyclades.okeanos.grnet.gr/plankton"
DEFAULT_PLANKTON_USER = "images@okeanos.grnet.gr"
NOFAILFAST = None
VERBOSE = None

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

red = '\x1b[31m'
yellow = '\x1b[33m'
green = '\x1b[32m'
normal = '\x1b[0m'

94

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


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
115
116
# --------------------------------------------------------------------
# BurninTestReulst class
117
118
119
120
class BurninTestResult(unittest.TextTestResult):
    def addSuccess(self, test):
        super(BurninTestResult, self).addSuccess(test)
        if self.showAll:
John Giannelos's avatar
John Giannelos committed
121
            if hasattr(test, 'result_dict'):
122
123
124
125
126
127
128
129
130
                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('.')
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
131
132
            self.stream.flush()

133
134
135
136
    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
137
138
            if hasattr(test, 'result_dict'):
                run_details = test.result_dict
139

John Giannelos's avatar
John Giannelos committed
140
141
142
143
                self.stream.write("\n")
                for i in run_details:
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
                self.stream.write("\n")
144
145
146
147
148
149
150
151
152

        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
153
154
            if hasattr(test, 'result_dict'):
                run_details = test.result_dict
155

John Giannelos's avatar
John Giannelos committed
156
157
158
159
                self.stream.write("\n")
                for i in run_details:
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
                self.stream.write("\n")
160
161
162
163
164
165

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


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
166
167
# --------------------------------------------------------------------
# Format Results
John Giannelos's avatar
John Giannelos committed
168
169
class burninFormatter(logging.Formatter):
    err_fmt = red + "ERROR: %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
170
    dbg_fmt = green + "* %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    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

189
log = logging.getLogger("burnin")
John Giannelos's avatar
John Giannelos committed
190
191
192
193
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(burninFormatter())
log.addHandler(handler)
194

195

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
196
197
# --------------------------------------------------------------------
# UnauthorizedTestCase class
198
class UnauthorizedTestCase(unittest.TestCase):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
199
    """Test unauthorized access"""
200
201
202
203
    @classmethod
    def setUpClass(cls):
        cls.result_dict = dict()

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

210
211
        with self.assertRaises(ClientError) as cm:
            c.list_servers()
John Giannelos's avatar
John Giannelos committed
212
            self.assertEqual(cm.exception.status, 401)
213
214


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
215
216
# --------------------------------------------------------------------
# ImagesTestCase class
217
218
219
220
221
222
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")
John Giannelos's avatar
John Giannelos committed
223
        cls.client = ComputeClient(API, TOKEN)
224
225
226
        cls.plankton = ImageClient(PLANKTON, TOKEN)
        cls.images = cls.plankton.list_public()
        cls.dimages = cls.plankton.list_public(detail=True)
227
        cls.result_dict = dict()
228
229
230

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

233
234
    def test_002_list_images_detailed(self):
        """Test detailed image list is the same length as list"""
John Giannelos's avatar
John Giannelos committed
235
        self.assertEqual(len(self.dimages), len(self.images))
236

237
238
239
240
241
242
243
    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):
244
245
246
247
        """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))
248
249
250
251
        self.assertEqual(sorted(list(set(names))), names)

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
252
        keys = frozenset(["osfamily", "root_partition"])
John Giannelos's avatar
John Giannelos committed
253
254
        details = self.client.list_images(detail=True)
        for i in details:
255
256
257
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
258
259
# --------------------------------------------------------------------
# FlavorsTestCase class
260
261
262
263
264
265
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")
John Giannelos's avatar
John Giannelos committed
266
        cls.client = ComputeClient(API, TOKEN)
267
268
        cls.flavors = cls.client.list_flavors()
        cls.dflavors = cls.client.list_flavors(detail=True)
269
        cls.result_dict = dict()
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299

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


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
300
301
# --------------------------------------------------------------------
# ServersTestCase class
302
303
304
305
306
307
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")
308

John Giannelos's avatar
John Giannelos committed
309
        cls.client = ComputeClient(API, TOKEN)
310
311
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)
312
        cls.result_dict = dict()
313

314
315
316
    def test_001_list_servers(self):
        """Test server list actually returns servers"""
        self.assertGreater(len(self.servers), 0)
317
318
319
320
321
322
323
324
325
326
327
328

    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)


329
# --------------------------------------------------------------------
330
331
332
333
334
335
336
# 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"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
337
        log.info("Spawning server for image `%s'" % cls.imagename)
John Giannelos's avatar
John Giannelos committed
338
339
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
340
        cls.result_dict = dict()
341
342

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

345
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
346

347
348
        for nic in nics:
            net_id = nic["network_id"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
349
            if self.cyclades.get_network_details(net_id)["public"]:
350
                public_addrs = nic["ipv4"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
351
352

        self.assertTrue(public_addrs is not None)
John Giannelos's avatar
John Giannelos committed
353

354
        return public_addrs
355
356

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

359
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
360

361
362
        for nic in nics:
            net_id = nic["network_id"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
363
            if self.cyclades.get_network_details(net_id)["public"]:
364
                public_addrs = nic["ipv6"]
John Giannelos's avatar
John Giannelos committed
365

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
366
        self.assertTrue(public_addrs is not None)
John Giannelos's avatar
John Giannelos committed
367

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
368
        return public_addrs
369

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
370
    def _connect_loginname(self, os_value):
371
        """Return the login name for connections based on the server OS"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
372
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
373
            return "user"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
374
        elif os_value in ("windows", "windows_alpha1"):
375
            return "Administrator"
376
        else:
377
            return "root"
378
379
380
381

    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)
382
383
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
384
385
386
387
388
389
390
391
392
393
394
        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)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
395
            except socket.error:
396
397
398
399
                sock = None
                continue
            try:
                sock.connect(sa)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
400
            except socket.error:
401
402
403
404
405
406
407
408
409
410
411
412
413
414
                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)
415

416
    def _get_hostname_over_ssh(self, hostip, username, password):
417
418
        lines, status = _ssh_execute(
            hostip, username, password, "hostname")
419
        self.assertEqual(len(lines), 1)
420
        return lines[0]
421
422
423
424

    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
                                   opmsg, callable, *args, **kwargs):
        if warn_timeout == fail_timeout:
425
426
427
428
            warn_timeout = fail_timeout + 1
        warn_tmout = time.time() + warn_timeout
        fail_tmout = time.time() + fail_timeout
        while True:
429
            self.assertLess(time.time(), fail_tmout,
430
                            "operation `%s' timed out" % opmsg)
431
            if time.time() > warn_tmout:
432
433
                log.warning("Server %d: `%s' operation `%s' not done yet",
                            self.serverid, self.servername, opmsg)
434
            try:
435
                log.info("%s... " % opmsg)
436
437
438
                return callable(*args, **kwargs)
            except AssertionError:
                pass
439
440
            time.sleep(self.query_interval)

441
    def _insist_on_tcp_connection(self, family, host, port):
442
443
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
444
445
446
        msg = "connect over %s to %s:%s" % \
              (familystr.get(family, "Unknown"), host, port)
        sock = self._try_until_timeout_expires(
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
447
448
449
            self.action_timeout, self.action_timeout,
            msg, self._get_connected_tcp_socket,
            family, host, port)
450
451
452
        return sock

    def _insist_on_status_transition(self, current_status, new_status,
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
453
                                     fail_timeout, warn_timeout=None):
454
455
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
456
457
458
459
460
        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)
461
462
463
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
464
465

    def _insist_on_ssh_hostname(self, hostip, username, password):
466
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
467
        hostname = self._try_until_timeout_expires(
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
468
469
470
            self.action_timeout, self.action_timeout,
            msg, self._get_hostname_over_ssh,
            hostip, username, password)
471
472
473

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

475
476
477
478
    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)
479
        log.info(msg)
480
481
482
483
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
484
            ssh.close()
485
486
        except socket.error:
            raise AssertionError
487

488
489
490
491
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
492
        sftp = paramiko.SFTPClient.from_transport(transport)
493
        sftp.get(remotepath, localpath)
494
495
496
        sftp.close()
        transport.close()

497
498
499
        f = open(localpath)
        remote_content = b64encode(f.read())

500
        # Check if files are the same
501
        return (remote_content == content)
502

503
504
505
506
507
508
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
509
510

        log.info("Submit new server request")
511
512
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
513

514
515
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
516
517
518
519
520
521
522
523
        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"]
524
        cls.username = None
525
526
        cls.passwd = server["adminPass"]

527
528
529
        self.result_dict["Server ID"] = str(server["id"])
        self.result_dict["Password"] = str(server["adminPass"])

530
531
    def test_002a_server_is_building_in_list(self):
        """Test server is in BUILD state, in server list"""
532
533
        log.info("Server in BUILD state in server list")

534
535
        self.result_dict.clear()

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

539
540
541
542
543
544
545
546
        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"""
547
548
549

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

550
551
552
553
554
555
556
        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):
557
558
559

        log.info("Creating server metadata")

560
        image = self.client.get_image_details(self.imageid)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
561
        os_value = image["metadata"]["values"]["os"]
562
        users = image["metadata"]["values"].get("users", None)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
563
        self.client.update_server_metadata(self.serverid, OS=os_value)
John Giannelos's avatar
John Giannelos committed
564

565
        userlist = users.split()
566

567
568
569
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
570
571
572

        if "root" in userlist:
            cls.username = "root"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
573
574
        elif users is None:
            cls.username = self._connect_loginname(os_value)
575
576
577
        else:
            cls.username = choice(userlist)

578
        self.assertIsNotNone(cls.username)
579
580
581

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
582
583
584

        log.info("Verifying image metadata")

585
586
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
587

John Giannelos's avatar
John Giannelos committed
588
        self.assertEqual(servermeta["OS"], imagemeta["os"])
589
590
591

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
592
593
594

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
595
596
        self._insist_on_status_transition(
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
597

John Giannelos's avatar
John Giannelos committed
598
599
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
600

John Giannelos's avatar
John Giannelos committed
601
602
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
603

John Giannelos's avatar
John Giannelos committed
604
605
606
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
607
608
        sock = self._insist_on_tcp_connection(
            socket.AF_INET, console["host"], console["port"])
John Giannelos's avatar
John Giannelos committed
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634

        # 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()
635

636
637
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
638

639
        log.info("Validate server's IPv4")
640

641
642
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
643
644
645
646

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

647
648
        self.assertEquals(IP(ipv4).version(), 4)

649
650
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
651
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
652

653
        log.info("Validate server's IPv6")
654

655
656
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
657
658
659
660

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

661
        self.assertEquals(IP(ipv6).version(), 6)
662

John Giannelos's avatar
John Giannelos committed
663
664
665
666
    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")
667
        self.result_dict.clear()
John Giannelos's avatar
John Giannelos committed
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688

        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)
689
690
691

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
692
693
694

        log.info("Shutting down server")

695
        self.cyclades.shutdown_server(self.serverid)
696
697
698

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
699
700

        log.info("Waiting until server becomes STOPPED")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
701
702
        self._insist_on_status_transition(
            "ACTIVE", "STOPPED", self.action_timeout, self.action_timeout)
703
704
705

    def test_010_submit_start_request(self):
        """Test submit start server request"""
706
707
708

        log.info("Starting server")

709
        self.cyclades.start_server(self.serverid)
710
711
712

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
713
714

        log.info("Waiting until server becomes ACTIVE")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
715
716
        self._insist_on_status_transition(
            "STOPPED", "ACTIVE", self.action_timeout, self.action_timeout)
717

John Giannelos's avatar
John Giannelos committed
718
719
    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
720

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

John Giannelos's avatar
John Giannelos committed
723
        self.test_006_server_responds_to_ping_IPv4()
724

John Giannelos's avatar
John Giannelos committed
725
726
    def test_012_ssh_to_server_IPv4(self):
        """Test SSH to server public IPv4 works, verify hostname"""
727

John Giannelos's avatar
John Giannelos committed
728
729
730
731
        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)
732

John Giannelos's avatar
John Giannelos committed
733
734
735
736
    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")
737

John Giannelos's avatar
John Giannelos committed
738
739
740
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
741

John Giannelos's avatar
John Giannelos committed
742
743
744
745
746
    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)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
747
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
748

John Giannelos's avatar
John Giannelos committed
749
750
751
752
        # 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()
753

John Giannelos's avatar
John Giannelos committed
754
755
756
757
    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")
758

John Giannelos's avatar
John Giannelos committed
759
760
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
761
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
762

John Giannelos's avatar
John Giannelos committed
763
764
765
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        sock.close()
766

John Giannelos's avatar
John Giannelos committed
767
768
769
    def test_016_personality_is_enforced(self):
        """Test file injection for personality enforcement"""
        self._skipIf(self.is_windows, "only implemented for Linux servers")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
770
        self._skipIf(self.personality is None, "No personality file selected")
771

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

John Giannelos's avatar
John Giannelos committed
774
        server = self.client.get_server_details(self.serverid)
775

John Giannelos's avatar
John Giannelos committed
776
777
778
779
780
781
782
        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)
783

784
785
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
786
787
788

        log.info("Deleting server")

789
790
791
792
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
793
794
795

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
796
797
        self._insist_on_status_transition(
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
798
799
800

    def test_019_server_no_longer_in_server_list(self):
        """Test server is no longer in server list"""
801
802
803

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

804
        servers = self.client.list_servers()
805
806
807
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


808
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
809
    """ Testing networking in cyclades """
810

811
    @classmethod
John Giannelos's avatar
John Giannelos committed
812
813
    def setUpClass(cls):
        "Initialize kamaki, get list of current networks"
John Giannelos's avatar
John Giannelos committed
814
815
816

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

818
819
820
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
                                          TEST_RUN_ID,
                                          cls.imagename)
821
822
823
824
825

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

828
829
        cls.result_dict = dict()

830
831
832
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)
833

834
835
836
    def _get_ipv4(self, server):
        """Get the public IPv4 of a server from the detailed server info"""

837
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
838

839
840
        for nic in nics:
            net_id = nic["network_id"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
841
            if self.client.get_network_details(net_id)["public"]:
842
                public_addrs = nic["ipv4"]
John Giannelos's avatar
John Giannelos committed
843

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
844
        self.assertTrue(public_addrs is not None)
John Giannelos's avatar
John Giannelos committed
845

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
846
        return public_addrs
847

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
848
    def _connect_loginname(self, os_value):
849
        """Return the login name for connections based on the server OS"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
850
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
851
            return "user"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
852
        elif os_value in ("windows", "windows_alpha1"):
853
854
855
856
            return "Administrator"
        else:
            return "root"

John Giannelos's avatar
John Giannelos committed
857
    def _ping_once(self, ip):
858

John Giannelos's avatar
John Giannelos committed
859
860
861
862
863
864
        """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()
865

John Giannelos's avatar
John Giannelos committed
866
867
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
868
    def test_00001a_submit_create_server_A(self):
869
        """Test submit create server request"""
870
871
872

        log.info("Creating test server A")

873
        serverA = self.client.create_server(self.servername, self.flavorid,
874
                                            self.imageid, personality=None)
875

John Giannelos's avatar
John Giannelos committed
876
877
878
879
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
880
881

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
882
883
884
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
885

886
887
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
888

889
890
        self.result_dict["Server A ID"] = str(serverA["id"])
        self.result_dict["Server A password"] = serverA["adminPass"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
891

John Giannelos's avatar
John Giannelos committed
892
    def test_00001b_serverA_becomes_active(self):
893
        """Test server becomes ACTIVE"""
894

895
        log.info("Waiting until test server A becomes ACTIVE")
896
        self.result_dict.clear()
897

898
        fail_tmout = time.time() + self.action_timeout
899
900
901
902
903
904
905
906
907
908
909
910
911
        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
912
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
913
        """Test submit create server request"""
914

915
        log.info("Creating test server B")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
916

John Giannelos's avatar
John Giannelos committed
917
        serverB = self.client.create_server(self.servername, self.flavorid,
918
919
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
920
921
922
923
924
925
926
927
928
929
        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"]

930
931
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
932

933
934
935
936
        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
937
    def test_00002b_serverB_becomes_active(self):
938
939
        """Test server becomes ACTIVE"""

940
        log.info("Waiting until test server B becomes ACTIVE")
941
        self.result_dict.clear()
942

943
        fail_tmout = time.time() + self.action_timeout
944
945
946
947
948
949
950
951
952
953
954
955
        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
956

957
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
958
        """Test submit create network request"""
959
960

        log.info("Submit new network request")
961
        self.result_dict.clear()
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
962

963
        name = SNF_TEST_PREFIX + TEST_RUN_ID
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
964
965
        #previous_num = len(self.client.list_networks())
        network = self.client.create_network(name, cidr='10.0.0.1/28')
966

967
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
968
        self.assertEqual(network['name'], name)
969

970
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
971
972
        cls = type(self)
        cls.networkid = network['id']
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
973
        #networks = self.client.list_networks()
974

John Giannelos's avatar
John Giannelos committed
975
976
        fail_tmout = time.time() + self.action_timeout

977
        #Test if new network is created
John Giannelos's avatar
John Giannelos committed
978
979
980
981
982
983
984
985
986
987
988
989
        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)
990

991
992
        self.result_dict["Private network ID"] = str(network['id'])

993
    def test_002_connect_to_network(self):
994
        """Test connect VMs to network"""
995

996
        log.info("Connect VMs to private network")
997
        self.result_dict.clear()
998

999
1000
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
1001

1002
        #Insist on connecting until action timeout
1003
        fail_tmout = time.time() + self.action_timeout
1004

1005
        while True:
John Giannelos's avatar
John Giannelos committed
1006

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1007
1008
1009
1010
1011
1012
            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
1013
1014

            if (self.networkid in netsA) and (self.networkid in netsB):
1015
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
1016
1017
                break
            elif time.time() > fail_tmout:
1018
1019
1020
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1021

John Giannelos's avatar
John Giannelos committed
1022
1023
1024
1025
        #Adding private IPs to class attributes
        cls = type(self)
        cls.priv_ip = dict()

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1026
1027
1028
1029
        nicsA = self.client.get_server_details(
            self.serverid['A'])['attachments']['values']
        nicsB = self.client.get_server_details(
            self.serverid['B'])['attachments']['values']
John Giannelos's avatar
John Giannelos committed
1030
1031
1032
1033
1034
1035
1036
1037
1038

        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: