Skip to content

Support wait_for_completion on Security configuration write APIs (#6337) - #6409

Open
nishthm wants to merge 1 commit into
opensearch-project:mainfrom
nishthm:6337-wait-for-completion
Open

Support wait_for_completion on Security configuration write APIs (#6337)#6409
nishthm wants to merge 1 commit into
opensearch-project:mainfrom
nishthm:6337-wait-for-completion

Conversation

@nishthm

@nishthm nishthm commented Aug 17, 2026

Copy link
Copy Markdown

Description

Category: Enhancement

Adds support for the standard wait_for_completion query parameter to Security configuration write APIs. Callers can now submit a configuration change with ?wait_for_completion=false and receive an OpenSearch task ID immediately ({"task":"<nodeId>:<taskId>"}), then poll the outcome via the standard Tasks API (GET /_tasks/{task_id}). Omitting the parameter — or setting it to true — preserves the existing synchronous behavior byte-for-byte.

Why: Security configuration writes today fan out a cluster-wide reload after the index write, and the caller has to hold the HTTP connection open for the entire lifecycle. If the connection drops there's no way to tell whether the change committed. Every other long-running OpenSearch API (reindex, update-by-query, force-merge, open-index…) solves this via the Tasks framework; Security did not. This change brings Security in line with that convention.

Old vs new behavior:

wait_for_completion Behavior
omitted / true (default) Unchanged. Same response body and status code the endpoint returned before this PR.
false Update is submitted through the task framework. HTTP 200 with body {"task":"nodeId:taskId"} is returned as soon as the task is registered — before the index write or fan-out reload has completed.

Task completion boundary. The task completes only after ConfigUpdatingActionListener receives all-node acknowledgements of the reload — exactly the point at which the sync path returns today. Any per-node reload failure surfaces as a task-level error.

Task result payload. The stored task result in .tasks contains only {"status":"...","message":"'entity' created."} — never configuration contents. Standard _tasks authorization is therefore sufficient to satisfy the "does not expose Security configuration contents" acceptance criterion; no security-plugin-specific gating on task lookup was added.

Cancellation. Deliberately not supported. SecurityConfigWriteRequest#createTask returns a plain Task rather than a CancellableTask, so POST /_tasks/{id}/_cancel is rejected by TransportCancelTasksAction with "task [...] doesn't support cancellation". Rationale: a mid-fan-out cancel could leave the index write committed but only a subset of nodes reloaded — worse than letting the request run to completion.

Scope

In this PR — the shared plumbing plus opt-in for the 5 endpoints that a Terraform provider calls when provisioning a cluster:

  • Roles (RolesApiAction)
  • Role mappings (RolesMappingApiAction)
  • Internal users (InternalUsersApiAction)
  • Tenants (TenantsApiAction)
  • Audit (AuditApiAction)

Deferred:

  • ActionGroupsApiAction, SecurityConfigApiAction — same shape (config write + fan-out reload), share the same plumbing, but not called by the Terraform provider. Opt-in is a one-line change per subclass; follow-up PR.
  • AccountApiAction (self-service password change) — explicitly excluded per feedback in the issue thread; async has near-zero value for an interactive endpoint.
  • AllowlistApiAction, NodesDnApiAction, RateLimitersApiAction, MultiTenancyConfigApiAction — same pattern, follow-up as demand appears.

Companion PRs to follow (separate repositories):

  • opensearch-project/documentation-website — document the new query parameter, response shape, task lookup.
  • opensearch-project/opensearch-api-specification — add wait_for_completion to the affected endpoint schemas so generated client SDKs pick it up.

Implementation

New files (src/main/java/org/opensearch/security/action/configupdate/):

  • SecurityConfigWriteActionActionType with name cluster:admin/opendistro_security/api/write_config.
  • SecurityConfigWriteRequest — carries cType, serialized config bytes, seqNo/primaryTerm for optimistic concurrency, security index name, task description, success message, and success status. createTask returns a plain (non-cancellable) Task. getShouldStoreResult() returns true so TransportAction.execute wraps the listener with TaskResultStoringActionListener automatically.
  • SecurityConfigWriteResponseActionResponse + ToXContentObject; body is {"status":"OK","message":"..."} matching the sync path exactly. Deliberately does not include configuration contents.
  • TransportSecurityConfigWriteActionHandledTransportAction that performs the IndexRequest and then broadcasts ConfigUpdateAction; only completes the listener after all-node ack, mirroring the sync path's ConfigUpdatingActionListener chain.

Modified files:

  • OpenSearchSecurityPlugin.java — registers the new transport action.
  • AbstractApiAction.java — adds supportsAsync() hook (default false) and a private maybeSubmitAsTask helper wired via withAsyncTaskSubmitter(...). Consumes wait_for_completion in prepareRequest directly (not inside consumeParameters) so subclass overrides that don't call super still don't reject the parameter as unrecognized.
  • RequestHandler.java — introduces the AsyncTaskSubmitter functional interface and pre-branches PUT/PATCH/DELETE on it. If the endpoint doesn't opt in, AsyncTaskSubmitter.NEVER returns false and the sync path is unchanged. Response message and status are precomputed once from the loaded configuration state so the sync body and the stored task result are byte-identical.
  • 5 endpoint classes — override supportsAsync() to return true.

Backwards compatibility. The pre-branch runs only when the endpoint has opted in and wait_for_completion=false is explicitly present. The sync response body was refactored to compute (status, message) once instead of twice, but produces the same JSON: {"status":"CREATED","message":"'my_role' created."} for a new PUT, {"status":"OK","message":"'my_role' updated."} for an update, {"status":"OK","message":"'my_role' deleted."} for a delete. The public static helper AbstractApiAction.saveAndUpdateConfigsAsync is left untouched because RollbackVersionApiAction and ConfigUpgradeApiAction still depend on its signature.

Issues Resolved

Closes #6337

Is this a backport? No. New feature, targets main.

Do these changes introduce new permission(s) to be displayed in the static dropdown on the front-end? No — the transport action's authorization runs through the same REST-admin check the existing sync path uses, at the REST layer, before task submission. No new plugin-specific action name needs a UI-side entry.

Testing

New integration test file: src/integrationTest/java/org/opensearch/security/api/WaitForCompletionRestApiIntegrationTest.java

Covers each acceptance criterion in the issue:

  • Sync success (parameter omitted, and parameter explicitly set to true) — response status and body match the pre-PR behavior.
  • Sync failure — invalid body still returns 400 even when wait_for_completion=false is requested, because validation runs before task submission (no phantom task IDs for requests that never execute).
  • Async success and task lookup — one test per opted-in endpoint (Roles, RolesMapping, InternalUsers, Tenants, Audit): submit with wait_for_completion=false, verify the returned task ID follows <nodeId>:<taskId> shape, poll _tasks/{id}?wait_for_completion=true&timeout=30s, assert completed=true and — for Roles and InternalUsers — that the stored task result contains the exact success message the sync path would have returned.
  • Task cancellation refused — POST /_tasks/{id}/_cancel returns a response whose body contains "doesn't support cancellation".

Regression coverage: the existing RolesRestApiIntegrationTest and InternalUsersRestApiIntegrationTest suites pass unmodified against these changes (17 tests, 0 failures).

Manual verification — run against a real multi-node local cluster built from this branch:

# Scenario Expected Actual
1 PUT /roles/sync_role (no param) 201 + "created" HTTP 201 {"status":"CREATED","message":"'sync_role' created."}
2 PUT /roles/async_role?wait_for_completion=false 200 + {"task":"nodeId:taskId"} HTTP 200 {"task":"WkDLHW0bRzG7OmOx3bpeRg:133"}
3 GET /_tasks/{id}?wait_for_completion=true Stored result mirrors sync response completed:true, cancellable:false, action:"cluster:admin/opendistro_security/api/write_config", description:"roles/async_role", response:{"status":"CREATED","message":"'async_role' created."}
4 POST /_tasks/{id}/_cancel Refused caused_by: illegal_argument_exception "task [...] doesn't support cancellation"
5 Invalid body + wait_for_completion=false Sync 400, no phantom task HTTP 400 {"status":"error","reason":"Invalid configuration","invalid_keys":{"keys":"unknown_field"}}
6 Async PUT on internal users Task ID returned HTTP 200 {"task":"...:218"}
7 Async PUT on non-opted-in endpoint (actiongroups) Parameter silently ignored, sync response HTTP 201 {"status":"CREATED","message":"'manual_ag' created."}

The task-manager output in row 3 also confirms "cancellable":false at the framework level, so _tasks/{id}/_cancel rejection in row 4 comes from
TransportCancelTasksAction rather than any custom check — non-cancellability is a property of the Task type, not a plugin-level filter.

Check List

  • New functionality includes testing
  • New functionality has been documented
  • New Roles/Permissions have a corresponding security dashboards plugin PR
  • API changes companion pull request created
  • Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

…nsearch-project#6337)

Adds the standard OpenSearch wait_for_completion query parameter to Security
configuration write endpoints. Callers can now submit a change with
?wait_for_completion=false and receive a task id immediately; the operation is
tracked through the task framework and its outcome is retrievable via
GET /_tasks/{task_id}. Omitting the parameter or setting it to true preserves
the existing synchronous behavior byte-for-byte.

Shared plumbing in AbstractApiAction + a new TransportSecurityConfigWriteAction
lets any writable endpoint opt in with a single method override. Opted-in
endpoints in this PR: Roles, RolesMapping, InternalUsers, Tenants, Audit
(everything a Terraform provider needs when provisioning a cluster).

Design decisions worth calling out for review:
- Task completes only after all-node ack of the config reload (matches the
  point at which the sync path returns today).
- Task result payload stored in .tasks contains only status and message — never
  configuration contents — so standard _tasks authz satisfies the 'do not
  expose configuration contents' acceptance criterion.
- Tasks are NOT cancellable: createTask returns a plain Task rather than a
  CancellableTask, so _tasks/{id}/_cancel is rejected with 'doesn't support
  cancellation'. A mid-fan-out cancel could leave the index write committed
  with a partial cluster reload, which is worse than letting the operation
  run to completion.

Closes opensearch-project#6337

Signed-off-by: Nishtha Mittal <nishthm@amazon.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 Security concerns

Potential authorization surface change:
async writes bypass any subclass-specific logic embedded in saveOrUpdateConfiguration because the transport action indexes the config document directly. If any endpoint's sync save path performs authorization or entity-integrity checks not already replicated in the endpoint validator chain, wait_for_completion=false becomes a way to skirt those checks. Additionally, the transport action does not appear to establish a privileged/system-index thread context before writing to .opendistro_security, which could either fail (in strict deployments) or, worse, run under caller identity in ways that differ from the sync path.

✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Authorization Bypass on Async Path

On the async path, maybeSubmitAsTask is invoked from within the onChangeRequest valid-branch after mapper validation, but the request is dispatched via nodeClient.executeLocally(SecurityConfigWriteAction.INSTANCE, ...) which writes directly to the security index bypassing the normal saveOrUpdateConfigurationHandler (which typically enforces admin/reserved/hidden/static entity checks and other endpoint-specific save logic). If saveOrUpdateConfiguration in subclasses contains additional gating (e.g., static resource protection, reserved entity checks at save time, or additional transformations beyond removeStatic()), those are silently skipped when wait_for_completion=false. Verify that all validation performed by each subclass's save path is already covered by the endpoint validator's onConfigChange / onConfigDelete chain; otherwise async requests may successfully write configurations that a sync request would reject.

private boolean maybeSubmitAsTask(
    final RestChannel channel,
    final RestRequest request,
    final Client client,
    final SecurityDynamicConfiguration<?> configuration,
    final String entityName,
    final String successMessage,
    final RestStatus successStatus
) {
    if (!supportsAsync()) {
        return false;
    }
    if (request.paramAsBoolean("wait_for_completion", true)) {
        return false;
    }
    if (!(client instanceof NodeClient)) {
        // Defensive: this handler always runs off a NodeClient today, so this branch is
        // effectively unreachable — but avoid a runtime ClassCastException if that ever
        // changes and gracefully fall back to sync execution.
        LOGGER.debug("Client is not a NodeClient; cannot submit as task, falling back to sync path");
        return false;
    }
    final NodeClient nodeClient = (NodeClient) client;
    final CType<?> cType = getConfigType();

    configuration.removeStatic();
    final BytesReference content;
    try {
        content = XContentHelper.toXContent(configuration, XContentType.JSON, ToXContent.EMPTY_PARAMS, false);
    } catch (final IOException e) {
        throw ExceptionsHelper.convertToOpenSearchException(e);
    }

    final String description = entityName == null ? cType.toLCString() : cType.toLCString() + "/" + entityName;
    final SecurityConfigWriteRequest updateRequest = new SecurityConfigWriteRequest(
        cType.toLCString(),
        content,
        configuration.getSeqNo(),
        configuration.getPrimaryTerm(),
        securityApiDependencies.securityIndexName(),
        description,
        successMessage,
        successStatus
    );

    // Persist the eventual task result in .tasks so callers can retrieve it via
    // GET /_tasks/{task_id} after completion. The request itself always signals this via
    // getShouldStoreResult() — see SecurityConfigWriteRequest.
    final org.opensearch.tasks.Task task = nodeClient.executeLocally(
        SecurityConfigWriteAction.INSTANCE,
        updateRequest,
        org.opensearch.tasks.LoggingTaskListener.instance()
    );

    try (final XContentBuilder builder = channel.newBuilder()) {
        builder.startObject();
        builder.field("task", nodeClient.getLocalNodeId() + ":" + task.getId());
        builder.endObject();
        channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder));
    } catch (final IOException e) {
        throw ExceptionsHelper.convertToOpenSearchException(e);
    }
    return true;
}
Missing Security Context

TransportSecurityConfigWriteAction calls client.index(...) and client.execute(ConfigUpdateAction.INSTANCE, ...) without wrapping in stashContext() or any privileged/system-index context. The pre-existing sync path in AbstractApiAction.saveAndUpdateConfigsAsync typically stashes the thread context so the write to the protected .opendistro_security system index succeeds. On the async path the transport action runs on the transport threadpool with the caller's headers, which may cause the index write to be rejected by the SystemIndex/SecurityFilter or evaluated against the caller's permissions instead of internal ones. Confirm the write actually reaches the security index in production configurations (not only the integration test cluster).

protected void doExecute(
    final Task task,
    final SecurityConfigWriteRequest request,
    final ActionListener<SecurityConfigWriteResponse> listener
) {
    final IndexRequest indexRequest = new IndexRequest(request.getSecurityIndex()).id(request.getCType())
        .setRefreshPolicy(RefreshPolicy.IMMEDIATE)
        .setIfSeqNo(request.getSeqNo())
        .setIfPrimaryTerm(request.getPrimaryTerm())
        .source(request.getCType(), request.getContent());

    // Step 1: write the config document. Optimistic concurrency is enforced by the
    // (seqNo, primaryTerm) preconditions above — a stale write surfaces as
    // VersionConflictEngineException and is propagated to the task listener untouched.
    client.index(indexRequest, ActionListener.wrap(indexResponse -> {
        // Step 2: fan out a reload request so every node re-reads the changed config from
        // the index. This is the same mechanism the pre-existing sync path uses via
        // ConfigUpdatingActionListener. We only complete the task listener after every node
        // acknowledges, so callers polling _tasks/{id} see the operation as in-progress
        // until the cluster is consistent.
        final var configUpdate = new org.opensearch.security.action.configupdate.ConfigUpdateRequest(
            new String[] { request.getCType() }
        );
        client.execute(ConfigUpdateAction.INSTANCE, configUpdate, ActionListener.wrap(configUpdateResponse -> {
            if (configUpdateResponse.hasFailures()) {
                // Surface the first per-node failure. The full failure list is available on
                // configUpdateResponse for anyone consuming the raw response object, but the
                // stored task result only carries the exception message.
                listener.onFailure(configUpdateResponse.failures().get(0));
                return;
            }
            listener.onResponse(new SecurityConfigWriteResponse(request.getSuccessStatus(), request.getSuccessMessage()));
        }, e -> {
            LOGGER.debug("Cluster-wide config reload failed for {}", request.getCType(), e);
            listener.onFailure(e);
        }));
    }, e -> {
        LOGGER.debug("Persisting configuration to security index failed for {}", request.getCType(), e);
        listener.onFailure(e);
    }));
}
Behavior Change: PATCH Response

The refactored PATCH handler now always sends Responses.payload(successStatus, successMessage) as an object body ({"status":"OK","message":"..."}), replacing the previous ok(channel, "'x' updated.") / ok(channel, "Resource updated.") paths. Depending on the previous ok(String) overload, the response body shape may have changed (e.g., plain-string message vs. structured payload). This could break clients that parse the PATCH response body. Verify the payload shape is byte-identical to the prior sync behavior as claimed in the PR description.

switch (method) {
    case PATCH:
        add(method, (channel, request, client) -> mapper.apply(request).valid(securityConfiguration -> {
            // Message + status for both the sync response body AND the async
            // stored task result must be identical, so we resolve them once
            // here based on the loaded configuration state.
            final boolean hasEntityName = securityConfiguration.maybeEntityName().isPresent();
            final String entityName = hasEntityName ? securityConfiguration.entityName() : null;
            final String successMessage = hasEntityName ? "'" + entityName + "' updated." : "Resource updated.";
            final RestStatus successStatus = RestStatus.OK;
            if (asyncTaskSubmitter.trySubmit(
                channel,
                request,
                client,
                securityConfiguration.configuration(),
                entityName,
                successMessage,
                successStatus
            )) {
                return;
            }
            saveOrUpdateConfigurationHandler.apply(
                client,
                securityConfiguration.configuration(),
                new AbstractApiAction.OnSucessActionListener<>(channel) {
                    @Override
                    public void onResponse(IndexResponse indexResponse) {
                        response(channel, successStatus, Responses.payload(successStatus, successMessage));
                    }
                }
            );
        }).error((status, toXContent) -> response(channel, status, toXContent)));
Optimistic Concurrency Window

maybeSubmitAsTask captures configuration.getSeqNo() and configuration.getPrimaryTerm() at REST-handler time and passes them to the transport action, which uses them as setIfSeqNo/setIfPrimaryTerm preconditions. Any concurrent write between task submission and task execution will fail with VersionConflictEngineException. This is surfaced only in the stored task result, not to the caller who already received {"task":"..."} with HTTP 200. Ensure this failure mode is clearly documented for callers, as it differs from the sync path where the conflict is reported directly in the HTTP response.

final SecurityConfigWriteRequest updateRequest = new SecurityConfigWriteRequest(
    cType.toLCString(),
    content,
    configuration.getSeqNo(),
    configuration.getPrimaryTerm(),
    securityApiDependencies.securityIndexName(),
    description,
    successMessage,
    successStatus
);

// Persist the eventual task result in .tasks so callers can retrieve it via
// GET /_tasks/{task_id} after completion. The request itself always signals this via
// getShouldStoreResult() — see SecurityConfigWriteRequest.
final org.opensearch.tasks.Task task = nodeClient.executeLocally(
    SecurityConfigWriteAction.INSTANCE,
    updateRequest,
    org.opensearch.tasks.LoggingTaskListener.instance()
);

@github-actions

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Stash thread context for index write

The client.index() call is not wrapped in the caller's thread context, unlike the
sync path in AbstractApiAction.saveAndUpdateConfigsAsync. Direct index writes to the
security index typically require system-context / stashed thread context to bypass
security filters. Without this, the write may fail authorization or leak the
caller's user context into the index operation. Wrap the call in
threadPool.getThreadContext().stashContext() as done elsewhere.

src/main/java/org/opensearch/security/action/configupdate/TransportSecurityConfigWriteAction.java [69]

-client.index(indexRequest, ActionListener.wrap(indexResponse -> {
+try (var ignored = client.threadPool().getThreadContext().stashContext()) {
+    client.index(indexRequest, ActionListener.wrap(indexResponse -> {
Suggestion importance[1-10]: 7

__

Why: Direct writes to the security index typically require a stashed system context to bypass security filters, and missing this could cause authorization failures. This is a potentially important correctness issue worth verifying.

Medium
General
Avoid mutating shared configuration object

configuration.removeStatic() mutates the shared configuration instance passed into
the pre-branch. If the async submission ultimately fails or falls back, or if the
same configuration object is referenced elsewhere in the request handling flow, the
static entries are permanently lost from that object. Either operate on a defensive
copy or ensure this mutation is safe/necessary at this point in the flow.

src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java [182-188]

-configuration.removeStatic();
+final SecurityDynamicConfiguration<?> configCopy = configuration.deepClone();
+configCopy.removeStatic();
 final BytesReference content;
 try {
-    content = XContentHelper.toXContent(configuration, XContentType.JSON, ToXContent.EMPTY_PARAMS, false);
+    content = XContentHelper.toXContent(configCopy, XContentType.JSON, ToXContent.EMPTY_PARAMS, false);
 } catch (final IOException e) {
     throw ExceptionsHelper.convertToOpenSearchException(e);
 }
Suggestion importance[1-10]: 6

__

Why: Mutating the shared configuration via removeStatic() could cause subtle side effects if the object is referenced downstream. A defensive copy would be safer, though impact depends on the object's lifecycle.

Low
Reject async param on non-async endpoints

When supportsAsync() is false but the caller passed wait_for_completion=false, the
request silently falls through to the sync path, which is misleading. Consider
either rejecting with a 400 for endpoints that don't support async, or at minimum
documenting this behavior. Also, wait_for_completion is consumed in prepareRequest
regardless of supportsAsync(), so on non-async endpoints the parameter is accepted
but ignored — this may confuse API clients expecting async behavior.

src/main/java/org/opensearch/security/dlic/rest/api/AbstractApiAction.java [166-171]

 if (!supportsAsync()) {
+    if (!request.paramAsBoolean("wait_for_completion", true)) {
+        throw new IllegalArgumentException("wait_for_completion=false is not supported for this endpoint");
+    }
     return false;
 }
 if (request.paramAsBoolean("wait_for_completion", true)) {
     return false;
 }
Suggestion importance[1-10]: 3

__

Why: Silently ignoring wait_for_completion=false on non-async endpoints is arguably intentional for backward compatibility, and rejecting could break clients. The suggestion is a debatable API design choice rather than a clear bug.

Low
Remove misleading matcher wrapper

The helper method not_ is defined after its first usage in
invalidBody_returnsSyncErrorEvenWhenAsyncRequested — while Java allows this via
forward reference for methods, the naming (not_ with trailing underscore) and the
misleading comment claiming a static-import collision is confusing since no not is
statically imported. Just static-import org.hamcrest.CoreMatchers.not and remove the
wrapper.

src/integrationTest/java/org/opensearch/security/api/WaitForCompletionRestApiIntegrationTest.java [138-140]

-private static org.hamcrest.Matcher<String> not_(org.hamcrest.Matcher<String> inner) {
-    return org.hamcrest.CoreMatchers.not(inner);
-}
+// (remove not_ wrapper; add: import static org.hamcrest.CoreMatchers.not;)
Suggestion importance[1-10]: 3

__

Why: Minor code style improvement in test code; the wrapper works but is confusingly named and documented. Low impact.

Low

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.20809% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.35%. Comparing base (af1d7ee) to head (a7d06dc).

Files with missing lines Patch % Lines
...ction/configupdate/SecurityConfigWriteRequest.java 44.44% 23 Missing and 2 partials ⚠️
...tion/configupdate/SecurityConfigWriteResponse.java 50.00% 9 Missing ⚠️
...nfigupdate/TransportSecurityConfigWriteAction.java 65.38% 8 Missing and 1 partial ⚠️
...arch/security/dlic/rest/api/AbstractApiAction.java 76.47% 6 Missing and 2 partials ⚠️
...nsearch/security/dlic/rest/api/RequestHandler.java 90.24% 2 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #6409      +/-   ##
==========================================
- Coverage   75.38%   75.35%   -0.03%     
==========================================
  Files         456      460       +4     
  Lines       30254    30402     +148     
  Branches     4574     4584      +10     
==========================================
+ Hits        22806    22909     +103     
- Misses       5310     5351      +41     
- Partials     2138     2142       +4     
Files with missing lines Coverage Δ
.../opensearch/security/OpenSearchSecurityPlugin.java 84.01% <100.00%> (+0.01%) ⬆️
...action/configupdate/SecurityConfigWriteAction.java 100.00% <100.00%> (ø)
...nsearch/security/dlic/rest/api/AuditApiAction.java 91.54% <100.00%> (+0.12%) ⬆️
...security/dlic/rest/api/InternalUsersApiAction.java 93.60% <100.00%> (+0.05%) ⬆️
...nsearch/security/dlic/rest/api/RolesApiAction.java 96.15% <100.00%> (+0.07%) ⬆️
.../security/dlic/rest/api/RolesMappingApiAction.java 97.36% <100.00%> (+0.07%) ⬆️
...earch/security/dlic/rest/api/TenantsApiAction.java 95.45% <100.00%> (+0.21%) ⬆️
...nsearch/security/dlic/rest/api/RequestHandler.java 95.14% <90.24%> (-3.71%) ⬇️
...arch/security/dlic/rest/api/AbstractApiAction.java 87.29% <76.47%> (-1.39%) ⬇️
...tion/configupdate/SecurityConfigWriteResponse.java 50.00% <50.00%> (ø)
... and 2 more

... and 5 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support asynchronous Security configuration APIs with wait_for_completion

1 participant