251 lines
8.7 KiB
Python
251 lines
8.7 KiB
Python
# 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 is 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()
|