burnin.py 59 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
85
86

# Setup logging (FIXME - verigak)
logging.basicConfig(format="%(message)s")
87
log = logging.getLogger("burnin")
88
89
log.setLevel(logging.INFO)

90

91
92
93
class UnauthorizedTestCase(unittest.TestCase):
    def test_unauthorized_access(self):
        """Test access without a valid token fails"""
94
95
        log.info("Authentication test")

96
        falseToken = '12345'
97
        c = ComputeClient(API, falseToken)
98

99
100
        with self.assertRaises(ClientError) as cm:
            c.list_servers()
John Giannelos's avatar
John Giannelos committed
101
            self.assertEqual(cm.exception.status, 401)
102
103
104
105
106
107
108
109


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

John Giannelos's avatar
John Giannelos committed
111
        cls.client = ComputeClient(API, TOKEN)
112
113
114
        cls.plankton = ImageClient(PLANKTON, TOKEN)
        cls.images = cls.plankton.list_public()
        cls.dimages = cls.plankton.list_public(detail=True)
115
116
117

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

120
121
    def test_002_list_images_detailed(self):
        """Test detailed image list is the same length as list"""
John Giannelos's avatar
John Giannelos committed
122
        self.assertEqual(len(self.dimages), len(self.images))
123

124
125
126
127
128
129
130
    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):
131
132
133
134
        """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))
135
136
137
138
        self.assertEqual(sorted(list(set(names))), names)

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
139
        keys = frozenset(["os", "description", "size"])
John Giannelos's avatar
John Giannelos committed
140
141
        details = self.client.list_images(detail=True)
        for i in details:
142
143
144
145
146
147
148
149
150
            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")
151

John Giannelos's avatar
John Giannelos committed
152
        cls.client = ComputeClient(API, TOKEN)
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
        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")
193

John Giannelos's avatar
John Giannelos committed
194
        cls.client = ComputeClient(API, TOKEN)
195
196
197
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)

198
199
200
    # def test_001_list_servers(self):
    #     """Test server list actually returns servers"""
    #     self.assertGreater(len(self.servers), 0)
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

    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"""
220
        log.info("Spawning server for image `%s'", cls.imagename)
221

John Giannelos's avatar
John Giannelos committed
222
223
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
224
225

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

228
229
230
231
232
233
234
235
236
        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):
237
        """Get the public IPv6 of a server from the detailed server info"""
238
239
240
241
242
243
244
245
        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"]

246
247
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
248
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
249
            return "user"
250
        elif os in ("windows", "windows_alpha1"):
251
            return "Administrator"
252
        else:
253
            return "root"
254
255
256
257

    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)
258
259
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
        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)
291

292
293
294
295
296
297
298
299
300
301
    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)
302
        return lines[0]
303
304
305
306

    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
                                   opmsg, callable, *args, **kwargs):
        if warn_timeout == fail_timeout:
307
308
309
310
            warn_timeout = fail_timeout + 1
        warn_tmout = time.time() + warn_timeout
        fail_tmout = time.time() + fail_timeout
        while True:
311
            self.assertLess(time.time(), fail_tmout,
312
                            "operation `%s' timed out" % opmsg)
313
            if time.time() > warn_tmout:
314
315
                log.warning("Server %d: `%s' operation `%s' not done yet",
                            self.serverid, self.servername, opmsg)
316
            try:
317
                log.info("%s... " % opmsg)
318
319
320
                return callable(*args, **kwargs)
            except AssertionError:
                pass
321
322
            time.sleep(self.query_interval)

323
    def _insist_on_tcp_connection(self, family, host, port):
324
325
        familystr = {socket.AF_INET: "IPv4", socket.AF_INET6: "IPv6",
                     socket.AF_UNSPEC: "Unspecified-IPv4/6"}
326
327
328
329
330
331
332
333
334
335
        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):
336
337
        msg = "Server %d: `%s', waiting for %s -> %s" % \
              (self.serverid, self.servername, current_status, new_status)
338
339
340
341
342
        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)
343
344
345
        # Ensure the status is actually the expected one
        server = self.client.get_server_details(self.serverid)
        self.assertEquals(server["status"], new_status)
346
347

    def _insist_on_ssh_hostname(self, hostip, username, password):
348
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
349
350
351
352
353
354
355
        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))
356

357
358
359
360
    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)
361
        log.info(msg)
362
363
364
365
366
367
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
368

369
370
371
372
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
373
        sftp = paramiko.SFTPClient.from_transport(transport)
374
        sftp.get(remotepath, localpath)
375
376
377
        sftp.close()
        transport.close()

378
379
380
        f = open(localpath)
        remote_content = b64encode(f.read())

381
        # Check if files are the same
382
        return (remote_content == content)
383

384
385
386
387
388
389
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
390
391

        log.info("Submit new server request")
392
393
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
394

395
396
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
397
398
399
400
401
402
403
404
        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"]
405
        cls.username = None
406
407
408
409
        cls.passwd = server["adminPass"]

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

412
413
414
415
416
417
418
419
420
421
422
        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"""
423
424
425

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

426
427
428
429
430
431
432
        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):
433
434
435

        log.info("Creating server metadata")

436
        image = self.client.get_image_details(self.imageid)
437
        os = image["metadata"]["values"]["os"]
438
        users = image["metadata"]["values"].get("users", None)
439
        self.client.update_server_metadata(self.serverid, OS=os)
440
441
        
        userlist = users.split()
442

443
444
445
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
446
447
448
449

        if "root" in userlist:
            cls.username = "root"
        elif users == None:
450
            cls.username = self._connect_loginname(os)
451
452
453
        else:
            cls.username = choice(userlist)

454
        self.assertIsNotNone(cls.username)
455
456
457

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
458
459
460

        log.info("Verifying image metadata")

461
462
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
463

John Giannelos's avatar
John Giannelos committed
464
        self.assertEqual(servermeta["OS"], imagemeta["os"])
465
466
467

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
468
469
470

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

471
        self._insist_on_status_transition("BUILD", "ACTIVE",
472
473
                                         self.build_fail, self.build_warning)

474
475
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
476

477
478
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
479

480
481
482
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
483
        sock = self._insist_on_tcp_connection(socket.AF_INET,
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
                                        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()
511

512
513
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
514

515
        log.info("Validate server's IPv4")
516

517
518
519
520
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
        self.assertEquals(IP(ipv4).version(), 4)

521
522
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
523
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
524

525
        log.info("Validate server's IPv6")
526

527
528
529
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        self.assertEquals(IP(ipv6).version(), 6)
530
531
532

    def test_006_server_responds_to_ping_IPv4(self):
        """Test server responds to ping on IPv4 address"""
533
534
535

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

536
        server = self.client.get_server_details(self.serverid)
537
538
539
        ip = self._get_ipv4(server)
        self._try_until_timeout_expires(self.action_timeout,
                                        self.action_timeout,
540
541
                                        "PING IPv4 to %s" % ip,
                                        self._ping_once,
542
                                        False, ip)
543

544
545
    def test_007_server_responds_to_ping_IPv6(self):
        """Test server responds to ping on IPv6 address"""
546
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")
547
548
        log.info("Testing if server responds to pings in IPv6")

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

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
559
560
561

        log.info("Shutting down server")

562
        self.cyclades.shutdown_server(self.serverid)
563
564
565

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
566
567

        log.info("Waiting until server becomes STOPPED")
568
569
        self._insist_on_status_transition("ACTIVE", "STOPPED",
                                         self.action_timeout,
570
571
572
573
                                         self.action_timeout)

    def test_010_submit_start_request(self):
        """Test submit start server request"""
574
575
576

        log.info("Starting server")

577
        self.cyclades.start_server(self.serverid)
578
579
580

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
581
582

        log.info("Waiting until server becomes ACTIVE")
583
584
        self._insist_on_status_transition("STOPPED", "ACTIVE",
                                         self.action_timeout,
585
586
587
588
                                         self.action_timeout)

    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
589
590
591

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

592
593
594
595
        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"""
596

597
        self._skipIf(self.is_windows, "only valid for Linux servers")
598
        server = self.client.get_server_details(self.serverid)
599
600
        self._insist_on_ssh_hostname(self._get_ipv4(server),
                                     self.username, self.passwd)
601

602
603
604
    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")
605
606
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

607
608
609
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
610
611
612

    def test_014_rdp_to_server_IPv4(self):
        "Test RDP connection to server public IPv4 works"""
613
        self._skipIf(not self.is_windows, "only valid for Windows servers")
614
615
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
616
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
617
618
619

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

623
624
625
    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")
626
627
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

628
629
630
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
631

632
633
634
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        sock.close()
635
636
637

    def test_016_personality_is_enforced(self):
        """Test file injection for personality enforcement"""
John Giannelos's avatar
John Giannelos committed
638
639
        self._skipIf(self.is_windows, "only implemented for Linux servers")
        self._skipIf(self.personality == None, "No personality file selected")
640
641
642

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

643
644
        server = self.client.get_server_details(self.serverid)

645
        for inj_file in self.personality:
646
647
648
649
650
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
                                                       inj_file['owner'],
                                                       self.passwd,
                                                       inj_file['path'],
                                                       inj_file['contents'])
651
            self.assertTrue(equal_files)
652

653
654
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
655
656
657

        log.info("Deleting server")

658
659
660
661
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
662
663
664

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

665
666
667
668
669
670
        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"""
671
672
673

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

674
        servers = self.client.list_servers()
675
676
677
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


678
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
679
    """ Testing networking in cyclades """
680

681
    @classmethod
John Giannelos's avatar
John Giannelos committed
682
683
    def setUpClass(cls):
        "Initialize kamaki, get list of current networks"
John Giannelos's avatar
John Giannelos committed
684
685
686

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

688
689
690
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
                                          TEST_RUN_ID,
                                          cls.imagename)
691
692
693
694
695

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

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

702
703
704
705
706
707
708
709
710
711
    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"]
712

713
714
715
716
717
718
719
720
721
    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
722
    def _ping_once(self, ip):
723

John Giannelos's avatar
John Giannelos committed
724
725
726
727
728
729
        """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()
730

John Giannelos's avatar
John Giannelos committed
731
732
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
733
    def test_00001a_submit_create_server_A(self):
734
        """Test submit create server request"""
735
736
737

        log.info("Creating test server A")

738
        serverA = self.client.create_server(self.servername, self.flavorid,
739
                                            self.imageid, personality=None)
740

John Giannelos's avatar
John Giannelos committed
741
742
743
744
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
745
746

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
747
748
749
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
750

751
752
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
753

John Giannelos's avatar
John Giannelos committed
754
    def test_00001b_serverA_becomes_active(self):
755
        """Test server becomes ACTIVE"""
756

757
        log.info("Waiting until test server A becomes ACTIVE")
758

759
        fail_tmout = time.time() + self.action_timeout
760
761
762
763
764
765
766
767
768
769
770
771
772
        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
773
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
774
        """Test submit create server request"""
775

776
777
        log.info("Creating test server B")

John Giannelos's avatar
John Giannelos committed
778
        serverB = self.client.create_server(self.servername, self.flavorid,
779
780
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
781
782
783
784
785
786
787
788
789
790
        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"]

791
792
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
793

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

797
798
        log.info("Waiting until test server B becomes ACTIVE")

799
        fail_tmout = time.time() + self.action_timeout
800
801
802
803
804
805
806
807
808
809
810
811
        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
812

813
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
814
        """Test submit create network request"""
815
816
817

        log.info("Submit new network request")

818
        name = SNF_TEST_PREFIX + TEST_RUN_ID
819
        previous_num = len(self.client.list_networks())
820
        network = self.client.create_network(name)
821

822
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
823
        self.assertEqual(network['name'], name)
824

825
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
826
827
        cls = type(self)
        cls.networkid = network['id']
828
829
830
831
        networks = self.client.list_networks()

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

833
    def test_002_connect_to_network(self):
834
        """Test connect VMs to network"""
835

836
837
        log.info("Connect VMs to private network")

838
839
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
840

841
        #Insist on connecting until action timeout
842
        fail_tmout = time.time() + self.action_timeout
843

844
        while True:
John Giannelos's avatar
John Giannelos committed
845
            connected = (self.client.get_network_details(self.networkid))
846
            connections = connected['servers']['values']
847
848
            if (self.serverid['A'] in connections) \
                    and (self.serverid['B'] in connections):
849
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
850
851
                break
            elif time.time() > fail_tmout:
852
853
854
855
856
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(conn_exists)
857

John Giannelos's avatar
John Giannelos committed
858
    def test_002a_reboot(self):
859
860
861
862
        """Rebooting server A"""

        log.info("Rebooting server A")

863
864
        self.client.shutdown_server(self.serverid['A'])

865
        fail_tmout = time.time() + self.action_timeout
866
867
868
869
870
871
872
873
874
875
876
877
        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'])

878
879
880
881
882
883
884
885
886
887
        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)
888

889
        self.assertTrue(active)
John Giannelos's avatar
John Giannelos committed
890

891
892
893
    def test_002b_ping_server_A(self):
        "Test if server A is pingable"

894
895
        log.info("Testing if server A is pingable")

John Giannelos's avatar
John Giannelos committed
896
897
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
898

899
        fail_tmout = time.time() + self.action_timeout
900

John Giannelos's avatar
John Giannelos committed
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
        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)
916
917

    def test_002c_reboot(self):
918
919
920
921
        """Reboot server B"""

        log.info("Rebooting server B")

922
923
        self.client.shutdown_server(self.serverid['B'])

924
        fail_tmout = time.time() + self.action_timeout
925
926
927
928
929
930
931
932
933
934
935
936
        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'])

937
938
939
940
941
942
943
944
945
946
        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)
947

948
        self.assertTrue(active)
949

John Giannelos's avatar
John Giannelos committed
950
    def test_002d_ping_server_B(self):
951
        """Test if server B is pingable"""
952

953
        log.info("Testing if server B is pingable")
John Giannelos's avatar
John Giannelos committed
954
955
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
956

957
        fail_tmout = time.time() + self.action_timeout
958

John Giannelos's avatar
John Giannelos committed
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
        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):
975
976
        """Set up eth1 for server A"""

977
978
        self._skipIf(self.is_windows, "only valid for Linux servers")

979
        log.info("Setting up interface eth1 for server A")
980

981
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
982
        image = self.client.get_image_details(self.imageid)
983
        os = image['metadata']['values']['os']
984

John Giannelos's avatar
John Giannelos committed
985
986
987
988
989
990
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
991
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
992
993
        else:
            loginname = choice(userlist)
994

995
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
996
        myPass = self.password['A']
997

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

John Giannelos's avatar
John Giannelos committed
1000
        res = False
1001

John Giannelos's avatar
John Giannelos committed
1002
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
1003
            with settings(
1004
                hide('warnings', 'running'),
1005
1006
1007
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
1008
1009
                ):

1010
                if len(sudo('ifconfig eth1 192.168.0.12')) == 0:
John Giannelos's avatar
John Giannelos committed
1011
                    res = True
1012

1013
1014
        else:
            with settings(
1015
                hide('warnings', 'running'),
1016
1017
1018
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
1019
                ):
1020

1021
                if len(run('ifconfig eth1 192.168.0.12')) == 0:
1022
                    res = True
John Giannelos's avatar
John Giannelos committed
1023
1024

        self.assertTrue(res)
1025

John Giannelos's avatar
John Giannelos committed
1026
    def test_003b_setup_interface_B(self):
1027
        """Setup eth1 for server B"""
1028

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

1031
        log.info("Setting up interface eth1 for server B")
1032

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

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

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

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

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

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

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

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

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

1073
                if len(run('ifconfig eth1 192.168.0.13')) == 0:
1074
1075
                    res = True