]>
Commit | Line | Data |
---|---|---|
01ca1bfc EJ |
1 | #! @PYTHON@ |
2 | # | |
e0edde6f | 3 | # Copyright (c) 2010 Nicira, Inc. |
01ca1bfc EJ |
4 | # |
5 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | # you may not use this file except in compliance with the License. | |
7 | # You may obtain a copy of the License at: | |
8 | # | |
9 | # http://www.apache.org/licenses/LICENSE-2.0 | |
10 | # | |
11 | # Unless required by applicable law or agreed to in writing, software | |
12 | # distributed under the License is distributed on an "AS IS" BASIS, | |
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | # See the License for the specific language governing permissions and | |
15 | # limitations under the License. | |
16 | ||
17 | import BaseHTTPServer | |
18 | import getopt | |
19 | import httplib | |
20 | import os | |
21 | import threading | |
22 | import time | |
23 | import signal #Causes keyboard interrupts to go to the main thread. | |
24 | import socket | |
25 | import sys | |
26 | ||
27 | print_safe_lock = threading.Lock() | |
28 | def print_safe(s): | |
29 | print_safe_lock.acquire() | |
30 | print(s) | |
31 | print_safe_lock.release() | |
32 | ||
33 | def start_thread(target, args): | |
34 | t = threading.Thread(target=target, args=args) | |
35 | t.setDaemon(True) | |
36 | t.start() | |
37 | return t | |
38 | ||
39 | #Caller is responsible for catching socket.error exceptions. | |
40 | def send_packet(key, length, dest_ip, dest_port): | |
41 | ||
42 | length -= 20 + 8 #IP and UDP headers. | |
43 | ||
44 | packet = str(key) | |
45 | packet += chr(0) * (length - len(packet)) | |
46 | ||
47 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
48 | sock.sendto(packet, (dest_ip, dest_port)) | |
49 | sock.close() | |
50 | \f | |
51 | #UDP Receiver | |
52 | class UDPReceiver: | |
53 | def __init__(self, vlan_ip, vlan_port): | |
54 | self.vlan_ip = vlan_ip | |
55 | self.vlan_port = vlan_port | |
56 | self.recv_callbacks = {} | |
57 | self.udp_run = False | |
58 | ||
59 | def recv_packet(self, key, success_callback, timeout_callback): | |
60 | ||
61 | event = threading.Event() | |
62 | ||
63 | def timeout_cb(): | |
64 | timeout_callback() | |
65 | event.set() | |
66 | ||
67 | timer = threading.Timer(30, timeout_cb) | |
68 | timer.daemon = True | |
69 | ||
70 | def success_cb(): | |
71 | timer.cancel() | |
72 | success_callback() | |
73 | event.set() | |
74 | ||
75 | # Start the timer first to avoid a timer.cancel() race condition. | |
76 | timer.start() | |
77 | self.recv_callbacks[key] = success_cb | |
78 | return event | |
79 | ||
80 | def udp_receiver(self): | |
81 | ||
82 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
83 | sock.settimeout(1) | |
84 | ||
85 | try: | |
86 | sock.bind((self.vlan_ip, self.vlan_port)) | |
87 | except socket.error, e: | |
88 | print_safe('Failed to bind to %s:%d with error: %s' | |
89 | % (self.vlan_ip, self.vlan_port, e)) | |
90 | os._exit(1) #sys.exit only exits the current thread. | |
91 | ||
92 | while self.udp_run: | |
93 | ||
94 | try: | |
95 | data, _ = sock.recvfrom(4096) | |
96 | except socket.timeout: | |
97 | continue | |
98 | except socket.error, e: | |
99 | print_safe('Failed to receive from %s:%d with error: %s' | |
100 | % (self.vlan_ip, self.vlan_port, e)) | |
101 | os._exit(1) | |
102 | ||
103 | data_str = data.split(chr(0))[0] | |
104 | ||
105 | if not data_str.isdigit(): | |
106 | continue | |
107 | ||
108 | key = int(data_str) | |
109 | ||
110 | if key in self.recv_callbacks: | |
111 | self.recv_callbacks[key]() | |
112 | del self.recv_callbacks[key] | |
113 | ||
114 | def start(self): | |
115 | self.udp_run = True | |
116 | start_thread(self.udp_receiver, ()) | |
117 | ||
118 | def stop(self): | |
119 | self.udp_run = False | |
120 | \f | |
121 | #Server | |
122 | vlan_server = None | |
123 | class VlanServer: | |
124 | ||
125 | def __init__(self, server_ip, server_port, vlan_ip, vlan_port): | |
126 | global vlan_server | |
127 | ||
128 | vlan_server = self | |
129 | ||
130 | self.server_ip = server_ip | |
131 | self.server_port = server_port | |
132 | ||
133 | self.recv_response = '%s:%d:' % (vlan_ip, vlan_port) | |
134 | ||
135 | self.result = {} | |
136 | self.result_lock = threading.Lock() | |
137 | ||
138 | self._test_id = 0 | |
139 | self._test_id_lock = threading.Lock() | |
140 | ||
141 | self.udp_recv = UDPReceiver(vlan_ip, vlan_port) | |
142 | ||
143 | def get_test_id(self): | |
144 | self._test_id_lock.acquire() | |
145 | ||
146 | self._test_id += 1 | |
147 | ret = self._test_id | |
148 | ||
149 | self._test_id_lock.release() | |
150 | return ret | |
151 | ||
152 | def set_result(self, key, value): | |
153 | ||
154 | self.result_lock.acquire() | |
155 | ||
156 | if key not in self.result: | |
157 | self.result[key] = value | |
158 | ||
159 | self.result_lock.release() | |
160 | ||
161 | def recv(self, test_id): | |
162 | self.udp_recv.recv_packet(test_id, | |
163 | lambda : self.set_result(test_id, 'Success'), | |
164 | lambda : self.set_result(test_id, 'Timeout')) | |
165 | ||
166 | return self.recv_response + str(test_id) | |
167 | ||
168 | def send(self, test_id, data): | |
169 | try: | |
170 | ip, port, size = data.split(':') | |
171 | port = int(port) | |
172 | size = int(size) | |
173 | except ValueError: | |
174 | self.set_result(test_id, | |
175 | 'Server failed to parse send request: %s' % data) | |
176 | return | |
177 | ||
178 | def send_thread(): | |
179 | send_time = 10 | |
180 | for _ in range(send_time * 2): | |
181 | try: | |
182 | send_packet(test_id, size, ip, port) | |
183 | except socket.error, e: | |
184 | self.set_result(test_id, 'Failure: ' + str(e)) | |
185 | return | |
186 | time.sleep(.5) | |
187 | ||
188 | self.set_result(test_id, 'Success') | |
189 | ||
190 | start_thread(send_thread, ()) | |
191 | ||
192 | return str(test_id) | |
193 | ||
194 | def run(self): | |
195 | self.udp_recv.start() | |
196 | try: | |
197 | BaseHTTPServer.HTTPServer((self.server_ip, self.server_port), | |
198 | VlanServerHandler).serve_forever() | |
199 | except socket.error, e: | |
200 | print_safe('Failed to start control server: %s' % e) | |
201 | self.udp_recv.stop() | |
202 | ||
203 | return 1 | |
204 | ||
205 | class VlanServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): | |
206 | def do_GET(self): | |
207 | ||
208 | #Guarantee three arguments. | |
209 | path = (self.path.lower().lstrip('/') + '//').split('/') | |
210 | ||
211 | resp = 404 | |
212 | body = None | |
213 | ||
214 | if path[0] == 'start': | |
215 | test_id = vlan_server.get_test_id() | |
216 | ||
217 | if path[1] == 'recv': | |
218 | resp = 200 | |
219 | body = vlan_server.recv(test_id) | |
220 | elif path[1] == 'send': | |
221 | resp = 200 | |
222 | body = vlan_server.send(test_id, path[2]) | |
223 | elif (path[0] == 'result' | |
224 | and path[1].isdigit() | |
225 | and int(path[1]) in vlan_server.result): | |
226 | resp = 200 | |
227 | body = vlan_server.result[int(path[1])] | |
228 | elif path[0] == 'ping': | |
229 | resp = 200 | |
230 | body = 'pong' | |
231 | ||
232 | self.send_response(resp) | |
233 | self.end_headers() | |
234 | ||
235 | if body: | |
236 | self.wfile.write(body) | |
237 | \f | |
238 | #Client | |
239 | class VlanClient: | |
240 | ||
241 | def __init__(self, server_ip, server_port, vlan_ip, vlan_port): | |
242 | self.server_ip_port = '%s:%d' % (server_ip, server_port) | |
243 | self.vlan_ip_port = "%s:%d" % (vlan_ip, vlan_port) | |
244 | self.udp_recv = UDPReceiver(vlan_ip, vlan_port) | |
245 | ||
246 | def request(self, resource): | |
247 | conn = httplib.HTTPConnection(self.server_ip_port) | |
248 | conn.request('GET', resource) | |
249 | return conn | |
250 | ||
251 | def send(self, size): | |
252 | ||
253 | def error_msg(e): | |
254 | print_safe('Send size %d unsuccessful: %s' % (size, e)) | |
255 | ||
256 | try: | |
257 | conn = self.request('/start/recv') | |
258 | data = conn.getresponse().read() | |
259 | except (socket.error, httplib.HTTPException), e: | |
260 | error_msg(e) | |
261 | return False | |
262 | ||
263 | try: | |
264 | ip, port, test_id = data.split(':') | |
265 | port = int(port) | |
266 | test_id = int(test_id) | |
267 | except ValueError: | |
268 | error_msg("Received invalid response from control server (%s)" % | |
269 | data) | |
270 | return False | |
271 | ||
272 | send_time = 5 | |
273 | ||
274 | for _ in range(send_time * 4): | |
275 | ||
276 | try: | |
277 | send_packet(test_id, size, ip, port) | |
278 | resp = self.request('/result/%d' % test_id).getresponse() | |
279 | data = resp.read() | |
280 | except (socket.error, httplib.HTTPException), e: | |
281 | error_msg(e) | |
282 | return False | |
283 | ||
284 | if resp.status == 200 and data == 'Success': | |
285 | print_safe('Send size %d successful' % size) | |
286 | return True | |
287 | elif resp.status == 200: | |
288 | error_msg(data) | |
289 | return False | |
290 | ||
291 | time.sleep(.25) | |
292 | ||
293 | error_msg('Timeout') | |
294 | return False | |
295 | ||
296 | def recv(self, size): | |
297 | ||
298 | def error_msg(e): | |
299 | print_safe('Receive size %d unsuccessful: %s' % (size, e)) | |
300 | ||
301 | resource = '/start/send/%s:%d' % (self.vlan_ip_port, size) | |
302 | try: | |
303 | conn = self.request(resource) | |
304 | test_id = conn.getresponse().read() | |
305 | except (socket.error, httplib.HTTPException), e: | |
306 | error_msg(e) | |
307 | return False | |
308 | ||
309 | if not test_id.isdigit(): | |
310 | error_msg('Invalid response %s' % test_id) | |
311 | return False | |
312 | ||
313 | success = [False] #Primitive datatypes can't be set from closures. | |
314 | ||
315 | def success_cb(): | |
316 | success[0] = True | |
317 | ||
318 | def failure_cb(): | |
319 | success[0] = False | |
320 | ||
321 | self.udp_recv.recv_packet(int(test_id), success_cb, failure_cb).wait() | |
322 | ||
323 | if success[0]: | |
324 | print_safe('Receive size %d successful' % size) | |
325 | else: | |
326 | error_msg('Timeout') | |
327 | ||
328 | return success[0] | |
329 | ||
330 | def server_up(self): | |
331 | ||
332 | def error_msg(e): | |
333 | print_safe('Failed control server connectivity test: %s' % e) | |
334 | ||
335 | try: | |
336 | resp = self.request('/ping').getresponse() | |
337 | data = resp.read() | |
338 | except (socket.error, httplib.HTTPException), e: | |
339 | error_msg(e) | |
340 | return False | |
341 | ||
342 | if resp.status != 200: | |
343 | error_msg('Invalid status %d' % resp.status) | |
344 | elif data != 'pong': | |
345 | error_msg('Invalid response %s' % data) | |
346 | ||
347 | return True | |
348 | ||
349 | def run(self): | |
350 | ||
351 | if not self.server_up(): | |
352 | return 1 | |
353 | ||
354 | self.udp_recv.start() | |
355 | ||
356 | success = True | |
357 | for size in [50, 500, 1000, 1500]: | |
358 | success = self.send(size) and success | |
359 | success = self.recv(size) and success | |
360 | ||
361 | self.udp_recv.stop() | |
362 | ||
363 | if success: | |
364 | print_safe('OK') | |
365 | return 0 | |
0acae9fd EJ |
366 | else: |
367 | print_safe('FAILED') | |
368 | return 1 | |
01ca1bfc EJ |
369 | \f |
370 | def usage(): | |
371 | print_safe("""\ | |
372 | %(argv0)s: Test vlan connectivity | |
373 | usage: %(argv0)s server vlan | |
374 | ||
375 | The following options are also available: | |
376 | -s, --server run in server mode | |
377 | -h, --help display this help message | |
378 | -V, --version display version information\ | |
379 | """ % {'argv0': sys.argv[0]}) | |
380 | ||
381 | def main(): | |
382 | ||
383 | try: | |
384 | options, args = getopt.gnu_getopt(sys.argv[1:], 'hVs', | |
385 | ['help', 'version', 'server']) | |
386 | except getopt.GetoptError, geo: | |
387 | print_safe('%s: %s\n' % (sys.argv[0], geo.msg)) | |
388 | return 1 | |
389 | ||
390 | server = False | |
391 | for key, _ in options: | |
392 | if key in ['-h', '--help']: | |
393 | usage() | |
394 | return 0 | |
395 | elif key in ['-V', '--version']: | |
396 | print_safe('ovs-vlan-test (Open vSwitch) @VERSION@') | |
397 | return 0 | |
398 | elif key in ['-s', '--server']: | |
399 | server = True | |
400 | else: | |
401 | print_safe('Unexpected option %s. (use --help for help)' % key) | |
402 | return 1 | |
403 | ||
404 | if len(args) != 2: | |
405 | print_safe('Expecting two arguments. (use --help for help)') | |
406 | return 1 | |
407 | ||
408 | try: | |
409 | server_ip, server_port = args[0].split(':') | |
410 | server_port = int(server_port) | |
411 | except ValueError: | |
412 | server_ip = args[0] | |
413 | server_port = 80 | |
414 | ||
415 | try: | |
416 | vlan_ip, vlan_port = args[1].split(':') | |
417 | vlan_port = int(vlan_port) | |
418 | except ValueError: | |
419 | vlan_ip = args[1] | |
420 | vlan_port = 15213 | |
421 | ||
422 | if server: | |
423 | return VlanServer(server_ip, server_port, vlan_ip, vlan_port).run() | |
424 | else: | |
425 | return VlanClient(server_ip, server_port, vlan_ip, vlan_port).run() | |
426 | ||
427 | if __name__ == '__main__': | |
428 | main_ret = main() | |
429 | ||
430 | # Python can throw exceptions if threads are running at exit. | |
431 | for th in threading.enumerate(): | |
432 | if th != threading.currentThread(): | |
433 | th.join() | |
434 | ||
435 | sys.exit(main_ret) |