/* Copyright (c) 2016 Red Hat, Inc. 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. */ #include "common-utils.h" #include "cli1-xdr.h" #include "xdr-generic.h" #include "glusterd.h" #include "glusterd-op-sm.h" #include "glusterd-store.h" #include "glusterd-geo-rep.h" #include "glusterd-utils.h" #include "glusterd-volgen.h" #include "run.h" #include "syscall.h" #include "byte-order.h" #include "glusterd-svc-helper.h" #include "compat-errno.h" #include "glusterd-tierd-svc.h" #include "glusterd-tierd-svc-helper.h" #include "glusterd-messages.h" #include "glusterd-mgmt.h" #include "glusterd-syncop.h" #include #include extern struct rpc_clnt_program gd_brick_prog; const char *gd_tier_op_list[GF_DEFRAG_CMD_TYPE_MAX] = { [GF_DEFRAG_CMD_START_TIER] = "start", [GF_DEFRAG_CMD_STOP_TIER] = "stop", }; int __glusterd_handle_tier (rpcsvc_request_t *req) { int32_t ret = -1; gf_cli_req cli_req = { {0,} }; dict_t *dict = NULL; glusterd_op_t cli_op = GD_OP_TIER_START_STOP; char *volname = NULL; int32_t cmd = 0; char msg[2048] = {0,}; xlator_t *this = NULL; glusterd_conf_t *conf = NULL; glusterd_volinfo_t *volinfo = NULL; char err_str[2048] = {0}; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, req, out); conf = this->private; GF_VALIDATE_OR_GOTO (this->name, conf, out); ret = xdr_to_generic (req->msg[0], &cli_req, (xdrproc_t)xdr_gf_cli_req); if (ret < 0) { req->rpc_err = GARBAGE_ARGS; goto out; } if (cli_req.dict.dict_len) { /* Unserialize the dictionary */ dict = dict_new (); ret = dict_unserialize (cli_req.dict.dict_val, cli_req.dict.dict_len, &dict); if (ret < 0) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_UNSERIALIZE_FAIL, "failed to " "unserialize req-buffer to dictionary"); snprintf (msg, sizeof (msg), "Unable to decode the " "command"); goto out; } else { dict->extra_stdfree = cli_req.dict.dict_val; } } ret = dict_get_str (dict, "volname", &volname); if (ret) { snprintf (msg, sizeof (msg), "Unable to get volume name"); gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get volume name, " "while handling tier command"); goto out; } ret = dict_get_int32 (dict, "rebalance-command", &cmd); if (ret) { snprintf (msg, sizeof (msg), "Unable to get the command"); gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get the cmd"); goto out; } if (conf->op_version < GD_OP_VERSION_3_7_0) { snprintf (msg, sizeof (msg), "Cannot execute command. The " "cluster is operating at version %d. Tier command " "%s is unavailable in this version", conf->op_version, gd_tier_op_list[cmd]); ret = -1; goto out; } if (conf->op_version < GD_OP_VERSION_3_10_0) { gf_msg_debug (this->name, 0, "The cluster is operating at " "version less than or equal to %d. Falling back " "to syncop framework.", GD_OP_VERSION_3_7_5); switch (cmd) { case GF_DEFRAG_CMD_DETACH_STOP: ret = dict_set_int32 (dict, "rebalance-command", GF_DEFRAG_CMD_STOP_DETACH_TIER); break; case GF_DEFRAG_CMD_DETACH_COMMIT: ret = glusterd_volinfo_find (volname, &volinfo); if (ret) { snprintf (err_str, sizeof (err_str), "Volume " "%s does not exist", volname); gf_msg (this->name, GF_LOG_ERROR, EINVAL, GD_MSG_VOL_NOT_FOUND, "%s", err_str); goto out; } ret = glusterd_set_detach_bricks (dict, volinfo); ret = dict_set_int32 (dict, "command", GF_OP_CMD_DETACH_COMMIT); break; case GF_DEFRAG_CMD_DETACH_COMMIT_FORCE: ret = glusterd_volinfo_find (volname, &volinfo); if (ret) { snprintf (err_str, sizeof (err_str), "Volume " "%s does not exist", volname); gf_msg (this->name, GF_LOG_ERROR, EINVAL, GD_MSG_VOL_NOT_FOUND, "%s", err_str); goto out; } ret = glusterd_set_detach_bricks (dict, volinfo); ret = dict_set_int32 (dict, "command", GF_OP_CMD_DETACH_COMMIT_FORCE); break; case GF_DEFRAG_CMD_DETACH_START: ret = glusterd_volinfo_find (volname, &volinfo); if (ret) { snprintf (err_str, sizeof (err_str), "Volume " "%s does not exist", volname); gf_msg (this->name, GF_LOG_ERROR, EINVAL, GD_MSG_VOL_NOT_FOUND, "%s", err_str); goto out; } ret = glusterd_set_detach_bricks (dict, volinfo); ret = dict_set_int32 (dict, "command", GF_OP_CMD_DETACH_START); break; default: break; } if (ret) { gf_log (this->name, GF_LOG_ERROR, "Failed to set dict"); goto out; } if ((cmd == GF_DEFRAG_CMD_STATUS_TIER) || (cmd == GF_DEFRAG_CMD_DETACH_STATUS) || (cmd == GF_DEFRAG_CMD_START_TIER) || (cmd == GF_DEFRAG_CMD_DETACH_STOP)) { ret = glusterd_op_begin (req, GD_OP_DEFRAG_BRICK_VOLUME, dict, msg, sizeof (msg)); } else ret = glusterd_op_begin (req, GD_OP_REMOVE_BRICK, dict, msg, sizeof (msg)); glusterd_friend_sm (); glusterd_op_sm (); } else { switch (cmd) { case GF_DEFRAG_CMD_STATUS_TIER: cli_op = GD_OP_TIER_STATUS; break; case GF_DEFRAG_CMD_DETACH_STATUS: cli_op = GD_OP_DETACH_TIER_STATUS; break; case GF_DEFRAG_CMD_DETACH_STOP: cli_op = GD_OP_REMOVE_TIER_BRICK; break; case GF_DEFRAG_CMD_DETACH_COMMIT: case GF_DEFRAG_CMD_DETACH_COMMIT_FORCE: case GF_DEFRAG_CMD_DETACH_START: cli_op = GD_OP_REMOVE_TIER_BRICK; ret = glusterd_volinfo_find (volname, &volinfo); if (ret) { snprintf (err_str, sizeof (err_str), "Volume " "%s does not exist", volname); gf_msg (this->name, GF_LOG_ERROR, EINVAL, GD_MSG_VOL_NOT_FOUND, "%s", err_str); goto out; } ret = glusterd_set_detach_bricks (dict, volinfo); break; default: break; } if (ret < 0) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED, "dict set failed"); goto out; } ret = glusterd_mgmt_v3_initiate_all_phases (req, cli_op, dict); } out: if (ret) { if (msg[0] == '\0') snprintf (msg, sizeof (msg), "Tier operation failed"); ret = glusterd_op_send_cli_response (cli_op, ret, 0, req, dict, msg); } return ret; } int glusterd_handle_tier (rpcsvc_request_t *req) { return glusterd_big_locked_handler (req, __glusterd_handle_tier); } static int glusterd_manage_tier (glusterd_volinfo_t *volinfo, int opcode) { int ret = -1; xlator_t *this = NULL; glusterd_conf_t *priv = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, volinfo, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); switch (opcode) { case GF_DEFRAG_CMD_START_TIER: case GF_DEFRAG_CMD_STOP_TIER: ret = volinfo->tierd.svc.manager (&(volinfo->tierd.svc), volinfo, PROC_START_NO_WAIT); break; default: ret = 0; break; } out: return ret; } static int glusterd_tier_enable (glusterd_volinfo_t *volinfo, char **op_errstr) { int32_t ret = -1; xlator_t *this = NULL; int32_t tier_online = -1; char pidfile[PATH_MAX] = {0}; int32_t pid = -1; glusterd_conf_t *priv = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, volinfo, out); GF_VALIDATE_OR_GOTO (this->name, op_errstr, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); if (glusterd_is_volume_started (volinfo) == 0) { *op_errstr = gf_strdup ("Volume is stopped, start volume " "to enable tier."); ret = -1; goto out; } GLUSTERD_GET_TIER_PID_FILE(pidfile, volinfo, priv); tier_online = gf_is_service_running (pidfile, &pid); if (tier_online) { *op_errstr = gf_strdup ("tier is already enabled"); ret = -1; goto out; } volinfo->is_tier_enabled = _gf_true; ret = 0; out: if (ret && op_errstr && !*op_errstr) gf_asprintf (op_errstr, "Enabling tier on volume %s has been " "unsuccessful", volinfo->volname); return ret; } static int glusterd_tier_disable (glusterd_volinfo_t *volinfo, char **op_errstr) { int32_t ret = -1; xlator_t *this = NULL; int32_t tier_online = -1; char pidfile[PATH_MAX] = {0}; int32_t pid = -1; glusterd_conf_t *priv = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, volinfo, out); GF_VALIDATE_OR_GOTO (this->name, op_errstr, out); priv = this->private; GLUSTERD_GET_TIER_PID_FILE(pidfile, volinfo, priv); tier_online = gf_is_service_running (pidfile, &pid); if (!tier_online) { *op_errstr = gf_strdup ("tier is already disabled"); ret = -1; goto out; } volinfo->is_tier_enabled = _gf_false; ret = 0; out: if (ret && op_errstr && !*op_errstr) gf_asprintf (op_errstr, "Disabling tier volume %s has " "been unsuccessful", volinfo->volname); return ret; } int glusterd_op_remove_tier_brick (dict_t *dict, char **op_errstr, dict_t *rsp_dict) { glusterd_conf_t *priv = NULL; xlator_t *this = NULL; int ret = -1; char *volname = NULL; glusterd_volinfo_t *volinfo = NULL; char *brick = NULL; int32_t count = 0; int32_t i = 1; char key[256] = {0,}; int32_t flag = 0; char err_str[4096] = {0,}; int need_rebalance = 0; int force = 0; int32_t cmd = 0; int32_t replica_count = 0; glusterd_brickinfo_t *brickinfo = NULL; glusterd_brickinfo_t *tmp = NULL; char *task_id_str = NULL; dict_t *bricks_dict = NULL; char *brick_tmpstr = NULL; uint32_t commit_hash = 0; int detach_commit = 0; void *tier_info = NULL; char *cold_shd_key = NULL; char *hot_shd_key = NULL; int delete_key = 1; glusterd_svc_t *svc = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, dict, out); GF_VALIDATE_OR_GOTO (this->name, op_errstr, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); ret = dict_get_str (dict, "volname", &volname); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get volume name"); goto out; } ret = glusterd_volinfo_find (volname, &volinfo); if (ret) { gf_msg (this->name, GF_LOG_ERROR, EINVAL, GD_MSG_VOL_NOT_FOUND, "Unable to get volinfo"); goto out; } ret = dict_get_int32 (dict, "rebalance-command", &cmd); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED, "cmd not found"); goto out; } if (is_origin_glusterd (dict) && (cmd != GF_DEFRAG_CMD_DETACH_START)) { if (!gf_uuid_is_null (volinfo->rebal.rebalance_id)) { ret = glusterd_copy_uuid_to_dict (volinfo->rebal.rebalance_id, dict, GF_REMOVE_BRICK_TID_KEY); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_REMOVE_BRICK_ID_SET_FAIL, "Failed to set remove-brick-id"); goto out; } } } /*check only if a tierd is supposed to be running * if no brick in the tierd volume is a local brick * skip it */ cds_list_for_each_entry (brickinfo, &volinfo->bricks, brick_list) { if (glusterd_is_local_brick (this, volinfo, brickinfo)) { flag = _gf_true; break; } } if (!flag) goto out; ret = -1; switch (cmd) { case GF_DEFRAG_CMD_DETACH_STOP: /* Fall back to the old volume file */ cds_list_for_each_entry_safe (brickinfo, tmp, &volinfo->bricks, brick_list) { if (!brickinfo->decommissioned) continue; brickinfo->decommissioned = 0; } ret = glusterd_create_volfiles_and_notify_services (volinfo); if (ret) { gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_VOLFILE_CREATE_FAIL, "failed to create volfiles"); goto out; } ret = glusterd_store_volinfo (volinfo, GLUSTERD_VOLINFO_VER_AC_INCREMENT); if (ret) { gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_VOLINFO_SET_FAIL, "failed to store volinfo"); goto out; } ret = glusterd_tierdsvc_restart (); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_TIERD_START_FAIL, "Couldn't restart tierd for " "vol: %s", volinfo->volname); goto out; } volinfo->tier.op = GD_OP_DETACH_NOT_STARTED; ret = 0; goto out; case GF_DEFRAG_CMD_DETACH_START: ret = dict_get_str (dict, GF_REMOVE_BRICK_TID_KEY, &task_id_str); if (ret) { gf_msg_debug (this->name, errno, "Missing remove-brick-id"); ret = 0; } else { ret = dict_set_str (rsp_dict, GF_REMOVE_BRICK_TID_KEY, task_id_str); gf_uuid_parse (task_id_str, volinfo->tier.rebalance_id); } force = 0; volinfo->tier.op = GD_OP_DETACH_TIER; volinfo->tier.defrag_status = GF_DEFRAG_STATUS_STARTED; break; case GF_DEFRAG_CMD_DETACH_COMMIT: if (volinfo->decommission_in_progress) { gf_asprintf (op_errstr, "use 'force' option as " "migration is in progress"); goto out; } if (volinfo->rebal.defrag_status == GF_DEFRAG_STATUS_FAILED) { gf_asprintf (op_errstr, "use 'force' option as " "migration has failed"); goto out; } case GF_DEFRAG_CMD_DETACH_COMMIT_FORCE: glusterd_op_perform_detach_tier (volinfo); detach_commit = 1; /* Disabling ctr when detaching a tier, since * currently tier is the only consumer of ctr. * Revisit this code when this constraint no * longer exist. */ dict_del (volinfo->dict, "features.ctr-enabled"); dict_del (volinfo->dict, "cluster.tier-mode"); hot_shd_key = gd_get_shd_key (volinfo->tier_info.hot_type); cold_shd_key = gd_get_shd_key (volinfo->tier_info.cold_type); if (hot_shd_key) { /* * Since post detach, shd graph will not * contain hot tier. So we need to clear * option set for hot tier. For a tiered * volume there can be different key * for both hot and cold. If hot tier is * shd compatible then we need to remove * the configured value when detaching a tier, * only if the key's are different or * cold key is NULL. So we will set * delete_key first, and if cold key is not * null and they are equal then we will clear * the flag. Otherwise we will delete the * key. */ if (cold_shd_key) delete_key = strcmp (hot_shd_key, cold_shd_key); if (delete_key) dict_del (volinfo->dict, hot_shd_key); } /* fall through */ if (volinfo->decommission_in_progress) { if (volinfo->tier.defrag) { LOCK (&volinfo->rebal.defrag->lock); /* Fake 'rebalance-complete' so the * graph change * happens right away */ volinfo->tier.defrag_status = GF_DEFRAG_STATUS_COMPLETE; UNLOCK (&volinfo->tier.defrag->lock); } } volinfo->tier.op = GD_OP_DETACH_NOT_STARTED; ret = 0; force = 1; break; default: gf_asprintf (op_errstr, "tier command failed. Invalid " "opcode"); ret = -1; goto out; } count = glusterd_set_detach_bricks(dict, volinfo); if (cmd == GF_DEFRAG_CMD_DETACH_START) { bricks_dict = dict_new (); if (!bricks_dict) { ret = -1; goto out; } ret = dict_set_int32 (bricks_dict, "count", count); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_SET_FAILED, "Failed to save remove-brick count"); goto out; } } while (i <= count) { snprintf (key, 256, "brick%d", i); ret = dict_get_str (dict, key, &brick); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get %s", key); goto out; } if (cmd == GF_DEFRAG_CMD_DETACH_START) { brick_tmpstr = gf_strdup (brick); if (!brick_tmpstr) { ret = -1; gf_msg (this->name, GF_LOG_ERROR, ENOMEM, GD_MSG_NO_MEMORY, "Failed to duplicate brick name"); goto out; } ret = dict_set_dynstr (bricks_dict, key, brick_tmpstr); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_SET_FAILED, "Failed to add brick to dict"); goto out; } brick_tmpstr = NULL; } ret = glusterd_op_perform_remove_brick (volinfo, brick, force, &need_rebalance); if (ret) goto out; i++; } if (detach_commit) { /* Clear related information from volinfo */ tier_info = ((void *)(&volinfo->tier_info)); memset (tier_info, 0, sizeof (volinfo->tier_info)); } if (cmd == GF_DEFRAG_CMD_DETACH_START) volinfo->tier.dict = dict_ref (bricks_dict); ret = dict_get_int32 (dict, "replica-count", &replica_count); if (!ret) { gf_msg (this->name, GF_LOG_INFO, errno, GD_MSG_DICT_GET_FAILED, "changing replica count %d to %d on volume %s", volinfo->replica_count, replica_count, volinfo->volname); volinfo->replica_count = replica_count; volinfo->sub_count = replica_count; volinfo->dist_leaf_count = glusterd_get_dist_leaf_count (volinfo); /* * volinfo->type and sub_count have already been set for * volumes undergoing a detach operation, they should not * be modified here. */ if ((replica_count == 1) && (cmd != GF_DEFRAG_CMD_DETACH_COMMIT) && (cmd != GF_DEFRAG_CMD_DETACH_COMMIT_FORCE)) { if (volinfo->type == GF_CLUSTER_TYPE_REPLICATE) { volinfo->type = GF_CLUSTER_TYPE_NONE; /* backward compatibility */ volinfo->sub_count = 0; } else { volinfo->type = GF_CLUSTER_TYPE_STRIPE; /* backward compatibility */ volinfo->sub_count = volinfo->dist_leaf_count; } } } volinfo->subvol_count = (volinfo->brick_count / volinfo->dist_leaf_count); ret = glusterd_create_volfiles_and_notify_services (volinfo); if (ret) { gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_VOLFILE_CREATE_FAIL, "failed to create" "volfiles"); goto out; } ret = glusterd_store_volinfo (volinfo, GLUSTERD_VOLINFO_VER_AC_INCREMENT); if (ret) { gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_VOLINFO_STORE_FAIL, "failed to store volinfo"); goto out; } if (cmd == GF_DEFRAG_CMD_DETACH_START && volinfo->status == GLUSTERD_STATUS_STARTED) { svc = &(volinfo->tierd.svc); ret = svc->reconfigure (volinfo); if (ret) goto out; ret = glusterd_svcs_reconfigure (); if (ret) { gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_NFS_RECONF_FAIL, "Unable to reconfigure NFS-Server"); goto out; } } /* Need to reset the defrag/rebalance status accordingly */ switch (volinfo->tier.defrag_status) { case GF_DEFRAG_STATUS_FAILED: case GF_DEFRAG_STATUS_COMPLETE: volinfo->tier.defrag_status = 0; /* FALLTHROUGH */ default: break; } if (!force && need_rebalance) { if (dict_get_uint32(dict, "commit-hash", &commit_hash) == 0) { volinfo->tier.commit_hash = commit_hash; } /* perform the rebalance operations */ ret = glusterd_handle_defrag_start (volinfo, err_str, sizeof (err_str), GF_DEFRAG_CMD_START_DETACH_TIER, /*change this label to GF_DEFRAG_CMD_DETACH_START * while removing old code */ glusterd_remove_brick_migrate_cbk, GD_OP_REMOVE_BRICK); if (!ret) volinfo->decommission_in_progress = 1; else if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_REBALANCE_START_FAIL, "failed to start the rebalance"); } } else { if (GLUSTERD_STATUS_STARTED == volinfo->status) ret = glusterd_svcs_manager (volinfo); } out: if (ret && err_str[0] && op_errstr) *op_errstr = gf_strdup (err_str); GF_FREE (brick_tmpstr); if (bricks_dict) dict_unref (bricks_dict); return ret; } int glusterd_op_tier_start_stop (dict_t *dict, char **op_errstr, dict_t *rsp_dict) { glusterd_volinfo_t *volinfo = NULL; int32_t ret = -1; char *volname = NULL; int cmd = -1; xlator_t *this = NULL; glusterd_brickinfo_t *brick = NULL; gf_boolean_t retval = _gf_false; glusterd_conf_t *priv = NULL; int32_t pid = -1; char pidfile[PATH_MAX] = {0}; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, dict, out); GF_VALIDATE_OR_GOTO (this->name, op_errstr, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); ret = dict_get_str (dict, "volname", &volname); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get volume name"); goto out; } ret = glusterd_volinfo_find (volname, &volinfo); if (ret) { gf_asprintf (op_errstr, FMTSTR_CHECK_VOL_EXISTS, volname); goto out; } ret = dict_get_int32 (dict, "rebalance-command", &cmd); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get cmd from " "dict"); goto out; } cds_list_for_each_entry (brick, &volinfo->bricks, brick_list) { if (gf_uuid_compare (MY_UUID, brick->uuid) == 0) { retval = _gf_true; break; } } /*check if this node needs tierd*/ if (!retval) goto out; switch (cmd) { case GF_DEFRAG_CMD_START_TIER: GLUSTERD_GET_TIER_PID_FILE(pidfile, volinfo, priv); /* we check if its running and skip so that we dont get a * failure during force start */ if (gf_is_service_running (pidfile, &pid)) goto out; ret = glusterd_tier_enable (volinfo, op_errstr); if (ret < 0) goto out; glusterd_store_perform_node_state_store (volinfo); break; case GF_DEFRAG_CMD_STOP_TIER: ret = glusterd_tier_disable (volinfo, op_errstr); if (ret < 0) goto out; break; default: gf_asprintf (op_errstr, "tier command failed. Invalid " "opcode"); ret = -1; goto out; } ret = glusterd_manage_tier (volinfo, cmd); if (ret) goto out; ret = glusterd_store_volinfo (volinfo, GLUSTERD_VOLINFO_VER_AC_INCREMENT); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_VOLINFO_STORE_FAIL, "Failed to store volinfo for tier"); goto out; } out: return ret; } int glusterd_op_stage_tier (dict_t *dict, char **op_errstr, dict_t *rsp_dict) { char *volname = NULL; int ret = -1; int32_t cmd = 0; char msg[2048] = {0}; glusterd_volinfo_t *volinfo = NULL; char *task_id_str = NULL; xlator_t *this = 0; int32_t is_force = 0; char pidfile[PATH_MAX] = {0}; int32_t tier_online = -1; int32_t pid = -1; int32_t brick_count = 0; gsync_status_param_t param = {0,}; glusterd_conf_t *priv = NULL; gf_boolean_t flag = _gf_false; glusterd_brickinfo_t *brickinfo = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, dict, out); GF_VALIDATE_OR_GOTO (this->name, op_errstr, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); ret = dict_get_str (dict, "volname", &volname); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED, "volname not found"); goto out; } ret = dict_get_int32 (dict, "rebalance-command", &cmd); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_DICT_GET_FAILED, "cmd not found"); goto out; } ret = glusterd_rebalance_cmd_validate (cmd, volname, &volinfo, msg, sizeof (msg)); if (ret) { gf_msg_debug (this->name, 0, "cmd validate failed"); goto out; } if (volinfo->type != GF_CLUSTER_TYPE_TIER) { snprintf (msg, sizeof(msg), "volume %s is not a tier " "volume", volinfo->volname); gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_VOL_NOT_TIER, "volume: %s is not a tier " "volume", volinfo->volname); ret = -1; goto out; } /* Check if the connected clients are all of version * glusterfs-3.6 and higher. This is needed to prevent some data * loss issues that could occur when older clients are connected * when rebalance is run. This check can be bypassed by using * 'force' */ ret = glusterd_check_client_op_version_support (volname, GD_OP_VERSION_3_6_0, NULL); if (ret) { ret = gf_asprintf (op_errstr, "Volume %s has one or " "more connected clients of a version" " lower than GlusterFS-v3.6.0. " "Tier operations not supported in" " below this version", volname); goto out; } /*check only if a tierd is supposed to be running * if no brick in the tierd volume is a local brick * skip it */ cds_list_for_each_entry (brickinfo, &volinfo->bricks, brick_list) { if (glusterd_is_local_brick (this, volinfo, brickinfo)) { flag = _gf_true; break; } } if (!flag) goto out; GLUSTERD_GET_TIER_PID_FILE(pidfile, volinfo, priv); tier_online = gf_is_service_running (pidfile, &pid); switch (cmd) { case GF_DEFRAG_CMD_START_TIER: ret = dict_get_int32 (dict, "force", &is_force); if (ret) is_force = 0; if (brickinfo->status != GF_BRICK_STARTED) { gf_asprintf (op_errstr, "Received" " tier start on volume " "with stopped brick %s", brickinfo->path); ret = -1; goto out; } if ((!is_force) && tier_online) { ret = gf_asprintf (op_errstr, "Tier daemon is " "already running on volume %s", volname); ret = -1; goto out; } ret = glusterd_defrag_start_validate (volinfo, msg, sizeof (msg), GD_OP_REBALANCE); if (ret) { gf_msg (this->name, 0, GF_LOG_ERROR, GD_MSG_REBALANCE_START_FAIL, "start validate failed"); goto out; } break; case GF_DEFRAG_CMD_STOP_TIER: if (!tier_online) { ret = gf_asprintf (op_errstr, "Tier daemon is " "not running on volume %s", volname); ret = -1; goto out; } break; case GF_DEFRAG_CMD_DETACH_START: ret = dict_get_int32 (dict, "count", &brick_count); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get brick count"); goto out; } if (!tier_online) { ret = gf_asprintf (op_errstr, "Tier daemon is " "not running on volume %s", volname); ret = -1; goto out; } if (volinfo->tier.op == GD_OP_DETACH_TIER) { snprintf (msg, sizeof (msg), "An earlier detach tier " "task exists for volume %s. Either commit it" " or stop it before starting a new task.", volinfo->volname); gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_OLD_REMOVE_BRICK_EXISTS, "Earlier remove-brick" " task exists for volume %s.", volinfo->volname); ret = -1; goto out; } if (glusterd_is_defrag_on(volinfo)) { snprintf (msg, sizeof (msg), "Migration is in progress." " Please retry after completion"); gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_OIP_RETRY_LATER, "Migration is" "in progress"); goto out; } ret = glusterd_remove_brick_validate_bricks (GF_OP_CMD_NONE, brick_count, dict, volinfo, op_errstr, cmd); if (ret) goto out; if (is_origin_glusterd (dict)) { ret = glusterd_generate_and_set_task_id (dict, GF_REMOVE_BRICK_TID_KEY); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_TASKID_GEN_FAIL, "Failed to generate task-id"); goto out; } } else { ret = dict_get_str (dict, GF_REMOVE_BRICK_TID_KEY, &task_id_str); if (ret) { gf_msg (this->name, GF_LOG_WARNING, errno, GD_MSG_DICT_GET_FAILED, "Missing remove-brick-id"); ret = 0; } } break; case GF_DEFRAG_CMD_DETACH_STOP: if (volinfo->tier.op != GD_OP_DETACH_TIER) { snprintf (msg, sizeof(msg), "Detach-tier " "not started"); ret = -1; goto out; } ret = 0; break; case GF_DEFRAG_CMD_STATUS_TIER: if (!tier_online) { ret = gf_asprintf (op_errstr, "Tier daemon is " "not running on volume %s", volname); ret = -1; goto out; } break; case GF_DEFRAG_CMD_DETACH_COMMIT: if (volinfo->tier.op != GD_OP_DETACH_TIER) { snprintf (msg, sizeof(msg), "Detach-tier " "not started"); ret = -1; goto out; } if ((volinfo->rebal.defrag_status == GF_DEFRAG_STATUS_STARTED) && (volinfo->tier.op == GD_OP_DETACH_TIER)) { ret = -1; snprintf (msg, sizeof (msg), "Detach is in progress. " "Please retry after completion"); gf_msg (this->name, GF_LOG_WARNING, 0, GD_MSG_OIP_RETRY_LATER, "Detach is in " "progress"); goto out; } ret = dict_get_int32 (dict, "count", &brick_count); if (ret) { gf_msg (this->name, GF_LOG_ERROR, errno, GD_MSG_DICT_GET_FAILED, "Unable to get brick count"); goto out; } ret = glusterd_remove_brick_validate_bricks (GF_OP_CMD_NONE, brick_count, dict, volinfo, op_errstr, cmd); if (ret) goto out; /* If geo-rep is configured, for this volume, it should be * stopped. */ param.volinfo = volinfo; ret = glusterd_check_geo_rep_running (¶m, op_errstr); if (ret || param.is_active) { ret = -1; goto out; } break; case GF_DEFRAG_CMD_DETACH_STATUS: if (volinfo->tier.op != GD_OP_DETACH_TIER) { snprintf (msg, sizeof(msg), "Detach-tier " "not started"); ret = -1; goto out; } break; case GF_DEFRAG_CMD_DETACH_COMMIT_FORCE: default: break; } ret = 0; out: if (ret && op_errstr && msg[0]) *op_errstr = gf_strdup (msg); return ret; } int32_t glusterd_add_tierd_to_dict (glusterd_volinfo_t *volinfo, dict_t *dict, int32_t count) { int ret = -1; int32_t pid = -1; int32_t brick_online = -1; char key[1024] = {0}; char base_key[1024] = {0}; char pidfile[PATH_MAX] = {0}; xlator_t *this = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, volinfo, out); GF_VALIDATE_OR_GOTO (this->name, dict, out); snprintf (base_key, sizeof (base_key), "brick%d", count); snprintf (key, sizeof (key), "%s.hostname", base_key); ret = dict_set_str (dict, key, "Tier Daemon"); if (ret) goto out; snprintf (key, sizeof (key), "%s.path", base_key); ret = dict_set_dynstr (dict, key, gf_strdup (uuid_utoa (MY_UUID))); if (ret) goto out; /* tier doesn't have a port. but the cli needs a port key with * an zero value to parse. * */ memset (key, 0, sizeof (key)); snprintf (key, sizeof (key), "%s.port", base_key); ret = dict_set_int32 (dict, key, 0); if (ret) goto out; glusterd_svc_build_tierd_pidfile (volinfo, pidfile, sizeof (pidfile)); brick_online = gf_is_service_running (pidfile, &pid); memset (key, 0, sizeof (key)); snprintf (key, sizeof (key), "%s.pid", base_key); ret = dict_set_int32 (dict, key, pid); if (ret) goto out; memset (key, 0, sizeof (key)); snprintf (key, sizeof (key), "%s.status", base_key); ret = dict_set_int32 (dict, key, brick_online); out: if (ret) gf_msg (this ? this->name : "glusterd", GF_LOG_ERROR, 0, GD_MSG_DICT_SET_FAILED, "Returning %d. adding values to dict failed", ret); return ret; } int32_t __glusterd_tier_status_cbk (struct rpc_req *req, struct iovec *iov, int count, void *myframe) { gd1_mgmt_brick_op_rsp rsp = {0}; int ret = -1; call_frame_t *frame = NULL; xlator_t *this = NULL; glusterd_conf_t *priv = NULL; struct syncargs *args = NULL; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, req, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); frame = myframe; args = frame->local; if (-1 == req->rpc_status) { args->op_errno = ENOTCONN; goto out; } ret = xdr_to_generic (*iov, &rsp, (xdrproc_t)xdr_gd1_mgmt_brick_op_rsp); if (ret < 0) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_RES_DECODE_FAIL, "Failed to decode brick op " "response received"); goto out; } if (rsp.output.output_len) { args->dict = dict_new (); if (!args->dict) { ret = -1; args->op_errno = ENOMEM; goto out; } ret = dict_unserialize (rsp.output.output_val, rsp.output.output_len, &args->dict); if (ret < 0) goto out; } args->op_ret = rsp.op_ret; args->op_errno = rsp.op_errno; args->errstr = gf_strdup (rsp.op_errstr); out: if ((rsp.op_errstr) && (strcmp (rsp.op_errstr, "") != 0)) free (rsp.op_errstr); free (rsp.output.output_val); if (req->rpc_status != -1) GLUSTERD_STACK_DESTROY(frame); __wake (args); return ret; } int32_t glusterd_tier_status_cbk (struct rpc_req *req, struct iovec *iov, int count, void *myframe) { return glusterd_big_locked_cbk (req, iov, count, myframe, __glusterd_tier_status_cbk); } int glusterd_op_tier_status (dict_t *dict, char **op_errstr, dict_t *rsp_dict, glusterd_op_t op) { int ret = -1; xlator_t *this = NULL; struct syncargs args = {0, }; glusterd_req_ctx_t *data = NULL; gd1_mgmt_brick_op_req *req = NULL; glusterd_conf_t *priv = NULL; int pending_bricks = 0; glusterd_pending_node_t *pending_node; glusterd_req_ctx_t *req_ctx = NULL; struct rpc_clnt *rpc = NULL; uuid_t *txn_id = NULL; extern glusterd_op_info_t opinfo; this = THIS; GF_VALIDATE_OR_GOTO (THIS->name, this, out); GF_VALIDATE_OR_GOTO (this->name, dict, out); GF_VALIDATE_OR_GOTO (this->name, rsp_dict, out); priv = this->private; GF_VALIDATE_OR_GOTO (this->name, priv, out); args.op_ret = -1; args.op_errno = ENOTCONN; data = GF_CALLOC (1, sizeof (*data), gf_gld_mt_op_allack_ctx_t); gf_uuid_copy (data->uuid, MY_UUID); /* we are printing the detach status for issue of detach start * by then we need the op to be GD_OP_DETACH_TIER_STATUS for it to * get the status. ad for the rest of the condition it can go as such. */ if (op == GD_OP_REMOVE_TIER_BRICK) data->op = GD_OP_DETACH_TIER_STATUS; else data->op = op; data->dict = dict; txn_id = &priv->global_txn_id; req_ctx = data; GF_VALIDATE_OR_GOTO (this->name, req_ctx, out); CDS_INIT_LIST_HEAD (&opinfo.pending_bricks); ret = dict_get_bin (req_ctx->dict, "transaction_id", (void **)&txn_id); gf_msg_debug (this->name, 0, "transaction ID = %s", uuid_utoa (*txn_id)); ret = glusterd_op_bricks_select (req_ctx->op, req_ctx->dict, op_errstr, &opinfo.pending_bricks, NULL); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_BRICK_SELECT_FAIL, "Failed to select bricks"); opinfo.op_errstr = *op_errstr; goto out; } cds_list_for_each_entry (pending_node, &opinfo.pending_bricks, list) { ret = glusterd_brick_op_build_payload (req_ctx->op, pending_node->node, (gd1_mgmt_brick_op_req **)&req, req_ctx->dict); if (ret) { gf_msg (this->name, GF_LOG_ERROR, 0, GD_MSG_BRICK_OP_PAYLOAD_BUILD_FAIL, "Failed to build brick op payload during " "'Volume %s'", gd_op_list[req_ctx->op]); goto out; } rpc = glusterd_pending_node_get_rpc (pending_node); if (!rpc) { opinfo.brick_pending_count = 0; ret = 0; if (req) { GF_FREE (req); req = NULL; } glusterd_defrag_volume_node_rsp (req_ctx->dict, NULL, rsp_dict); goto out; } GD_SYNCOP (rpc, (&args), NULL, glusterd_tier_status_cbk, req, &gd_brick_prog, req->op, xdr_gd1_mgmt_brick_op_req); if (req) { GF_FREE (req); req = NULL; } if (!ret) pending_bricks++; glusterd_pending_node_put_rpc (pending_node); } glusterd_handle_node_rsp (req_ctx->dict, pending_node->node, req_ctx->op, args.dict, rsp_dict, op_errstr, pending_node->type); gf_msg_trace (this->name, 0, "Sent commit op req for operation " "'Volume %s' to %d bricks", gd_op_list[req_ctx->op], pending_bricks); opinfo.brick_pending_count = pending_bricks; out: if (ret) opinfo.op_ret = ret; ret = glusterd_set_txn_opinfo (txn_id, &opinfo); if (ret) gf_msg (THIS->name, GF_LOG_ERROR, 0, GD_MSG_TRANS_OPINFO_SET_FAIL, "Unable to set transaction's opinfo"); gf_msg_debug (this ? this->name : "glusterd", 0, "Returning %d. Failed to get tier status", ret); return ret; }