diff --git a/doc/source/admin/run_trove_in_production.rst b/doc/source/admin/run_trove_in_production.rst index 5bb242baae..742a81b796 100644 --- a/doc/source/admin/run_trove_in_production.rst +++ b/doc/source/admin/run_trove_in_production.rst @@ -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 + + [] + 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 diff --git a/releasenotes/notes/support-configuring-uid-gid-each-datastore-1afd2b76832b716e.yaml b/releasenotes/notes/support-configuring-uid-gid-each-datastore-1afd2b76832b716e.yaml new file mode 100644 index 0000000000..915a4f5143 --- /dev/null +++ b/releasenotes/notes/support-configuring-uid-gid-each-datastore-1afd2b76832b716e.yaml @@ -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 diff --git a/trove/cmd/guest.py b/trove/cmd/guest.py index f613283463..ad5d888023 100644 --- a/trove/cmd/guest.py +++ b/trove/cmd/guest.py @@ -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 diff --git a/trove/common/cfg.py b/trove/common/cfg.py index 9198f52d2c..8bbbf6cd92 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -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), diff --git a/trove/guestagent/datastore/mysql_common/manager.py b/trove/guestagent/datastore/mysql_common/manager.py index 715617f12d..094707029c 100644 --- a/trove/guestagent/datastore/mysql_common/manager.py +++ b/trove/guestagent/datastore/mysql_common/manager.py @@ -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) diff --git a/trove/guestagent/datastore/mysql_common/service.py b/trove/guestagent/datastore/mysql_common/service.py index 040bc16e97..7198f6c3f4 100644 --- a/trove/guestagent/datastore/mysql_common/service.py +++ b/trove/guestagent/datastore/mysql_common/service.py @@ -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() diff --git a/trove/guestagent/datastore/postgres/manager.py b/trove/guestagent/datastore/postgres/manager.py index 2967a56f48..34109ecbf1 100644 --- a/trove/guestagent/datastore/postgres/manager.py +++ b/trove/guestagent/datastore/postgres/manager.py @@ -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. diff --git a/trove/guestagent/datastore/postgres/service.py b/trove/guestagent/datastore/postgres/service.py index ebce60f45d..52e7ae4aef 100644 --- a/trove/guestagent/datastore/postgres/service.py +++ b/trove/guestagent/datastore/postgres/service.py @@ -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"}, diff --git a/trove/guestagent/datastore/service.py b/trove/guestagent/datastore/service.py index 5d2a9f85ac..aabba562ce 100644 --- a/trove/guestagent/datastore/service.py +++ b/trove/guestagent/datastore/service.py @@ -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 diff --git a/trove/guestagent/strategies/replication/postgresql.py b/trove/guestagent/strategies/replication/postgresql.py index 19c6d8ff50..735ddc5b61 100644 --- a/trove/guestagent/strategies/replication/postgresql.py +++ b/trove/guestagent/strategies/replication/postgresql.py @@ -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")