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

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

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

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

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

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

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

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


74
75
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
red = '\x1b[31m'
yellow = '\x1b[33m'
green = '\x1b[32m'
normal = '\x1b[0m'


class burninFormatter(logging.Formatter):

    err_fmt = red + "ERROR: %(msg)s" + normal
    dbg_fmt = green + "   * %(msg)s" + normal
    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


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

126

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

132
        falseToken = '12345'
133
        c = ComputeClient(API, falseToken)
134

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


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

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

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

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

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

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
175
        keys = frozenset(["os", "description", "size"])
John Giannelos's avatar
John Giannelos committed
176
177
        details = self.client.list_images(detail=True)
        for i in details:
178
179
180
181
182
183
184
185
186
            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")
187

John Giannelos's avatar
John Giannelos committed
188
        cls.client = ComputeClient(API, TOKEN)
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
227
228
        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")
229

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

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

    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"""
John Giannelos's avatar
John Giannelos committed
256
        log.info("Spawning server for image `%s'", %cls.imagename)
257

John Giannelos's avatar
John Giannelos committed
258
259
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
260
261

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

264
265
266
267
268
269
270
271
272
        public_addrs = filter(lambda x: x["id"] == "public",
                              server["addresses"]["values"])
        self.assertEqual(len(public_addrs), 1)
        ipv4_addrs = filter(lambda x: x["version"] == 4,
                            public_addrs[0]["values"])
        self.assertEqual(len(ipv4_addrs), 1)
        return ipv4_addrs[0]["addr"]

    def _get_ipv6(self, server):
273
        """Get the public IPv6 of a server from the detailed server info"""
274
275
276
277
278
279
280
281
        public_addrs = filter(lambda x: x["id"] == "public",
                              server["addresses"]["values"])
        self.assertEqual(len(public_addrs), 1)
        ipv6_addrs = filter(lambda x: x["version"] == 6,
                            public_addrs[0]["values"])
        self.assertEqual(len(ipv6_addrs), 1)
        return ipv6_addrs[0]["addr"]

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

    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)
294
295
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
296
297
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
        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)
327

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

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

359
    def _insist_on_tcp_connection(self, family, host, port):
360
361
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
362
363
364
365
366
367
368
369
370
371
        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):
372
373
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
374
375
376
377
378
        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)
379
380
381
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
382
383

    def _insist_on_ssh_hostname(self, hostip, username, password):
384
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
385
386
387
388
389
390
391
        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))
392

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

405
406
407
408
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

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

414
415
416
        f = open(localpath)
        remote_content = b64encode(f.read())

417
        # Check if files are the same
418
        return (remote_content == content)
419

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

    def test_001_submit_create_server(self):
        """Test submit create server request"""
426
427

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

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

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

448
449
450
451
452
453
454
455
456
457
458
        servers = self.client.list_servers(detail=True)
        servers = filter(lambda x: x["name"] == self.servername, servers)
        self.assertEqual(len(servers), 1)
        server = servers[0]
        self.assertEqual(server["name"], self.servername)
        self.assertEqual(server["flavorRef"], self.flavorid)
        self.assertEqual(server["imageRef"], self.imageid)
        self.assertEqual(server["status"], "BUILD")

    def test_002b_server_is_building_in_details(self):
        """Test server is in BUILD state, in details"""
459
460
461

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

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

        log.info("Creating server metadata")

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

477
        userlist = users.split()
478

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

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

490
        self.assertIsNotNone(cls.username)
491
492
493

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

        log.info("Verifying image metadata")

497
498
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
499

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

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
504
505
506

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

507
        self._insist_on_status_transition("BUILD", "ACTIVE",
508
509
                                         self.build_fail, self.build_warning)

510
511
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
512

513
514
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
515

516
517
518
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
519
        sock = self._insist_on_tcp_connection(socket.AF_INET,
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
                                        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()
547

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

551
        log.info("Validate server's IPv4")
552

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

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

561
        log.info("Validate server's IPv6")
562

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

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

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

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

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

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

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
595
596
597

        log.info("Shutting down server")

598
        self.cyclades.shutdown_server(self.serverid)
599
600
601

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
602
603

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

    def test_010_submit_start_request(self):
        """Test submit start server request"""
610
611
612

        log.info("Starting server")

613
        self.cyclades.start_server(self.serverid)
614
615
616

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
617
618

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

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

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

628
629
630
631
        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"""
632

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

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

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

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

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

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

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

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

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

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

679
680
        server = self.client.get_server_details(self.serverid)

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

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

        log.info("Deleting server")

694
695
696
697
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
698
699
700

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

701
702
703
704
705
706
        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"""
707
708
709

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

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


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

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

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

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

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

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

738
739
740
741
742
743
744
745
746
747
    def _get_ipv4(self, server):
        """Get the public IPv4 of a server from the detailed server info"""

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

749
750
751
752
753
754
755
756
757
    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
758
    def _ping_once(self, ip):
759

John Giannelos's avatar
John Giannelos committed
760
761
762
763
764
765
        """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()
766

John Giannelos's avatar
John Giannelos committed
767
768
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
769
    def test_00001a_submit_create_server_A(self):
770
        """Test submit create server request"""
771
772
773

        log.info("Creating test server A")

774
        serverA = self.client.create_server(self.servername, self.flavorid,
775
                                            self.imageid, personality=None)
776

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

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

787
788
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
789

John Giannelos's avatar
John Giannelos committed
790
    def test_00001b_serverA_becomes_active(self):
791
        """Test server becomes ACTIVE"""
792

793
        log.info("Waiting until test server A becomes ACTIVE")
794

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

812
813
        log.info("Creating test server B")

John Giannelos's avatar
John Giannelos committed
814
        serverB = self.client.create_server(self.servername, self.flavorid,
815
816
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
817
818
819
820
821
822
823
824
825
826
        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"]

827
828
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
829

John Giannelos's avatar
John Giannelos committed
830
    def test_00002b_serverB_becomes_active(self):
831
832
        """Test server becomes ACTIVE"""

833
834
        log.info("Waiting until test server B becomes ACTIVE")

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

849
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
850
        """Test submit create network request"""
851
852
853

        log.info("Submit new network request")

854
        name = SNF_TEST_PREFIX + TEST_RUN_ID
855
        previous_num = len(self.client.list_networks())
856
        network = self.client.create_network(name)
857

858
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
859
        self.assertEqual(network['name'], name)
860

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

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

869
    def test_002_connect_to_network(self):
870
        """Test connect VMs to network"""
871

872
873
        log.info("Connect VMs to private network")

874
875
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
876

877
        #Insist on connecting until action timeout
878
        fail_tmout = time.time() + self.action_timeout
879

880
        while True:
John Giannelos's avatar
John Giannelos committed
881
            connected = (self.client.get_network_details(self.networkid))
882
            connections = connected['servers']['values']
883
884
            if (self.serverid['A'] in connections) \
                    and (self.serverid['B'] in connections):
885
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
886
887
                break
            elif time.time() > fail_tmout:
888
889
890
891
892
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(conn_exists)
893

John Giannelos's avatar
John Giannelos committed
894
    def test_002a_reboot(self):
895
896
897
898
        """Rebooting server A"""

        log.info("Rebooting server A")

899
900
        self.client.shutdown_server(self.serverid['A'])

901
        fail_tmout = time.time() + self.action_timeout
902
903
904
905
906
907
908
909
910
911
912
913
        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'])

914
915
916
917
918
919
920
921
922
923
        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)
924

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

927
928
929
    def test_002b_ping_server_A(self):
        "Test if server A is pingable"

930
931
        log.info("Testing if server A is pingable")

John Giannelos's avatar
John Giannelos committed
932
933
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
934

935
        fail_tmout = time.time() + self.action_timeout
936

John Giannelos's avatar
John Giannelos committed
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
        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)
952
953

    def test_002c_reboot(self):
954
955
956
957
        """Reboot server B"""

        log.info("Rebooting server B")

958
959
        self.client.shutdown_server(self.serverid['B'])

960
        fail_tmout = time.time() + self.action_timeout
961
962
963
964
965
966
967
968
969
970
971
972
        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'])

973
974
975
976
977
978
979
980
981
982
        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)
983

984
        self.assertTrue(active)
985

John Giannelos's avatar
John Giannelos committed
986
    def test_002d_ping_server_B(self):
987
        """Test if server B is pingable"""
988

989
        log.info("Testing if server B is pingable")
John Giannelos's avatar
John Giannelos committed
990
991
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
992

993
        fail_tmout = time.time() + self.action_timeout
994

John Giannelos's avatar
John Giannelos committed
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
        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):
1011
1012
        """Set up eth1 for server A"""

1013
1014
        self._skipIf(self.is_windows, "only valid for Linux servers")

1015
        log.info("Setting up interface eth1 for server A")
1016

1017
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
1018
        image = self.client.get_image_details(self.imageid)
1019
        os = image['metadata']['values']['os']
1020

John Giannelos's avatar
John Giannelos committed
1021
1022
1023
1024
1025
1026
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1027
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1028
1029
        else:
            loginname = choice(userlist)
1030

1031
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
1032
        myPass = self.password['A']
1033

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

John Giannelos's avatar
John Giannelos committed
1036
        res = False
1037

John Giannelos's avatar
John Giannelos committed
1038
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
1039
            with settings(
1040
                hide('warnings', 'running'),
1041
1042
1043
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
1044
1045
                ):

1046
                if len(sudo('ifconfig eth1 192.168.0.12')) == 0:
John Giannelos's avatar
John Giannelos committed
1047
                    res = True
1048

1049
1050
        else:
            with settings(
1051
                hide('warnings', 'running'),
1052
1053
1054
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
1055
                ):
1056

1057
                if len(run('ifconfig eth1 192.168.0.12')) == 0:
1058
                    res = True
John Giannelos's avatar
John Giannelos committed
1059
1060

        self.assertTrue(res)
1061

John Giannelos's avatar
John Giannelos committed
1062
    def test_003b_setup_interface_B(self):
1063
        """Setup eth1 for server B"""
1064

1065
1066
        self._skipIf(self.is_windows, "only valid for Linux servers")

1067
        log.info("Setting up interface eth1 for server B")
1068

1069
        server = self.client.get_server_details(self.serverid['B'])
John Giannelos's avatar
John Giannelos committed
1070
        image = self.client.get_image_details(self.imageid)
1071
        os = image['metadata']['values']['os']
1072

John Giannelos's avatar
John Giannelos committed
1073
1074
1075
1076
1077
1078
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1079
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1080
1081
1082
1083
        else:
            loginname = choice(userlist)

        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
1084
1085
        myPass = self.password['B']

1086
        log.info("SSH in server B as %s/%s" % (loginname, myPass))
1087

John Giannelos's avatar
John Giannelos committed
1088
1089
        res = False

John Giannelos's avatar
John Giannelos committed
1090
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
1091
            with settings(
1092
                hide('warnings', 'running'),
1093
1094
1095
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
1096
                ):
1097

1098
                if len(sudo('ifconfig eth1 192.168.0.13')) == 0: