441 lines
17 KiB
Python
441 lines
17 KiB
Python
# Copyright 2015 IBM Corp.
|
|
# 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.
|
|
|
|
from oslo_log import log as logging
|
|
|
|
from trove.common import cfg
|
|
from trove.common import exception
|
|
from trove.common import instance as rd_instance
|
|
from trove.common import utils as utils
|
|
from trove.guestagent.common import operating_system
|
|
from trove.guestagent.datastore.experimental.db2 import system
|
|
from trove.guestagent.datastore import service
|
|
from trove.guestagent.db import models
|
|
from trove.openstack.common.gettextutils import _
|
|
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
IGNORE_USERS_LIST = CONF.db2.ignore_users
|
|
|
|
|
|
class DB2App(object):
|
|
"""
|
|
Handles installation and configuration of DB2
|
|
on a Trove instance.
|
|
"""
|
|
def __init__(self, status, state_change_wait_time=None):
|
|
LOG.debug("Initialize DB2App.")
|
|
self.state_change_wait_time = (
|
|
state_change_wait_time if state_change_wait_time else
|
|
CONF.state_change_wait_time
|
|
)
|
|
LOG.debug("state_change_wait_time = %s." % self.state_change_wait_time)
|
|
self.status = status
|
|
|
|
def complete_install_or_restart(self):
|
|
self.status.end_install_or_restart()
|
|
|
|
def change_ownership(self, mount_point):
|
|
"""
|
|
When DB2 server instance is installed, it does not have the
|
|
DB2 local database directory created (/home/db2inst1/db2inst1).
|
|
This gets created when we mount the cinder volume. So we need
|
|
to change ownership of this directory to the DB2 instance user
|
|
- db2inst1.
|
|
"""
|
|
LOG.debug("Changing ownership of the DB2 data directory.")
|
|
try:
|
|
operating_system.chown(mount_point,
|
|
system.DB2_INSTANCE_OWNER,
|
|
system.DB2_INSTANCE_OWNER,
|
|
recursive=False, as_root=True)
|
|
except exception.ProcessExecutionError:
|
|
raise RuntimeError(_(
|
|
"Command to change ownership of DB2 data directory failed."))
|
|
|
|
def _enable_db_on_boot(self):
|
|
LOG.debug("Enable DB on boot.")
|
|
try:
|
|
run_command(system.ENABLE_AUTOSTART)
|
|
except exception.ProcessExecutionError:
|
|
raise RuntimeError(_(
|
|
"Command to enable DB2 server on boot failed."))
|
|
|
|
def _disable_db_on_boot(self):
|
|
LOG.debug("Disable DB2 on boot.")
|
|
try:
|
|
run_command(system.DISABLE_AUTOSTART)
|
|
except exception.ProcessExecutionError:
|
|
raise RuntimeError(_(
|
|
"Command to disable DB2 server on boot failed."))
|
|
|
|
def start_db_with_conf_changes(self, config_contents):
|
|
'''
|
|
Will not be implementing configuration change API for DB2 in
|
|
the Kilo release. Currently all that this method does is to start
|
|
the DB2 server without any configuration changes. Looks like
|
|
this needs to be implemented to enable volume resize on the guest
|
|
agent side.
|
|
'''
|
|
LOG.info(_("Starting DB2 with configuration changes."))
|
|
self.start_db(True)
|
|
|
|
def start_db(self, update_db=False):
|
|
LOG.debug("Start the DB2 server instance.")
|
|
self._enable_db_on_boot()
|
|
try:
|
|
run_command(system.START_DB2)
|
|
except exception.ProcessExecutionError:
|
|
pass
|
|
|
|
if not self.status.wait_for_real_status_to_change_to(
|
|
rd_instance.ServiceStatuses.RUNNING,
|
|
self.state_change_wait_time, update_db):
|
|
LOG.error(_("Start of DB2 server instance failed."))
|
|
self.status.end_install_or_restart()
|
|
raise RuntimeError(_("Could not start DB2."))
|
|
|
|
def stop_db(self, update_db=False, do_not_start_on_reboot=False):
|
|
LOG.debug("Stop the DB2 server instance.")
|
|
if do_not_start_on_reboot:
|
|
self._disable_db_on_boot()
|
|
try:
|
|
run_command(system.STOP_DB2)
|
|
except exception.ProcessExecutionError:
|
|
pass
|
|
|
|
if not (self.status.wait_for_real_status_to_change_to(
|
|
rd_instance.ServiceStatuses.SHUTDOWN,
|
|
self.state_change_wait_time, update_db)):
|
|
LOG.error(_("Could not stop DB2."))
|
|
self.status.end_install_or_restart()
|
|
raise RuntimeError(_("Could not stop DB2."))
|
|
|
|
def restart(self):
|
|
LOG.debug("Restarting DB2 server instance.")
|
|
try:
|
|
self.status.begin_restart()
|
|
self.stop_db()
|
|
self.start_db()
|
|
finally:
|
|
self.status.end_install_or_restart()
|
|
|
|
|
|
class DB2AppStatus(service.BaseDbStatus):
|
|
"""
|
|
Handles all of the status updating for the DB2 guest agent.
|
|
"""
|
|
def _get_actual_db_status(self):
|
|
LOG.debug("Getting the status of the DB2 server instance.")
|
|
try:
|
|
out, err = utils.execute_with_timeout(
|
|
system.DB2_STATUS, shell=True)
|
|
if "0" not in out:
|
|
return rd_instance.ServiceStatuses.RUNNING
|
|
else:
|
|
return rd_instance.ServiceStatuses.SHUTDOWN
|
|
except exception.ProcessExecutionError:
|
|
LOG.exception(_("Error getting the DB2 server status."))
|
|
return rd_instance.ServiceStatuses.CRASHED
|
|
|
|
|
|
def run_command(command, superuser=system.DB2_INSTANCE_OWNER,
|
|
timeout=system.TIMEOUT):
|
|
return utils.execute_with_timeout("sudo", "su", "-", superuser, "-c",
|
|
command, timeout=timeout)
|
|
|
|
|
|
class DB2Admin(object):
|
|
"""
|
|
Handles administrative tasks on the DB2 instance.
|
|
"""
|
|
def create_database(self, databases):
|
|
"""Create the given database(s)."""
|
|
dbName = None
|
|
db_create_failed = []
|
|
LOG.debug("Creating DB2 databases.")
|
|
for item in databases:
|
|
mydb = models.ValidatedMySQLDatabase()
|
|
mydb.deserialize(item)
|
|
dbName = mydb.name
|
|
LOG.debug("Creating DB2 database: %s." % dbName)
|
|
try:
|
|
run_command(system.CREATE_DB_COMMAND % {'dbname': dbName})
|
|
except exception.ProcessExecutionError:
|
|
LOG.exception(_(
|
|
"There was an error creating database: %s.") % dbName)
|
|
db_create_failed.append(dbName)
|
|
pass
|
|
if len(db_create_failed) > 0:
|
|
LOG.exception(_("Creating the following databases failed: %s.") %
|
|
db_create_failed)
|
|
|
|
def delete_database(self, database):
|
|
"""Delete the specified database."""
|
|
dbName = None
|
|
try:
|
|
mydb = models.ValidatedMySQLDatabase()
|
|
mydb.deserialize(database)
|
|
dbName = mydb.name
|
|
LOG.debug("Deleting DB2 database: %s." % dbName)
|
|
run_command(system.DELETE_DB_COMMAND % {'dbname': dbName})
|
|
except exception.ProcessExecutionError:
|
|
LOG.exception(_(
|
|
"There was an error while deleting database:%s.") % dbName)
|
|
raise exception.GuestError(_("Unable to delete database: %s.") %
|
|
dbName)
|
|
|
|
def list_databases(self, limit=None, marker=None, include_marker=False):
|
|
LOG.debug("Listing all the DB2 databases.")
|
|
databases = []
|
|
next_marker = None
|
|
|
|
try:
|
|
out, err = run_command(system.LIST_DB_COMMAND)
|
|
dblist = out.split()
|
|
result = iter(dblist)
|
|
count = 0
|
|
|
|
if marker is not None:
|
|
try:
|
|
item = result.next()
|
|
while item != marker:
|
|
item = result.next()
|
|
|
|
if item == marker:
|
|
marker = None
|
|
except StopIteration:
|
|
pass
|
|
|
|
try:
|
|
item = result.next()
|
|
while item:
|
|
count = count + 1
|
|
if (limit and count <= limit) or limit is None:
|
|
db2_db = models.MySQLDatabase()
|
|
db2_db.name = item
|
|
LOG.debug("database = %s ." % item)
|
|
db2_db.character_set = None
|
|
db2_db.collate = None
|
|
next_marker = db2_db.name
|
|
databases.append(db2_db.serialize())
|
|
item = result.next()
|
|
else:
|
|
next_marker = None
|
|
break
|
|
except StopIteration:
|
|
next_marker = None
|
|
LOG.debug("databases = %s." % str(databases))
|
|
except exception.ProcessExecutionError as pe:
|
|
LOG.exception(_("An error occured listing databases: %s.") %
|
|
pe.message)
|
|
pass
|
|
return databases, next_marker
|
|
|
|
def create_user(self, users):
|
|
LOG.debug("Creating user(s) for accessing DB2 database(s).")
|
|
try:
|
|
for item in users:
|
|
user = models.MySQLUser()
|
|
user.deserialize(item)
|
|
try:
|
|
LOG.debug("Creating OS user: %s." % user.name)
|
|
utils.execute_with_timeout(
|
|
system.CREATE_USER_COMMAND % {
|
|
'login': user.name, 'login': user.name,
|
|
'passwd': user.password}, shell=True)
|
|
except exception.ProcessExecutionError as pe:
|
|
LOG.exception(_("Error creating user: %s.") % user.name)
|
|
continue
|
|
|
|
for database in user.databases:
|
|
mydb = models.ValidatedMySQLDatabase()
|
|
mydb.deserialize(database)
|
|
try:
|
|
LOG.debug("Granting user: %s access to database: %s."
|
|
% (user.name, mydb.name))
|
|
run_command(system.GRANT_USER_ACCESS % {
|
|
'dbname': mydb.name, 'login': user.name})
|
|
except exception.ProcessExecutionError as pe:
|
|
LOG.debug(
|
|
"Error granting user: %s access to database: %s."
|
|
% (user.name, mydb.name))
|
|
LOG.debug(pe)
|
|
pass
|
|
except exception.ProcessExecutionError as pe:
|
|
LOG.exception(_("An error occured creating users: %s.") %
|
|
pe.message)
|
|
pass
|
|
|
|
def delete_user(self, user):
|
|
LOG.debug("Delete a given user.")
|
|
db2_user = models.MySQLUser()
|
|
db2_user.deserialize(user)
|
|
userName = db2_user.name
|
|
user_dbs = db2_user.databases
|
|
LOG.debug("For user %s, databases to be deleted = %r." % (
|
|
userName, user_dbs))
|
|
|
|
if len(user_dbs) == 0:
|
|
databases = self.list_access(db2_user.name, None)
|
|
else:
|
|
databases = user_dbs
|
|
|
|
LOG.debug("databases for user = %r." % databases)
|
|
for database in databases:
|
|
mydb = models.ValidatedMySQLDatabase()
|
|
mydb.deserialize(database)
|
|
try:
|
|
run_command(system.REVOKE_USER_ACCESS % {
|
|
'dbname': mydb.name,
|
|
'login': userName})
|
|
LOG.debug("Revoked access for user:%s on database:%s." % (
|
|
userName, mydb.name))
|
|
except exception.ProcessExecutionError as pe:
|
|
LOG.debug("Error occured while revoking access to %s." %
|
|
mydb.name)
|
|
pass
|
|
try:
|
|
utils.execute_with_timeout(system.DELETE_USER_COMMAND % {
|
|
'login': db2_user.name.lower()}, shell=True)
|
|
except exception.ProcessExecutionError as pe:
|
|
LOG.exception(_(
|
|
"There was an error while deleting user: %s.") % pe)
|
|
raise exception.GuestError(_("Unable to delete user: %s.") %
|
|
userName)
|
|
|
|
def list_users(self, limit=None, marker=None, include_marker=False):
|
|
LOG.debug(
|
|
"List all users for all the databases in a DB2 server instance.")
|
|
users = []
|
|
user_map = {}
|
|
next_marker = None
|
|
count = 0
|
|
|
|
databases, marker = self.list_databases()
|
|
for database in databases:
|
|
db2_db = models.MySQLDatabase()
|
|
db2_db.deserialize(database)
|
|
out = None
|
|
try:
|
|
out, err = run_command(
|
|
system.LIST_DB_USERS % {'dbname': db2_db.name})
|
|
except exception.ProcessExecutionError:
|
|
LOG.debug(
|
|
"There was an error while listing users for database: %s."
|
|
% db2_db.name)
|
|
continue
|
|
|
|
userlist = []
|
|
for item in out.split('\n'):
|
|
LOG.debug("item = %r" % item)
|
|
user = item.split() if item != "" else None
|
|
LOG.debug("user = %r" % (user))
|
|
if user is not None and user[0] not in IGNORE_USERS_LIST \
|
|
and user[1] == 'Y':
|
|
userlist.append(user[0])
|
|
result = iter(userlist)
|
|
|
|
if marker is not None:
|
|
try:
|
|
item = result.next()
|
|
while item != marker:
|
|
item = result.next()
|
|
|
|
if item == marker:
|
|
marker = None
|
|
except StopIteration:
|
|
pass
|
|
|
|
try:
|
|
item = result.next()
|
|
db2db = models.MySQLDatabase()
|
|
db2db.name = db2_db.name
|
|
|
|
while item:
|
|
'''
|
|
Check if the user has already been discovered. If so,
|
|
add this database to the database list for this user.
|
|
'''
|
|
if item in user_map:
|
|
db2user = user_map.get(item)
|
|
db2user.databases.append(db2db.serialize())
|
|
item = result.next()
|
|
continue
|
|
'''
|
|
If this user was not previously discovered, then add
|
|
this to the user's list.
|
|
'''
|
|
count = count + 1
|
|
if (limit and count <= limit) or limit is None:
|
|
db2_user = models.MySQLUser()
|
|
db2_user.name = item
|
|
db2_user.databases.append(db2db.serialize())
|
|
users.append(db2_user.serialize())
|
|
user_map.update({item: db2_user})
|
|
item = result.next()
|
|
else:
|
|
next_marker = None
|
|
break
|
|
except StopIteration:
|
|
next_marker = None
|
|
|
|
if count == limit:
|
|
break
|
|
return users, next_marker
|
|
|
|
def get_user(self, username, hostname):
|
|
LOG.debug("Get details of a given database user.")
|
|
user = self._get_user(username, hostname)
|
|
if not user:
|
|
return None
|
|
return user.serialize()
|
|
|
|
def _get_user(self, username, hostname):
|
|
LOG.debug("Get details of a given database user %s." % username)
|
|
user = models.MySQLUser()
|
|
user.name = username
|
|
databases, marker = self.list_databases()
|
|
out = None
|
|
for database in databases:
|
|
db2_db = models.MySQLDatabase()
|
|
db2_db.deserialize(database)
|
|
try:
|
|
out, err = run_command(
|
|
system.LIST_DB_USERS % {'dbname': db2_db.name})
|
|
except exception.ProcessExecutionError:
|
|
LOG.debug(
|
|
"Error while trying to get the users for database: %s." %
|
|
db2_db.name)
|
|
continue
|
|
|
|
for item in out.split('\n'):
|
|
user_access = item.split() if item != "" else None
|
|
if (user_access is not None and
|
|
user_access[0].lower() == username.lower() and
|
|
user_access[1] == 'Y'):
|
|
user.databases = db2_db.name
|
|
break
|
|
return user
|
|
|
|
def list_access(self, username, hostname):
|
|
"""
|
|
Show all the databases to which the user has more than
|
|
USAGE granted.
|
|
"""
|
|
LOG.debug("Listing databases that user: %s has access to." % username)
|
|
user = self._get_user(username, hostname)
|
|
return user.databases
|