2121import org .apache .fluss .client .ConnectionFactory ;
2222import org .apache .fluss .client .FlussConnection ;
2323import org .apache .fluss .client .admin .Admin ;
24+ import org .apache .fluss .client .admin .FlussAdmin ;
2425import org .apache .fluss .client .table .Table ;
2526import org .apache .fluss .client .table .scanner .batch .BatchScanner ;
2627import org .apache .fluss .client .table .writer .AppendWriter ;
3334import org .apache .fluss .config .cluster .AlterConfigOpType ;
3435import org .apache .fluss .config .cluster .ConfigEntry ;
3536import org .apache .fluss .exception .AuthorizationException ;
37+ import org .apache .fluss .exception .KvSnapshotNotExistException ;
38+ import org .apache .fluss .exception .LakeTableSnapshotNotExistException ;
39+ import org .apache .fluss .exception .TableNotPartitionedException ;
3640import org .apache .fluss .metadata .DataLakeFormat ;
3741import org .apache .fluss .metadata .DatabaseDescriptor ;
3842import org .apache .fluss .metadata .TableBucket ;
4347import org .apache .fluss .rpc .GatewayClientProxy ;
4448import org .apache .fluss .rpc .RpcClient ;
4549import org .apache .fluss .rpc .gateway .AdminGateway ;
50+ import org .apache .fluss .rpc .gateway .AdminReadOnlyGateway ;
4651import org .apache .fluss .rpc .gateway .CoordinatorGateway ;
4752import org .apache .fluss .rpc .gateway .TabletServerGateway ;
4853import org .apache .fluss .rpc .messages .ControlledShutdownRequest ;
54+ import org .apache .fluss .rpc .messages .GetKvSnapshotMetadataRequest ;
4955import org .apache .fluss .rpc .messages .InitWriterRequest ;
5056import org .apache .fluss .rpc .messages .InitWriterResponse ;
5157import org .apache .fluss .rpc .messages .MetadataRequest ;
6066import org .apache .fluss .security .acl .Resource ;
6167import org .apache .fluss .security .acl .ResourceFilter ;
6268import org .apache .fluss .server .testutils .FlussClusterExtension ;
69+ import org .apache .fluss .server .zk .ZooKeeperClient ;
70+ import org .apache .fluss .server .zk .data .TableRegistration ;
6371import org .apache .fluss .shaded .guava32 .com .google .common .collect .Lists ;
6472import org .apache .fluss .utils .CloseableIterator ;
6573
74+ import org .assertj .core .api .ThrowableAssert ;
6675import org .junit .jupiter .api .AfterEach ;
6776import org .junit .jupiter .api .BeforeEach ;
6877import org .junit .jupiter .api .Test ;
6978import org .junit .jupiter .api .extension .RegisterExtension ;
79+ import org .junit .jupiter .params .ParameterizedTest ;
80+ import org .junit .jupiter .params .provider .ValueSource ;
7081
7182import java .time .Duration ;
7283import java .util .Arrays ;
7788
7889import static org .apache .fluss .config .ConfigOptions .DATALAKE_FORMAT ;
7990import static org .apache .fluss .record .TestData .DATA1_SCHEMA ;
91+ import static org .apache .fluss .record .TestData .DATA1_SCHEMA_PK ;
8092import static org .apache .fluss .record .TestData .DATA1_TABLE_DESCRIPTOR ;
8193import static org .apache .fluss .record .TestData .DATA1_TABLE_DESCRIPTOR_PK ;
94+ import static org .apache .fluss .record .TestData .DATA1_TABLE_INFO_PK ;
8295import static org .apache .fluss .record .TestData .DATA1_TABLE_PATH ;
8396import static org .apache .fluss .record .TestData .DATA1_TABLE_PATH_PK ;
8497import static org .apache .fluss .security .acl .AccessControlEntry .WILD_CARD_HOST ;
@@ -390,10 +403,26 @@ void testAlterTable() throws Exception {
390403 }
391404
392405 @ Test
393- void testListTables () throws Exception {
406+ void testDescribeTableOperation () throws Exception {
407+ // test describe table operations like:
408+ // 1. listTables
409+ // 2. getTableInfo
410+ // 3. getTableSchema
411+ // 4. getLatestKvSnapshots
412+ // 5. listPartitionInfos
413+ // 6. getLatestLakeSnapshot
414+
415+ // first check call these methods without authorization.
394416 assertThat (guestAdmin .listTables (DATA1_TABLE_PATH_PK .getDatabaseName ()).get ())
395417 .isEqualTo (Collections .emptyList ());
396-
418+ assertNoTableDescribeAuth (() -> guestAdmin .getTableInfo (DATA1_TABLE_PATH_PK ).get ());
419+ assertNoTableDescribeAuth (() -> guestAdmin .getTableSchema (DATA1_TABLE_PATH_PK ).get ());
420+ assertNoTableDescribeAuth (() -> guestAdmin .getLatestKvSnapshots (DATA1_TABLE_PATH_PK ).get ());
421+ assertNoTableDescribeAuth (() -> guestAdmin .listPartitionInfos (DATA1_TABLE_PATH_PK ).get ());
422+ assertNoTableDescribeAuth (
423+ () -> guestAdmin .getLatestLakeSnapshot (DATA1_TABLE_PATH_PK ).get ());
424+
425+ // add acl to allow guest describe table resource
397426 List <AclBinding > aclBindings =
398427 Collections .singletonList (
399428 new AclBinding (
@@ -405,8 +434,70 @@ void testListTables() throws Exception {
405434 PermissionType .ALLOW )));
406435 rootAdmin .createAcls (aclBindings ).all ().get ();
407436 FLUSS_CLUSTER_EXTENSION .waitUntilAuthenticationSync (aclBindings , true );
437+
438+ // check call these methods with authorization.
408439 assertThat (guestAdmin .listTables (DATA1_TABLE_PATH_PK .getDatabaseName ()).get ())
409440 .isEqualTo (Collections .singletonList (DATA1_TABLE_PATH_PK .getTableName ()));
441+ assertThat (guestAdmin .getTableInfo (DATA1_TABLE_PATH_PK ).get ().getTablePath ())
442+ .isEqualTo (DATA1_TABLE_INFO_PK .getTablePath ());
443+ assertThat (guestAdmin .getTableSchema (DATA1_TABLE_PATH_PK ).get ().getSchema ())
444+ .isEqualTo (DATA1_SCHEMA_PK );
445+ assertThat (guestAdmin .tableExists (DATA1_TABLE_PATH_PK ).get ()).isTrue ();
446+ assertThat (guestAdmin .getLatestKvSnapshots (DATA1_TABLE_PATH_PK ).get ().getBucketIds ())
447+ .containsExactlyInAnyOrder (0 , 1 , 2 );
448+ assertThatThrownBy (() -> guestAdmin .listPartitionInfos (DATA1_TABLE_PATH_PK ).get ())
449+ .rootCause ()
450+ .isInstanceOf (TableNotPartitionedException .class )
451+ .hasMessageContaining (
452+ "Table 'test_db_1.test_pk_table_1' is not a partitioned table." );
453+ assertThatThrownBy (() -> guestAdmin .getLatestLakeSnapshot (DATA1_TABLE_PATH_PK ).get ())
454+ .rootCause ()
455+ .isInstanceOf (LakeTableSnapshotNotExistException .class )
456+ .hasMessageContaining ("Lake table snapshot not exist for table" );
457+ }
458+
459+ @ ParameterizedTest
460+ @ ValueSource (strings = {"CoordinatorServer" , "TabletServer" })
461+ void testGetKvSnapshotMetadata (String serverType ) throws Exception {
462+ AdminReadOnlyGateway readOnlyGateway ;
463+ if (serverType .equals ("CoordinatorServer" )) {
464+ readOnlyGateway = ((FlussAdmin ) guestAdmin ).getAdminGateway ();
465+ } else {
466+ readOnlyGateway = ((FlussAdmin ) guestAdmin ).getAdminReadOnlyGateway ();
467+ }
468+
469+ ZooKeeperClient zooKeeperClient = FLUSS_CLUSTER_EXTENSION .getZooKeeperClient ();
470+ TableRegistration tableRegistration = zooKeeperClient .getTable (DATA1_TABLE_PATH_PK ).get ();
471+ long tableId = tableRegistration .tableId ;
472+ FLUSS_CLUSTER_EXTENSION .waitUntilTableReady (tableId );
473+
474+ GetKvSnapshotMetadataRequest request = new GetKvSnapshotMetadataRequest ();
475+ request .setTableId (tableId ).setBucketId (0 ).setSnapshotId (0 );
476+ // Make sure all tabletServer has ready replica and ready metadata for the table.
477+ FLUSS_CLUSTER_EXTENSION .waitUntilAllReplicaReady (new TableBucket (tableId , 0 ));
478+
479+ // call getKvSnapshotMetadata without authorization.
480+ assertNoTableDescribeAuth (() -> readOnlyGateway .getKvSnapshotMetadata (request ).get ());
481+
482+ // add acl to allow guest describe table resource
483+ List <AclBinding > aclBindings =
484+ Collections .singletonList (
485+ new AclBinding (
486+ Resource .database (DATA1_TABLE_PATH_PK .getDatabaseName ()),
487+ new AccessControlEntry (
488+ guestPrincipal ,
489+ "*" ,
490+ OperationType .DESCRIBE ,
491+ PermissionType .ALLOW )));
492+ rootAdmin .createAcls (aclBindings ).all ().get ();
493+ FLUSS_CLUSTER_EXTENSION .waitUntilAuthenticationSync (aclBindings , true );
494+
495+ // call getKvSnapshotMetadata with authorization. no authorization exception should be
496+ // thrown.
497+ assertThatThrownBy (() -> readOnlyGateway .getKvSnapshotMetadata (request ).get ())
498+ .rootCause ()
499+ .isInstanceOf (KvSnapshotNotExistException .class )
500+ .hasMessageContaining ("Failed to get kv snapshot metadata for table bucket" );
410501 }
411502
412503 @ Test
@@ -572,9 +663,9 @@ void testProduceWithNoWriteAuthorization() throws Exception {
572663 .rootCause ()
573664 .hasMessageContaining (
574665 String .format (
575- "No permission to WRITE table %s in database %s" ,
576- noWriteAclTable . getTableName () ,
577- noWriteAclTable . getDatabaseName () ));
666+ "Principal FlussPrincipal{name='guest', type='User'} have no authorization to "
667+ + "operate WRITE on resource Resource{type=TABLE, name='%s'} " ,
668+ noWriteAclTable ));
578669 }
579670 }
580671
@@ -609,10 +700,9 @@ void testProduceAndConsumer() throws Exception {
609700 assertThatThrownBy (() -> batchScanner .pollBatch (Duration .ofMinutes (1 )))
610701 .hasMessageContaining (
611702 String .format (
612- "No permission to %s table %s in database %s" ,
613- READ ,
614- DATA1_TABLE_PATH .getTableName (),
615- DATA1_TABLE_PATH .getDatabaseName ()));
703+ "Principal FlussPrincipal{name='guest', type='User'} have no authorization to "
704+ + "operate %s on resource Resource{type=TABLE, name='%s'}" ,
705+ READ , DATA1_TABLE_PATH ));
616706 }
617707 rootAdmin
618708 .createAcls (
@@ -955,4 +1045,13 @@ private static Configuration initConfig() {
9551045 conf .set (ConfigOptions .AUTHORIZER_ENABLED , true );
9561046 return conf ;
9571047 }
1048+
1049+ private void assertNoTableDescribeAuth (ThrowableAssert .ThrowingCallable callable ) {
1050+ assertThatThrownBy (callable )
1051+ .cause ()
1052+ .isInstanceOf (AuthorizationException .class )
1053+ .hasMessageContaining (
1054+ "Principal FlussPrincipal{name='guest', type='User'} have no authorization to "
1055+ + "operate DESCRIBE on resource Resource{type=TABLE, name='test_db_1.test_pk_table_1'}" );
1056+ }
9581057}
0 commit comments