From 22441874de9eaeda8220d8f51ad1753009f3d3a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Fri, 5 Sep 2014 13:52:31 +0200
Subject: [PATCH 5/7] responders: new interface for cache request

Many areas of responders performs an expiration check and refresh
of cached objects during single or multiple domain search. This code
is duplicated on many areas of the code with small or none
modifications.

This interface aims to reduce code duplication between responders,
by providing one universal API for requesting cached objects.

This API will take care of cache lookup, expiration check, cache
refresh, out of band cache request, negative cache in both single
and multi domain searches.
---
 Makefile.am                                 |  24 +-
 src/responder/common/responder_cache_req.c  | 560 ++++++++++++++++++++++++++++
 src/responder/common/responder_cache_req.h  |  66 ++++
 src/tests/cmocka/test_responder_cache_req.c | 529 ++++++++++++++++++++++++++
 4 files changed, 1178 insertions(+), 1 deletion(-)
 create mode 100644 src/responder/common/responder_cache_req.c
 create mode 100644 src/responder/common/responder_cache_req.h
 create mode 100644 src/tests/cmocka/test_responder_cache_req.c

diff --git a/Makefile.am b/Makefile.am
index 53ace65b9a9647ffdaff0776d5a55d3e7393a38c..024992b3dc4d683cad952688bfc56e9cb0551b75 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -214,6 +214,7 @@ if HAVE_CMOCKA
         test_search_bases \
         sdap-tests \
         test_sysdb_views \
+        responder_cache_req-tests \
         $(NULL)
 
 if BUILD_IFP
@@ -409,6 +410,7 @@ SSSD_RESPONDER_OBJ = \
     src/responder/common/responder_packet.c \
     src/responder/common/responder_get_domains.c \
     src/responder/common/responder_utils.c \
+    src/responder/common/responder_cache_req.c \
     src/monitor/monitor_iface_generated.c \
     src/monitor/monitor_iface_generated.h \
     src/providers/data_provider_iface_generated.c \
@@ -520,6 +522,7 @@ dist_noinst_HEADERS = \
     src/responder/common/responder.h \
     src/responder/common/responder_packet.h \
     src/responder/common/responder_sbus.h \
+    src/responder/common/responder_cache_req.h \
     src/responder/pam/pamsrv.h \
     src/responder/pam/pam_helpers.h \
     src/responder/nss/nsssrv.h \
@@ -1674,7 +1677,8 @@ TEST_MOCK_RESP_OBJ = \
      src/responder/common/responder_packet.c \
      src/responder/common/responder_cmd.c \
      src/responder/common/negcache.c \
-     src/responder/common/responder_common.c
+     src/responder/common/responder_common.c \
+     src/responder/common/responder_cache_req.c
 
 TEST_MOCK_PROVIDER_OBJ = \
      src/util/sss_ldap.c \
@@ -2078,6 +2082,24 @@ test_sysdb_views_LDADD = \
     libsss_test_common.la \
     $(NULL)
 
+responder_cache_req_tests_SOURCES = \
+    $(TEST_MOCK_OBJ) \
+    $(TEST_MOCK_RESP_OBJ) \
+    src/tests/cmocka/test_responder_cache_req.c \
+    $(NULL)
+responder_cache_req_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(NULL)
+responder_cache_req_tests_LDFLAGS = \
+    -Wl,-wrap,sss_dp_get_account_send \
+    $(NULL)
+responder_cache_req_tests_LDADD = \
+    $(CMOCKA_LIBS) \
+    $(SSSD_LIBS) \
+    $(SSSD_INTERNAL_LTLIBS) \
+    libsss_test_common.la \
+    $(NULL)
+
 endif # HAVE_CMOCKA
 
 noinst_PROGRAMS = pam_test_client
diff --git a/src/responder/common/responder_cache_req.c b/src/responder/common/responder_cache_req.c
new file mode 100644
index 0000000000000000000000000000000000000000..dad4df65c5c99b2fefbe0394a9c394115299bc75
--- /dev/null
+++ b/src/responder/common/responder_cache_req.c
@@ -0,0 +1,560 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <dbus/dbus.h>
+#include <ldb.h>
+#include <talloc.h>
+#include <tevent.h>
+
+#include "util/util.h"
+#include "db/sysdb.h"
+#include "responder/common/responder_cache_req.h"
+
+static errno_t cache_req_check_ncache(enum sss_dp_acct_type dp_type,
+                                      struct sss_nc_ctx *ncache,
+                                      int neg_timeout,
+                                      struct sss_domain_info *domain,
+                                      const char *name)
+{
+    errno_t ret;
+
+    switch (dp_type) {
+    case SSS_DP_USER:
+    case SSS_DP_INITGROUPS:
+        ret = sss_ncache_check_user(ncache, neg_timeout, domain, name);
+        break;
+    default:
+        ret = EINVAL;
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported DP request type\n");
+        break;
+    }
+
+    if (ret == EEXIST) {
+        DEBUG(SSSDBG_TRACE_FUNC, "[%s] does not exist in [%s]! "
+              "(negative cache)\n", name, domain->name);
+    }
+
+    return ret;
+}
+
+static void cache_req_add_to_ncache(enum sss_dp_acct_type dp_type,
+                                    struct sss_nc_ctx *ncache,
+                                    struct sss_domain_info *domain,
+                                    const char *name)
+{
+    errno_t ret;
+
+    switch (dp_type) {
+    case SSS_DP_USER:
+    case SSS_DP_INITGROUPS:
+        ret = sss_ncache_set_user(ncache, false, domain, name);
+        break;
+    default:
+        ret = EINVAL;
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported DP request type\n");
+        break;
+    }
+
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE, "Cannot set negcache for %s@%s [%d]: %s\n",
+              name, domain->name, ret, sss_strerror(ret));
+
+        /* not fatal */
+    }
+
+    return;
+}
+
+static errno_t cache_req_get_object(TALLOC_CTX *mem_ctx,
+                                    enum sss_dp_acct_type dp_type,
+                                    struct sss_domain_info *domain,
+                                    const char *name,
+                                    struct ldb_result **_result)
+{
+    struct ldb_result *result = NULL;
+    bool one_item_only;
+    errno_t ret;
+
+    DEBUG(SSSDBG_FUNC_DATA, "Requesting info for [%s@%s]\n",
+          name, domain->name);
+
+    switch (dp_type) {
+    case SSS_DP_USER:
+        one_item_only = true;
+        ret = sysdb_getpwnam(mem_ctx, domain, name, &result);
+        break;
+    case SSS_DP_INITGROUPS:
+        one_item_only = false;
+        ret = sysdb_initgroups(mem_ctx, domain, name, &result);
+        break;
+    default:
+        ret = EINVAL;
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported DP request type\n");
+        break;
+    }
+
+    if (ret != EOK) {
+        goto done;
+    } else if (result->count == 0) {
+        ret = ENOENT;
+        goto done;
+    } else if (one_item_only && result->count > 1) {
+        ret = ENOENT;
+        DEBUG(SSSDBG_CRIT_FAILURE, "Multiple objects were found when"
+              "sysdb search expected only one!\n");
+        goto done;
+    }
+
+    *_result = result;
+
+done:
+    return ret;
+}
+
+struct cache_req_cache_state {
+    /* input data */
+    struct tevent_context *ev;
+    struct resp_ctx *rctx;
+    struct sss_nc_ctx *ncache;
+    int neg_timeout;
+    int cache_refresh_percent;
+    enum sss_dp_acct_type dp_type;
+    struct sss_domain_info *domain;
+    const char *name;
+
+    /* output data */
+    struct ldb_result *result;
+};
+
+static errno_t cache_req_cache_search(struct tevent_req *req);
+static errno_t cache_req_cache_check(struct tevent_req *req);
+static void cache_req_cache_done(struct tevent_req *subreq);
+
+static struct tevent_req *cache_req_cache_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               struct resp_ctx *rctx,
+                                               struct sss_nc_ctx *ncache,
+                                               int neg_timeout,
+                                               int cache_refresh_percent,
+                                               enum sss_dp_acct_type dp_type,
+                                               struct sss_domain_info *domain,
+                                               const char *name)
+{
+    struct cache_req_cache_state *state = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct cache_req_cache_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_req_create() failed\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->rctx = rctx;
+    state->ncache = ncache;
+    state->neg_timeout = neg_timeout;
+    state->cache_refresh_percent = cache_refresh_percent;
+    state->dp_type = dp_type;
+    state->domain = domain;
+
+    /* Sanitize input name. */
+    state->name = sss_get_cased_name(state, name, domain->case_sensitive);
+    if (state->name == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    state->name = sss_reverse_replace_space(state, state->name,
+                                            state->rctx->override_space);
+    if (state->name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "sss_reverse_replace_space failed\n");
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    /* Check negative cache first. */
+    ret = cache_req_check_ncache(state->dp_type, state->ncache,
+                                 state->neg_timeout, state->domain,
+                                 state->name);
+    if (ret == EEXIST) {
+        ret = ENOENT;
+        goto immediately;
+    }
+
+    /* We will first search the cache. If we get cache miss or the entry
+     * is expired we will contact data provider and then search again. */
+    ret = cache_req_cache_search(req);
+    if (ret != EAGAIN) {
+        goto immediately;
+    }
+
+    return req;
+
+immediately:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+
+    return req;
+}
+
+static errno_t cache_req_cache_search(struct tevent_req *req)
+{
+    struct cache_req_cache_state *state = NULL;
+    errno_t ret;
+
+    state = tevent_req_data(req, struct cache_req_cache_state);
+
+    ret = cache_req_get_object(state, state->dp_type, state->domain,
+                               state->name, &state->result);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to make request to our cache "
+              "[%d]: %s\n", ret, sss_strerror(ret));
+        return ret;
+    }
+
+    /* Verify that the cache is up to date. */
+    ret = cache_req_cache_check(req);
+    if (req != EOK) {
+        return ret;
+    }
+
+    /* One result found */
+    DEBUG(SSSDBG_TRACE_FUNC,
+          "Returning info for [%s@%s]\n", state->name, state->domain->name);
+    return EOK;
+}
+
+static errno_t cache_req_cache_check(struct tevent_req *req)
+{
+    struct cache_req_cache_state *state = NULL;
+    struct tevent_req *subreq = NULL;
+    uint64_t cache_expire = 0;
+    errno_t ret;
+
+    state = tevent_req_data(req, struct cache_req_cache_state);
+
+    if (state->result == NULL || state->result->count == 0) {
+        ret = ENOENT;
+    } else {
+        if (state->dp_type == SSS_DP_INITGROUPS) {
+            cache_expire = ldb_msg_find_attr_as_uint64(state->result->msgs[0],
+                                                       SYSDB_INITGR_EXPIRE, 0);
+        } else {
+            cache_expire = ldb_msg_find_attr_as_uint64(state->result->msgs[0],
+                                                       SYSDB_CACHE_EXPIRE, 0);
+        }
+
+        ret = sss_cmd_check_cache(state->result->msgs[0],
+                                  state->cache_refresh_percent, cache_expire);
+    }
+
+    switch (ret) {
+    case EOK:
+        DEBUG(SSSDBG_TRACE_FUNC, "Cached entry is valid, returning...\n");
+        return EOK;
+    case EAGAIN:
+        /* Out of band update. The calling function will return the cached
+         * entry immediately. No callback is required. */
+
+        DEBUG(SSSDBG_TRACE_FUNC, "Performing midpoint cache update\n");
+
+        subreq = sss_dp_get_account_send(state, state->rctx, state->domain,
+                                         true, state->dp_type, state->name,
+                                         0, NULL);
+        if (subreq == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory sending out-of-band "
+                                       "data provider request\n");
+            /* This is non-fatal, so we'll continue here */
+        } else {
+            DEBUG(SSSDBG_TRACE_FUNC, "Updating cache out-of-band\n");
+        }
+
+        return EOK;
+    case ENOENT:
+        /* Cache miss or the cache is expired. We need to get the updated
+         * information before returning it. */
+
+        subreq = sss_dp_get_account_send(state, state->rctx, state->domain,
+                                         true, state->dp_type, state->name,
+                                         0, NULL);
+        if (subreq == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  "Out of memory sending data provider request\n");
+            return ENOMEM;
+        }
+
+        tevent_req_set_callback(subreq, cache_req_cache_done, req);
+        return EAGAIN;
+    default:
+        /* error */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Error checking cache [%d]: %s\n",
+                                   ret, sss_strerror(ret));
+        return ret;
+    }
+}
+
+static void cache_req_cache_done(struct tevent_req *subreq)
+{
+    struct cache_req_cache_state *state = NULL;
+    struct tevent_req *req = NULL;
+    char *err_msg = NULL;
+    dbus_uint16_t err_maj;
+    dbus_uint32_t err_min;
+    errno_t ret;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct cache_req_cache_state);
+
+    ret = sss_dp_get_account_recv(state, subreq, &err_maj, &err_min, &err_msg);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Could not get account info [%d]: %s\n",
+                                 ret, sss_strerror(ret));
+    }
+
+    if (err_maj) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Unable to get information from Data Provider\n"
+              "Error: %u, %u, %s\n"
+              "Will try to return what we have in cache\n",
+              (unsigned int)err_maj, (unsigned int)err_min, err_msg);
+    }
+
+    /* Get result from cache again. */
+    ret = cache_req_get_object(state, state->dp_type, state->domain,
+                               state->name, &state->result);
+    if (ret == ENOENT) {
+        cache_req_add_to_ncache(state->dp_type, state->ncache,
+                                state->domain, state->name);
+        ret = ENOENT;
+    } else if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to make request to our cache "
+              "[%d]: %s\n", ret, sss_strerror(ret));
+    }
+
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    /* One result found */
+    DEBUG(SSSDBG_TRACE_FUNC,
+          "Returning info for [%s@%s]\n", state->name, state->domain->name);
+
+    tevent_req_done(req);
+}
+
+static errno_t cache_req_cache_recv(TALLOC_CTX *mem_ctx,
+                                    struct tevent_req *req,
+                                    struct ldb_result **_result)
+{
+    struct cache_req_cache_state *state = NULL;
+    state = tevent_req_data(req, struct cache_req_cache_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *_result = talloc_steal(mem_ctx, state->result);
+
+    return EOK;
+}
+
+
+struct cache_req_state {
+    /* input data */
+    struct tevent_context *ev;
+    struct resp_ctx *rctx;
+    struct sss_nc_ctx *ncache;
+    int neg_timeout;
+    int cache_refresh_percent;
+    const char *name;
+    enum sss_dp_acct_type dp_type;
+
+    /* work data */
+    struct ldb_result *result;
+    struct sss_domain_info *domain;
+    struct sss_domain_info *selected_domain;
+    bool check_next;
+};
+
+static errno_t cache_req_next_domain(struct tevent_req *req);
+static void cache_req_done(struct tevent_req *subreq);
+
+struct tevent_req *cache_req_send(TALLOC_CTX *mem_ctx,
+                                  struct tevent_context *ev,
+                                  struct resp_ctx *rctx,
+                                  struct sss_nc_ctx *ncache,
+                                  int neg_timeout,
+                                  int cache_refresh_percent,
+                                  enum sss_dp_acct_type dp_type,
+                                  const char *domain,
+                                  const char *name)
+{
+    struct cache_req_state *state = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    req = tevent_req_create(mem_ctx, &state, struct cache_req_state);
+    if (req == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("tevent_req_create() failed\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->rctx = rctx;
+    state->ncache = ncache;
+    state->neg_timeout = neg_timeout;
+    state->cache_refresh_percent = cache_refresh_percent;
+    state->dp_type = dp_type;
+    state->name = talloc_strdup(state, name);
+    if (state->name == NULL) {
+        ret = ENOMEM;
+        goto immediately;
+    }
+
+    if (domain != NULL) {
+        /* single-domain search */
+        state->domain = responder_get_domain(state->rctx, domain);
+        if (state->domain == NULL) {
+            ret = EINVAL;
+            goto immediately;
+        }
+
+        state->check_next = false;
+    } else {
+        /* multi-domain search */
+        state->domain = state->rctx->domains;
+        state->check_next = true;
+    }
+
+    ret = cache_req_next_domain(req);
+    if (ret != EAGAIN) {
+        goto immediately;
+    }
+
+    return req;
+
+immediately:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+
+    return req;
+}
+
+static errno_t cache_req_next_domain(struct tevent_req *req)
+{
+    struct cache_req_state *state = NULL;
+    struct tevent_req *subreq = NULL;
+
+    state = tevent_req_data(req, struct cache_req_state);
+
+    while (state->domain != NULL) {
+       /* If it is a domainless search, skip domains that require fully
+        * qualified names instead. */
+        while (state->domain != NULL && state->check_next
+                && state->domain->fqnames) {
+            state->domain = get_next_domain(state->domain, false);
+        }
+
+        state->selected_domain = state->domain;
+
+        if (state->domain == NULL) {
+            break;
+        }
+
+        subreq = cache_req_cache_send(state, state->ev, state->rctx,
+                                      state->ncache, state->neg_timeout,
+                                      state->cache_refresh_percent,
+                                      state->dp_type, state->domain,
+                                      state->name);
+        if (subreq == NULL) {
+            return ENOMEM;
+        }
+
+        tevent_req_set_callback(subreq, cache_req_done, req);
+
+        /* we will continue with the following domain the next time */
+        if (state->check_next) {
+            state->domain = get_next_domain(state->domain, false);
+        }
+
+        return EAGAIN;
+    }
+
+    return ENOENT;
+}
+
+static void cache_req_done(struct tevent_req *subreq)
+{
+    struct cache_req_state *state = NULL;
+    struct tevent_req *req = NULL;
+    errno_t ret;
+
+    req = tevent_req_callback_data(subreq, struct tevent_req);
+    state = tevent_req_data(req, struct cache_req_state);
+
+    ret = cache_req_cache_recv(state, subreq, &state->result);
+    talloc_zfree(subreq);
+    if (ret == EOK) {
+        tevent_req_done(req);
+        return;
+    }
+
+    if (state->check_next == false) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = cache_req_next_domain(req);
+    if (ret != EAGAIN) {
+        tevent_req_error(req, ret);
+    }
+
+    return;
+}
+
+errno_t cache_req_recv(TALLOC_CTX *mem_ctx,
+                       struct tevent_req *req,
+                       struct ldb_result **_result,
+                       struct sss_domain_info **_domain)
+{
+    struct cache_req_state *state = NULL;
+    state = tevent_req_data(req, struct cache_req_state);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    if (_result != NULL) {
+        *_result = talloc_steal(mem_ctx, state->result);
+    }
+
+    if (_domain != NULL) {
+        *_domain = state->selected_domain;
+    }
+
+    return EOK;
+}
diff --git a/src/responder/common/responder_cache_req.h b/src/responder/common/responder_cache_req.h
new file mode 100644
index 0000000000000000000000000000000000000000..fdad94a13f521e5f32bde74f64898c867edf2ef8
--- /dev/null
+++ b/src/responder/common/responder_cache_req.h
@@ -0,0 +1,66 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef RESPONDER_CACHE_H_
+#define RESPONDER_CACHE_H_
+
+#include <talloc.h>
+#include <tevent.h>
+#include "db/sysdb.h"
+#include "responder/common/responder.h"
+#include "responder/common/negcache.h"
+
+/**
+ * Currently only SSS_DP_USER and SSS_DP_INITGROUPS are supported.
+ *
+ * @todo support other request types
+ */
+struct tevent_req *cache_req_send(TALLOC_CTX *mem_ctx,
+                                  struct tevent_context *ev,
+                                  struct resp_ctx *rctx,
+                                  struct sss_nc_ctx *ncache,
+                                  int neg_timeout,
+                                  int cache_refresh_percent,
+                                  enum sss_dp_acct_type dp_type,
+                                  const char *domain,
+                                  const char *name);
+
+errno_t cache_req_recv(TALLOC_CTX *mem_ctx,
+                       struct tevent_req *req,
+                       struct ldb_result **_result,
+                       struct sss_domain_info **_domain);
+
+#define cache_req_user_by_name_send(mem_ctx, ev, rctx, ncache, neg_timeout, \
+                                    cache_refresh_percent, domain, name) \
+    cache_req_send(mem_ctx, ev, rctx, ncache, neg_timeout, \
+                   cache_refresh_percent, SSS_DP_USER, domain, name)
+
+#define cache_req_user_by_name_recv(mem_ctx, req, _result, _domain) \
+    cache_req_recv(mem_ctx, req, _result, _domain)
+
+#define cache_req_initgr_by_name_send(mem_ctx, ev, rctx, ncache, neg_timeout, \
+                                      cache_refresh_percent, domain, name) \
+    cache_req_send(mem_ctx, ev, rctx, ncache, neg_timeout, \
+                   cache_refresh_percent, SSS_DP_INITGROUPS, domain, name)
+
+#define cache_req_initgr_by_name_recv(mem_ctx, req, _result, _domain) \
+    cache_req_recv(mem_ctx, req, _result, _domain)
+
+#endif /* RESPONDER_CACHE_H_ */
diff --git a/src/tests/cmocka/test_responder_cache_req.c b/src/tests/cmocka/test_responder_cache_req.c
new file mode 100644
index 0000000000000000000000000000000000000000..694e8dd46d2d18b06ea364e0d10d0c3f12c8b265
--- /dev/null
+++ b/src/tests/cmocka/test_responder_cache_req.c
@@ -0,0 +1,529 @@
+/*
+    Authors:
+        Pavel Březina <pbrezina@redhat.com>
+
+    Copyright (C) 2014 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <errno.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+#include "db/sysdb.h"
+#include "responder/common/responder_cache_req.h"
+
+#define TESTS_PATH "tests_responder_cache_req"
+#define TEST_CONF_DB "test_responder_cache_req_conf.ldb"
+#define TEST_DOM_NAME "responder_cache_req_test"
+#define TEST_ID_PROVIDER "ldap"
+
+#define new_single_domain_test(test) \
+    unit_test_setup_teardown(test_ ## test, \
+                             test_single_domain_setup, \
+                             test_single_domain_teardown)
+
+#define new_multi_domain_test(test) \
+    unit_test_setup_teardown(test_ ## test, \
+                             test_multi_domain_setup, \
+                             test_multi_domain_teardown)
+
+struct cache_req_test_ctx {
+    struct sss_test_ctx *tctx;
+    struct resp_ctx *rctx;
+    struct sss_nc_ctx *ncache;
+
+    struct ldb_result *result;
+    struct sss_domain_info *domain;
+    bool dp_called;
+    bool create_user;
+};
+
+const char *domains[] = {"responder_cache_req_test_a",
+                         "responder_cache_req_test_b",
+                         "responder_cache_req_test_c",
+                         "responder_cache_req_test_d",
+                         NULL};
+
+struct cli_protocol_version *register_cli_protocol_version(void)
+{
+    static struct cli_protocol_version version[] = {
+        { 0, NULL, NULL }
+    };
+
+    return version;
+}
+
+struct tevent_req *
+__wrap_sss_dp_get_account_send(TALLOC_CTX *mem_ctx,
+                               struct resp_ctx *rctx,
+                               struct sss_domain_info *dom,
+                               bool fast_reply,
+                               enum sss_dp_acct_type type,
+                               const char *opt_name,
+                               uint32_t opt_id,
+                               const char *extra)
+{
+    struct cache_req_test_ctx *ctx = NULL;
+    errno_t ret;
+
+    ctx = sss_mock_ptr_type(struct cache_req_test_ctx*);
+    ctx->dp_called = true;
+
+    if (ctx->create_user) {
+        ret = sysdb_store_user(ctx->tctx->dom, "test-user", "pwd", 1000, 1000,
+                               NULL, NULL, NULL, "cn=test-user,dc=test", NULL,
+                               NULL, 1000, time(NULL));
+        assert_int_equal(ret, EOK);
+    }
+
+    return test_req_succeed_send(mem_ctx, rctx->ev);
+}
+
+static void cache_req_user_test_done(struct tevent_req *req)
+{
+    struct cache_req_test_ctx *ctx = NULL;
+
+    ctx = tevent_req_callback_data(req, struct cache_req_test_ctx);
+
+    ctx->tctx->error = cache_req_user_by_name_recv(ctx, req,
+                                                   &ctx->result, &ctx->domain);
+    talloc_zfree(req);
+
+    ctx->tctx->done = true;
+}
+
+void test_single_domain_setup(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    errno_t ret;
+
+    test_dom_suite_setup(TESTS_PATH);
+
+    test_ctx = talloc_zero(NULL, struct cache_req_test_ctx);
+    assert_non_null(test_ctx);
+    *state = test_ctx;
+
+    test_ctx->tctx = create_dom_test_ctx(test_ctx, TESTS_PATH, TEST_CONF_DB,
+                                         TEST_DOM_NAME, TEST_ID_PROVIDER, NULL);
+    assert_non_null(test_ctx->tctx);
+
+    test_ctx->rctx = mock_rctx(test_ctx, test_ctx->tctx->ev,
+                               test_ctx->tctx->dom, NULL);
+    assert_non_null(test_ctx->rctx);
+
+    ret = sss_ncache_init(test_ctx, &test_ctx->ncache);
+    assert_int_equal(ret, EOK);
+}
+
+void test_single_domain_teardown(void **state)
+{
+    talloc_zfree(*state);
+    test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+}
+
+void test_multi_domain_setup(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    errno_t ret;
+
+    test_dom_suite_setup(TESTS_PATH);
+
+    test_ctx = talloc_zero(NULL, struct cache_req_test_ctx);
+    assert_non_null(test_ctx);
+    *state = test_ctx;
+
+    test_ctx->tctx = create_multidom_test_ctx(test_ctx, TESTS_PATH,
+                                              TEST_CONF_DB, domains,
+                                              TEST_ID_PROVIDER, NULL);
+    assert_non_null(test_ctx->tctx);
+
+    test_ctx->rctx = mock_rctx(test_ctx, test_ctx->tctx->ev,
+                               test_ctx->tctx->dom, NULL);
+    assert_non_null(test_ctx->rctx);
+
+    ret = sss_ncache_init(test_ctx, &test_ctx->ncache);
+    assert_int_equal(ret, EOK);
+}
+
+void test_multi_domain_teardown(void **state)
+{
+    talloc_zfree(*state);
+    test_multidom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, domains);
+}
+
+void test_user_multiple_domains_found(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    struct sss_domain_info *domain = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    domain = find_domain_by_name(test_ctx->tctx->dom,
+                                 "responder_cache_req_test_d", true);
+    assert_non_null(domain);
+
+    ret = sysdb_store_user(domain, name, "pwd", 1000, 1000,
+                           NULL, NULL, NULL, "cn=test-user,dc=test", NULL,
+                           NULL, 1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
+    will_return_always(sss_dp_get_account_recv, 0);
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 10, 0,
+                                      NULL, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_true(test_ctx->dp_called);
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 1);
+    assert_non_null(test_ctx->result->msgs);
+    assert_non_null(test_ctx->result->msgs[0]);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, name);
+
+    assert_non_null(test_ctx->domain);
+    assert_string_equal(domain->name, test_ctx->domain->name);
+}
+
+void test_user_multiple_domains_notfound(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    will_return_always(__wrap_sss_dp_get_account_send, test_ctx);
+    will_return_always(sss_dp_get_account_recv, 0);
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 10, 0,
+                                      NULL, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_true(test_ctx->dp_called);
+}
+
+void test_user_cache_valid(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    ret = sysdb_store_user(test_ctx->tctx->dom, name, "pwd", 1000, 1000,
+                           NULL, NULL, NULL, "cn=test-user,dc=test", NULL,
+                           NULL, 1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 10, 0,
+                                      test_ctx->tctx->dom->name, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 1);
+    assert_non_null(test_ctx->result->msgs);
+    assert_non_null(test_ctx->result->msgs[0]);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, name);
+}
+
+void test_user_cache_expired(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    ret = sysdb_store_user(test_ctx->tctx->dom, name, "pwd", 1000, 1000,
+                           NULL, NULL, NULL, "cn=test-user,dc=test", NULL,
+                           NULL, -1000, time(NULL));
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* DP should be contacted */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 10, 0,
+                                      test_ctx->tctx->dom->name, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_true(test_ctx->dp_called);
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 1);
+    assert_non_null(test_ctx->result->msgs);
+    assert_non_null(test_ctx->result->msgs[0]);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, name);
+}
+
+void test_user_cache_midpoint(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    ret = sysdb_store_user(test_ctx->tctx->dom, name, "pwd", 1000, 1000,
+                           NULL, NULL, NULL, "cn=test-user,dc=test", NULL,
+                           NULL, 50, time(NULL) - 26);
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    /* DP should be contacted without callback */
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 10, 50,
+                                      test_ctx->tctx->dom->name, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_true(test_ctx->dp_called);
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 1);
+    assert_non_null(test_ctx->result->msgs);
+    assert_non_null(test_ctx->result->msgs[0]);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, name);
+}
+
+void test_user_ncache(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    ret = sss_ncache_set_user(test_ctx->ncache, false,
+                              test_ctx->tctx->dom, name);
+    assert_int_equal(ret, EOK);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 100, 0,
+                                      test_ctx->tctx->dom->name, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_false(test_ctx->dp_called);
+}
+
+void test_user_missing_found(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    const char *ldbname = NULL;
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    test_ctx->create_user = true;
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 100, 0,
+                                      test_ctx->tctx->dom->name, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ERR_OK);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_true(test_ctx->dp_called);
+
+    assert_non_null(test_ctx->result);
+    assert_int_equal(test_ctx->result->count, 1);
+    assert_non_null(test_ctx->result->msgs);
+    assert_non_null(test_ctx->result->msgs[0]);
+
+    ldbname = ldb_msg_find_attr_as_string(test_ctx->result->msgs[0],
+                                          SYSDB_NAME, NULL);
+    assert_non_null(ldbname);
+    assert_string_equal(ldbname, name);
+}
+
+void test_user_missing_notfound(void **state)
+{
+    struct cache_req_test_ctx *test_ctx = NULL;
+    TALLOC_CTX *req_mem_ctx = NULL;
+    struct tevent_req *req = NULL;
+    const char *name = "test-user";
+    errno_t ret;
+
+    test_ctx = talloc_get_type_abort(*state, struct cache_req_test_ctx);
+
+    req_mem_ctx = talloc_new(global_talloc_context);
+    check_leaks_push(req_mem_ctx);
+
+    will_return(__wrap_sss_dp_get_account_send, test_ctx);
+    mock_account_recv_simple();
+
+    req = cache_req_user_by_name_send(req_mem_ctx, test_ctx->tctx->ev,
+                                      test_ctx->rctx, test_ctx->ncache, 100, 0,
+                                      test_ctx->tctx->dom->name, name);
+    assert_non_null(req);
+    tevent_req_set_callback(req, cache_req_user_test_done, test_ctx);
+
+    ret = test_ev_loop(test_ctx->tctx);
+    assert_int_equal(ret, ENOENT);
+    assert_true(check_leaks_pop(req_mem_ctx));
+
+    assert_true(test_ctx->dp_called);
+}
+
+int main(int argc, const char *argv[])
+{
+    poptContext pc;
+    int opt;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const UnitTest tests[] = {
+        new_single_domain_test(user_cache_valid),
+        new_single_domain_test(user_cache_expired),
+        new_single_domain_test(user_cache_midpoint),
+        new_single_domain_test(user_ncache),
+        new_single_domain_test(user_missing_found),
+        new_single_domain_test(user_missing_notfound),
+        new_multi_domain_test(user_multiple_domains_found),
+        new_multi_domain_test(user_multiple_domains_notfound)
+    };
+
+    /* Set debug level to invalid value so we can deside if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+       default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old db to be sure */
+    tests_set_cwd();
+    test_multidom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, domains);
+    test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+
+    return run_tests(tests);
+}
-- 
1.7.11.7

