dengzhhu653 commented on code in PR #6884:
URL: https://github.com/apache/hive/pull/6884#discussion_r4462566501
##########
standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/TransactionHandler.java:
##########
@@ -505,9 +528,195 @@ public ShowCompactResponse
show_compact(ShowCompactRequest rqst) throws TExcepti
@Override
public boolean submit_for_cleanup(CompactionRequest rqst, long
highestWriteId, long txnId)
throws TException {
+ if (!hasProxyPrivilege()) {
+ try {
+ if (!authorizeAndPrepareCompactionRequest(rqst, true)) {
+ return false;
+ }
+ } catch (NoSuchObjectException e) {
+ // The table/partition (or the database) does not exist: there is
nothing an untrusted caller may clean up.
+ LOG.info("Ignoring the cleanup request of {}.{}: {}",
rqst.getDbname(), rqst.getTablename(),
+ e.getMessage());
+ return false;
+ }
+ }
return getTxnHandler().submitForCleanup(rqst, highestWriteId, txnId);
}
+ /**
+ * Authorize a compaction/cleanup request of a caller that has no proxy
privilege and derive the
+ * server-side request attributes: the table is authorized with a {@link
PreAlterTableEvent}, the
+ * canonical db/table names are used for the queued request, the run-as user
is derived from the
+ * storage location and a caller-supplied cleanup location must stay inside
the table/partition location.
+ *
+ * <p>A cleanup request ({@code cleanup == true}) may also target the
location of a table that does not exist (yet),
+ * e.g. the rollback of a failed CTAS. Such a request is only accepted when
the location is the default managed
+ * location of the table (see {@link #authorizeCleanupOfMissingTable}).
+ *
+ * @return {@code true} if the request is authorized and prepared, {@code
false} if it has to be ignored
+ * @throws NoSuchObjectException if the table or the partition does not exist
+ */
+ private boolean authorizeAndPrepareCompactionRequest(CompactionRequest rqst,
boolean cleanup)
+ throws TException {
+ if (rqst.getDbname() == null || rqst.getTablename() == null) {
+ throw new MetaException("The DB and table name cannot be null.");
+ }
+ String[] parsedDbName = parseDbName(rqst.getDbname(), conf);
+ String catName = parsedDbName[CAT_NAME];
+ String dbName = parsedDbName[DB_NAME];
+ if (!getDefaultCatalog(conf).equalsIgnoreCase(catName)) {
+ // the compaction queue has no notion of catalogs, so the request would
end up on the default catalog
+ throw new MetaException("Compaction and cleanup requests are only
supported for the default catalog, got "
+ + catName);
+ }
+
+ GetTableRequest getTableRequest = new GetTableRequest(dbName,
rqst.getTablename());
+ getTableRequest.setCatName(catName);
+ Table table;
+ try {
+ table = get_table_core(getTableRequest);
+ } catch (NoSuchObjectException e) {
+ if (!cleanup) {
+ throw e;
+ }
+ return authorizeCleanupOfMissingTable(rqst, catName, dbName, e);
+ }
+ // the listeners (e.g. the transactional validation) may modify the new
table, so the old state must be kept
+ firePreEvent(new PreAlterTableEvent(table, table.deepCopy(), this));
+
+ // queue the request for the table that has been authorized
+ rqst.setDbname(table.getDbName());
+ rqst.setTablename(table.getTableName());
+
+ String location = table.getSd() == null ? null :
table.getSd().getLocation();
+ // The partitions of non-native tables (e.g. Iceberg) are not stored in
HMS: they are resolved by the storage
+ // handler, so such requests are authorized and validated against the
table.
+ if (rqst.isSetPartitionname() && rqst.getPartitionname() != null &&
!MetaStoreUtils.isNonNativeTable(table)) {
+ List<String> partVals = getPartValsFromName(table,
rqst.getPartitionname());
+ Partition partition = getMS().getPartition(catName, table.getDbName(),
table.getTableName(), partVals);
+ // queue the canonical partition name, the Cleaner looks the partition
up by it
+ rqst.setPartitionname(Warehouse.makePartName(table.getPartitionKeys(),
partition.getValues()));
+ location = partition.getSd().getLocation();
+ }
+ if (location == null) {
+ throw new MetaException("Cannot determine storage location of " +
table.getDbName() + "."
+ + table.getTableName() + "; compaction is not supported for this
table or partition");
+ }
+
+ String requestedLocation = rqst.getProperties() == null ? null :
rqst.getProperties().get(META_TABLE_LOCATION);
+ if (requestedLocation != null) {
+ // The Cleaner purges the supplied location as is, so it must not point
outside the authorized table
+ Path requested = normalizePath(new Path(requestedLocation));
+ if (!FileUtils.isPathWithinSubtree(requested, normalizePath(new
Path(location)))) {
+ throw new MetaException("Cleanup location " + requestedLocation + " is
not inside the location of "
+ + table.getDbName() + "." + table.getTableName());
+ }
+ rqst.putToProperties(META_TABLE_LOCATION, requested.toString());
+ }
+ try {
+ rqst.setRunas(TxnUtils.findUserToRunAs(location, table, conf));
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new MetaException("Interrupted while determining the run-as user
of the compaction request");
+ } catch (IOException e) {
+ throw new MetaException("Cannot determine the run-as user of the
compaction request: " + e.getMessage());
+ }
+ return true;
+ }
+
+ /**
+ * Authorize the cleanup of the location of a table that does not exist,
e.g. the rollback of a failed CTAS, which
+ * leaves the data behind but never creates the table.
+ * <p>The request is only accepted when the location is the default managed
location of the table, which is
+ * constructed from the database and the table name, or the table location
of a soft-delete (lockless reads) table,
+ * i.e. the default location with the {@code .v<txnId>} suffix;
sub-directories of both are accepted as well.
+ * Since the table does not exist, nobody else owns that location, and the
caller is authorized to alter
+ * a table of that name (a placeholder table is used for the authorization
event).
+ *
+ * @return {@code true} if the request is authorized and prepared, {@code
false} if it has to be ignored
+ * @throws NoSuchObjectException if the request is not a cleanup of a
location or the database does not exist
+ */
+ private boolean authorizeCleanupOfMissingTable(CompactionRequest rqst,
String catName, String dbName,
+ NoSuchObjectException notFound) throws TException {
+ String requestedLocation = rqst.getProperties() == null ? null :
rqst.getProperties().get(META_TABLE_LOCATION);
+ if (requestedLocation == null || (rqst.isSetPartitionname() &&
rqst.getPartitionname() != null)) {
+ throw notFound;
+ }
+ if (!MetaStoreUtils.validateName(rqst.getTablename(), conf)) {
+ LOG.warn("Ignoring the cleanup request of {}.{}: invalid table name",
rqst.getDbname(), rqst.getTablename());
+ return false;
+ }
+ Database db = get_database_core(catName, dbName);
+ String tableName = rqst.getTablename().toLowerCase();
+ Path managedTablePath =
normalizePath(getWh().getDefaultManagedTablePath(db, tableName));
+ Path requested = normalizePath(new Path(requestedLocation));
+ if (!isManagedLocationOfTable(requested, managedTablePath)) {
+ LOG.warn("Ignoring the cleanup request of {}.{}: the location {} is not
inside the managed location {} "
+ + "of the table", db.getName(), tableName, requested,
managedTablePath);
+ return false;
+ }
+
+ // The table does not exist, so a placeholder that looks like a
transactional managed table (the only kind of
+ // table whose data is cleaned up this way) is authorized: the caller
needs the right to alter such a table.
+ Table placeholder = new Table();
+ placeholder.setCatName(catName);
+ placeholder.setDbName(db.getName());
+ placeholder.setTableName(tableName);
+ placeholder.setOwner(db.getOwnerName() != null ? db.getOwnerName() :
getCallerUserName());
+ placeholder.setTableType(TableType.MANAGED_TABLE.name());
+ Map<String, String> parameters = new HashMap<>();
+ parameters.put(hive_metastoreConstants.TABLE_IS_TRANSACTIONAL, "true");
+ parameters.put(hive_metastoreConstants.TABLE_TRANSACTIONAL_PROPERTIES,
"insert_only");
+ placeholder.setParameters(parameters);
+ placeholder.setPartitionKeys(new ArrayList<>());
+ StorageDescriptor sd = new StorageDescriptor();
+ sd.setLocation(managedTablePath.toString());
+ sd.setCols(new ArrayList<>());
+ sd.setSerdeInfo(new SerDeInfo());
+ placeholder.setSd(sd);
+ firePreEvent(new PreAlterTableEvent(placeholder, placeholder.deepCopy(),
this));
Review Comment:
As this table doesn't exist, how about sending a`PreReadDatabase` event?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]