Move some comments into README
Make stack_create test contained Add reverse relation checks Combine rabbit config into dict Move create_or_get_keypair to charmhelpers Move file url to standard library in charmhelpers Update Makefile
This commit is contained in:
parent
8f05645256
commit
02ad7890f0
7
Makefile
7
Makefile
@ -6,12 +6,9 @@ lint:
|
||||
@flake8 --exclude hooks/charmhelpers hooks tests unit_tests
|
||||
@charm proof
|
||||
|
||||
unit_test:
|
||||
@echo Unit tests...
|
||||
@$(PYTHON) /usr/bin/nosetests --nologcapture --with-coverage unit_tests
|
||||
|
||||
test: unit_test
|
||||
test:
|
||||
@# Bundletester expects unit tests here.
|
||||
@$(PYTHON) /usr/bin/nosetests --nologcapture --with-coverage unit_tests
|
||||
|
||||
functional_test:
|
||||
@echo Starting all functional, lint and unit tests...
|
||||
|
23
tests/README
23
tests/README
@ -1,6 +1,29 @@
|
||||
This directory provides Amulet tests that focus on verification of heat
|
||||
deployments.
|
||||
|
||||
test_* methods are called in lexical sort order.
|
||||
|
||||
Test name convention to ensure desired test order:
|
||||
1xx service and endpoint checks
|
||||
2xx relation checks
|
||||
3xx config checks
|
||||
4xx functional checks
|
||||
9xx restarts and other final checks
|
||||
|
||||
Common uses of heat relations in deployments:
|
||||
- [ heat, mysql ]
|
||||
- [ heat, keystone ]
|
||||
- [ heat, rabbitmq-server ]
|
||||
|
||||
More detailed relations of heat service in a common deployment:
|
||||
relations:
|
||||
amqp:
|
||||
- rabbitmq-server
|
||||
identity-service:
|
||||
- keystone
|
||||
shared-db:
|
||||
- mysql
|
||||
|
||||
In order to run tests, you'll need charm-tools installed (in addition to
|
||||
juju, of course):
|
||||
sudo add-apt-repository ppa:juju/stable
|
||||
|
@ -2,34 +2,9 @@
|
||||
|
||||
"""
|
||||
Basic heat functional test.
|
||||
|
||||
test_* methods are called in lexical sort order.
|
||||
|
||||
Convention to ensure desired test order:
|
||||
1xx service and endpoint checks
|
||||
2xx relation checks
|
||||
3xx config checks
|
||||
4xx functional checks
|
||||
9xx restarts and other final checks
|
||||
|
||||
Common relation definitions:
|
||||
- [ heat, mysql ]
|
||||
- [ heat, keystone ]
|
||||
- [ heat, rabbitmq-server ]
|
||||
|
||||
Resultant relations of heat service:
|
||||
relations:
|
||||
amqp:
|
||||
- rabbitmq-server
|
||||
identity-service:
|
||||
- keystone
|
||||
shared-db:
|
||||
- mysql
|
||||
"""
|
||||
import amulet
|
||||
import os
|
||||
import time
|
||||
from heatclient.openstack.common.py3kcompat import urlutils
|
||||
from heatclient.common import template_utils
|
||||
|
||||
from charmhelpers.contrib.openstack.amulet.deployment import (
|
||||
@ -153,26 +128,140 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
# Authenticate admin with heat endpoint
|
||||
self.heat = u.authenticate_heat_admin(self.keystone)
|
||||
|
||||
def file_url(self, file_rel_path):
|
||||
"""Return file:// url for a file expressed as a relative path."""
|
||||
file_abs_path = os.path.abspath(file_rel_path)
|
||||
file_url = urlutils.urljoin('file:',
|
||||
urlutils.pathname2url(file_abs_path))
|
||||
return file_url
|
||||
def _image_create(self):
|
||||
"""Create an image to be used by the heat template, verify it exists"""
|
||||
u.log.debug('Creating glance image ({})...'.format(IMAGE_NAME))
|
||||
|
||||
def create_or_get_keypair(self, keypair_name="testkey"):
|
||||
"""Create a new keypair, or return pointer if it already exists."""
|
||||
# Create a new image
|
||||
image_new = u.create_cirros_image(self.glance, IMAGE_NAME)
|
||||
|
||||
# Confirm image is created and has status of 'active'
|
||||
if not image_new:
|
||||
message = 'glance image create failed'
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
# Verify new image name
|
||||
images_list = list(self.glance.images.list())
|
||||
if images_list[0].name != IMAGE_NAME:
|
||||
message = ('glance image create failed or unexpected '
|
||||
'image name {}'.format(images_list[0].name))
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def _keypair_create(self):
|
||||
"""Create a keypair to be used by the heat template,
|
||||
or get a keypair if it exists."""
|
||||
self.keypair = u.create_or_get_keypair(self.nova,
|
||||
keypair_name=KEYPAIR_NAME)
|
||||
if not self.keypair:
|
||||
msg = 'Failed to create or get keypair.'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
u.log.debug("Keypair: {} {}".format(self.keypair.id,
|
||||
self.keypair.fingerprint))
|
||||
|
||||
def _stack_create(self):
|
||||
"""Create a heat stack from a basic heat template, verify its status"""
|
||||
u.log.debug('Creating heat stack...')
|
||||
|
||||
t_url = u.file_to_url(TEMPLATE_REL_PATH)
|
||||
r_req = self.heat.http_client.raw_request
|
||||
u.log.debug('template url: {}'.format(t_url))
|
||||
|
||||
t_files, template = template_utils.get_template_contents(t_url, r_req)
|
||||
env_files, env = template_utils.process_environment_and_files(
|
||||
env_path=None)
|
||||
|
||||
fields = {
|
||||
'stack_name': STACK_NAME,
|
||||
'timeout_mins': '15',
|
||||
'disable_rollback': False,
|
||||
'parameters': {
|
||||
'admin_pass': 'Ubuntu',
|
||||
'key_name': KEYPAIR_NAME,
|
||||
'image': IMAGE_NAME
|
||||
},
|
||||
'template': template,
|
||||
'files': dict(list(t_files.items()) + list(env_files.items())),
|
||||
'environment': env
|
||||
}
|
||||
|
||||
# Create the stack.
|
||||
try:
|
||||
_keypair = self.nova.keypairs.get(keypair_name)
|
||||
u.log.debug('Keypair ({}) already exists, '
|
||||
'using it.'.format(keypair_name))
|
||||
return _keypair
|
||||
except:
|
||||
u.log.debug('Keypair ({}) does not exist, '
|
||||
'creating it.'.format(keypair_name))
|
||||
_stack = self.heat.stacks.create(**fields)
|
||||
u.log.debug('Stack data: {}'.format(_stack))
|
||||
_stack_id = _stack['stack']['id']
|
||||
u.log.debug('Creating new stack, ID: {}'.format(_stack_id))
|
||||
except Exception as e:
|
||||
# Generally, an api or cloud config error if this is hit.
|
||||
msg = 'Failed to create heat stack: {}'.format(e)
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
_keypair = self.nova.keypairs.create(name=keypair_name)
|
||||
return _keypair
|
||||
# Confirm stack reaches COMPLETE status.
|
||||
# /!\ Heat stacks reach a COMPLETE status even when nova cannot
|
||||
# find resources (a valid hypervisor) to fit the instance, in
|
||||
# which case the heat stack self-deletes! Confirm anyway...
|
||||
ret = u.resource_reaches_status(self.heat.stacks, _stack_id,
|
||||
expected_stat="COMPLETE",
|
||||
msg="Stack status wait")
|
||||
_stacks = list(self.heat.stacks.list())
|
||||
u.log.debug('All stacks: {}'.format(_stacks))
|
||||
if not ret:
|
||||
msg = 'Heat stack failed to reach expected state.'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm stack still exists.
|
||||
try:
|
||||
_stack = self.heat.stacks.get(STACK_NAME)
|
||||
except Exception as e:
|
||||
# Generally, a resource availability issue if this is hit.
|
||||
msg = 'Failed to get heat stack: {}'.format(e)
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm stack name.
|
||||
u.log.debug('Expected, actual stack name: {}, '
|
||||
'{}'.format(STACK_NAME, _stack.stack_name))
|
||||
if STACK_NAME != _stack.stack_name:
|
||||
msg = 'Stack name mismatch, {} != {}'.format(STACK_NAME,
|
||||
_stack.stack_name)
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
def _stack_resource_compute(self):
|
||||
"""Confirm that the stack has created a subsequent nova
|
||||
compute resource, and confirm its status."""
|
||||
u.log.debug('Confirming heat stack resource status...')
|
||||
|
||||
# Confirm existence of a heat-generated nova compute resource.
|
||||
_resource = self.heat.resources.get(STACK_NAME, RESOURCE_TYPE)
|
||||
_server_id = _resource.physical_resource_id
|
||||
if _server_id:
|
||||
u.log.debug('Heat template spawned nova instance, '
|
||||
'ID: {}'.format(_server_id))
|
||||
else:
|
||||
msg = 'Stack failed to spawn a nova compute resource (instance).'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm nova instance reaches ACTIVE status.
|
||||
ret = u.resource_reaches_status(self.nova.servers, _server_id,
|
||||
expected_stat="ACTIVE",
|
||||
msg="nova instance")
|
||||
if not ret:
|
||||
msg = 'Nova compute instance failed to reach expected state.'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
def _stack_delete(self):
|
||||
"""Delete a heat stack, verify."""
|
||||
u.log.debug('Deleting heat stack...')
|
||||
u.delete_resource(self.heat.stacks, STACK_NAME, msg="heat stack")
|
||||
|
||||
def _image_delete(self):
|
||||
"""Delete that image."""
|
||||
u.log.debug('Deleting glance image...')
|
||||
image = self.nova.images.find(name=IMAGE_NAME)
|
||||
u.delete_resource(self.nova.images, image, msg="glance image")
|
||||
|
||||
def _keypair_delete(self):
|
||||
"""Delete that keypair."""
|
||||
u.log.debug('Deleting keypair...')
|
||||
u.delete_resource(self.nova.keypairs, KEYPAIR_NAME, msg="nova keypair")
|
||||
|
||||
def test_100_services(self):
|
||||
"""Verify the expected services are running on the corresponding
|
||||
@ -263,6 +352,23 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
message = u.relation_error('heat:mysql shared-db', ret)
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_201_mysql_heat_shared_db_relation(self):
|
||||
"""Verify the mysql:heat shared-db relation data"""
|
||||
u.log.debug('Checking mysql:heat shared-db relation data...')
|
||||
unit = self.mysql_sentry
|
||||
relation = ['shared-db', 'heat:shared-db']
|
||||
expected = {
|
||||
'private-address': u.valid_ip,
|
||||
'db_host': u.valid_ip,
|
||||
'heat_allowed_units': 'heat/0',
|
||||
'heat_password': u.not_null
|
||||
}
|
||||
|
||||
ret = u.validate_relation_data(unit, relation, expected)
|
||||
if ret:
|
||||
message = u.relation_error('mysql:heat shared-db', ret)
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_202_heat_keystone_identity_relation(self):
|
||||
"""Verify the heat:keystone identity-service relation data"""
|
||||
u.log.debug('Checking heat:keystone identity-service relation data...')
|
||||
@ -285,6 +391,30 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
message = u.relation_error('heat:keystone identity-service', ret)
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_203_keystone_heat_identity_relation(self):
|
||||
"""Verify the keystone:heat identity-service relation data"""
|
||||
u.log.debug('Checking keystone:heat identity-service relation data...')
|
||||
unit = self.keystone_sentry
|
||||
relation = ['identity-service', 'heat:identity-service']
|
||||
expected = {
|
||||
'service_protocol': 'http',
|
||||
'service_tenant': 'services',
|
||||
'admin_token': 'ubuntutesting',
|
||||
'service_password': u.not_null,
|
||||
'service_port': '5000',
|
||||
'auth_port': '35357',
|
||||
'auth_protocol': 'http',
|
||||
'private-address': u.valid_ip,
|
||||
'auth_host': u.valid_ip,
|
||||
'service_username': 'heat-cfn_heat',
|
||||
'service_tenant_id': u.not_null,
|
||||
'service_host': u.valid_ip
|
||||
}
|
||||
ret = u.validate_relation_data(unit, relation, expected)
|
||||
if ret:
|
||||
message = u.relation_error('keystone:heat identity-service', ret)
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_204_heat_rmq_amqp_relation(self):
|
||||
"""Verify the heat:rabbitmq-server amqp relation data"""
|
||||
u.log.debug('Checking heat:rabbitmq-server amqp relation data...')
|
||||
@ -301,6 +431,22 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
message = u.relation_error('heat:rabbitmq-server amqp', ret)
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_205_rmq_heat_amqp_relation(self):
|
||||
"""Verify the rabbitmq-server:heat amqp relation data"""
|
||||
u.log.debug('Checking rabbitmq-server:heat amqp relation data...')
|
||||
unit = self.rabbitmq_sentry
|
||||
relation = ['amqp', 'heat:amqp']
|
||||
expected = {
|
||||
'private-address': u.valid_ip,
|
||||
'password': u.not_null,
|
||||
'hostname': u.valid_ip,
|
||||
}
|
||||
|
||||
ret = u.validate_relation_data(unit, relation, expected)
|
||||
if ret:
|
||||
message = u.relation_error('rabbitmq-server:heat amqp', ret)
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_300_heat_config(self):
|
||||
"""Verify the data in the heat config file."""
|
||||
u.log.debug('Checking heat config file data...')
|
||||
@ -338,6 +484,10 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
'environment_dir': '/etc/heat/environment.d',
|
||||
'deferred_auth_method': 'password',
|
||||
'host': 'heat',
|
||||
'rabbit_userid': 'heat',
|
||||
'rabbit_virtual_host': 'openstack',
|
||||
'rabbit_password': rmq_rel['password'],
|
||||
'rabbit_host': rmq_rel['hostname']
|
||||
},
|
||||
'keystone_authtoken': {
|
||||
'auth_uri': auth_uri,
|
||||
@ -363,15 +513,6 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
},
|
||||
}
|
||||
|
||||
expected['DEFAULT'].update(
|
||||
{
|
||||
'rabbit_userid': 'heat',
|
||||
'rabbit_virtual_host': 'openstack',
|
||||
'rabbit_password': rmq_rel['password'],
|
||||
'rabbit_host': rmq_rel['hostname']
|
||||
}
|
||||
)
|
||||
|
||||
for section, pairs in expected.iteritems():
|
||||
ret = u.validate_config_data(unit, conf, section, pairs)
|
||||
if ret:
|
||||
@ -379,7 +520,8 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_400_heat_resource_types_list(self):
|
||||
"""Check default heat resource list functionality."""
|
||||
"""Check default heat resource list behavior, also confirm
|
||||
heat functionality."""
|
||||
u.log.debug('Checking default heat resouce list...')
|
||||
try:
|
||||
types = list(self.heat.resource_types.list())
|
||||
@ -390,7 +532,7 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
u.log.error('{}'.format(msg))
|
||||
raise
|
||||
if len(types) > 0:
|
||||
u.log.debug('Resource type list length is non-zero '
|
||||
u.log.debug('Resource type list is populated '
|
||||
'({}, ok).'.format(len(types)))
|
||||
else:
|
||||
msg = 'Resource type list length is zero!'
|
||||
@ -402,7 +544,8 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
raise
|
||||
|
||||
def test_402_heat_stack_list(self):
|
||||
"""Check default heat stack list functionality."""
|
||||
"""Check default heat stack list behavior, also confirm
|
||||
heat functionality."""
|
||||
u.log.debug('Checking default heat stack list...')
|
||||
try:
|
||||
stacks = list(self.heat.stacks.list())
|
||||
@ -417,139 +560,16 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
u.log.error(msg)
|
||||
raise
|
||||
|
||||
def test_410_image_create(self):
|
||||
"""Create an image to be used by the heat template, verify it exists"""
|
||||
u.log.debug('Creating glance image ({})...'.format(IMAGE_NAME))
|
||||
|
||||
# Create a new image
|
||||
image_new = u.create_cirros_image(self.glance, IMAGE_NAME)
|
||||
|
||||
# Confirm image is created and has status of 'active'
|
||||
if not image_new:
|
||||
message = 'glance image create failed'
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
# Verify new image name
|
||||
images_list = list(self.glance.images.list())
|
||||
if images_list[0].name != IMAGE_NAME:
|
||||
message = ('glance image create failed or unexpected '
|
||||
'image name {}'.format(images_list[0].name))
|
||||
amulet.raise_status(amulet.FAIL, msg=message)
|
||||
|
||||
def test_411_nova_keypair_create(self):
|
||||
"""Create a keypair to be used by the heat template,
|
||||
or get a keypair if it exists."""
|
||||
self.keypair = self.create_or_get_keypair(keypair_name=KEYPAIR_NAME)
|
||||
if not self.keypair:
|
||||
msg = 'Failed to create or get keypair.'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
u.log.debug("Keypair: {} {}".format(self.keypair.id,
|
||||
self.keypair.fingerprint))
|
||||
|
||||
def test_412_heat_stack_create(self):
|
||||
"""Create a heat stack from a basic heat template, verify its status"""
|
||||
u.log.debug('Creating heat stack...')
|
||||
|
||||
t_url = self.file_url(TEMPLATE_REL_PATH)
|
||||
r_req = self.heat.http_client.raw_request
|
||||
u.log.debug('template url: {}'.format(t_url))
|
||||
|
||||
t_files, template = template_utils.get_template_contents(t_url, r_req)
|
||||
env_files, env = template_utils.process_environment_and_files(
|
||||
env_path=None)
|
||||
|
||||
fields = {
|
||||
'stack_name': STACK_NAME,
|
||||
'timeout_mins': '15',
|
||||
'disable_rollback': False,
|
||||
'parameters': {
|
||||
'admin_pass': 'Ubuntu',
|
||||
'key_name': KEYPAIR_NAME,
|
||||
'image': IMAGE_NAME
|
||||
},
|
||||
'template': template,
|
||||
'files': dict(list(t_files.items()) + list(env_files.items())),
|
||||
'environment': env
|
||||
}
|
||||
|
||||
# Create the stack.
|
||||
try:
|
||||
_stack = self.heat.stacks.create(**fields)
|
||||
u.log.debug('Stack data: {}'.format(_stack))
|
||||
_stack_id = _stack['stack']['id']
|
||||
u.log.debug('Creating new stack, ID: {}'.format(_stack_id))
|
||||
except Exception as e:
|
||||
# Generally, an api or cloud config error if this is hit.
|
||||
msg = 'Failed to create heat stack: {}'.format(e)
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm stack reaches COMPLETE status.
|
||||
# /!\ Heat stacks reach a COMPLETE status even when nova cannot
|
||||
# find resources (a valid hypervisor) to fit the instance, in
|
||||
# which case the heat stack self-deletes! Confirm anyway...
|
||||
ret = u.resource_reaches_status(self.heat.stacks, _stack_id,
|
||||
expected_stat="COMPLETE",
|
||||
msg="Stack status wait")
|
||||
_stacks = list(self.heat.stacks.list())
|
||||
u.log.debug('All stacks: {}'.format(_stacks))
|
||||
if not ret:
|
||||
msg = 'Heat stack failed to reach expected state.'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm stack still exists.
|
||||
try:
|
||||
_stack = self.heat.stacks.get(STACK_NAME)
|
||||
except Exception as e:
|
||||
# Generally, a resource availability issue if this is hit.
|
||||
msg = 'Failed to get heat stack: {}'.format(e)
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm stack name.
|
||||
u.log.debug('Expected, actual stack name: {}, '
|
||||
'{}'.format(STACK_NAME, _stack.stack_name))
|
||||
if STACK_NAME != _stack.stack_name:
|
||||
msg = 'Stack name mismatch, {} != {}'.format(STACK_NAME,
|
||||
_stack.stack_name)
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
def test_413_heat_stack_resource_compute(self):
|
||||
"""Confirm that the stack has created a subsequent nova
|
||||
compute resource, and confirm its status."""
|
||||
u.log.debug('Confirming heat stack resource status...')
|
||||
|
||||
# Confirm existence of a heat-generated nova compute resource.
|
||||
_resource = self.heat.resources.get(STACK_NAME, RESOURCE_TYPE)
|
||||
_server_id = _resource.physical_resource_id
|
||||
if _server_id:
|
||||
u.log.debug('Heat template spawned nova instance, '
|
||||
'ID: {}'.format(_server_id))
|
||||
else:
|
||||
msg = 'Stack failed to spawn a nova compute resource (instance).'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
# Confirm nova instance reaches ACTIVE status.
|
||||
ret = u.resource_reaches_status(self.nova.servers, _server_id,
|
||||
expected_stat="ACTIVE",
|
||||
msg="nova instance")
|
||||
if not ret:
|
||||
msg = 'Nova compute instance failed to reach expected state.'
|
||||
amulet.raise_status(amulet.FAIL, msg=msg)
|
||||
|
||||
def test_490_heat_stack_delete(self):
|
||||
"""Delete a heat stack, verify."""
|
||||
u.log.debug('Deleting heat stack...')
|
||||
u.delete_resource(self.heat.stacks, STACK_NAME, msg="heat stack")
|
||||
|
||||
def test_491_image_delete(self):
|
||||
"""Delete that image."""
|
||||
u.log.debug('Deleting glance image...')
|
||||
image = self.nova.images.find(name=IMAGE_NAME)
|
||||
u.delete_resource(self.nova.images, image, msg="glance image")
|
||||
|
||||
def test_492_keypair_delete(self):
|
||||
"""Delete that keypair."""
|
||||
u.log.debug('Deleting keypair...')
|
||||
u.delete_resource(self.nova.keypairs, KEYPAIR_NAME, msg="nova keypair")
|
||||
def test_410_heat_stack_create_delete(self):
|
||||
"""Create a heat stack from template, confirm that a corresponding
|
||||
nova compute resource is spawned, delete stack."""
|
||||
self._image_create()
|
||||
self._keypair_create()
|
||||
self._stack_create()
|
||||
self._stack_resource_compute()
|
||||
self._stack_delete()
|
||||
self._image_delete()
|
||||
self._keypair_delete()
|
||||
|
||||
def test_900_heat_restart_on_config_change(self):
|
||||
"""Verify that the specified services are restarted when the config
|
||||
|
@ -18,10 +18,12 @@ import ConfigParser
|
||||
import distro_info
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import sys
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
|
||||
class AmuletUtils(object):
|
||||
@ -72,7 +74,11 @@ class AmuletUtils(object):
|
||||
return False
|
||||
|
||||
def get_ubuntu_release_from_sentry(self, sentry_unit):
|
||||
"""Get Ubuntu release codename from sentry unit"""
|
||||
"""Get Ubuntu release codename from sentry unit.
|
||||
|
||||
:param sentry_unit: amulet sentry/service unit pointer
|
||||
:returns: list of strings - release codename, failure message
|
||||
"""
|
||||
msg = None
|
||||
cmd = 'lsb_release -cs'
|
||||
release, code = sentry_unit.run(cmd)
|
||||
@ -88,86 +94,40 @@ class AmuletUtils(object):
|
||||
"({})".format(release, self.ubuntu_releases))
|
||||
return release, msg
|
||||
|
||||
def normalize_service_check_command(self, series, cmd):
|
||||
"""Normalize a service check command with init system logic,
|
||||
providing backward compatibility for tests which presume
|
||||
a specific init system is present.
|
||||
"""
|
||||
# NOTE(beisner): this work-around is intended to be a temporary
|
||||
# unblocker of vivid, wily and later tests. See deprecation
|
||||
# warning on validate_services().
|
||||
systemd_switch = self.ubuntu_releases.index('vivid')
|
||||
|
||||
# Preserve sudo usage and strip it out if present
|
||||
if cmd.startswith('sudo '):
|
||||
sudo_if_sudo, cmd = cmd[:5], cmd[5:]
|
||||
else:
|
||||
sudo_if_sudo = ''
|
||||
|
||||
# Guess the service name
|
||||
cmd_words = list(set(cmd.split()))
|
||||
for remove_items in ['status', 'service']:
|
||||
if remove_items in cmd_words:
|
||||
cmd_words.remove(remove_items)
|
||||
service_name = cmd_words[0]
|
||||
self.log.debug('Service name: {}'.format(service_name))
|
||||
|
||||
if (cmd.startswith('status') and
|
||||
self.ubuntu_releases.index(series) >= systemd_switch):
|
||||
# systemd init expected, but upstart command found
|
||||
self.log.debug('Correcting for an upstart command '
|
||||
'on a systemd release')
|
||||
return '{}{} {} {}'.format(sudo_if_sudo, 'service',
|
||||
service_name, 'status')
|
||||
elif (cmd.startswith('service') and
|
||||
self.ubuntu_releases.index(series) < systemd_switch):
|
||||
# upstart init expected, but systemd command found
|
||||
self.log.debug('Correcting for a systemd command on '
|
||||
'an upstart release')
|
||||
return '{}{} {}'.format(sudo_if_sudo, 'status', service_name)
|
||||
return cmd
|
||||
|
||||
def validate_services(self, commands):
|
||||
"""Validate services.
|
||||
|
||||
Verify the specified services are running on the corresponding
|
||||
"""Validate that lists of commands succeed on service units. Can be
|
||||
used to verify system services are running on the corresponding
|
||||
service units.
|
||||
"""
|
||||
|
||||
:param command: dict with sentry keys and arbitrary command list values
|
||||
:returns: None if successful, Failure string message otherwise
|
||||
"""
|
||||
self.log.debug('Checking status of system services...')
|
||||
|
||||
# /!\ DEPRECATION WARNING (beisner):
|
||||
# This method is present to preserve functionality
|
||||
# of older tests which presume upstart init system, until they are
|
||||
# rewritten to use validate_services_by_name().
|
||||
# New and existing tests should be rewritten to use
|
||||
# validate_services_by_name() as it is aware of init systems.
|
||||
self.log.warn('/!\\ DEPRECATION WARNING: use '
|
||||
'validate_services_by_name instead of validate_services '
|
||||
'due to init system differences.')
|
||||
|
||||
for k, v in six.iteritems(commands):
|
||||
for cmd in v:
|
||||
|
||||
# Ask unit for its Ubuntu release codename
|
||||
release, ret = self.get_ubuntu_release_from_sentry(k)
|
||||
if ret:
|
||||
return ret
|
||||
|
||||
# Conditionally correct for init system assumptions
|
||||
cmd_normalized = self.normalize_service_check_command(release,
|
||||
cmd)
|
||||
self.log.debug('Command, normalized with init logic: '
|
||||
'{}'.format(cmd_normalized))
|
||||
|
||||
output, code = k.run(cmd_normalized)
|
||||
output, code = k.run(cmd)
|
||||
self.log.debug('{} `{}` returned '
|
||||
'{}'.format(k.info['unit_name'],
|
||||
cmd_normalized, code))
|
||||
cmd, code))
|
||||
if code != 0:
|
||||
return "command `{}` returned {}".format(cmd, str(code))
|
||||
return None
|
||||
|
||||
def validate_services_by_name(self, sentry_services):
|
||||
"""Validate system service status by service name, automatically
|
||||
detecting init system based on Ubuntu release codename."""
|
||||
detecting init system based on Ubuntu release codename.
|
||||
|
||||
:param sentry_resources: dict with sentry keys and svc list values
|
||||
:returns: None if successful, Failure string message otherwise
|
||||
"""
|
||||
self.log.debug('Checking status of system services...')
|
||||
|
||||
# Point at which systemd became a thing
|
||||
@ -441,3 +401,8 @@ class AmuletUtils(object):
|
||||
_release_list = _d.all
|
||||
self.log.debug('Ubuntu release list: {}'.format(_release_list))
|
||||
return _release_list
|
||||
|
||||
def file_to_url(self, file_rel_path):
|
||||
"""Convert a relative file path to a file URL."""
|
||||
_abs_path = os.path.abspath(file_rel_path)
|
||||
return urlparse.urlparse(_abs_path, scheme='file').geturl()
|
||||
|
@ -110,7 +110,8 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
(self.precise_essex, self.precise_folsom, self.precise_grizzly,
|
||||
self.precise_havana, self.precise_icehouse,
|
||||
self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
|
||||
self.trusty_kilo, self.vivid_kilo) = range(10)
|
||||
self.trusty_kilo, self.vivid_kilo, self.trusty_liberty,
|
||||
self.wily_liberty) = range(12)
|
||||
|
||||
releases = {
|
||||
('precise', None): self.precise_essex,
|
||||
@ -121,8 +122,11 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
('trusty', None): self.trusty_icehouse,
|
||||
('trusty', 'cloud:trusty-juno'): self.trusty_juno,
|
||||
('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
|
||||
('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,
|
||||
('utopic', None): self.utopic_juno,
|
||||
('vivid', None): self.vivid_kilo}
|
||||
('vivid', None): self.vivid_kilo,
|
||||
('wily', None): self.wily_liberty}
|
||||
|
||||
return releases[(self.series, self.openstack)]
|
||||
|
||||
def _get_openstack_release_string(self):
|
||||
@ -138,6 +142,7 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
('trusty', 'icehouse'),
|
||||
('utopic', 'juno'),
|
||||
('vivid', 'kilo'),
|
||||
('wily', 'liberty'),
|
||||
])
|
||||
if self.openstack:
|
||||
os_origin = self.openstack.split(':')[1]
|
||||
|
@ -325,6 +325,20 @@ class OpenStackAmuletUtils(AmuletUtils):
|
||||
|
||||
return True
|
||||
|
||||
def create_or_get_keypair(self, nova, keypair_name="testkey"):
|
||||
"""Create a new keypair, or return pointer if it already exists."""
|
||||
try:
|
||||
_keypair = nova.keypairs.get(keypair_name)
|
||||
self.log.debug('Keypair ({}) already exists, '
|
||||
'using it.'.format(keypair_name))
|
||||
return _keypair
|
||||
except:
|
||||
self.log.debug('Keypair ({}) does not exist, '
|
||||
'creating it.'.format(keypair_name))
|
||||
|
||||
_keypair = nova.keypairs.create(name=keypair_name)
|
||||
return _keypair
|
||||
|
||||
def delete_resource(self, resource, resource_id,
|
||||
msg="resource", max_wait=120):
|
||||
"""Delete one openstack resource, such as one instance, keypair,
|
||||
|
@ -18,7 +18,6 @@ parameters:
|
||||
type: string
|
||||
description: Flavor for the server to be created
|
||||
default: m1.tiny
|
||||
# default: m1.small
|
||||
constraints:
|
||||
- custom_constraint: nova.flavor
|
||||
image:
|
||||
|
Loading…
x
Reference in New Issue
Block a user