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)
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()
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
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()
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 *')
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:
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())
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
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__)