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

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

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

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

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

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

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

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


74
75
API = None
TOKEN = None
John Giannelos's avatar
John Giannelos committed
76
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
77
78
DEFAULT_PLANKTON = "https://cyclades.okeanos.grnet.gr/plankton"
DEFAULT_PLANKTON_USER = "images@okeanos.grnet.gr"
79

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

John Giannelos's avatar
John Giannelos committed
85
86
87
88
89
90
91
92
red = '\x1b[31m'
yellow = '\x1b[33m'
green = '\x1b[32m'
normal = '\x1b[0m'

class burninFormatter(logging.Formatter):

    err_fmt = red + "ERROR: %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
93
    dbg_fmt = green + "* %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    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

118
log = logging.getLogger("burnin")
John Giannelos's avatar
John Giannelos committed
119
120
121
122
log.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(burninFormatter())
log.addHandler(handler)
123

124

125
126
127
class UnauthorizedTestCase(unittest.TestCase):
    def test_unauthorized_access(self):
        """Test access without a valid token fails"""
128
129
        log.info("Authentication test")

130
        falseToken = '12345'
131
        c = ComputeClient(API, falseToken)
132

133
134
        with self.assertRaises(ClientError) as cm:
            c.list_servers()
John Giannelos's avatar
John Giannelos committed
135
            self.assertEqual(cm.exception.status, 401)
136
137
138
139
140
141
142
143


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

John Giannelos's avatar
John Giannelos committed
145
        cls.client = ComputeClient(API, TOKEN)
146
147
148
        cls.plankton = ImageClient(PLANKTON, TOKEN)
        cls.images = cls.plankton.list_public()
        cls.dimages = cls.plankton.list_public(detail=True)
149
150
151

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

154
155
    def test_002_list_images_detailed(self):
        """Test detailed image list is the same length as list"""
John Giannelos's avatar
John Giannelos committed
156
        self.assertEqual(len(self.dimages), len(self.images))
157

158
159
160
161
162
163
164
    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):
165
166
167
168
        """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))
169
170
171
172
        self.assertEqual(sorted(list(set(names))), names)

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
173
        keys = frozenset(["osfamily", "root_partition"])
John Giannelos's avatar
John Giannelos committed
174
175
        details = self.client.list_images(detail=True)
        for i in details:
176
177
178
179
180
181
182
183
184
            self.assertTrue(keys.issubset(i["metadata"]["values"].keys()))


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

John Giannelos's avatar
John Giannelos committed
186
        cls.client = ComputeClient(API, TOKEN)
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
        cls.flavors = cls.client.list_flavors()
        cls.dflavors = cls.client.list_flavors(detail=True)

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

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

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

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

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

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

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


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

John Giannelos's avatar
John Giannelos committed
228
        cls.client = ComputeClient(API, TOKEN)
229
230
231
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)

232
233
234
    # def test_001_list_servers(self):
    #     """Test server list actually returns servers"""
    #     self.assertGreater(len(self.servers), 0)
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253

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

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


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

    @classmethod
    def setUpClass(cls):
        """Initialize a kamaki instance"""
254
        log.info("Spawning server for image `%s'" %cls.imagename)
John Giannelos's avatar
John Giannelos committed
255
256
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
257
258

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

John Giannelos's avatar
John Giannelos committed
261
262
263
        public_addrs = filter(lambda x: x["network_id"] == "public",
                              server["attachments"]["values"])

264
        self.assertEqual(len(public_addrs), 1)
John Giannelos's avatar
John Giannelos committed
265
266
267
268

        self.assertTrue(public_addrs[0]['ipv4'] != None)

        return public_addrs[0]['ipv4']
269
270

    def _get_ipv6(self, server):
271
        """Get the public IPv6 of a server from the detailed server info"""
John Giannelos's avatar
John Giannelos committed
272
273
274
275

        public_addrs = filter(lambda x: x["network_id"] == "public",
                              server["attachments"]["values"])

276
        self.assertEqual(len(public_addrs), 1)
John Giannelos's avatar
John Giannelos committed
277
278
279
280
281
282

        self.assertTrue(public_addrs[0]['ipv6'] != None)

        return public_addrs[0]['ipv6']


283

284
285
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
286
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
287
            return "user"
288
        elif os in ("windows", "windows_alpha1"):
289
            return "Administrator"
290
        else:
291
            return "root"
292
293
294
295

    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)
296
297
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
        self.assertEquals(server["status"], new_status)

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

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

330
331
332
333
334
335
336
337
338
339
    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)
340
        return lines[0]
341
342
343
344

    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
                                   opmsg, callable, *args, **kwargs):
        if warn_timeout == fail_timeout:
345
346
347
348
            warn_timeout = fail_timeout + 1
        warn_tmout = time.time() + warn_timeout
        fail_tmout = time.time() + fail_timeout
        while True:
349
            self.assertLess(time.time(), fail_tmout,
350
                            "operation `%s' timed out" % opmsg)
351
            if time.time() > warn_tmout:
352
353
                log.warning("Server %d: `%s' operation `%s' not done yet",
                            self.serverid, self.servername, opmsg)
354
            try:
355
                log.info("%s... " % opmsg)
356
357
358
                return callable(*args, **kwargs)
            except AssertionError:
                pass
359
360
            time.sleep(self.query_interval)

361
    def _insist_on_tcp_connection(self, family, host, port):
362
363
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
364
365
366
367
368
369
370
371
372
373
        msg = "connect over %s to %s:%s" % \
              (familystr.get(family, "Unknown"), host, port)
        sock = self._try_until_timeout_expires(
                self.action_timeout, self.action_timeout,
                msg, self._get_connected_tcp_socket,
                family, host, port)
        return sock

    def _insist_on_status_transition(self, current_status, new_status,
                                    fail_timeout, warn_timeout=None):
374
375
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
376
377
378
379
380
        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)
381
382
383
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
384
385

    def _insist_on_ssh_hostname(self, hostip, username, password):
386
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
387
388
389
390
391
392
393
        hostname = self._try_until_timeout_expires(
                self.action_timeout, self.action_timeout,
                msg, self._get_hostname_over_ssh,
                hostip, username, password)

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

395
396
397
398
    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)
399
        log.info(msg)
400
401
402
403
404
405
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
406

407
408
409
410
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
411
        sftp = paramiko.SFTPClient.from_transport(transport)
412
        sftp.get(remotepath, localpath)
413
414
415
        sftp.close()
        transport.close()

416
417
418
        f = open(localpath)
        remote_content = b64encode(f.read())

419
        # Check if files are the same
420
        return (remote_content == content)
421

422
423
424
425
426
427
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
428
429

        log.info("Submit new server request")
430
431
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
432

433
434
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
435
436
437
438
439
440
441
442
        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"]
443
        cls.username = None
444
445
446
447
        cls.passwd = server["adminPass"]

    def test_002a_server_is_building_in_list(self):
        """Test server is in BUILD state, in server list"""
448
449
        log.info("Server in BUILD state in server list")

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

453
454
455
456
457
458
459
460
        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"""
461
462
463

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

464
465
466
467
468
469
470
        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):
471
472
473

        log.info("Creating server metadata")

474
        image = self.client.get_image_details(self.imageid)
475
        os = image["metadata"]["values"]["os"]
476
        users = image["metadata"]["values"].get("users", None)
477
        self.client.update_server_metadata(self.serverid, OS=os)
John Giannelos's avatar
John Giannelos committed
478

479
        userlist = users.split()
480

481
482
483
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
484
485
486
487

        if "root" in userlist:
            cls.username = "root"
        elif users == None:
488
            cls.username = self._connect_loginname(os)
489
490
491
        else:
            cls.username = choice(userlist)

492
        self.assertIsNotNone(cls.username)
493
494
495

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
496
497
498

        log.info("Verifying image metadata")

499
500
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
501

John Giannelos's avatar
John Giannelos committed
502
        self.assertEqual(servermeta["OS"], imagemeta["os"])
503
504
505

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
506
507
508

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

509
        self._insist_on_status_transition("BUILD", "ACTIVE",
510
511
                                         self.build_fail, self.build_warning)

John Giannelos's avatar
John Giannelos committed
512
513
    # def test_003a_get_server_oob_console(self):
    #     """Test getting OOB server console over VNC
514

John Giannelos's avatar
John Giannelos committed
515
516
    #     Implementation of RFB protocol follows
    #     http://www.realvnc.com/docs/rfbproto.pdf.
517

John Giannelos's avatar
John Giannelos committed
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
    #     """
    #     console = self.cyclades.get_server_console(self.serverid)
    #     self.assertEquals(console['type'], "vnc")
    #     sock = self._insist_on_tcp_connection(socket.AF_INET,
    #                                     console["host"], console["port"])

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

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

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

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

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

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

550
551
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
552

553
        log.info("Validate server's IPv4")
554

555
556
557
558
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
        self.assertEquals(IP(ipv4).version(), 4)

559
560
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
561
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
562

563
        log.info("Validate server's IPv6")
564

565
566
567
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        self.assertEquals(IP(ipv6).version(), 6)
568
569
570

    def test_006_server_responds_to_ping_IPv4(self):
        """Test server responds to ping on IPv4 address"""
571
572
573

        log.info("Testing if server responds to pings in IPv4")

574
        server = self.client.get_server_details(self.serverid)
575
576
577
        ip = self._get_ipv4(server)
        self._try_until_timeout_expires(self.action_timeout,
                                        self.action_timeout,
578
579
                                        "PING IPv4 to %s" % ip,
                                        self._ping_once,
580
                                        False, ip)
581

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

587
588
589
590
591
592
593
        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)
594
595
596

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
597
598
599

        log.info("Shutting down server")

600
        self.cyclades.shutdown_server(self.serverid)
601
602
603

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
604
605

        log.info("Waiting until server becomes STOPPED")
606
607
        self._insist_on_status_transition("ACTIVE", "STOPPED",
                                         self.action_timeout,
608
609
610
611
                                         self.action_timeout)

    def test_010_submit_start_request(self):
        """Test submit start server request"""
612
613
614

        log.info("Starting server")

615
        self.cyclades.start_server(self.serverid)
616
617
618

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
619
620

        log.info("Waiting until server becomes ACTIVE")
621
622
        self._insist_on_status_transition("STOPPED", "ACTIVE",
                                         self.action_timeout,
623
624
625
626
                                         self.action_timeout)

    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
627
628
629

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

630
631
632
633
        self.test_006_server_responds_to_ping_IPv4()

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

635
        self._skipIf(self.is_windows, "only valid for Linux servers")
636
        server = self.client.get_server_details(self.serverid)
637
638
        self._insist_on_ssh_hostname(self._get_ipv4(server),
                                     self.username, self.passwd)
639

640
641
642
    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")
643
644
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

645
646
647
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
648
649
650

    def test_014_rdp_to_server_IPv4(self):
        "Test RDP connection to server public IPv4 works"""
651
        self._skipIf(not self.is_windows, "only valid for Windows servers")
652
653
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
654
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
655
656
657

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

661
662
663
    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")
664
665
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

666
667
668
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
669

670
671
672
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        sock.close()
673
674
675

    def test_016_personality_is_enforced(self):
        """Test file injection for personality enforcement"""
John Giannelos's avatar
John Giannelos committed
676
677
        self._skipIf(self.is_windows, "only implemented for Linux servers")
        self._skipIf(self.personality == None, "No personality file selected")
678
679
680

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

681
682
        server = self.client.get_server_details(self.serverid)

683
        for inj_file in self.personality:
684
685
686
687
688
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
                                                       inj_file['owner'],
                                                       self.passwd,
                                                       inj_file['path'],
                                                       inj_file['contents'])
689
            self.assertTrue(equal_files)
690

691
692
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
693
694
695

        log.info("Deleting server")

696
697
698
699
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
700
701
702

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

703
704
705
706
707
708
        self._insist_on_status_transition("ACTIVE", "DELETED",
                                         self.action_timeout,
                                         self.action_timeout)

    def test_019_server_no_longer_in_server_list(self):
        """Test server is no longer in server list"""
709
710
711

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

712
        servers = self.client.list_servers()
713
714
715
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


716
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
717
    """ Testing networking in cyclades """
718

719
    @classmethod
John Giannelos's avatar
John Giannelos committed
720
721
    def setUpClass(cls):
        "Initialize kamaki, get list of current networks"
John Giannelos's avatar
John Giannelos committed
722
723
724

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

726
727
728
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
                                          TEST_RUN_ID,
                                          cls.imagename)
729
730
731
732
733

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

    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)
739

740
741
742
    def _get_ipv4(self, server):
        """Get the public IPv4 of a server from the detailed server info"""

John Giannelos's avatar
John Giannelos committed
743
744
745
        public_addrs = filter(lambda x: x["network_id"] == "public",
                              server["attachments"]["values"])

746
        self.assertEqual(len(public_addrs), 1)
John Giannelos's avatar
John Giannelos committed
747
748
749
750

        self.assertTrue(public_addrs[0]['ipv4'] != None)

        return public_addrs[0]['ipv4']
751

752
753
754
755
756
757
758
759
760
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
            return "user"
        elif os in ("windows", "windows_alpha1"):
            return "Administrator"
        else:
            return "root"

John Giannelos's avatar
John Giannelos committed
761
    def _ping_once(self, ip):
762

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

John Giannelos's avatar
John Giannelos committed
770
771
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
772
    def test_00001a_submit_create_server_A(self):
773
        """Test submit create server request"""
774
775
776

        log.info("Creating test server A")

777
        serverA = self.client.create_server(self.servername, self.flavorid,
778
                                            self.imageid, personality=None)
779

John Giannelos's avatar
John Giannelos committed
780
781
782
783
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
784
785

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
786
787
788
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
789

790
791
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
792

John Giannelos's avatar
John Giannelos committed
793
    def test_00001b_serverA_becomes_active(self):
794
        """Test server becomes ACTIVE"""
795

796
        log.info("Waiting until test server A becomes ACTIVE")
797

798
        fail_tmout = time.time() + self.action_timeout
799
800
801
802
803
804
805
806
807
808
809
810
811
        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
812
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
813
        """Test submit create server request"""
814

815
816
        log.info("Creating test server B")

John Giannelos's avatar
John Giannelos committed
817
        serverB = self.client.create_server(self.servername, self.flavorid,
818
819
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
820
821
822
823
824
825
826
827
828
829
        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"]

830
831
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
832

John Giannelos's avatar
John Giannelos committed
833
    def test_00002b_serverB_becomes_active(self):
834
835
        """Test server becomes ACTIVE"""

836
837
        log.info("Waiting until test server B becomes ACTIVE")

838
        fail_tmout = time.time() + self.action_timeout
839
840
841
842
843
844
845
846
847
848
849
850
        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
851

852
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
853
        """Test submit create network request"""
854
855
856

        log.info("Submit new network request")

857
        name = SNF_TEST_PREFIX + TEST_RUN_ID
858
        previous_num = len(self.client.list_networks())
John Giannelos's avatar
John Giannelos committed
859
        network = self.client.create_network(name,cidr='10.0.0.1/28')
860

861
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
862
        self.assertEqual(network['name'], name)
863

864
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
865
866
        cls = type(self)
        cls.networkid = network['id']
867
868
        networks = self.client.list_networks()

John Giannelos's avatar
John Giannelos committed
869
870
        fail_tmout = time.time() + self.action_timeout

871
        #Test if new network is created
John Giannelos's avatar
John Giannelos committed
872
873
874
875
876
877
878
879
880
881
882
883
        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)
884

885
    def test_002_connect_to_network(self):
886
        """Test connect VMs to network"""
887

888
889
        log.info("Connect VMs to private network")

890
891
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
892

893
        #Insist on connecting until action timeout
894
        fail_tmout = time.time() + self.action_timeout
895

896
        while True:
John Giannelos's avatar
John Giannelos committed
897
898
899
900
901

            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']]

            if (self.networkid in netsA) and (self.networkid in netsB):
902
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
903
904
                break
            elif time.time() > fail_tmout:
905
906
907
908
909
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(conn_exists)
910

John Giannelos's avatar
John Giannelos committed
911
    def test_002a_reboot(self):
912
913
914
915
        """Rebooting server A"""

        log.info("Rebooting server A")

916
917
        self.client.shutdown_server(self.serverid['A'])

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

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

931
932
933
934
935
936
937
938
939
940
        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)
941

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

944
    def test_002b_ping_server_A(self):
John Giannelos's avatar
John Giannelos committed
945
        "Test if server A responds to IPv4 pings"
946

John Giannelos's avatar
John Giannelos committed
947
        log.info("Testing if server A responds to IPv4 pings ")
948

John Giannelos's avatar
John Giannelos committed
949
950
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
951

952
        fail_tmout = time.time() + self.action_timeout
953

John Giannelos's avatar
John Giannelos committed
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
        s = False

        while True:

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

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

            else:
                time.sleep(self.query_interval)

        self.assertTrue(s)
969
970

    def test_002c_reboot(self):
971
972
973
974
        """Reboot server B"""

        log.info("Rebooting server B")

975
976
        self.client.shutdown_server(self.serverid['B'])

977
        fail_tmout = time.time() + self.action_timeout
978
979
980
981
982
983
984
985
986
987
988
989
        while True:
            d = self.client.get_server_details(self.serverid['B'])
            status = d['status']
            if status == 'STOPPED':
                break
            elif time.time() > fail_tmout:
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

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

990
991
992
993
994
995
996
997
998
999
        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)
1000

1001
        self.assertTrue(active)
1002

John Giannelos's avatar
John Giannelos committed
1003
    def test_002d_ping_server_B(self):
John Giannelos's avatar
John Giannelos committed
1004
        """Test if server B responds to IPv4 pings"""
1005

John Giannelos's avatar
John Giannelos committed
1006
        log.info("Testing if server B responds to IPv4 pings")
John Giannelos's avatar
John Giannelos committed
1007
1008
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
1009

1010
        fail_tmout = time.time() + self.action_timeout
1011

John Giannelos's avatar
John Giannelos committed
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
        s = False

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

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

            else:
                time.sleep(self.query_interval)

        self.assertTrue(s)

    def test_003a_setup_interface_A(self):
1028
1029
        """Set up eth1 for server A"""

1030
1031
        self._skipIf(self.is_windows, "only valid for Linux servers")

1032
        log.info("Setting up interface eth1 for server A")
1033

1034
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
1035
        image = self.client.get_image_details(self.imageid)
1036
        os = image['metadata']['values']['os']
1037

John Giannelos's avatar
John Giannelos committed
1038
1039
1040
1041
1042
1043
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1044
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1045
1046
        else:
            loginname = choice(userlist)
1047

1048
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
1049
        myPass = self.password['A']
1050

1051
        log.info("SSH in server A as %s/%s" % (loginname, myPass))
1052

John Giannelos's avatar
John Giannelos committed
1053
        res = False
1054

John Giannelos's avatar
John Giannelos committed
1055
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
1056
            with settings(
1057
                hide('warnings', 'running'),
1058
1059
1060
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
1061
1062
                ):

John Giannelos's avatar
John Giannelos committed
1063
                if len(sudo('ifconfig eth1 10.0.0.5')) == 0:
John Giannelos's avatar
John Giannelos committed
1064
                    res = True
1065

1066
1067
        else:
            with settings(
1068
                hide('warnings', 'running'),
1069
1070
1071
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
1072
                ):
1073

John Giannelos's avatar
John Giannelos committed
1074
                if len(run('ifconfig eth1 10.0.0.5')) == 0:
1075
                    res = True
John Giannelos's avatar
John Giannelos committed
1076
1077

        self.assertTrue(res)
1078

John Giannelos's avatar
John Giannelos committed
1079
    def test_003b_setup_interface_B(self):
1080
        """Setup eth1 for server B"""
1081

1082
1083
        self._skipIf(self.is_windows, "only valid for Linux servers")

1084
        log.info("Setting up interface eth1 for server B")
1085

1086
        server = self.client.get_server_details(self.serverid['B'])
John Giannelos's avatar
John Giannelos committed
1087
        image = self.client.get_image_details(self.imageid)
1088
        os = image['metadata']['values']['os']
1089

John Giannelos's avatar
John Giannelos committed
1090
1091
1092
1093
1094
1095
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1096
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1097
1098
1099
1100
        else:
            loginname = choice(userlist)

        hostip = self._get_ipv4(server)