Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions xlators/mgmt/glusterd/src/glusterd-snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -5479,6 +5479,13 @@ glusterd_snapshot_remove_prevalidate(dict_t *dict, char **op_errstr,
char *snapname = NULL;
xlator_t *this = THIS;
glusterd_snap_t *snap = NULL;
glusterd_volinfo_t *snap_vol = NULL;
glusterd_brickinfo_t *brickinfo = NULL;
struct glusterd_snap_ops *snap_ops = NULL;
int32_t brick_count = -1;
gf_boolean_t has_dependent = _gf_false;
char dependent_info[4096] = "";
char err_str[4096] = "";

GF_VALIDATE_OR_GOTO(this->name, op_errno, out);

Expand All @@ -5505,6 +5512,50 @@ glusterd_snapshot_remove_prevalidate(dict_t *dict, char **op_errstr,
goto out;
}

/* A backend snapshot that still has a dependent clone (created via
* `zfs clone` for snapshot clone/restore) cannot be removed by the
* provider's plain `zfs destroy`. Refuse the delete here, before any
* decommission state is written, rather than letting it fail at commit
* time and report success. Providers that do not track dependents (e.g.
* LVM) leave .dependents NULL and are skipped, so their behaviour is
* unchanged. The check is per-brick and only the node owning a brick can
* query its backend, so each peer validates its local bricks. */
cds_list_for_each_entry(snap_vol, &snap->volumes, vol_list)
{
snap_ops = NULL;
glusterd_snapshot_plugin_by_name(snap_vol->snap_plugin, &snap_ops);
if (!snap_ops || !snap_ops->dependents)
continue;

brick_count = -1;
cds_list_for_each_entry(brickinfo, &snap_vol->bricks, brick_list)
{
brick_count++;
if (gf_uuid_compare(brickinfo->uuid, MY_UUID))
continue;

has_dependent = _gf_false;
dependent_info[0] = '\0';
ret = snap_ops->dependents(brickinfo, snap_vol->snapshot->snapname,
snap_vol->volname, brick_count,
&has_dependent, dependent_info,
sizeof(dependent_info));
if (ret == 0 && has_dependent) {
snprintf(err_str, sizeof(err_str),
"Snapshot %s cannot be deleted: its backend snapshot "
"still has a dependent clone (%s). Remove the "
"dependent clone before deleting this snapshot.",
snapname, dependent_info);
gf_msg(this->name, GF_LOG_ERROR, 0, GD_MSG_SNAP_REMOVE_FAIL,
"%s", err_str);
*op_errstr = gf_strdup(err_str);
*op_errno = EG_OPNOTSUP;
ret = -1;
goto out;
}
}
}

ret = dict_set_dynstr_with_alloc(dict, "snapuuid",
uuid_utoa(snap->snap_id));
if (ret) {
Expand Down
15 changes: 15 additions & 0 deletions xlators/mgmt/glusterd/src/glusterd-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,21 @@ struct glusterd_snap_ops {
char *snap_clone_volume_id,
char *snap_brick_dir, int brick_num,
glusterd_brickinfo_t *brickinfo, int restore);

/* Optional. Reports whether the backend snapshot of this brick still has
* a dependent that prevents its removal (e.g. a ZFS clone created from
* it). Lets a snapshot delete be refused in prevalidate rather than
* failing at commit time. *has_dependent is set _gf_true only on a
* positive, confirmed dependency; dependent_info (if non-NULL) receives a
* short, human-readable description of the dependent for the error
* message. Returns 0 when the check ran (regardless of the result) and
* non-zero only when the check itself could not be performed. Providers
* that do not track dependents leave this NULL. */
int32_t (*const dependents)(glusterd_brickinfo_t *snap_brickinfo,
char *snapname, char *snap_volume_id,
int32_t brick_num, gf_boolean_t *has_dependent,
char *dependent_info,
size_t dependent_info_len);
};

extern struct glusterd_snap_ops lvm_snap_ops;
Expand Down
105 changes: 104 additions & 1 deletion xlators/mgmt/glusterd/src/snapshot/glusterd-zfs-snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,108 @@ glusterd_zfs_snapshot_remove(glusterd_brickinfo_t *snap_brickinfo,
return ret;
}

/* Report whether the backend ZFS snapshot of this brick still has a dependent
* clone (a dataset created from it via `zfs clone`, e.g. by snapshot clone or
* restore). Such a snapshot cannot be removed by the provider's plain
* `zfs destroy`, so a delete that proceeds would silently leave it behind. Used
* by the delete prevalidate to refuse the operation up front.
*
* *has_dependent is set _gf_true only on a positive, confirmed dependency. Any
* inconclusive result -- dataset not resolvable, snapshot already gone, zfs not
* runnable -- is treated as "no confirmed dependent" so the check never becomes
* a new way to block a legitimate delete (this preserves the existing tolerant
* behaviour for an already-removed backend snapshot).
*/
int32_t
glusterd_zfs_snapshot_dependents(glusterd_brickinfo_t *snap_brickinfo,
char *snapname, char *snap_volume_id,
int32_t brick_num, gf_boolean_t *has_dependent,
char *dependent_info, size_t dependent_info_len)
{
int32_t ret = -1;
xlator_t *this = THIS;
runner_t runner = {
0,
};
char msg[1024] = "";
int len;
char snap_device[NAME_MAX] = "";
char *dataset = NULL;
char clones[4096] = "";
char *ptr = NULL;

GF_ASSERT(snap_brickinfo);
GF_VALIDATE_OR_GOTO(this->name, has_dependent, out);

*has_dependent = _gf_false;

ret = glusterd_zfs_dataset(snap_brickinfo->origin_path, &dataset);
if (ret) {
/* Cannot resolve the dataset (e.g. the brick/backend is already
* gone). Do not block the delete on an inconclusive check. */
ret = 0;
goto out;
}

/* glusterd_zfs_dataset() returns a pointer into a stack buffer, so the
* dataset name must be consumed immediately, before any other call can
* clobber it (mirrors glusterd_zfs_snapshot_remove). */
len = snprintf(snap_device, sizeof(snap_device), "%s@%s_%d", dataset,
snap_volume_id, brick_num);
if ((len < 0) || (len >= sizeof(snap_device))) {
ret = 0;
goto out;
}

runinit(&runner);
len = snprintf(msg, sizeof(msg),
"check dependent clones of the snapshot of brick %s, "
"snap name: %s",
snap_brickinfo->origin_path, snapname);
if (len < 0) {
strcpy(msg, "<error>");
}
runner_add_args(&runner, ZFS_COMMAND, "get", "-H", "-o", "value", "clones",
snap_device, NULL);
runner_redir(&runner, STDOUT_FILENO, RUN_PIPE);
runner_log(&runner, "", GF_LOG_DEBUG, msg);

ret = runner_start(&runner);
if (ret == -1) {
/* zfs could not be run, or the snapshot does not exist. Treat as
* "no confirmed dependent" and let the delete proceed. */
runner_end(&runner);
ret = 0;
goto out;
}

ptr = fgets(clones, sizeof(clones), runner_chio(&runner, STDOUT_FILENO));
(void)runner_end(&runner); /* reap the child */

if (!ptr) {
/* No output -> no clones reported. */
ret = 0;
goto out;
}

/* Drop the trailing newline. */
clones[strcspn(clones, "\n")] = '\0';

/* ZFS prints "-" (or nothing) for the `clones` property when the snapshot
* has no clones. Only a non-empty, non-"-" value is a confirmed dependent.
*/
if (clones[0] != '\0' && strcmp(clones, "-") != 0) {
*has_dependent = _gf_true;
if (dependent_info && dependent_info_len > 0)
snprintf(dependent_info, dependent_info_len, "%s", clones);
}

ret = 0;

out:
return ret;
}

/* No op since .zfs directory is used */
int32_t
glusterd_zfs_snapshot_activate(glusterd_brickinfo_t *snap_brickinfo,
Expand Down Expand Up @@ -516,4 +618,5 @@ struct glusterd_snap_ops zfs_snap_ops = {
.activate = glusterd_zfs_snapshot_activate,
.deactivate = glusterd_zfs_snapshot_deactivate,
.restore = glusterd_zfs_snapshot_restore,
.brick_path = glusterd_zfs_snap_clone_brick_path};
.brick_path = glusterd_zfs_snap_clone_brick_path,
.dependents = glusterd_zfs_snapshot_dependents};