burnin.py 71.3 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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
61
import fabric.api as fabric
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
    import unittest

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# --------------------------------------------------------------------
# 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'

96

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
97
98
# --------------------------------------------------------------------
# BurninTestReulst class
99
100
101
102
class BurninTestResult(unittest.TextTestResult):
    def addSuccess(self, test):
        super(BurninTestResult, self).addSuccess(test)
        if self.showAll:
John Giannelos's avatar
John Giannelos committed
103
            if hasattr(test, 'result_dict'):
104
105
106
107
108
109
110
111
112
                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
113
114
            self.stream.flush()

115
116
117
118
    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
119
120
            if hasattr(test, 'result_dict'):
                run_details = test.result_dict
121

John Giannelos's avatar
John Giannelos committed
122
123
124
125
                self.stream.write("\n")
                for i in run_details:
                    self.stream.write("%s : %s \n" % (i, run_details[i]))
                self.stream.write("\n")
126
127
128
129
130
131
132
133
134

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

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

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


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
148
149
# --------------------------------------------------------------------
# Format Results
John Giannelos's avatar
John Giannelos committed
150
151
class burninFormatter(logging.Formatter):
    err_fmt = red + "ERROR: %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
152
    dbg_fmt = green + "* %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
    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

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

177

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
178
179
# --------------------------------------------------------------------
# UnauthorizedTestCase class
180
class UnauthorizedTestCase(unittest.TestCase):
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
181
    """Test unauthorized access"""
182
183
184
185
    @classmethod
    def setUpClass(cls):
        cls.result_dict = dict()

186
187
    def test_unauthorized_access(self):
        """Test access without a valid token fails"""
188
        log.info("Authentication test")
189
        falseToken = '12345'
190
        c = ComputeClient(API, falseToken)
191

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


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
197
198
# --------------------------------------------------------------------
# ImagesTestCase class
199
200
201
202
203
204
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
205
        cls.client = ComputeClient(API, TOKEN)
206
207
208
        cls.plankton = ImageClient(PLANKTON, TOKEN)
        cls.images = cls.plankton.list_public()
        cls.dimages = cls.plankton.list_public(detail=True)
209
        cls.result_dict = dict()
210
211
212

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

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

219
220
221
222
223
224
225
    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):
226
227
228
229
        """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))
230
231
232
233
        self.assertEqual(sorted(list(set(names))), names)

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


Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
240
241
# --------------------------------------------------------------------
# FlavorsTestCase class
242
243
244
245
246
247
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
248
        cls.client = ComputeClient(API, TOKEN)
249
250
        cls.flavors = cls.client.list_flavors()
        cls.dflavors = cls.client.list_flavors(detail=True)
251
        cls.result_dict = dict()
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

    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
282
283
# --------------------------------------------------------------------
# ServersTestCase class
284
285
286
287
288
289
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")
290

John Giannelos's avatar
John Giannelos committed
291
        cls.client = ComputeClient(API, TOKEN)
292
293
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)
294
        cls.result_dict = dict()
295

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

    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)


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

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

327
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
328

329
330
        for nic in nics:
            net_id = nic["network_id"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
331
            if self.cyclades.get_network_details(net_id)["public"]:
332
                public_addrs = nic["ipv4"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
333
334

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

336
        return public_addrs
337
338

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

341
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
342

343
344
        for nic in nics:
            net_id = nic["network_id"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
345
            if self.cyclades.get_network_details(net_id)["public"]:
346
                public_addrs = nic["ipv6"]
John Giannelos's avatar
John Giannelos committed
347

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
350
        return public_addrs
351

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
352
    def _connect_loginname(self, os_value):
353
        """Return the login name for connections based on the server OS"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
354
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
355
            return "user"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
356
        elif os_value in ("windows", "windows_alpha1"):
357
            return "Administrator"
358
        else:
359
            return "root"
360
361
362
363

    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)
364
365
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
366
367
368
369
370
371
372
373
374
375
376
        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
377
            except socket.error:
378
379
380
381
                sock = None
                continue
            try:
                sock.connect(sa)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
382
            except socket.error:
383
384
385
386
387
388
389
390
391
392
393
394
395
396
                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)
397

398
399
400
401
402
403
404
405
406
407
    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)
408
        return lines[0]
409
410
411
412

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

429
    def _insist_on_tcp_connection(self, family, host, port):
430
431
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
432
433
434
        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
435
436
437
            self.action_timeout, self.action_timeout,
            msg, self._get_connected_tcp_socket,
            family, host, port)
438
439
440
        return sock

    def _insist_on_status_transition(self, current_status, new_status,
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
441
                                     fail_timeout, warn_timeout=None):
442
443
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
444
445
446
447
448
        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)
449
450
451
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
452
453

    def _insist_on_ssh_hostname(self, hostip, username, password):
454
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
455
        hostname = self._try_until_timeout_expires(
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
456
457
458
            self.action_timeout, self.action_timeout,
            msg, self._get_hostname_over_ssh,
            hostip, username, password)
459
460
461

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

463
464
465
466
    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)
467
        log.info(msg)
468
469
470
471
472
473
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
474

475
476
477
478
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
479
        sftp = paramiko.SFTPClient.from_transport(transport)
480
        sftp.get(remotepath, localpath)
481
482
483
        sftp.close()
        transport.close()

484
485
486
        f = open(localpath)
        remote_content = b64encode(f.read())

487
        # Check if files are the same
488
        return (remote_content == content)
489

490
491
492
493
494
495
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
496
497

        log.info("Submit new server request")
498
499
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
500

501
502
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
503
504
505
506
507
508
509
510
        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"]
511
        cls.username = None
512
513
        cls.passwd = server["adminPass"]

514
515
516
        self.result_dict["Server ID"] = str(server["id"])
        self.result_dict["Password"] = str(server["adminPass"])

517
518
    def test_002a_server_is_building_in_list(self):
        """Test server is in BUILD state, in server list"""
519
520
        log.info("Server in BUILD state in server list")

521
522
        self.result_dict.clear()

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

526
527
528
529
530
531
532
533
        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"""
534
535
536

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

537
538
539
540
541
542
543
        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):
544
545
546

        log.info("Creating server metadata")

547
        image = self.client.get_image_details(self.imageid)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
548
        os_value = image["metadata"]["values"]["os"]
549
        users = image["metadata"]["values"].get("users", None)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
550
        self.client.update_server_metadata(self.serverid, OS=os_value)
John Giannelos's avatar
John Giannelos committed
551

552
        userlist = users.split()
553

554
555
556
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
557
558
559

        if "root" in userlist:
            cls.username = "root"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
560
561
        elif users is None:
            cls.username = self._connect_loginname(os_value)
562
563
564
        else:
            cls.username = choice(userlist)

565
        self.assertIsNotNone(cls.username)
566
567
568

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
569
570
571

        log.info("Verifying image metadata")

572
573
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
574

John Giannelos's avatar
John Giannelos committed
575
        self.assertEqual(servermeta["OS"], imagemeta["os"])
576
577
578

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
579
580
581

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
582
583
        self._insist_on_status_transition(
            "BUILD", "ACTIVE", self.build_fail, self.build_warning)
584

John Giannelos's avatar
John Giannelos committed
585
586
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
587

John Giannelos's avatar
John Giannelos committed
588
589
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
590

John Giannelos's avatar
John Giannelos committed
591
592
593
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
594
595
        sock = self._insist_on_tcp_connection(
            socket.AF_INET, console["host"], console["port"])
John Giannelos's avatar
John Giannelos committed
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621

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

623
624
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
625

626
        log.info("Validate server's IPv4")
627

628
629
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
630
631
632
633

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

634
635
        self.assertEquals(IP(ipv4).version(), 4)

636
637
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
638
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
639

640
        log.info("Validate server's IPv6")
641

642
643
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
644
645
646
647

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

648
        self.assertEquals(IP(ipv6).version(), 6)
649

John Giannelos's avatar
John Giannelos committed
650
651
652
653
    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")
654
        self.result_dict.clear()
John Giannelos's avatar
John Giannelos committed
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675

        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)
676
677
678

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
679
680
681

        log.info("Shutting down server")

682
        self.cyclades.shutdown_server(self.serverid)
683
684
685

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
686
687

        log.info("Waiting until server becomes STOPPED")
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
688
689
        self._insist_on_status_transition(
            "ACTIVE", "STOPPED", self.action_timeout, self.action_timeout)
690
691
692

    def test_010_submit_start_request(self):
        """Test submit start server request"""
693
694
695

        log.info("Starting server")

696
        self.cyclades.start_server(self.serverid)
697
698
699

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
700
701

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

John Giannelos's avatar
John Giannelos committed
705
706
    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
707

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

John Giannelos's avatar
John Giannelos committed
710
        self.test_006_server_responds_to_ping_IPv4()
711

John Giannelos's avatar
John Giannelos committed
712
713
    def test_012_ssh_to_server_IPv4(self):
        """Test SSH to server public IPv4 works, verify hostname"""
714

John Giannelos's avatar
John Giannelos committed
715
716
717
718
        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)
719

John Giannelos's avatar
John Giannelos committed
720
721
722
723
    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")
724

John Giannelos's avatar
John Giannelos committed
725
726
727
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
728

John Giannelos's avatar
John Giannelos committed
729
730
731
732
733
    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
734
        sock = self._insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
735

John Giannelos's avatar
John Giannelos committed
736
737
738
739
        # 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()
740

John Giannelos's avatar
John Giannelos committed
741
742
743
744
    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")
745

John Giannelos's avatar
John Giannelos committed
746
747
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
748
        sock = self._get_tcp_connection(socket.AF_INET6, ipv6, 3389)
749

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

John Giannelos's avatar
John Giannelos committed
754
755
756
    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
757
        self._skipIf(self.personality is None, "No personality file selected")
758

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

John Giannelos's avatar
John Giannelos committed
761
        server = self.client.get_server_details(self.serverid)
762

John Giannelos's avatar
John Giannelos committed
763
764
765
766
767
768
769
        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)
770

771
772
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
773
774
775

        log.info("Deleting server")

776
777
778
779
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
780
781
782

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
783
784
        self._insist_on_status_transition(
            "ACTIVE", "DELETED", self.action_timeout, self.action_timeout)
785
786
787

    def test_019_server_no_longer_in_server_list(self):
        """Test server is no longer in server list"""
788
789
790

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

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


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

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

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

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

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

815
816
        cls.result_dict = dict()

817
818
819
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)
820

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

824
        nics = server["attachments"]["values"]
John Giannelos's avatar
John Giannelos committed
825

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

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
833
        return public_addrs
834

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
835
    def _connect_loginname(self, os_value):
836
        """Return the login name for connections based on the server OS"""
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
837
        if os_value in ("Ubuntu", "Kubuntu", "Fedora"):
838
            return "user"
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
839
        elif os_value in ("windows", "windows_alpha1"):
840
841
842
843
            return "Administrator"
        else:
            return "root"

John Giannelos's avatar
John Giannelos committed
844
    def _ping_once(self, ip):
845

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

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

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

        log.info("Creating test server A")

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

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

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

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

876
877
        self.result_dict["Server A ID"] = str(serverA["id"])
        self.result_dict["Server A password"] = serverA["adminPass"]
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
878

John Giannelos's avatar
John Giannelos committed
879
    def test_00001b_serverA_becomes_active(self):
880
        """Test server becomes ACTIVE"""
881

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

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

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

John Giannelos's avatar
John Giannelos committed
904
        serverB = self.client.create_server(self.servername, self.flavorid,
905
906
                                            self.imageid, personality=None)

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

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

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

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

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

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

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

950
        name = SNF_TEST_PREFIX + TEST_RUN_ID
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
951
952
        #previous_num = len(self.client.list_networks())
        network = self.client.create_network(name, cidr='10.0.0.1/28')
953

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

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

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

964
        #Test if new network is created
John Giannelos's avatar
John Giannelos committed
965
966
967
968
969
970
971
972
973
974
975
976
        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)
977

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

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

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

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

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

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

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
994
995
996
997
998
999
            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
1000
1001

            if (self.networkid in netsA) and (self.networkid in netsB):
1002
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
1003
1004
                break
            elif time.time() > fail_tmout:
1005
1006
1007
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)
Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1008

John Giannelos's avatar
John Giannelos committed
1009
1010
1011
1012
        #Adding private IPs to class attributes
        cls = type(self)
        cls.priv_ip = dict()

Ilias Tsitsimpis's avatar
Ilias Tsitsimpis committed
1013
1014
1015
1016
        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
1017
1018
1019
1020
1021
1022
1023
1024
1025

        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"]
1026
1027

        self.assertTrue(conn_exists)
1028

John Giannelos's avatar
John Giannelos committed
1029
    def test_002a_reboot(self):
1030
1031
1032
1033
        """Rebooting server A"""

        log.info("Rebooting server A")