OpenStack Orchestration (Heat)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
8.7 KiB

# 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
# 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(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'],
dbusername = 'testuser'['heat', '-d', 'jeos-create',
args[0], args[1], args[2]])
gclient = glance_client.Client(host="", 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
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
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['heat', '-d', 'create', 'teststack',
'--template-file=' + self.basepath +
'--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
for server in nt.servers.list():
if == 'WikiDatabase': # TODO: get from template
address = server.addresses
print "Status: %s" % server.status
if address:
ip = address['wordpress'][0]['addr']
print 'IP found:', ip
elif server.status == 'ERROR':
print 'Heat error? Aborting'
assert False
tries = 0
while True:
subprocess.check_output(['nc', '-z', ip, '22'])
except Exception:
print 'SSH not up yet...'
tries += 1
assert tries < 100
print 'SSH daemon response detected'
time.sleep(5) # yuck, sometimes SSH is not *really* up
ssh = paramiko.SSHClient()
ssh.connect(ip, username='ec2-user', allow_agent=True,
look_for_keys=True, password='password')
sftp = ssh.open_sftp()
tries = 0
while True:
except IOError, e:
tries += 1
if e[0] == 2:
assert tries < 40
print "Boot not finished yet..."
print "boot-finished file found, start host checks"
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', '']
cfntools = {}
for file in cfn_tools_files:
with open(self.basepath + '/heat/cfntools/' + file, 'rb') as f:
sha = hashlib.sha1(
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.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',
'': self.basepath +
f = open(self.basepath +
t = json.loads(
template = parser.Template(t)
params = parser.Parameters('test', t,
{'KeyName': keyname,
'DBUsername': dbusername,
'DBPassword': creds['password']})
stack = parser.Stack(None, 'test', template, params)
parsed_t = stack.resolve_static_refs(t)
remote_file ='/var/lib/cloud/instance/scripts/startup')
remote_file_list ='\n')
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 ='/var/lib/cloud/instance/user-data.txt.i')
msg = email.message_from_file(remote_file)
for part in msg.walk():
# multipart/* are just containers
if part.get_content_maintype() == 'multipart':
file = part.get_filename()
data = part.get_payload()
if file in filepaths.keys():
with open(filepaths[file]) as f:
assert data ==
# cleanup
ssh.close()['heat', 'delete', 'teststack'])
if __name__ == '__main__':