dpward / rpms / sssd

Forked from rpms/sssd 3 years ago
Clone

Blame SOURCES/0082-CACHE_REQ-Use-the-domain-locator-request-to-only-sea.patch

ced1f5
From 1b4b03720c409b183debe0e0532b1009301e9cb2 Mon Sep 17 00:00:00 2001
ced1f5
From: Jakub Hrozek <jhrozek@redhat.com>
ced1f5
Date: Sun, 19 Nov 2017 22:47:00 +0100
ced1f5
Subject: [PATCH 82/83] CACHE_REQ: Use the domain-locator request to only
ced1f5
 search domains where the entry was found
ced1f5
MIME-Version: 1.0
ced1f5
Content-Type: text/plain; charset=UTF-8
ced1f5
Content-Transfer-Encoding: 8bit
ced1f5
ced1f5
Uses the internal cache_req interface around the getAccountDomain to only
ced1f5
search the domain returned by the cache_req_locate_domain_recv() request.
ced1f5
ced1f5
If that request returns that no domain matched, all domains (belonging
ced1f5
to the currently processed main domain) are skipped by setting the
ced1f5
per-type negative cache.
ced1f5
ced1f5
if a domain is reported as containing an object, all domains except that
ced1f5
one are marked with the negative cache entries.
ced1f5
ced1f5
Resolves:
ced1f5
https://pagure.io/SSSD/sssd/issue/3468
ced1f5
ced1f5
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
ced1f5
Reviewed-by: Sumit Bose <sbose@redhat.com>
ced1f5
(cherry picked from commit f2a5e29f063f9d623c1336d76f4b2bc500c1a5e2)
ced1f5
---
ced1f5
 src/responder/common/cache_req/cache_req.c        |  402 +++++-
ced1f5
 src/responder/common/cache_req/cache_req_domain.h |    1 +
ced1f5
 src/tests/cmocka/test_responder_cache_req.c       | 1373 +++++++++++++++++++++
ced1f5
 3 files changed, 1758 insertions(+), 18 deletions(-)
ced1f5
ced1f5
diff --git a/src/responder/common/cache_req/cache_req.c b/src/responder/common/cache_req/cache_req.c
ced1f5
index 110df561101be538e3f0496addfa2e14e42ea918..ad9bc040dd999a205713141e6a1512e47b69c45e 100644
ced1f5
--- a/src/responder/common/cache_req/cache_req.c
ced1f5
+++ b/src/responder/common/cache_req/cache_req.c
ced1f5
@@ -363,6 +363,53 @@ static void cache_req_global_ncache_add(struct cache_req *cr)
ced1f5
     return;
ced1f5
 }
ced1f5
 
ced1f5
+static bool cache_req_check_acct_domain_lookup_type(struct cache_req *cr,
ced1f5
+                                                    struct sss_domain_info *dom)
ced1f5
+{
ced1f5
+    struct sss_domain_info *head;
ced1f5
+    int nret;
ced1f5
+
ced1f5
+    head = get_domains_head(dom);
ced1f5
+    if (head == NULL) {
ced1f5
+        return false;
ced1f5
+    }
ced1f5
+
ced1f5
+    nret = sss_ncache_check_domain_locate_type(cr->rctx->ncache,
ced1f5
+                                               head,
ced1f5
+                                               cr->plugin->name);
ced1f5
+    if (nret == ENOENT) {
ced1f5
+        return true;
ced1f5
+    }
ced1f5
+    return false;
ced1f5
+}
ced1f5
+
ced1f5
+static errno_t cache_req_set_acct_domain_lookup_type(struct cache_req *cr,
ced1f5
+                                                     struct sss_domain_info *dom)
ced1f5
+{
ced1f5
+    struct sss_domain_info *head;
ced1f5
+
ced1f5
+    head = get_domains_head(dom);
ced1f5
+    if (head == NULL) {
ced1f5
+        return EINVAL;
ced1f5
+    }
ced1f5
+
ced1f5
+    return sss_ncache_set_domain_locate_type(cr->rctx->ncache,
ced1f5
+                                             head,
ced1f5
+                                             cr->plugin->name);
ced1f5
+}
ced1f5
+
ced1f5
+static void cache_req_domain_set_locate_flag(struct cache_req_domain *domains,
ced1f5
+                                             struct cache_req *cr)
ced1f5
+{
ced1f5
+    struct cache_req_domain *crd_iter;
ced1f5
+
ced1f5
+    DLIST_FOR_EACH(crd_iter, domains) {
ced1f5
+        if (cache_req_check_acct_domain_lookup_type(cr, crd_iter->domain)) {
ced1f5
+            crd_iter->locate_domain = true;
ced1f5
+        }
ced1f5
+    }
ced1f5
+}
ced1f5
+
ced1f5
 static bool
ced1f5
 cache_req_assume_upn(struct cache_req *cr)
ced1f5
 {
ced1f5
@@ -391,6 +438,227 @@ cache_req_assume_upn(struct cache_req *cr)
ced1f5
     return true;
ced1f5
 }
ced1f5
 
ced1f5
+struct cache_req_locate_dom_state {
ced1f5
+    /* input data */
ced1f5
+    struct tevent_context *ev;
ced1f5
+    struct cache_req *cr;
ced1f5
+    struct cache_req_domain *req_domains;
ced1f5
+
ced1f5
+    /* Return values in case the first cache lookup succeeds */
ced1f5
+    struct ldb_result *result;
ced1f5
+    bool dp_success;
ced1f5
+};
ced1f5
+
ced1f5
+static void cache_req_locate_dom_cache_done(struct tevent_req *subreq);
ced1f5
+static void cache_req_locate_dom_done(struct tevent_req *subreq);
ced1f5
+static void cache_req_locate_dom_mark_neg_all(
ced1f5
+                                struct cache_req_locate_dom_state *state);
ced1f5
+static void cache_req_locate_dom_mark_neg_domains(
ced1f5
+                                struct cache_req_locate_dom_state *state,
ced1f5
+                                const char *found_domain_name);
ced1f5
+
ced1f5
+static struct tevent_req *cache_req_locate_dom_send(TALLOC_CTX *mem_ctx,
ced1f5
+                                                    struct tevent_context *ev,
ced1f5
+                                                    struct cache_req *cr,
ced1f5
+                                                    struct cache_req_domain *req_domains)
ced1f5
+{
ced1f5
+    struct tevent_req *req;
ced1f5
+    struct tevent_req *subreq;
ced1f5
+    struct cache_req_locate_dom_state *state = NULL;
ced1f5
+    errno_t ret;
ced1f5
+
ced1f5
+    req = tevent_req_create(mem_ctx, &state,
ced1f5
+                            struct cache_req_locate_dom_state);
ced1f5
+    if (req == NULL) {
ced1f5
+        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
ced1f5
+        return NULL;
ced1f5
+    }
ced1f5
+    state->ev = ev;
ced1f5
+    state->cr = cr;
ced1f5
+    state->req_domains = req_domains;
ced1f5
+
ced1f5
+    /* It is wasteful to run the domain locator request if the results are
ced1f5
+     * present in the cache, because the domain locator always contacts
ced1f5
+     * the DP. Therefore, first run a cache-only search and only if the
ced1f5
+     * requested data is not available, run the locator
ced1f5
+     *
ced1f5
+     * FIXME - this could be optimized further if we are running the
ced1f5
+     * second iteration with cache_first, then we don't need to search
ced1f5
+     * again
ced1f5
+     */
ced1f5
+    subreq = cache_req_search_send(state,
ced1f5
+                                   state->ev,
ced1f5
+                                   state->cr,
ced1f5
+                                   false,       /* Don't bypass cache */
ced1f5
+                                   true);       /* Do bypass DP */
ced1f5
+    if (subreq == NULL) {
ced1f5
+        ret = ENOMEM;
ced1f5
+        goto immediately;
ced1f5
+    }
ced1f5
+    tevent_req_set_callback(subreq, cache_req_locate_dom_cache_done, req);
ced1f5
+
ced1f5
+    return req;
ced1f5
+
ced1f5
+immediately:
ced1f5
+    tevent_req_error(req, ret);
ced1f5
+    tevent_req_post(req, ev);
ced1f5
+    return req;
ced1f5
+}
ced1f5
+
ced1f5
+static void cache_req_locate_dom_cache_done(struct tevent_req *subreq)
ced1f5
+{
ced1f5
+    struct cache_req_locate_dom_state *state = NULL;
ced1f5
+    struct tevent_req *req;
ced1f5
+    errno_t ret;
ced1f5
+
ced1f5
+    req = tevent_req_callback_data(subreq, struct tevent_req);
ced1f5
+    state = tevent_req_data(req, struct cache_req_locate_dom_state);
ced1f5
+
ced1f5
+    ret = cache_req_search_recv(state, subreq, &state->result, &state->dp_success);
ced1f5
+    talloc_zfree(subreq);
ced1f5
+
ced1f5
+    switch (ret) {
ced1f5
+    case EOK:
ced1f5
+        /* Just finish the request and let the caller handle the result */
ced1f5
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Result found in the cache\n");
ced1f5
+        tevent_req_done(req);
ced1f5
+        return;
ced1f5
+    case ENOENT:
ced1f5
+        /* Not cached and locator was requested, run the locator
ced1f5
+         * DP request plugin
ced1f5
+         */
ced1f5
+        subreq = cache_req_locate_domain_send(state,
ced1f5
+                                              state->ev,
ced1f5
+                                              state->cr);
ced1f5
+        if (subreq == NULL) {
ced1f5
+            tevent_req_error(req, ENOMEM);
ced1f5
+            return;
ced1f5
+        }
ced1f5
+        tevent_req_set_callback(subreq, cache_req_locate_dom_done, req);
ced1f5
+        return;
ced1f5
+    default:
ced1f5
+        DEBUG(SSSDBG_OP_FAILURE,
ced1f5
+              "cache_req_search_recv returned [%d]: %s\n", ret, sss_strerror(ret));
ced1f5
+        break;
ced1f5
+    }
ced1f5
+
ced1f5
+    tevent_req_error(req, ret);
ced1f5
+    return;
ced1f5
+}
ced1f5
+
ced1f5
+static void cache_req_locate_dom_done(struct tevent_req *subreq)
ced1f5
+{
ced1f5
+    struct cache_req_locate_dom_state *state;
ced1f5
+    struct tevent_req *req;
ced1f5
+    errno_t ret;
ced1f5
+    char *found_domain_name;
ced1f5
+    int nret;
ced1f5
+
ced1f5
+    req = tevent_req_callback_data(subreq, struct tevent_req);
ced1f5
+    state = tevent_req_data(req, struct cache_req_locate_dom_state);
ced1f5
+
ced1f5
+    ret = cache_req_locate_domain_recv(state, subreq, &found_domain_name);
ced1f5
+    talloc_zfree(subreq);
ced1f5
+    switch (ret) {
ced1f5
+    case ERR_GET_ACCT_DOM_NOT_SUPPORTED:
ced1f5
+        nret = cache_req_set_acct_domain_lookup_type(state->cr,
ced1f5
+                                                     state->cr->domain);
ced1f5
+        if (nret != EOK) {
ced1f5
+            DEBUG(SSSDBG_MINOR_FAILURE,
ced1f5
+                  "Failed to disable domain locating functionality for %s\n",
ced1f5
+                  state->cr->plugin->name);
ced1f5
+        }
ced1f5
+        DEBUG(SSSDBG_CONF_SETTINGS,
ced1f5
+              "Disabled domain locating functionality for %s\n",
ced1f5
+              state->cr->plugin->name);
ced1f5
+        break;
ced1f5
+    case ERR_NOT_FOUND:
ced1f5
+        cache_req_locate_dom_mark_neg_all(state);
ced1f5
+        break;
ced1f5
+    case EOK:
ced1f5
+        cache_req_locate_dom_mark_neg_domains(state, found_domain_name);
ced1f5
+        break;
ced1f5
+    default:
ced1f5
+        /* We explicitly ignore errors here */
ced1f5
+        break;
ced1f5
+    }
ced1f5
+
ced1f5
+    tevent_req_done(req);
ced1f5
+    return;
ced1f5
+}
ced1f5
+
ced1f5
+static void cache_req_locate_dom_mark_neg_all(
ced1f5
+                                struct cache_req_locate_dom_state *state)
ced1f5
+{
ced1f5
+    struct cache_req_domain *iter;
ced1f5
+
ced1f5
+    DLIST_FOR_EACH(iter, state->req_domains) {
ced1f5
+        if (get_domains_head(state->cr->domain) != get_domains_head(iter->domain)) {
ced1f5
+            /* Only add to negative cache for domains from the same "main"
ced1f5
+             * domain" */
ced1f5
+            continue;
ced1f5
+        }
ced1f5
+        cache_req_search_ncache_add_to_domain(state->cr, iter->domain);
ced1f5
+    }
ced1f5
+}
ced1f5
+
ced1f5
+static void cache_req_locate_dom_mark_neg_domains(
ced1f5
+                                struct cache_req_locate_dom_state *state,
ced1f5
+                                const char *found_domain_name)
ced1f5
+{
ced1f5
+    struct sss_domain_info *found_domain;
ced1f5
+    struct cache_req_domain *iter;
ced1f5
+
ced1f5
+    found_domain = find_domain_by_name(get_domains_head(state->cr->domain),
ced1f5
+                                       found_domain_name,
ced1f5
+                                       true);
ced1f5
+    if (found_domain == NULL) {
ced1f5
+        DEBUG(SSSDBG_MINOR_FAILURE,
ced1f5
+                "Cannot find domain %s\n", found_domain_name);
ced1f5
+        return;
ced1f5
+    }
ced1f5
+
ced1f5
+    /* Set negcache in all subdomains of the one being examined
ced1f5
+     * except the found one */
ced1f5
+    DLIST_FOR_EACH(iter, state->req_domains) {
ced1f5
+        if (strcasecmp(found_domain_name,
ced1f5
+                       iter->domain->name) == 0) {
ced1f5
+            continue;
ced1f5
+        }
ced1f5
+
ced1f5
+        if (get_domains_head(found_domain) != get_domains_head(iter->domain)) {
ced1f5
+            /* Don't set negative cache for domains outside the main
ced1f5
+             * domain/subdomain tree b/c the locator request is not
ced1f5
+             * authoritative for them
ced1f5
+             */
ced1f5
+            continue;
ced1f5
+        }
ced1f5
+        cache_req_search_ncache_add_to_domain(state->cr, iter->domain);
ced1f5
+    }
ced1f5
+}
ced1f5
+
ced1f5
+static errno_t cache_req_locate_dom_cache_recv(TALLOC_CTX *mem_ctx,
ced1f5
+                                               struct tevent_req *req,
ced1f5
+                                               struct ldb_result **_result,
ced1f5
+                                               bool *_dp_success)
ced1f5
+{
ced1f5
+    struct cache_req_locate_dom_state *state;
ced1f5
+
ced1f5
+    state = tevent_req_data(req, struct cache_req_locate_dom_state);
ced1f5
+
ced1f5
+    if (_dp_success != NULL) {
ced1f5
+        *_dp_success = state->dp_success;
ced1f5
+    }
ced1f5
+
ced1f5
+    TEVENT_REQ_RETURN_ON_ERROR(req);
ced1f5
+
ced1f5
+    if (_result != NULL) {
ced1f5
+        *_result = talloc_steal(mem_ctx, state->result);
ced1f5
+    }
ced1f5
+
ced1f5
+    return EOK;
ced1f5
+}
ced1f5
+
ced1f5
 struct cache_req_search_domains_state {
ced1f5
     /* input data */
ced1f5
     struct tevent_context *ev;
ced1f5
@@ -398,6 +666,7 @@ struct cache_req_search_domains_state {
ced1f5
 
ced1f5
     /* work data */
ced1f5
     struct cache_req_domain *cr_domain;
ced1f5
+    struct cache_req_domain *req_domains;
ced1f5
     struct sss_domain_info *selected_domain;
ced1f5
     struct cache_req_result **results;
ced1f5
     size_t num_results;
ced1f5
@@ -408,6 +677,10 @@ struct cache_req_search_domains_state {
ced1f5
 };
ced1f5
 
ced1f5
 static errno_t cache_req_search_domains_next(struct tevent_req *req);
ced1f5
+static errno_t cache_req_handle_result(struct tevent_req *req,
ced1f5
+                                       struct ldb_result *result);
ced1f5
+
ced1f5
+static void cache_req_search_domains_locate_done(struct tevent_req *subreq);
ced1f5
 
ced1f5
 static void cache_req_search_domains_done(struct tevent_req *subreq);
ced1f5
 
ced1f5
@@ -417,6 +690,7 @@ cache_req_search_domains_send(TALLOC_CTX *mem_ctx,
ced1f5
                               struct cache_req *cr,
ced1f5
                               struct cache_req_domain *cr_domain,
ced1f5
                               bool check_next,
ced1f5
+                              bool first_iteration,
ced1f5
                               bool bypass_cache,
ced1f5
                               bool bypass_dp)
ced1f5
 {
ced1f5
@@ -435,11 +709,23 @@ cache_req_search_domains_send(TALLOC_CTX *mem_ctx,
ced1f5
     state->cr = cr;
ced1f5
 
ced1f5
     state->cr_domain = cr_domain;
ced1f5
+    state->req_domains = cr_domain;
ced1f5
     state->check_next = check_next;
ced1f5
     state->dp_success = true;
ced1f5
     state->bypass_cache = bypass_cache;
ced1f5
     state->bypass_dp = bypass_dp;
ced1f5
 
ced1f5
+    if (cr->plugin->dp_get_domain_send_fn != NULL
ced1f5
+            && ((state->check_next && cr_domain->next != NULL)
ced1f5
+                || (state->bypass_cache && !first_iteration))) {
ced1f5
+        /* If the request is not qualified with a domain name AND
ced1f5
+         * there are multiple domains to search OR if this is the second
ced1f5
+         * pass during the "check-cache-first" schema, it makes sense
ced1f5
+         * to try to run the domain-locator plugin
ced1f5
+         */
ced1f5
+        cache_req_domain_set_locate_flag(cr_domain, cr);
ced1f5
+    }
ced1f5
+
ced1f5
     ret = cache_req_search_domains_next(req);
ced1f5
     if (ret == EAGAIN) {
ced1f5
         return req;
ced1f5
@@ -510,12 +796,23 @@ static errno_t cache_req_search_domains_next(struct tevent_req *req)
ced1f5
             return ret;
ced1f5
         }
ced1f5
 
ced1f5
+        if (state->cr_domain->locate_domain) {
ced1f5
+            subreq = cache_req_locate_dom_send(state,
ced1f5
+                                               state->ev,
ced1f5
+                                               cr,
ced1f5
+                                               state->req_domains);
ced1f5
+            if (subreq == NULL) {
ced1f5
+                return ENOMEM;
ced1f5
+            }
ced1f5
+            tevent_req_set_callback(subreq, cache_req_search_domains_locate_done, req);
ced1f5
+            return EAGAIN;
ced1f5
+        }
ced1f5
+
ced1f5
         subreq = cache_req_search_send(state, state->ev, cr,
ced1f5
                                        state->bypass_cache, state->bypass_dp);
ced1f5
         if (subreq == NULL) {
ced1f5
             return ENOMEM;
ced1f5
         }
ced1f5
-
ced1f5
         tevent_req_set_callback(subreq, cache_req_search_domains_done, req);
ced1f5
 
ced1f5
         /* we will continue with the following domain the next time */
ced1f5
@@ -549,6 +846,89 @@ static errno_t cache_req_search_domains_next(struct tevent_req *req)
ced1f5
     return ENOENT;
ced1f5
 }
ced1f5
 
ced1f5
+static void cache_req_search_domains_locate_done(struct tevent_req *subreq)
ced1f5
+{
ced1f5
+    struct cache_req_search_domains_state *state;
ced1f5
+    struct ldb_result *result = NULL;
ced1f5
+    struct tevent_req *req;
ced1f5
+    bool dp_success;
ced1f5
+    errno_t ret;
ced1f5
+
ced1f5
+    req = tevent_req_callback_data(subreq, struct tevent_req);
ced1f5
+    state = tevent_req_data(req, struct cache_req_search_domains_state);
ced1f5
+
ced1f5
+    ret = cache_req_locate_dom_cache_recv(state, subreq, &result, &dp_success);
ced1f5
+    talloc_zfree(subreq);
ced1f5
+
ced1f5
+    /* Remember if any DP request fails, but here it shouldn't matter
ced1f5
+     * as the only DP request that should realistically happen is midpoint
ced1f5
+     * refresh */
ced1f5
+    state->dp_success = !dp_success ? false : state->dp_success;
ced1f5
+
ced1f5
+    /* Don't locate the domain again */
ced1f5
+    state->cr_domain->locate_domain = false;
ced1f5
+
ced1f5
+    switch (ret) {
ced1f5
+    case EOK:
ced1f5
+        if (result != NULL) {
ced1f5
+            /* Handle result as normally */
ced1f5
+            ret = cache_req_handle_result(req, result);
ced1f5
+            if (ret != EAGAIN) {
ced1f5
+                goto done;
ced1f5
+            }
ced1f5
+        }
ced1f5
+        break;
ced1f5
+    default:
ced1f5
+        /* Some serious error has happened. Finish. */
ced1f5
+        goto done;
ced1f5
+    }
ced1f5
+
ced1f5
+    /* This is a domain less search, continue with the next domain. */
ced1f5
+    ret = cache_req_search_domains_next(req);
ced1f5
+
ced1f5
+done:
ced1f5
+    switch (ret) {
ced1f5
+    case EOK:
ced1f5
+        tevent_req_done(req);
ced1f5
+        break;
ced1f5
+    case EAGAIN:
ced1f5
+        break;
ced1f5
+    default:
ced1f5
+        tevent_req_error(req, ret);
ced1f5
+        break;
ced1f5
+    }
ced1f5
+    return;
ced1f5
+}
ced1f5
+
ced1f5
+static errno_t cache_req_handle_result(struct tevent_req *req,
ced1f5
+                                       struct ldb_result *result)
ced1f5
+{
ced1f5
+    struct cache_req_search_domains_state *state;
ced1f5
+    errno_t ret;
ced1f5
+
ced1f5
+    state = tevent_req_data(req, struct cache_req_search_domains_state);
ced1f5
+
ced1f5
+    /* We got some data from this search. Save it. */
ced1f5
+    ret = cache_req_create_and_add_result(state,
ced1f5
+                                          state->cr,
ced1f5
+                                          state->selected_domain,
ced1f5
+                                          result,
ced1f5
+                                          state->cr->data->name.lookup,
ced1f5
+                                          &state->results,
ced1f5
+                                          &state->num_results);
ced1f5
+    if (ret != EOK) {
ced1f5
+        /* We were unable to save data. */
ced1f5
+        return ret;
ced1f5
+    }
ced1f5
+
ced1f5
+    if (!state->check_next || !state->cr->plugin->search_all_domains) {
ced1f5
+        /* We are not interested in more results. */
ced1f5
+        return EOK;
ced1f5
+    }
ced1f5
+
ced1f5
+    return EAGAIN;
ced1f5
+}
ced1f5
+
ced1f5
 static void cache_req_search_domains_done(struct tevent_req *subreq)
ced1f5
 {
ced1f5
     struct cache_req_search_domains_state *state;
ced1f5
@@ -568,25 +948,10 @@ static void cache_req_search_domains_done(struct tevent_req *subreq)
ced1f5
 
ced1f5
     switch (ret) {
ced1f5
     case EOK:
ced1f5
-        /* We got some data from this search. Save it. */
ced1f5
-        ret = cache_req_create_and_add_result(state,
ced1f5
-                                              state->cr,
ced1f5
-                                              state->selected_domain,
ced1f5
-                                              result,
ced1f5
-                                              state->cr->data->name.lookup,
ced1f5
-                                              &state->results,
ced1f5
-                                              &state->num_results);
ced1f5
-        if (ret != EOK) {
ced1f5
-            /* We were unable to save data. */
ced1f5
+        ret = cache_req_handle_result(req, result);
ced1f5
+        if (ret != EAGAIN) {
ced1f5
             goto done;
ced1f5
         }
ced1f5
-
ced1f5
-        if (!state->check_next || !state->cr->plugin->search_all_domains) {
ced1f5
-            /* We are not interested in more results. */
ced1f5
-            ret = EOK;
ced1f5
-            goto done;
ced1f5
-        }
ced1f5
-
ced1f5
         break;
ced1f5
     case ENOENT:
ced1f5
         if (state->check_next == false) {
ced1f5
@@ -1030,6 +1395,7 @@ cache_req_search_domains(struct tevent_req *req,
ced1f5
 
ced1f5
     subreq = cache_req_search_domains_send(state, state->ev, state->cr,
ced1f5
                                            cr_domain, check_next,
ced1f5
+                                           state->first_iteration,
ced1f5
                                            bypass_cache, bypass_dp);
ced1f5
     if (subreq == NULL) {
ced1f5
         return ENOMEM;
ced1f5
diff --git a/src/responder/common/cache_req/cache_req_domain.h b/src/responder/common/cache_req/cache_req_domain.h
ced1f5
index ebdc71dd635d5d8a5d06e30e96c5d4101b6d98bf..5769b6aee309d9ba3edd5bb73a3cef6dc3193fdc 100644
ced1f5
--- a/src/responder/common/cache_req/cache_req_domain.h
ced1f5
+++ b/src/responder/common/cache_req/cache_req_domain.h
ced1f5
@@ -26,6 +26,7 @@
ced1f5
 struct cache_req_domain {
ced1f5
     struct sss_domain_info *domain;
ced1f5
     bool fqnames;
ced1f5
+    bool locate_domain;
ced1f5
 
ced1f5
     struct cache_req_domain *prev;
ced1f5
     struct cache_req_domain *next;
ced1f5
diff --git a/src/tests/cmocka/test_responder_cache_req.c b/src/tests/cmocka/test_responder_cache_req.c
ced1f5
index f075480a019e476407a3081a795c3c289455aca8..0ee0070d0c9fbb89020f522b2f7613f1076a8cbb 100644
ced1f5
--- a/src/tests/cmocka/test_responder_cache_req.c
ced1f5
+++ b/src/tests/cmocka/test_responder_cache_req.c
ced1f5
@@ -27,6 +27,7 @@
ced1f5
 #include "tests/cmocka/common_mock_resp.h"
ced1f5
 #include "db/sysdb.h"
ced1f5
 #include "responder/common/cache_req/cache_req.h"
ced1f5
+#include "db/sysdb_private.h"   /* new_subdomain() */
ced1f5
 
ced1f5
 #define TESTS_PATH "tp_" BASE_FILE_STEM
ced1f5
 #define TEST_CONF_DB "test_responder_cache_req_conf.ldb"
ced1f5
@@ -63,6 +64,11 @@ struct test_group {
ced1f5
                                     test_multi_domain_setup, \
ced1f5
                                     test_multi_domain_teardown)
ced1f5
 
ced1f5
+#define new_subdomain_test(test) \
ced1f5
+    cmocka_unit_test_setup_teardown(test_ ## test, \
ced1f5
+                                    test_subdomain_setup, \
ced1f5
+                                    test_subdomain_teardown)
ced1f5
+
ced1f5
 #define run_cache_req(ctx, send_fn, done_fn, dom, crp, lookup, expret) do { \
ced1f5
     TALLOC_CTX *req_mem_ctx;                                                \
ced1f5
     struct tevent_req *req;                                                 \
ced1f5
@@ -110,6 +116,7 @@ struct cache_req_test_ctx {
ced1f5
     struct sss_test_ctx *tctx;
ced1f5
     struct resp_ctx *rctx;
ced1f5
     struct sss_nc_ctx *ncache;
ced1f5
+    struct sss_domain_info *subdomain;
ced1f5
 
ced1f5
     struct cache_req_result *result;
ced1f5
     bool dp_called;
ced1f5
@@ -120,6 +127,8 @@ struct cache_req_test_ctx {
ced1f5
     bool create_user2;
ced1f5
     bool create_group1;
ced1f5
     bool create_group2;
ced1f5
+    bool create_subgroup1;
ced1f5
+    bool create_subuser1;
ced1f5
 };
ced1f5
 
ced1f5
 const char *domains[] = {"responder_cache_req_test_a",
ced1f5
@@ -128,6 +137,8 @@ const char *domains[] = {"responder_cache_req_test_a",
ced1f5
                          "responder_cache_req_test_d",
ced1f5
                          NULL};
ced1f5
 
ced1f5
+const char *subdomain_name = "responder_cache_req_test_a_sub";
ced1f5
+
ced1f5
 struct cli_protocol_version *register_cli_protocol_version(void)
ced1f5
 {
ced1f5
     static struct cli_protocol_version version[] = {
ced1f5
@@ -487,6 +498,26 @@ __wrap_sss_dp_get_account_send(TALLOC_CTX *mem_ctx,
ced1f5
         prepare_group(ctx->tctx->dom, &groups[1], 1000, time(NULL));
ced1f5
     }
ced1f5
 
ced1f5
+    if (ctx->create_subgroup1) {
ced1f5
+        struct sss_domain_info *domain = NULL;
ced1f5
+
ced1f5
+        domain = find_domain_by_name(ctx->tctx->dom,
ced1f5
+                                     subdomain_name,
ced1f5
+                                     true);
ced1f5
+        assert_non_null(domain);
ced1f5
+        prepare_group(domain, &groups[0], 1000, time(NULL));
ced1f5
+    }
ced1f5
+
ced1f5
+    if (ctx->create_subuser1) {
ced1f5
+        struct sss_domain_info *domain = NULL;
ced1f5
+
ced1f5
+        domain = find_domain_by_name(ctx->tctx->dom,
ced1f5
+                                     subdomain_name,
ced1f5
+                                     true);
ced1f5
+        assert_non_null(domain);
ced1f5
+        prepare_user(domain, &users[0], 1000, time(NULL));
ced1f5
+    }
ced1f5
+
ced1f5
     return test_req_succeed_send(mem_ctx, rctx->ev);
ced1f5
 }
ced1f5
 
ced1f5
@@ -581,6 +612,67 @@ static int test_multi_domain_teardown(void **state)
ced1f5
     return 0;
ced1f5
 }
ced1f5
 
ced1f5
+static int test_subdomain_setup(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    int ret;
ced1f5
+    const char *const testdom[4] = { subdomain_name, "TEST_A.SUB", "test_a", "S-3" };
ced1f5
+
ced1f5
+    assert_true(leak_check_setup());
ced1f5
+
ced1f5
+    test_dom_suite_setup(TESTS_PATH);
ced1f5
+
ced1f5
+    test_ctx = talloc_zero(global_talloc_context, struct cache_req_test_ctx);
ced1f5
+    assert_non_null(test_ctx);
ced1f5
+    *state = test_ctx;
ced1f5
+
ced1f5
+    test_ctx->tctx = create_dom_test_ctx(test_ctx, TESTS_PATH, TEST_CONF_DB,
ced1f5
+                                         TEST_DOM_NAME, TEST_ID_PROVIDER, NULL);
ced1f5
+    assert_non_null(test_ctx->tctx);
ced1f5
+
ced1f5
+    test_ctx->rctx = mock_rctx(test_ctx, test_ctx->tctx->ev,
ced1f5
+                               test_ctx->tctx->dom, NULL);
ced1f5
+    assert_non_null(test_ctx->rctx);
ced1f5
+
ced1f5
+    ret = sss_ncache_init(test_ctx, 10, 0, &test_ctx->ncache);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    test_ctx->subdomain = new_subdomain(test_ctx, test_ctx->tctx->dom,
ced1f5
+                              testdom[0], testdom[1], testdom[2], testdom[3],
ced1f5
+                              false, false, NULL, NULL, 0,
ced1f5
+                              test_ctx->tctx->confdb);
ced1f5
+    assert_non_null(test_ctx->subdomain);
ced1f5
+
ced1f5
+    ret = sysdb_subdomain_store(test_ctx->tctx->sysdb,
ced1f5
+                                testdom[0], testdom[1], testdom[2], testdom[3],
ced1f5
+                                false, false, NULL, 0, NULL);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    ret = sysdb_update_subdomains(test_ctx->tctx->dom,
ced1f5
+                                  test_ctx->tctx->confdb);
ced1f5
+    assert_int_equal(ret, EOK);
ced1f5
+
ced1f5
+    *state = test_ctx;
ced1f5
+    check_leaks_push(test_ctx);
ced1f5
+    return 0;
ced1f5
+}
ced1f5
+
ced1f5
+static int test_subdomain_teardown(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    talloc_zfree(test_ctx->result);
ced1f5
+    talloc_zfree(test_ctx->rctx->cr_domains);
ced1f5
+
ced1f5
+    assert_true(check_leaks_pop(test_ctx));
ced1f5
+    talloc_zfree(test_ctx);
ced1f5
+    test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
ced1f5
+    assert_true(leak_check_teardown());
ced1f5
+    return 0;
ced1f5
+}
ced1f5
+
ced1f5
 void test_user_by_name_multiple_domains_found(void **state)
ced1f5
 {
ced1f5
     struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
@@ -974,6 +1066,7 @@ void test_user_by_id_multiple_domains_found(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
@@ -990,12 +1083,317 @@ void test_user_by_id_multiple_domains_notfound(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_user_by_id(test_ctx, NULL, 0, ENOENT);
ced1f5
     assert_true(test_ctx->dp_called);
ced1f5
 }
ced1f5
 
ced1f5
+void test_user_by_id_multiple_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+    /* Even though the locator tells us to skip all domains except d, the domains
ced1f5
+     * are standalone and the result of the locator request is only valid within
ced1f5
+     * the subdomains
ced1f5
+     */
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_multiple_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_sub_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    /* Even though the ID is present in the last domain,
ced1f5
+     * we're not calling sss_dp_get_account_send,
ced1f5
+     * because the locator will cause cache_req to skip
ced1f5
+     * all domains except _d
ced1f5
+     */
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_sub_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_sub_domains_locator_cache_midpoint(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], 50, time(NULL) - 26);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once and we're not waiting
ced1f5
+     * for the results (so, we're not mocking _recv)
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 50, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_sub_domains_locator_missing_found(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    test_ctx->create_subuser1 = true;
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_sub_domains_locator_missing_notfound(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ENOENT);
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+}
ced1f5
+
ced1f5
+void test_user_by_id_sub_domains_locator_cache_expired_two_calls(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    test_ctx->create_subuser1 = true;
ced1f5
+    prepare_user(domain, &users[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    /* Request the same user again */
ced1f5
+    test_ctx->tctx->done = false;
ced1f5
+    talloc_zfree(test_ctx->result);
ced1f5
+
ced1f5
+    run_user_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
 void test_user_by_id_cache_valid(void **state)
ced1f5
 {
ced1f5
     struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
@@ -1332,6 +1730,7 @@ void test_group_by_id_multiple_domains_found(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
@@ -1348,12 +1747,318 @@ void test_group_by_id_multiple_domains_notfound(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_group_by_id(test_ctx, NULL, 0, ENOENT);
ced1f5
     assert_true(test_ctx->dp_called);
ced1f5
 }
ced1f5
 
ced1f5
+void test_group_by_id_multiple_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    /* Even though the locator tells us to skip all domains except d, the domains
ced1f5
+     * are standalone and the result of the locator request is only valid within
ced1f5
+     * the subdomains
ced1f5
+     */
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_multiple_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_sub_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    /* Even though the ID is present in the last domain,
ced1f5
+     * we're not calling sss_dp_get_account_send,
ced1f5
+     * because the locator will cause cache_req to skip
ced1f5
+     * all domains except _d
ced1f5
+     */
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_sub_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_sub_domains_locator_cache_midpoint(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], 50, time(NULL) - 26);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once and we're not waiting
ced1f5
+     * for the results (so, we're not mocking _recv)
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 50, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_sub_domains_locator_missing_found(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    test_ctx->create_subgroup1 = true;
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_sub_domains_locator_missing_notfound(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ENOENT);
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+}
ced1f5
+
ced1f5
+void test_group_by_id_sub_domains_locator_cache_expired_two_calls(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    test_ctx->create_subgroup1 = true;
ced1f5
+    prepare_group(domain, &groups[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    /* Request the same group again */
ced1f5
+    test_ctx->tctx->done = false;
ced1f5
+    talloc_zfree(test_ctx->result);
ced1f5
+
ced1f5
+    run_group_by_id(test_ctx, NULL, 0, ERR_OK);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
 void test_group_by_id_cache_valid(void **state)
ced1f5
 {
ced1f5
     struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
@@ -2311,6 +3016,7 @@ void test_object_by_id_user_multiple_domains_found(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK);
ced1f5
@@ -2328,6 +3034,7 @@ void test_object_by_id_user_multiple_domains_notfound(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ENOENT);
ced1f5
@@ -2476,6 +3183,7 @@ void test_object_by_id_group_multiple_domains_found(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK);
ced1f5
@@ -2493,12 +3201,641 @@ void test_object_by_id_group_multiple_domains_notfound(void **state)
ced1f5
     /* Mock values. */
ced1f5
     will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
     will_return_always(sss_dp_req_recv, 0);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
 
ced1f5
     /* Test. */
ced1f5
     run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ENOENT);
ced1f5
     assert_true(test_ctx->dp_called);
ced1f5
 }
ced1f5
 
ced1f5
+void test_object_by_id_user_multiple_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK);
ced1f5
+    /* Even though the locator tells us to skip all domains except d, the domains
ced1f5
+     * are standalone and the result of the locator request is only valid within
ced1f5
+     * the subdomains
ced1f5
+     */
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_multiple_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_sub_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    /* Even though the ID is present in the last domain,
ced1f5
+     * we're not calling sss_dp_get_account_send,
ced1f5
+     * because the locator will cause cache_req to skip
ced1f5
+     * all domains except _d
ced1f5
+     */
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_sub_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_sub_domains_locator_cache_midpoint(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_user(domain, &users[0], 50, time(NULL) - 26);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once and we're not waiting
ced1f5
+     * for the results (so, we're not mocking _recv)
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 50, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_sub_domains_locator_missing_found(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    test_ctx->create_subuser1 = true;
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_sub_domains_locator_missing_notfound(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND);
ced1f5
+
ced1f5
+    /* The test won't even ask the DP for the object, just iterate
ced1f5
+     * over the domains using the negative cache and quit
ced1f5
+     */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, ENOENT);
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_user_sub_domains_locator_cache_expired_two_calls(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup user. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    test_ctx->create_subuser1 = true;
ced1f5
+    prepare_user(domain, &users[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, EOK);
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    /* Request the same user again */
ced1f5
+    test_ctx->tctx->done = false;
ced1f5
+    talloc_zfree(test_ctx->result);
ced1f5
+
ced1f5
+    run_object_by_id(test_ctx, NULL, users[0].uid, attrs, 0, EOK);
ced1f5
+    check_user(test_ctx, &users[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_multiple_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK);
ced1f5
+    /* Even though the locator tells us to skip all domains except d, the domains
ced1f5
+     * are standalone and the result of the locator request is only valid within
ced1f5
+     * the subdomains
ced1f5
+     */
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_multiple_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, "responder_cache_req_test_d");
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 "responder_cache_req_test_d", true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, EOK);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+    will_return_always(sss_dp_get_account_domain_recv, ERR_GET_ACCT_DOM_NOT_SUPPORTED);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_sub_domains_locator_cache_valid(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], 1000, time(NULL));
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    /* Even though the ID is present in the last domain,
ced1f5
+     * we're not calling sss_dp_get_account_send,
ced1f5
+     * because the locator will cause cache_req to skip
ced1f5
+     * all domains except _d
ced1f5
+     */
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_sub_domains_locator_cache_expired(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_sub_domains_locator_cache_midpoint(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    prepare_group(domain, &groups[0], 50, time(NULL) - 26);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once and we're not waiting
ced1f5
+     * for the results (so, we're not mocking _recv)
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 50, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_sub_domains_locator_missing_found(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    test_ctx->create_subgroup1 = true;
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ERR_OK);
ced1f5
+
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_sub_domains_locator_missing_notfound(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, ERR_NOT_FOUND);
ced1f5
+
ced1f5
+    /* The test won't even ask the DP for the object, just iterate
ced1f5
+     * over the domains using the negative cache and quit
ced1f5
+     */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, ENOENT);
ced1f5
+    assert_false(test_ctx->dp_called);
ced1f5
+}
ced1f5
+
ced1f5
+void test_object_by_id_group_sub_domains_locator_cache_expired_two_calls(void **state)
ced1f5
+{
ced1f5
+    struct cache_req_test_ctx *test_ctx = NULL;
ced1f5
+    struct sss_domain_info *domain = NULL;
ced1f5
+    const char *locator_domain;
ced1f5
+    TALLOC_CTX *tmp_ctx;
ced1f5
+    const char *attrs[] = SYSDB_PW_ATTRS;
ced1f5
+
ced1f5
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
ced1f5
+
ced1f5
+    tmp_ctx = talloc_new(test_ctx);
ced1f5
+    assert_non_null(tmp_ctx);
ced1f5
+
ced1f5
+    /* Has to be a talloc ptr, not just const, so it's stealable inside cache_req */
ced1f5
+    locator_domain = talloc_strdup(tmp_ctx, subdomain_name);
ced1f5
+    assert_non_null(locator_domain);
ced1f5
+
ced1f5
+    /* Setup group. */
ced1f5
+    domain = find_domain_by_name(test_ctx->tctx->dom,
ced1f5
+                                 subdomain_name,
ced1f5
+                                 true);
ced1f5
+    assert_non_null(domain);
ced1f5
+    test_ctx->create_subgroup1 = true;
ced1f5
+    prepare_group(domain, &groups[0], -1000, time(NULL));
ced1f5
+
ced1f5
+    /* Note - DP will only be called once (so, we're not using will_return_always)
ced1f5
+     * because the locator will tell us which domain to look into. For the recv
ced1f5
+     * function, we use always b/c internally it mocks several values.
ced1f5
+     */
ced1f5
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
ced1f5
+    will_return_always(sss_dp_req_recv, 0);
ced1f5
+
ced1f5
+    will_return(sss_dp_get_account_domain_recv, EOK);
ced1f5
+    will_return(sss_dp_get_account_domain_recv, locator_domain);
ced1f5
+
ced1f5
+    /* Test. */
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, EOK);
ced1f5
+    assert_true(test_ctx->dp_called);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    /* Request the same group again */
ced1f5
+    test_ctx->tctx->done = false;
ced1f5
+    talloc_zfree(test_ctx->result);
ced1f5
+
ced1f5
+    run_object_by_id(test_ctx, NULL, groups[0].gid, attrs, 0, EOK);
ced1f5
+    check_group(test_ctx, &groups[0], domain);
ced1f5
+
ced1f5
+    talloc_free(tmp_ctx);
ced1f5
+}
ced1f5
+
ced1f5
 int main(int argc, const char *argv[])
ced1f5
 {
ced1f5
     poptContext pc;
ced1f5
@@ -2557,6 +3894,24 @@ int main(int argc, const char *argv[])
ced1f5
         new_multi_domain_test(group_by_id_multiple_domains_found),
ced1f5
         new_multi_domain_test(group_by_id_multiple_domains_notfound),
ced1f5
 
ced1f5
+        new_multi_domain_test(group_by_id_multiple_domains_locator_cache_valid),
ced1f5
+        new_multi_domain_test(group_by_id_multiple_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(group_by_id_sub_domains_locator_cache_valid),
ced1f5
+        new_subdomain_test(group_by_id_sub_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(group_by_id_sub_domains_locator_cache_midpoint),
ced1f5
+        new_subdomain_test(group_by_id_sub_domains_locator_missing_found),
ced1f5
+        new_subdomain_test(group_by_id_sub_domains_locator_missing_notfound),
ced1f5
+        new_subdomain_test(group_by_id_sub_domains_locator_cache_expired_two_calls),
ced1f5
+
ced1f5
+        new_multi_domain_test(user_by_id_multiple_domains_locator_cache_valid),
ced1f5
+        new_multi_domain_test(user_by_id_multiple_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(user_by_id_sub_domains_locator_cache_valid),
ced1f5
+        new_subdomain_test(user_by_id_sub_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(user_by_id_sub_domains_locator_cache_midpoint),
ced1f5
+        new_subdomain_test(user_by_id_sub_domains_locator_missing_found),
ced1f5
+        new_subdomain_test(user_by_id_sub_domains_locator_missing_notfound),
ced1f5
+        new_subdomain_test(user_by_id_sub_domains_locator_cache_expired_two_calls),
ced1f5
+
ced1f5
         new_single_domain_test(user_by_recent_filter_valid),
ced1f5
         new_single_domain_test(users_by_recent_filter_valid),
ced1f5
         new_single_domain_test(group_by_recent_filter_valid),
ced1f5
@@ -2603,6 +3958,24 @@ int main(int argc, const char *argv[])
ced1f5
         new_single_domain_test(object_by_id_group_missing_notfound),
ced1f5
         new_multi_domain_test(object_by_id_group_multiple_domains_found),
ced1f5
         new_multi_domain_test(object_by_id_group_multiple_domains_notfound),
ced1f5
+
ced1f5
+        new_multi_domain_test(object_by_id_user_multiple_domains_locator_cache_valid),
ced1f5
+        new_multi_domain_test(object_by_id_user_multiple_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(object_by_id_user_sub_domains_locator_cache_valid),
ced1f5
+        new_subdomain_test(object_by_id_user_sub_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(object_by_id_user_sub_domains_locator_cache_midpoint),
ced1f5
+        new_subdomain_test(object_by_id_user_sub_domains_locator_missing_found),
ced1f5
+        new_subdomain_test(object_by_id_user_sub_domains_locator_missing_notfound),
ced1f5
+        new_subdomain_test(object_by_id_user_sub_domains_locator_cache_expired_two_calls),
ced1f5
+
ced1f5
+        new_multi_domain_test(object_by_id_group_multiple_domains_locator_cache_valid),
ced1f5
+        new_multi_domain_test(object_by_id_group_multiple_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(object_by_id_group_sub_domains_locator_cache_valid),
ced1f5
+        new_subdomain_test(object_by_id_group_sub_domains_locator_cache_expired),
ced1f5
+        new_subdomain_test(object_by_id_group_sub_domains_locator_cache_midpoint),
ced1f5
+        new_subdomain_test(object_by_id_group_sub_domains_locator_missing_found),
ced1f5
+        new_subdomain_test(object_by_id_group_sub_domains_locator_missing_notfound),
ced1f5
+        new_subdomain_test(object_by_id_group_sub_domains_locator_cache_expired_two_calls),
ced1f5
     };
ced1f5
 
ced1f5
     /* Set debug level to invalid value so we can deside if -d 0 was used. */
ced1f5
-- 
ced1f5
2.14.3
ced1f5