fuel-ostf/fuel_health/heatmanager.py

237 lines
8.8 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack, LLC
# Copyright 2013 Mirantis, Inc.
# All Rights Reserved.
#
# 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 logging
import os
import traceback
import fuel_health.common.ssh
from fuel_health.common.utils.data_utils import rand_name
import fuel_health.nmanager
import fuel_health.test
LOG = logging.getLogger(__name__)
class HeatBaseTest(fuel_health.nmanager.NovaNetworkScenarioTest):
"""Base class for Heat openstack sanity and smoke tests."""
@classmethod
def setUpClass(cls):
super(HeatBaseTest, cls).setUpClass()
cls.flavors = []
if cls.manager.clients_initialized:
if cls.heat_client is None:
cls.fail('Heat is unavailable.')
cls.wait_interval = cls.config.compute.build_interval
cls.wait_timeout = cls.config.compute.build_timeout
@classmethod
def tearDownClass(cls):
super(HeatBaseTest, cls).tearDownClass()
if cls.flavors:
try:
[cls.compute_client.flavors.delete(flavor)
for flavor in cls.flavors]
except Exception:
LOG.debug(traceback.format_exc())
def setUp(self):
super(HeatBaseTest, self).setUp()
self.check_clients_state()
if not self.find_micro_flavor():
self.fail('m1.micro flavor was not created.')
@staticmethod
def _list_stacks(client):
return client.stacks.list()
def _find_stack(self, client, key, value):
for stack in self._list_stacks(client):
if hasattr(stack, key) and getattr(stack, key) == value:
return stack
return None
def _create_stack(self, client, template,
disable_rollback=True, parameters={}):
stack_name = rand_name('ost1_test-')
client.stacks.create(stack_name=stack_name,
template=template,
parameters=parameters,
disable_rollback=disable_rollback)
self.addCleanup(self._delete_stack, stack_name)
# heat client doesn't return stack details after creation
# so need to request them:
stack = self._find_stack(client, 'stack_name', stack_name)
return stack
def _delete_stack(self, stack_name):
LOG.debug("Deleting stack: %s" % stack_name)
stack = self._find_stack(self.heat_client, 'stack_name', stack_name)
if stack is None:
return
try:
self.heat_client.stacks.delete(stack.id)
except Exception:
LOG.debug(traceback.format_exc())
self.fail("Cleanup: Failed to delete stack '%s'" % stack_name)
self._wait_for_stack_deleted(stack.id)
LOG.debug("Resource '%s' has been deleted." % stack_name)
def _update_stack(self, client, stack_id, template, parameters={}):
client.stacks.update(stack_id=stack_id,
template=template,
parameters=parameters)
return self._find_stack(client, 'id', stack_id)
def _wait_for_stack_status(self, stack_id, expected_status,
timeout=None, interval=None):
"""The method is a customization of test.status_timeout().
It addresses `stack_status` instead of `status` field and
checks for FAILED instead of ERROR status.
The rest is the same.
"""
if timeout is None:
timeout = self.wait_timeout
if interval is None:
interval = self.wait_interval
def check_status():
stack = self.heat_client.stacks.get(stack_id)
new_status = stack.stack_status
if 'FAIL' in new_status:
self.fail("Failed to get to expected status. "
"In %s state." % new_status)
elif new_status == expected_status:
return True # All good.
LOG.debug("Waiting for %s to get to %s status. "
"Currently in %s status",
stack, expected_status, new_status)
if not fuel_health.test.call_until_true(check_status,
timeout,
interval):
self.fail("Timed out waiting to become %s"
% expected_status)
def _wait_for_stack_deleted(self, stack_id):
f = lambda: self._find_stack(self.heat_client, 'id', stack_id) is None
if not fuel_health.test.call_until_true(f,
self.wait_timeout,
self.wait_interval):
self.fail("Timed out waiting for stack to be deleted.")
def _wait_for_autoscaling(self, exp_count,
timeout, interval, reduced_stack_name):
LOG.info('expected count is {0}'.format(exp_count))
def count_instances(reduced_stack_name):
res = []
_list = self.compute_client.servers.list()
for server in _list:
LOG.info('instance name is {0}'.format(server.name))
if server.name.startswith(reduced_stack_name):
res.append(server)
LOG.info('!!! current res is {0}'.format(res))
return len(res) == exp_count
return fuel_health.test.call_until_true(
count_instances, timeout, interval, reduced_stack_name)
def _wait_for_vm_ready_for_load(self, conn_string, timeout, interval):
"""Wait for fake file to be created on the instance
to make sure that vm is ready.
"""
cmd = (conn_string +
" 'touch /tmp/ostf-heat.txt; "
"test -f /tmp/ostf-heat.txt && echo -ne YES || echo -ne NO'")
def check():
return self._run_ssh_cmd(cmd)[0] == "YES"
return fuel_health.test.call_until_true(
check, timeout, interval)
def _save_key_to_file(self, key):
return self._run_ssh_cmd(
"KEY=`mktemp`; echo '%s' > $KEY; "
"chmod 600 $KEY; echo -ne $KEY;" % key)[0]
def _delete_key_file(self, filepath):
self._run_ssh_cmd("rm -f %s" % filepath)
def _load_vm_cpu(self, connection_string):
self._run_ssh_cmd(connection_string + " 'rm -f /tmp/ostf-heat.txt'")
return self._run_ssh_cmd(connection_string +
" 'cat /dev/urandom |"
" gzip -9 > /dev/null &'")[0]
def _release_vm_cpu(self, connection_string):
pid = self._run_ssh_cmd(connection_string +
' ps -ef | grep \"cat /dev/urandom\" '
'| grep -v grep | awk \"{print $1}\"')[0]
return self._run_ssh_cmd(connection_string +
" kill -9 %s" % pid.strip())[0]
@staticmethod
def _load_template(file_name):
"""Load specified template file from etc directory."""
filepath = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "etc", file_name)
with open(filepath) as f:
return f.read()
@staticmethod
def _customize_template(template):
"""By default, heat templates expect neutron subnets to be available.
But if nova-network is used instead of neutron then
subnet usage should be removed from the template.
"""
return '\n'.join(line for line in template.splitlines()
if 'Ref: Subnet' not in line)
def _get_stack_instances(self, stack_id):
servers = self.heat_client.stacks.get(stack_id).outputs
server_ids = [server['output_value'] for server in servers]
LOG.info('SERVERS {0}'.format(server_ids))
return server_ids
def _get_instances_by_name_mask(self, mask_name):
self.instances = []
# find just created instance
instance_list = self.compute_client.servers.list()
LOG.info('Instances list is {0}'.format(instance_list))
LOG.info('Expected instance name includes {0}'.format(mask_name))
for inst in instance_list:
LOG.info('Instance name is {0}'.format(inst.name))
if inst.name.startswith(mask_name):
self.instances.append(inst)
return self.instances