Statistics
| Branch: | Tag: | Revision:

root / snf-tools / synnefo_tools / burnin / server_tests.py @ c2f037ff

History | View | Annotate | Download (13.6 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 Proper
48
from synnefo_tools.burnin.cyclades_common import CycladesTests
49

    
50

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

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

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

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

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

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

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

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

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

    
122
    def test_006_get_server_oob_console(self):
123
        """Test getting OOB server console over VNC
124

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
286

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

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

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

    
304
    def test_001_images_to_use(self):
305
        """Find images to be used by GeneratedServerTestSuite"""
306
        self.avail_images = self._parse_images()
307

    
308
    def test_002_flavors_to_use(self):
309
        """Find flavors to be used by GeneratedServerTestSuite"""
310
        self.avail_flavors = self._parse_flavors()
311

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

    
331
        self.gen_classes = gen_classes
332

    
333
    def test_004_run_testsuites(self):
334
        """Run the generated tests"""
335
        self._run_tests(self.gen_classes)