Commit 85f75dd7 authored by Lars Ellenberg's avatar Lars Ellenberg Committed by Philipp Reisner
Browse files

drbd: introduce in-kernel "down" command



This greatly simplifies deconfiguration of whole resources.

Signed-off-by: default avatarPhilipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: default avatarLars Ellenberg <lars.ellenberg@linbit.com>
parent 3c5e5f6a
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -2303,9 +2303,7 @@ struct drbd_tconn *drbd_new_tconn(const char *name)

void drbd_free_tconn(struct drbd_tconn *tconn)
{
	mutex_lock(&drbd_cfg_mutex);
	list_del(&tconn->all_tconn);
	mutex_unlock(&drbd_cfg_mutex);
	idr_destroy(&tconn->volumes);

	free_cpumask_var(tconn->cpu_mask);
+152 −51
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ int drbd_adm_delete_minor(struct sk_buff *skb, struct genl_info *info);

int drbd_adm_create_connection(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_delete_connection(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_down(struct sk_buff *skb, struct genl_info *info);

int drbd_adm_set_role(struct sk_buff *skb, struct genl_info *info);
int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info);
@@ -1416,6 +1417,18 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
	return 0;
}

static int adm_detach(struct drbd_conf *mdev)
{
	enum drbd_ret_code retcode;
	drbd_suspend_io(mdev); /* so no-one is stuck in drbd_al_begin_io */
	retcode = drbd_request_state(mdev, NS(disk, D_DISKLESS));
	wait_event(mdev->misc_wait,
			mdev->state.disk != D_DISKLESS ||
			!atomic_read(&mdev->local_cnt));
	drbd_resume_io(mdev);
	return retcode;
}

/* Detaching the disk is a process in multiple stages.  First we need to lock
 * out application IO, in-flight IO, IO stuck in drbd_al_begin_io.
 * Then we transition to D_DISKLESS, and wait for put_ldev() to return all
@@ -1423,7 +1436,6 @@ int drbd_adm_attach(struct sk_buff *skb, struct genl_info *info)
 * Only then we have finally detached. */
int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info)
{
	struct drbd_conf *mdev;
	enum drbd_ret_code retcode;

	retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
@@ -1432,13 +1444,7 @@ int drbd_adm_detach(struct sk_buff *skb, struct genl_info *info)
	if (retcode != NO_ERROR)
		goto out;

	mdev = adm_ctx.mdev;
	drbd_suspend_io(mdev); /* so no-one is stuck in drbd_al_begin_io */
	retcode = drbd_request_state(mdev, NS(disk, D_DISKLESS));
	wait_event(mdev->misc_wait,
			mdev->state.disk != D_DISKLESS ||
			!atomic_read(&mdev->local_cnt));
	drbd_resume_io(mdev);
	retcode = adm_detach(adm_ctx.mdev);
out:
	drbd_adm_finish(info, retcode);
	return 0;
@@ -1680,10 +1686,49 @@ int drbd_adm_connect(struct sk_buff *skb, struct genl_info *info)
	return 0;
}

static enum drbd_state_rv conn_try_disconnect(struct drbd_tconn *tconn, bool force)
{
	enum drbd_state_rv rv;
	if (force) {
		spin_lock_irq(&tconn->req_lock);
		if (tconn->cstate >= C_WF_CONNECTION)
			_conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
		spin_unlock_irq(&tconn->req_lock);
		return SS_SUCCESS;
	}

	rv = conn_request_state(tconn, NS(conn, C_DISCONNECTING), 0);

	switch (rv) {
	case SS_NOTHING_TO_DO:
	case SS_ALREADY_STANDALONE:
		return SS_SUCCESS;
	case SS_PRIMARY_NOP:
		/* Our state checking code wants to see the peer outdated. */
		rv = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
							pdsk, D_OUTDATED), CS_VERBOSE);
		break;
	case SS_CW_FAILED_BY_PEER:
		/* The peer probably wants to see us outdated. */
		rv = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
							disk, D_OUTDATED), 0);
		if (rv == SS_IS_DISKLESS || rv == SS_LOWER_THAN_OUTDATED) {
			conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
			rv = SS_SUCCESS;
		}
		break;
	default:;
		/* no special handling necessary */
	}

	return rv;
}

int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
{
	struct disconnect_parms parms;
	struct drbd_tconn *tconn;
	enum drbd_state_rv rv;
	enum drbd_ret_code retcode;
	int err;

@@ -1704,35 +1749,8 @@ int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
		}
	}

	if (parms.force_disconnect) {
		spin_lock_irq(&tconn->req_lock);
		if (tconn->cstate >= C_WF_CONNECTION)
			_conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
		spin_unlock_irq(&tconn->req_lock);
		goto done;
	}

	retcode = conn_request_state(tconn, NS(conn, C_DISCONNECTING), 0);

	if (retcode == SS_NOTHING_TO_DO)
		goto done;
	else if (retcode == SS_ALREADY_STANDALONE)
		goto done;
	else if (retcode == SS_PRIMARY_NOP) {
		/* Our state checking code wants to see the peer outdated. */
		retcode = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
							pdsk, D_OUTDATED), CS_VERBOSE);
	} else if (retcode == SS_CW_FAILED_BY_PEER) {
		/* The peer probably wants to see us outdated. */
		retcode = conn_request_state(tconn, NS2(conn, C_DISCONNECTING,
							disk, D_OUTDATED), 0);
		if (retcode == SS_IS_DISKLESS || retcode == SS_LOWER_THAN_OUTDATED) {
			conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
			retcode = SS_SUCCESS;
		}
	}

	if (retcode < SS_SUCCESS)
	rv = conn_try_disconnect(tconn, parms.force_disconnect);
	if (rv < SS_SUCCESS)
		goto fail;

	if (wait_event_interruptible(tconn->ping_wait,
@@ -1743,7 +1761,6 @@ int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
		goto fail;
	}

 done:
	retcode = NO_ERROR;
 fail:
	drbd_adm_finish(info, retcode);
@@ -2644,9 +2661,21 @@ int drbd_adm_add_minor(struct sk_buff *skb, struct genl_info *info)
	return 0;
}

static enum drbd_ret_code adm_delete_minor(struct drbd_conf *mdev)
{
	if (mdev->state.disk == D_DISKLESS &&
	    /* no need to be mdev->state.conn == C_STANDALONE &&
	     * we may want to delete a minor from a live replication group.
	     */
	    mdev->state.role == R_SECONDARY) {
		drbd_delete_device(mdev_to_minor(mdev));
		return NO_ERROR;
	} else
		return ERR_MINOR_CONFIGURED;
}

int drbd_adm_delete_minor(struct sk_buff *skb, struct genl_info *info)
{
	struct drbd_conf *mdev;
	enum drbd_ret_code retcode;

	retcode = drbd_adm_prepare(skb, info, DRBD_ADM_NEED_MINOR);
@@ -2655,19 +2684,89 @@ int drbd_adm_delete_minor(struct sk_buff *skb, struct genl_info *info)
	if (retcode != NO_ERROR)
		goto out;

	mdev = adm_ctx.mdev;
	if (mdev->state.disk == D_DISKLESS &&
	    /* no need to be mdev->state.conn == C_STANDALONE &&
	     * we may want to delete a minor from a live replication group.
	     */
	    mdev->state.role == R_SECONDARY) {
		drbd_delete_device(mdev_to_minor(mdev));
		retcode = NO_ERROR;
	mutex_lock(&drbd_cfg_mutex);
	retcode = adm_delete_minor(adm_ctx.mdev);
	mutex_unlock(&drbd_cfg_mutex);
	/* if this was the last volume of this connection,
	 * this will terminate all threads */
	if (retcode == NO_ERROR)
		conn_reconfig_done(adm_ctx.tconn);
	} else
		retcode = ERR_MINOR_CONFIGURED;
out:
	drbd_adm_finish(info, retcode);
	return 0;
}

int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
{
	enum drbd_ret_code retcode;
	enum drbd_state_rv rv;
	struct drbd_conf *mdev;
	unsigned i;

	retcode = drbd_adm_prepare(skb, info, 0);
	if (!adm_ctx.reply_skb)
		return retcode;
	if (retcode != NO_ERROR)
		goto out;

	if (!adm_ctx.tconn) {
		retcode = ERR_CONN_NOT_KNOWN;
		goto out;
	}

	mutex_lock(&drbd_cfg_mutex);
	/* demote */
	idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
		retcode = drbd_set_role(mdev, R_SECONDARY, 0);
		if (retcode < SS_SUCCESS) {
			drbd_msg_put_info("failed to demote");
			goto out_unlock;
		}
	}

	/* disconnect */
	rv = conn_try_disconnect(adm_ctx.tconn, 0);
	if (rv < SS_SUCCESS) {
		retcode = rv; /* enum type mismatch! */
		drbd_msg_put_info("failed to disconnect");
		goto out_unlock;
	}

	/* detach */
	idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
		rv = adm_detach(mdev);
		if (rv < SS_SUCCESS) {
			retcode = rv; /* enum type mismatch! */
			drbd_msg_put_info("failed to detach");
			goto out_unlock;
		}
	}

	/* delete volumes */
	idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
		retcode = adm_delete_minor(mdev);
		if (retcode != NO_ERROR) {
			/* "can not happen" */
			drbd_msg_put_info("failed to delete volume");
			goto out_unlock;
		}
	}

	/* stop all threads */
	conn_reconfig_done(adm_ctx.tconn);

	/* delete connection */
	if (conn_lowest_minor(adm_ctx.tconn) < 0) {
		drbd_free_tconn(adm_ctx.tconn);
		retcode = NO_ERROR;
	} else {
		/* "can not happen" */
		retcode = ERR_CONN_IN_USE;
		drbd_msg_put_info("failed to delete connection");
		goto out_unlock;
	}
out_unlock:
	mutex_unlock(&drbd_cfg_mutex);
out:
	drbd_adm_finish(info, retcode);
	return 0;
@@ -2683,12 +2782,14 @@ int drbd_adm_delete_connection(struct sk_buff *skb, struct genl_info *info)
	if (retcode != NO_ERROR)
		goto out;

	mutex_lock(&drbd_cfg_mutex);
	if (conn_lowest_minor(adm_ctx.tconn) < 0) {
		drbd_free_tconn(adm_ctx.tconn);
		retcode = NO_ERROR;
	} else {
		retcode = ERR_CONN_IN_USE;
	}
	mutex_unlock(&drbd_cfg_mutex);

out:
	drbd_adm_finish(info, retcode);
+2 −0
Original line number Diff line number Diff line
@@ -347,3 +347,5 @@ GENL_op(DRBD_ADM_OUTDATE, 25, GENL_doit(drbd_adm_outdate),
	GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, GENLA_F_REQUIRED))
GENL_op(DRBD_ADM_GET_TIMEOUT_TYPE, 26, GENL_doit(drbd_adm_get_timeout_type),
	GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, GENLA_F_REQUIRED))
GENL_op(DRBD_ADM_DOWN,		27, GENL_doit(drbd_adm_down),
	GENL_tla_expected(DRBD_NLA_CFG_CONTEXT, GENLA_F_REQUIRED))