From: Steven Dake Date: Fri, 7 Sep 2012 20:22:42 +0000 (-0700) Subject: Rework functional test case infrasatructure X-Git-Tag: 2014.1~1449 X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=c292ddb164bceab5883aa585a6a2357c0e33b7aa;p=openstack-build%2Fheat-build.git Rework functional test case infrasatructure To support multi-instance, two new classes were made: Stack - represents a stack Instance - represents an instance For multi-instance stacks, create multiple Instance objects for each instance in the stack. For each instance in a stack, an instance object can be created which helps validate the individual instance sets up properly. test_WordPress_Single_Instance_With_EBS.py fails - see issue #226 Change-Id: Iddec87cd1332a9b5796c5c7e7d382ef723c3544e Signed-off-by: Steven Dake Signed-off-by: Tomas Sedovic Signed-off-by: Ian Main --- diff --git a/heat/tests/functional/test_WordPress_Single_Instance.py b/heat/tests/functional/test_WordPress_Single_Instance.py index c3f57c43..ce89afef 100644 --- a/heat/tests/functional/test_WordPress_Single_Instance.py +++ b/heat/tests/functional/test_WordPress_Single_Instance.py @@ -20,35 +20,28 @@ import unittest @attr(speed='slow') -@attr(tag=['func', 'wordpress']) +@attr(tag=['func', 'wordpress', 'WordPress_Single_Instance.template']) class WordPressFunctionalTest(unittest.TestCase): def setUp(self): template = 'WordPress_Single_Instance.template' - self.func_utils = util.FuncUtils() - - self.func_utils.prepare_jeos('F17', 'x86_64', 'cfntools') - self.func_utils.create_stack(template, 'F17') - self.func_utils.check_cfntools() - self.func_utils.wait_for_provisioning() - self.func_utils.check_user_data(template) - - self.ssh = self.func_utils.get_ssh_client() + self.stack = util.Stack(template, 'F17', 'x86_64', 'cfntools') + self.WikiDatabase = util.Instance('WikiDatabase') + self.WikiDatabase.check_cfntools() + self.WikiDatabase.wait_for_provisioning() def test_instance(self): # ensure wordpress was installed by checking for expected # configuration file over ssh - wp_file = '/etc/wordpress/wp-config.php' - stdin, stdout, sterr = self.ssh.exec_command('ls ' + wp_file) - result = stdout.readlines().pop().rstrip() - assert result == wp_file + self.assertTrue(self.WikiDatabase.file_present + ('/etc/wordpress/wp-config.php')) print "Wordpress installation detected" # Verify the output URL parses as expected, ie check that # the wordpress installation is operational - stack_url = self.func_utils.get_stack_output("WebsiteURL") + stack_url = self.stack.get_stack_output("WebsiteURL") print "Got stack output WebsiteURL=%s, verifying" % stack_url ver = verify.VerifyStack() - assert True == ver.verify_wordpress(stack_url) + self.assertTrue(ver.verify_wordpress(stack_url)) - self.func_utils.cleanup() + self.stack.cleanup() diff --git a/heat/tests/functional/test_WordPress_Single_Instance_With_EBS.py b/heat/tests/functional/test_WordPress_Single_Instance_With_EBS.py index 2fcac54f..7b198d42 100644 --- a/heat/tests/functional/test_WordPress_Single_Instance_With_EBS.py +++ b/heat/tests/functional/test_WordPress_Single_Instance_With_EBS.py @@ -23,38 +23,33 @@ import unittest @attr(speed='slow') -@attr(tag=['func', 'wordpress', 'ebs']) +@attr(tag=['func', 'wordpress', 'ebs', + 'WordPress_Single_Instance_With_EBS.template']) class WordPressSingleEBSFunctionalTest(unittest.TestCase): def setUp(self): template = 'WordPress_Single_Instance_With_EBS.template' - self.func_utils = util.FuncUtils() - - self.func_utils.prepare_jeos('F17', 'x86_64', 'cfntools') - self.func_utils.create_stack(template, 'F17') - self.func_utils.check_cfntools() - self.func_utils.wait_for_provisioning() - self.func_utils.check_user_data(template) - - self.ssh = self.func_utils.get_ssh_client() + self.stack = util.Stack(template, 'F17', 'x86_64', 'cfntools') + self.WikiDatabase = util.Instance('WikiDatabase') + self.WikiDatabase.check_cfntools() + self.WikiDatabase.wait_for_provisioning() def test_instance(self): - # 1. ensure wordpress was installed - wp_file = '/etc/wordpress/wp-config.php' - stdin, stdout, sterr = self.ssh.exec_command('ls ' + wp_file) - result = stdout.readlines().pop().rstrip() - self.assertEqual(result, wp_file) + # ensure wordpress was installed + self.assertTrue(self.WikiDatabase.file_present + ('/etc/wordpress/wp-config.php')) print "Wordpress installation detected" # Verify the output URL parses as expected, ie check that # the wordpress installation is operational - stack_url = self.func_utils.get_stack_output("WebsiteURL") + stack_url = self.stack.get_stack_output("WebsiteURL") print "Got stack output WebsiteURL=%s, verifying" % stack_url ver = verify.VerifyStack() self.assertTrue(ver.verify_wordpress(stack_url)) # Check EBS volume is present and mounted - stdin, stdout, sterr = self.ssh.exec_command('grep vdc /proc/mounts') + stdin, stdout, sterr = self.WikiDatabase.exec_command( + 'grep vdc /proc/mounts') result = stdout.readlines().pop().rstrip() self.assertTrue(len(result)) print "Checking EBS volume is attached : %s" % result @@ -63,4 +58,4 @@ class WordPressSingleEBSFunctionalTest(unittest.TestCase): mountpoint = result.split()[1] self.assertEqual(mountpoint, '/var/lib/mysql') - self.func_utils.cleanup() + self.stack.cleanup() diff --git a/heat/tests/functional/test_WordPress_Single_Instance_With_EBS_EIP.py b/heat/tests/functional/test_WordPress_Single_Instance_With_EBS_EIP.py index c069f8fc..3e404ca8 100644 --- a/heat/tests/functional/test_WordPress_Single_Instance_With_EBS_EIP.py +++ b/heat/tests/functional/test_WordPress_Single_Instance_With_EBS_EIP.py @@ -28,26 +28,19 @@ class WordPressEBSEIPFunctionalTest(unittest.TestCase): def setUp(self): template = 'WordPress_Single_Instance_With_EBS_EIP.template' - self.func_utils = util.FuncUtils() - - self.func_utils.prepare_jeos('F17', 'x86_64', 'cfntools') - self.func_utils.create_stack(template, 'F17') - self.func_utils.check_cfntools() - self.func_utils.wait_for_provisioning() - #self.func_utils.check_user_data(template) - - self.ssh = self.func_utils.get_ssh_client() + self.stack = util.Stack(template, 'F17', 'x86_64', 'cfntools') + self.WikiDatabase = util.Instance('WikiDatabase') + self.WikiDatabase.check_cfntools() + self.WikiDatabase.wait_for_provisioning() def test_instance(self): - # 1. ensure wordpress was installed - wp_file = '/etc/wordpress/wp-config.php' - stdin, stdout, sterr = self.ssh.exec_command('ls ' + wp_file) - result = stdout.readlines().pop().rstrip() - self.assertEqual(result, wp_file) + # ensure wordpress was installed + self.assertTrue(self.WikiDatabase.file_present + ('/etc/wordpress/wp-config.php')) print "Wordpress installation detected" # 2. check floating ip assignment - nclient = self.func_utils.get_nova_client() + nclient = self.stack.get_nova_client() if len(nclient.floating_ips.list()) == 0: print 'zero floating IPs detected' self.assertTrue(False) @@ -55,36 +48,37 @@ class WordPressEBSEIPFunctionalTest(unittest.TestCase): found = 0 mylist = nclient.floating_ips.list() for item in mylist: - if item.instance_id == self.func_utils.phys_rec_id: + if item.instance_id == self.stack.phys_rec_id: print 'floating IP found', item.ip found = 1 break self.assertEqual(found, 1) - # Check EBS volume is present and mounted - stdin, stdout, sterr = self.ssh.exec_command('grep vdc /proc/mounts') - result = stdout.readlines().pop().rstrip() - self.assertTrue(len(result)) - print "Checking EBS volume is attached : %s" % result - devname = result.split()[0] - self.assertEqual(devname, '/dev/vdc1') - mountpoint = result.split()[1] - self.assertEqual(mountpoint, '/var/lib/mysql') - # Verify the output URL parses as expected, ie check that # the wordpress installation is operational # Note that the WebsiteURL uses the non-EIP address - stack_url = self.func_utils.get_stack_output("WebsiteURL") + stack_url = self.stack.get_stack_output("WebsiteURL") print "Got stack output WebsiteURL=%s, verifying" % stack_url ver = verify.VerifyStack() self.assertTrue(ver.verify_wordpress(stack_url)) # Then the InstanceIPAddress is the EIP address # which should also render the wordpress page - stack_eip = self.func_utils.get_stack_output("InstanceIPAddress") + stack_eip = self.stack.get_stack_output("InstanceIPAddress") eip_url = "http://%s/wordpress" % stack_eip print "Got stack output InstanceIPAddress=%s, verifying url %s" %\ (stack_eip, eip_url) self.assertTrue(ver.verify_wordpress(eip_url)) - self.func_utils.cleanup() + # Check EBS volume is present and mounted + stdin, stdout, sterr = self.WikiDatabase.exec_command( + 'grep vdc /proc/mounts') + result = stdout.readlines().pop().rstrip() + self.assertTrue(len(result)) + print "Checking EBS volume is attached : %s" % result + devname = result.split()[0] + self.assertEqual(devname, '/dev/vdc1') + mountpoint = result.split()[1] + self.assertEqual(mountpoint, '/var/lib/mysql') + + self.stack.cleanup() diff --git a/heat/tests/functional/test_WordPress_Single_Instance_With_EIP.py b/heat/tests/functional/test_WordPress_Single_Instance_With_EIP.py index 26c72b30..f16eb9c6 100644 --- a/heat/tests/functional/test_WordPress_Single_Instance_With_EIP.py +++ b/heat/tests/functional/test_WordPress_Single_Instance_With_EIP.py @@ -28,44 +28,46 @@ class WordPressEIPFunctionalTest(unittest.TestCase): def setUp(self): template = 'WordPress_Single_Instance_With_EIP.template' - self.func_utils = util.FuncUtils() - - self.func_utils.prepare_jeos('F17', 'x86_64', 'cfntools') - self.func_utils.create_stack(template, 'F17') - self.func_utils.check_cfntools() - self.func_utils.wait_for_provisioning() - #self.func_utils.check_user_data(template) - - self.ssh = self.func_utils.get_ssh_client() + self.stack = util.Stack(template, 'F17', 'x86_64', 'cfntools') + self.WikiDatabase = util.Instance('WikiDatabase') + self.WikiDatabase.check_cfntools() + self.WikiDatabase.wait_for_provisioning() def test_instance(self): - # 1. ensure wordpress was installed - wp_file = '/etc/wordpress/wp-config.php' - stdin, stdout, sterr = self.ssh.exec_command('ls ' + wp_file) - result = stdout.readlines().pop().rstrip() - assert result == wp_file + # ensure wordpress was installed + self.assertTrue(self.WikiDatabase.file_present + ('/etc/wordpress/wp-config.php')) print "Wordpress installation detected" # 2. check floating ip assignment - nclient = self.func_utils.get_nova_client() + nclient = self.stack.get_nova_client() if len(nclient.floating_ips.list()) == 0: print 'zero floating IPs detected' - assert False + self.assertTrue(False) else: found = 0 mylist = nclient.floating_ips.list() for item in mylist: - if item.instance_id == self.func_utils.phys_rec_id: + if item.instance_id == self.stack.phys_rec_id: print 'floating IP found', item.ip found = 1 break - assert found == 1 + self.assertEqual(found, 1) # Verify the output URL parses as expected, ie check that # the wordpress installation is operational - stack_url = self.func_utils.get_stack_output("WebsiteURL") + # Note that the WebsiteURL uses the non-EIP address + stack_url = self.stack.get_stack_output("WebsiteURL") print "Got stack output WebsiteURL=%s, verifying" % stack_url ver = verify.VerifyStack() - assert True == ver.verify_wordpress(stack_url) + self.assertTrue(ver.verify_wordpress(stack_url)) + + # Then the InstanceIPAddress is the EIP address + # which should also render the wordpress page + stack_eip = self.stack.get_stack_output("InstanceIPAddress") + eip_url = "http://%s/wordpress" % stack_eip + print "Got stack output InstanceIPAddress=%s, verifying url %s" %\ + (stack_eip, eip_url) + self.assertTrue(ver.verify_wordpress(eip_url)) - self.func_utils.cleanup() + self.stack.cleanup() diff --git a/heat/tests/functional/test_WordPress_Single_Instance_With_HA.py b/heat/tests/functional/test_WordPress_Single_Instance_With_HA.py index 253700bb..ab2539c9 100644 --- a/heat/tests/functional/test_WordPress_Single_Instance_With_HA.py +++ b/heat/tests/functional/test_WordPress_Single_Instance_With_HA.py @@ -19,25 +19,21 @@ import unittest @attr(speed='slow') -@attr(tag=['func', 'wordpress', 'HA']) +@attr(tag=['func', 'wordpress', 'HA', + 'WordPress_Single_Instance_With_HA.template']) class HaFunctionalTest(unittest.TestCase): - - func_utils = util.FuncUtils() - def setUp(self): template = 'WordPress_Single_Instance_With_HA.template' - self.func_utils.prepare_jeos('F17', 'x86_64', 'cfntools') - self.func_utils.create_stack(template, 'F17') - self.func_utils.check_cfntools() - self.func_utils.wait_for_provisioning() - self.func_utils.check_user_data(template) - - self.ssh = self.func_utils.get_ssh_client() + self.stack = util.Stack(template, 'F17', 'x86_64', 'cfntools') + self.WikiDatabase = util.Instance('WikiDatabase') + self.WikiDatabase.check_cfntools() + self.WikiDatabase.wait_for_provisioning() def service_is_running(self, name): stdin, stdout, sterr = \ - self.ssh.exec_command('systemctl status %s' % name + '.service') + self.WikiDatabase.exec_command( + 'systemctl status %s' % name + '.service') lines = stdout.readlines() for line in lines: @@ -48,17 +44,15 @@ class HaFunctionalTest(unittest.TestCase): def test_instance(self): # ensure wordpress was installed - wp_file = '/etc/wordpress/wp-config.php' - stdin, stdout, sterr = self.ssh.exec_command('ls ' + wp_file) - result = stdout.readlines().pop().rstrip() - self.assertEqual(result, wp_file) + self.assertTrue(self.WikiDatabase.file_present + ('/etc/wordpress/wp-config.php')) print "Wordpress installation detected" # check the httpd service is running self.assertTrue(self.service_is_running('httpd')) # kill httpd - self.ssh.exec_command('sudo systemctl stop httpd.service') + self.WikiDatabase.exec_command('sudo systemctl stop httpd.service') # check that httpd service recovers # should take less than 60 seconds, but no worse than 70 seconds @@ -68,4 +62,4 @@ class HaFunctionalTest(unittest.TestCase): self.assertTrue(tries < 8) time.sleep(10) - self.func_utils.cleanup() + self.stack.cleanup() diff --git a/heat/tests/functional/test_WordPress_With_LB.py b/heat/tests/functional/test_WordPress_With_LB.py index b67b7c0c..56dec557 100644 --- a/heat/tests/functional/test_WordPress_With_LB.py +++ b/heat/tests/functional/test_WordPress_With_LB.py @@ -25,30 +25,30 @@ class WordPressWithLBFunctionalTest(unittest.TestCase): def setUp(self): template = 'WordPress_With_LB.template' - self.func_utils = util.FuncUtils() + self.stack = util.Stack(template, 'F17', 'x86_64', 'cfntools') - self.func_utils.prepare_jeos('F17', 'x86_64', 'cfntools') - self.func_utils.create_stack(template, 'F17') - self.func_utils.check_cfntools() - self.func_utils.wait_for_provisioning() - self.func_utils.check_user_data(template) + self.WikiServerOne = util.Instance('WikiServerOne') + self.LBInstance = util.Instance('LB_instance') + self.MySqlDatabaseServer = util.Instance('MySqlDatabaseServer') - self.ssh = self.func_utils.get_ssh_client() + self.WikiServerOne.check_cfntools() + self.LBInstance.check_cfntools() + self.MySqlDatabaseServer.check_cfntools() + + self.WikiServerOne.wait_for_provisioning() + self.LBInstance.wait_for_provisioning() + self.MySqlDatabaseServer.wait_for_provisioning() def test_instance(self): - # ensure wordpress was installed by checking for expected - # configuration file over ssh - wp_file = '/etc/wordpress/wp-config.php' - stdin, stdout, sterr = self.ssh.exec_command('ls ' + wp_file) - result = stdout.readlines().pop().rstrip() - self.assertTrue(result == wp_file) - print "Wordpress installation detected" + self.assertTrue(self.WikiServerOne.file_present + ('/etc/wordpress/wp-config.php')) + print 'Wordpress installation detected.' # Verify the output URL parses as expected, ie check that - # the wordpress installation is operational - stack_url = self.func_utils.get_stack_output("WebsiteURL") - print "Got stack output WebsiteURL=%s, verifying" % stack_url + # thewordpress installation is operational + stack_url = self.stack.get_stack_output("WebsiteURL") + print "Verifying stack output from WebsiteUrl=(%s)." % stack_url ver = verify.VerifyStack() self.assertTrue(ver.verify_wordpress(stack_url)) - self.func_utils.cleanup() + self.stack.cleanup() diff --git a/heat/tests/functional/util.py b/heat/tests/functional/util.py index 5f978b72..e98706c2 100644 --- a/heat/tests/functional/util.py +++ b/heat/tests/functional/util.py @@ -38,195 +38,68 @@ from heat.engine import parser from heat import client as heat_client -class FuncUtils: +class Instance(object): + def __init__(self, instance_name): + self.name = instance_name + + # during nose test execution this file will be imported even if + # the unit tag was specified + try: + os.environ['OS_AUTH_STRATEGY'] + except KeyError: + raise SkipTest('OS_AUTH_STRATEGY unset, skipping functional test') + + if os.environ['OS_AUTH_STRATEGY'] != 'keystone': + print 'keystone authentication required' + assert False + + self.creds = dict(username=os.environ['OS_USERNAME'], + password=os.environ['OS_PASSWORD'], + tenant=os.environ['OS_TENANT_NAME'], + auth_url=os.environ['OS_AUTH_URL'], + strategy=os.environ['OS_AUTH_STRATEGY']) + dbusername = 'testuser' + stackname = 'teststack' + + # this test is in heat/tests/functional, so go up 3 dirs + basepath = os.path.abspath( + os.path.dirname(os.path.realpath(__file__)) + '/../../..') - # during nose test execution this file will be imported even if - # the unit tag was specified - try: - os.environ['OS_AUTH_STRATEGY'] - except KeyError: - raise SkipTest('OS_AUTH_STRATEGY not set, skipping functional test') - - if os.environ['OS_AUTH_STRATEGY'] != 'keystone': - print 'keystone authentication required' - assert False - - creds = dict(username=os.environ['OS_USERNAME'], - password=os.environ['OS_PASSWORD'], - tenant=os.environ['OS_TENANT_NAME'], - auth_url=os.environ['OS_AUTH_URL'], - strategy=os.environ['OS_AUTH_STRATEGY']) - dbusername = 'testuser' - stackname = 'teststack' - - # this test is in heat/tests/functional, so go up 3 dirs - basepath = os.path.abspath( - os.path.dirname(os.path.realpath(__file__)) + '/../../..') - - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - sftp = None - novaclient = None - glanceclient = None - heatclient = None - phys_rec_id = None - - def get_ssh_client(self): - if self.ssh.get_transport() != None: - return self.ssh - return None - - def get_sftp_client(self): - if self.sftp != None: - return self.sftp - return None - - def get_nova_client(self): - if self.novaclient != None: - return self.novaclient - return None - - def get_glance_client(self): - if self.glanceclient != None: - return self.glanceclient - return None - - def get_heat_client(self): - if self.heatclient != None: - return self.heatclient - return None - - def prepare_jeos(self, p_os, arch, type): - imagename = p_os + '-' + arch + '-' + type - - self.glanceclient = glance_client.Client(host="0.0.0.0", port=9292, - use_ssl=False, auth_tok=None, creds=self.creds) - - # skip creating jeos if image already available - if not self.poll_glance(self.glanceclient, imagename, False): - if os.geteuid() != 0: - print 'test must be run as root to create jeos' - assert False - - # -d: debug, -G: register with glance - subprocess.call(['heat-jeos', '-d', '-G', 'create', imagename]) - - # Nose seems to change the behavior of the subprocess call to be - # asynchronous. So poll glance until image is registered. - self.poll_glance(self.glanceclient, imagename, True) - - def poll_glance(self, gclient, imagename, block): - imagelistname = None - tries = 0 - while imagelistname != imagename: - tries += 1 - assert tries < 50 - if block: - time.sleep(15) - print "Checking glance for image registration" - imageslist = gclient.get_images() - for x in imageslist: - imagelistname = x['name'] - if imagelistname == imagename: - print "Found image registration for %s" % imagename - # technically not necessary, but glance registers image - # before completely through with its operations - time.sleep(10) - return True - if not block: - break - return False - - def create_stack(self, template_file, distribution): self.novaclient = nova_client.Client(self.creds['username'], self.creds['password'], self.creds['tenant'], self.creds['auth_url'], service_type='compute') - keyname = self.novaclient.keypairs.list().pop().name - - self.heatclient = heat_client.get_client('0.0.0.0', 8000, - self.creds['username'], self.creds['password'], - self.creds['tenant'], self.creds['auth_url'], - self.creds['strategy'], None, None, False) - - assert self.heatclient - - # Dummy up the optparse.Values we get from CLI args in bin/heat - stack_paramstr = ';'.join(['InstanceType=m1.xlarge', - 'DBUsername=' + self.dbusername, - 'DBPassword=' + os.environ['OS_PASSWORD'], - 'KeyName=' + keyname, - 'LinuxDistribution=' + distribution]) - template_params = optparse.Values({'parameters': stack_paramstr}) + self.ssh = paramiko.SSHClient() - # Format parameters and create the stack - parameters = {} - parameters['StackName'] = self.stackname - template_path = self.basepath + '/templates/' + template_file - parameters['TemplateBody'] = open(template_path).read() - parameters.update(self.heatclient.format_parameters(template_params)) - result = self.heatclient.create_stack(**parameters) + self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - # Check result looks OK - root = etree.fromstring(result) - create_list = root.xpath('/CreateStackResponse/CreateStackResult') - assert create_list - assert len(create_list) == 1 - - # Extract StackId from the result, and check the StackName part - stackid = create_list[0].findtext('StackId') - idname = stackid.split('/')[1] - print "Checking %s contains name %s" % (stackid, self.stackname) - assert idname == self.stackname - - alist = None - tries = 0 - print 'Waiting for stack creation to be completed' - while not alist: - tries += 1 - assert tries < 500 - time.sleep(10) - events = self.heatclient.list_stack_events(**parameters) - root = etree.fromstring(events) - alist = root.xpath('//member[StackName="' + self.stackname + - '" and ResourceStatus="CREATE_COMPLETE" \ - and ResourceType="AWS::EC2::Instance"]') - - elem = alist.pop() - self.phys_rec_id = elem.findtext('PhysicalResourceId') - - print "Checking network address assignment" ip = None tries = 0 while ip is None: - tries += 1 - assert tries < 500 - time.sleep(10) - - for server in self.novaclient.servers.list(): - if server.id == self.phys_rec_id: + servers = self.novaclient.servers.list() + for server in servers: + if server.name == instance_name: address = server.addresses - print "Status: %s" % server.status if address: ip = address.items()[0][1][0]['addr'] - print 'IP found:', ip - break - elif server.status == 'ERROR': - print 'Heat error? Aborting' - assert False - return + time.sleep(10) + tries += 1 + assert tries < 500 + print 'Instance (%s) ip (%s) status (%s)' % (self.name, ip, + server.status) tries = 0 while True: try: subprocess.check_output(['nc', '-z', ip, '22']) except Exception: - print 'SSH not up yet...' + print('Instance (%s) ip (%s) SSH not up yet, waiting...' % + (self.name, ip)) time.sleep(10) tries += 1 assert tries < 50 else: - print 'SSH daemon response detected' + print 'Instance (%s) ip (%s) SSH detected.' % (self.name, ip) break tries = 0 @@ -239,13 +112,15 @@ class FuncUtils: except paramiko.AuthenticationException: print 'Authentication error' time.sleep(2) - except Exception, e: + except Exception as e: if e.errno != errno.EHOSTUNREACH: raise - print 'Preparing to connect over SSH' + print('Instance (%s) ip (%s) connecting via SSH.' % + (self.name, ip)) time.sleep(2) else: - print 'SSH connected' + print('Instance (%s) ip (%s) connected via SSH.' % + (self.name, ip)) break self.sftp = self.ssh.open_sftp() @@ -257,15 +132,28 @@ class FuncUtils: tries += 1 if e.errno == errno.ENOENT: assert tries < 50 - print "Boot not finished yet..." + print("Instance (%s) ip (%s) not booted, waiting..." % + (self.name, ip)) time.sleep(15) else: print e.errno raise else: - print "Guest fully booted" + print("Instance (%s) ip (%s) finished booting." % + (self.name, ip)) break + def exec_command(self, cmd): + return self.ssh.exec_command(cmd) + + def file_present(self, path): + print "Verifying file '%s' exists" % path + stdin, stdout, sterr = self.ssh.exec_command('ls "%s"' % path) + lines = stdout.readlines() + assert len(lines) == 1 + result = lines.pop().rstrip() + return result == path + def check_cfntools(self): stdin, stdout, stderr = \ self.ssh.exec_command('cd /opt/aws/bin; sha1sum *') @@ -286,10 +174,10 @@ class FuncUtils: cur_file = data[1].rstrip() if cur_file in cfn_tools_files: assert data[0] == cfntools[cur_file] - print 'VM cfntools integrity verified' + print 'Instance (%s) cfntools integrity verified.' % self.name def wait_for_provisioning(self): - print "Waiting for provisioning to complete" + print "Instance (%s) waiting for provisioning to complete." % self.name tries = 0 while True: try: @@ -298,25 +186,26 @@ class FuncUtils: tries += 1 if e.errno == errno.ENOENT: assert tries < 500 - print "Provisioning not finished yet..." + print("Instance (%s) provisioning incomplete, waiting..." % + self.name) time.sleep(15) else: print e.errno raise else: - print "Provisioning completed" + print "Instance (%s) provisioning completed." % self.name break def check_user_data(self, template_file): return # until TODO is fixed - transport = self.ssh.get_transport() - channel = transport.open_session() - channel.get_pty() - channel.invoke_shell() # sudo requires tty - channel.sendall('sudo chmod 777 \ - sudo chmod 777 /var/lib/cloud/instance/user-data.txt.i\n') - time.sleep(1) # necessary for sendall to complete +# transport = self.ssh.get_transport() +# channel = transport.open_session() +# channel.get_pty() +# channel.invoke_shell() # sudo requires tty +# channel.sendall('sudo chmod 777 \ +# sudo chmod 777 /var/lib/cloud/instance/user-data.txt.i\n') +# time.sleep(1) # necessary for sendall to complete f = open(self.basepath + '/templates/' + template_file) t = json.loads(f.read()) @@ -369,6 +258,170 @@ class FuncUtils: with open(filepaths[file]) as f: assert data == f.read() + def get_ssh_client(self): + if self.ssh.get_transport() != None: + return self.ssh + return None + + def get_sftp_client(self): + if self.sftp != None: + return self.sftp + return None + + def close_ssh_client(self): + self.ssh.close() + + +class Stack(object): + def __init__(self, template_file, distribution, arch, jeos_type): + + self.prepare_jeos(distribution, arch, jeos_type) + + self.novaclient = nova_client.Client(self.creds['username'], + self.creds['password'], self.creds['tenant'], + self.creds['auth_url'], service_type='compute') + + keyname = self.novaclient.keypairs.list().pop().name + + self.heatclient = heat_client.get_client('0.0.0.0', 8000, + self.creds['username'], self.creds['password'], + self.creds['tenant'], self.creds['auth_url'], + self.creds['strategy'], None, None, False) + + assert self.heatclient + + # Dummy up the optparse.Values we get from CLI args in bin/heat + stack_paramstr = ';'.join(['InstanceType=m1.xlarge', + 'DBUsername=' + self.dbusername, + 'DBPassword=' + os.environ['OS_PASSWORD'], + 'KeyName=' + keyname, + 'LinuxDistribution=' + distribution]) + template_params = optparse.Values({'parameters': stack_paramstr}) + + # Format parameters and create the stack + parameters = {} + parameters['StackName'] = self.stackname + template_path = self.basepath + '/templates/' + template_file + parameters['TemplateBody'] = open(template_path).read() + parameters.update(self.heatclient.format_parameters(template_params)) + result = self.heatclient.create_stack(**parameters) + + # Check result looks OK + root = etree.fromstring(result) + create_list = root.xpath('/CreateStackResponse/CreateStackResult') + assert create_list + assert len(create_list) == 1 + + # Extract StackId from the result, and check the StackName part + stackid = create_list[0].findtext('StackId') + idname = stackid.split('/')[1] + print "Checking %s contains name %s" % (stackid, self.stackname) + assert idname == self.stackname + + alist = None + tries = 0 + print 'Waiting for stack creation to be completed' + while not alist: + tries += 1 + assert tries < 500 + time.sleep(10) + events = self.heatclient.list_stack_events(**parameters) + root = etree.fromstring(events) + alist = root.xpath('//member[StackName="' + self.stackname + + '" and ResourceStatus="CREATE_COMPLETE" \ + and ResourceType="AWS::EC2::Instance"]') + + elem = alist.pop() + self.phys_rec_id = elem.findtext('PhysicalResourceId') + + # during nose test execution this file will be imported even if + # the unit tag was specified + try: + os.environ['OS_AUTH_STRATEGY'] + except KeyError: + raise SkipTest('OS_AUTH_STRATEGY not set, skipping functional test') + + if os.environ['OS_AUTH_STRATEGY'] != 'keystone': + print 'keystone authentication required' + assert False + + creds = dict(username=os.environ['OS_USERNAME'], + password=os.environ['OS_PASSWORD'], + tenant=os.environ['OS_TENANT_NAME'], + auth_url=os.environ['OS_AUTH_URL'], + strategy=os.environ['OS_AUTH_STRATEGY']) + dbusername = 'testuser' + stackname = 'teststack' + + # this test is in heat/tests/functional, so go up 3 dirs + basepath = os.path.abspath( + os.path.dirname(os.path.realpath(__file__)) + '/../../..') + + novaclient = None + glanceclient = None + heatclient = None + + def cleanup(self): + parameters = {'StackName': self.stackname} + c = self.get_heat_client() + c.delete_stack(**parameters) + + def get_nova_client(self): + if self.novaclient != None: + return self.novaclient + return None + + def get_glance_client(self): + if self.glanceclient != None: + return self.glanceclient + return None + + def get_heat_client(self): + if self.heatclient != None: + return self.heatclient + return None + + def prepare_jeos(self, p_os, arch, type): + imagename = p_os + '-' + arch + '-' + type + + self.glanceclient = glance_client.Client(host="0.0.0.0", port=9292, + use_ssl=False, auth_tok=None, creds=self.creds) + + # skip creating jeos if image already available + if not self.poll_glance(self.glanceclient, imagename, False): + if os.geteuid() != 0: + print 'test must be run as root to create jeos' + assert False + + # -d: debug, -G: register with glance + subprocess.call(['heat-jeos', '-d', '-G', 'create', imagename]) + + # Nose seems to change the behavior of the subprocess call to be + # asynchronous. So poll glance until image is registered. + self.poll_glance(self.glanceclient, imagename, True) + + def poll_glance(self, gclient, imagename, block): + imagelistname = None + tries = 0 + while imagelistname != imagename: + tries += 1 + assert tries < 50 + if block: + time.sleep(15) + print "Checking glance for image registration" + imageslist = gclient.get_images() + for x in imageslist: + imagelistname = x['name'] + if imagelistname == imagename: + print "Found image registration for %s" % imagename + # technically not necessary, but glance registers image + # before completely through with its operations + time.sleep(10) + return True + if not block: + break + return False + def get_stack_output(self, output_key): ''' Extract a specified output from the DescribeStacks details @@ -385,11 +438,6 @@ class FuncUtils: value = output.findtext('OutputValue') return value - def cleanup(self): - self.ssh.close() - parameters = {'StackName': self.stackname} - c = self.get_heat_client() - c.delete_stack(**parameters) if __name__ == '__main__': sys.argv.append(__file__)