burnin.py 55.7 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
54

John Giannelos's avatar
John Giannelos committed
55
56
from kamaki.clients.compute import ComputeClient
from kamaki.clients.cyclades import CycladesClient
John Giannelos's avatar
John Giannelos committed
57
from kamaki.clients import ClientError
John Giannelos's avatar
John Giannelos committed
58

John Giannelos's avatar
John Giannelos committed
59
from fabric.api import *
60

61
from vncauthproxy.d3des import generate_response as d3des_generate_response
62
63
64
65
66

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


72
73
API = None
TOKEN = None
John Giannelos's avatar
John Giannelos committed
74
DEFAULT_API = "https://cyclades.okeanos.grnet.gr/api/v1.1"
75

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

# Setup logging (FIXME - verigak)
logging.basicConfig(format="%(message)s")
83
log = logging.getLogger("burnin")
84
85
log.setLevel(logging.INFO)

86

87
88
89
class UnauthorizedTestCase(unittest.TestCase):
    def test_unauthorized_access(self):
        """Test access without a valid token fails"""
90
91
        log.info("Authentication test")

92
        falseToken = '12345'
93
        c = ComputeClient(API, falseToken)
94

95
96
        with self.assertRaises(ClientError) as cm:
            c.list_servers()
John Giannelos's avatar
John Giannelos committed
97
            self.assertEqual(cm.exception.status, 401)
98
99
100
101
102
103
104
105


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

John Giannelos's avatar
John Giannelos committed
107
        cls.client = ComputeClient(API, TOKEN)
108
109
110
111
112
        cls.images = cls.client.list_images()
        cls.dimages = cls.client.list_images(detail=True)

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

115
116
    def test_002_list_images_detailed(self):
        """Test detailed image list is the same length as list"""
John Giannelos's avatar
John Giannelos committed
117
        self.assertEqual(len(self.dimages), len(self.images))
118

119
120
121
122
123
124
125
126
127
128
129
130
131
    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):
        """Test images have unique names"""
        names = sorted(map(lambda x: x["name"], self.images))
        self.assertEqual(sorted(list(set(names))), names)

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
132
        keys = frozenset(["os", "description", "size"])
133
134
135
136
137
138
139
140
141
142
        for i in self.dimages:
            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")
143

John Giannelos's avatar
John Giannelos committed
144
        cls.client = ComputeClient(API, TOKEN)
145
146
147
148
149
150
151
152
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
        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")
185

John Giannelos's avatar
John Giannelos committed
186
        cls.client = ComputeClient(API, TOKEN)
187
188
189
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)

190
191
192
    # def test_001_list_servers(self):
    #     """Test server list actually returns servers"""
    #     self.assertGreater(len(self.servers), 0)
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211

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

John Giannelos's avatar
John Giannelos committed
214
215
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
216
217

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

220
221
222
223
224
225
226
227
228
        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):
229
        """Get the public IPv6 of a server from the detailed server info"""
230
231
232
233
234
235
236
237
        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"]

238
239
    def _connect_loginname(self, os):
        """Return the login name for connections based on the server OS"""
240
        if os in ("Ubuntu", "Kubuntu", "Fedora"):
241
            return "user"
242
        elif os in ("windows", "windows_alpha1"):
243
            return "Administrator"
244
        else:
245
            return "root"
246
247
248
249

    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)
250
251
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
        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)
283

284
285
286
287
288
289
290
291
292
293
    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)
294
        return lines[0]
295
296
297
298

    def _try_until_timeout_expires(self, warn_timeout, fail_timeout,
                                   opmsg, callable, *args, **kwargs):
        if warn_timeout == fail_timeout:
299
300
301
302
            warn_timeout = fail_timeout + 1
        warn_tmout = time.time() + warn_timeout
        fail_tmout = time.time() + fail_timeout
        while True:
303
            self.assertLess(time.time(), fail_tmout,
304
                            "operation `%s' timed out" % opmsg)
305
            if time.time() > warn_tmout:
306
307
                log.warning("Server %d: `%s' operation `%s' not done yet",
                            self.serverid, self.servername, opmsg)
308
            try:
309
                log.info("%s... " % opmsg)
310
311
312
                return callable(*args, **kwargs)
            except AssertionError:
                pass
313
314
            time.sleep(self.query_interval)

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

    def _insist_on_ssh_hostname(self, hostip, username, password):
340
        msg = "SSH to %s, as %s/%s" % (hostip, username, password)
341
342
343
344
345
346
347
        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))
348

349
350
351
352
    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)
353
        log.info(msg)
354
355
356
357
358
359
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(hostip, username=username, password=password)
        except socket.error:
            raise AssertionError
360

361
362
363
364
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
365
        sftp = paramiko.SFTPClient.from_transport(transport)
366
        sftp.get(remotepath, localpath)
367
368
369
        sftp.close()
        transport.close()

370
371
372
        f = open(localpath)
        remote_content = b64encode(f.read())

373
        # Check if files are the same
374
        return (remote_content == content)
375

376
377
378
379
380
381
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
382
383

        log.info("Submit new server request")
384
385
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
386

387
388
        log.info("Server id: " + str(server["id"]))
        log.info("Server password: " + server["adminPass"])
389
390
391
392
393
394
395
396
        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"]
397
        cls.username = None
398
399
400
401
        cls.passwd = server["adminPass"]

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

404
405
406
407
408
409
410
411
412
413
414
        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"""
415
416
417

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

418
419
420
421
422
423
424
        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):
425
426
427

        log.info("Creating server metadata")

428
        image = self.client.get_image_details(self.imageid)
429
430
        os = image["metadata"]["values"]["os"]
        loginname = image["metadata"]["values"].get("users", None)
431
        self.client.update_server_metadata(self.serverid, OS=os)
432

433
434
        userlist = loginname.split()

435
436
437
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
438
439
440
441

        if "root" in userlist:
            cls.username = "root"
        elif users == None:
442
            cls.username = self._connect_loginname(os)
443
444
445
        else:
            cls.username = choice(userlist)

446
        self.assertIsNotNone(cls.username)
447
448
449

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
450
451
452

        log.info("Verifying image metadata")

453
454
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
455

John Giannelos's avatar
John Giannelos committed
456
        self.assertEqual(servermeta["OS"], imagemeta["os"])
457
458
459

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
460
461
462

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

463
        self._insist_on_status_transition("BUILD", "ACTIVE",
464
465
                                         self.build_fail, self.build_warning)

466
467
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
468

469
470
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
471

472
473
474
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
475
        sock = self._insist_on_tcp_connection(socket.AF_INET,
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
                                        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()
503

504
505
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
506

507
        log.info("Validate server's IPv4")
508

509
510
511
512
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
        self.assertEquals(IP(ipv4).version(), 4)

513
514
    def test_005_server_has_ipv6(self):
        """Test active server has a valid IPv6 address"""
515

516
        log.info("Validate server's IPv6")
517

518
519
520
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        self.assertEquals(IP(ipv6).version(), 6)
521
522
523

    def test_006_server_responds_to_ping_IPv4(self):
        """Test server responds to ping on IPv4 address"""
524
525
526

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

527
        server = self.client.get_server_details(self.serverid)
528
529
530
        ip = self._get_ipv4(server)
        self._try_until_timeout_expires(self.action_timeout,
                                        self.action_timeout,
531
532
                                        "PING IPv4 to %s" % ip,
                                        self._ping_once,
533
                                        False, ip)
534

535
536
    def test_007_server_responds_to_ping_IPv6(self):
        """Test server responds to ping on IPv6 address"""
537
538
539

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

540
541
542
543
544
545
546
        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)
547
548
549

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
550
551
552

        log.info("Shutting down server")

553
        self.cyclades.shutdown_server(self.serverid)
554
555
556

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
557
558

        log.info("Waiting until server becomes STOPPED")
559
560
        self._insist_on_status_transition("ACTIVE", "STOPPED",
                                         self.action_timeout,
561
562
563
564
                                         self.action_timeout)

    def test_010_submit_start_request(self):
        """Test submit start server request"""
565
566
567

        log.info("Starting server")

568
        self.cyclades.start_server(self.serverid)
569
570
571

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
572
573

        log.info("Waiting until server becomes ACTIVE")
574
575
        self._insist_on_status_transition("STOPPED", "ACTIVE",
                                         self.action_timeout,
576
577
578
579
                                         self.action_timeout)

    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
580
581
582

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

583
584
585
586
        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"""
587

588
        self._skipIf(self.is_windows, "only valid for Linux servers")
589
        server = self.client.get_server_details(self.serverid)
590
591
        self._insist_on_ssh_hostname(self._get_ipv4(server),
                                     self.username, self.passwd)
592

593
594
595
596
597
598
    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")
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
599
600
601

    def test_014_rdp_to_server_IPv4(self):
        "Test RDP connection to server public IPv4 works"""
602
        self._skipIf(not self.is_windows, "only valid for Windows servers")
603
604
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
605
        sock = _insist_on_tcp_connection(socket.AF_INET, ipv4, 3389)
606
607
608

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

612
613
614
615
616
617
    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")
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
618

619
620
621
        # No actual RDP processing done. We assume the RDP server is there
        # if the connection to the RDP port is successful.
        sock.close()
622
623
624

    def test_016_personality_is_enforced(self):
        """Test file injection for personality enforcement"""
John Giannelos's avatar
John Giannelos committed
625
626
        self._skipIf(self.is_windows, "only implemented for Linux servers")
        self._skipIf(self.personality == None, "No personality file selected")
627
628
629

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

630
631
        server = self.client.get_server_details(self.serverid)

632
        for inj_file in self.personality:
633
634
635
636
637
            equal_files = self._check_file_through_ssh(self._get_ipv4(server),
                                                       inj_file['owner'],
                                                       self.passwd,
                                                       inj_file['path'],
                                                       inj_file['contents'])
638
            self.assertTrue(equal_files)
639

640
641
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
642
643
644

        log.info("Deleting server")

645
646
647
648
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
649
650
651

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

652
653
654
655
656
657
        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"""
658
659
660

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

661
        servers = self.client.list_servers()
662
663
664
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


665
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
666
    """ Testing networking in cyclades """
667

668
    @classmethod
John Giannelos's avatar
John Giannelos committed
669
670
    def setUpClass(cls):
        "Initialize kamaki, get list of current networks"
John Giannelos's avatar
John Giannelos committed
671
672
673

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

675
676
677
        cls.servername = "%s%s for %s" % (SNF_TEST_PREFIX,
                                          TEST_RUN_ID,
                                          cls.imagename)
678
679
680
681
682
683

        #Dictionary initialization for the vms credentials
        cls.serverid = dict()
        cls.username = dict()
        cls.password = dict()

684
685
686
687
688
689
690
691
692
693
    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"]
694

695
696
697
698
699
700
701
702
703
    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
704
    def _ping_once(self, ip):
705

John Giannelos's avatar
John Giannelos committed
706
707
708
709
710
711
        """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()
712

John Giannelos's avatar
John Giannelos committed
713
714
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
715
    def test_00001a_submit_create_server_A(self):
716
        """Test submit create server request"""
717
718
719

        log.info("Creating test server A")

720
        serverA = self.client.create_server(self.servername, self.flavorid,
721
                                            self.imageid, personality=None)
722

John Giannelos's avatar
John Giannelos committed
723
724
725
726
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
727
728

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
729
730
731
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
732

733
734
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
735

John Giannelos's avatar
John Giannelos committed
736
    def test_00001b_serverA_becomes_active(self):
737
        """Test server becomes ACTIVE"""
738

739
        log.info("Waiting until test server A becomes ACTIVE")
740

741
        fail_tmout = time.time() + self.action_timeout
742
743
744
745
746
747
748
749
750
751
752
753
754
        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
755
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
756
        """Test submit create server request"""
757

758
759
        log.info("Creating test server B")

John Giannelos's avatar
John Giannelos committed
760
        serverB = self.client.create_server(self.servername, self.flavorid,
761
762
                                            self.imageid, personality=None)

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

773
774
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
775

John Giannelos's avatar
John Giannelos committed
776
    def test_00002b_serverB_becomes_active(self):
777
778
        """Test server becomes ACTIVE"""

779
780
        log.info("Waiting until test server B becomes ACTIVE")

781
        fail_tmout = time.time() + self.action_timeout
782
783
784
785
786
787
788
789
790
791
792
793
        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
794

795
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
796
        """Test submit create network request"""
797
798
799

        log.info("Submit new network request")

800
        name = SNF_TEST_PREFIX + TEST_RUN_ID
801
        previous_num = len(self.client.list_networks())
802
        network = self.client.create_network(name)
803

804
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
805
        self.assertEqual(network['name'], name)
806

807
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
808
809
        cls = type(self)
        cls.networkid = network['id']
810
811
812
813
        networks = self.client.list_networks()

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

815
    def test_002_connect_to_network(self):
816
        """Test connect VMs to network"""
817

818
819
        log.info("Connect VMs to private network")

820
821
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
822

823
        #Insist on connecting until action timeout
824
        fail_tmout = time.time() + self.action_timeout
825

826
        while True:
John Giannelos's avatar
John Giannelos committed
827
            connected = (self.client.get_network_details(self.networkid))
828
            connections = connected['servers']['values']
829
830
            if (self.serverid['A'] in connections) \
                    and (self.serverid['B'] in connections):
831
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
832
833
                break
            elif time.time() > fail_tmout:
834
835
836
837
838
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(conn_exists)
839

John Giannelos's avatar
John Giannelos committed
840
    def test_002a_reboot(self):
841
842
843
844
        """Rebooting server A"""

        log.info("Rebooting server A")

845
846
        self.client.shutdown_server(self.serverid['A'])

847
        fail_tmout = time.time() + self.action_timeout
848
849
850
851
852
853
854
855
856
857
858
859
        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'])

860
861
862
863
864
865
866
867
868
869
        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)
870

871
        self.assertTrue(active)
John Giannelos's avatar
John Giannelos committed
872

873
874
875
    def test_002b_ping_server_A(self):
        "Test if server A is pingable"

876
877
        log.info("Testing if server A is pingable")

John Giannelos's avatar
John Giannelos committed
878
879
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
880

881
        fail_tmout = time.time() + self.action_timeout
882

John Giannelos's avatar
John Giannelos committed
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
        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)
898
899

    def test_002c_reboot(self):
900
901
902
903
        """Reboot server B"""

        log.info("Rebooting server B")

904
905
        self.client.shutdown_server(self.serverid['B'])

906
        fail_tmout = time.time() + self.action_timeout
907
908
909
910
911
912
913
914
915
916
917
918
        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'])

919
920
921
922
923
924
925
926
927
928
        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)
929

930
        self.assertTrue(active)
931

John Giannelos's avatar
John Giannelos committed
932
    def test_002d_ping_server_B(self):
933
        """Test if server B is pingable"""
934

935
        log.info("Testing if server B is pingable")
John Giannelos's avatar
John Giannelos committed
936
937
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
938

939
        fail_tmout = time.time() + self.action_timeout
940

John Giannelos's avatar
John Giannelos committed
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
        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):
957
958
959
        """Set up eth1 for server A"""

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

961
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
962
        image = self.client.get_image_details(self.imageid)
963
        os = image['metadata']['values']['os']
964

John Giannelos's avatar
John Giannelos committed
965
966
967
968
969
970
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
971
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
972
973
        else:
            loginname = choice(userlist)
974

975
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
976
        myPass = self.password['A']
977

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

John Giannelos's avatar
John Giannelos committed
980
        res = False
981

John Giannelos's avatar
John Giannelos committed
982
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
983
            with settings(
984
                hide('warnings', 'running'),
985
986
987
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
988
989
                ):

990
                if len(sudo('ifconfig eth1 192.168.0.12')) == 0:
John Giannelos's avatar
John Giannelos committed
991
                    res = True
992

993
994
        else:
            with settings(
995
                hide('warnings', 'running'),
996
997
998
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
999
                ):
1000

1001
                if len(run('ifconfig eth1 192.168.0.12')) == 0:
1002
                    res = True
John Giannelos's avatar
John Giannelos committed
1003
1004

        self.assertTrue(res)
1005

John Giannelos's avatar
John Giannelos committed
1006
    def test_003b_setup_interface_B(self):
1007
        """Setup eth1 for server B"""
1008

1009
        log.info("Setting up interface eth1 for server B")
1010

1011
        server = self.client.get_server_details(self.serverid['B'])
John Giannelos's avatar
John Giannelos committed
1012
        image = self.client.get_image_details(self.imageid)
1013
        os = image['metadata']['values']['os']
1014

John Giannelos's avatar
John Giannelos committed
1015
1016
1017
1018
1019
1020
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1021
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1022
1023
1024
1025
        else:
            loginname = choice(userlist)

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

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

John Giannelos's avatar
John Giannelos committed
1030
1031
        res = False

John Giannelos's avatar
John Giannelos committed
1032
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
1033
            with settings(
1034
                hide('warnings', 'running'),
1035
1036
1037
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
1038
                ):
1039

1040
                if len(sudo('ifconfig eth1 192.168.0.13')) == 0:
John Giannelos's avatar
John Giannelos committed
1041
                    res = True
1042

1043
        else:
1044
            with settings(
1045
                hide('warnings', 'running'),
1046
1047
1048
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
1049
1050
                ):

1051
                if len(run('ifconfig eth1 192.168.0.13')) == 0:
1052
1053
                    res = True

John Giannelos's avatar
John Giannelos committed
1054
        self.assertTrue(res)
1055

John Giannelos's avatar
John Giannelos committed
1056
    def test_003c_test_connection_exists(self):
1057
        """Ping server B from server A to test if connection exists"""
1058

1059
1060
        log.info("Testing if server A is actually connected to server B")

1061
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
1062
        image = self.client.get_image_details(self.imageid)
1063
1064
        os = image['metadata']['values']['os']
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
1065
1066
1067
1068
1069
1070
1071

        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1072
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1073
1074
        else:
            loginname = choice(userlist)
1075

John Giannelos's avatar
John Giannelos committed
1076
1077
        myPass = self.password['A']

1078
1079
1080
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
1081
            ssh.connect(hostip, username=loginname, password=myPass)
1082
1083
1084
        except socket.error:
            raise AssertionError

1085
1086
        cmd = "if ping -c 2 -w 3 192.168.0.13 >/dev/null; \
               then echo \'True\'; fi;"
1087
1088
1089
        stdin, stdout, stderr = ssh.exec_command(cmd)