nfsd4: allow 4.0 clients to change callback path

The rfc allows a client to change the callback parameters, but we didn't
previously implement it.

Teach the callbacks to rerun themselves (by placing themselves on a
workqueue) when they recognize that their rpc task has been killed and
that the callback connection has changed.

Then we can change the callback connection by setting up a new rpc
client, modifying the nfs4 client to point at it, waiting for any work
in progress to complete, and then shutting down the old client.

Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
This commit is contained in:
J. Bruce Fields 2010-03-07 23:39:01 -05:00
parent 2bf23875f5
commit 4b21d0defc
3 changed files with 24 additions and 14 deletions

View File

@ -457,9 +457,8 @@ static int max_cb_time(void)
/* Reference counting, callback cleanup, etc., all look racy as heck. /* Reference counting, callback cleanup, etc., all look racy as heck.
* And why is cl_cb_set an atomic? */ * And why is cl_cb_set an atomic? */
int setup_callback_client(struct nfs4_client *clp) int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *cb)
{ {
struct nfs4_cb_conn *cb = &clp->cl_cb_conn;
struct rpc_timeout timeparms = { struct rpc_timeout timeparms = {
.to_initval = max_cb_time(), .to_initval = max_cb_time(),
.to_retries = 0, .to_retries = 0,
@ -481,7 +480,7 @@ int setup_callback_client(struct nfs4_client *clp)
if (!clp->cl_principal && (clp->cl_flavor >= RPC_AUTH_GSS_KRB5)) if (!clp->cl_principal && (clp->cl_flavor >= RPC_AUTH_GSS_KRB5))
return -EINVAL; return -EINVAL;
if (cb->cb_minorversion) { if (cb->cb_minorversion) {
args.bc_xprt = clp->cl_cb_conn.cb_xprt; args.bc_xprt = cb->cb_xprt;
args.protocol = XPRT_TRANSPORT_BC_TCP; args.protocol = XPRT_TRANSPORT_BC_TCP;
} }
/* Create RPC client */ /* Create RPC client */
@ -491,7 +490,7 @@ int setup_callback_client(struct nfs4_client *clp)
PTR_ERR(client)); PTR_ERR(client));
return PTR_ERR(client); return PTR_ERR(client);
} }
clp->cl_cb_client = client; nfsd4_set_callback_client(clp, client);
return 0; return 0;
} }
@ -548,14 +547,13 @@ void do_probe_callback(struct nfs4_client *clp)
/* /*
* Set up the callback client and put a NFSPROC4_CB_NULL on the wire... * Set up the callback client and put a NFSPROC4_CB_NULL on the wire...
*/ */
void void nfsd4_probe_callback(struct nfs4_client *clp, struct nfs4_cb_conn *cb)
nfsd4_probe_callback(struct nfs4_client *clp)
{ {
int status; int status;
BUG_ON(atomic_read(&clp->cl_cb_set)); BUG_ON(atomic_read(&clp->cl_cb_set));
status = setup_callback_client(clp); status = setup_callback_client(clp, cb);
if (status) { if (status) {
warn_no_callback_path(clp, status); warn_no_callback_path(clp, status);
return; return;
@ -645,18 +643,32 @@ static void nfsd4_cb_done(struct rpc_task *task, void *calldata)
} }
} }
static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata) static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata)
{ {
struct nfs4_delegation *dp = calldata; struct nfs4_delegation *dp = calldata;
struct nfs4_client *clp = dp->dl_client; struct nfs4_client *clp = dp->dl_client;
struct rpc_clnt *current_rpc_client = clp->cl_cb_client;
nfsd4_cb_done(task, calldata); nfsd4_cb_done(task, calldata);
if (current_rpc_client == NULL) {
/* We're shutting down; give up. */
/* XXX: err, or is it ok just to fall through
* and rpc_restart_call? */
return;
}
switch (task->tk_status) { switch (task->tk_status) {
case -EIO: case -EIO:
/* Network partition? */ /* Network partition? */
atomic_set(&clp->cl_cb_set, 0); atomic_set(&clp->cl_cb_set, 0);
warn_no_callback_path(clp, task->tk_status); warn_no_callback_path(clp, task->tk_status);
if (current_rpc_client != task->tk_client) {
/* queue a callback on the new connection: */
nfsd4_cb_recall(dp);
return;
}
case -EBADHANDLE: case -EBADHANDLE:
case -NFS4ERR_BAD_STATEID: case -NFS4ERR_BAD_STATEID:
/* Race: client probably got cb_recall /* Race: client probably got cb_recall
@ -705,8 +717,7 @@ void nfsd4_destroy_callback_queue(void)
destroy_workqueue(callback_wq); destroy_workqueue(callback_wq);
} }
void nfsd4_set_callback_client(struct nfs4_client *clp, struct rpc_clnt void nfsd4_set_callback_client(struct nfs4_client *clp, struct rpc_clnt *new)
*new)
{ {
struct rpc_clnt *old = clp->cl_cb_client; struct rpc_clnt *old = clp->cl_cb_client;

View File

@ -1312,7 +1312,7 @@ nfsd4_create_session(struct svc_rqst *rqstp,
cstate->minorversion; cstate->minorversion;
unconf->cl_cb_conn.cb_prog = cr_ses->callback_prog; unconf->cl_cb_conn.cb_prog = cr_ses->callback_prog;
unconf->cl_cb_seq_nr = 1; unconf->cl_cb_seq_nr = 1;
nfsd4_probe_callback(unconf); nfsd4_probe_callback(unconf, &unconf->cl_cb_conn);
} }
conf = unconf; conf = unconf;
} else { } else {
@ -1605,9 +1605,8 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
if (!same_creds(&conf->cl_cred, &unconf->cl_cred)) if (!same_creds(&conf->cl_cred, &unconf->cl_cred))
status = nfserr_clid_inuse; status = nfserr_clid_inuse;
else { else {
/* XXX: We just turn off callbacks until we can handle
* change request correctly. */
atomic_set(&conf->cl_cb_set, 0); atomic_set(&conf->cl_cb_set, 0);
nfsd4_probe_callback(conf, &unconf->cl_cb_conn);
expire_client(unconf); expire_client(unconf);
status = nfs_ok; status = nfs_ok;
@ -1641,7 +1640,7 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,
} }
move_to_confirmed(unconf); move_to_confirmed(unconf);
conf = unconf; conf = unconf;
nfsd4_probe_callback(conf); nfsd4_probe_callback(conf, &conf->cl_cb_conn);
status = nfs_ok; status = nfs_ok;
} }
} else if ((!conf || (conf && !same_verf(&conf->cl_confirm, &confirm))) } else if ((!conf || (conf && !same_verf(&conf->cl_confirm, &confirm)))

View File

@ -390,7 +390,7 @@ extern int nfs4_in_grace(void);
extern __be32 nfs4_check_open_reclaim(clientid_t *clid); extern __be32 nfs4_check_open_reclaim(clientid_t *clid);
extern void nfs4_free_stateowner(struct kref *kref); extern void nfs4_free_stateowner(struct kref *kref);
extern int set_callback_cred(void); extern int set_callback_cred(void);
extern void nfsd4_probe_callback(struct nfs4_client *clp); extern void nfsd4_probe_callback(struct nfs4_client *clp, struct nfs4_cb_conn *);
extern void nfsd4_do_callback_rpc(struct work_struct *); extern void nfsd4_do_callback_rpc(struct work_struct *);
extern void nfsd4_cb_recall(struct nfs4_delegation *dp); extern void nfsd4_cb_recall(struct nfs4_delegation *dp);
extern int nfsd4_create_callback_queue(void); extern int nfsd4_create_callback_queue(void);