Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin / server_tests.py @ 2c60bfa6

History | View | Annotate | Download (14.4 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
"""
35
This is the burnin class that tests the Servers' functionality
36

37
"""
38

    
39
import sys
40
import stat
41
import base64
42
import random
43
import socket
44

    
45
from vncauthproxy.d3des import generate_response as d3des_generate_response
46

    
47
from synnefo_tools.burnin.common import BurninTests, Proper, run_test
48
from synnefo_tools.burnin.cyclades_common import CycladesTests
49

    
50

    
51
# Too many public methods. pylint: disable-msg=R0904
52
# Too many instance attributes. pylint: disable-msg=R0902
53
# This class gets replicated into actual TestCases dynamically
54
class GeneratedServerTestSuite(CycladesTests):
55
    """Test Spawning Serverfunctionality"""
56
    use_image = Proper(value=None)
57
    personality = Proper(value=None)
58
    avail_flavors = Proper(value=None)
59
    use_flavor = Proper(value=None)
60
    server = Proper(value=None)
61
    ipv4 = Proper(value=None)
62
    ipv6 = Proper(value=None)
63
    username = Proper(value=None)
64
    password = Proper(value=None)
65

    
66
    def test_001_submit_create_server(self):
67
        """Submit a create server request"""
68
        if self._image_is(self.use_image, "linux"):
69
            # Enforce personality test
70
            self.info("Creating personality content to be used")
71
            self.personality = [{
72
                'path': "/root/test_inj_file",
73
                'owner': "root",
74
                'group': "root",
75
                'mode': stat.S_IRUSR | stat.S_IWUSR,
76
                'contents': base64.b64encode("This is a personality file")
77
            }]
78
        self.use_flavor = random.choice(self.avail_flavors)
79

    
80
        self.server = self._create_server(
81
            self.use_image, self.use_flavor,
82
            personality=self.personality, network=True)
83
        self.username = self._get_connection_username(self.server)
84
        self.password = self.server['adminPass']
85

    
86
    def test_002_server_build_list(self):
87
        """Test server is in BUILD state, in server list"""
88
        servers = self._get_list_of_servers(detail=True)
89
        servers = [s for s in servers if s['id'] == self.server['id']]
90

    
91
        self.assertEqual(len(servers), 1)
92
        server = servers[0]
93
        self.assertEqual(server['name'], self.server['name'])
94
        self.assertEqual(server['flavor']['id'], self.use_flavor['id'])
95
        self.assertEqual(server['image']['id'], self.use_image['id'])
96
        self.assertEqual(server['status'], "BUILD")
97

    
98
    def test_003_server_build_details(self):
99
        """Test server is in BUILD state, in details"""
100
        server = self._get_server_details(self.server)
101
        self.assertEqual(server['name'], self.server['name'])
102
        self.assertEqual(server['flavor']['id'], self.use_flavor['id'])
103
        self.assertEqual(server['image']['id'], self.use_image['id'])
104
        self.assertEqual(server['status'], "BUILD")
105

    
106
    def test_004_set_server_metadata(self):
107
        """Test setting some of the server's metadata"""
108
        image = self.clients.cyclades.get_image_details(self.use_image['id'])
109
        os_value = image['metadata']['os']
110
        self.clients.cyclades.update_server_metadata(
111
            self.server['id'], OS=os_value)
112

    
113
        servermeta = \
114
            self.clients.cyclades.get_server_metadata(self.server['id'])
115
        imagemeta = \
116
            self.clients.cyclades.get_image_metadata(self.use_image['id'])
117
        self.assertEqual(servermeta['OS'], imagemeta['os'])
118

    
119
    def test_005_server_becomes_active(self):
120
        """Test server becomes ACTIVE"""
121
        self._insist_on_server_transition(self.server, ["BUILD"], "ACTIVE")
122

    
123
    def test_006_attach_second_network(self):
124
        """Attach a second public IP to our server"""
125
        floating_ip = self._create_floating_ip()
126
        self._create_port(floating_ip['floating_network_id'],
127
                          device_id=self.server['id'],
128
                          floating_ip=floating_ip)
129

    
130
    def test_007_get_server_oob_console(self):
131
        """Test getting OOB server console over VNC
132

133
        Implementation of RFB protocol follows
134
        http://www.realvnc.com/docs/rfbproto.pdf.
135

136
        """
137
        console = self.clients.cyclades.get_server_console(self.server['id'])
138
        self.assertEquals(console['type'], "vnc")
139
        sock = self._insist_on_tcp_connection(
140
            socket.AF_INET, console['host'], console['port'])
141

    
142
        # Step 1. ProtocolVersion message (par. 6.1.1)
143
        version = sock.recv(1024)
144
        self.assertEquals(version, 'RFB 003.008\n')
145
        sock.send(version)
146

    
147
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
148
        sec = sock.recv(1024)
149
        self.assertEquals(list(sec), ['\x01', '\x02'])
150

    
151
        # Step 3. Request VNC Authentication (par 6.1.2)
152
        sock.send('\x02')
153

    
154
        # Step 4. Receive Challenge (par 6.2.2)
155
        challenge = sock.recv(1024)
156
        self.assertEquals(len(challenge), 16)
157

    
158
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
159
        response = d3des_generate_response(
160
            (console["password"] + '\0' * 8)[:8], challenge)
161
        sock.send(response)
162

    
163
        # Step 6. SecurityResult (par 6.1.3)
164
        result = sock.recv(4)
165
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
166
        sock.close()
167

    
168
    def test_008_server_has_ipv4(self):
169
        """Test active server has a valid IPv4 address"""
170
        server = self.clients.cyclades.get_server_details(self.server['id'])
171
        # Update the server attribute
172
        self.server = server
173

    
174
        self.ipv4 = self._get_ips(server, version=4)
175

    
176
    def test_009_server_has_ipv6(self):
177
        """Test active server has a valid IPv6 address"""
178
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
179

    
180
        self.ipv6 = self._get_ips(self.server, version=6)
181

    
182
    def test_010_server_ping_ipv4(self):
183
        """Test server responds to ping on IPv4 address"""
184
        self._insist_on_ping(self.ipv4[0], version=4)
185
        self._insist_on_ping(self.ipv4[1], version=4)
186

    
187
    def test_011_server_ping_ipv6(self):
188
        """Test server responds to ping on IPv6 address"""
189
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
190
        self._insist_on_ping(self.ipv6[0], version=6)
191

    
192
    def test_012_submit_shutdown(self):
193
        """Test submit request to shutdown server"""
194
        self.clients.cyclades.shutdown_server(self.server['id'])
195

    
196
    def test_013_server_becomes_stopped(self):
197
        """Test server becomes STOPPED"""
198
        self._insist_on_server_transition(self.server, ["ACTIVE"], "STOPPED")
199

    
200
    def test_014_submit_start(self):
201
        """Test submit start server request"""
202
        self.clients.cyclades.start_server(self.server['id'])
203

    
204
    def test_015_server_becomes_active(self):
205
        """Test server becomes ACTIVE again"""
206
        self._insist_on_server_transition(self.server, ["STOPPED"], "ACTIVE")
207

    
208
    def test_016_server_ping_ipv4(self):
209
        """Test server OS is actually up and running again"""
210
        self.test_010_server_ping_ipv4()
211

    
212
    def test_017_ssh_to_server_ipv4(self):
213
        """Test SSH to server public IPv4 works, verify hostname"""
214
        self._skip_if(not self._image_is(self.use_image, "linux"),
215
                      "only valid for Linux servers")
216
        hostname1 = self._insist_get_hostname_over_ssh(
217
            self.ipv4[0], self.username, self.password)
218
        hostname2 = self._insist_get_hostname_over_ssh(
219
            self.ipv4[1], self.username, self.password)
220
        # The hostname must be of the form 'prefix-id'
221
        self.assertTrue(hostname1.endswith("-%d" % self.server['id']))
222
        self.assertEqual(hostname1, hostname2)
223

    
224
    def test_018_ssh_to_server_ipv6(self):
225
        """Test SSH to server public IPv6 works, verify hostname"""
226
        self._skip_if(not self._image_is(self.use_image, "linux"),
227
                      "only valid for Linux servers")
228
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
229
        hostname = self._insist_get_hostname_over_ssh(
230
            self.ipv6[0], self.username, self.password)
231
        # The hostname must be of the form 'prefix-id'
232
        self.assertTrue(hostname.endswith("-%d" % self.server['id']))
233

    
234
    def test_019_rdp_to_server_ipv4(self):
235
        """Test RDP connection to server public IPv4 works"""
236
        self._skip_if(not self._image_is(self.use_image, "windows"),
237
                      "only valid for Windows servers")
238
        sock = self._insist_on_tcp_connection(
239
            socket.AF_INET, self.ipv4[0], 3389)
240
        # No actual RDP processing done. We assume the RDP server is there
241
        # if the connection to the RDP port is successful.
242
        # pylint: disable-msg=W0511
243
        # FIXME: Use rdesktop, analyze exit code? see manpage
244
        sock.close()
245

    
246
    def test_020_rdp_to_server_ipv6(self):
247
        """Test RDP connection to server public IPv6 works"""
248
        self._skip_if(not self._image_is(self.use_image, "windows"),
249
                      "only valid for Windows servers")
250
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
251
        sock = self._insist_on_tcp_connection(
252
            socket.AF_INET, self.ipv6[0], 3389)
253
        # No actual RDP processing done. We assume the RDP server is there
254
        # if the connection to the RDP port is successful.
255
        # pylint: disable-msg=W0511
256
        # FIXME: Use rdesktop, analyze exit code? see manpage
257
        sock.close()
258

    
259
    def test_021_personality(self):
260
        """Test file injection for personality enforcement"""
261
        self._skip_if(not self._image_is(self.use_image, "linux"),
262
                      "only implemented for linux servers")
263
        assert self.personality is not None, "No personality used"
264

    
265
        for inj_file in self.personality:
266
            self._check_file_through_ssh(
267
                self.ipv4[0], inj_file['owner'], self.password,
268
                inj_file['path'], inj_file['contents'])
269

    
270
    def test_022_destroy_floating_ips(self):
271
        """Destroy the floating IPs"""
272
        self._disconnect_from_network(self.server)
273

    
274
    def test_023_submit_delete_request(self):
275
        """Test submit request to delete server"""
276
        self._delete_servers([self.server])
277

    
278

    
279
# --------------------------------------------------------------------
280
# The actuall test class. We use this class to dynamically create
281
# tests from the GeneratedServerTestSuite class. Each of these classes
282
# will run the same tests using different images and or flavors.
283
# The creation and running of our GeneratedServerTestSuite class will
284
# happen as a testsuite itself (everything here is a test!).
285
class ServerTestSuite(BurninTests):
286
    """Generate and run the GeneratedServerTestSuite
287

288
    We will generate as many testsuites as the number of images given.
289
    Each of these testsuites will use the given flavors at will (random).
290

291
    """
292
    avail_images = Proper(value=None)
293
    avail_flavors = Proper(value=None)
294
    gen_classes = Proper(value=None)
295

    
296
    def test_001_images_to_use(self):
297
        """Find images to be used by GeneratedServerTestSuite"""
298
        if self.images is None:
299
            self.info("No --images given. Will use the default %s",
300
                      "^Debian Base$")
301
            filters = ["name:^Debian Base$"]
302
        else:
303
            filters = self.images
304

    
305
        self.avail_images = self._find_images(filters)
306
        self.info("Found %s images. Let's create an equal number of tests",
307
                  len(self.avail_images))
308

    
309
    def test_002_flavors_to_use(self):
310
        """Find flavors to be used by GeneratedServerTestSuite"""
311
        flavors = self._get_list_of_flavors(detail=True)
312

    
313
        if self.flavors is None:
314
            self.info("No --flavors given. Will use all of them")
315
            self.avail_flavors = flavors
316
        else:
317
            self.avail_flavors = self._find_flavors(
318
                self.flavors, flavors=flavors)
319
        self.info("Found %s flavors to choose from", len(self.avail_flavors))
320

    
321
    def test_003_create_testsuites(self):
322
        """Generate the GeneratedServerTestSuite tests"""
323
        gen_classes = []
324
        for img in self.avail_images:
325
            name = (str("GeneratedServerTestSuite_(%s)" %
326
                    img['name']).replace(" ", "_"))
327
            self.info("Constructing class %s", name)
328
            class_dict = {
329
                'use_image': Proper(value=img),
330
                'avail_flavors': Proper(value=self.avail_flavors)
331
            }
332
            cls = type(name, (GeneratedServerTestSuite,), class_dict)
333
            # Make sure the class can be pickled, by listing it among
334
            # the attributes of __main__. A PicklingError is raised otherwise.
335
            thismodule = sys.modules[__name__]
336
            setattr(thismodule, name, cls)
337
            # Append the generated class
338
            gen_classes.append(cls)
339

    
340
        self.gen_classes = gen_classes
341

    
342
    def test_004_run_testsuites(self):
343
        """Run the generated tests"""
344
        success = True
345
        for gen_cls in self.gen_classes:
346
            self.info("Running testsuite %s", gen_cls.__name__)
347
            success = run_test(gen_cls) and success  # With this order
348
            if self.failfast and not success:
349
                break
350
        self.assertTrue(success, "Some of the generated tests failed")