Add UID/GID configurations for each datastore

When trove prepares a container, it grants permissions for folders with
the default user/group "database" (ID 1001). However, some Docker images
require a different user for provisioning the database. Therefore, we
need to make it flexible so that the database container can run with
the required user and group, regardless of the default settings.

Story: #2010827
Task: #48386
Change-Id: Ie0268951db7a9e711089714cecf7fbf311eecec8
This commit is contained in:
Bo Tran Van 2023-07-27 14:09:23 +07:00 committed by wu.chunyang
parent be9bd3f3a0
commit 27778a90d0
10 changed files with 170 additions and 54 deletions

View File

@ -303,6 +303,25 @@ Some config options specifically for trove guest agent:
docker_image = your-registry/your-repo/mysql
backup_docker_image = your-registry/your-repo/db-backup-mysql
* Setting username, uid, gid for each datastore
Currently, when a database container is running, it is owned by user:
database (UID: 1001) and group: database (GID: 1001).
In some cases, you may need to set the owner of files,
directories or container to adapt to your own datastore image.
To achieve this, you can configure the option
database_service_uname, database_service_uid, database_service_gid
in trove-guestagent.conf with following:
.. code-block:: ini
[<datastore_manage>]
database_service_uid = 1001
database_service_gid = 0
database_service_uname = postgres
Make Trove work with multiple versions for each datastore
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When Trove do a backup/restore actions, The Trove guest agent pulls container

View File

@ -0,0 +1,7 @@
---
features:
- |
Add support of setting owner,uid,gid for each datastore instances
by configuring database_service_uname,
database_service_uid and database_service_gid
Story 2010827 <https://storyboard.openstack.org/#!/story/2010827>

View File

@ -83,8 +83,11 @@ def main():
# Create user and group for running docker container.
LOG.info('Creating user and group for database service')
uid = cfg.get_configuration_property('database_service_uid')
operating_system.create_user('database', uid)
uid = CONF.get(CONF.datastore_manager
).database_service_uid or CONF.database_service_uid
gid = CONF.get(CONF.datastore_manager).database_service_gid or uid
uname = CONF.get(CONF.datastore_manager).database_service_uname
operating_system.create_user(uname, user_id=uid, group_id=gid)
# Mount device if needed.
# When doing rebuild, the device should be already formatted but not
@ -97,9 +100,8 @@ def main():
device_path, mount_point)
device.format()
device.mount(mount_point)
operating_system.chown(mount_point, CONF.database_service_uid,
CONF.database_service_uid,
recursive=True, as_root=True)
operating_system.chown(
mount_point, uid, gid, recursive=True, as_root=True)
# rpc module must be loaded after decision about thread monkeypatching
# because if thread module is not monkeypatched we can't use eventlet

View File

@ -544,6 +544,12 @@ mysql_group = cfg.OptGroup(
'mysql', title='MySQL options',
help="Oslo option group designed for MySQL datastore")
mysql_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -621,6 +627,12 @@ percona_group = cfg.OptGroup(
'percona', title='Percona options',
help="Oslo option group designed for Percona datastore")
percona_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -690,6 +702,12 @@ pxc_group = cfg.OptGroup(
'pxc', title='Percona XtraDB Cluster options',
help="Oslo option group designed for Percona XtraDB Cluster datastore")
pxc_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -771,6 +789,12 @@ redis_group = cfg.OptGroup(
'redis', title='Redis options',
help="Oslo option group designed for Redis datastore")
redis_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -830,6 +854,12 @@ cassandra_group = cfg.OptGroup(
'cassandra', title='Cassandra options',
help="Oslo option group designed for Cassandra datastore")
cassandra_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -914,6 +944,12 @@ couchbase_group = cfg.OptGroup(
'couchbase', title='Couchbase options',
help="Oslo option group designed for Couchbase datastore")
couchbase_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -959,6 +995,12 @@ mongodb_group = cfg.OptGroup(
'mongodb', title='MongoDB options',
help="Oslo option group designed for MongoDB datastore")
mongodb_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -1038,6 +1080,12 @@ postgresql_group = cfg.OptGroup(
'postgresql', title='PostgreSQL options',
help="Oslo option group for the PostgreSQL datastore.")
postgresql_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt(
'enable_clean_wal_archives',
default=True,
@ -1123,6 +1171,12 @@ couchdb_group = cfg.OptGroup(
'couchdb', title='CouchDB options',
help="Oslo option group designed for CouchDB datastore")
couchdb_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -1174,6 +1228,12 @@ vertica_group = cfg.OptGroup(
'vertica', title='Vertica options',
help="Oslo option group designed for Vertica datastore")
vertica_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -1241,6 +1301,12 @@ db2_group = cfg.OptGroup(
'db2', title='DB2 options',
help="Oslo option group designed for DB2 datastore")
db2_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),
@ -1284,6 +1350,12 @@ mariadb_group = cfg.OptGroup(
'mariadb', title='MariaDB options',
help="Oslo option group designed for MariaDB datastore")
mariadb_opts = [
cfg.StrOpt('database_service_uname', default='database',
help='The name of database service user.'),
cfg.StrOpt('database_service_uid',
help='The UID of database service user.'),
cfg.StrOpt('database_service_gid',
help='The GID of database service user.'),
cfg.BoolOpt('icmp', default=False,
help='Whether to permit ICMP.',
deprecated_for_removal=True),

View File

@ -71,10 +71,11 @@ class MySqlManager(manager.Manager):
"""This is called from prepare in the base class."""
data_dir = mount_point + '/data'
self.app.stop_db()
operating_system.ensure_directory(data_dir,
user=CONF.database_service_uid,
group=CONF.database_service_uid,
as_root=True)
operating_system.ensure_directory(
data_dir,
user=self.app.database_service_uid,
group=self.app.database_service_gid,
as_root=True)
# This makes sure the include dir is created.
self.app.set_data_dir(data_dir)
@ -223,9 +224,11 @@ class MySqlManager(manager.Manager):
# Allow database service user to access the temporary files.
try:
for file in [init_file.name, err_file.name]:
operating_system.chown(file, CONF.database_service_uid,
CONF.database_service_uid, force=True,
as_root=True)
operating_system.chown(
file,
self.app.database_service_uid,
self.app.database_service_gid,
force=True, as_root=True)
except Exception as err:
LOG.error('Failed to change file owner, error: %s', str(err))
for file in [init_file.name, err_file.name]:
@ -338,8 +341,8 @@ class MySqlManager(manager.Manager):
mount_point = CONF.get(CONF.datastore_manager).mount_point
data_dir = mount_point + '/data'
operating_system.ensure_directory(data_dir,
user=CONF.database_service_uid,
group=CONF.database_service_uid,
user=self.app.database_service_uid,
group=self.app.database_service_gid,
as_root=True)
# This makes sure the include dir is created.
self.app.set_data_dir(data_dir)

View File

@ -445,7 +445,7 @@ class BaseMySqlApp(service.BaseDbApp):
return self._configuration_manager
self._configuration_manager = ConfigurationManager(
MYSQL_CONFIG, CONF.database_service_uid, CONF.database_service_uid,
MYSQL_CONFIG, self.database_service_uid, self.database_service_gid,
service.BaseDbApp.CFG_CODEC, requires_root=True,
override_strategy=ImportOverrideStrategy(CNF_INCLUDE_DIR, CNF_EXT)
)
@ -591,14 +591,14 @@ class BaseMySqlApp(service.BaseDbApp):
root_pass = utils.generate_random_password()
# Get uid and gid
user = "%s:%s" % (CONF.database_service_uid, CONF.database_service_uid)
user = "%s:%s" % (self.database_service_uid, self.database_service_gid)
# Create folders for mysql on localhost
for folder in ['/etc/mysql', constants.MYSQL_HOST_SOCKET_PATH,
'/etc/mysql/mysql.conf.d']:
operating_system.ensure_directory(
folder, user=CONF.database_service_uid,
group=CONF.database_service_uid, force=True,
folder, user=self.database_service_uid,
group=self.database_service_gid, force=True,
as_root=True)
volumes = {
@ -678,8 +678,8 @@ class BaseMySqlApp(service.BaseDbApp):
for folder in ['/etc/mysql', constants.MYSQL_HOST_SOCKET_PATH,
'/etc/mysql/mysql.conf.d']:
operating_system.ensure_directory(
folder, user=CONF.database_service_uid,
group=CONF.database_service_uid, force=True,
folder, user=self.database_service_uid,
group=self.database_service_gid, force=True,
as_root=True)
try:
@ -742,8 +742,8 @@ class BaseMySqlApp(service.BaseDbApp):
LOG.debug('Deleting ib_logfile files after restore from backup %s',
backup_id)
operating_system.chown(restore_location, CONF.database_service_uid,
CONF.database_service_uid, force=True,
operating_system.chown(restore_location, self.database_service_uid,
self.database_service_gid, force=True,
as_root=True)
self.wipe_ib_logfiles()

View File

@ -123,14 +123,12 @@ class PostgresManager(manager.Manager):
device_path, mount_point, backup_info,
config_contents, root_password, overrides,
cluster_config, snapshot, ds_version=None):
operating_system.ensure_directory(self.app.datadir,
user=CONF.database_service_uid,
group=CONF.database_service_uid,
as_root=True)
operating_system.ensure_directory(service.WAL_ARCHIVE_DIR,
user=CONF.database_service_uid,
group=CONF.database_service_uid,
as_root=True)
for datadir in [self.app.datadir, service.WAL_ARCHIVE_DIR]:
operating_system.ensure_directory(
datadir,
user=self.app.database_service_uid,
group=self.app.database_service_gid,
as_root=True)
LOG.info('Preparing database config files')
self.app.configuration_manager.reset_configuration(config_contents)
@ -149,9 +147,11 @@ class PostgresManager(manager.Manager):
signal_file = f"{self.app.datadir}/recovery.signal"
operating_system.execute_shell_cmd(
f"touch {signal_file}", [], shell=True, as_root=True)
operating_system.chown(signal_file, CONF.database_service_uid,
CONF.database_service_uid, force=True,
as_root=True)
operating_system.chown(
signal_file,
user=self.app.database_service_uid,
group=self.app.database_service_gid,
force=True, as_root=True)
if snapshot:
# This instance is a replica
@ -198,7 +198,8 @@ class PostgresManager(manager.Manager):
}
def is_log_enabled(self, logname):
return self.configuration_manager.get_value('logging_collector', False)
return self.configuration_manager.get_value(
'logging_collector', default=False)
def create_backup(self, context, backup_info):
"""Create backup for the database.

View File

@ -85,8 +85,8 @@ class PgSqlApp(service.BaseDbApp):
self._configuration_manager = configuration.ConfigurationManager(
CONFIG_FILE,
CONF.database_service_uid,
CONF.database_service_uid,
self.database_service_uid,
self.database_service_gid,
stream_codecs.KeyValueCodec(
value_quoting=True,
bool_case=stream_codecs.KeyValueCodec.BOOL_LOWER,
@ -143,8 +143,8 @@ class PgSqlApp(service.BaseDbApp):
stream_codecs.PropertiesCodec(string_mappings={'\t': None}),
as_root=True)
operating_system.chown(HBA_CONFIG_FILE,
CONF.database_service_uid,
CONF.database_service_uid,
self.database_service_uid,
self.database_service_gid,
as_root=True)
operating_system.chmod(HBA_CONFIG_FILE,
operating_system.FileMode.SET_USR_RO,
@ -174,14 +174,14 @@ class PgSqlApp(service.BaseDbApp):
postgres_pass = utils.generate_random_password()
# Get uid and gid
user = "%s:%s" % (CONF.database_service_uid, CONF.database_service_uid)
user = "%s:%s" % (self.database_service_uid, self.database_service_gid)
# Create folders for postgres on localhost
for folder in ['/etc/postgresql',
constants.POSTGRESQL_HOST_SOCKET_PATH]:
operating_system.ensure_directory(
folder, user=CONF.database_service_uid,
group=CONF.database_service_uid, force=True,
folder, user=self.database_service_uid,
group=self.database_service_gid, force=True,
as_root=True)
volumes = {
@ -244,8 +244,8 @@ class PgSqlApp(service.BaseDbApp):
for folder in ['/etc/postgresql',
constants.POSTGRESQL_HOST_SOCKET_PATH]:
operating_system.ensure_directory(
folder, user=CONF.database_service_uid,
group=CONF.database_service_uid, force=True,
folder, user=self.database_service_uid,
group=self.database_service_gid, force=True,
as_root=True)
try:
@ -311,8 +311,8 @@ class PgSqlApp(service.BaseDbApp):
raise Exception(msg)
for dir in [WAL_ARCHIVE_DIR, self.datadir]:
operating_system.chown(dir, CONF.database_service_uid,
CONF.database_service_uid, force=True,
operating_system.chown(dir, self.database_service_uid,
self.database_service_gid, force=True,
as_root=True)
def is_replica(self):
@ -341,7 +341,7 @@ class PgSqlApp(service.BaseDbApp):
def pg_rewind(self, conn_info):
docker_image = CONF.get(CONF.datastore_manager).docker_image
image = f'{docker_image}:{CONF.datastore_version}'
user = "%s:%s" % (CONF.database_service_uid, CONF.database_service_uid)
user = "%s:%s" % (self.database_service_uid, self.database_service_gid)
volumes = {
constants.POSTGRESQL_HOST_SOCKET_PATH:
{"bind": "/var/run/postgresql", "mode": "rw"},

View File

@ -562,3 +562,13 @@ class BaseDbApp(object):
sent=timeutils.utcnow_ts(microsecond=True),
**backup_state)
LOG.debug("Updated state for %s to %s.", backup_id, backup_state)
@property
def database_service_uid(self):
return cfg.get_configuration_property(
'database_service_uid') or CONF.database_service_uid
@property
def database_service_gid(self):
return cfg.get_configuration_property(
'database_service_gid') or self.database_service_uid

View File

@ -41,8 +41,9 @@ class PostgresqlReplicationStreaming(base.Replication):
"""
pw = utils.generate_random_password()
operating_system.write_file(pwfile, pw, as_root=True)
operating_system.chown(pwfile, user=CONF.database_service_uid,
group=CONF.database_service_uid, as_root=True)
operating_system.chown(pwfile, user=service.database_service_uid,
group=service.database_service_gid,
as_root=True)
operating_system.chmod(pwfile, FileMode.SET_USR_RWX(),
as_root=True)
LOG.debug(f"File {pwfile} created")
@ -108,8 +109,9 @@ class PostgresqlReplicationStreaming(base.Replication):
operating_system.copy(tmp_hba, pg_service.HBA_CONFIG_FILE,
force=True, as_root=True)
operating_system.chown(pg_service.HBA_CONFIG_FILE,
user=CONF.database_service_uid,
group=CONF.database_service_uid, as_root=True)
user=service.database_service_uid,
group=service.database_service_gid,
as_root=True)
operating_system.chmod(pg_service.HBA_CONFIG_FILE,
FileMode.SET_USR_RWX(),
as_root=True)
@ -166,8 +168,8 @@ class PostgresqlReplicationStreaming(base.Replication):
signal_file = f"{service.datadir}/standby.signal"
operating_system.execute_shell_cmd(
f"touch {signal_file}", [], shell=True, as_root=True)
operating_system.chown(signal_file, CONF.database_service_uid,
CONF.database_service_uid, force=True,
operating_system.chown(signal_file, service.database_service_uid,
service.database_service_gid, force=True,
as_root=True)
LOG.debug("Standby signal file created")
@ -217,8 +219,8 @@ class PostgresqlReplicationStreaming(base.Replication):
signal_file = f"{service.datadir}/standby.signal"
operating_system.execute_shell_cmd(
f"touch {signal_file}", [], shell=True, as_root=True)
operating_system.chown(signal_file, CONF.database_service_uid,
CONF.database_service_uid, force=True,
operating_system.chown(signal_file, service.database_service_uid,
service.database_service_gid, force=True,
as_root=True)
LOG.debug("Standby signal file created")