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:
Ryan Beisner 2015-06-11 15:38:21 +00:00
parent 82fcb9ca89
commit 14c49be93c
7 changed files with 280 additions and 257 deletions

View File

@ -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...

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -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,

View File

@ -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: