morningman commented on code in PR #68220:
URL: https://github.com/apache/doris/pull/68220#discussion_r4069578669


##########
fe/fe-authorization/fe-authorization-plugins/fe-authorization-plugin-ranger-hive/src/main/java/org/apache/doris/catalog/authorizer/ranger/hive/RangerHiveAccessController.java:
##########
@@ -343,6 +343,9 @@ private RangerAccessRequestImpl 
createRequest(AuthorizedSubject subject, Set<Str
             AccessContext context) {
         RangerAccessRequestImpl request = new RangerAccessRequestImpl();
         request.setUser(subject.getUser());
+        // Ranger's groups for the user, out of the user store this service's 
plugin downloads; a Hive

Review Comment:
   Not in this PR. Ranger derives roles only when the request carries none, and 
ranger-hive has sent the Doris roles since it existed: Ranger roles held by the 
user have been suppressed for any user with a Doris role in master today, and 
group-held Ranger roles now behave the same way. Merging Ranger's roles into 
the Doris roles changes what a request matches for every user that has a Doris 
role - new grants and new denies on role items that never matched - which is 
exactly the change the ranger-doris request builder's comment keeps out of this 
PR. A follow-up with its own release note.
   



##########
fe/fe-authorization/fe-authorization-plugins/fe-authorization-plugin-ranger-common/src/main/java/org/apache/doris/catalog/authorizer/ranger/LoadedRangerPlugin.java:
##########
@@ -0,0 +1,130 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.catalog.authorizer.ranger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.util.ServicePolicies;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * What the two Ranger plugins Doris embeds have in common: they are built 
with their service's policies or
+ * not at all, and the requests built over them carry the groups Ranger keeps 
for a user.
+ *
+ * <p><b>Built with the policies, or not at all.</b> {@code 
RangerBasePlugin.init()} downloads the service's
+ * roles and policies - and, with the enricher below on them, its user store - 
before it returns, and it
+ * survives a Ranger admin it cannot reach: it answers out of the local policy 
cache
+ * ({@code ranger.plugin.<type>.policy.cache.dir}) when there is one, and out 
of no policies at all when there
+ * is not, refusing every check until a later poll succeeds. That is the right 
thing for a plugin embedded in
+ * a service that has to keep running, and the wrong thing at the moment Doris 
binds one: an FE whose instance
+ * scope {@code access_controller_type=ranger-doris} governs, and which has no 
policies, is an FE nobody can
+ * use - no account bypasses the source - and a catalog bound to a {@code 
ranger-hive} source in that state
+ * refuses every statement against it, in both cases with nothing but a line 
in fe.log to say why. So
+ * {@link #init()} refuses that state instead: a load that ended with no 
policies, from the admin or the
+ * cache, stops what it started and throws, with the cause. That fails the FE 
start, the {@code CREATE
+ * CATALOG} (whose dry run builds the plugin), or the statement binding a 
catalog to its source again, which
+ * is where an operator sees it. Once built, an outage of the admin is 
Ranger's to survive, as it always
+ * was: the refresher keeps the last policies it downloaded and keeps polling.
+ *
+ * <p>The user store is not part of that. A load that found the policies but 
no user store is logged and
+ * accepted - policy items written against a group do not apply until the 
store arrives, which the enricher
+ * keeps asking for - because "no store has arrived" cannot be told apart from 
"this admin serves none", and
+ * an admin from before the user store download existed, or one that fails 
that download while serving the
+ * policies, ran every deployment before groups were attached at all; see 
{@link RangerUserStoreGroups}.
+ *
+ * <p><b>Requests carry Ranger's own groups.</b> Doris has none to offer, and 
a policy item written against a
+ * group matches nothing without them; the store they are read from is asked 
for with the policies, see
+ * {@link #setPolicies} and {@link RangerUserStoreGroups}.
+ */
+public abstract class LoadedRangerPlugin extends RangerBasePlugin {
+    private static final Logger LOG = 
LogManager.getLogger(LoadedRangerPlugin.class);
+
+    protected LoadedRangerPlugin(String serviceType, String serviceName, 
String appId) {
+        super(serviceType, serviceName, appId);
+    }
+
+    /**
+     * Loads the plugin - {@code RangerBasePlugin.init()}: roles, policies and 
user store, on this thread -
+     * and refuses to leave it without policies; see the class comment.
+     *
+     * @throws IllegalStateException when the load ended with no policies from 
either the admin or the cache
+     */
+    @Override
+    public void init() {
+        LOG.info(RangerUserStoreGroups.describe(getConfig()));
+        long startedAtNanos = System.nanoTime();
+        try {
+            super.init();
+            if (getPoliciesVersion() < 0) {

Review Comment:
   Fixed in 7ca5ef5c49a, in two parts. The interval is validated before the 
load (see the thread below), so the `often` path no longer reaches the engine. 
And `LoadedRangerPlugin.setPolicies` now notes when a versioned payload left 
the plugin without an engine - anything the engine's construction throws lands 
there - so the refusal reads "policies (version N) were downloaded, but no 
policy engine could be built out of them; RangerBasePlugin.setPolicies logged 
the cause" instead of blaming the admin or the cache. 
`LoadedRangerPluginTest.testRefusedWithPoliciesNoEngineCouldBeBuiltOutOf` 
drives it with a context enricher whose `init()` throws.
   



##########
fe/fe-authorization/fe-authorization-plugins/fe-authorization-plugin-ranger-common/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerUserStoreGroups.java:
##########
@@ -0,0 +1,142 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.catalog.authorizer.ranger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.contextenricher.RangerAdminUserStoreRetriever;
+import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
+import org.apache.ranger.plugin.policyengine.RangerPluginContext;
+import org.apache.ranger.plugin.service.RangerAuthContext;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.util.RangerUserStoreUtil;
+import org.apache.ranger.plugin.util.ServiceDefUtil;
+import org.apache.ranger.plugin.util.ServicePolicies;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Where a request built by a Doris source gets the groups a Ranger policy 
item may be written against.
+ *
+ * <p>A Ranger policy item names users, groups and roles, and Ranger's own 
plugins fill in the groups from
+ * the service they run in - the Hive plugin asks Hadoop's group mapping. 
Doris has nothing to ask: an
+ * account holds roles, and no part of the engine knows which groups a user is 
in. So a request that carries
+ * no groups matches no group item at all, and a service whose policies are 
kept by group - the normal way
+ * to run Ranger, so that nobody edits a policy each time somebody joins a 
team - grants nothing to a Doris
+ * user; and, worse, denies nothing either. An item that denies a group is 
exactly as silent about it.
+ *
+ * <p>What Ranger provides for a plugin in that position is its <b>user 
store</b>: the users and groups Ranger
+ * Admin itself holds, kept current by usersync from LDAP or the OS, which a 
plugin downloads next to its
+ * policies through a {@link RangerUserStoreEnricher}. Ranger 2.5 and later 
reads the groups of the request's
+ * user out of it when {@code ranger.plugin.<type>.use.rangerGroups} is set; 
the two sources Doris embeds do
+ * the same thing here, in the request builder, so that it neither depends on 
the Ranger version they were
+ * built against - branches still on 2.4 have no such setting - nor on an 
operator finding a Ranger property
+ * no Doris document names.
+ *
+ * <p>It is on by default, because a group item that is silently ignored is a 
bug in the deployment's eyes,
+ * not a behaviour anybody opted into; the property Ranger reads for this is 
the one that switches it off,
+ * so that a deployment which has already decided the question in Ranger's 
terms has decided it here too.
+ * Switched off, requests carry no groups and this source matches what it 
matched before.
+ */
+public final class RangerUserStoreGroups {
+    private static final Logger LOG = 
LogManager.getLogger(RangerUserStoreGroups.class);
+
+    /**
+     * Suffix of the property switching this off, {@code 
ranger.plugin.<type>.use.rangerGroups} in full: the
+     * name Ranger itself reads for the same thing, so that one setting 
decides both.
+     */
+    public static final String USE_RANGER_GROUPS = ".use.rangerGroups";
+
+    /** How often the plugin asks Ranger Admin for a newer user store, when 
nothing configures it. */
+    private static final String DEFAULT_REFRESH_INTERVAL_MS = 
Integer.toString(60 * 1000);
+
+    private RangerUserStoreGroups() {
+    }
+
+    /** Whether the plugin configured by {@code config} attaches user store 
groups; on unless switched off. */
+    public static boolean enabledFor(RangerPluginConfig config) {
+        // No configuration at all - a plugin stubbed out in a test - has not 
switched anything off.
+        return config == null || config.getBoolean(config.getPropertyPrefix() 
+ USE_RANGER_GROUPS, true);
+    }
+
+    /**
+     * Puts a user store enricher on the service definition Ranger just 
downloaded, unless one is there or
+     * this is switched off, so that the plugin fetches and refreshes the user 
store {@link #groupsOf} reads.
+     *
+     * <p>Called by {@link LoadedRangerPlugin#setPolicies} before handing the 
policies on, on every
+     * call and not only the first: a download of policy deltas comes with its 
own copy of the service
+     * definition, which is why {@code RangerBasePlugin} re-adds the enricher 
on deltas too. The retriever
+     * class and the refresh interval are read under the option names Ranger 
itself reads them under, so that
+     * an operator who has tuned them for Ranger's own {@code 
use.rangerGroups} has tuned them here.
+     */
+    public static void addUserStoreEnricher(RangerPluginConfig config, 
ServicePolicies policies) {
+        if (policies == null || config == null || !enabledFor(config)) {
+            return;
+        }
+        String retriever = 
config.get(RangerUserStoreEnricher.USERSTORE_RETRIEVER_CLASSNAME_OPTION,
+                RangerAdminUserStoreRetriever.class.getCanonicalName());
+        String refreshIntervalMs = 
config.get(RangerUserStoreEnricher.USERSTORE_REFRESHER_POLLINGINTERVAL_OPTION,

Review Comment:
   Fixed in 7ca5ef5c49a. `userStoreRefresherPollingInterval` must be a positive 
number of milliseconds, checked in `RangerUserStoreGroups.validate()` before 
`RangerBasePlugin.init()` starts anything - so neither the store download nor 
the refresher thread exists when a bad value is refused. 
`RangerUserStoreGroupsTest.testRefusesAnIntervalThatIsNotAPositiveNumberOfMilliseconds`
 covers `0`, `-1`, `often` and `5s`; 
`LoadedRangerPluginTest.testRefusedBeforeTheLoadForAnIntervalTheEnricherCouldNotSchedule`
 checks that with `0` there is no engine, no policy refresher and no 
`RangerUserStoreRefresher` thread afterwards.
   



##########
fe/fe-authorization/fe-authorization-plugins/fe-authorization-plugin-ranger-common/src/main/java/org/apache/doris/catalog/authorizer/ranger/RangerUserStoreGroups.java:
##########
@@ -0,0 +1,142 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+package org.apache.doris.catalog.authorizer.ranger;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.contextenricher.RangerAdminUserStoreRetriever;
+import org.apache.ranger.plugin.contextenricher.RangerUserStoreEnricher;
+import org.apache.ranger.plugin.policyengine.RangerPluginContext;
+import org.apache.ranger.plugin.service.RangerAuthContext;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.util.RangerUserStoreUtil;
+import org.apache.ranger.plugin.util.ServiceDefUtil;
+import org.apache.ranger.plugin.util.ServicePolicies;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Where a request built by a Doris source gets the groups a Ranger policy 
item may be written against.
+ *
+ * <p>A Ranger policy item names users, groups and roles, and Ranger's own 
plugins fill in the groups from
+ * the service they run in - the Hive plugin asks Hadoop's group mapping. 
Doris has nothing to ask: an
+ * account holds roles, and no part of the engine knows which groups a user is 
in. So a request that carries
+ * no groups matches no group item at all, and a service whose policies are 
kept by group - the normal way
+ * to run Ranger, so that nobody edits a policy each time somebody joins a 
team - grants nothing to a Doris
+ * user; and, worse, denies nothing either. An item that denies a group is 
exactly as silent about it.
+ *
+ * <p>What Ranger provides for a plugin in that position is its <b>user 
store</b>: the users and groups Ranger
+ * Admin itself holds, kept current by usersync from LDAP or the OS, which a 
plugin downloads next to its
+ * policies through a {@link RangerUserStoreEnricher}. Ranger 2.5 and later 
reads the groups of the request's
+ * user out of it when {@code ranger.plugin.<type>.use.rangerGroups} is set; 
the two sources Doris embeds do
+ * the same thing here, in the request builder, so that it neither depends on 
the Ranger version they were
+ * built against - branches still on 2.4 have no such setting - nor on an 
operator finding a Ranger property
+ * no Doris document names.
+ *
+ * <p>It is on by default, because a group item that is silently ignored is a 
bug in the deployment's eyes,

Review Comment:
   Doris documentation lives in apache/doris-website; the checklist says this 
needs docs and names the page (`ranger.md`: group-based policies and the two 
properties). The docs PR follows the code, as for every Doris change; there is 
nothing to add to this repository.
   



##########
regression-test/suites/ranger_p2/test_ranger_group_policy.groovy:
##########
@@ -0,0 +1,212 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.
+
+import org.apache.ranger.RangerClient
+import org.apache.ranger.plugin.model.RangerPolicy
+
+// Policies written against a Ranger GROUP and never against the user: how a 
deployment runs Ranger so that
+// nobody edits a policy each time somebody joins a team. Doris has no groups 
of its own, so these apply
+// only because the ranger-doris source reads the user's groups out of 
Ranger's user store - the store the
+// plugin downloads next to its policies. Every other suite here writes its 
policy items against a user.
+suite("test_ranger_group_policy", "p2,ranger,external") {
+       String enabled = context.config.otherConfigs.get("enableRangerTest")
+       String rangerEndpoint = 
context.config.otherConfigs.get("rangerEndpoint")
+       String rangerUser = context.config.otherConfigs.get("rangerUser")
+       String rangerPassword = 
context.config.otherConfigs.get("rangerPassword")
+       String rangerServiceName = 
context.config.otherConfigs.get("rangerServiceName")
+
+       if (enabled == null || !enabled.equalsIgnoreCase("true")) {
+               return
+       }
+
+       String db = 'ranger_group_db_1'
+       String table = 'ranger_group_tbl_1'
+       // The same database, no policy of any kind: what the group's SELECT 
must not open.
+       String otherTable = 'ranger_group_tbl_2'
+       String user = 'ranger_group_user_1'
+       String pwd = 'C123_567p'
+       String group = 'ranger_group_readers_1'
+       String accessPolicy = 'ranger_test_group_access_policy'
+       String rowFilterPolicy = 'ranger_test_group_row_filter_policy'
+       String maskPolicy = 'ranger_test_group_mask_policy'
+       String denyPolicy = 'ranger_test_group_deny_policy'
+       // A table the user is allowed on by name while the group is denied on 
it; the deny has to win.
+       String deniedTable = 'ranger_group_tbl_3'
+
+       sql """CREATE DATABASE IF NOT EXISTS ${db}"""
+       [table, otherTable, deniedTable].each {
+               sql """DROP TABLE IF EXISTS ${db}.${it}"""
+               sql """CREATE TABLE ${db}.${it} (
+                       id BIGINT,
+                       c1 VARCHAR(20),
+                       c2 VARCHAR(20)
+               )
+               DISTRIBUTED BY HASH(id) BUCKETS 2
+               PROPERTIES (
+                       "replication_num" = "1"
+               )"""
+               sql """INSERT INTO ${db}.${it} VALUES
+               (1, 'DataOne01', 'SampleA1'),
+               (2, 'DataTwo02', 'SampleB2'),
+               (3, 'DataThr03', 'SampleC3'),
+               (4, 'DataFou04', 'SampleD4'),
+               (5, 'DataFiv05', 'SampleE5'),
+               (6, 'DataSix06', 'SampleF6'),
+               (7, 'DataSev07', 'SampleG7'),
+               (8, 'DataEig08', 'SampleH8'),
+               (9, 'DataNin09', 'SampleI9'),
+               (10, 'DataTen10', 'SampleJ0')"""
+       }
+       sql """DROP USER IF EXISTS ${user}"""
+       sql """CREATE USER '${user}' IDENTIFIED BY '${pwd}'"""
+
+       // Ranger's side of the same names: the group, the user, and the 
membership. Nothing syncs users between
+       // Doris and Ranger, so the names are simply kept equal, as the other 
suites do for users.
+       createRangerGroup(group)
+       createRangerUser(user, pwd, ["ROLE_USER"] as String[])
+       setRangerUserGroups(user, [group])
+
+       RangerClient rangerClient = new 
RangerClient("http://${rangerEndpoint}";, "simple", rangerUser, rangerPassword, 
null)
+       def dropPolicy = { String name ->
+               try {
+                       rangerClient.deletePolicy(rangerServiceName, name)
+               } catch (Exception e) {
+                       log.info("Policy not found: ${e.getMessage()}")
+               }
+       }
+       [accessPolicy, rowFilterPolicy, maskPolicy, denyPolicy].each { 
dropPolicy(it) }
+
+       def resourcesOf = { String tbl, String column ->
+               Map<String, RangerPolicy.RangerPolicyResource> resources = new 
HashMap<>()
+               resources.put("catalog", new 
RangerPolicy.RangerPolicyResource("internal"))
+               resources.put("database", new 
RangerPolicy.RangerPolicyResource(db))
+               resources.put("table", new 
RangerPolicy.RangerPolicyResource(tbl))
+               if (column != null) {
+                       resources.put("column", new 
RangerPolicy.RangerPolicyResource(column))
+               }
+               return resources
+       }
+       def select = [new RangerPolicy.RangerPolicyItemAccess("SELECT")]
+
+       // 1. Access: SELECT on the table for the group.
+       RangerPolicy policy = new RangerPolicy()
+       policy.setService(rangerServiceName)
+       policy.setName(accessPolicy)
+       policy.setResources(resourcesOf(table, null))
+       RangerPolicy.RangerPolicyItem accessItem = new 
RangerPolicy.RangerPolicyItem()
+       accessItem.setGroups([group])
+       accessItem.setAccesses(select)
+       policy.setPolicyItems([accessItem])
+       rangerClient.createPolicy(policy)
+
+       // 2. A row filter for the group.
+       policy = new RangerPolicy()
+       policy.setService(rangerServiceName)
+       policy.setName(rowFilterPolicy)
+       policy.setPolicyType(RangerPolicy.POLICY_TYPE_ROWFILTER)
+       policy.setResources(resourcesOf(table, null))
+       RangerPolicy.RangerRowFilterPolicyItem rowFilterItem = new 
RangerPolicy.RangerRowFilterPolicyItem()
+       rowFilterItem.setGroups([group])
+       rowFilterItem.setAccesses(select)
+       rowFilterItem.setRowFilterInfo(new 
RangerPolicy.RangerPolicyItemRowFilterInfo("id >= 5"))
+       policy.setRowFilterPolicyItems([rowFilterItem])
+       rangerClient.createPolicy(policy)
+
+       // 3. A column mask for the group.
+       policy = new RangerPolicy()
+       policy.setService(rangerServiceName)
+       policy.setName(maskPolicy)
+       policy.setPolicyType(RangerPolicy.POLICY_TYPE_DATAMASK)
+       policy.setResources(resourcesOf(table, "c1"))
+       RangerPolicy.RangerDataMaskPolicyItem maskItem = new 
RangerPolicy.RangerDataMaskPolicyItem()
+       maskItem.setGroups([group])
+       maskItem.setAccesses(select)
+       maskItem.setDataMaskInfo(new 
RangerPolicy.RangerPolicyItemDataMaskInfo("MASK_SHOW_LAST_4", null, null))
+       policy.setDataMaskPolicyItems([maskItem])
+       rangerClient.createPolicy(policy)
+
+       // 4. A deny written against the group, on a table the user is allowed 
on by name. Before groups were
+       // attached this deny was silently ignored and the user read the table 
- the dangerous half of the bug.
+       policy = new RangerPolicy()
+       policy.setService(rangerServiceName)
+       policy.setName(denyPolicy)
+       policy.setResources(resourcesOf(deniedTable, null))
+       RangerPolicy.RangerPolicyItem allowUserItem = new 
RangerPolicy.RangerPolicyItem()
+       allowUserItem.setUsers([user])
+       allowUserItem.setAccesses(select)
+       policy.setPolicyItems([allowUserItem])
+       RangerPolicy.RangerPolicyItem denyGroupItem = new 
RangerPolicy.RangerPolicyItem()
+       denyGroupItem.setGroups([group])
+       denyGroupItem.setAccesses(select)
+       policy.setDenyPolicyItems([denyGroupItem])
+       rangerClient.createPolicy(policy)
+
+       def tokens = context.config.jdbcUrl.split('/')
+       def defaultJdbcUrl = tokens[0] + "//" + tokens[2] + "/?"
+       def readable = { String tbl ->
+               return connect("${user}", "${pwd}", "${defaultJdbcUrl}") {
+                       try {
+                               sql """SELECT * FROM internal.${db}.${tbl}"""
+                               return true
+                       } catch (Exception e) {
+                               log.info("not readable yet: ${e.getMessage()}")
+                               return false
+                       }
+               }
+       }
+
+       // The policies reach the FE within its policy poll interval; the 
membership reaches it with the next
+       // user store download, which the plugin makes every 60 seconds unless 
userStoreRefresherPollingInterval
+       // in ranger-doris-security.xml says otherwise. Hence waiting on the 
effect rather than a fixed sleep.
+       logger.info("waiting for the group's SELECT to reach ${user}")
+       awaitUntil(180, 3) { readable(table) }

Review Comment:
   Fixed in 7ca5ef5c49a. The access policy is now created last: neither a row 
filter, a mask nor a deny grants anything, so `readable(table)` turning true 
proves the FE holds the generation the other three are in. No extra probe 
needed; the comment above the four `createPolicy` calls says why the order 
matters.
   



-- 
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]

Reply via email to