burnin.py 57.4 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

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

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

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

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

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

John Giannelos's avatar
John Giannelos committed
151
        cls.client = ComputeClient(API, TOKEN)
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
190
191
        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")
192

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

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

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

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

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

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

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

    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)
257
258
        if server["status"] not in (current_status, new_status):
            return None  # Do not raise exception, return so the test fails
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
288
289
        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)
290

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Creating server metadata")

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

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

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

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

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

        log.info("Verifying image metadata")

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

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

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

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

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

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

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

479
480
481
        """
        console = self.cyclades.get_server_console(self.serverid)
        self.assertEquals(console['type'], "vnc")
482
        sock = self._insist_on_tcp_connection(socket.AF_INET,
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
508
509
                                        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()
510

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Shutting down server")

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

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

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

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

        log.info("Starting server")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Deleting server")

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

        log.info("Creating test server A")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        log.info("Submit new network request")

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

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

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

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

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

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

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

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

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

        self.assertTrue(conn_exists)
856

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

        log.info("Rebooting server A")

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

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

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

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

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

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

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

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

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

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

        log.info("Rebooting server B")

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

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

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

947
        self.assertTrue(active)
948

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.assertTrue(res)
1024

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

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

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

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

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

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

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

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

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

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

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

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

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

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