Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin / server_tests.py @ 362130c3

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
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_get_server_oob_console(self):
124
        """Test getting OOB server console over VNC
125

126
        Implementation of RFB protocol follows
127
        http://www.realvnc.com/docs/rfbproto.pdf.
128

129
        """
130
        console = self.clients.cyclades.get_server_console(self.server['id'])
131
        self.assertEquals(console['type'], "vnc")
132
        sock = self._insist_on_tcp_connection(
133
            socket.AF_INET, console['host'], console['port'])
134

    
135
        # Step 1. ProtocolVersion message (par. 6.1.1)
136
        version = sock.recv(1024)
137
        self.assertEquals(version, 'RFB 003.008\n')
138
        sock.send(version)
139

    
140
        # Step 2. Security (par 6.1.2): Only VNC Authentication supported
141
        sec = sock.recv(1024)
142
        self.assertEquals(list(sec), ['\x01', '\x02'])
143

    
144
        # Step 3. Request VNC Authentication (par 6.1.2)
145
        sock.send('\x02')
146

    
147
        # Step 4. Receive Challenge (par 6.2.2)
148
        challenge = sock.recv(1024)
149
        self.assertEquals(len(challenge), 16)
150

    
151
        # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2)
152
        response = d3des_generate_response(
153
            (console["password"] + '\0' * 8)[:8], challenge)
154
        sock.send(response)
155

    
156
        # Step 6. SecurityResult (par 6.1.3)
157
        result = sock.recv(4)
158
        self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00'])
159
        sock.close()
160

    
161
    def test_007_server_has_ipv4(self):
162
        """Test active server has a valid IPv4 address"""
163
        server = self.clients.cyclades.get_server_details(self.server['id'])
164
        # Update the server attribute
165
        self.server = server
166

    
167
        self.ipv4 = self._get_ips(server, version=4)
168

    
169
    def test_008_server_has_ipv6(self):
170
        """Test active server has a valid IPv6 address"""
171
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
172

    
173
        self.ipv6 = self._get_ips(self.server, version=6)
174

    
175
    def test_009_server_ping_ipv4(self):
176
        """Test server responds to ping on IPv4 address"""
177
        for ipv4 in self.ipv4:
178
            self._insist_on_ping(ipv4, version=4)
179

    
180
    def test_010_server_ping_ipv6(self):
181
        """Test server responds to ping on IPv6 address"""
182
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
183
        self._insist_on_ping(self.ipv6[0], version=6)
184

    
185
    def test_011_attach_second_network(self):
186
        """Attach a second public IP to our server"""
187
        floating_ip = self._create_floating_ip()
188
        self._create_port(floating_ip['floating_network_id'],
189
                          device_id=self.server['id'],
190
                          floating_ip=floating_ip)
191

    
192
        # Update server attributes
193
        server = self.clients.cyclades.get_server_details(self.server['id'])
194
        self.server = server
195
        self.ipv4 = self._get_ips(server, version=4)
196
        self.assertEqual(len(self.ipv4), 2)
197

    
198
        # Test new IPv4
199
        self.test_009_server_ping_ipv4()
200

    
201
    def test_012_submit_shutdown(self):
202
        """Test submit request to shutdown server"""
203
        self.clients.cyclades.shutdown_server(self.server['id'])
204

    
205
    def test_013_server_becomes_stopped(self):
206
        """Test server becomes STOPPED"""
207
        self._insist_on_server_transition(self.server, ["ACTIVE"], "STOPPED")
208

    
209
    def test_014_submit_start(self):
210
        """Test submit start server request"""
211
        self.clients.cyclades.start_server(self.server['id'])
212

    
213
    def test_015_server_becomes_active(self):
214
        """Test server becomes ACTIVE again"""
215
        self._insist_on_server_transition(self.server, ["STOPPED"], "ACTIVE")
216

    
217
    def test_016_server_ping_ipv4(self):
218
        """Test server OS is actually up and running again"""
219
        self.test_009_server_ping_ipv4()
220

    
221
    def test_017_ssh_to_server_ipv4(self):
222
        """Test SSH to server public IPv4 works, verify hostname"""
223
        self._skip_if(not self._image_is(self.use_image, "linux"),
224
                      "only valid for Linux servers")
225
        hostname1 = self._insist_get_hostname_over_ssh(
226
            self.ipv4[0], self.username, self.password)
227
        hostname2 = self._insist_get_hostname_over_ssh(
228
            self.ipv4[1], self.username, self.password)
229
        # The hostname must be of the form 'prefix-id'
230
        self.assertTrue(hostname1.endswith("-%d" % self.server['id']))
231
        self.assertEqual(hostname1, hostname2)
232

    
233
    def test_018_ssh_to_server_ipv6(self):
234
        """Test SSH to server public IPv6 works, verify hostname"""
235
        self._skip_if(not self._image_is(self.use_image, "linux"),
236
                      "only valid for Linux servers")
237
        self._skip_if(not self.use_ipv6, "--no-ipv6 flag enabled")
238
        hostname = self._insist_get_hostname_over_ssh(
239
            self.ipv6[0], self.username, self.password)
240
        # The hostname must be of the form 'prefix-id'
241
        self.assertTrue(hostname.endswith("-%d" % self.server['id']))
242

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

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

    
268
    def test_021_personality(self):
269
        """Test file injection for personality enforcement"""
270
        self._skip_if(not self._image_is(self.use_image, "linux"),
271
                      "only implemented for linux servers")
272
        assert self.personality is not None, "No personality used"
273

    
274
        for inj_file in self.personality:
275
            self._check_file_through_ssh(
276
                self.ipv4[0], inj_file['owner'], self.password,
277
                inj_file['path'], inj_file['contents'])
278

    
279
    def test_022_destroy_floating_ips(self):
280
        """Destroy the floating IPs"""
281
        self._disconnect_from_network(self.server)
282

    
283
    def test_023_submit_delete_request(self):
284
        """Test submit request to delete server"""
285
        self._delete_servers([self.server])
286

    
287

    
288
# --------------------------------------------------------------------
289
# The actuall test class. We use this class to dynamically create
290
# tests from the GeneratedServerTestSuite class. Each of these classes
291
# will run the same tests using different images and or flavors.
292
# The creation and running of our GeneratedServerTestSuite class will
293
# happen as a testsuite itself (everything here is a test!).
294
class ServerTestSuite(BurninTests):
295
    """Generate and run the GeneratedServerTestSuite
296

297
    We will generate as many testsuites as the number of images given.
298
    Each of these testsuites will use the given flavors at will (random).
299

300
    """
301
    avail_images = Proper(value=None)
302
    avail_flavors = Proper(value=None)
303
    gen_classes = Proper(value=None)
304

    
305
    def test_001_images_to_use(self):
306
        """Find images to be used by GeneratedServerTestSuite"""
307
        if self.images is None:
308
            self.info("No --images given. Will use the default %s",
309
                      "^Debian Base$")
310
            filters = ["name:^Debian Base$"]
311
        else:
312
            filters = self.images
313

    
314
        self.avail_images = self._find_images(filters)
315
        self.info("Found %s images. Let's create an equal number of tests",
316
                  len(self.avail_images))
317

    
318
    def test_002_flavors_to_use(self):
319
        """Find flavors to be used by GeneratedServerTestSuite"""
320
        flavors = self._get_list_of_flavors(detail=True)
321

    
322
        if self.flavors is None:
323
            self.info("No --flavors given. Will use all of them")
324
            self.avail_flavors = flavors
325
        else:
326
            self.avail_flavors = self._find_flavors(
327
                self.flavors, flavors=flavors)
328
        self.info("Found %s flavors to choose from", len(self.avail_flavors))
329

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

    
349
        self.gen_classes = gen_classes
350

    
351
    def test_004_run_testsuites(self):
352
        """Run the generated tests"""
353
        self._run_tests(self.gen_classes)