Add functional test to verify jeos and stack ops

(Tox.ini has been modified to only run tests tagged with 'unit'
to prevent this test from running with unit tests.)

This test requires an OpenStack install present and will not run on
StackForge. This test creates a JEOS, waits for glance registration,
detects key registered with keystone, creates stack, and verifies over
SSH that:
- cfn helper script SHAs match tree
- verifies presence of wordpress
- verifies expected user data is present in multipart mime file

closes #112

Change-Id: I22a0dfe41986d466ac689c050fc33585e3e6229e
Signed-off-by: Jeff Peeler <jpeeler@redhat.com>
This commit is contained in:
Jeff Peeler 2012-05-23 16:13:00 -04:00
parent a4f5ae264d
commit a83fcc6aa3
2 changed files with 253 additions and 3 deletions

View File

@ -0,0 +1,250 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Functional test case that utilizes the bin/heat CLI tool"""
import sys
import os
import optparse
import paramiko
import subprocess
import hashlib
import email
import json
import time # for sleep
import nose
from nose.plugins.attrib import attr
from nose import with_setup
from novaclient.v1_1 import client
from glance import client as glance_client
from heat import utils
from heat.engine import parser
class TestBinHeat():
"""Functional tests for the bin/heat CLI tool"""
def setUp(self):
if os.geteuid() != 0:
print 'test must be run as root'
assert False
if os.environ['OS_AUTH_STRATEGY'] != 'keystone':
print 'keystone authentication required'
assert False
# this test is in heat/tests/functional, so go up 3 dirs
self.basepath = os.path.abspath(
os.path.dirname(os.path.realpath(__file__)) + '/../../..')
@attr(speed='slow')
@attr(tag=['func', 'jeos'])
def test_jeos_create(self):
# 0. Verify JEOS and cloud init
args = ('F16', 'x86_64', 'cfntools')
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'
subprocess.call(['heat', '-d', 'jeos-create',
args[0], args[1], args[2]])
gclient = glance_client.Client(host="0.0.0.0", port=9292,
use_ssl=False, auth_tok=None, creds=creds)
# Nose seems to change the behavior of the subprocess call to be
# asynchronous. So poll glance until image is registered.
imagename = '-'.join(str(i) for i in args)
imagelistname = None
tries = 0
while imagelistname != imagename:
tries += 1
assert tries < 5000
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
break
# technically not necessary, but glance registers image before
# completely through with its operations
time.sleep(10)
nt = client.Client(os.environ['OS_USERNAME'],
os.environ['OS_PASSWORD'], os.environ['OS_TENANT_NAME'],
os.environ['OS_AUTH_URL'], service_type='compute')
keyname = nt.keypairs.list().pop().name
subprocess.call(['heat', '-d', 'create', 'teststack',
'--template-file=' + self.basepath +
'/templates/WordPress_Single_Instance.template',
'--parameters=InstanceType=m1.xlarge;DBUsername=' + dbusername +
';DBPassword=' + os.environ['OS_PASSWORD'] +
';KeyName=' + keyname])
print "Waiting for OpenStack to initialize and assign network address"
ip = None
tries = 0
while ip == None:
tries += 1
assert tries < 500
time.sleep(10)
for server in nt.servers.list():
if server.name == 'WikiDatabase': # TODO: get from template
address = server.addresses
print "Status: %s" % server.status
if address:
ip = address['wordpress'][0]['addr']
print 'IP found:', ip
break
elif server.status == 'ERROR':
print 'Heat error? Aborting'
assert False
return
tries = 0
while True:
try:
subprocess.check_output(['nc', '-z', ip, '22'])
except Exception:
print 'SSH not up yet...'
time.sleep(10)
tries += 1
assert tries < 100
else:
print 'SSH daemon response detected'
time.sleep(5) # yuck, sometimes SSH is not *really* up
break
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ip, username='ec2-user', allow_agent=True,
look_for_keys=True, password='password')
sftp = ssh.open_sftp()
tries = 0
while True:
try:
sftp.stat('/var/lib/cloud/instance/boot-finished')
except IOError, e:
tries += 1
if e[0] == 2:
assert tries < 40
print "Boot not finished yet..."
time.sleep(15)
else:
raise
else:
print "boot-finished file found, start host checks"
break
stdin, stdout, stderr = ssh.exec_command('cd /opt/aws/bin; sha1sum *')
files = stdout.readlines()
cfn_tools_files = ['cfn-init', 'cfn-hup', 'cfn-signal',
'cfn-get-metadata', 'cfn_helper.py']
cfntools = {}
for file in cfn_tools_files:
with open(self.basepath + '/heat/cfntools/' + file, 'rb') as f:
sha = hashlib.sha1(f.read()).hexdigest()
cfntools[file] = sha
# 1. make sure cfntools SHA in tree match VM's version
for x in range(len(files)):
data = files.pop().split(' ')
cur_file = data[1].rstrip()
if cur_file in cfn_tools_files:
assert data[0] == cfntools[cur_file]
# 2. ensure wordpress was installed
wp_file = '/etc/wordpress/wp-config.php'
stdin, stdout, sterr = ssh.exec_command('ls ' + wp_file)
result = stdout.readlines().pop().rstrip()
assert result == wp_file
# 3. check multipart mime accuracy
transport = ssh.get_transport()
channel = transport.open_session()
channel.get_pty()
channel.invoke_shell() # sudo requires tty
channel.sendall('sudo chmod 777 \
/var/lib/cloud/instance/scripts/startup; \
sudo chmod 777 /var/lib/cloud/instance/user-data.txt.i\n')
time.sleep(1) # necessary for sendall to complete
filepaths = {
'cloud-config': self.basepath + '/heat/cloudinit/config',
'part-handler.py': self.basepath +
'/heat/cloudinit/part-handler.py'
}
f = open(self.basepath +
'/templates/WordPress_Single_Instance.template')
t = json.loads(f.read())
f.close()
params = {}
params['KeyStoneCreds'] = None
t['Parameters']['KeyName']['Value'] = keyname
t['Parameters']['DBUsername']['Value'] = dbusername
t['Parameters']['DBPassword']['Value'] = creds['password']
stack = parser.Stack('test', t, 0, params)
parsed_t = stack.resolve_static_refs(t)
remote_file = sftp.open('/var/lib/cloud/instance/scripts/startup')
remote_file_list = remote_file.read().split('\n')
remote_file.close()
t_data = parsed_t['Resources']['WikiDatabase']['Properties']
t_data = t_data['UserData']['Fn::Base64']['Fn::Join'].pop()
joined_t_data = ''.join(t_data)
t_data_list = joined_t_data.split('\n')
assert t_data_list == remote_file_list
remote_file = sftp.open('/var/lib/cloud/instance/user-data.txt.i')
msg = email.message_from_file(remote_file)
remote_file.close()
for part in msg.walk():
# multipart/* are just containers
if part.get_content_maintype() == 'multipart':
continue
file = part.get_filename()
data = part.get_payload()
if file in filepaths.keys():
with open(filepaths[file]) as f:
assert data == f.read()
# cleanup
ssh.close()
subprocess.call(['heat', 'delete', 'teststack'])
if __name__ == '__main__':
sys.argv.append(__file__)
nose.main()

View File

@ -10,7 +10,7 @@ setenv = VIRTUAL_ENV={envdir}
NOSE_OPENSTACK_SHOW_ELAPSED=1
deps = -r{toxinidir}/tools/pip-requires
-r{toxinidir}/tools/test-requires
commands = nosetests
commands = nosetests -a tag='unit'
[testenv:pep8]
deps = pep8
@ -20,7 +20,7 @@ commands = pep8 --repeat --show-source heat setup.py
commands = {posargs}
[testenv:cover]
commands = nosetests --cover-erase --cover-package=heat --with-xcoverage
commands = nosetests --cover-erase --cover-package=heat --with-xcoverage -a tag='unit'
[tox:jenkins]
downloadcache = ~/cache/pip
@ -35,7 +35,7 @@ setenv = NOSE_WITH_XUNIT=1
[testenv:jenkinscover]
setenv = NOSE_WITH_XUNIT=1
commands = nosetests --cover-erase --cover-package=heat --with-xcoverage
commands = nosetests --cover-erase --cover-package=heat --with-xcoverage -a tag='unit'
[testenv:jenkinsvenv]
setenv = NOSE_WITH_XUNIT=1