heat/heat/tests/functional/util.py

268 lines
9.0 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.
import sys
import os
import optparse
import paramiko
import subprocess
import hashlib
import email
import json
import time # for sleep
import nose
import errno
from pkg_resources import resource_string
from nose.plugins.attrib import attr
from nose import with_setup
from nose.exc import SkipTest
from novaclient.v1_1 import client
from heat import utils
from heat.engine import parser
class FuncUtils:
# 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')
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
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 create_stack(self, template_file, distribution):
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', self.stackname,
'--template-file=' + self.basepath +
'/templates/' + template_file,
'--parameters=InstanceType=m1.xlarge;DBUsername=' +
self.dbusername +
';DBPassword=' + os.environ['OS_PASSWORD'] +
';KeyName=' + keyname +
';LinuxDistribution=' + distribution])
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():
# TODO: get PhysicalResourceId instead
if server.name == 'WikiDatabase':
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
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 < 50
else:
print 'SSH daemon response detected'
break
tries = 0
while True:
try:
tries += 1
assert tries < 50
self.ssh.connect(ip, username='ec2-user', allow_agent=True,
look_for_keys=True, password='password')
except paramiko.AuthenticationException:
print 'Authentication error'
time.sleep(2)
except Exception, e:
if e.errno != errno.EHOSTUNREACH:
raise
print 'Preparing to connect over SSH'
time.sleep(2)
else:
print 'SSH connected'
break
self.sftp = self.ssh.open_sftp()
tries = 0
while True:
try:
self.sftp.stat('/var/lib/cloud/instance/boot-finished')
except IOError, e:
tries += 1
if e.errno == errno.ENOENT:
assert tries < 50
print "Boot not finished yet..."
time.sleep(15)
else:
print e.errno
raise
else:
print "Guest fully booted"
break
def check_cfntools(self):
stdin, stdout, stderr = \
self.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:
file_data = resource_string('heat_jeos', 'cfntools/' + file)
sha = hashlib.sha1(file_data).hexdigest()
cfntools[file] = sha
# 1. make sure installed cfntools SHA 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]
print 'VM cfntools integrity verified'
def wait_for_provisioning(self):
print "Waiting for provisioning to complete"
tries = 0
while True:
try:
self.sftp.stat('/var/lib/cloud/instance/provision-finished')
except IOError, e:
tries += 1
if e.errno == errno.ENOENT:
assert tries < 500
print "Provisioning not finished yet..."
time.sleep(15)
else:
print e.errno
raise
else:
print "Provisioning completed"
break
def check_user_data(self, template_file):
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())
f.close()
template = parser.Template(t)
params = parser.Parameters('test', t,
{'KeyName': 'required_parameter',
'DBUsername': self.dbusername,
'DBPassword': self.creds['password']})
stack = parser.Stack(None, 'test', template, params)
parsed_t = stack.resolve_static_data(t)
remote_file = self.sftp.open('/var/lib/cloud/data/cfn-userdata')
remote_file_list = remote_file.read().split('\n')
remote_file_list_u = map(unicode, remote_file_list)
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')
# must match user data injection
t_data_list.insert(len(t_data_list) - 1,
u'touch /var/lib/cloud/instance/provision-finished')
assert t_data_list == remote_file_list_u
remote_file = self.sftp.open('/var/lib/cloud/instance/user-data.txt.i')
msg = email.message_from_file(remote_file)
remote_file.close()
filepaths = {
'cloud-config': self.basepath + '/heat/cloudinit/config',
'part-handler.py': self.basepath +
'/heat/cloudinit/part-handler.py'
}
# check multipart mime accuracy
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()
def cleanup(self):
self.ssh.close()
subprocess.call(['heat', 'delete', self.stackname])
if __name__ == '__main__':
sys.argv.append(__file__)
nose.main()