burnin.py 57 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
57
from kamaki.clients.image import ImageClient
John Giannelos's avatar
John Giannelos committed
58
from kamaki.clients import ClientError
John Giannelos's avatar
John Giannelos committed
59

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

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

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


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

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

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

89

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

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

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


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

110
111
112
        cls.plankton = ImageClient(PLANKTON, TOKEN)
        cls.images = cls.plankton.list_public()
        cls.dimages = cls.plankton.list_public(detail=True)
113
114
115

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

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

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

    def test_005_image_metadata(self):
        """Test every image has specific metadata defined"""
137
        keys = frozenset(["os", "description", "size"])
138
139
140
141
142
143
144
145
146
147
        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")
148

John Giannelos's avatar
John Giannelos committed
149
        cls.client = ComputeClient(API, TOKEN)
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
185
186
187
188
189
        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")
190

John Giannelos's avatar
John Giannelos committed
191
        cls.client = ComputeClient(API, TOKEN)
192
193
194
        cls.servers = cls.client.list_servers()
        cls.dservers = cls.client.list_servers(detail=True)

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

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

John Giannelos's avatar
John Giannelos committed
219
220
        cls.client = ComputeClient(API, TOKEN)
        cls.cyclades = CycladesClient(API, TOKEN)
221
222

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

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

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

    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)
255
256
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
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
283
284
285
286
287
        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)
288

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

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

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

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

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

366
367
368
369
        transport = paramiko.Transport((hostip, 22))
        transport.connect(username=username, password=password)

        localpath = '/tmp/' + SNF_TEST_PREFIX + 'injection'
370
        sftp = paramiko.SFTPClient.from_transport(transport)
371
        sftp.get(remotepath, localpath)
372
373
374
        sftp.close()
        transport.close()

375
376
377
        f = open(localpath)
        remote_content = b64encode(f.read())

378
        # Check if files are the same
379
        return (remote_content == content)
380

381
382
383
384
385
386
    def _skipIf(self, condition, msg):
        if condition:
            self.skipTest(msg)

    def test_001_submit_create_server(self):
        """Test submit create server request"""
387
388

        log.info("Submit new server request")
389
390
        server = self.client.create_server(self.servername, self.flavorid,
                                           self.imageid, self.personality)
391

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

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

409
410
411
412
413
414
415
416
417
418
419
        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"""
420
421
422

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

423
424
425
426
427
428
429
        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):
430
431
432

        log.info("Creating server metadata")

433
        image = self.client.get_image_details(self.imageid)
434
435
        os = image["metadata"]["values"]["os"]
        loginname = image["metadata"]["values"].get("users", None)
436
        self.client.update_server_metadata(self.serverid, OS=os)
437

438
439
        userlist = loginname.split()

440
441
442
        # Determine the username to use for future connections
        # to this host
        cls = type(self)
443
444
445
446

        if "root" in userlist:
            cls.username = "root"
        elif users == None:
447
            cls.username = self._connect_loginname(os)
448
449
450
        else:
            cls.username = choice(userlist)

451
        self.assertIsNotNone(cls.username)
452
453
454

    def test_002d_verify_server_metadata(self):
        """Test server metadata keys are set based on image metadata"""
455
456
457

        log.info("Verifying image metadata")

458
459
        servermeta = self.client.get_server_metadata(self.serverid)
        imagemeta = self.client.get_image_metadata(self.imageid)
460

John Giannelos's avatar
John Giannelos committed
461
        self.assertEqual(servermeta["OS"], imagemeta["os"])
462
463
464

    def test_003_server_becomes_active(self):
        """Test server becomes ACTIVE"""
465
466
467

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

468
        self._insist_on_status_transition("BUILD", "ACTIVE",
469
470
                                         self.build_fail, self.build_warning)

471
472
    def test_003a_get_server_oob_console(self):
        """Test getting OOB server console over VNC
473

474
475
        Implementation of RFB protocol follows
        http://www.realvnc.com/docs/rfbproto.pdf.
476

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

509
510
    def test_004_server_has_ipv4(self):
        """Test active server has a valid IPv4 address"""
511

512
        log.info("Validate server's IPv4")
513

514
515
516
517
        server = self.client.get_server_details(self.serverid)
        ipv4 = self._get_ipv4(server)
        self.assertEquals(IP(ipv4).version(), 4)

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

522
        log.info("Validate server's IPv6")
523

524
525
526
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        self.assertEquals(IP(ipv6).version(), 6)
527
528
529

    def test_006_server_responds_to_ping_IPv4(self):
        """Test server responds to ping on IPv4 address"""
530
531
532

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

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

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

546
547
548
549
550
551
552
        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)
553
554
555

    def test_008_submit_shutdown_request(self):
        """Test submit request to shutdown server"""
556
557
558

        log.info("Shutting down server")

559
        self.cyclades.shutdown_server(self.serverid)
560
561
562

    def test_009_server_becomes_stopped(self):
        """Test server becomes STOPPED"""
563
564

        log.info("Waiting until server becomes STOPPED")
565
566
        self._insist_on_status_transition("ACTIVE", "STOPPED",
                                         self.action_timeout,
567
568
569
570
                                         self.action_timeout)

    def test_010_submit_start_request(self):
        """Test submit start server request"""
571
572
573

        log.info("Starting server")

574
        self.cyclades.start_server(self.serverid)
575
576
577

    def test_011_server_becomes_active(self):
        """Test server becomes ACTIVE again"""
578
579

        log.info("Waiting until server becomes ACTIVE")
580
581
        self._insist_on_status_transition("STOPPED", "ACTIVE",
                                         self.action_timeout,
582
583
584
585
                                         self.action_timeout)

    def test_011a_server_responds_to_ping_IPv4(self):
        """Test server OS is actually up and running again"""
586
587
588

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

589
590
591
592
        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"""
593

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

599
600
601
    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")
602
603
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

604
605
606
        server = self.client.get_server_details(self.serverid)
        self._insist_on_ssh_hostname(self._get_ipv6(server),
                                     self.username, self.passwd)
607
608
609

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

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

620
621
622
    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")
623
624
        self._skipIf(NO_IPV6, "--no-ipv6 flag enabled")

625
626
627
        server = self.client.get_server_details(self.serverid)
        ipv6 = self._get_ipv6(server)
        sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
628

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

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

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

640
641
        server = self.client.get_server_details(self.serverid)

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

650
651
    def test_017_submit_delete_request(self):
        """Test submit request to delete server"""
652
653
654

        log.info("Deleting server")

655
656
657
658
        self.client.delete_server(self.serverid)

    def test_018_server_becomes_deleted(self):
        """Test server becomes DELETED"""
659
660
661

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

662
663
664
665
666
667
        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"""
668
669
670

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

671
        servers = self.client.list_servers()
672
673
674
        self.assertNotIn(self.serverid, [s["id"] for s in servers])


675
class NetworkTestCase(unittest.TestCase):
John Giannelos's avatar
John Giannelos committed
676
    """ Testing networking in cyclades """
677

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

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

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

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

694
695
696
697
698
699
700
701
702
703
    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"]
704

705
706
707
708
709
710
711
712
713
    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
714
    def _ping_once(self, ip):
715

John Giannelos's avatar
John Giannelos committed
716
717
718
719
720
721
        """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()
722

John Giannelos's avatar
John Giannelos committed
723
724
        return (ret == 0)

John Giannelos's avatar
John Giannelos committed
725
    def test_00001a_submit_create_server_A(self):
726
        """Test submit create server request"""
727
728
729

        log.info("Creating test server A")

730
        serverA = self.client.create_server(self.servername, self.flavorid,
731
                                            self.imageid, personality=None)
732

John Giannelos's avatar
John Giannelos committed
733
734
735
736
        self.assertEqual(serverA["name"], self.servername)
        self.assertEqual(serverA["flavorRef"], self.flavorid)
        self.assertEqual(serverA["imageRef"], self.imageid)
        self.assertEqual(serverA["status"], "BUILD")
737
738

        # Update class attributes to reflect data on building server
John Giannelos's avatar
John Giannelos committed
739
740
741
        self.serverid['A'] = serverA["id"]
        self.username['A'] = None
        self.password['A'] = serverA["adminPass"]
742

743
744
        log.info("Server A id:" + str(serverA["id"]))
        log.info("Server password " + (self.password['A']))
745

John Giannelos's avatar
John Giannelos committed
746
    def test_00001b_serverA_becomes_active(self):
747
        """Test server becomes ACTIVE"""
748

749
        log.info("Waiting until test server A becomes ACTIVE")
750

751
        fail_tmout = time.time() + self.action_timeout
752
753
754
755
756
757
758
759
760
761
762
763
764
        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
765
    def test_00002a_submit_create_server_B(self):
John Giannelos's avatar
John Giannelos committed
766
        """Test submit create server request"""
767

768
769
        log.info("Creating test server B")

John Giannelos's avatar
John Giannelos committed
770
        serverB = self.client.create_server(self.servername, self.flavorid,
771
772
                                            self.imageid, personality=None)

John Giannelos's avatar
John Giannelos committed
773
774
775
776
777
778
779
780
781
782
        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"]

783
784
        log.info("Server B id: " + str(serverB["id"]))
        log.info("Password " + (self.password['B']))
785

John Giannelos's avatar
John Giannelos committed
786
    def test_00002b_serverB_becomes_active(self):
787
788
        """Test server becomes ACTIVE"""

789
790
        log.info("Waiting until test server B becomes ACTIVE")

791
        fail_tmout = time.time() + self.action_timeout
792
793
794
795
796
797
798
799
800
801
802
803
        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
804

805
    def test_001_create_network(self):
John Giannelos's avatar
John Giannelos committed
806
        """Test submit create network request"""
807
808
809

        log.info("Submit new network request")

810
        name = SNF_TEST_PREFIX + TEST_RUN_ID
811
        previous_num = len(self.client.list_networks())
812
        network = self.client.create_network(name)
813

814
        #Test if right name is assigned
John Giannelos's avatar
John Giannelos committed
815
        self.assertEqual(network['name'], name)
816

817
        # Update class attributes
John Giannelos's avatar
John Giannelos committed
818
819
        cls = type(self)
        cls.networkid = network['id']
820
821
822
823
        networks = self.client.list_networks()

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

825
    def test_002_connect_to_network(self):
826
        """Test connect VMs to network"""
827

828
829
        log.info("Connect VMs to private network")

830
831
        self.client.connect_server(self.serverid['A'], self.networkid)
        self.client.connect_server(self.serverid['B'], self.networkid)
832

833
        #Insist on connecting until action timeout
834
        fail_tmout = time.time() + self.action_timeout
835

836
        while True:
John Giannelos's avatar
John Giannelos committed
837
            connected = (self.client.get_network_details(self.networkid))
838
            connections = connected['servers']['values']
839
840
            if (self.serverid['A'] in connections) \
                    and (self.serverid['B'] in connections):
841
                conn_exists = True
John Giannelos's avatar
John Giannelos committed
842
843
                break
            elif time.time() > fail_tmout:
844
845
846
847
848
                self.assertLess(time.time(), fail_tmout)
            else:
                time.sleep(self.query_interval)

        self.assertTrue(conn_exists)
849

John Giannelos's avatar
John Giannelos committed
850
    def test_002a_reboot(self):
851
852
853
854
        """Rebooting server A"""

        log.info("Rebooting server A")

855
856
        self.client.shutdown_server(self.serverid['A'])

857
        fail_tmout = time.time() + self.action_timeout
858
859
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 == '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'])

870
871
872
873
874
875
876
877
878
879
        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)
880

881
        self.assertTrue(active)
John Giannelos's avatar
John Giannelos committed
882

883
884
885
    def test_002b_ping_server_A(self):
        "Test if server A is pingable"

886
887
        log.info("Testing if server A is pingable")

John Giannelos's avatar
John Giannelos committed
888
889
        server = self.client.get_server_details(self.serverid['A'])
        ip = self._get_ipv4(server)
890

891
        fail_tmout = time.time() + self.action_timeout
892

John Giannelos's avatar
John Giannelos committed
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
        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)
908
909

    def test_002c_reboot(self):
910
911
912
913
        """Reboot server B"""

        log.info("Rebooting server B")

914
915
        self.client.shutdown_server(self.serverid['B'])

916
        fail_tmout = time.time() + self.action_timeout
917
918
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 == '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'])

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

940
        self.assertTrue(active)
941

John Giannelos's avatar
John Giannelos committed
942
    def test_002d_ping_server_B(self):
943
        """Test if server B is pingable"""
944

945
        log.info("Testing if server B is pingable")
John Giannelos's avatar
John Giannelos committed
946
947
        server = self.client.get_server_details(self.serverid['B'])
        ip = self._get_ipv4(server)
948

949
        fail_tmout = time.time() + self.action_timeout
950

John Giannelos's avatar
John Giannelos committed
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
        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):
967
968
969
        """Set up eth1 for server A"""

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

971
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
972
        image = self.client.get_image_details(self.imageid)
973
        os = image['metadata']['values']['os']
974

John Giannelos's avatar
John Giannelos committed
975
976
977
978
979
980
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
981
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
982
983
        else:
            loginname = choice(userlist)
984

985
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
986
        myPass = self.password['A']
987

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

John Giannelos's avatar
John Giannelos committed
990
        res = False
991

John Giannelos's avatar
John Giannelos committed
992
        if loginname != "root":
John Giannelos's avatar
John Giannelos committed
993
            with settings(
994
                hide('warnings', 'running'),
995
996
997
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
John Giannelos's avatar
John Giannelos committed
998
999
                ):

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

1003
1004
        else:
            with settings(
1005
                hide('warnings', 'running'),
1006
1007
1008
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
1009
                ):
1010

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

        self.assertTrue(res)
1015

John Giannelos's avatar
John Giannelos committed
1016
    def test_003b_setup_interface_B(self):
1017
        """Setup eth1 for server B"""
1018

1019
        log.info("Setting up interface eth1 for server B")
1020

1021
        server = self.client.get_server_details(self.serverid['B'])
John Giannelos's avatar
John Giannelos committed
1022
        image = self.client.get_image_details(self.imageid)
1023
        os = image['metadata']['values']['os']
1024

John Giannelos's avatar
John Giannelos committed
1025
1026
1027
1028
1029
1030
        users = image["metadata"]["values"].get("users", None)
        userlist = users.split()

        if "root" in userlist:
            loginname = "root"
        elif users == None:
1031
            loginname = self._connect_loginname(os)
John Giannelos's avatar
John Giannelos committed
1032
1033
1034
1035
        else:
            loginname = choice(userlist)

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

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

John Giannelos's avatar
John Giannelos committed
1040
1041
        res = False

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

1050
                if len(sudo('ifconfig eth1 192.168.0.13')) == 0:
John Giannelos's avatar
John Giannelos committed
1051
                    res = True
1052

1053
        else:
1054
            with settings(
1055
                hide('warnings', 'running'),
1056
1057
1058
                warn_only=True,
                host_string=hostip,
                user=loginname, password=myPass
1059
1060
                ):

1061
                if len(run('ifconfig eth1 192.168.0.13')) == 0:
1062
1063
                    res = True

John Giannelos's avatar
John Giannelos committed
1064
        self.assertTrue(res)
1065

John Giannelos's avatar
John Giannelos committed
1066
    def test_003c_test_connection_exists(self):
1067
        """Ping server B from server A to test if connection exists"""
1068

1069
1070
        log.info("Testing if server A is actually connected to server B")

1071
        server = self.client.get_server_details(self.serverid['A'])
John Giannelos's avatar
John Giannelos committed
1072
        image = self.client.get_image_details(self.imageid)
1073
1074
        os = image['metadata']['values']['os']
        hostip = self._get_ipv4(server)
John Giannelos's avatar
John Giannelos committed
1075
1076
1077
1078
1079
1080
1081