Merge "fix int-tests running with out volume"

This commit is contained in:
Jenkins 2013-01-10 20:10:39 +00:00 committed by Gerrit Code Review
commit 3ddfd656d5
11 changed files with 290 additions and 37 deletions

View File

@ -201,7 +201,7 @@ class API(proxy.RpcProxy):
def get_volume_info(self):
"""Make a synchronous call to get volume info for the container"""
LOG.debug(_("Check Volume Info on Instance %s"), self.id)
self._check_for_hearbeat()
# self._check_for_hearbeat()
return self._call("get_filesystem_stats", AGENT_LOW_TIMEOUT,
fs_path="/var/lib/mysql")

View File

@ -82,7 +82,7 @@ def get_auth_password():
pwd, err = utils.execute_with_timeout(
"sudo",
"awk",
"/password\\t=/{print $3}",
"/password\\t=/{print $3; exit}",
"/etc/mysql/my.cnf")
if err:
LOG.err(err)
@ -224,7 +224,7 @@ class MySqlAppStatus(object):
@property
def is_mysql_running(self):
"""True if MySQL is running."""
return (self.status is not None,
return (self.status is not None and
self.status == rd_models.ServiceStatuses.RUNNING)
@staticmethod
@ -624,6 +624,9 @@ class MySqlApp(object):
self.status.end_install_or_restart()
def _replace_mycnf_with_template(self, template_path, original_path):
LOG.debug("replacing the mycnf with template")
LOG.debug("template_path(%s) original_path(%s)"
% (template_path, original_path))
if os.path.isfile(template_path):
utils.execute_with_timeout(
"sudo", "mv", original_path,
@ -634,6 +637,7 @@ class MySqlApp(object):
def _write_temp_mycnf_with_admin_account(self, original_file_path,
temp_file_path, password):
utils.execute_with_timeout("sudo", "chmod", "0711", MYSQL_BASE_DIR)
mycnf_file = open(original_file_path, 'r')
tmp_file = open(temp_file_path, 'w')
for line in mycnf_file:
@ -729,7 +733,10 @@ class MySqlApp(object):
raise RuntimeError("Could not start MySQL!")
def start_mysql_with_conf_changes(self, updated_memory_mb):
LOG.info(_("Starting mysql with conf changes..."))
LOG.info(_("Starting mysql with conf changes to memory(%s)...")
% updated_memory_mb)
LOG.info(_("inside the guest - self.status.is_mysql_running(%s)...")
% self.status.is_mysql_running)
if self.status.is_mysql_running:
LOG.error(_("Cannot execute start_mysql_with_conf_changes because "
"MySQL state == %s!") % self.status)
@ -742,3 +749,25 @@ class MySqlApp(object):
#(cp16net) could raise an exception, does it need to be handled here?
version = pkg.pkg_version(self.MYSQL_PACKAGE_VERSION)
return not version is None
class Interrogator(object):
def get_filesystem_volume_stats(self, fs_path):
out, err = utils.execute_with_timeout(
"stat",
"-f",
"-t",
fs_path)
if err:
LOG.err(err)
raise RuntimeError("Filesystem not found (%s) : %s"
% (fs_path, err))
stats = out.split()
output = {}
output['block_size'] = int(stats[4])
output['total_blocks'] = int(stats[6])
output['free_blocks'] = int(stats[7])
output['total'] = int(stats[6]) * int(stats[4])
output['free'] = int(stats[7]) * int(stats[4])
output['used'] = int(output['total']) - int(output['free'])
return output

View File

@ -83,3 +83,7 @@ class Manager(periodic_task.PeriodicTasks):
def stop_mysql(self, context):
app = dbaas.MySqlApp(dbaas.MySqlAppStatus.get())
app.stop_mysql()
def get_filesystem_stats(self, context, fs_path):
""" Gets the filesystem stats for the path given """
return dbaas.Interrogator().get_filesystem_volume_stats(fs_path)

View File

@ -178,7 +178,7 @@ class SimpleInstance(object):
### Report as Shutdown while deleting, unless there's an error.
if 'DELETING' == ACTION:
if self.db_info.server_status in ["ACTIVE", "SHUTDOWN"]:
if self.db_info.server_status in ["ACTIVE", "SHUTDOWN", "DELETED"]:
return InstanceStatus.SHUTDOWN
else:
msg = _("While shutting down instance (%s): server had "

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import time
from eventlet import greenthread
from datetime import datetime
import traceback
@ -353,7 +354,7 @@ class BuiltInstanceTasks(BuiltInstance):
return "instance_id=%s, status=%s, flavor_id=%s, "\
"dest. flavor id=%s)" % (self.db_info.id,
self.server.status,
str(self.flavor['id']),
str(self.server.flavor['id']),
str(new_flavor_id))
try:
@ -363,12 +364,14 @@ class BuiltInstanceTasks(BuiltInstance):
LOG.debug("Instance %s calling Compute resize..."
% self.db_info.id)
if new_flavor_id:
LOG.debug("Instance with new flavor id")
self.server.resize(new_flavor_id)
else:
LOG.debug("Migrating instance %s without flavor change ..."
% self.db_info.id)
self.server.migrate()
LOG.debug("refreshing the compute status of the instance")
# Do initial check and confirm the status is appropriate.
self._refresh_compute_server_info()
if (self.server.status != "RESIZE" and
@ -376,31 +379,46 @@ class BuiltInstanceTasks(BuiltInstance):
msg = "Unexpected status after call to resize! : %s"
raise ReddwarfError(msg % resize_status_msg())
LOG.debug("the compute status of the instance : (%s)"
% self.server.status)
# Wait for the flavor to change.
def update_server_info():
self._refresh_compute_server_info()
LOG.debug("refreshed... compute status (%s)"
% self.server.status)
return self.server.status != 'RESIZE'
LOG.debug("polling the server until its not RESIZE")
utils.poll_until(
update_server_info,
sleep_time=2,
time_out=60 * 2)
time_out=60 * 10)
LOG.debug("compute status should not be RESIZE now")
LOG.debug("instance_id=%s, status=%s, "
"dest. flavor id=%s)" % (self.db_info.id,
self.server.status,
str(new_flavor_id)))
# Do check to make sure the status and flavor id are correct.
if new_flavor_id:
if str(self.server.flavor['id']) != str(new_flavor_id):
msg = "Assertion failed! flavor_id=%s and not %s" \
% (self.server.flavor['id'], new_flavor_id)
msg = ("Assertion failed! flavor_id=%s and not %s"
% (self.server.flavor['id'], new_flavor_id))
raise ReddwarfError(msg)
if (self.server.status != "VERIFY_RESIZE"):
msg = "Assertion failed! status=%s and not %s" \
% (self.server.status, 'VERIFY_RESIZE')
msg = ("Assertion failed! status=%s and not %s"
% (self.server.status, 'VERIFY_RESIZE'))
raise ReddwarfError(msg)
LOG.debug("wait a sec man!!!")
time.sleep(5)
# Confirm the resize with Nova.
LOG.debug("Instance %s calling Compute confirm resize..."
% self.db_info.id)
self.server.confirm_resize()
LOG.debug("Compute confirm resize DONE ...")
if new_flavor_id:
# Record the new flavor_id in our database.
LOG.debug("Updating instance %s to flavor_id %s."
@ -409,8 +427,7 @@ class BuiltInstanceTasks(BuiltInstance):
except Exception as ex:
new_memory_size = old_memory_size
new_flavor_id = None
LOG.error("Error resizing instance %s." % self.db_info.id)
LOG.error(ex)
LOG.exception("Error resizing instance %s." % self.db_info.id)
finally:
# Tell the guest to restart MySQL with the new RAM size.
# This is in the finally because we have to call this, or

View File

@ -50,14 +50,14 @@ class TestMysqlAccess(object):
@test
def test_mysql_admin(self):
"""Ensure we aren't allowed access with os_admin and wrong password."""
assert_mysql_connection_fails("os_admin", "asdfd-asdf234",
instance_info.get_address())
util.mysql_connection().assert_fails(
"os_admin", "asdfd-asdf234", instance_info.get_address())
@test
def test_mysql_root(self):
"""Ensure we aren't allowed access with root and wrong password."""
assert_mysql_connection_fails("root", "dsfgnear",
instance_info.get_address())
util.mysql_connection().assert_fails(
"root", "dsfgnear", instance_info.get_address())
@test(depends_on_groups=[GROUP_START],

View File

@ -97,9 +97,7 @@ class InstanceTestInfo(object):
def get_address(self):
result = self.dbaas_admin.mgmt.instances.show(self.id)
addresses = result.server['addresses']
address = addresses[test_config.visible_address_group][0]
return address['addr']
return result.ip[0]
def get_local_id(self):
mgmt_instance = self.dbaas_admin.management.show(self.id)
@ -244,8 +242,6 @@ class CreateInstance(unittest.TestCase):
"way_too_large", instance_info.dbaas_flavor_href,
{'size': too_big + 1}, [])
assert_equal(413, dbaas.last_http_code)
#else:
# raise SkipTest("N/A: No max accepted volume size defined.")
def test_create(self):
databases = []
@ -290,7 +286,6 @@ class CreateInstance(unittest.TestCase):
else:
report.log("Test was invoked with TESTS_USE_INSTANCE_ID=%s, so no "
"instance was actually created." % id)
report.log("Local id = %d" % instance_info.get_local_id())
# Check these attrs only are returned in create response
expected_attrs = ['created', 'flavor', 'addresses', 'id', 'links',
@ -665,7 +660,7 @@ class TestInstanceListing(object):
if create_new_instance():
assert_true(0.12 < instance.volume['used'] < 0.25)
@test
@test(enabled=do_not_delete_instance())
def test_instance_not_shown_to_other_user(self):
daffy_ids = [instance.id for instance in
self.other_client.instances.list()]
@ -679,7 +674,7 @@ class TestInstanceListing(object):
for id in admin_ids:
assert_equal(daffy_ids.count(id), 0)
@test
@test(enabled=do_not_delete_instance())
def test_instance_not_deleted_by_other_user(self):
assert_raises(exceptions.NotFound,
self.other_client.instances.get, instance_info.id)

View File

@ -66,8 +66,9 @@ class MySqlConnection(object):
"""Connect to MySQL database."""
print("Connecting to MySQL, mysql --host %s -u %s -p%s"
% (self.host, MYSQL_USERNAME, MYSQL_PASSWORD))
self.client = LocalSqlClient(util.init_engine(
MYSQL_USERNAME, MYSQL_PASSWORD, self.host), use_flush=False)
sql_engine = LocalSqlClient.init_engine(MYSQL_USERNAME, MYSQL_PASSWORD,
self.host)
self.client = LocalSqlClient(sql_engine, use_flush=False)
def is_connected(self):
try:
@ -108,8 +109,8 @@ class ActionTestBase(object):
return self.dbaas.instances.get(self.instance_id)
@property
def instance_local_id(self):
return instance_info.get_local_id()
def instance_address(self):
return instance_info.get_address()
@property
def instance_id(self):
@ -146,7 +147,7 @@ class ActionTestBase(object):
check.equal(instance.status, "ACTIVE")
def find_mysql_proc_on_instance(self):
return util.find_mysql_procid_on_instance(self.instance_local_id)
return util.find_mysql_procid_on_instance(self.instance_address)
def log_current_users(self):
users = self.dbaas.users.list(self.instance_id)
@ -217,7 +218,7 @@ class RebootTestBase(ActionTestBase):
self.fix_mysql() # kill files
cmd = """ssh %s 'sudo cp /dev/null /var/lib/mysql/ib_logfile%d'"""
for index in range(2):
full_cmd = cmd % (self.instance_local_id, index)
full_cmd = cmd % (self.instance_address, index)
print("RUNNING COMMAND: %s" % full_cmd)
util.process(full_cmd)
@ -226,13 +227,13 @@ class RebootTestBase(ActionTestBase):
if not FAKE_MODE:
cmd = "ssh %s 'sudo rm /var/lib/mysql/ib_logfile%d'"
for index in range(2):
util.process(cmd % (self.instance_local_id, index))
util.process(cmd % (self.instance_address, index))
def wait_for_failure_status(self):
"""Wait until status becomes running."""
def is_finished_rebooting():
instance = self.instance
if instance.status == "REBOOT":
if instance.status == "REBOOT" or instance.status == "ACTIVE":
return False
assert_equal("SHUTDOWN", instance.status)
return True

View File

@ -158,8 +158,8 @@ class TestUsers(object):
def show_databases(self, user, password):
print("Going to connect to %s, %s, %s"
% (instance_info.get_address(), user, password))
with create_mysql_connection(instance_info.get_address(),
user, password) as db:
with util.mysql_connection().create(instance_info.get_address(),
user, password) as db:
print(db)
dbs = db.execute("show databases")
return [row['Database'] for row in dbs]
@ -264,8 +264,8 @@ class TestUsers(object):
def _check_connection(self, username, password):
if not FAKE:
util.assert_mysql_connection_fails(username, password,
instance_info.get_address())
util.mysql_connection().assert_fails(username, password,
instance_info.get_address())
# Also determine the db is gone via API.
result = self.dbaas.users.list(instance_info.id)
assert_equal(200, self.dbaas.last_http_code)

View File

@ -59,6 +59,7 @@ from reddwarf.tests.util import test_config as CONFIG
from reddwarf.tests.util.client import TestClient as TestClient
from reddwarf.tests.util.users import Requirements
from reddwarf.common.exception import PollTimeOut
from reddwarf.common.utils import import_object
WHITE_BOX = test_config.white_box
@ -221,6 +222,45 @@ else:
from reddwarf.common.utils import poll_until
def mysql_connection():
cls = CONFIG.get('mysql_connection',
"local.MySqlConnection")
if cls == "local.MySqlConnection":
return MySqlConnection()
return import_object(cls)()
def find_mysql_procid_on_instance(ip_address):
"""Returns the process id of MySql on an instance if running, or None."""
cmd = "ssh %s ps aux | grep /usr/sbin/mysqld " \
"| awk '{print $2}'" % ip_address
stdout, stderr = process(cmd)
try:
return int(stdout)
except ValueError:
return None
class MySqlConnection(object):
def assert_fails(self, user_name, password, ip):
from reddwarf.tests.util import mysql
try:
with mysql.create_mysql_connection(ip, user_name, password) as db:
pass
fail("Should have failed to connect: mysql --host %s -u %s -p%s"
% (ip, user_name, password))
except mysql.MySqlPermissionsFailure:
return # Good, this is what we wanted.
except mysql.MySqlConnectionFailure as mcf:
fail("Expected to see permissions failure. Instead got message:"
"%s" % mcf.message)
def create(self, ip, user_name, password):
from reddwarf.tests.util import mysql
return mysql.create_mysql_connection(ip, user_name, password)
class LocalSqlClient(object):
"""A sqlalchemy wrapper to manage transactions"""
@ -250,3 +290,11 @@ class LocalSqlClient(object):
self.trans.rollback()
self.trans = None
raise
@staticmethod
def init_engine(user, password, host):
return create_engine("mysql://%s:%s@%s:3306" %
(user, password, host),
pool_recycle=1800, echo=True)
self.engine = engine
self.use_flush = use_flush

View File

@ -0,0 +1,159 @@
import pexpect
import re
from sqlalchemy import create_engine
from reddwarf.tests.config import CONFIG
from sqlalchemy.exc import OperationalError
try:
from sqlalchemy.exc import ResourceClosedError
except ImportError:
ResourceClosedError = Exception
def create_mysql_connection(host, user, password):
connection = CONFIG.mysql_connection_method
if connection['type'] == "direct":
return SqlAlchemyConnection(host, user, password)
elif connection['type'] == "tunnel":
if 'ssh' not in connection:
raise RuntimeError("If connection type is 'tunnel' then a "
"property 'ssh' is expected.")
return PexpectMySqlConnection(connection['ssh'], host, user, password)
else:
raise RuntimeError("Unknown Bad test configuration for "
"mysql_connection_method")
class MySqlConnectionFailure(RuntimeError):
def __init__(self, msg):
super(MySqlConnectionFailure, self).__init__(msg)
class MySqlPermissionsFailure(RuntimeError):
def __init__(self, msg):
super(MySqlPermissionsFailure, self).__init__(msg)
class SqlAlchemyConnection(object):
def __init__(self, host, user, password):
self.host = host
self.user = user
self.password = password
try:
self.engine = self._init_engine(user, password, host)
except OperationalError as oe:
if self._exception_is_permissions_issue(oe.message):
raise MySqlPermissionsFailure(oe)
else:
raise MySqlConnectionFailure(oe)
@staticmethod
def _exception_is_permissions_issue(msg):
"""Assert message cited a permissions issue and not something else."""
pos_error = re.compile(".*Host '[\w\.]*' is not allowed to connect to "
"this MySQL server.*")
pos_error1 = re.compile(".*Access denied for user "
"'[\w\*\!\@\#\^\&]*'@'[\w\.]*'.*")
if (pos_error.match(msg) or pos_error1.match(msg)):
return True
def __enter__(self):
try:
self.conn = self.engine.connect()
except OperationalError as oe:
if self._exception_is_permissions_issue(oe.message):
raise MySqlPermissionsFailure(oe)
else:
raise MySqlConnectionFailure(oe)
self.trans = self.conn.begin()
return self
def execute(self, cmd):
"""Execute some code."""
cmd = cmd.replace("%", "%%")
try:
return self.conn.execute(cmd).fetchall()
except:
self.trans.rollback()
self.trans = None
try:
raise
except ResourceClosedError:
return []
def __exit__(self, type, value, traceback):
if self.trans:
if type is not None: # An error occurred
self.trans.rollback()
else:
self.trans.commit()
self.conn.close()
@staticmethod
def _init_engine(user, password, host):
return create_engine("mysql://%s:%s@%s:3306" % (user, password, host),
pool_recycle=1800, echo=True)
class PexpectMySqlConnection(object):
TIME_OUT = 30
def __init__(self, ssh_args, host, user, password):
self.host = host
self.user = user
self.password = password
cmd = 'ssh %s' % ssh_args
self.proc = pexpect.spawn(cmd)
print(cmd)
self.proc.expect(":~\$", timeout=self.TIME_OUT)
cmd2 = "mysql --host '%s' -u '%s' '-p%s'\n" % \
(self.host, self.user, self.password)
print(cmd2)
self.proc.send(cmd2)
result = self.proc.expect([
'mysql>',
'Access denied',
"Can't connect to MySQL server"],
timeout=self.TIME_OUT)
if result == 1:
raise MySqlPermissionsFailure(self.proc.before)
elif result == 2:
raise MySqlConnectionFailure(self.proc.before)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.proc.close()
def execute(self, cmd):
self.proc.send(cmd + "\G\n")
outcome = self.proc.expect(['Empty set', 'mysql>'],
timeout=self.TIME_OUT)
if outcome == 0:
return []
else:
# This next line might be invaluable for long test runs.
print("Interpreting output: %s" % self.proc.before)
lines = self.proc.before.split("\r\n")
result = []
row = None
for line in lines:
plural_s = "s" if len(result) != 0 else ""
end_line = "%d row%s in set" % ((len(result) + 1), plural_s)
if len(result) == 0:
end_line = "1 row in set"
if (line.startswith("***************************") or
line.startswith(end_line)):
if row is not None:
result.append(row)
row = {}
elif row is not None:
colon = line.find(": ")
field = line[:colon]
value = line[colon + 2:]
row[field] = value
return result