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
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
94
    dbg_fmt = green + "* %(msg)s" + normal
John Giannelos's avatar
John Giannelos committed
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
    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(["osfamily", "root_partition"])
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"""
256
        log.info("Spawning server for image `%s'" %cls.imagename)
John Giannelos's avatar
John Giannelos committed
257
258
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
259
260

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

263
264
265
266
267
268
269
270
271
        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):
272
        """Get the public IPv6 of a server from the detailed server info"""
273
274
275
276
277
278
279
280
        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"]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

447
448
449
450
451
452
453
454
455
456
457
        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"""
458
459
460

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

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

        log.info("Creating server metadata")

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

476
        userlist = users.split()
477

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

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

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

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

        log.info("Verifying image metadata")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Shutting down server")

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

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

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

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

        log.info("Starting server")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Deleting server")

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

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

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

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

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

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


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

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

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

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

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

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

737
738
739
740
741
742
743
744
745
746
    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"]
747

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

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

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

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

        log.info("Creating test server A")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Submit new network request")

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

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

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

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

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

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

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

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

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

        self.assertTrue(conn_exists)
892

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

        log.info("Rebooting server A")

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

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

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

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

926
    def test_002b_ping_server_A(self):
John Giannelos's avatar
John Giannelos committed
927
        "Test if server A responds to IPv4 pings"
928

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

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

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

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

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

        log.info("Rebooting server B")

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

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

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

983
        self.assertTrue(active)
984

John Giannelos's avatar
John Giannelos committed
985
    def test_002d_ping_server_B(self):
John Giannelos's avatar
John Giannelos committed
986
        """Test if server B responds to IPv4 pings"""
987

John Giannelos's avatar
John Giannelos committed
988
        log.info("Testing if server B responds to IPv4 pings")
John Giannelos's avatar
John Giannelos committed
989
990
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
991

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.assertTrue(res)
1060

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

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

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

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

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

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

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

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

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

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