Revision abbf2cd9 test/ganeti.http_unittest.py
b/test/ganeti.http_unittest.py | ||
---|---|---|
26 | 26 |
import unittest |
27 | 27 |
import time |
28 | 28 |
import tempfile |
29 |
import pycurl |
|
30 |
import itertools |
|
31 |
import threading |
|
29 | 32 |
from cStringIO import StringIO |
30 | 33 |
|
31 | 34 |
from ganeti import http |
35 |
from ganeti import compat |
|
32 | 36 |
|
33 | 37 |
import ganeti.http.server |
34 | 38 |
import ganeti.http.client |
... | ... | |
330 | 334 |
self.assertEqual(cr.headers, []) |
331 | 335 |
self.assertEqual(cr.url, "https://localhost:1234/version") |
332 | 336 |
|
337 |
def testPlainAddressIPv4(self): |
|
338 |
cr = http.client.HttpClientRequest("192.0.2.9", 19956, "GET", "/version") |
|
339 |
self.assertEqual(cr.url, "https://192.0.2.9:19956/version") |
|
340 |
|
|
341 |
def testPlainAddressIPv6(self): |
|
342 |
cr = http.client.HttpClientRequest("2001:db8::cafe", 15110, "GET", "/info") |
|
343 |
self.assertEqual(cr.url, "https://[2001:db8::cafe]:15110/info") |
|
344 |
|
|
333 | 345 |
def testOldStyleHeaders(self): |
334 | 346 |
headers = { |
335 | 347 |
"Content-type": "text/plain", |
... | ... | |
365 | 377 |
cr = http.client.HttpClientRequest("localhost", 1234, "GET", "/version") |
366 | 378 |
self.assertEqual(cr.post_data, "") |
367 | 379 |
|
368 |
def testIdentity(self): |
|
369 |
# These should all use different connections, hence also have a different |
|
370 |
# identity |
|
371 |
cr1 = http.client.HttpClientRequest("localhost", 1234, "GET", "/version") |
|
372 |
cr2 = http.client.HttpClientRequest("localhost", 9999, "GET", "/version") |
|
373 |
cr3 = http.client.HttpClientRequest("node1", 1234, "GET", "/version") |
|
374 |
cr4 = http.client.HttpClientRequest("node1", 9999, "GET", "/version") |
|
375 | 380 |
|
376 |
self.assertEqual(len(set([cr1.identity, cr2.identity, |
|
377 |
cr3.identity, cr4.identity])), 4) |
|
381 |
class _FakeCurl: |
|
382 |
def __init__(self): |
|
383 |
self.opts = {} |
|
384 |
self.info = NotImplemented |
|
385 |
|
|
386 |
def setopt(self, opt, value): |
|
387 |
assert opt not in self.opts, "Option set more than once" |
|
388 |
self.opts[opt] = value |
|
389 |
|
|
390 |
def getinfo(self, info): |
|
391 |
return self.info.pop(info) |
|
392 |
|
|
393 |
|
|
394 |
class TestClientStartRequest(unittest.TestCase): |
|
395 |
@staticmethod |
|
396 |
def _TestCurlConfig(curl): |
|
397 |
curl.setopt(pycurl.SSLKEYTYPE, "PEM") |
|
398 |
|
|
399 |
def test(self): |
|
400 |
for method in [http.HTTP_GET, http.HTTP_PUT, "CUSTOM"]: |
|
401 |
for port in [8761, 29796, 19528]: |
|
402 |
for curl_config_fn in [None, self._TestCurlConfig]: |
|
403 |
for read_timeout in [None, 0, 1, 123, 36000]: |
|
404 |
self._TestInner(method, port, curl_config_fn, read_timeout) |
|
405 |
|
|
406 |
def _TestInner(self, method, port, curl_config_fn, read_timeout): |
|
407 |
for response_code in [http.HTTP_OK, http.HttpNotFound.code, |
|
408 |
http.HTTP_NOT_MODIFIED]: |
|
409 |
for response_body in [None, "Hello World", |
|
410 |
"Very Long\tContent here\n" * 171]: |
|
411 |
for errmsg in [None, "error"]: |
|
412 |
req = http.client.HttpClientRequest("localhost", port, method, |
|
413 |
"/version", |
|
414 |
curl_config_fn=curl_config_fn, |
|
415 |
read_timeout=read_timeout) |
|
416 |
curl = _FakeCurl() |
|
417 |
pending = http.client._StartRequest(curl, req) |
|
418 |
self.assertEqual(pending.GetCurlHandle(), curl) |
|
419 |
self.assertEqual(pending.GetCurrentRequest(), req) |
|
420 |
|
|
421 |
# Check options |
|
422 |
opts = curl.opts |
|
423 |
self.assertEqual(opts.pop(pycurl.CUSTOMREQUEST), method) |
|
424 |
self.assertEqual(opts.pop(pycurl.URL), |
|
425 |
"https://localhost:%s/version" % port) |
|
426 |
if read_timeout is None: |
|
427 |
self.assertEqual(opts.pop(pycurl.TIMEOUT), 0) |
|
428 |
else: |
|
429 |
self.assertEqual(opts.pop(pycurl.TIMEOUT), read_timeout) |
|
430 |
self.assertFalse(opts.pop(pycurl.VERBOSE)) |
|
431 |
self.assertTrue(opts.pop(pycurl.NOSIGNAL)) |
|
432 |
self.assertEqual(opts.pop(pycurl.USERAGENT), |
|
433 |
http.HTTP_GANETI_VERSION) |
|
434 |
self.assertEqual(opts.pop(pycurl.PROXY), "") |
|
435 |
self.assertFalse(opts.pop(pycurl.POSTFIELDS)) |
|
436 |
self.assertFalse(opts.pop(pycurl.HTTPHEADER)) |
|
437 |
write_fn = opts.pop(pycurl.WRITEFUNCTION) |
|
438 |
self.assertTrue(callable(write_fn)) |
|
439 |
if hasattr(pycurl, "SSL_SESSIONID_CACHE"): |
|
440 |
self.assertFalse(opts.pop(pycurl.SSL_SESSIONID_CACHE)) |
|
441 |
if curl_config_fn: |
|
442 |
self.assertEqual(opts.pop(pycurl.SSLKEYTYPE), "PEM") |
|
443 |
else: |
|
444 |
self.assertFalse(pycurl.SSLKEYTYPE in opts) |
|
445 |
self.assertFalse(opts) |
|
446 |
|
|
447 |
if response_body is not None: |
|
448 |
offset = 0 |
|
449 |
while offset < len(response_body): |
|
450 |
piece = response_body[offset:offset + 10] |
|
451 |
write_fn(piece) |
|
452 |
offset += len(piece) |
|
453 |
|
|
454 |
curl.info = { |
|
455 |
pycurl.RESPONSE_CODE: response_code, |
|
456 |
} |
|
457 |
|
|
458 |
# Finalize request |
|
459 |
pending.Done(errmsg) |
|
460 |
|
|
461 |
self.assertFalse(curl.info) |
|
462 |
|
|
463 |
# Can only finalize once |
|
464 |
self.assertRaises(AssertionError, pending.Done, True) |
|
465 |
|
|
466 |
if errmsg: |
|
467 |
self.assertFalse(req.success) |
|
468 |
else: |
|
469 |
self.assertTrue(req.success) |
|
470 |
self.assertEqual(req.error, errmsg) |
|
471 |
self.assertEqual(req.resp_status_code, response_code) |
|
472 |
if response_body is None: |
|
473 |
self.assertEqual(req.resp_body, "") |
|
474 |
else: |
|
475 |
self.assertEqual(req.resp_body, response_body) |
|
476 |
|
|
477 |
# Check if resetting worked |
|
478 |
assert not hasattr(curl, "reset") |
|
479 |
opts = curl.opts |
|
480 |
self.assertFalse(opts.pop(pycurl.POSTFIELDS)) |
|
481 |
self.assertTrue(callable(opts.pop(pycurl.WRITEFUNCTION))) |
|
482 |
self.assertFalse(opts) |
|
483 |
|
|
484 |
self.assertFalse(curl.opts, |
|
485 |
msg="Previous checks did not consume all options") |
|
486 |
assert id(opts) == id(curl.opts) |
|
378 | 487 |
|
379 |
# But this one should have the same
|
|
380 |
cr1vglist = http.client.HttpClientRequest("localhost", 1234,
|
|
381 |
"GET", "/vg_list")
|
|
382 |
self.assertEqual(cr1.identity, cr1vglist.identity)
|
|
488 |
def _TestWrongTypes(self, *args, **kwargs):
|
|
489 |
req = http.client.HttpClientRequest(*args, **kwargs)
|
|
490 |
self.assertRaises(AssertionError, http.client._StartRequest,
|
|
491 |
_FakeCurl(), req)
|
|
383 | 492 |
|
493 |
def testWrongHostType(self): |
|
494 |
self._TestWrongTypes(unicode("localhost"), 8080, "GET", "/version") |
|
495 |
|
|
496 |
def testWrongUrlType(self): |
|
497 |
self._TestWrongTypes("localhost", 8080, "GET", unicode("/version")) |
|
498 |
|
|
499 |
def testWrongMethodType(self): |
|
500 |
self._TestWrongTypes("localhost", 8080, unicode("GET"), "/version") |
|
501 |
|
|
502 |
def testWrongHeaderType(self): |
|
503 |
self._TestWrongTypes("localhost", 8080, "GET", "/version", |
|
504 |
headers={ |
|
505 |
unicode("foo"): "bar", |
|
506 |
}) |
|
507 |
|
|
508 |
def testWrongPostDataType(self): |
|
509 |
self._TestWrongTypes("localhost", 8080, "GET", "/version", |
|
510 |
post_data=unicode("verylongdata" * 100)) |
|
511 |
|
|
512 |
|
|
513 |
class _EmptyCurlMulti: |
|
514 |
def perform(self): |
|
515 |
return (pycurl.E_MULTI_OK, 0) |
|
516 |
|
|
517 |
def info_read(self): |
|
518 |
return (0, [], []) |
|
519 |
|
|
520 |
|
|
521 |
class TestClientProcessRequests(unittest.TestCase): |
|
522 |
def testEmpty(self): |
|
523 |
requests = [] |
|
524 |
http.client.ProcessRequests(requests, _curl=NotImplemented, |
|
525 |
_curl_multi=_EmptyCurlMulti) |
|
526 |
self.assertEqual(requests, []) |
|
527 |
|
|
528 |
|
|
529 |
class TestProcessCurlRequests(unittest.TestCase): |
|
530 |
class _FakeCurlMulti: |
|
531 |
def __init__(self): |
|
532 |
self.handles = [] |
|
533 |
self.will_fail = [] |
|
534 |
self._expect = ["perform"] |
|
535 |
self._counter = itertools.count() |
|
536 |
|
|
537 |
def add_handle(self, curl): |
|
538 |
assert curl not in self.handles |
|
539 |
self.handles.append(curl) |
|
540 |
if self._counter.next() % 3 == 0: |
|
541 |
self.will_fail.append(curl) |
|
542 |
|
|
543 |
def remove_handle(self, curl): |
|
544 |
self.handles.remove(curl) |
|
545 |
|
|
546 |
def perform(self): |
|
547 |
assert self._expect.pop(0) == "perform" |
|
548 |
|
|
549 |
if self._counter.next() % 2 == 0: |
|
550 |
self._expect.append("perform") |
|
551 |
return (pycurl.E_CALL_MULTI_PERFORM, None) |
|
552 |
|
|
553 |
self._expect.append("info_read") |
|
554 |
|
|
555 |
return (pycurl.E_MULTI_OK, len(self.handles)) |
|
556 |
|
|
557 |
def info_read(self): |
|
558 |
assert self._expect.pop(0) == "info_read" |
|
559 |
successful = [] |
|
560 |
failed = [] |
|
561 |
if self.handles: |
|
562 |
if self._counter.next() % 17 == 0: |
|
563 |
curl = self.handles[0] |
|
564 |
if curl in self.will_fail: |
|
565 |
failed.append((curl, -1, "test error")) |
|
566 |
else: |
|
567 |
successful.append(curl) |
|
568 |
remaining_messages = len(self.handles) % 3 |
|
569 |
if remaining_messages > 0: |
|
570 |
self._expect.append("info_read") |
|
571 |
else: |
|
572 |
self._expect.append("select") |
|
573 |
else: |
|
574 |
remaining_messages = 0 |
|
575 |
self._expect.append("select") |
|
576 |
return (remaining_messages, successful, failed) |
|
577 |
|
|
578 |
def select(self, timeout): |
|
579 |
# Never compare floats for equality |
|
580 |
assert timeout >= 0.95 and timeout <= 1.05 |
|
581 |
assert self._expect.pop(0) == "select" |
|
582 |
self._expect.append("perform") |
|
384 | 583 |
|
385 |
class TestClient(unittest.TestCase): |
|
386 | 584 |
def test(self): |
387 |
pool = http.client.HttpClientPool(None) |
|
388 |
self.assertFalse(pool._pool) |
|
585 |
requests = [_FakeCurl() for _ in range(10)] |
|
586 |
multi = self._FakeCurlMulti() |
|
587 |
for (curl, errmsg) in http.client._ProcessCurlRequests(multi, requests): |
|
588 |
self.assertTrue(curl not in multi.handles) |
|
589 |
if curl in multi.will_fail: |
|
590 |
self.assertTrue("test error" in errmsg) |
|
591 |
else: |
|
592 |
self.assertTrue(errmsg is None) |
|
593 |
self.assertFalse(multi.handles) |
|
594 |
self.assertEqual(multi._expect, ["select"]) |
|
595 |
|
|
596 |
|
|
597 |
class TestProcessRequests(unittest.TestCase): |
|
598 |
class _DummyCurlMulti: |
|
599 |
pass |
|
600 |
|
|
601 |
def testNoMonitor(self): |
|
602 |
self._Test(False) |
|
603 |
|
|
604 |
def testWithMonitor(self): |
|
605 |
self._Test(True) |
|
606 |
|
|
607 |
class _MonitorChecker: |
|
608 |
def __init__(self): |
|
609 |
self._monitor = None |
|
610 |
|
|
611 |
def GetMonitor(self): |
|
612 |
return self._monitor |
|
613 |
|
|
614 |
def __call__(self, monitor): |
|
615 |
assert callable(monitor.GetLockInfo) |
|
616 |
self._monitor = monitor |
|
617 |
|
|
618 |
def _Test(self, use_monitor): |
|
619 |
def cfg_fn(port, curl): |
|
620 |
curl.opts["__port__"] = port |
|
621 |
|
|
622 |
def _LockCheckReset(monitor, curl): |
|
623 |
self.assertTrue(monitor._lock.is_owned(shared=0), |
|
624 |
msg="Lock must be owned in exclusive mode") |
|
625 |
curl.opts["__lockcheck__"] = True |
|
626 |
|
|
627 |
requests = \ |
|
628 |
[http.client.HttpClientRequest("localhost", i, "POST", "/version%s" % i, |
|
629 |
curl_config_fn=compat.partial(cfg_fn, i)) |
|
630 |
for i in range(15176, 15501)] |
|
631 |
requests_count = len(requests) |
|
632 |
|
|
633 |
if use_monitor: |
|
634 |
lock_monitor_cb = self._MonitorChecker() |
|
635 |
else: |
|
636 |
lock_monitor_cb = None |
|
637 |
|
|
638 |
def _ProcessRequests(multi, handles): |
|
639 |
self.assertTrue(isinstance(multi, self._DummyCurlMulti)) |
|
640 |
self.assertEqual(len(requests), len(handles)) |
|
641 |
self.assertTrue(compat.all(isinstance(curl, _FakeCurl) |
|
642 |
for curl in handles)) |
|
643 |
|
|
644 |
for idx, curl in enumerate(handles): |
|
645 |
port = curl.opts["__port__"] |
|
646 |
|
|
647 |
if use_monitor: |
|
648 |
# Check if lock information is correct |
|
649 |
lock_info = lock_monitor_cb.GetMonitor().GetLockInfo(None) |
|
650 |
expected = \ |
|
651 |
[("rpc/localhost/version%s" % handle.opts["__port__"], None, |
|
652 |
[threading.currentThread().getName()], None) |
|
653 |
for handle in handles[idx:]] |
|
654 |
self.assertEqual(sorted(lock_info), sorted(expected)) |
|
655 |
|
|
656 |
if port % 3 == 0: |
|
657 |
response_code = http.HTTP_OK |
|
658 |
msg = None |
|
659 |
else: |
|
660 |
response_code = http.HttpNotFound.code |
|
661 |
msg = "test error" |
|
662 |
|
|
663 |
curl.info = { |
|
664 |
pycurl.RESPONSE_CODE: response_code, |
|
665 |
} |
|
666 |
|
|
667 |
# Unset options which will be reset |
|
668 |
assert not hasattr(curl, "reset") |
|
669 |
if use_monitor: |
|
670 |
setattr(curl, "reset", |
|
671 |
compat.partial(_LockCheckReset, lock_monitor_cb.GetMonitor(), |
|
672 |
curl)) |
|
673 |
else: |
|
674 |
self.assertFalse(curl.opts.pop(pycurl.POSTFIELDS)) |
|
675 |
self.assertTrue(callable(curl.opts.pop(pycurl.WRITEFUNCTION))) |
|
676 |
|
|
677 |
yield (curl, msg) |
|
678 |
|
|
679 |
if use_monitor: |
|
680 |
self.assertTrue(compat.all(curl.opts["__lockcheck__"] |
|
681 |
for curl in handles)) |
|
682 |
|
|
683 |
http.client.ProcessRequests(requests, lock_monitor_cb=lock_monitor_cb, |
|
684 |
_curl=_FakeCurl, |
|
685 |
_curl_multi=self._DummyCurlMulti, |
|
686 |
_curl_process=_ProcessRequests) |
|
687 |
for req in requests: |
|
688 |
if req.port % 3 == 0: |
|
689 |
self.assertTrue(req.success) |
|
690 |
self.assertEqual(req.error, None) |
|
691 |
else: |
|
692 |
self.assertFalse(req.success) |
|
693 |
self.assertTrue("test error" in req.error) |
|
694 |
|
|
695 |
# See if monitor was disabled |
|
696 |
if use_monitor: |
|
697 |
monitor = lock_monitor_cb.GetMonitor() |
|
698 |
self.assertEqual(monitor._pending_fn, None) |
|
699 |
self.assertEqual(monitor.GetLockInfo(None), []) |
|
700 |
else: |
|
701 |
self.assertEqual(lock_monitor_cb, None) |
|
702 |
|
|
703 |
self.assertEqual(len(requests), requests_count) |
|
704 |
|
|
705 |
def testBadRequest(self): |
|
706 |
bad_request = http.client.HttpClientRequest("localhost", 27784, |
|
707 |
"POST", "/version") |
|
708 |
bad_request.success = False |
|
709 |
|
|
710 |
self.assertRaises(AssertionError, http.client.ProcessRequests, |
|
711 |
[bad_request], _curl=NotImplemented, |
|
712 |
_curl_multi=NotImplemented, _curl_process=NotImplemented) |
|
389 | 713 |
|
390 | 714 |
|
391 | 715 |
if __name__ == '__main__': |
Also available in: Unified diff