Merge "Create templated config files"

This commit is contained in:
Jenkins 2013-07-16 05:11:54 +00:00 committed by Gerrit Code Review
commit db9c21c881
13 changed files with 262 additions and 101 deletions

View File

@ -18,3 +18,4 @@ python-swiftclient
iso8601 iso8601
oslo.config>=1.1.0 oslo.config>=1.1.0
jsonschema>=1.0.0,!=1.4.0,<2 jsonschema>=1.0.0,!=1.4.0,<2
Jinja2

46
trove/common/template.py Normal file
View File

@ -0,0 +1,46 @@
# Copyright 2012 OpenStack Foundation
#
# 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.
_ENV = None
import jinja2
def get_env():
global _ENV
if not _ENV:
_ENV = create_env()
return _ENV
def create_env():
loader = jinja2.ChoiceLoader([
jinja2.FileSystemLoader("/etc/trove/templates"),
jinja2.PackageLoader("trove", "templates")
])
return jinja2.Environment(loader=loader)
class SingleInstanceConfigTemplate(object):
_location_types = {'mysql': '/etc/mysql/my.cnf',
'percona': '/etc/mysql/my.cnf'}
def __init__(self, location_type, flavor_dict):
self.config_location = self._location_types[location_type]
self.flavor_dict = flavor_dict
self.template = get_env().get_template("mysql.config.template")
def render(self):
self.config_contents = self.template.render(
flavor=self.flavor_dict)

View File

@ -205,7 +205,7 @@ class API(proxy.RpcProxy):
def prepare(self, memory_mb, databases, users, def prepare(self, memory_mb, databases, users,
device_path='/dev/vdb', mount_point='/mnt/volume', device_path='/dev/vdb', mount_point='/mnt/volume',
backup_id=None): backup_id=None, config_location=None, config_contents=None):
"""Make an asynchronous call to prepare the guest """Make an asynchronous call to prepare the guest
as a database container optionally includes a backup id for restores as a database container optionally includes a backup id for restores
""" """
@ -213,19 +213,21 @@ class API(proxy.RpcProxy):
self._cast_with_consumer( self._cast_with_consumer(
"prepare", databases=databases, memory_mb=memory_mb, "prepare", databases=databases, memory_mb=memory_mb,
users=users, device_path=device_path, mount_point=mount_point, users=users, device_path=device_path, mount_point=mount_point,
backup_id=backup_id) backup_id=backup_id, config_location=config_location,
config_contents=config_contents)
def restart(self): def restart(self):
"""Restart the MySQL server.""" """Restart the MySQL server."""
LOG.debug(_("Sending the call to restart MySQL on the Guest.")) LOG.debug(_("Sending the call to restart MySQL on the Guest."))
self._call("restart", AGENT_HIGH_TIMEOUT) self._call("restart", AGENT_HIGH_TIMEOUT)
def start_db_with_conf_changes(self, updated_memory_size): def start_db_with_conf_changes(self, config_location, config_contents):
"""Start the MySQL server.""" """Start the MySQL server."""
LOG.debug(_("Sending the call to start MySQL on the Guest with " LOG.debug(_("Sending the call to start MySQL on the Guest with "
"a timeout of %s.") % AGENT_HIGH_TIMEOUT) "a timeout of %s.") % AGENT_HIGH_TIMEOUT)
self._call("start_db_with_conf_changes", AGENT_HIGH_TIMEOUT, self._call("start_db_with_conf_changes", AGENT_HIGH_TIMEOUT,
updated_memory_size=updated_memory_size) config_location=config_location,
config_contents=config_contents)
def reset_configuration(self, configuration): def reset_configuration(self, configuration):
"""Ignore running state of MySQL, and just change the config file """Ignore running state of MySQL, and just change the config file

View File

@ -18,7 +18,7 @@ CONF = cfg.CONF
class Manager(periodic_task.PeriodicTasks): class Manager(periodic_task.PeriodicTasks):
@periodic_task.periodic_task(ticks_between_runs=10) @periodic_task.periodic_task(ticks_between_runs=3)
def update_status(self, context): def update_status(self, context):
"""Update the status of the MySQL service""" """Update the status of the MySQL service"""
MySqlAppStatus.get().update() MySqlAppStatus.get().update()
@ -82,7 +82,8 @@ class Manager(periodic_task.PeriodicTasks):
LOG.info(_("Restored database successfully")) LOG.info(_("Restored database successfully"))
def prepare(self, context, databases, memory_mb, users, device_path=None, def prepare(self, context, databases, memory_mb, users, device_path=None,
mount_point=None, backup_id=None): mount_point=None, backup_id=None, config_location=None,
config_contents=None):
"""Makes ready DBAAS on a Guest container.""" """Makes ready DBAAS on a Guest container."""
MySqlAppStatus.get().begin_mysql_install() MySqlAppStatus.get().begin_mysql_install()
# status end_mysql_install set with secure() # status end_mysql_install set with secure()
@ -109,7 +110,7 @@ class Manager(periodic_task.PeriodicTasks):
if backup_id: if backup_id:
self._perform_restore(backup_id, context, CONF.mount_point, app) self._perform_restore(backup_id, context, CONF.mount_point, app)
LOG.info(_("Securing mysql now.")) LOG.info(_("Securing mysql now."))
app.secure(memory_mb) app.secure(config_location, config_contents)
if backup_id and MySqlAdmin().is_root_enabled(): if backup_id and MySqlAdmin().is_root_enabled():
MySqlAdmin().report_root_enabled(context) MySqlAdmin().report_root_enabled(context)
else: else:
@ -128,9 +129,10 @@ class Manager(periodic_task.PeriodicTasks):
app = MySqlApp(MySqlAppStatus.get()) app = MySqlApp(MySqlAppStatus.get())
app.restart() app.restart()
def start_db_with_conf_changes(self, context, updated_memory_size): def start_db_with_conf_changes(self, context, config_location,
config_contents):
app = MySqlApp(MySqlAppStatus.get()) app = MySqlApp(MySqlAppStatus.get())
app.start_db_with_conf_changes(updated_memory_size) app.start_db_with_conf_changes(config_location, config_contents)
def stop_db(self, context, do_not_start_on_reboot=False): def stop_db(self, context, do_not_start_on_reboot=False):
app = MySqlApp(MySqlAppStatus.get()) app = MySqlApp(MySqlAppStatus.get())

View File

@ -29,10 +29,7 @@ MYSQLD_ARGS = None
PREPARING = False PREPARING = False
UUID = False UUID = False
ORIG_MYCNF = "/etc/mysql/my.cnf"
FINAL_MYCNF = "/var/lib/mysql/my.cnf"
TMP_MYCNF = "/tmp/my.cnf.tmp" TMP_MYCNF = "/tmp/my.cnf.tmp"
DBAAS_MYCNF = "/etc/dbaas/my.cnf/my.cnf.%dM"
MYSQL_BASE_DIR = "/var/lib/mysql" MYSQL_BASE_DIR = "/var/lib/mysql"
CONF = cfg.CONF CONF = cfg.CONF
@ -614,7 +611,7 @@ class MySqlApp(object):
def complete_install_or_restart(self): def complete_install_or_restart(self):
self.status.end_install_or_restart() self.status.end_install_or_restart()
def secure(self, memory_mb): def secure(self, config_location, config_contents):
LOG.info(_("Generating admin password...")) LOG.info(_("Generating admin password..."))
admin_password = generate_random_password() admin_password = generate_random_password()
@ -625,7 +622,7 @@ class MySqlApp(object):
self._create_admin_user(client, admin_password) self._create_admin_user(client, admin_password)
self.stop_db() self.stop_db()
self._write_mycnf(memory_mb, admin_password) self._write_mycnf(admin_password, config_location, config_contents)
self.start_mysql() self.start_mysql()
LOG.info(_("Dbaas secure complete.")) LOG.info(_("Dbaas secure complete."))
@ -757,40 +754,27 @@ class MySqlApp(object):
if "No such file or directory" not in str(pe): if "No such file or directory" not in str(pe):
raise raise
def _write_mycnf(self, update_memory_mb, admin_password): def _write_mycnf(self, admin_password, config_location, config_contents):
""" """
Install the set of mysql my.cnf templates from dbaas-mycnf package. Install the set of mysql my.cnf templates.
The package generates a template suited for the current Update the os_admin user and password to the my.cnf
container flavor. Update the os_admin user and password file for direct login from localhost
to the my.cnf file for direct login from localhost
""" """
LOG.info(_("Writing my.cnf templates.")) LOG.info(_("Writing my.cnf templates."))
if admin_password is None: if admin_password is None:
admin_password = get_auth_password() admin_password = get_auth_password()
# As of right here, the admin_password contains the password to be with open(TMP_MYCNF, 'w') as t:
# applied to the my.cnf file, whether it was there before (and we t.write(config_contents)
# passed it in) or we generated a new one just now (because we didn't utils.execute_with_timeout("sudo", "mv", TMP_MYCNF,
# find it). config_location)
LOG.debug(_("Installing my.cnf templates")) self._write_temp_mycnf_with_admin_account(config_location,
pkg.pkg_install("dbaas-mycnf", self.TIME_OUT) TMP_MYCNF,
LOG.info(_("Replacing my.cnf with template."))
template_path = DBAAS_MYCNF % update_memory_mb
# replace my.cnf with template.
self._replace_mycnf_with_template(template_path, ORIG_MYCNF)
LOG.info(_("Writing new temp my.cnf."))
self._write_temp_mycnf_with_admin_account(ORIG_MYCNF, TMP_MYCNF,
admin_password) admin_password)
# permissions work-around utils.execute_with_timeout("sudo", "mv", TMP_MYCNF,
LOG.info(_("Moving tmp into final.")) config_location)
utils.execute_with_timeout("sudo", "mv", TMP_MYCNF, FINAL_MYCNF)
LOG.info(_("Removing original my.cnf."))
utils.execute_with_timeout("sudo", "rm", ORIG_MYCNF)
LOG.info(_("Symlinking final my.cnf."))
utils.execute_with_timeout("sudo", "ln", "-s", FINAL_MYCNF, ORIG_MYCNF)
self.wipe_ib_logfiles() self.wipe_ib_logfiles()
def start_mysql(self, update_db=False): def start_mysql(self, update_db=False):
@ -825,9 +809,8 @@ class MySqlApp(object):
self.status.end_install_or_restart() self.status.end_install_or_restart()
raise RuntimeError("Could not start MySQL!") raise RuntimeError("Could not start MySQL!")
def start_db_with_conf_changes(self, updated_memory_mb): def start_db_with_conf_changes(self, config_location, config_contents):
LOG.info(_("Starting mysql with conf changes to memory(%s)...") LOG.info(_("Starting mysql with conf changes..."))
% updated_memory_mb)
LOG.info(_("inside the guest - self.status.is_mysql_running(%s)...") LOG.info(_("inside the guest - self.status.is_mysql_running(%s)...")
% self.status.is_mysql_running) % self.status.is_mysql_running)
if self.status.is_mysql_running: if self.status.is_mysql_running:
@ -835,14 +818,14 @@ class MySqlApp(object):
"MySQL state == %s!") % self.status) "MySQL state == %s!") % self.status)
raise RuntimeError("MySQL not stopped.") raise RuntimeError("MySQL not stopped.")
LOG.info(_("Initiating config.")) LOG.info(_("Initiating config."))
self._write_mycnf(updated_memory_mb, None) self._write_mycnf(None, config_location, config_contents)
self.start_mysql(True) self.start_mysql(True)
def reset_configuration(self, configuration): def reset_configuration(self, configuration):
updated_memory_mb = configuration['memory_mb'] config_location = configuration['config_location']
LOG.info(_("Changing configuration to memory(%s)...") config_contents = configuration['config_contents']
% updated_memory_mb) LOG.info(_("Resetting configuration"))
self._write_mycnf(updated_memory_mb, None) self._write_mycnf(None, config_location, config_contents)
def is_installed(self): def is_installed(self):
#(cp16net) could raise an exception, does it need to be handled here? #(cp16net) could raise an exception, does it need to be handled here?

View File

@ -17,6 +17,7 @@ import traceback
from eventlet import greenthread from eventlet import greenthread
from novaclient import exceptions as nova_exceptions from novaclient import exceptions as nova_exceptions
from trove.common import cfg from trove.common import cfg
from trove.common import template
from trove.common import utils from trove.common import utils
from trove.common.exception import GuestError from trove.common.exception import GuestError
from trove.common.exception import GuestTimeout from trove.common.exception import GuestTimeout
@ -110,7 +111,20 @@ class NotifyMixin(object):
payload) payload)
class FreshInstanceTasks(FreshInstance, NotifyMixin): class ConfigurationMixin(object):
"""Configuration Mixin
Configuration related tasks for instances and resizes.
"""
def _render_config(self, service_type, flavor):
config = template.SingleInstanceConfigTemplate(
service_type, flavor)
config.render()
return config
class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def create_instance(self, flavor, image_id, databases, users, def create_instance(self, flavor, image_id, databases, users,
service_type, volume_size, security_groups, service_type, volume_size, security_groups,
@ -136,9 +150,13 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin):
err = inst_models.InstanceTasks.BUILDING_ERROR_DNS err = inst_models.InstanceTasks.BUILDING_ERROR_DNS
self._log_and_raise(e, msg, err) self._log_and_raise(e, msg, err)
config = self._render_config(service_type, flavor)
if server: if server:
self._guest_prepare(server, flavor['ram'], volume_info, self._guest_prepare(server, flavor['ram'], volume_info,
databases, users, backup_id) databases, users, backup_id,
config.config_location,
config.config_contents)
if not self.db_info.task_status.is_error: if not self.db_info.task_status.is_error:
self.update_db(task_status=inst_models.InstanceTasks.NONE) self.update_db(task_status=inst_models.InstanceTasks.NONE)
@ -331,13 +349,16 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin):
return server return server
def _guest_prepare(self, server, flavor_ram, volume_info, def _guest_prepare(self, server, flavor_ram, volume_info,
databases, users, backup_id=None): databases, users, backup_id=None,
config_location=None, config_contents=None):
LOG.info("Entering guest_prepare.") LOG.info("Entering guest_prepare.")
# Now wait for the response from the create to do additional work # Now wait for the response from the create to do additional work
self.guest.prepare(flavor_ram, databases, users, self.guest.prepare(flavor_ram, databases, users,
device_path=volume_info['device_path'], device_path=volume_info['device_path'],
mount_point=volume_info['mount_point'], mount_point=volume_info['mount_point'],
backup_id=backup_id) backup_id=backup_id,
config_location=config_location,
config_contents=config_contents)
def _create_dns_entry(self): def _create_dns_entry(self):
LOG.debug("%s: Creating dns entry for instance: %s" % LOG.debug("%s: Creating dns entry for instance: %s" %
@ -377,7 +398,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin):
(greenthread.getcurrent(), self.id)) (greenthread.getcurrent(), self.id))
class BuiltInstanceTasks(BuiltInstance, NotifyMixin): class BuiltInstanceTasks(BuiltInstance, NotifyMixin, ConfigurationMixin):
""" """
Performs the various asynchronous instance related tasks. Performs the various asynchronous instance related tasks.
""" """
@ -563,7 +584,7 @@ class BackupTasks(object):
backup.delete() backup.delete()
class ResizeActionBase(object): class ResizeActionBase(ConfigurationMixin):
"""Base class for executing a resize action.""" """Base class for executing a resize action."""
def __init__(self, instance): def __init__(self, instance):
@ -711,7 +732,10 @@ class ResizeAction(ResizeActionBase):
% self.instance.id) % self.instance.id)
LOG.debug("Repairing config.") LOG.debug("Repairing config.")
try: try:
config = {'memory_mb': self.old_flavor['ram']} config = self._render_config(self.instance.service_type,
self.old_flavor)
config = {'config_location': config.config_location,
'config_contents': config.config_contents}
self.instance.guest.reset_configuration(config) self.instance.guest.reset_configuration(config)
except GuestTimeout as gt: except GuestTimeout as gt:
LOG.exception("Error sending reset_configuration call.") LOG.exception("Error sending reset_configuration call.")
@ -730,7 +754,10 @@ class ResizeAction(ResizeActionBase):
modify_at=timeutils.isotime()) modify_at=timeutils.isotime())
def _start_mysql(self): def _start_mysql(self):
self.instance.guest.start_db_with_conf_changes(self.new_flavor['ram']) config = self._render_config(self.instance.service_type,
self.new_flavor)
self.instance.guest.start_db_with_conf_changes(config.config_location,
config.config_contents)
class MigrateAction(ResizeActionBase): class MigrateAction(ResizeActionBase):

View File

@ -0,0 +1,58 @@
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
user = mysql
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
####tmpdir = /tmp
tmpdir = /var/tmp
pid_file = /var/run/mysqld/mysqld.pid
skip-external-locking
key_buffer_size = {{ 50 * flavor['ram']//512 }}M
max_allowed_packet = {{ 1 * flavor['ram']//512 }}M
thread_stack = 192K
thread_cache_size = {{ 4 * flavor['ram']//512 }}
myisam-recover = BACKUP
query_cache_type = 1
query_cache_limit = 1M
query_cache_size = {{ 8 * flavor['ram']//512 }}M
log_error = /var/log/mysql/mysqld.log
innodb_data_file_path = ibdata1:10M:autoextend
innodb_buffer_pool_size = {{ 150 * flavor['ram']//512 }}M
innodb_file_per_table = 1
innodb_log_files_in_group = 2
innodb_log_file_size=50M
innodb_log_buffer_size=25M
connect_timeout = 15
wait_timeout = 120
join_buffer_size = 1M
read_buffer_size = 512K
read_rnd_buffer_size = 512K
sort_buffer_size = 1M
tmp_table_size = {{ 16 * flavor['ram']//512 }}M
max_heap_table_size = {{ 16 * flavor['ram']//512 }}M
table_open_cache = {{ 256 * flavor['ram']//512 }}
table_definition_cache = {{ 256 * flavor['ram']//512 }}
open_files_limit = {{ 512 * flavor['ram']//512 }}
max_user_connections = {{ 100 * flavor['ram']//512 }}
max_connections = {{ 100 * flavor['ram']//512 }}
default_storage_engine = innodb
local-infile = 0
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
[isamchk]
key_buffer = 16M
!includedir /etc/mysql/conf.d/

View File

@ -21,6 +21,7 @@ from novaclient.exceptions import BadRequest
from novaclient.v1_1.servers import Server from novaclient.v1_1.servers import Server
from trove.common.exception import PollTimeOut from trove.common.exception import PollTimeOut
from trove.common import template
from trove.common import utils from trove.common import utils
from trove.common.context import TroveContext from trove.common.context import TroveContext
from trove.guestagent import api as guest from trove.guestagent import api as guest
@ -114,7 +115,11 @@ class ResizeTests(ResizeTestBase):
self._teardown() self._teardown()
def _start_mysql(self): def _start_mysql(self):
self.instance.guest.start_db_with_conf_changes(NEW_FLAVOR.ram) config = template.SingleInstanceConfigTemplate(
"mysql", NEW_FLAVOR.__dict__)
config.render()
self.instance.guest.start_db_with_conf_changes(config.config_location,
config.config_contents)
def test_guest_wont_stop_mysql(self): def test_guest_wont_stop_mysql(self):
self.guest.stop_db(do_not_start_on_reboot=True)\ self.guest.stop_db(do_not_start_on_reboot=True)\

View File

@ -181,7 +181,8 @@ class FakeGuest(object):
return self.users.get((username, hostname), None) return self.users.get((username, hostname), None)
def prepare(self, memory_mb, databases, users, device_path=None, def prepare(self, memory_mb, databases, users, device_path=None,
mount_point=None, backup_id=None): mount_point=None, backup_id=None, config_location=None,
config_contents=None):
from trove.instance.models import DBInstance from trove.instance.models import DBInstance
from trove.instance.models import InstanceServiceStatus from trove.instance.models import InstanceServiceStatus
from trove.instance.models import ServiceStatuses from trove.instance.models import ServiceStatuses
@ -225,7 +226,7 @@ class FakeGuest(object):
# There's nothing to do here, since there is no config to update. # There's nothing to do here, since there is no config to update.
pass pass
def start_db_with_conf_changes(self, updated_memory_size): def start_db_with_conf_changes(self, config_location, config_contents):
time.sleep(2) time.sleep(2)
self._set_status('RUNNING') self._set_status('RUNNING')

View File

@ -0,0 +1,57 @@
#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 testtools
import re
from trove.common import template
from trove.tests.unittests.util import util
class TemplateTest(testtools.TestCase):
def setUp(self):
super(TemplateTest, self).setUp()
util.init_db()
self.env = template.get_env()
self.template = self.env.get_template("mysql.config.template")
self.flavor_dict = {'ram': 1024}
def tearDown(self):
super(TemplateTest, self).tearDown()
def validate_template(self, contents, teststr, test_flavor):
# expected query_cache_size = {{ 8 * flavor_multiplier }}M
flavor_multiplier = test_flavor['ram'] / 512
found_group = None
for line in contents.split('\n'):
m = re.search('^%s.*' % teststr, line)
if m:
found_group = m.group(0)
if not found_group:
raise "Could not find text in template"
# Check that the last group has been rendered
memsize = found_group.split(" ")[2]
self.assertEqual(memsize, "%sM" % (8 * flavor_multiplier))
def test_rendering(self):
rendered = self.template.render(flavor=self.flavor_dict)
self.validate_template(rendered, "query_cache_size", self.flavor_dict)
def test_single_instance_config_rendering(self):
location = "/etc/mysql/my.cnf"
config = template.SingleInstanceConfigTemplate('mysql',
self.flavor_dict)
config.render()
self.assertEqual(location, config.config_location)
self.validate_template(config.config_contents, "query_cache_size",
self.flavor_dict)

View File

@ -177,9 +177,9 @@ class ApiTest(testtools.TestCase):
def test_start_db_with_conf_changes(self): def test_start_db_with_conf_changes(self):
exp_msg = RpcMsgMatcher('start_db_with_conf_changes', exp_msg = RpcMsgMatcher('start_db_with_conf_changes',
'updated_memory_size') 'config_location', 'config_contents')
self._mock_rpc_call(exp_msg) self._mock_rpc_call(exp_msg)
self.api.start_db_with_conf_changes('512') self.api.start_db_with_conf_changes(None, None)
self._verify_rpc_call(exp_msg) self._verify_rpc_call(exp_msg)
def test_stop_db(self): def test_stop_db(self):
@ -218,12 +218,13 @@ class ApiTest(testtools.TestCase):
when(rpc).create_connection(new=True).thenReturn(mock_conn) when(rpc).create_connection(new=True).thenReturn(mock_conn)
when(mock_conn).create_consumer(any(), any(), any()).thenReturn(None) when(mock_conn).create_consumer(any(), any(), any()).thenReturn(None)
exp_msg = RpcMsgMatcher('prepare', 'memory_mb', 'databases', 'users', exp_msg = RpcMsgMatcher('prepare', 'memory_mb', 'databases', 'users',
'device_path', 'mount_point', 'backup_id') 'device_path', 'mount_point', 'backup_id',
'config_location', 'config_contents')
when(rpc).cast(any(), any(), exp_msg).thenReturn(None) when(rpc).cast(any(), any(), exp_msg).thenReturn(None)
self.api.prepare('2048', 'db1', 'user1', '/dev/vdt', '/mnt/opt', self.api.prepare('2048', 'db1', 'user1', '/dev/vdt', '/mnt/opt',
'bkup-1232') 'bkup-1232', 'loc', 'cont')
self._verify_rpc_connection_and_cast(rpc, mock_conn, exp_msg) self._verify_rpc_connection_and_cast(rpc, mock_conn, exp_msg)
@ -232,11 +233,12 @@ class ApiTest(testtools.TestCase):
when(rpc).create_connection(new=True).thenReturn(mock_conn) when(rpc).create_connection(new=True).thenReturn(mock_conn)
when(mock_conn).create_consumer(any(), any(), any()).thenReturn(None) when(mock_conn).create_consumer(any(), any(), any()).thenReturn(None)
exp_msg = RpcMsgMatcher('prepare', 'memory_mb', 'databases', 'users', exp_msg = RpcMsgMatcher('prepare', 'memory_mb', 'databases', 'users',
'device_path', 'mount_point', 'backup_id') 'device_path', 'mount_point', 'backup_id',
'config_location', 'config_contents')
when(rpc).cast(any(), any(), exp_msg).thenReturn(None) when(rpc).cast(any(), any(), exp_msg).thenReturn(None)
self.api.prepare('2048', 'db1', 'user1', '/dev/vdt', '/mnt/opt', self.api.prepare('2048', 'db1', 'user1', '/dev/vdt', '/mnt/opt',
'backup_id_123') 'backup_id_123', 'loc', 'cont')
self._verify_rpc_connection_and_cast(rpc, mock_conn, exp_msg) self._verify_rpc_connection_and_cast(rpc, mock_conn, exp_msg)

View File

@ -61,6 +61,7 @@ FAKE_DB_2 = {"_name": "testDB2", "_character_set": "latin2",
"_collate": "latin2_general_ci"} "_collate": "latin2_general_ci"}
FAKE_USER = [{"_name": "random", "_password": "guesswhat", FAKE_USER = [{"_name": "random", "_password": "guesswhat",
"_databases": [FAKE_DB]}] "_databases": [FAKE_DB]}]
MYCNF = '/etc/mysql/my.cnf'
class FakeAppStatus(MySqlAppStatus): class FakeAppStatus(MySqlAppStatus):
@ -595,7 +596,7 @@ class MySqlAppTest(testtools.TestCase):
self.mysql_starts_successfully() self.mysql_starts_successfully()
self.appStatus.status = ServiceStatuses.SHUTDOWN self.appStatus.status = ServiceStatuses.SHUTDOWN
self.mySqlApp.start_db_with_conf_changes(Mock()) self.mySqlApp.start_db_with_conf_changes(Mock(), Mock())
self.assertTrue(self.mySqlApp._write_mycnf.called) self.assertTrue(self.mySqlApp._write_mycnf.called)
self.assertTrue(self.mySqlApp.start_mysql.called) self.assertTrue(self.mySqlApp.start_mysql.called)
@ -609,7 +610,8 @@ class MySqlAppTest(testtools.TestCase):
self.appStatus.status = ServiceStatuses.RUNNING self.appStatus.status = ServiceStatuses.RUNNING
self.assertRaises(RuntimeError, self.assertRaises(RuntimeError,
self.mySqlApp.start_db_with_conf_changes, Mock()) self.mySqlApp.start_db_with_conf_changes,
Mock(), Mock())
class MySqlAppInstallTest(MySqlAppTest): class MySqlAppInstallTest(MySqlAppTest):
@ -641,7 +643,7 @@ class MySqlAppInstallTest(MySqlAppTest):
self.mysql_starts_successfully() self.mysql_starts_successfully()
sqlalchemy.create_engine = Mock() sqlalchemy.create_engine = Mock()
self.mySqlApp.secure(100) self.mySqlApp.secure(MYCNF, 'contents')
self.assertTrue(self.mySqlApp.stop_db.called) self.assertTrue(self.mySqlApp.stop_db.called)
self.assertTrue(self.mySqlApp._write_mycnf.called) self.assertTrue(self.mySqlApp._write_mycnf.called)
@ -668,13 +670,13 @@ class MySqlAppInstallTest(MySqlAppTest):
self.mySqlApp.start_mysql = Mock() self.mySqlApp.start_mysql = Mock()
self.mySqlApp.stop_db = Mock() self.mySqlApp.stop_db = Mock()
self.mySqlApp._write_mycnf = \ self.mySqlApp._write_mycnf = \
Mock(side_effect=pkg.PkgPackageStateError("Install error")) Mock(side_effect=IOError("Could not write file"))
self.mysql_stops_successfully() self.mysql_stops_successfully()
self.mysql_starts_successfully() self.mysql_starts_successfully()
sqlalchemy.create_engine = Mock() sqlalchemy.create_engine = Mock()
self.assertRaises(pkg.PkgPackageStateError, self.assertRaises(IOError,
self.mySqlApp.secure, 100) self.mySqlApp.secure, "/etc/mycnf/my.cnf", "foo")
self.assertTrue(self.mySqlApp.stop_db.called) self.assertTrue(self.mySqlApp.stop_db.called)
self.assertTrue(self.mySqlApp._write_mycnf.called) self.assertTrue(self.mySqlApp._write_mycnf.called)
@ -731,26 +733,6 @@ def mock_admin_sql_connection():
class MySqlAppMockTest(testtools.TestCase): class MySqlAppMockTest(testtools.TestCase):
@classmethod
def stub_file(cls, filename):
return MySqlAppMockTest.StubFile(filename)
class StubFile(object):
def __init__(self, filename):
when(__builtin__).open(filename, any()).thenReturn(self)
def next(self):
raise StopIteration
def __iter__(self):
return self
def write(self, data):
pass
def close(self):
pass
def tearDown(self): def tearDown(self):
super(MySqlAppMockTest, self).tearDown() super(MySqlAppMockTest, self).tearDown()
unstub() unstub()
@ -760,8 +742,6 @@ class MySqlAppMockTest(testtools.TestCase):
when(mock_conn).execute(any()).thenReturn(None) when(mock_conn).execute(any()).thenReturn(None)
when(utils).execute_with_timeout("sudo", any(str), "stop").thenReturn( when(utils).execute_with_timeout("sudo", any(str), "stop").thenReturn(
None) None)
when(pkg).pkg_install("dbaas-mycnf", any()).thenRaise(
pkg.PkgPackageStateError("Install error"))
# skip writing the file for now # skip writing the file for now
when(os.path).isfile(any()).thenReturn(False) when(os.path).isfile(any()).thenReturn(False)
mock_status = mock(MySqlAppStatus) mock_status = mock(MySqlAppStatus)
@ -769,7 +749,7 @@ class MySqlAppMockTest(testtools.TestCase):
any(), any(), any()).thenReturn(True) any(), any(), any()).thenReturn(True)
app = MySqlApp(mock_status) app = MySqlApp(mock_status)
self.assertRaises(pkg.PkgPackageStateError, app.secure, 2048) self.assertRaises(TypeError, app.secure, MYCNF, None)
verify(mock_conn, atleast=2).execute(any()) verify(mock_conn, atleast=2).execute(any())
inorder.verify(mock_status).wait_for_real_status_to_change_to( inorder.verify(mock_status).wait_for_real_status_to_change_to(
@ -782,20 +762,16 @@ class MySqlAppMockTest(testtools.TestCase):
when(mock_conn).execute(any()).thenReturn(None) when(mock_conn).execute(any()).thenReturn(None)
when(utils).execute_with_timeout("sudo", any(str), "stop").thenReturn( when(utils).execute_with_timeout("sudo", any(str), "stop").thenReturn(
None) None)
when(pkg).pkg_install("dbaas-mycnf", any()).thenReturn(None)
# skip writing the file for now # skip writing the file for now
when(os.path).isfile(any()).thenReturn(False) when(os.path).isfile(any()).thenReturn(False)
when(utils).execute_with_timeout( when(utils).execute_with_timeout(
"sudo", "chmod", any(), any()).thenReturn(None) "sudo", "chmod", any(), any()).thenReturn(None)
MySqlAppMockTest.stub_file("/etc/mysql/my.cnf")
MySqlAppMockTest.stub_file("/etc/dbaas/my.cnf/my.cnf.2048M")
MySqlAppMockTest.stub_file("/tmp/my.cnf.tmp")
mock_status = mock(MySqlAppStatus) mock_status = mock(MySqlAppStatus)
when(mock_status).wait_for_real_status_to_change_to( when(mock_status).wait_for_real_status_to_change_to(
any(), any(), any()).thenReturn(True) any(), any(), any()).thenReturn(True)
app = MySqlApp(mock_status) app = MySqlApp(mock_status)
when(app)._write_mycnf(any(), any()).thenReturn(True)
app.secure(2048) app.secure(MYCNF, 'foo')
verify(mock_conn, never).execute(TextClauseMatcher('root')) verify(mock_conn, never).execute(TextClauseMatcher('root'))

View File

@ -181,7 +181,8 @@ class GuestAgentManagerTest(testtools.TestCase):
if backup_id: if backup_id:
verify(backup).restore(self.context, backup_id, '/var/lib/mysql') verify(backup).restore(self.context, backup_id, '/var/lib/mysql')
verify(dbaas.MySqlApp).install_if_needed() verify(dbaas.MySqlApp).install_if_needed()
verify(dbaas.MySqlApp).secure('2048') # We dont need to make sure the exact contents are there
verify(dbaas.MySqlApp).secure(any(), any())
verify(dbaas.MySqlAdmin, never).create_database() verify(dbaas.MySqlAdmin, never).create_database()
verify(dbaas.MySqlAdmin, never).create_user() verify(dbaas.MySqlAdmin, never).create_user()
times_report = 1 if is_root_enabled else 0 times_report = 1 if is_root_enabled else 0