Viewing: update_recovery.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015, 2017, Intel Corporation.
*/
/*
* This file implement the methods to handle the update recovery.
*
* During DNE recovery, the recovery thread will redo the operation according
* to the transaction no, and these replay are either from client replay req
* or update replay records(for distribute transaction) in the update log.
* For distribute transaction replay, the replay thread will call
* distribute_txn_replay_handle() to handle the updates.
*
* After the Master MDT restarts, it will retrieve the update records from all
* of MDTs, for each distributed operation, it will check updates on all MDTs,
* if some updates records are missing on some MDTs, the replay thread will redo
* updates on these MDTs.
*
* Author: Di Wang <di.wang@intel.com>
*/
#define DEBUG_SUBSYSTEM S_CLASS
#include <lu_target.h>
#include <lustre_obdo.h>
#include <lustre_update.h>
#include <lustre_swab.h>
#include <md_object.h>
#include <obd.h>
#include <obd_class.h>
#include "tgt_internal.h"
/**
* dtrq_lookup() - Lookup distribute_txn_replay req
* @tdtd: distribute_txn_data, which holds the replay list.
* @batchid: batchid used by lookup.
*
* Lookup distribute_txn_replay in the replay list by batchid.
* It is assumed the list has been locked before calling this function.
*
* Return:
* * pointer of the replay if succeeds.
* * %NULL if can not find it.
*/
static struct distribute_txn_replay_req *
dtrq_lookup(struct target_distribute_txn_data *tdtd, __u64 batchid)
{
struct distribute_txn_replay_req *tmp;
struct distribute_txn_replay_req *dtrq = NULL;
list_for_each_entry(tmp, &tdtd->tdtd_replay_list, dtrq_list) {
if (tmp->dtrq_batchid == batchid) {
dtrq = tmp;
break;
}
}
return dtrq;
}
/**
* dtrq_insert() - insert distribute txn replay req
* @tdtd: target distribute txn data where replay list is
* @new: distribute txn replay to be inserted
*
* Insert distribute txn replay to the replay list, and it assumes the
* list has been looked. Note: the replay list is a sorted list, which
* is sorted by master transno. It is assumed the replay list has been
* locked before calling this function.
*
* Return:
* * %0 if insertion succeeds
* * %EEXIST if the dtrq already exists
*/
static int dtrq_insert(struct target_distribute_txn_data *tdtd,
struct distribute_txn_replay_req *new)
{
struct distribute_txn_replay_req *iter;
/* Check if the dtrq has been added to the list */
iter = dtrq_lookup(tdtd, new->dtrq_batchid);
if (iter != NULL)
return -EEXIST;
list_for_each_entry_reverse(iter, &tdtd->tdtd_replay_list, dtrq_list) {
if (iter->dtrq_master_transno > new->dtrq_master_transno)
continue;
/* If there are mulitple replay req with same transno, then
* sort them with batchid */
if (iter->dtrq_master_transno == new->dtrq_master_transno &&
iter->dtrq_batchid > new->dtrq_batchid)
continue;
list_add(&new->dtrq_list, &iter->dtrq_list);
break;
}
if (list_empty(&new->dtrq_list))
list_add(&new->dtrq_list, &tdtd->tdtd_replay_list);
return 0;
}
/**
* dtrq_create() - create distribute txn replay req
* @tdtd: target distribute txn data where replay list is.
* @lur: update records from the update log.
*
* Allocate distribute txn replay req according to the update records.
*
* Return:
* * pointer of distribute txn replay req if the creation succeeds.
* * %NULL if the creation fails.
*/
static struct distribute_txn_replay_req *
dtrq_create(struct target_distribute_txn_data *tdtd,
struct llog_update_record *lur)
{
struct distribute_txn_replay_req *new;
OBD_ALLOC_PTR(new);
if (new == NULL)
RETURN(ERR_PTR(-ENOMEM));
new->dtrq_lur_size = llog_update_record_size(lur);
OBD_ALLOC_LARGE(new->dtrq_lur, new->dtrq_lur_size);
if (new->dtrq_lur == NULL) {
OBD_FREE_PTR(new);
RETURN(ERR_PTR(-ENOMEM));
}
memcpy(new->dtrq_lur, lur, new->dtrq_lur_size);
/* If the transno in the update record is 0, it means the
* update are from master MDT, and it will use the master
* last committed transno as its master transno. Later, if
* the update records are gotten from slave MDTs, then these
* transno will be replaced.
* See insert_update_records_to_replay_list(). */
if (lur->lur_update_rec.ur_master_transno == 0) {
new->dtrq_lur->lur_update_rec.ur_master_transno =
tdtd->tdtd_lut->lut_obd->obd_last_committed;
new->dtrq_master_transno =
tdtd->tdtd_lut->lut_obd->obd_last_committed;
} else {
new->dtrq_master_transno =
lur->lur_update_rec.ur_master_transno;
}
new->dtrq_batchid = lur->lur_update_rec.ur_batchid;
spin_lock_init(&new->dtrq_sub_list_lock);
INIT_LIST_HEAD(&new->dtrq_sub_list);
INIT_LIST_HEAD(&new->dtrq_list);
RETURN(new);
}
/**
* dtrq_sub_lookup() - Lookup distribute sub replay
* @dtrq: the distribute txn replay req to lookup
* @mdt_index: the mdt_index as the key of lookup
*
* Lookup distribute sub replay in the sub list of distribute_txn_replay by
* mdt_index.
*
* Return:
* * the pointer of sub replay if it can be found.
* * %NULL if it can not find.
*/
struct distribute_txn_replay_req_sub *
dtrq_sub_lookup(struct distribute_txn_replay_req *dtrq, __u32 mdt_index)
{
struct distribute_txn_replay_req_sub *dtrqs = NULL;
struct distribute_txn_replay_req_sub *tmp;
list_for_each_entry(tmp, &dtrq->dtrq_sub_list, dtrqs_list) {
if (tmp->dtrqs_mdt_index == mdt_index) {
dtrqs = tmp;
break;
}
}
return dtrqs;
}
/**
* dtrq_sub_add_cookie() - Try to add cookie to sub distribute txn request
* @dtrqs: sub replay req where cookies to be added.
* @cookie: cookie to be added.
*
* Check if the update log cookie has been added to the request, if not,
* add it to the dtrqs_cookie_list.
*
* Return:
* * %0 if the cookie is adding succeeds.
* * %negative errno if adding fails.
*/
static int dtrq_sub_add_cookie(struct distribute_txn_replay_req_sub *dtrqs,
struct llog_cookie *cookie)
{
struct sub_thandle_cookie *new;
OBD_ALLOC_PTR(new);
if (new == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&new->stc_list);
new->stc_cookie = *cookie;
/* Note: only single thread will access one sub_request each time,
* so no need lock here */
list_add(&new->stc_list, &dtrqs->dtrqs_cookie_list);
return 0;
}
/**
* dtrq_sub_create_and_insert() - Insert distribute txn sub req replay
* @dtrq: d to be added
* @cookie: the cookie of the update record
* @mdt_index: the mdt_index of the update record
*
* Allocate sub replay req and insert distribute txn replay list.
*
* Return:
* * %0 if the adding succeeds.
* * %negative errno if the adding fails.
*/
static int
dtrq_sub_create_and_insert(struct distribute_txn_replay_req *dtrq,
struct llog_cookie *cookie,
__u32 mdt_index)
{
struct distribute_txn_replay_req_sub *dtrqs = NULL;
struct distribute_txn_replay_req_sub *new;
int rc;
ENTRY;
spin_lock(&dtrq->dtrq_sub_list_lock);
dtrqs = dtrq_sub_lookup(dtrq, mdt_index);
spin_unlock(&dtrq->dtrq_sub_list_lock);
if (dtrqs != NULL) {
rc = dtrq_sub_add_cookie(dtrqs, cookie);
RETURN(0);
}
OBD_ALLOC_PTR(new);
if (new == NULL)
RETURN(-ENOMEM);
INIT_LIST_HEAD(&new->dtrqs_list);
INIT_LIST_HEAD(&new->dtrqs_cookie_list);
new->dtrqs_mdt_index = mdt_index;
spin_lock(&dtrq->dtrq_sub_list_lock);
dtrqs = dtrq_sub_lookup(dtrq, mdt_index);
if (dtrqs == NULL) {
list_add(&new->dtrqs_list, &dtrq->dtrq_sub_list);
dtrqs = new;
} else {
OBD_FREE_PTR(new);
}
spin_unlock(&dtrq->dtrq_sub_list_lock);
rc = dtrq_sub_add_cookie(dtrqs, cookie);
RETURN(rc);
}
/**
* dtrq_append_updates() - append updates to the current replay updates
* @dtrq: the update replay request where the new update records will be added.
* @record: the new update record.
*
* Append more updates to the existent replay update. And this is only
* used when combining mulitple updates into one large updates during
* replay.
*
* Return:
* * %0 if appending succeeds.
* * %negative errno if appending fails.
*/
static int dtrq_append_updates(struct distribute_txn_replay_req *dtrq,
struct update_records *record)
{
struct llog_update_record *new_lur;
size_t lur_size = dtrq->dtrq_lur_size;
void *ptr;
ENTRY;
/* Because several threads might retrieve the same records from
* different targets, and we only need one copy of records. So
* we will check if the records is in the next one, if not, just
* skip it */
spin_lock(&dtrq->dtrq_sub_list_lock);
if (dtrq->dtrq_lur->lur_update_rec.ur_index + 1 != record->ur_index) {
spin_unlock(&dtrq->dtrq_sub_list_lock);
RETURN(0);
}
dtrq->dtrq_lur->lur_update_rec.ur_index++;
spin_unlock(&dtrq->dtrq_sub_list_lock);
lur_size += update_records_size(record);
OBD_ALLOC_LARGE(new_lur, lur_size);
if (new_lur == NULL) {
spin_lock(&dtrq->dtrq_sub_list_lock);
dtrq->dtrq_lur->lur_update_rec.ur_index--;
spin_unlock(&dtrq->dtrq_sub_list_lock);
RETURN(-ENOMEM);
}
/* Copy the old and new records to the new allocated buffer */
memcpy(new_lur, dtrq->dtrq_lur, dtrq->dtrq_lur_size);
ptr = (char *)&new_lur->lur_update_rec +
update_records_size(&new_lur->lur_update_rec);
memcpy(ptr, &record->ur_ops,
update_records_size(record) -
offsetof(struct update_records, ur_ops));
new_lur->lur_update_rec.ur_update_count += record->ur_update_count;
new_lur->lur_update_rec.ur_param_count += record->ur_param_count;
new_lur->lur_hdr.lrh_len = llog_update_record_size(new_lur);
/* Replace the records */
OBD_FREE_LARGE(dtrq->dtrq_lur, dtrq->dtrq_lur_size);
dtrq->dtrq_lur = new_lur;
dtrq->dtrq_lur_size = lur_size;
dtrq->dtrq_lur->lur_update_rec.ur_flags = record->ur_flags;
update_records_dump(&new_lur->lur_update_rec, D_INFO, true);
RETURN(0);
}
/**
* insert_update_records_to_replay_list() - Insert update records to the replay
* list.
* @tdtd: distribute txn replay data where the replay list is.
* @lur: the update record
* @cookie: cookie of the record
* @mdt_index: mdt index of the record
*
* Allocate distribute txn replay req and insert it into the replay
* list, then insert the update records into the replay req.
*
* Return:
* * %0 if the adding succeeds.
* * %negative errno if the adding fails.
*/
int
insert_update_records_to_replay_list(struct target_distribute_txn_data *tdtd,
struct llog_update_record *lur,
struct llog_cookie *cookie,
__u32 mdt_index)
{
struct distribute_txn_replay_req *dtrq;
struct update_records *record = &lur->lur_update_rec;
bool replace_record = false;
int rc = 0;
ENTRY;
CDEBUG(D_HA, "%s: insert record batchid = %llu transno = %llu mdt_index %u\n",
tdtd->tdtd_lut->lut_obd->obd_name,
record->ur_batchid, record->ur_master_transno, mdt_index);
/* Update batchid if necessary */
spin_lock(&tdtd->tdtd_batchid_lock);
if (record->ur_batchid >= tdtd->tdtd_batchid) {
CDEBUG(D_HA, "%s update batchid from %llu to %llu\n",
tdtd->tdtd_lut->lut_obd->obd_name,
tdtd->tdtd_batchid, record->ur_batchid);
tdtd->tdtd_batchid = record->ur_batchid + 1;
}
spin_unlock(&tdtd->tdtd_batchid_lock);
again:
spin_lock(&tdtd->tdtd_replay_list_lock);
/* First try to build the replay update request with the records */
dtrq = dtrq_lookup(tdtd, record->ur_batchid);
if (dtrq == NULL) {
spin_unlock(&tdtd->tdtd_replay_list_lock);
dtrq = dtrq_create(tdtd, lur);
if (IS_ERR(dtrq))
RETURN(PTR_ERR(dtrq));
spin_lock(&tdtd->tdtd_replay_list_lock);
rc = dtrq_insert(tdtd, dtrq);
if (rc < 0) {
spin_unlock(&tdtd->tdtd_replay_list_lock);
dtrq_destroy(dtrq);
if (rc == -EEXIST)
goto again;
return rc;
}
} else {
/* If the master transno in update header is not
* matched with the one in the record, then it means
* the dtrq is originally created by master record,
* so we need update master transno and reposition
* the dtrq(by master transno) in the list and also
* replace update record */
if (record->ur_master_transno != 0 &&
dtrq->dtrq_master_transno != record->ur_master_transno &&
dtrq->dtrq_lur != NULL) {
list_del_init(&dtrq->dtrq_list);
dtrq->dtrq_lur->lur_update_rec.ur_master_transno =
record->ur_master_transno;
dtrq->dtrq_master_transno = record->ur_master_transno;
replace_record = true;
/* try to insert again */
rc = dtrq_insert(tdtd, dtrq);
if (rc < 0) {
spin_unlock(&tdtd->tdtd_replay_list_lock);
dtrq_destroy(dtrq);
return rc;
}
}
}
spin_unlock(&tdtd->tdtd_replay_list_lock);
/* Because there should be only thread access the update record, so
* we do not need lock here */
if (replace_record) {
/* Replace the update record and master transno */
OBD_FREE_LARGE(dtrq->dtrq_lur, dtrq->dtrq_lur_size);
dtrq->dtrq_lur = NULL;
dtrq->dtrq_lur_size = llog_update_record_size(lur);
OBD_ALLOC_LARGE(dtrq->dtrq_lur, dtrq->dtrq_lur_size);
if (dtrq->dtrq_lur == NULL)
return -ENOMEM;
memcpy(dtrq->dtrq_lur, lur, dtrq->dtrq_lur_size);
}
/* This is a partial update records, let's try to append
* the record to the current replay request */
if (record->ur_flags & UPDATE_RECORD_CONTINUE)
rc = dtrq_append_updates(dtrq, record);
/* Then create and add sub update request */
rc = dtrq_sub_create_and_insert(dtrq, cookie, mdt_index);
RETURN(rc);
}
EXPORT_SYMBOL(insert_update_records_to_replay_list);
/**
* dtrq_list_dump() - Dump updates of distribute txns.
* @tdtd: distribute txn data where all of distribute txn are listed.
* @mask: debug mask
*
* Output all of recovery updates in the distribute txn list to the
* debug log.
*/
void dtrq_list_dump(struct target_distribute_txn_data *tdtd, unsigned int mask)
{
struct distribute_txn_replay_req *dtrq;
spin_lock(&tdtd->tdtd_replay_list_lock);
list_for_each_entry(dtrq, &tdtd->tdtd_replay_list, dtrq_list)
update_records_dump(&dtrq->dtrq_lur->lur_update_rec, mask,
false);
spin_unlock(&tdtd->tdtd_replay_list_lock);
}
EXPORT_SYMBOL(dtrq_list_dump);
/**
* dtrq_destroy() - Destroy distribute txn replay req
* @dtrq: distribute txn replqy req to be destroyed.
*
* Destroy distribute txn replay req and all of subs.
*/
void dtrq_destroy(struct distribute_txn_replay_req *dtrq)
{
struct distribute_txn_replay_req_sub *dtrqs;
struct distribute_txn_replay_req_sub *tmp;
LASSERT(list_empty(&dtrq->dtrq_list));
CDEBUG(D_HA, "destroy x%llu t%llu\n", dtrq->dtrq_xid,
dtrq->dtrq_master_transno);
spin_lock(&dtrq->dtrq_sub_list_lock);
list_for_each_entry_safe(dtrqs, tmp, &dtrq->dtrq_sub_list, dtrqs_list) {
struct sub_thandle_cookie *stc;
struct sub_thandle_cookie *tmp;
list_del(&dtrqs->dtrqs_list);
list_for_each_entry_safe(stc, tmp, &dtrqs->dtrqs_cookie_list,
stc_list) {
list_del(&stc->stc_list);
OBD_FREE_PTR(stc);
}
OBD_FREE_PTR(dtrqs);
}
spin_unlock(&dtrq->dtrq_sub_list_lock);
if (dtrq->dtrq_lur != NULL)
OBD_FREE_LARGE(dtrq->dtrq_lur, dtrq->dtrq_lur_size);
OBD_FREE_PTR(dtrq);
}
EXPORT_SYMBOL(dtrq_destroy);
/**
* dtrq_list_destroy() - Destroy all of replay req.
* @tdtd: target distribute txn data where the replay list is.
*
* Destroy all of replay req in the replay list.
*/
void dtrq_list_destroy(struct target_distribute_txn_data *tdtd)
{
struct distribute_txn_replay_req *dtrq;
struct distribute_txn_replay_req *tmp;
spin_lock(&tdtd->tdtd_replay_list_lock);
list_for_each_entry_safe(dtrq, tmp, &tdtd->tdtd_replay_list,
dtrq_list) {
list_del_init(&dtrq->dtrq_list);
dtrq_destroy(dtrq);
}
list_for_each_entry_safe(dtrq, tmp, &tdtd->tdtd_replay_finish_list,
dtrq_list) {
list_del_init(&dtrq->dtrq_list);
dtrq_destroy(dtrq);
}
spin_unlock(&tdtd->tdtd_replay_list_lock);
}
EXPORT_SYMBOL(dtrq_list_destroy);
/**
* distribute_txn_get_next_req() - Get next req in the replay list
* @tdtd: distribute txn data where the replay list is
*
* Get next req needs to be replayed, since it is a sorted list
* (by master MDT transno)
*
* Return the pointer of update recovery header
*/
struct distribute_txn_replay_req *
distribute_txn_get_next_req(struct target_distribute_txn_data *tdtd)
{
struct distribute_txn_replay_req *dtrq = NULL;
spin_lock(&tdtd->tdtd_replay_list_lock);
if (!list_empty(&tdtd->tdtd_replay_list)) {
dtrq = list_first_entry(&tdtd->tdtd_replay_list,
struct distribute_txn_replay_req,
dtrq_list);
list_del_init(&dtrq->dtrq_list);
}
spin_unlock(&tdtd->tdtd_replay_list_lock);
return dtrq;
}
EXPORT_SYMBOL(distribute_txn_get_next_req);
/**
* distribute_txn_get_next_transno() - Get next transno in the replay list
* @tdtd: distribute txn data where the replay list is
*
* Get next transno in the replay list, because this is the sorted
* list, so it will return the transno of next req in the list.
*
* Returns the transno of next update in the list
*/
__u64 distribute_txn_get_next_transno(struct target_distribute_txn_data *tdtd)
{
struct distribute_txn_replay_req *dtrq = NULL;
__u64 transno = 0;
spin_lock(&tdtd->tdtd_replay_list_lock);
if (!list_empty(&tdtd->tdtd_replay_list)) {
dtrq = list_first_entry(&tdtd->tdtd_replay_list,
struct distribute_txn_replay_req,
dtrq_list);
transno = dtrq->dtrq_master_transno;
}
spin_unlock(&tdtd->tdtd_replay_list_lock);
CDEBUG(D_HA, "%s: Next update transno %llu\n",
tdtd->tdtd_lut->lut_obd->obd_name, transno);
return transno;
}
EXPORT_SYMBOL(distribute_txn_get_next_transno);
struct distribute_txn_replay_req *
distribute_txn_lookup_finish_list(struct target_distribute_txn_data *tdtd,
__u64 transno)
{
struct distribute_txn_replay_req *dtrq = NULL;
struct distribute_txn_replay_req *iter;
spin_lock(&tdtd->tdtd_replay_list_lock);
list_for_each_entry(iter, &tdtd->tdtd_replay_finish_list, dtrq_list) {
if (iter->dtrq_master_transno == transno) {
dtrq = iter;
break;
}
}
spin_unlock(&tdtd->tdtd_replay_list_lock);
return dtrq;
}
bool is_req_replayed_by_update(struct ptlrpc_request *req)
{
struct lu_target *tgt = class_exp2tgt(req->rq_export);
struct distribute_txn_replay_req *dtrq;
if (tgt->lut_tdtd == NULL)
return false;
dtrq = distribute_txn_lookup_finish_list(tgt->lut_tdtd,
lustre_msg_get_transno(req->rq_reqmsg));
if (dtrq == NULL)
return false;
return true;
}
EXPORT_SYMBOL(is_req_replayed_by_update);
/**
* update_is_committed() - Check if the update of one object is committed
* @env: execution environment
* @dtrq: replay request
* @dt_obj: object for the update
* @top_th: top thandle
* @st: sub thandle which the update belongs to
*
* Check whether the update for the object is committed by checking whether
* the correspondent sub exists in the replay req. If it is committed, mark
* the committed flag in correspondent the sub thandle.
*
* Return:
* * %1 if the update is not committed.
* * %0 if the update is committed.
* * %negative errno if some other failures happen.
*/
static int update_is_committed(const struct lu_env *env,
struct distribute_txn_replay_req *dtrq,
struct dt_object *dt_obj,
struct top_thandle *top_th,
struct sub_thandle *st)
{
struct seq_server_site *seq_site;
const struct lu_fid *fid = lu_object_fid(&dt_obj->do_lu);
struct distribute_txn_replay_req_sub *dtrqs;
__u32 mdt_index;
ENTRY;
if (st->st_sub_th != NULL)
RETURN(1);
if (st->st_committed)
RETURN(0);
seq_site = lu_site2seq(dt_obj->do_lu.lo_dev->ld_site);
if (fid_is_update_log(fid) || fid_is_update_log_dir(fid)) {
mdt_index = fid_oid(fid);
} else if (!fid_seq_in_fldb(fid_seq(fid))) {
mdt_index = seq_site->ss_node_id;
} else {
struct lu_server_fld *fld;
struct lu_seq_range range = {0};
int rc;
fld = seq_site->ss_server_fld;
fld_range_set_type(&range, LU_SEQ_RANGE_MDT);
LASSERT(fld->lsf_seq_lookup != NULL);
rc = fld->lsf_seq_lookup(env, fld, fid_seq(fid),
&range);
if (rc < 0)
RETURN(rc);
mdt_index = range.lsr_index;
}
dtrqs = dtrq_sub_lookup(dtrq, mdt_index);
if (dtrqs != NULL || top_th->tt_multiple_thandle->tmt_committed) {
st->st_committed = 1;
if (dtrqs != NULL) {
struct sub_thandle_cookie *stc;
struct sub_thandle_cookie *tmp;
list_for_each_entry_safe(stc, tmp,
&dtrqs->dtrqs_cookie_list,
stc_list)
list_move(&stc->stc_list, &st->st_cookie_list);
}
RETURN(0);
}
CDEBUG(D_HA, "Update of "DFID "on MDT%u is not committed\n", PFID(fid),
mdt_index);
RETURN(1);
}
/**
* update_recovery_create() - Implementation of different update methods for
* update recovery.
* @env: execution environment
* @dt_obj: target object to be created
* @op: update operation to be replayed
* @params: common update parameters which holds all parameters of the operation
* @ta: pointer to the thandle_exec_args struct
* @th: transaction handle
*
* These following functions update_recovery_$(update_name) implement
* different updates recovery methods. They will extract the parameters
* from the common parameters area and call correspondent dt API to redo
* the update.
*
* Return:
* * %0 if it succeeds.
* * %negative errno if it fails.
*/
static int update_recovery_create(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
struct llog_update_record *lur = uti->uti_dtrq->dtrq_lur;
struct lu_attr *attr = &uti->uti_attr;
struct obdo *wobdo;
struct obdo *lobdo = &uti->uti_obdo;
struct dt_object_format dof;
__u16 size;
unsigned int param_count;
int rc;
ENTRY;
if (dt_object_exists(dt_obj))
RETURN(-EEXIST);
param_count = lur->lur_update_rec.ur_param_count;
wobdo = update_params_get_param_buf(params, op->uop_params_off[0],
param_count, &size);
if (wobdo == NULL)
RETURN(-EIO);
if (size != sizeof(*wobdo))
RETURN(-EIO);
if (LLOG_REC_HDR_NEEDS_SWABBING(&lur->lur_hdr))
lustre_swab_obdo(wobdo);
lustre_get_wire_obdo(NULL, lobdo, wobdo);
la_from_obdo(attr, lobdo, lobdo->o_valid);
dof.dof_type = dt_mode_to_dft(attr->la_mode);
rc = out_tx_create(env, dt_obj, attr, NULL, &dof,
ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_destroy(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
int rc;
ENTRY;
rc = out_tx_destroy(env, dt_obj, ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_ref_add(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
int rc;
ENTRY;
rc = out_tx_ref_add(env, dt_obj, ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_ref_del(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
int rc;
ENTRY;
rc = out_tx_ref_del(env, dt_obj, ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_attr_set(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
struct llog_update_record *lur = uti->uti_dtrq->dtrq_lur;
struct obdo *wobdo;
struct obdo *lobdo = &uti->uti_obdo;
struct lu_attr *attr = &uti->uti_attr;
__u16 size;
unsigned int param_count;
int rc;
ENTRY;
param_count = lur->lur_update_rec.ur_param_count;
wobdo = update_params_get_param_buf(params, op->uop_params_off[0],
param_count, &size);
if (wobdo == NULL)
RETURN(-EIO);
if (size != sizeof(*wobdo))
RETURN(-EIO);
if (LLOG_REC_HDR_NEEDS_SWABBING(&lur->lur_hdr))
lustre_swab_obdo(wobdo);
lustre_get_wire_obdo(NULL, lobdo, wobdo);
la_from_obdo(attr, lobdo, lobdo->o_valid);
rc = out_tx_attr_set(env, dt_obj, attr, ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_xattr_set(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
char *buf;
char *name;
int fl;
__u16 size;
__u32 param_count;
int rc;
ENTRY;
param_count = uti->uti_dtrq->dtrq_lur->lur_update_rec.ur_param_count;
name = update_params_get_param_buf(params,
op->uop_params_off[0],
param_count, &size);
if (name == NULL)
RETURN(-EIO);
buf = update_params_get_param_buf(params,
op->uop_params_off[1],
param_count, &size);
if (buf == NULL)
RETURN(-EIO);
uti->uti_buf.lb_buf = buf;
uti->uti_buf.lb_len = (size_t)size;
buf = update_params_get_param_buf(params, op->uop_params_off[2],
param_count, &size);
if (buf == NULL)
RETURN(-EIO);
if (size != sizeof(fl))
RETURN(-EIO);
fl = le32_to_cpu(*(int *)buf);
rc = out_tx_xattr_set(env, dt_obj, &uti->uti_buf, name, fl, ta, th,
NULL, 0);
RETURN(rc);
}
static int update_recovery_index_insert(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
struct lu_fid *fid;
char *name;
__u32 param_count;
__u32 *ptype;
__u32 type;
__u16 size;
int rc;
ENTRY;
param_count = uti->uti_dtrq->dtrq_lur->lur_update_rec.ur_param_count;
name = update_params_get_param_buf(params, op->uop_params_off[0],
param_count, &size);
if (name == NULL)
RETURN(-EIO);
fid = update_params_get_param_buf(params, op->uop_params_off[1],
param_count, &size);
if (fid == NULL)
RETURN(-EIO);
if (size != sizeof(*fid))
RETURN(-EIO);
fid_le_to_cpu(fid, fid);
ptype = update_params_get_param_buf(params, op->uop_params_off[2],
param_count, &size);
if (ptype == NULL)
RETURN(-EIO);
if (size != sizeof(*ptype))
RETURN(-EIO);
type = le32_to_cpu(*ptype);
if (!dt_try_as_dir(env, dt_obj, false))
RETURN(-ENOTDIR);
uti->uti_rec.rec_fid = fid;
uti->uti_rec.rec_type = type;
rc = out_tx_index_insert(env, dt_obj,
(const struct dt_rec *)&uti->uti_rec,
(const struct dt_key *)name, ta, th,
NULL, 0);
RETURN(rc);
}
static int update_recovery_index_delete(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
__u32 param_count;
char *name;
__u16 size;
int rc;
ENTRY;
param_count = uti->uti_dtrq->dtrq_lur->lur_update_rec.ur_param_count;
name = update_params_get_param_buf(params, op->uop_params_off[0],
param_count, &size);
if (name == NULL)
RETURN(-EIO);
if (!dt_try_as_dir(env, dt_obj, true))
RETURN(-ENOTDIR);
rc = out_tx_index_delete(env, dt_obj,
(const struct dt_key *)name, ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_write(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
char *buf;
__u32 param_count;
__u64 pos;
__u16 size;
int rc;
ENTRY;
param_count = uti->uti_dtrq->dtrq_lur->lur_update_rec.ur_param_count;
buf = update_params_get_param_buf(params, op->uop_params_off[0],
param_count, &size);
if (buf == NULL)
RETURN(-EIO);
uti->uti_buf.lb_buf = buf;
uti->uti_buf.lb_len = size;
buf = update_params_get_param_buf(params, op->uop_params_off[1],
param_count, &size);
if (buf == NULL)
RETURN(-EIO);
pos = le64_to_cpu(*(__u64 *)buf);
rc = out_tx_write(env, dt_obj, &uti->uti_buf, pos,
ta, th, NULL, 0);
RETURN(rc);
}
static int update_recovery_xattr_del(const struct lu_env *env,
struct dt_object *dt_obj,
const struct update_op *op,
const struct update_params *params,
struct thandle_exec_args *ta,
struct thandle *th)
{
struct update_thread_info *uti = update_env_info(env);
__u32 param_count;
char *name;
__u16 size;
int rc;
ENTRY;
param_count = uti->uti_dtrq->dtrq_lur->lur_update_rec.ur_param_count;
name = update_params_get_param_buf(params, op->uop_params_off[0],
param_count, &size);
if (name == NULL)
RETURN(-EIO);
rc = out_tx_xattr_del(env, dt_obj, name, ta, th, NULL, 0);
RETURN(rc);
}
/**
* update_recovery_update_ses() - Update session information
* @env: execution environment.
* @tdtd: distribute data structure of the recovering tgt.
* @th: thandle of this update replay.
* @master_th: master sub thandle.
* @dtrq: pointer to distribute_txn_replay_req (replay request info)
* @ta_arg: the tx arg structure to hold the update for updating reply data.
*
* Update session information so tgt_txn_stop_cb()->tgt_last_rcvd_update()
* can be called correctly during update replay.
*/
static void update_recovery_update_ses(struct lu_env *env,
struct target_distribute_txn_data *tdtd,
struct thandle *th,
struct thandle *master_th,
struct distribute_txn_replay_req *dtrq,
struct tx_arg *ta_arg)
{
struct tgt_session_info *tsi;
struct lu_target *lut = tdtd->tdtd_lut;
struct lsd_reply_header *lrh = &lut->lut_reply_header;
struct lsd_reply_data *lrd;
struct top_thandle *top_th;
struct obd_export *export;
struct cfs_hash *hash;
size_t size;
tsi = tgt_ses_info(env);
if (tsi->tsi_exp != NULL)
return;
size = ta_arg->u.write.buf.lb_len;
lrd = ta_arg->u.write.buf.lb_buf;
if (size != lrh->lrh_reply_size || lrd == NULL)
return;
lrd->lrd_transno = le64_to_cpu(lrd->lrd_transno);
lrd->lrd_xid = le64_to_cpu(lrd->lrd_xid);
lrd->lrd_data = le64_to_cpu(lrd->lrd_data);
lrd->lrd_result = le32_to_cpu(lrd->lrd_result);
lrd->lrd_client_gen = le32_to_cpu(lrd->lrd_client_gen);
CDEBUG(D_HA, "xid=%llu transno=%llu\n", lrd->lrd_xid, lrd->lrd_transno);
if (lrd->lrd_transno != tgt_th_info(env)->tti_transno)
return;
hash = cfs_hash_getref(lut->lut_obd->obd_gen_hash);
if (hash == NULL)
return;
export = cfs_hash_lookup(hash, &lrd->lrd_client_gen);
if (export == NULL) {
cfs_hash_putref(hash);
return;
}
tsi->tsi_exp = export;
tsi->tsi_xid = lrd->lrd_xid;
tsi->tsi_opdata = lrd->lrd_data;
tsi->tsi_result = lrd->lrd_result;
tsi->tsi_client_gen = lrd->lrd_client_gen;
dtrq->dtrq_xid = lrd->lrd_xid;
top_th = container_of(th, struct top_thandle, tt_super);
top_th->tt_master_sub_thandle = master_th;
cfs_hash_putref(hash);
}
/**
* update_recovery_exec() - Execute updates in the update replay records
* @env: execution environment
* @tdtd: distribute txn replay data which hold all of replay reqs and all
* replay parameters.
* @dtrq: distribute transaction replay req.
* @ta: thandle execute args.
*
* Declare distribute txn replay by update records and add the updates
* to the execution list. Note: it will check if the update has been
* committed, and only execute the updates if it is not committed to
* disk.
*
* Return:
* * %0 if declare succeeds.
* * %negative errno if declare fails.
*/
static int update_recovery_exec(const struct lu_env *env,
struct target_distribute_txn_data *tdtd,
struct distribute_txn_replay_req *dtrq,
struct thandle_exec_args *ta)
{
struct llog_update_record *lur = dtrq->dtrq_lur;
struct update_records *records = &lur->lur_update_rec;
struct update_ops *ops = &records->ur_ops;
struct update_params *params = update_records_get_params(records);
struct top_thandle *top_th = container_of(ta->ta_handle,
struct top_thandle,
tt_super);
struct top_multiple_thandle *tmt = top_th->tt_multiple_thandle;
struct update_op *op;
unsigned int i;
int rc = 0;
ENTRY;
/* These records have been swabbed in llog_cat_process() */
for (i = 0, op = &ops->uops_op[0]; i < records->ur_update_count;
i++, op = update_op_next_op(op)) {
struct lu_fid *fid = &op->uop_fid;
struct dt_object *dt_obj;
struct dt_object *sub_dt_obj;
struct dt_device *sub_dt;
struct sub_thandle *st;
if (op->uop_type == OUT_NOOP)
continue;
dt_obj = dt_locate(env, tdtd->tdtd_dt, fid);
if (IS_ERR(dt_obj)) {
rc = PTR_ERR(dt_obj);
if (rc == -EREMCHG)
LCONSOLE_WARN("%.16s: hit invalid OI mapping for "
DFID" during recovering, "
"that may because auto scrub is disabled on related MDT, and "
"will cause recovery failure. Please enable auto scrub and "
"retry the recovery.\n",
tdtd->tdtd_lut->lut_obd->obd_name,
PFID(fid));
break;
}
sub_dt_obj = dt_object_child(dt_obj);
/* Create sub thandle if not */
sub_dt = lu2dt_dev(sub_dt_obj->do_lu.lo_dev);
st = lookup_sub_thandle(tmt, sub_dt);
if (st == NULL) {
st = create_sub_thandle(tmt, sub_dt);
if (IS_ERR(st))
GOTO(next, rc = PTR_ERR(st));
}
/* check if updates on the OSD/OSP are committed */
rc = update_is_committed(env, dtrq, dt_obj, top_th, st);
if (rc == 0)
/* If this is committed, goto next */
goto next;
if (rc < 0)
GOTO(next, rc);
/* Create thandle for sub thandle if needed */
if (st->st_sub_th == NULL) {
rc = sub_thandle_trans_create(env, top_th, st);
if (rc != 0)
GOTO(next, rc);
}
CDEBUG(D_HA, "replay %uth update\n", i);
switch (op->uop_type) {
case OUT_CREATE:
rc = update_recovery_create(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_DESTROY:
rc = update_recovery_destroy(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_REF_ADD:
rc = update_recovery_ref_add(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_REF_DEL:
rc = update_recovery_ref_del(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_ATTR_SET:
rc = update_recovery_attr_set(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_XATTR_SET:
rc = update_recovery_xattr_set(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_INDEX_INSERT:
rc = update_recovery_index_insert(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_INDEX_DELETE:
rc = update_recovery_index_delete(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_WRITE:
rc = update_recovery_write(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
case OUT_XATTR_DEL:
rc = update_recovery_xattr_del(env, sub_dt_obj,
op, params, ta,
st->st_sub_th);
break;
default:
CERROR("Unknown update type %u\n", (__u32)op->uop_type);
rc = -EINVAL;
break;
}
next:
dt_object_put(env, dt_obj);
if (rc < 0)
break;
}
ta->ta_handle->th_result = rc;
RETURN(rc);
}
/**
* distribute_txn_replay_handle() - redo updates on MDT if needed.
* @env: execution environment
* @tdtd: target distribute txn data, which holds the replay list and all
* parameters needed by replay process.
* @dtrq: distribute txn replay req.
*
* During DNE recovery, the recovery thread (target_recovery_thread) will call
* this function to replay distribute txn updates on all MDTs. It only replay
* updates on the MDT where the update record is missing.
*
* If the update already exists on the MDT, then it does not need replay the
* updates on that MDT, and only mark the sub transaction has been committed
* there.
*
* Return:
* * %0 if replay succeeds.
* * %negative errno if replay failes.
*/
int distribute_txn_replay_handle(struct lu_env *env,
struct target_distribute_txn_data *tdtd,
struct distribute_txn_replay_req *dtrq)
{
struct update_records *records = &dtrq->dtrq_lur->lur_update_rec;
struct thandle_exec_args *ta;
struct lu_context session_env;
struct thandle *th = NULL;
struct top_thandle *top_th;
struct top_multiple_thandle *tmt;
struct thandle_update_records *tur = NULL;
int i;
int rc = 0;
ENTRY;
/* initialize session, it is needed for the handler of target */
rc = lu_context_init(&session_env, LCT_SERVER_SESSION | LCT_NOREF);
if (rc) {
CERROR("%s: failure to initialize session: rc = %d\n",
tdtd->tdtd_lut->lut_obd->obd_name, rc);
RETURN(rc);
}
lu_context_enter(&session_env);
env->le_ses = &session_env;
lu_env_refill(env);
update_records_dump(records, D_HA, true);
th = top_trans_create(env, NULL);
if (IS_ERR(th))
GOTO(exit_session, rc = PTR_ERR(th));
ta = &update_env_info(env)->uti_tea;
ta->ta_argno = 0;
update_env_info(env)->uti_dtrq = dtrq;
/* Create distribute transaction structure for this top thandle */
top_th = container_of(th, struct top_thandle, tt_super);
rc = top_trans_create_tmt(env, top_th);
if (rc < 0)
GOTO(stop_trans, rc);
th->th_dev = tdtd->tdtd_dt;
ta->ta_handle = th;
/* check if the distribute transaction has been committed */
tmt = top_th->tt_multiple_thandle;
tmt->tmt_master_sub_dt = tdtd->tdtd_lut->lut_bottom;
tmt->tmt_batchid = dtrq->dtrq_batchid;
tgt_th_info(env)->tti_transno = dtrq->dtrq_master_transno;
if (tmt->tmt_batchid <= tdtd->tdtd_committed_batchid)
tmt->tmt_committed = 1;
rc = update_recovery_exec(env, tdtd, dtrq, ta);
if (rc < 0)
GOTO(stop_trans, rc);
/* If no updates are needed to be replayed, then mark this records as
* committed, so commit thread distribute_txn_commit_thread() will
* delete the record */
if (ta->ta_argno == 0)
tmt->tmt_committed = 1;
tur = &update_env_info(env)->uti_tur;
tur->tur_update_records = dtrq->dtrq_lur;
tur->tur_update_records_buf_size = dtrq->dtrq_lur_size;
tur->tur_update_params = NULL;
tur->tur_update_param_count = 0;
tmt->tmt_update_records = tur;
distribute_txn_insert_by_batchid(tmt);
rc = top_trans_start(env, NULL, th);
if (rc < 0)
GOTO(stop_trans, rc);
for (i = 0; i < ta->ta_argno; i++) {
struct tx_arg *ta_arg;
struct dt_object *dt_obj;
struct dt_device *sub_dt;
struct sub_thandle *st;
ta_arg = ta->ta_args[i];
dt_obj = ta_arg->object;
LASSERT(tmt->tmt_committed == 0);
sub_dt = lu2dt_dev(dt_obj->do_lu.lo_dev);
st = lookup_sub_thandle(tmt, sub_dt);
LASSERT(st != NULL);
LASSERT(st->st_sub_th != NULL);
rc = ta->ta_args[i]->exec_fn(env, st->st_sub_th,
ta->ta_args[i]);
/* If the update is to update the reply data, then
* we need set the session information, so
* tgt_last_rcvd_update() can be called correctly */
if (rc == 0 && dt_obj == tdtd->tdtd_lut->lut_reply_data)
update_recovery_update_ses(env, tdtd, th,
st->st_sub_th, dtrq, ta_arg);
if (unlikely(rc < 0)) {
CWARN("%s: error during execution of #%u from %s:%d: rc = %d\n",
dt_obd_name(ta->ta_handle->th_dev), i,
ta->ta_args[i]->file, ta->ta_args[i]->line, rc);
while (--i > 0) {
if (ta->ta_args[i]->undo_fn != NULL) {
dt_obj = ta->ta_args[i]->object;
sub_dt =
lu2dt_dev(dt_obj->do_lu.lo_dev);
st = lookup_sub_thandle(tmt, sub_dt);
LASSERT(st != NULL);
LASSERT(st->st_sub_th != NULL);
ta->ta_args[i]->undo_fn(env,
st->st_sub_th,
ta->ta_args[i]);
} else {
CERROR("%s: undo for %s:%d: rc = %d\n",
dt_obd_name(ta->ta_handle->th_dev),
ta->ta_args[i]->file,
ta->ta_args[i]->line, -ENOTSUPP);
}
}
break;
}
CDEBUG(D_HA, "%s: executed %u/%u: rc = %d\n",
dt_obd_name(sub_dt), i, ta->ta_argno, rc);
}
stop_trans:
if (rc < 0)
th->th_result = rc;
rc = top_trans_stop(env, tdtd->tdtd_dt, th);
for (i = 0; i < ta->ta_argno; i++) {
if (ta->ta_args[i]->object != NULL) {
dt_object_put(env, ta->ta_args[i]->object);
ta->ta_args[i]->object = NULL;
}
}
if (tur != NULL)
tur->tur_update_records = NULL;
if (tgt_ses_info(env)->tsi_exp != NULL) {
class_export_put(tgt_ses_info(env)->tsi_exp);
tgt_ses_info(env)->tsi_exp = NULL;
}
exit_session:
lu_context_exit(&session_env);
lu_context_fini(&session_env);
RETURN(rc);
}
EXPORT_SYMBOL(distribute_txn_replay_handle);