/*
Copyright (c) 2013-2014 Red Hat, Inc. <http://www.redhat.com>
This file is part of GlusterFS.
This file is licensed to you under your choice of the GNU Lesser
General Public License, version 3 or any later version (LGPLv3 or
later), or the GNU General Public License, version 2 (GPLv2), in all
cases as published by the Free Software Foundation.
*/
#ifndef _CONFIG_H
#define _CONFIG_H
#include "config.h"
#endif
#include <inttypes.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/resource.h>
#include <sys/statvfs.h>
#include <sys/mount.h>
#include <signal.h>
#if defined(GF_LINUX_HOST_OS)
#include <mntent.h>
#else
#include "mntent_compat.h"
#endif
#ifdef __NetBSD__
#define umount2(dir, flags) unmount(dir, ((flags) != 0) ? MNT_FORCE : 0)
#endif
#if defined(GF_DARWIN_HOST_OS) || defined(__FreeBSD__)
#include <sys/param.h>
#include <sys/mount.h>
#define umount2(dir, flags) unmount(dir, ((flags) != 0) ? MNT_FORCE : 0)
#endif
#include <regex.h>
#include "globals.h"
#include "compat.h"
#include "protocol-common.h"
#include "xlator.h"
#include "logging.h"
#include "timer.h"
#include "glusterd-mem-types.h"
#include "glusterd.h"
#include "glusterd-sm.h"
#include "glusterd-op-sm.h"
#include "glusterd-utils.h"
#include "glusterd-store.h"
#include "run.h"
#include "glusterd-volgen.h"
#include "glusterd-mgmt.h"
#include "glusterd-syncop.h"
#include "glusterd-snapshot-utils.h"
#include "glusterd-snapd-svc.h"
#include "glusterfs3.h"
#include "syscall.h"
#include "cli1-xdr.h"
#include "xdr-generic.h"
#include "lvm-defaults.h"
char snap_mount_folder[PATH_MAX];
struct snap_create_args_ {
xlator_t *this;
dict_t *dict;
dict_t *rsp_dict;
glusterd_volinfo_t *snap_vol;
glusterd_brickinfo_t *brickinfo;
struct syncargs *args;
int32_t volcount;
int32_t brickcount;
int32_t brickorder;
};
typedef struct snap_create_args_ snap_create_args_t;
/* This function is called to get the device path of the snap lvm. Usually
if /dev/mapper/<group-name>-<lvm-name> is the device for the lvm,
then the snap device will be /dev/<group-name>/<snapname>.
This function takes care of building the path for the snap device.
*/
char *
glusterd_build_snap_device_path (char *device, char *snapname,
int32_t brickcount)
{
char snap[PATH_MAX] = "";
char msg[1024] = "";
char volgroup[PATH_MAX] = "";
char *snap_device = NULL;
xlator_t *this = NULL;
runner_t runner = {0,};
char *ptr = NULL;
int ret = -1;
this = THIS;
GF_ASSERT (this);
if (!device) {
gf_log (this->name, GF_LOG_ERROR, "device is NULL");
goto out;
}
if (!snapname) {
gf_log (this->name, GF_LOG_ERROR, "snapname is NULL");
goto out;
}
runinit (&runner);
runner_add_args (&runner, "/sbin/lvs", "--noheadings", "-o", "vg_name",
device, NULL);
runner_redir (&runner, STDOUT_FILENO, RUN_PIPE);
snprintf (msg, sizeof (msg), "Get volume group for device %s", device);
runner_log (&runner, this->name, GF_LOG_DEBUG, msg);
ret = runner_start (&runner);
if (ret == -1) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get volume group "
"for device %s", device);
runner_end (&runner);
goto out;
}
ptr = fgets(volgroup, sizeof(volgroup),
runner_chio (&runner, STDOUT_FILENO));
if (!ptr || !strlen(volgroup)) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get volume group "
"for snap %s", snapname);
runner_end (&runner);
ret = -1;
goto out;
}
runner_end (&runner);
snprintf (snap, sizeof(snap), "/dev/%s/%s_%d", gf_trim(volgroup),
snapname, brickcount);
snap_device = gf_strdup (snap);
if (!snap_device) {
gf_log (this->name, GF_LOG_WARNING, "Cannot copy the "
"snapshot device name for snapname: %s", snapname);
}
out:
return snap_device;
}
/* Look for disconnected peers, for missed snap creates or deletes */
static int32_t
glusterd_find_missed_snap (dict_t *rsp_dict, glusterd_volinfo_t *vol,
struct cds_list_head *peers, int32_t op)
{
int32_t brick_count = -1;
int32_t ret = -1;
xlator_t *this = NULL;
glusterd_peerinfo_t *peerinfo = NULL;
glusterd_brickinfo_t *brickinfo = NULL;
this = THIS;
GF_ASSERT (this);
GF_ASSERT (rsp_dict);
GF_ASSERT (peers);
GF_ASSERT (vol);
brick_count = 0;
cds_list_for_each_entry (brickinfo, &vol->bricks, brick_list) {
if (!gf_uuid_compare (brickinfo->uuid, MY_UUID)) {
/* If the brick belongs to the same node */
brick_count++;
continue;
}
rcu_read_lock ();
cds_list_for_each_entry_rcu (peerinfo, peers, uuid_list) {
if (gf_uuid_compare (peerinfo->uuid, brickinfo->uuid)) {
/* If the brick doesnt belong to this peer */
continue;
}
/* Found peer who owns the brick, *
* if peer is not connected or not *
* friend add it to missed snap list */
if (!(peerinfo->connected) ||
(peerinfo->state.state !=
GD_FRIEND_STATE_BEFRIENDED)) {
ret = glusterd_add_missed_snaps_to_dict
(rsp_dict,
vol, brickinfo,
brick_count + 1,
op);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to add missed snapshot "
"info for %s:%s in the "
"rsp_dict", brickinfo->hostname,
brickinfo->path);
rcu_read_unlock ();
goto out;
}
}
}
rcu_read_unlock ();
brick_count++;
}
ret = 0;
out:
gf_log (this->name, GF_LOG_TRACE, "Returning %d", ret);
return ret;
}
int
snap_max_limits_display_commit (dict_t *rsp_dict, char *volname,
char *op_errstr, int len)
{
char err_str[PATH_MAX] = "";
char buf[PATH_MAX] = "";
glusterd_conf_t *conf = NULL;
glusterd_volinfo_t *volinfo = NULL;
int ret = -1;
uint64_t active_hard_limit = 0;
uint64_t snap_max_limit = 0;
uint64_t soft_limit_value = -1;
uint64_t count = 0;
xlator_t *this = NULL;
uint64_t opt_hard_max = GLUSTERD_SNAPS_MAX_HARD_LIMIT;
uint64_t opt_soft_max = GLUSTERD_SNAPS_DEF_SOFT_LIMIT_PERCENT;
char *auto_delete = "disable";
char *snap_activate = "disable";
this = THIS;
GF_ASSERT (this);
GF_ASSERT (rsp_dict);
GF_ASSERT (op_errstr);
conf = this->private;
GF_ASSERT (conf);
/* config values snap-max-hard-limit and snap-max-soft-limit are
* optional and hence we are not erroring out if values are not
* present
*/
gd_get_snap_conf_values_if_present (conf->opts, &opt_hard_max,
&opt_soft_max);
if (!volname) {
/* For system limit */
cds_list_for_each_entry (volinfo, &conf->volumes, vol_list) {
if (volinfo->is_snap_volume == _gf_true)
continue;
snap_max_limit = volinfo->snap_max_hard_limit;
if (snap_max_limit > opt_hard_max)
active_hard_limit = opt_hard_max;
else
active_hard_limit = snap_max_limit;
soft_limit_value = (opt_soft_max *
active_hard_limit) / 100;
snprintf (buf, sizeof(buf), "volume%"PRId64"-volname",
count);
ret = dict_set_str (rsp_dict, buf, volinfo->volname);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
snprintf (buf, sizeof(buf),
"volume%"PRId64"-snap-max-hard-limit", count);
ret = dict_set_uint64 (rsp_dict, buf, snap_max_limit);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
snprintf (buf, sizeof(buf),
"volume%"PRId64"-active-hard-limit", count);
ret = dict_set_uint64 (rsp_dict, buf,
active_hard_limit);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
snprintf (buf, sizeof(buf),
"volume%"PRId64"-snap-max-soft-limit", count);
ret = dict_set_uint64 (rsp_dict, buf, soft_limit_value);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
count++;
}
ret = dict_set_uint64 (rsp_dict, "voldisplaycount", count);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set voldisplaycount");
goto out;
}
} else {
/* For one volume */
ret = glusterd_volinfo_find (volname, &volinfo);
if (ret) {
snprintf (err_str, PATH_MAX, "Volume (%s) does not "
"exist", volname);
goto out;
}
snap_max_limit = volinfo->snap_max_hard_limit;
if (snap_max_limit > opt_hard_max)
active_hard_limit = opt_hard_max;
else
active_hard_limit = snap_max_limit;
soft_limit_value = (opt_soft_max *
active_hard_limit) / 100;
snprintf (buf, sizeof(buf), "volume%"PRId64"-volname", count);
ret = dict_set_str (rsp_dict, buf, volinfo->volname);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
snprintf (buf, sizeof(buf),
"volume%"PRId64"-snap-max-hard-limit", count);
ret = dict_set_uint64 (rsp_dict, buf, snap_max_limit);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
snprintf (buf, sizeof(buf),
"volume%"PRId64"-active-hard-limit", count);
ret = dict_set_uint64 (rsp_dict, buf, active_hard_limit);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
snprintf (buf, sizeof(buf),
"volume%"PRId64"-snap-max-soft-limit", count);
ret = dict_set_uint64 (rsp_dict, buf, soft_limit_value);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s", buf);
goto out;
}
count++;
ret = dict_set_uint64 (rsp_dict, "voldisplaycount", count);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set voldisplaycount");
goto out;
}
}
ret = dict_set_uint64 (rsp_dict,
GLUSTERD_STORE_KEY_SNAP_MAX_HARD_LIMIT,
opt_hard_max);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s in response dictionary",
GLUSTERD_STORE_KEY_SNAP_MAX_HARD_LIMIT);
goto out;
}
ret = dict_set_uint64 (rsp_dict,
GLUSTERD_STORE_KEY_SNAP_MAX_SOFT_LIMIT,
opt_soft_max);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s in response dictionary",
GLUSTERD_STORE_KEY_SNAP_MAX_SOFT_LIMIT);
goto out;
}
/* "auto-delete" might not be set by user explicitly,
* in that case it's better to consider the default value.
* Hence not erroring out if Key is not found.
*/
ret = dict_get_str (conf->opts, GLUSTERD_STORE_KEY_SNAP_AUTO_DELETE,
&auto_delete);
ret = dict_set_dynstr_with_alloc (rsp_dict,
GLUSTERD_STORE_KEY_SNAP_AUTO_DELETE,
auto_delete);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s in response dictionary",
GLUSTERD_STORE_KEY_SNAP_AUTO_DELETE);
goto out;
}
/* "snap-activate-on-create" might not be set by user explicitly,
* in that case it's better to consider the default value.
* Hence not erroring out if Key is not found.
*/
ret = dict_get_str (conf->opts, GLUSTERD_STORE_KEY_SNAP_ACTIVATE,
&snap_activate);
ret = dict_set_dynstr_with_alloc (rsp_dict,
GLUSTERD_STORE_KEY_SNAP_ACTIVATE,
snap_activate);
if (ret) {
snprintf (err_str, PATH_MAX,
"Failed to set %s in response dictionary",
GLUSTERD_STORE_KEY_SNAP_ACTIVATE);
goto out;
}
ret = 0;
out:
if (ret) {
strncpy (op_errstr, err_str, len);
gf_log (this->name, GF_LOG_ERROR, "%s", err_str);
}
return ret;
}
/* Third argument of scandir(used in glusterd_copy_geo_rep_session_files)
* is filter function. As we dont want "." and ".." files present in the
* directory, we are excliding these 2 files.
* "file_select" function here does the job of filtering.
*/
int
file_select (const struct dirent *entry)
{
if (entry == NULL)
return (FALSE);
if ((strcmp(entry->d_name, ".") == 0) ||
(strcmp(entry->d_name, "..") == 0))
return (FALSE);
else
return (TRUE);
}
int32_t
glusterd_copy_geo_rep_session_files (char *session,
glusterd_volinfo_t *snap_vol)
{
int32_t ret = -1;
char snap_session_dir[PATH_MAX] = "";
char georep_session_dir[PATH_MAX] = "";
regex_t *reg_exp = NULL;
int file_count = -1;
struct dirent **files = {0,};
xlator_t *this = NULL;
int i = 0;
char src_path[PATH_MAX] = "";
char dest_path[PATH_MAX] = "";
glusterd_conf_t *priv = NULL;
this = THIS;
GF_ASSERT (this);
priv = this->private;
GF_ASSERT (priv);
GF_ASSERT (session);
GF_ASSERT (snap_vol);
ret = snprintf (georep_session_dir, sizeof (georep_session_dir),
"%s/%s/%s", priv->workdir, GEOREP,
session);
if (ret < 0) { /* Negative value is an error */
goto out;
}
ret = snprintf (snap_session_dir, sizeof (snap_session_dir),
"%s/%s/%s/%s/%s", priv->workdir,
GLUSTERD_VOL_SNAP_DIR_PREFIX,
snap_vol->snapshot->snapname, GEOREP, session);
if (ret < 0) { /* Negative value is an error */
goto out;
}
ret = mkdir_p (snap_session_dir, 0777, _gf_true);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Creating directory %s failed", snap_session_dir);
goto out;
}
/* TODO : good to have - Allocate in stack instead of heap */
reg_exp = GF_CALLOC (1, sizeof (regex_t), gf_common_mt_regex_t);
if (!reg_exp) {
ret = -1;
gf_log (this->name, GF_LOG_ERROR, "Failed to allocate "
"memory for regular expression");
goto out;
}
ret = regcomp (reg_exp, "(.*status$)|(.*conf$)\0", REG_EXTENDED);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to "
"compile the regular expression");
goto out;
}
/* If there are no files in a particular session then fail it*/
file_count = scandir (georep_session_dir, &files, file_select,
alphasort);
if (file_count <= 0) {
ret = -1;
gf_log (this->name, GF_LOG_ERROR, "Session files not present "
"in %s", georep_session_dir);
goto out;
}
/* Now compare the file name with regular expression to see if
* there is a match
*/
for (i = 0 ; i < file_count; i++) {
if (regexec (reg_exp, files[i]->d_name, 0, NULL, 0))
continue;
ret = snprintf (src_path, sizeof (src_path), "%s/%s",
georep_session_dir, files[i]->d_name);
if (ret < 0) {
goto out;
}
ret = snprintf (dest_path , sizeof (dest_path), "%s/%s",
snap_session_dir, files[i]->d_name);
if (ret < 0) {
goto out;
}
ret = glusterd_copy_file (src_path, dest_path);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Could not "
"copy file %s of session %s",
files[i]->d_name, session);
goto out;
}
}
out:
if (reg_exp)
GF_FREE (reg_exp);
return ret;
}
/* This function will take backup of the volume store
* of the to-be restored volume. This will help us to
* revert the operation if it fails.
*
* @param volinfo volinfo of the origin volume
*
* @return 0 on success and -1 on failure
*/
int
glusterd_snapshot_backup_vol (glusterd_volinfo_t *volinfo)
{
char pathname[PATH_MAX] = {0,};
int ret = -1;
int op_ret = 0;
char delete_path[PATH_MAX] = {0,};
char trashdir[PATH_MAX] = {0,};
glusterd_conf_t *priv = NULL;
xlator_t *this = NULL;
this = THIS;
GF_ASSERT (this);
priv = this->private;
GF_ASSERT (priv);
GF_ASSERT (volinfo);
GLUSTERD_GET_VOLUME_DIR (pathname, volinfo, priv);
snprintf (delete_path, sizeof (delete_path),
"%s/"GLUSTERD_TRASH"/vols-%s.deleted", priv->workdir,
volinfo->volname);
snprintf (trashdir, sizeof (trashdir), "%s/"GLUSTERD_TRASH,
priv->workdir);
/* Create trash folder if it is not there */
ret = mkdir (trashdir, 0777);
if (ret && errno != EEXIST) {
gf_log (this->name, GF_LOG_ERROR, "Failed to create trash "
"directory, reason : %s", strerror (errno));
ret = -1;
goto out;
}
/* Move the origin volume volder to the backup location */
ret = rename (pathname, delete_path);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to rename snap "
"directory %s to %s", pathname, delete_path);
goto out;
}
/* Re-create an empty origin volume folder so that restore can
* happen. */
ret = mkdir (pathname, 0777);
if (ret && errno != EEXIST) {
gf_log (this->name, GF_LOG_ERROR, "Failed to create origin "
"volume directory (%s), reason : %s",
pathname, strerror (errno));
ret = -1;
goto out;
}
ret = 0;
out:
/* Save the actual return value */
op_ret = ret;
if (ret) {
/* Revert the changes in case of failure */
ret = rmdir (pathname);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG,
"Failed to rmdir: %s,err: %s",
pathname, strerror (errno));
}
ret = rename (delete_path, pathname);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to rename directory %s to %s",
delete_path, pathname);
}
ret = rmdir (trashdir);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG,
"Failed to rmdir: %s, Reason: %s",
trashdir, strerror (errno));
}
}
gf_log (this->name, GF_LOG_TRACE, "Returning %d", op_ret);
return op_ret;
}
int32_t
glusterd_copy_geo_rep_files (glusterd_volinfo_t *origin_vol,
glusterd_volinfo_t *snap_vol, dict_t *rsp_dict)
{
int32_t ret = -1;
int i = 0;
xlator_t *this = NULL;
char key[PATH_MAX] = "";
char session[PATH_MAX] = "";
char slave[PATH_MAX] = "";
char snapgeo_dir[PATH_MAX] = "";
glusterd_conf_t *priv = NULL;
this = THIS;
GF_ASSERT (this);
priv = this->private;
GF_ASSERT (priv);
GF_ASSERT (origin_vol);
GF_ASSERT (snap_vol);
GF_ASSERT (rsp_dict);
/* This condition is not satisfied if the volume
* is slave volume.
*/
if (!origin_vol->gsync_slaves) {
ret = 0;
goto out;
}
GLUSTERD_GET_SNAP_GEO_REP_DIR(snapgeo_dir, snap_vol->snapshot, priv);
ret = mkdir (snapgeo_dir, 0777);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Creating directory %s failed", snapgeo_dir);
goto out;
}
for (i = 1 ; i <= origin_vol->gsync_slaves->count ; i++) {
ret = snprintf (key, sizeof (key), "slave%d", i);
if (ret < 0) /* Negative value is an error */
goto out;
ret = glusterd_get_geo_rep_session (key, origin_vol->volname,
origin_vol->gsync_slaves,
session, slave);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get geo-rep session");
goto out;
}
ret = glusterd_copy_geo_rep_session_files (session, snap_vol);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to copy files"
" related to session %s", session);
goto out;
}
}
out:
return ret;
}
/* This function will restore a snapshot volumes
*
* @param dict dictionary containing snapshot restore request
* @param op_errstr In case of any failure error message will be returned
* in this variable
* @return Negative value on Failure and 0 in success
*/
int
glusterd_snapshot_restore (dict_t *dict, char **op_errstr, dict_t *rsp_dict)
{
int ret = -1;
int32_t volcount = -1;
char *snapname = NULL;
xlator_t *this = NULL;
glusterd_volinfo_t *snap_volinfo = NULL;
glusterd_volinfo_t *tmp = NULL;
glusterd_volinfo_t *parent_volinfo = NULL;
glusterd_snap_t *snap = NULL;
glusterd_conf_t *priv = NULL;
this = THIS;
GF_ASSERT (this);
GF_ASSERT (dict);
GF_ASSERT (op_errstr);
GF_ASSERT (rsp_dict);
priv = this->private;
GF_ASSERT (priv);
ret = dict_get_str (dict, "snapname", &snapname);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get "
"snap name");
goto out;
}
snap = glusterd_find_snap_by_name (snapname);
if (NULL == snap) {
ret = gf_asprintf (op_errstr, "Snapshot (%s) does not exist",
snapname);
if (ret < 0) {
goto out;
}
gf_log (this->name, GF_LOG_ERROR, "%s", *op_errstr);
ret = -1;
goto out;
}
volcount = 0;
cds_list_for_each_entry_safe (snap_volinfo, tmp, &snap->volumes,
vol_list) {
volcount++;
ret = glusterd_volinfo_find (snap_volinfo->parent_volname,
&parent_volinfo);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Could not get volinfo of %s",
snap_volinfo->parent_volname);
goto out;
}
ret = dict_set_dynstr_with_alloc (rsp_dict, "snapuuid",
uuid_utoa (snap->snap_id));
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set snap "
"uuid in response dictionary for %s snapshot",
snap->snapname);
goto out;
}
ret = dict_set_dynstr_with_alloc (rsp_dict, "volname",
snap_volinfo->parent_volname);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set snap "
"uuid in response dictionary for %s snapshot",
snap->snapname);
goto out;
}
ret = dict_set_dynstr_with_alloc (rsp_dict, "volid",
uuid_utoa (parent_volinfo->volume_id));
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set snap "
"uuid in response dictionary for %s snapshot",
snap->snapname);
goto out;
}
/* Take backup of the volinfo folder */
ret = glusterd_snapshot_backup_vol (parent_volinfo);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to backup "
"volume backend files for %s volume",
parent_volinfo->volname);
goto out;
}
if (is_origin_glusterd (dict) == _gf_true) {
/* From origin glusterd check if *
* any peers with snap bricks is down */
ret = glusterd_find_missed_snap
(rsp_dict, snap_volinfo,
&priv->peers,
GF_SNAP_OPTION_TYPE_RESTORE);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to find missed snap restores");
goto out;
}
}
ret = gd_restore_snap_volume (dict, rsp_dict, parent_volinfo,
snap_volinfo, volcount);
if (ret) {
/* No need to update op_errstr because it is assumed
* that the called function will do that in case of
* failure.
*/
gf_log (this->name, GF_LOG_ERROR, "Failed to restore "
"snap for %s", snapname);
goto out;
}
/* Restore is successful therefore delete the original volume's
* volinfo. If the volinfo is already restored then we should
* delete the backend LVMs */
if (!gf_uuid_is_null (parent_volinfo->restored_from_snap)) {
ret = glusterd_lvm_snapshot_remove (rsp_dict,
parent_volinfo);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to remove LVM backend");
goto out;
}
}
/* Detach the volinfo from priv->volumes, so that no new
* command can ref it any more and then unref it.
*/
cds_list_del_init (&parent_volinfo->vol_list);
glusterd_volinfo_unref (parent_volinfo);
}
ret = 0;
/* TODO: Need to check if we need to delete the snap after the
* operation is successful or not. Also need to persist the state
* of restore operation in the store.
*/
out:
return ret;
}
/* This function is called before actual restore is taken place. This function
* will validate whether the snapshot volumes are ready to be restored or not.
*
* @param dict dictionary containing snapshot restore request
* @param op_errstr In case of any failure error message will be returned
* in this variable
* @param rsp_dict response dictionary
* @return Negative value on Failure and 0 in success
*/
int32_t
glusterd_snapshot_restore_prevalidate (dict_t *dict, char **op_errstr,
dict_t *rsp_dict)
{
int ret = -1;
int32_t i = 0;
int32_t volcount = 0;
int32_t brick_count = 0;
gf_boolean_t snap_restored = _gf_false;
char key[PATH_MAX] = {0, };
char *volname = NULL;
char *snapname = NULL;
glusterd_volinfo_t *volinfo = NULL;
glusterd_brickinfo_t *brickinfo = NULL;
glusterd_snap_t *snap = NULL;
xlator_t *this = NULL;
this = THIS;
GF_ASSERT (this);
GF_ASSERT (dict);
GF_ASSERT (op_errstr);
GF_ASSERT (rsp_dict);
ret = dict_get_str (dict, "snapname", &snapname);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get "
"snap name");
goto out;
}
snap = glusterd_find_snap_by_name (snapname);
if (NULL == snap) {
ret = gf_asprintf (op_errstr, "Snapshot (%s) does not exist",
snapname);
if (ret < 0) {
goto out;
}
gf_log (this->name, GF_LOG_ERROR, "%s", *op_errstr);
ret = -1;
goto out;
}
snap_restored = snap->snap_restored;
if (snap_restored) {
ret = gf_asprintf (op_errstr, "Snapshot (%s) is already "
"restored", snapname);
if (ret < 0) {
goto out;
}
gf_log (this->name, GF_LOG_ERROR, "%s", *op_errstr);
ret = -1;
goto out;
}
ret = dict_set_str (rsp_dict, "snapname", snapname);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set "
"snap name(%s)", snapname);
goto out;
}
ret = dict_get_int32 (dict, "volcount", &volcount);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get volume count");
goto out;
}
/* Snapshot restore will only work if all the volumes,
that are part of the snapshot, are stopped. */
for (i = 1; i <= volcount; ++i) {
snprintf (key, sizeof (key), "volname%d", i);
ret = dict_get_str (dict, key, &volname);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to "
"get volume name");
goto out;
}
ret = glusterd_volinfo_find (volname, &volinfo);
if (ret) {
ret = gf_asprintf (op_errstr, "Volume (%s) "
"does not exist", volname);
if (ret < 0) {
goto out;
}
gf_log (this->name, GF_LOG_ERROR, "%s", *op_errstr);
ret = -1;
goto out;
}
if (glusterd_is_volume_started (volinfo)) {
ret = gf_asprintf (op_errstr, "Volume (%s) has been "
"started. Volume needs to be stopped before restoring "
"a snapshot.", volname);
if (ret < 0) {
goto out;
}
gf_log (this->name, GF_LOG_ERROR, "%s", *op_errstr);
ret = -1;
goto out;
}
}
/* Get brickinfo for snap_volumes */
volcount = 0;
cds_list_for_each_entry (volinfo, &snap->volumes, vol_list) {
volcount++;
brick_count = 0;
cds_list_for_each_entry (brickinfo, &volinfo->bricks,
brick_list) {
brick_count++;
if (gf_uuid_compare (brickinfo->uuid, MY_UUID))
continue;
snprintf (key, sizeof (key), "snap%d.brick%d.path",
volcount, brick_count);
ret = dict_set_str (rsp_dict, key, brickinfo->path);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.snap_status",
volcount, brick_count);
ret = dict_set_int32 (rsp_dict, key,
brickinfo->snap_status);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.device_path",
volcount, brick_count);
ret = dict_set_str (rsp_dict, key,
brickinfo->device_path);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.fs_type",
volcount, brick_count);
ret = dict_set_str (rsp_dict, key,
brickinfo->fstype);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.mnt_opts",
volcount, brick_count);
ret = dict_set_str (rsp_dict, key,
brickinfo->mnt_opts);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
}
snprintf (key, sizeof (key), "snap%d.brick_count", volcount);
ret = dict_set_int32 (rsp_dict, key, brick_count);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
}
ret = dict_set_int32 (rsp_dict, "volcount", volcount);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
out:
return ret;
}
int
snap_max_hard_limits_validate (dict_t *dict, char *volname,
uint64_t value, char **op_errstr)
{
char err_str[PATH_MAX] = "";
glusterd_conf_t *conf = NULL;
glusterd_volinfo_t *volinfo = NULL;
int ret = -1;
uint64_t max_limit = GLUSTERD_SNAPS_MAX_HARD_LIMIT;
xlator_t *this = NULL;
uint64_t opt_hard_max = GLUSTERD_SNAPS_MAX_HARD_LIMIT;
this = THIS;
GF_ASSERT (this);
GF_ASSERT (dict);
GF_ASSERT (op_errstr);
conf = this->private;
GF_ASSERT (conf);
if (volname) {
ret = glusterd_volinfo_find (volname, &volinfo);
if (!ret) {
if (volinfo->is_snap_volume) {
ret = -1;
snprintf (err_str, PATH_MAX,
"%s is a snap volume. Configuring "
"snap-max-hard-limit for a snap "
"volume is prohibited.", volname);
goto out;
}
}
}
/* "snap-max-hard-limit" might not be set by user explicitly,
* in that case it's better to use the default value.
* Hence not erroring out if Key is not found.
*/
ret = dict_get_uint64 (conf->opts,
GLUSTERD_STORE_KEY_SNAP_MAX_HARD_LIMIT,
&opt_hard_max);
if (ret) {
ret = 0;
gf_log (this->name, GF_LOG_DEBUG, "%s is not present in "
"opts dictionary",
GLUSTERD_STORE_KEY_SNAP_MAX_HARD_LIMIT);
}
/* volume snap-max-hard-limit cannot exceed system snap-max-hard-limit.
* Hence during prevalidate following checks are made to ensure the
* snap-max-hard-limit set on one particular volume does not
* exceed snap-max-hard-limit set globally (system limit).
*/
if (value && volname) {
max_limit = opt_hard_max;
}
if ((value < 0) || (value > max_limit)) {
ret = -1;
snprintf (err_str, PATH_MAX, "Invalid snap-max-hard-limit "
"%"PRIu64 ". Expected range 1 - %"PRIu64,
value, max_limit);
goto out;
}
ret = 0;
out:
if (ret) {
*op_errstr = gf_strdup (err_str);
gf_log (this->name, GF_LOG_ERROR, "%s", err_str);
}
return ret;
}
int
glusterd_snapshot_config_prevalidate (dict_t *dict, char **op_errstr)
{
char *volname = NULL;
glusterd_volinfo_t *volinfo = NULL;
xlator_t *this = NULL;
int ret = -1;
int config_command = 0;
char err_str[PATH_MAX] = {0,};
glusterd_conf_t *conf = NULL;
uint64_t hard_limit = 0;
uint64_t soft_limit = 0;
gf_loglevel_t loglevel = GF_LOG_ERROR;
uint64_t max_limit = GLUSTERD_SNAPS_MAX_HARD_LIMIT;
int32_t cur_auto_delete = 0;
int32_t req_auto_delete = 0;
int32_t cur_snap_activate = 0;
int32_t req_snap_activate = 0;
this = THIS;
GF_ASSERT (this);
GF_ASSERT (dict);
GF_ASSERT (op_errstr);
conf = this->private;
GF_ASSERT (conf);
ret = dict_get_int32 (dict, "config-command", &config_command);
if (ret) {
snprintf (err_str, sizeof (err_str),
"failed to get config-command type");
goto out;
}
if (config_command != GF_SNAP_CONFIG_TYPE_SET) {
ret = 0;
goto out;
}
ret = dict_get_str (dict, "volname", &volname);
if (volname) {
ret = glusterd_volinfo_find (volname, &volinfo);
if (ret) {
snprintf (err_str, sizeof (err_str),
"Volume (%s) does not exist.", volname);
goto out;
}
}
/* config values snap-max-hard-limit and snap-max-soft-limit are
* optional and hence we are not erroring out if values are not
* present
*/
gd_get_snap_conf_values_if_present (dict, &hard_limit, &soft_limit);
if (hard_limit) {
/* Validations for snap-max-hard-limits */
ret = snap_max_hard_limits_validate (dict, volname,
hard_limit, op_errstr);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"snap-max-hard-limit validation failed.");
goto out;
}
}
if (soft_limit) {
max_limit = GLUSTERD_SNAPS_MAX_SOFT_LIMIT_PERCENT;
if ((soft_limit < 0) || (soft_limit > max_limit)) {
ret = -1;
snprintf (err_str, PATH_MAX, "Invalid "
"snap-max-soft-limit ""%"
PRIu64 ". Expected range 1 - %"PRIu64,
soft_limit, max_limit);
goto out;
}
}
if (hard_limit || soft_limit) {
ret = 0;
goto out;
}
if (dict_get(dict, GLUSTERD_STORE_KEY_SNAP_AUTO_DELETE)) {
req_auto_delete = dict_get_str_boolean (dict,
GLUSTERD_STORE_KEY_SNAP_AUTO_DELETE,
_gf_false);
if (req_auto_delete < 0) {
ret = -1;
snprintf (err_str, sizeof (err_str), "Please enter a "
"valid boolean value for auto-delete");
goto out;
}
/* Ignoring the error as the auto-delete is optional and
might not be present in the options dictionary.*/
cur_auto_delete = dict_get_str_boolean (conf->opts,
GLUSTERD_STORE_KEY_SNAP_AUTO_DELETE,
_gf_false);
if (cur_auto_delete == req_auto_delete) {
ret = -1;
if (cur_auto_delete == _gf_true)
snprintf (err_str, sizeof (err_str),
"auto-delete is already enabled");
else
snprintf (err_str, sizeof (err_str),
"auto-delete is already disabled");
goto out;
}
} else if (dict_get(dict, GLUSTERD_STORE_KEY_SNAP_ACTIVATE)) {
req_snap_activate = dict_get_str_boolean (dict,
GLUSTERD_STORE_KEY_SNAP_ACTIVATE,
_gf_false);
if (req_snap_activate < 0) {
ret = -1;
snprintf (err_str, sizeof (err_str), "Please enter a "
"valid boolean value for activate-on-create");
goto out;
}
/* Ignoring the error as the activate-on-create is optional and
might not be present in the options dictionary.*/
cur_snap_activate = dict_get_str_boolean (conf->opts,
GLUSTERD_STORE_KEY_SNAP_ACTIVATE,
_gf_false);
if (cur_snap_activate == req_snap_activate) {
ret = -1;
if (cur_snap_activate == _gf_true)
snprintf (err_str, sizeof (err_str),
"activate-on-create is already enabled");
else
snprintf (err_str, sizeof (err_str),
"activate-on-create is already disabled");
goto out;
}
} else {
ret = -1;
snprintf (err_str, sizeof (err_str), "Invalid option");
goto out;
}
ret = 0;
out:
if (ret && err_str[0] != '\0') {
gf_log (this->name, loglevel, "%s", err_str);
*op_errstr = gf_strdup (err_str);
}
return ret;
}
/* This function will be called from RPC handler routine.
* This function is responsible for getting the requested
* snapshot config into the dictionary.
*
* @param req RPC request object. Required for sending a response back.
* @param op glusterd operation. Required for sending a response back.
* @param dict pointer to dictionary which will contain both
* request and response key-pair values.
* @return -1 on error and 0 on success
*/
int
glusterd_handle_snapshot_config (rpcsvc_request_t *req, glusterd_op_t op,
dict_t *dict, char *err_str, size_t len)
{
int32_t ret = -1;
char *volname = NULL;
xlator_t *this = NULL;
int config_command = 0;
this = THIS;
GF_ASSERT (this);
GF_VALIDATE_OR_GOTO (this->name, req, out);
GF_VALIDATE_OR_GOTO (this->name, dict, out);
/* TODO : Type of lock to be taken when we are setting
* limits system wide
*/
ret = dict_get_int32 (dict, "config-command", &config_command);
if (ret) {
snprintf (err_str, len,
"Failed to get config-command type");
goto out;
}
ret = dict_get_str (dict, "volname", &volname);
switch (config_command) {
case GF_SNAP_CONFIG_TYPE_SET:
if (!volname) {
ret = dict_set_int32 (dict, "hold_vol_locks",
_gf_false);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Unable to set hold_vol_locks value "
"as _gf_false");
goto out;
}
}
ret = glusterd_mgmt_v3_initiate_all_phases (req, op, dict);
break;
case GF_SNAP_CONFIG_DISPLAY:
/* Reading data from local node only */
ret = snap_max_limits_display_commit (dict, volname,
err_str, len);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"snap-max-limit "
"display commit failed.");
goto out;
}
/* If everything is successful then send the response
* back to cli
*/
ret = glusterd_op_send_cli_response (op, 0, 0, req, dict,
err_str);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to send cli "
"response");
goto out;
}
break;
default:
gf_log (this->name, GF_LOG_ERROR, "Unknown config type");
ret = -1;
break;
}
out:
return ret;
}
int
glusterd_snap_create_clone_pre_val_use_rsp_dict (dict_t *dst, dict_t *src)
{
char *snap_brick_dir = NULL;
char *snap_device = NULL;
char key[PATH_MAX] = "";
char *value = "";
char snapbrckcnt[PATH_MAX] = "";
char snapbrckord[PATH_MAX] = "";
int ret = -1;
int64_t i = -1;
int64_t j = -1;
int64_t volume_count = 0;
int64_t brick_count = 0;
int64_t brick_order = 0;
xlator_t *this = NULL;
int32_t brick_online = 0;
this = THIS;
GF_ASSERT (this);
GF_ASSERT (dst);
GF_ASSERT (src);
ret = dict_get_int64 (src, "volcount", &volume_count);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "failed to "
"get the volume count");
goto out;
}
for (i = 0; i < volume_count; i++) {
memset (snapbrckcnt, '\0', sizeof(snapbrckcnt));
ret = snprintf (snapbrckcnt, sizeof(snapbrckcnt) - 1,
"vol%"PRId64"_brickcount", i+1);
ret = dict_get_int64 (src, snapbrckcnt, &brick_count);
if (ret) {
gf_log (this->name, GF_LOG_TRACE,
"No bricks for this volume in this dict");
continue;
}
for (j = 0; j < brick_count; j++) {
/* Fetching data from source dict */
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".brickdir%"PRId64, i+1, j);
ret = dict_get_ptr (src, key,
(void **)&snap_brick_dir);
if (ret) {
gf_log (this->name, GF_LOG_WARNING,
"Unable to fetch %s", key);
continue;
}
/* Fetching brick order from source dict */
snprintf (snapbrckord, sizeof(snapbrckord) - 1,
"vol%"PRId64".brick%"PRId64".order", i+1, j);
ret = dict_get_int64 (src, snapbrckord, &brick_order);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get brick order");
goto out;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".brickdir%"PRId64, i+1,
brick_order);
ret = dict_set_dynstr_with_alloc (dst, key,
snap_brick_dir);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".fstype%"PRId64, i+1, j);
ret = dict_get_str (src, key, &value);
if (ret) {
gf_log (this->name, GF_LOG_WARNING,
"Unable to fetch %s", key);
continue;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".fstype%"PRId64, i+1,
brick_order);
ret = dict_set_dynstr_with_alloc (dst, key, value);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".mnt_opts%"PRId64, i+1, j);
ret = dict_get_str (src, key, &value);
if (ret) {
gf_log (this->name, GF_LOG_WARNING,
"Unable to fetch %s", key);
continue;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".mnt_opts%"PRId64, i+1,
brick_order);
ret = dict_set_dynstr_with_alloc (dst, key, value);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".brick_snapdevice%"PRId64,
i+1, j);
ret = dict_get_ptr (src, key,
(void **)&snap_device);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Unable to fetch snap_device");
goto out;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".brick_snapdevice%"PRId64,
i+1, brick_order);
ret = dict_set_dynstr_with_alloc (dst, key,
snap_device);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"vol%"PRId64".brick%"PRId64".status", i+1, brick_order);
ret = dict_get_int32 (src, key, &brick_online);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "failed to "
"get the brick status");
goto out;
}
ret = dict_set_int32 (dst, key, brick_online);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "failed to "
"set the brick status");
goto out;
}
brick_online = 0;
}
}
ret = 0;
out:
gf_log (this->name, GF_LOG_TRACE, "Returning %d", ret);
return ret;
}
/* Aggregate brickinfo's of the snap volumes to be restored from */
int32_t
glusterd_snap_restore_use_rsp_dict (dict_t *dst, dict_t *src)
{
char key[PATH_MAX] = "";
char *strvalue = NULL;
int32_t value = -1;
int32_t i = -1;
int32_t j = -1;
int32_t vol_count = -1;
int32_t brickcount = -1;
int32_t ret = -1;
xlator_t *this = NULL;
this = THIS;
GF_ASSERT (this);
if (!dst || !src) {
gf_log (this->name, GF_LOG_ERROR, "Source or Destination "
"dict is empty.");
goto out;
}
ret = dict_get_int32 (src, "volcount", &vol_count);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG, "No volumes");
ret = 0;
goto out;
}
for (i = 1; i <= vol_count; i++) {
snprintf (key, sizeof (key), "snap%d.brick_count", i);
ret = dict_get_int32 (src, key, &brickcount);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get %s", key);
goto out;
}
for (j = 1; j <= brickcount; j++) {
snprintf (key, sizeof (key), "snap%d.brick%d.path",
i, j);
ret = dict_get_str (src, key, &strvalue);
if (ret) {
/* The brickinfo will be present in
* another rsp_dict */
gf_log (this->name, GF_LOG_DEBUG,
"%s not present", key);
ret = 0;
continue;
}
ret = dict_set_dynstr_with_alloc (dst, key, strvalue);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.snap_status", i, j);
ret = dict_get_int32 (src, key, &value);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get %s", key);
goto out;
}
ret = dict_set_int32 (dst, key, value);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.device_path", i, j);
ret = dict_get_str (src, key, &strvalue);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get %s", key);
goto out;
}
ret = dict_set_dynstr_with_alloc (dst, key, strvalue);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.fs_type", i, j);
ret = dict_get_str (src, key, &strvalue);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get %s", key);
goto out;
}
ret = dict_set_dynstr_with_alloc (dst, key, strvalue);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"snap%d.brick%d.mnt_opts", i, j);
ret = dict_get_str (src, key, &strvalue);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to get %s", key);
goto out;
}
ret = dict_set_dynstr_with_alloc (dst, key, strvalue);
if (ret) {
gf_log (this->name, GF_LOG_DEBUG,
"Failed to set %s", key);
goto out;
}
}
}
out:
gf_log (this->name, GF_LOG_TRACE, "Returning %d", ret);
return ret;
}
int
glusterd_snap_pre_validate_use_rsp_dict (dict_t *dst, dict_t *src)
{
int ret = -1;
int32_t snap_command = 0;
xlator_t *this = NULL;
this = THIS;
GF_ASSERT (this);
if (!dst || !src) {
gf_log (this->name, GF_LOG_ERROR, "Source or Destination "
"dict is empty.");
goto out;
}
ret = dict_get_int32 (dst, "type", &snap_command);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "unable to get the type of "
"the snapshot command");
goto out;
}
switch (snap_command) {
case GF_SNAP_OPTION_TYPE_CREATE:
case GF_SNAP_OPTION_TYPE_CLONE:
ret = glusterd_snap_create_clone_pre_val_use_rsp_dict (dst,
src);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Unable to use "
"rsp dict");
goto out;
}
break;
case GF_SNAP_OPTION_TYPE_RESTORE:
ret = glusterd_snap_restore_use_rsp_dict (dst, src);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Unable to use "
"rsp dict");
goto out;
}
break;
default:
break;
}
ret = 0;
out:
gf_log (this->name, GF_LOG_DEBUG, "Returning %d", ret);
return ret;
}
int
glusterd_add_brick_status_to_dict (dict_t *dict, glusterd_volinfo_t *volinfo,
glusterd_brickinfo_t *brickinfo,
char *key_prefix)
{
char pidfile[PATH_MAX] = {0, };
int32_t brick_online = 0;
pid_t pid = 0;
xlator_t *this = NULL;
glusterd_conf_t *conf = NULL;
int ret = -1;
GF_ASSERT (dict);
GF_ASSERT (volinfo);
GF_ASSERT (brickinfo);
this = THIS;
GF_ASSERT (this);
conf = this->private;
GF_ASSERT (conf);
if (!key_prefix) {
gf_log (this->name, GF_LOG_ERROR, "key prefix is NULL");
goto out;
}
GLUSTERD_GET_BRICK_PIDFILE (pidfile, volinfo, brickinfo, conf);
brick_online = gf_is_service_running (pidfile, &pid);
ret = dict_set_int32 (dict, key_prefix, brick_online);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key_prefix);
goto out;
}
brick_online = 0;
ret = 0;
out:
return ret;
}
/* This function will check whether the given device
* is a thinly provisioned LV or not.
*
* @param device LV device path
*
* @return _gf_true if LV is thin else _gf_false
*/
gf_boolean_t
glusterd_is_thinp_brick (char *device)
{
int ret = -1;
char msg [1024] = "";
char pool_name [PATH_MAX] = "";
char *ptr = NULL;
xlator_t *this = NULL;
runner_t runner = {0,};
gf_boolean_t is_thin = _gf_false;
this = THIS;
GF_VALIDATE_OR_GOTO ("glusterd", this, out);
GF_VALIDATE_OR_GOTO (this->name, device, out);
snprintf (msg, sizeof (msg), "Get thin pool name for device %s",
device);
runinit (&runner);
runner_add_args (&runner, "/sbin/lvs", "--noheadings", "-o", "pool_lv",
device, NULL);
runner_redir (&runner, STDOUT_FILENO, RUN_PIPE);
runner_log (&runner, this->name, GF_LOG_DEBUG, msg);
ret = runner_start (&runner);
if (ret == -1) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get thin pool "
"name for device %s", device);
runner_end (&runner);
goto out;
}
ptr = fgets(pool_name, sizeof(pool_name),
runner_chio (&runner, STDOUT_FILENO));
if (!ptr || !strlen(pool_name)) {
gf_log (this->name, GF_LOG_ERROR, "Failed to get pool name "
"for device %s", device);
runner_end (&runner);
ret = -1;
goto out;
}
runner_end (&runner);
/* Trim all the whitespaces. */
ptr = gf_trim (pool_name);
/* If the LV has thin pool associated with this
* then it is a thinly provisioned LV else it is
* regular LV */
if (0 != ptr [0]) {
is_thin = _gf_true;
}
out:
return is_thin;
}
int
glusterd_snap_create_clone_common_prevalidate (dict_t *rsp_dict, int flags,
char *snapname, char *err_str, char *snap_volname, int64_t volcount,
glusterd_volinfo_t *volinfo, gf_loglevel_t *loglevel, int clone) {
char *device = NULL;
char key[PATH_MAX] = "";
int ret = -1;
int64_t i = 1;
int64_t brick_order = 0;
int64_t brick_count = 0;
xlator_t *this = NULL;
glusterd_conf_t *conf = NULL;
glusterd_brickinfo_t *brickinfo = NULL;
this = THIS;
conf = this->private;
GF_ASSERT (conf);
if (!snapname || !volinfo) {
gf_log (this->name, GF_LOG_ERROR, "Failed to validate "
"snapname or volume information");
ret = -1;
goto out;
}
cds_list_for_each_entry (brickinfo, &volinfo->bricks,
brick_list) {
if (gf_uuid_compare (brickinfo->uuid, MY_UUID)) {
brick_order++;
continue;
}
if (!glusterd_is_brick_started (brickinfo)) {
if (!clone && (flags & GF_CLI_FLAG_OP_FORCE)) {
gf_log (this->name, GF_LOG_WARNING,
"brick %s:%s is not started",
brickinfo->hostname,
brickinfo->path);
brick_order++;
brick_count++;
continue;
}
if (!clone) {
snprintf (err_str, PATH_MAX,
"One or more bricks are not running. "
"Please run volume status command to see "
"brick status.\n"
"Please start the stopped brick "
"and then issue snapshot create "
"command or use [force] option in "
"snapshot create to override this "
"behavior.");
} else {
snprintf (err_str, PATH_MAX,
"One or more bricks are not running. "
"Please run snapshot status command to see "
"brick status.\n"
"Please start the stopped brick "
"and then issue snapshot clone "
"command ");
}
ret = -1;
goto out;
}
device = glusterd_get_brick_mount_device
(brickinfo->path);
if (!device) {
snprintf (err_str, PATH_MAX,
"getting device name for the brick "
"%s:%s failed", brickinfo->hostname,
brickinfo->path);
ret = -1;
goto out;
}
if (!clone) {
if (!glusterd_is_thinp_brick (device)) {
snprintf (err_str, PATH_MAX,
"Snapshot is supported only for "
"thin provisioned LV. Ensure that "
"all bricks of %s are thinly "
"provisioned LV.", volinfo->volname);
ret = -1;
goto out;
}
}
device = glusterd_build_snap_device_path (device,
snap_volname,
brick_count);
if (!device) {
snprintf (err_str, PATH_MAX,
"cannot copy the snapshot device "
"name (volname: %s, snapname: %s)",
volinfo->volname, snapname);
*loglevel = GF_LOG_WARNING;
ret = -1;
goto out;
}
snprintf (key, sizeof(key),
"vol%"PRId64".brick_snapdevice%"PRId64,
i, brick_count);
ret = dict_set_dynstr (rsp_dict, key, device);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
GF_FREE (device);
goto out;
}
device = NULL;
ret = glusterd_update_mntopts (brickinfo->path,
brickinfo);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to "
"update mount options for %s brick",
brickinfo->path);
}
snprintf (key, sizeof(key), "vol%"PRId64".fstype%"
PRId64, i, brick_count);
ret = dict_set_dynstr_with_alloc (rsp_dict, key,
brickinfo->fstype);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof(key), "vol%"PRId64".mnt_opts%"
PRId64, i, brick_count);
ret = dict_set_dynstr_with_alloc (rsp_dict, key,
brickinfo->mnt_opts);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof(key), "vol%"PRId64".brickdir%"PRId64, i,
brick_count);
ret = dict_set_dynstr_with_alloc (rsp_dict, key,
brickinfo->mount_dir);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof(key) - 1,
"vol%"PRId64".brick%"PRId64".order", i, brick_count);
ret = dict_set_int64 (rsp_dict, key, brick_order);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to set %s", key);
goto out;
}
snprintf (key, sizeof (key),
"vol%"PRId64".brick%"PRId64".status", i, brick_order);
ret = glusterd_add_brick_status_to_dict (rsp_dict,
volinfo,
brickinfo,
key);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "failed to "
"add brick status to dict");
goto out;
}
brick_count++;
brick_order++;
}
snprintf (key, sizeof(key) - 1, "vol%"PRId64"_brickcount", volcount);
ret = dict_set_int64 (rsp_dict, key, brick_count);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set %s",
key);
goto out;
}
ret = 0;
out:
if (device)
GF_FREE (device);
return ret;
}
int
glusterd_snapshot_clone_prevalidate (dict_t *dict, char **op_errstr,
dict_t *rsp_dict)
{
char *clonename = NULL;
char *snapname = NULL;
char key[PATH_MAX] = "";
glusterd_snap_t *snap = NULL;
char err_str[PATH_MAX] = "";
int ret = -1;
int64_t volcount = 1;
glusterd_volinfo_t *snap_vol = NULL;
xlator_t *this = NULL;
uuid_t *snap_volid = NULL;
gf_loglevel_t loglevel = GF_LOG_ERROR;
this = THIS;
GF_ASSERT (op_errstr);
GF_ASSERT (dict);
ret = dict_get_str (dict, "clonename", &clonename);
if (ret) {
snprintf (err_str, sizeof (err_str), "Failed to "
"get the clone name");
goto out;
}
ret = dict_get_str (dict, "snapname", &snapname);
if (ret) {
snprintf (err_str, sizeof (err_str), "Failed to get snapname");
goto out;
}
if (glusterd_check_volume_exists(clonename)) {
ret = -1;
snprintf (err_str, sizeof (err_str), "Volume with name:%s "
"already exists", clonename);
goto out;
}
/* need to find snap volinfo*/
snap = glusterd_find_snap_by_name (snapname);
if (!snap) {
ret = -1;
snprintf (err_str, sizeof (err_str), "Failed to find :%s "
"snap", snapname);
goto out;
}
/* TODO : As of now there is only one volume in snapshot.
* Change this when multiple volume snapshot is introduced
*/
snap_vol = list_entry (snap->volumes.next,
glusterd_volinfo_t, vol_list);
if (!snap_vol) {
gf_log ("glusterd", GF_LOG_ERROR, "Failed to get snap "
"volinfo %s", snap->snapname);
goto out;
}
snprintf (key, sizeof(key) - 1, "vol1_volid");
ret = dict_get_bin (dict, key, (void **)&snap_volid);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Unable to fetch snap_volid");
goto out;
}
/* Adding snap bricks mount paths to the dict */
ret = glusterd_snap_create_clone_common_prevalidate (rsp_dict, 0,
snapname, err_str, clonename, 1, snap_vol,
&loglevel, 1);
if (ret) {
goto out;
}
ret = dict_set_int64 (rsp_dict, "volcount", volcount);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set volcount");
goto out;
}
ret = 0;
out:
if (ret && err_str[0] != '\0') {
gf_log (this->name, loglevel, "%s", err_str);
*op_errstr = gf_strdup (err_str);
}
gf_log (this->name, GF_LOG_TRACE, "Returning %d", ret);
return ret;
}
int
glusterd_snapshot_create_prevalidate (dict_t *dict, char **op_errstr,
dict_t *rsp_dict)
{
char *volname = NULL;
char *snapname = NULL;
char key[PATH_MAX] = "";
char snap_volname[64] = "";
char err_str[PATH_MAX] = "";
int ret = -1;
int64_t i = 0;
int64_t volcount = 0;
glusterd_volinfo_t *volinfo = NULL;
xlator_t *this = NULL;
uuid_t *snap_volid = NULL;
gf_loglevel_t loglevel = GF_LOG_ERROR;
glusterd_conf_t *conf = NULL;
int64_t effective_max_limit = 0;
int flags = 0;
uint64_t opt_hard_max = GLUSTERD_SNAPS_MAX_HARD_LIMIT;
this = THIS;
GF_ASSERT (op_errstr);
conf = this->private;
GF_ASSERT (conf);
ret = dict_get_int64 (dict, "volcount", &volcount);
if (ret) {
snprintf (err_str, sizeof (err_str), "Failed to "
"get the volume count");
goto out;
}
if (volcount <= 0) {
snprintf (err_str, sizeof (err_str),
"Invalid volume count %"PRId64" supplied", volcount);
ret = -1;
goto out;
}
ret = dict_get_str (dict, "snapname", &snapname);
if (ret) {
snprintf (err_str, sizeof (err_str), "Failed to get snapname");
goto out;
}
ret = dict_get_int32 (dict, "flags", &flags);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Unable to get flags");
goto out;
}
if (glusterd_find_snap_by_name (snapname)) {
ret = -1;
snprintf (err_str, sizeof (err_str), "Snapshot %s already "
"exists", snapname);
goto out;
}
for (i = 1; i <= volcount; i++) {
snprintf (key, sizeof (key), "volname%"PRId64, i);
ret = dict_get_str (dict, key, &volname);
if (ret) {
snprintf (err_str, sizeof (err_str),
"failed to get volume name");
goto out;
}
ret = glusterd_volinfo_find (volname, &volinfo);
if (ret) {
snprintf (err_str, sizeof (err_str),
"Volume (%s) does not exist ", volname);
goto out;
}
ret = -1;
if (!glusterd_is_volume_started (volinfo)) {
snprintf (err_str, sizeof (err_str), "volume %s is "
"not started", volinfo->volname);
loglevel = GF_LOG_WARNING;
goto out;
}
if (glusterd_is_defrag_on (volinfo)) {
snprintf (err_str, sizeof (err_str),
"rebalance process is running for the "
"volume %s", volname);
loglevel = GF_LOG_WARNING;
goto out;
}
if (gd_vol_is_geo_rep_active (volinfo)) {
snprintf (err_str, sizeof (err_str),
"geo-replication session is running for "
"the volume %s. Session needs to be "
"stopped before taking a snapshot.",
volname);
loglevel = GF_LOG_WARNING;
goto out;
}
if (volinfo->is_snap_volume == _gf_true) {
snprintf (err_str, sizeof (err_str),
"Volume %s is a snap volume", volname);
loglevel = GF_LOG_WARNING;
goto out;
}
/* "snap-max-hard-limit" might not be set by user explicitly,
* in that case it's better to consider the default value.
* Hence not erroring out if Key is not found.
*/
ret = dict_get_uint64 (conf->opts,
GLUSTERD_STORE_KEY_SNAP_MAX_HARD_LIMIT,
&opt_hard_max);
if (ret) {
ret = 0;
gf_log (this->name, GF_LOG_DEBUG, "%s is not present "
"in opts dictionary",
GLUSTERD_STORE_KEY_SNAP_MAX_HARD_LIMIT);
}
if (volinfo->snap_max_hard_limit < opt_hard_max)
effective_max_limit = volinfo->snap_max_hard_limit;
else
effective_max_limit = opt_hard_max;
if (volinfo->snap_count >= effective_max_limit) {
ret = -1;
snprintf (err_str, sizeof (err_str),
"The number of existing snaps has reached "
"the effective maximum limit of %"PRIu64", "
"for the volume (%s). Please delete few "
"snapshots before taking further snapshots.",
effective_max_limit, volname);
loglevel = GF_LOG_WARNING;
goto out;
}
snprintf (key, sizeof(key) - 1, "vol%"PRId64"_volid", i);
ret = dict_get_bin (dict, key, (void **)&snap_volid);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Unable to fetch snap_volid");
goto out;
}
/* snap volume uuid is used as lvm snapshot name.
This will avoid restrictions on snapshot names
provided by user */
GLUSTERD_GET_UUID_NOHYPHEN (snap_volname, *snap_volid);
ret = glusterd_snap_create_clone_common_prevalidate (rsp_dict,
flags, snapname, err_str, snap_volname, i,
volinfo, &loglevel, 0);
if (ret) {
gf_log (this->name, GF_LOG_ERROR,
"Failed to pre validate");
goto out;
}
}
ret = dict_set_int64 (rsp_dict, "volcount", volcount);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "Failed to set volcount");
goto out;
}
ret = 0;
out:
if (ret && err_str[0] != '\0') {
gf_log (this->name, loglevel, "%s", err_str);
*op_errstr = gf_strdup (err_str);
}
gf_log (this->name, GF_LOG_TRACE, "Returning %d", ret);
return ret;
}
glusterd_snap_t*
glusterd_new_snap_object()
{
glusterd_snap_t *snap = NULL;
snap = GF_CALLOC (1, sizeof (*snap), gf_gld_mt_snap_t);
if (snap) {
if (LOCK_INIT (&snap->lock)) {
gf_log (THIS->name, GF_LOG_ERROR, "Failed initiating"
" snap lock");
GF_FREE (snap);
return NULL;
}
CDS_INIT_LIST_HEAD (&snap->snap_list);
CDS_INIT_LIST_HEAD (&snap->volumes);
snap->snapname[0] = 0;
snap->snap_status = GD_SNAP_STATUS_INIT;
}
return snap;
};
/* Function glusterd_list_add_snapvol adds the volinfo object (snapshot volume)
to the snapshot object list and to the parent volume list */
int32_t
glusterd_list_add_snapvol (glusterd_volinfo_t *origin_vol,
glusterd_volinfo_t *snap_vol)
{
int ret = -1;
glusterd_snap_t *snap = NULL;
GF_VALIDATE_OR_GOTO ("glusterd", origin_vol, out);
GF_VALIDATE_OR_GOTO ("glusterd", snap_vol, out);
snap = snap_vol->snapshot;
GF_ASSERT (snap);
cds_list_add_tail (&snap_vol->vol_list, &snap->volumes);
LOCK (&origin_vol->lock);
{
glusterd_list_add_order (&snap_vol->snapvol_list,
&origin_vol->snap_volumes,
glusterd_compare_snap_vol_time);
origin_vol->snap_count++;
}
UNLOCK (&origin_vol->lock);
gf_log (THIS->name, GF_LOG_DEBUG, "Snapshot %s added to the list",
snap->snapname);
ret = 0;
out:
return ret;
}
glusterd_snap_t*
glusterd_find_snap_by_name (char *snapname)
{
glusterd_snap_t *snap = NULL;
glusterd_conf_t *priv = NULL;
priv = THIS->private;
GF_ASSERT (priv);
GF_ASSERT (snapname);
cds_list_for_each_entry (snap, &priv->snapshots, snap_list) {
if (!strcmp (snap->snapname, snapname)) {
gf_log (THIS->name, GF_LOG_DEBUG, "Found "
"snap %s (%s)", snap->snapname,
uuid_utoa (snap->snap_id));
goto out;
}
}
snap = NULL;
out:
return snap;
}
glusterd_snap_t*
glusterd_find_snap_by_id (uuid_t snap_id)
{
glusterd_snap_t *snap = NULL;
glusterd_conf_t *priv = NULL;
priv = THIS->private;
GF_ASSERT (priv);
if (gf_uuid_is_null(snap_id))
goto out;
cds_list_for_each_entry (snap, &priv->snapshots, snap_list) {
if (!gf_uuid_compare (snap->snap_id, snap_id)) {
gf_log (THIS->name, GF_LOG_DEBUG, "Found "
"snap %s (%s)", snap->snapname,
uuid_utoa (snap->snap_id));
goto out;
}
}
snap = NULL;
out:
return snap;
}
int
glusterd_do_lvm_snapshot_remove (glusterd_volinfo_t *snap_vol,
glusterd_brickinfo_t *brickinfo,
const char *mount_pt, const char *snap_device)
{
int ret = -1;
xlator_t *this = NULL;
glusterd_conf_t *priv = NULL;
runner_t runner = {0,};
char msg[1024] = {0, };
char pidfile[PATH_MAX] = {0, };
pid_t pid = -1;
int retry_count = 0;
char *mnt_pt = NULL;
struct mntent *entry = NULL;
gf_boolean_t unmount = _gf_true;
this = THIS;
GF_ASSERT (this);
priv = this->private;
GF_ASSERT (priv);
if (!brickinfo) {
gf_log (this->name, GF_LOG_ERROR, "brickinfo NULL");
goto out;
}
GF_ASSERT (snap_vol);
GF_ASSERT (mount_pt);
GF_ASSERT (snap_device);
GLUSTERD_GET_BRICK_PIDFILE (pidfile, snap_vol, brickinfo, priv);
if (gf_is_service_running (pidfile, &pid)) {
ret = kill (pid, SIGKILL);
if (ret && errno != ESRCH) {
gf_log (this->name, GF_LOG_ERROR, "Unable to kill pid "
"%d reason : %s", pid, strerror(errno));
goto out;
}
}
/* Check if the brick is mounted and then try unmounting the brick */
ret = glusterd_get_brick_root (brickinfo->path, &mnt_pt);
if (ret) {
gf_log (this->name, GF_LOG_WARNING, "Getting the root "
"of the brick for volume %s (snap %s) failed. "
"Removing lv (%s).", snap_vol->volname,
snap_vol->snapshot->snapname, snap_device);
/* The brick path is already unmounted. Remove the lv only *
* Need not fail the operation */
ret = 0;
unmount = _gf_false;
}
if ((unmount == _gf_true) && (strcmp (mnt_pt, mount_pt))) {
gf_log (this->name, GF_LOG_WARNING,
"Lvm is not mounted for brick %s:%s. "
"Removing lv (%s).", brickinfo->hostname,
brickinfo->path, snap_device);
/* The brick path is already unmounted. Remove the lv only *
* Need not fail the operation */
unmount = _gf_false;
}
/* umount cannot be done when the brick process is still in the process
of shutdown, so give three re-tries */
while ((unmount == _gf_true) && (retry_count < 3)) {
retry_count++;
/*umount2 system call doesn't cleanup mtab entry after un-mount.
So use external umount command*/
ret = glusterd_umount(mount_pt);
if (!ret)
break;
gf_log (this->name, GF_LOG_DEBUG, "umount failed for "
"path %s (brick: %s): %s. Retry(%d)", mount_pt,
brickinfo->path, strerror (errno), retry_count);
sleep (1);
}
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "umount failed for "
"path %s (brick: %s): %s.", mount_pt,
brickinfo->path, strerror (errno));
goto out;
}
runinit (&runner);
snprintf (msg, sizeof(msg), "remove snapshot of the brick %s:%s, "
"device: %s", brickinfo->hostname, brickinfo->path,
snap_device);
runner_add_args (&runner, LVM_REMOVE, "-f", snap_device, NULL);
runner_log (&runner, "", GF_LOG_DEBUG, msg);
ret = runner_run (&runner);
if (ret) {
gf_log (this->name, GF_LOG_ERROR, "removing snapshot of the "
"brick (%s:%s) of device %s failed",
brickinfo->hostname, brickinfo->path, snap_device);
goto out;
}
out:
return ret;
}
int32_t
glusterd_lvm_snapshot_remove (dict_t *rsp_dict, glusterd_volinfo_t *snap_vol)
{
struct mntent save_entry = {0,};
int32_t brick_count = -1;
int32_t ret = -1;
int32_t err = 0;
glusterd_brickinfo_t *brickinfo = NULL;
xlator_t *this = NULL;
char buff[PATH_MAX] = "";
char brick_dir[PATH_MAX] = "";
char *tmp = NULL;
char *brick_mount_path = NULL;
gf_boolean_t is_brick_dir_present = _gf_false;
struct stat stbuf = {0,};
this = THIS;
GF_ASSERT (this);
|