anvil/devstack/components/db.py

336 lines
13 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2012 Yahoo! 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 time
from devstack import component as comp
from devstack import exceptions as excp
from devstack import log as logging
from devstack import settings
from devstack import shell as sh
from devstack import trace as tr
from devstack import utils
#id
TYPE = settings.DB
LOG = logging.getLogger("devstack.components.db")
#used for special setups
MYSQL = 'mysql'
START_WAIT_TIME = settings.WAIT_ALIVE_SECS
#TODO maybe we should handle this differently in the future, blah
#ie in a special set of classes (make this class abstract)...
DB_ACTIONS = {
MYSQL: {
# Of course these aren't distro independent...
'runtime': {
settings.UBUNTU11: {
'start': ["service", "mysql", 'start'],
'stop': ["service", 'mysql', "stop"],
'status': ["service", 'mysql', "status"],
'restart': ["service", 'mysql', "restart"],
},
settings.RHEL6: {
'start': ["service", "mysqld", 'start'],
'stop': ["service", 'mysqld', "stop"],
'status': ["service", 'mysqld', "status"],
'restart': ["service", 'mysqld', "restart"],
},
settings.FEDORA16: {
'start': ["service", "mysqld", 'start'],
'stop': ["service", 'mysqld', "stop"],
'status': ["service", 'mysqld', "status"],
'restart': ["service", 'mysqld', "restart"],
},
},
#modification commands
#NOTE: we aren't stopping any sql injection...
'set_pwd': ['mysql', '--user=%USER%', '--password=%OLD_PASSWORD%', '-e',
("\"USE mysql; UPDATE user SET "
" password=PASSWORD('%NEW_PASSWORD%') WHERE User='%USER%'; FLUSH PRIVILEGES;\"")],
'create_db': ['mysql', '--user=%USER%', '--password=%PASSWORD%',
'-e', 'CREATE DATABASE %DB%;'],
'drop_db': ['mysql', '--user=%USER%', '--password=%PASSWORD%',
'-e', 'DROP DATABASE IF EXISTS %DB%;'],
'grant_all': ["mysql", "--user=%USER%", "--password=%PASSWORD%", '-e',
("\"GRANT ALL PRIVILEGES ON *.* TO '%USER%'@'%' "
" IDENTIFIED BY '%PASSWORD%'; FLUSH PRIVILEGES;\"")],
},
}
#need to reset pw to blank since this distributions don't seem to always reset it when u uninstall the db
RESET_BASE_PW = ''
#links about how to reset if it fails
SQL_RESET_PW_LINKS = ['https://help.ubuntu.com/community/MysqlPasswordReset',
'http://dev.mysql.com/doc/refman/5.0/en/resetting-permissions.html']
#used as a generic error message
BASE_ERROR = 'Currently we do not know how to [%s] for database type [%s]'
#the pkg json files db requires for installation
REQ_PKGS = ['db.json']
#config keys we warm up so u won't be prompted later
WARMUP_PWS = ['sql']
class DBUninstaller(comp.PkgUninstallComponent):
def __init__(self, *args, **kargs):
comp.PkgUninstallComponent.__init__(self, TYPE, *args, **kargs)
self.runtime = DBRuntime(*args, **kargs)
def warm_configs(self):
for pw_key in WARMUP_PWS:
self.cfg.get("passwords", pw_key)
def pre_uninstall(self):
dbtype = self.cfg.get("db", "type")
dbactions = DB_ACTIONS.get(dbtype)
try:
if dbactions:
LOG.info(("Attempting to reset your db password to \"%s\" so"
" that we can set it the next time you install.") % (RESET_BASE_PW))
pwd_cmd = dbactions.get('set_pwd')
if pwd_cmd:
LOG.info("Ensuring your database is started before we operate on it.")
self.runtime.restart()
user = self.cfg.get("db", "sql_user")
old_pw = self.cfg.get("passwords", 'sql')
params = {
'OLD_PASSWORD': old_pw,
'NEW_PASSWORD': RESET_BASE_PW,
'USER': user,
}
cmds = [{'cmd': pwd_cmd}]
utils.execute_template(*cmds, params=params, shell=True)
except IOError:
LOG.warn(("Could not reset the database password. You might have to manually "
"reset the password to \"%s\" before the next install") % (RESET_BASE_PW))
LOG.info("To aid in this check out: [%s]", " or ".join(SQL_RESET_PW_LINKS))
class DBInstaller(comp.PkgInstallComponent):
def __init__(self, *args, **kargs):
comp.PkgInstallComponent.__init__(self, TYPE, *args, **kargs)
self.runtime = DBRuntime(*args, **kargs)
def _get_param_map(self, config_fn):
#this dictionary will be used for parameter replacement
#in pre-install and post-install sections
host_ip = self.cfg.get('host', 'ip')
out = {
'PASSWORD': self.cfg.get("passwords", "sql"),
'BOOT_START': ("%s" % (True)).lower(),
'USER': self.cfg.get("db", "sql_user"),
'SERVICE_HOST': host_ip,
'HOST_IP': host_ip
}
return out
def warm_configs(self):
for pw_key in WARMUP_PWS:
self.cfg.get("passwords", pw_key)
def _configure_db_confs(self):
dbtype = self.cfg.get("db", "type")
#TODO: maybe this should be a subclass that handles these differences
if self.distro == settings.RHEL6 and dbtype == MYSQL:
LOG.info("Fixing up %s mysql configs." % (settings.RHEL6))
fc = sh.load_file('/etc/my.cnf')
lines = fc.splitlines()
new_lines = list()
for line in lines:
if line.startswith('skip-grant-tables'):
line = '#' + line
new_lines.append(line)
fc = utils.joinlinesep(*new_lines)
with sh.Rooted(True):
sh.write_file('/etc/my.cnf', fc)
elif self.distro == settings.UBUNTU11 and dbtype == MYSQL:
LOG.info("Fixing up %s mysql configs." % (settings.UBUNTU11))
fc = sh.load_file('/etc/mysql/my.cnf')
lines = fc.splitlines()
new_lines = list()
for line in lines:
if line.startswith('bind-address'):
line = 'bind-address = %s' % ('0.0.0.0')
new_lines.append(line)
fc = utils.joinlinesep(*new_lines)
with sh.Rooted(True):
sh.write_file('/etc/mysql/my.cnf', fc)
def _get_pkgs(self):
return list(REQ_PKGS)
def post_install(self):
comp.PkgInstallComponent.post_install(self)
#fix up the db configs
self._configure_db_confs()
#extra actions to ensure we are granted access
dbtype = self.cfg.get("db", "type")
dbactions = DB_ACTIONS.get(dbtype)
#set your password
try:
if dbactions:
pwd_cmd = dbactions.get('set_pwd')
if pwd_cmd:
LOG.info(("Attempting to set your db password"
" just incase it wasn't set previously."))
LOG.info("Ensuring your database is started before we operate on it.")
self.runtime.restart()
params = {
'NEW_PASSWORD': self.cfg.get("passwords", "sql"),
'USER': self.cfg.get("db", "sql_user"),
'OLD_PASSWORD': RESET_BASE_PW,
}
cmds = [{'cmd': pwd_cmd}]
utils.execute_template(*cmds, params=params, shell=True)
except IOError:
LOG.warn(("Couldn't set your db password. It might have already been "
"set by a previous process."))
#ensure access granted
if dbactions:
grant_cmd = dbactions.get('grant_all')
if grant_cmd:
user = self.cfg.get("db", "sql_user")
LOG.info("Updating the DB to give user '%s' full control of all databases." % (user))
LOG.info("Ensuring your database is started before we operate on it.")
self.runtime.restart()
params = {
'PASSWORD': self.cfg.get("passwords", "sql"),
'USER': user,
}
cmds = list()
cmds.append({
'cmd': grant_cmd,
})
#shell seems to be needed here
#since python escapes this to much...
utils.execute_template(*cmds, params=params, shell=True)
class DBRuntime(comp.EmptyRuntime):
def __init__(self, *args, **kargs):
comp.EmptyRuntime.__init__(self, TYPE, *args, **kargs)
self.tracereader = tr.TraceReader(self.tracedir, tr.IN_TRACE)
def _get_run_actions(self, act, exception_cls):
dbtype = self.cfg.get("db", "type")
type_actions = DB_ACTIONS.get(dbtype)
if type_actions is None:
msg = BASE_ERROR % (act, dbtype)
raise NotImplementedError(msg)
distro_options = type_actions.get('runtime').get(self.distro)
if distro_options is None:
msg = BASE_ERROR % (act, dbtype)
raise NotImplementedError(msg)
return distro_options.get(act)
def start(self):
if self.status() != comp.STATUS_STARTED:
startcmd = self._get_run_actions('start', excp.StartException)
sh.execute(*startcmd,
run_as_root=True,
check_exit_code=True)
LOG.info("Please wait %s seconds while it starts up." % START_WAIT_TIME)
time.sleep(START_WAIT_TIME)
return 1
else:
return 0
def stop(self):
if self.status() != comp.STATUS_STOPPED:
stopcmd = self._get_run_actions('stop', excp.StopException)
sh.execute(*stopcmd,
run_as_root=True,
check_exit_code=True)
return 1
else:
return 0
def restart(self):
LOG.info("Restarting your database.")
restartcmd = self._get_run_actions('restart', excp.RestartException)
sh.execute(*restartcmd,
run_as_root=True,
check_exit_code=True)
LOG.info("Please wait %s seconds while it restarts." % START_WAIT_TIME)
time.sleep(START_WAIT_TIME)
return 1
def status(self):
statuscmd = self._get_run_actions('status', excp.StatusException)
run_result = sh.execute(*statuscmd,
run_as_root=True,
check_exit_code=False)
if not run_result:
return comp.STATUS_UNKNOWN
(sysout, stderr) = run_result
combined = str(sysout) + str(stderr)
combined = combined.lower()
if combined.find("running") != -1:
return comp.STATUS_STARTED
elif combined.find("stop") != -1:
return comp.STATUS_STOPPED
else:
return comp.STATUS_UNKNOWN
def drop_db(cfg, dbname):
dbtype = cfg.get("db", "type")
dbactions = DB_ACTIONS.get(dbtype)
if dbactions and dbactions.get('drop_db'):
dropcmd = dbactions.get('drop_db')
params = dict()
params['PASSWORD'] = cfg.get("passwords", "sql")
params['USER'] = cfg.get("db", "sql_user")
params['DB'] = dbname
cmds = list()
cmds.append({
'cmd': dropcmd,
'run_as_root': False,
})
utils.execute_template(*cmds, params=params)
else:
msg = BASE_ERROR % ('drop', dbtype)
raise NotImplementedError(msg)
def create_db(cfg, dbname):
dbtype = cfg.get("db", "type")
dbactions = DB_ACTIONS.get(dbtype)
if dbactions and dbactions.get('create_db'):
createcmd = dbactions.get('create_db')
params = dict()
params['PASSWORD'] = cfg.get("passwords", "sql")
params['USER'] = cfg.get("db", "sql_user")
params['DB'] = dbname
cmds = list()
cmds.append({
'cmd': createcmd,
'run_as_root': False,
})
utils.execute_template(*cmds, params=params)
else:
msg = BASE_ERROR % ('create', dbtype)
raise NotImplementedError(msg)