Revision 9659e075 snf-cyclades-app/synnefo/tools/burnin.py
b/snf-cyclades-app/synnefo/tools/burnin.py | ||
---|---|---|
49 | 49 |
import sys |
50 | 50 |
import time |
51 | 51 |
import hashlib |
52 |
|
|
52 |
from base64 import b64encode |
|
53 |
from pwd import getpwuid |
|
54 |
from grp import getgrgid |
|
53 | 55 |
from IPy import IP |
54 | 56 |
from multiprocessing import Process, Queue |
55 | 57 |
from random import choice |
... | ... | |
366 | 368 |
return md5.digest() |
367 | 369 |
|
368 | 370 |
|
369 |
def _check_file_through_ssh(self, hostip, username, pavssword, path):
|
|
370 |
msg = "SSH to %s, as %s/%s" % (hostip, username, password) |
|
371 |
def _check_file_through_ssh(self, hostip, username, password, remotepath, content):
|
|
372 |
msg = "Trying file injection through SSH to %s, as %s/%s" % (hostip, username, password)
|
|
371 | 373 |
try: |
372 | 374 |
ssh = paramiko.SSHClient() |
373 | 375 |
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) |
... | ... | |
375 | 377 |
except socket.error: |
376 | 378 |
raise AssertionError |
377 | 379 |
|
380 |
transport = paramiko.Transport((hostip,22)) |
|
378 | 381 |
transport.connect(username = username, password = password) |
379 |
remotepath = path |
|
382 |
|
|
380 | 383 |
localpath = '/tmp/'+SNF_TEST_PREFIX+'injection' |
381 | 384 |
sftp = paramiko.SFTPClient.from_transport(transport) |
382 | 385 |
sftp.get(remotepath, localpath) |
... | ... | |
384 | 387 |
sftp.close() |
385 | 388 |
transport.close() |
386 | 389 |
|
390 |
f = open(localpath) |
|
391 |
remote_content = b64encode(f.read()) |
|
392 |
|
|
387 | 393 |
# Check if files are the same |
388 |
return _file_md5(localpath) == _file_md5('test.txt')
|
|
394 |
return remote_content == content
|
|
389 | 395 |
|
390 | 396 |
def _skipIf(self, condition, msg): |
391 | 397 |
if condition: |
... | ... | |
395 | 401 |
"""Test submit create server request""" |
396 | 402 |
server = self.client.create_server(self.servername, self.flavorid, |
397 | 403 |
self.imageid, self.personality) |
404 |
|
|
398 | 405 |
self.assertEqual(server["name"], self.servername) |
399 | 406 |
self.assertEqual(server["flavorRef"], self.flavorid) |
400 | 407 |
self.assertEqual(server["imageRef"], self.imageid) |
... | ... | |
450 | 457 |
self._insist_on_status_transition("BUILD", "ACTIVE", |
451 | 458 |
self.build_fail, self.build_warning) |
452 | 459 |
|
453 |
def test_003a_get_server_oob_console(self): |
|
454 |
"""Test getting OOB server console over VNC |
|
460 |
# def test_003a_get_server_oob_console(self):
|
|
461 |
# """Test getting OOB server console over VNC
|
|
455 | 462 |
|
456 |
Implementation of RFB protocol follows |
|
457 |
http://www.realvnc.com/docs/rfbproto.pdf. |
|
463 |
# Implementation of RFB protocol follows
|
|
464 |
# http://www.realvnc.com/docs/rfbproto.pdf.
|
|
458 | 465 |
|
459 |
""" |
|
466 |
# """ |
|
467 |
|
|
468 |
# console = self.cyclades.get_server_console(self.serverid) |
|
469 |
# self.assertEquals(console['type'], "vnc") |
|
470 |
# sock = self._insist_on_tcp_connection(socket.AF_UNSPEC, |
|
471 |
# console["host"], console["port"]) |
|
472 |
|
|
473 |
# # Step 1. ProtocolVersion message (par. 6.1.1) |
|
474 |
# version = sock.recv(1024) |
|
475 |
# self.assertEquals(version, 'RFB 003.008\n') |
|
476 |
# sock.send(version) |
|
477 |
|
|
478 |
# # Step 2. Security (par 6.1.2): Only VNC Authentication supported |
|
479 |
# sec = sock.recv(1024) |
|
480 |
# self.assertEquals(list(sec), ['\x01', '\x02']) |
|
481 |
|
|
482 |
# # Step 3. Request VNC Authentication (par 6.1.2) |
|
483 |
# sock.send('\x02') |
|
484 |
|
|
485 |
# # Step 4. Receive Challenge (par 6.2.2) |
|
486 |
# challenge = sock.recv(1024) |
|
487 |
# self.assertEquals(len(challenge), 16) |
|
488 |
|
|
489 |
# # Step 5. DES-Encrypt challenge, use password as key (par 6.2.2) |
|
490 |
# response = d3des_generate_response( |
|
491 |
# (console["password"] + '\0' * 8)[:8], challenge) |
|
492 |
# sock.send(response) |
|
493 |
|
|
494 |
# # Step 6. SecurityResult (par 6.1.3) |
|
495 |
# result = sock.recv(4) |
|
496 |
# self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00']) |
|
497 |
# sock.close() |
|
460 | 498 |
|
461 |
console = self.cyclades.get_server_console(self.serverid) |
|
462 |
self.assertEquals(console['type'], "vnc") |
|
463 |
sock = self._insist_on_tcp_connection(socket.AF_UNSPEC, |
|
464 |
console["host"], console["port"]) |
|
465 |
|
|
466 |
# Step 1. ProtocolVersion message (par. 6.1.1) |
|
467 |
version = sock.recv(1024) |
|
468 |
self.assertEquals(version, 'RFB 003.008\n') |
|
469 |
sock.send(version) |
|
470 |
|
|
471 |
# Step 2. Security (par 6.1.2): Only VNC Authentication supported |
|
472 |
sec = sock.recv(1024) |
|
473 |
self.assertEquals(list(sec), ['\x01', '\x02']) |
|
474 |
|
|
475 |
# Step 3. Request VNC Authentication (par 6.1.2) |
|
476 |
sock.send('\x02') |
|
477 |
|
|
478 |
# Step 4. Receive Challenge (par 6.2.2) |
|
479 |
challenge = sock.recv(1024) |
|
480 |
self.assertEquals(len(challenge), 16) |
|
481 |
|
|
482 |
# Step 5. DES-Encrypt challenge, use password as key (par 6.2.2) |
|
483 |
response = d3des_generate_response( |
|
484 |
(console["password"] + '\0' * 8)[:8], challenge) |
|
485 |
sock.send(response) |
|
486 |
|
|
487 |
# Step 6. SecurityResult (par 6.1.3) |
|
488 |
result = sock.recv(4) |
|
489 |
self.assertEquals(list(result), ['\x00', '\x00', '\x00', '\x00']) |
|
490 |
sock.close() |
|
491 |
|
|
492 | 499 |
def test_004_server_has_ipv4(self): |
493 | 500 |
"""Test active server has a valid IPv4 address""" |
494 | 501 |
server = self.client.get_server_details(self.serverid) |
495 | 502 |
ipv4 = self._get_ipv4(server) |
496 | 503 |
self.assertEquals(IP(ipv4).version(), 4) |
497 | 504 |
|
498 |
def test_005_server_has_ipv6(self): |
|
499 |
"""Test active server has a valid IPv6 address""" |
|
500 |
server = self.client.get_server_details(self.serverid) |
|
501 |
ipv6 = self._get_ipv6(server) |
|
502 |
self.assertEquals(IP(ipv6).version(), 6) |
|
505 |
# def test_005_server_has_ipv6(self):
|
|
506 |
# """Test active server has a valid IPv6 address"""
|
|
507 |
# server = self.client.get_server_details(self.serverid)
|
|
508 |
# ipv6 = self._get_ipv6(server)
|
|
509 |
# self.assertEquals(IP(ipv6).version(), 6)
|
|
503 | 510 |
|
504 | 511 |
def test_006_server_responds_to_ping_IPv4(self): |
505 | 512 |
"""Test server responds to ping on IPv4 address""" |
... | ... | |
511 | 518 |
self._ping_once, |
512 | 519 |
False, ip) |
513 | 520 |
|
514 |
def test_007_server_responds_to_ping_IPv6(self): |
|
515 |
"""Test server responds to ping on IPv6 address""" |
|
516 |
server = self.client.get_server_details(self.serverid) |
|
517 |
ip = self._get_ipv6(server) |
|
518 |
self._try_until_timeout_expires(self.action_timeout, |
|
519 |
self.action_timeout, |
|
520 |
"PING IPv6 to %s" % ip, |
|
521 |
self._ping_once, |
|
522 |
True, ip) |
|
521 |
# def test_007_server_responds_to_ping_IPv6(self):
|
|
522 |
# """Test server responds to ping on IPv6 address"""
|
|
523 |
# server = self.client.get_server_details(self.serverid)
|
|
524 |
# ip = self._get_ipv6(server)
|
|
525 |
# self._try_until_timeout_expires(self.action_timeout,
|
|
526 |
# self.action_timeout,
|
|
527 |
# "PING IPv6 to %s" % ip,
|
|
528 |
# self._ping_once,
|
|
529 |
# True, ip)
|
|
523 | 530 |
|
524 | 531 |
def test_008_submit_shutdown_request(self): |
525 | 532 |
"""Test submit request to shutdown server""" |
... | ... | |
552 | 559 |
self._insist_on_ssh_hostname(self._get_ipv4(server), |
553 | 560 |
self.username, self.passwd) |
554 | 561 |
|
555 |
def test_013_ssh_to_server_IPv6(self): |
|
556 |
"""Test SSH to server public IPv6 works, verify hostname""" |
|
557 |
self._skipIf(self.is_windows, "only valid for Linux servers") |
|
558 |
server = self.client.get_server_details(self.serverid) |
|
559 |
self._insist_on_ssh_hostname(self._get_ipv6(server), |
|
560 |
self.username, self.passwd) |
|
562 |
# def test_013_ssh_to_server_IPv6(self):
|
|
563 |
# """Test SSH to server public IPv6 works, verify hostname"""
|
|
564 |
# self._skipIf(self.is_windows, "only valid for Linux servers")
|
|
565 |
# server = self.client.get_server_details(self.serverid)
|
|
566 |
# self._insist_on_ssh_hostname(self._get_ipv6(server),
|
|
567 |
# self.username, self.passwd)
|
|
561 | 568 |
|
562 | 569 |
def test_014_rdp_to_server_IPv4(self): |
563 | 570 |
"Test RDP connection to server public IPv4 works""" |
... | ... | |
571 | 578 |
# FIXME: Use rdesktop, analyze exit code? see manpage [costasd] |
572 | 579 |
sock.close() |
573 | 580 |
|
574 |
def test_015_rdp_to_server_IPv6(self): |
|
575 |
"Test RDP connection to server public IPv6 works""" |
|
576 |
self._skipIf(not self.is_windows, "only valid for Windows servers") |
|
577 |
server = self.client.get_server_details(self.serverid) |
|
578 |
ipv6 = self._get_ipv6(server) |
|
579 |
sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389) |
|
581 |
# def test_015_rdp_to_server_IPv6(self):
|
|
582 |
# "Test RDP connection to server public IPv6 works"""
|
|
583 |
# self._skipIf(not self.is_windows, "only valid for Windows servers")
|
|
584 |
# server = self.client.get_server_details(self.serverid)
|
|
585 |
# ipv6 = self._get_ipv6(server)
|
|
586 |
# sock = _get_tcp_connection(socket.AF_INET6, ipv6, 3389)
|
|
580 | 587 |
|
581 |
# No actual RDP processing done. We assume the RDP server is there |
|
582 |
# if the connection to the RDP port is successful. |
|
583 |
sock.close() |
|
588 |
# # No actual RDP processing done. We assume the RDP server is there
|
|
589 |
# # if the connection to the RDP port is successful.
|
|
590 |
# sock.close()
|
|
584 | 591 |
|
585 | 592 |
def test_016_personality_is_enforced(self): |
586 | 593 |
"""Test file injection for personality enforcement""" |
587 | 594 |
self._skipIf(self.is_windows, "only implemented for Linux servers") |
588 |
|
|
595 |
self._skipIf(self.personality == None, "No personality file selected") |
|
589 | 596 |
|
590 |
#Create new server |
|
591 |
server = self.client.create_server(self.servername, self.flavorid, |
|
592 |
self.imageid, self.personality) #/path/to/file |
|
593 |
self.assertEqual(server["name"], self.servername) |
|
594 |
self.assertEqual(server["flavorRef"], self.flavorid) |
|
595 |
self.assertEqual(server["imageRef"], self.imageid) |
|
596 |
self.assertEqual(server["status"], "BUILD") |
|
597 |
|
|
598 |
#Test if is in active state |
|
599 |
servers = self.client.list_servers(detail=True) |
|
600 |
servers = filter(lambda x: x["name"] == self.servername, servers) |
|
601 |
self.assertEqual(len(servers), 1) |
|
602 |
|
|
603 |
#Test if server is building in details |
|
604 | 597 |
server = self.client.get_server_details(self.serverid) |
605 |
self.assertEqual(server["name"], self.servername) |
|
606 |
self.assertEqual(server["flavorRef"], self.flavorid) |
|
607 |
self.assertEqual(server["imageRef"], self.imageid) |
|
608 |
self.assertEqual(server["status"], "BUILD") |
|
609 | 598 |
|
610 |
#Insist on transition |
|
611 |
self._insist_on_status_transition("BUILD", "ACTIVE", |
|
612 |
self.build_fail, self.build_warning) |
|
613 |
|
|
614 |
#Test if file injected exists |
|
615 |
equal = self._check_file_through_ssh(self._get_ipv4(server), self.username, self.password) |
|
599 |
for inj_file in self.personality: |
|
600 |
equal_files = self._check_file_through_ssh(self._get_ipv4(server), inj_file['owner'], |
|
601 |
self.passwd, inj_file['path'], inj_file['contents']) |
|
602 |
self.assertTrue(equal_files) |
|
616 | 603 |
|
617 |
self.assertTrue(equal) |
|
618 | 604 |
|
619 | 605 |
def test_017_submit_delete_request(self): |
620 | 606 |
"""Test submit request to delete server""" |
... | ... | |
918 | 904 |
help="Delete stale servers from previous runs, whose "\ |
919 | 905 |
"name starts with `%s'" % SNF_TEST_PREFIX, |
920 | 906 |
default=False) |
907 |
parser.add_option("--force-personality", |
|
908 |
action="store", dest="personality_path", |
|
909 |
help="Force a personality file injection. File path required. ", |
|
910 |
default=None) |
|
911 |
|
|
921 | 912 |
|
922 | 913 |
# FIXME: Change the default for build-fanout to 10 |
923 | 914 |
# FIXME: Allow the user to specify a specific set of Images to test |
... | ... | |
988 | 979 |
imageid = str(image["id"]) |
989 | 980 |
flavorid = choice([f["id"] for f in DFLAVORS if f["disk"] >= 20]) |
990 | 981 |
imagename = image["name"] |
991 |
personality = None # FIXME |
|
982 |
|
|
983 |
|
|
984 |
if opts.personality_path != None: |
|
985 |
f = open(opts.personality_path) |
|
986 |
content = b64encode(f.read()) |
|
987 |
personality = [] |
|
988 |
st = os.stat(opts.personality_path) |
|
989 |
personality.append({ |
|
990 |
'path': '/root/test_inj_file', |
|
991 |
'owner': 'root', |
|
992 |
'group': 'root', |
|
993 |
'mode': 0x7777 & st.st_mode, |
|
994 |
'contents': content |
|
995 |
}) |
|
996 |
else: |
|
997 |
personality = None |
|
998 |
|
|
992 | 999 |
servername = "%s%s for %s" % (SNF_TEST_PREFIX, TEST_RUN_ID, imagename) |
993 | 1000 |
is_windows = imagename.lower().find("windows") >= 0 |
994 | 1001 |
|
... | ... | |
1000 | 1007 |
action_timeout=opts.action_timeout, |
1001 | 1008 |
build_warning=opts.build_warning, |
1002 | 1009 |
build_fail=opts.build_fail, |
1003 |
query_interval=opts.query_interval) |
|
1010 |
query_interval=opts.query_interval, |
|
1011 |
) |
|
1004 | 1012 |
|
1005 | 1013 |
|
1006 | 1014 |
#Running all the testcases sequentially |
1007 | 1015 |
#seq_cases = [UnauthorizedTestCase, FlavorsTestCase, ImagesTestCase, ServerTestCase, NetworkTestCase] |
1008 | 1016 |
|
1009 |
seq_cases = [NetworkTestCase]
|
|
1017 |
seq_cases = [ServerTestCase]
|
|
1010 | 1018 |
for case in seq_cases: |
1011 | 1019 |
suite = unittest.TestLoader().loadTestsFromTestCase(case) |
1012 | 1020 |
unittest.TextTestRunner(verbosity=2).run(suite) |
Also available in: Unified diff