1
0
mirror of https://github.com/gluster/glusterd2.git synced 2026-02-05 21:45:43 +01:00
Files
glusterd2/plugins/glustershd/rest.go
Vishal Pandey c7be1da250 Changes in format to run glfsheal to get heal info.
The change is basically in the way we pass xml flag to glfsheal binary.
Previously it was passed with a simple 'xml', now after the change
https://review.gluster.org/#/c/glusterfs/+/21501/
its passed as '--xml' to glfsheal.

Signed-off-by: Vishal Pandey <vpandey@redhat.com>
2018-11-28 13:42:43 +05:30

363 lines
9.9 KiB
Go

package glustershd
import (
"bytes"
"encoding/xml"
"fmt"
"net/http"
"os/exec"
"path"
"strconv"
"strings"
"github.com/gluster/glusterd2/glusterd2/gdctx"
"github.com/gluster/glusterd2/glusterd2/peer"
restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils"
"github.com/gluster/glusterd2/glusterd2/transaction"
"github.com/gluster/glusterd2/glusterd2/volume"
gderrors "github.com/gluster/glusterd2/pkg/errors"
glustershdapi "github.com/gluster/glusterd2/plugins/glustershd/api"
"github.com/gorilla/mux"
"github.com/pborman/uuid"
config "github.com/spf13/viper"
)
type healTypes int8
const (
indexHeal healTypes = 1 + iota
fullHeal
)
func runGlfshealBin(volname string, args []string) (string, error) {
var out bytes.Buffer
var buffer bytes.Buffer
var healInfoOutput string
buffer.WriteString(fmt.Sprintf("%s", volname))
for _, arg := range args {
buffer.WriteString(fmt.Sprintf(" %s", arg))
}
args = strings.Fields(buffer.String())
path, err := exec.LookPath("glfsheal")
if err != nil {
return healInfoOutput, err
}
cmd := exec.Command(path, args...)
cmd.Stdout = &out
if err = cmd.Run(); err != nil {
return healInfoOutput, err
}
healInfoOutput = out.String()
return healInfoOutput, nil
}
func getHealInfo(volname string, option string) (string, error) {
var options []string
glusterdSockpath := path.Join(config.GetString("rundir"), "glusterd2.socket")
options = append(options, option, "--xml", "glusterd-sock", glusterdSockpath)
return runGlfshealBin(volname, options)
}
func convertToInt(value string) (int64, error) {
if value == "-" {
return -1, nil
}
intVal, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return -1, err
}
return intVal, nil
}
func filterHealInfo(healInfo glustershdapi.HealInfo) (glustershdapi.HealInfo, error) {
var err error
for i := range healInfo.Bricks {
if healInfo.Bricks[i].TotalEntriesRaw != "" {
healInfo.Bricks[i].TotalEntries = new(int64)
*healInfo.Bricks[i].TotalEntries, err = convertToInt(healInfo.Bricks[i].TotalEntriesRaw)
if err != nil {
return healInfo, err
}
}
if healInfo.Bricks[i].EntriesInHealPendingRaw != "" {
healInfo.Bricks[i].EntriesInHealPending = new(int64)
*healInfo.Bricks[i].EntriesInHealPending, err = convertToInt(healInfo.Bricks[i].EntriesInHealPendingRaw)
if err != nil {
return healInfo, err
}
}
if healInfo.Bricks[i].EntriesInSplitBrainRaw != "" {
healInfo.Bricks[i].EntriesInSplitBrain = new(int64)
*healInfo.Bricks[i].EntriesInSplitBrain, err = convertToInt(healInfo.Bricks[i].EntriesInSplitBrainRaw)
if err != nil {
return healInfo, err
}
}
if healInfo.Bricks[i].EntriesPossiblyHealingRaw != "" {
healInfo.Bricks[i].EntriesPossiblyHealing = new(int64)
*healInfo.Bricks[i].EntriesPossiblyHealing, err = convertToInt(healInfo.Bricks[i].EntriesPossiblyHealingRaw)
if err != nil {
return healInfo, err
}
}
if healInfo.Bricks[i].EntriesRaw != "" {
healInfo.Bricks[i].Entries = new(int64)
*healInfo.Bricks[i].Entries, err = convertToInt(healInfo.Bricks[i].EntriesRaw)
if err != nil {
return healInfo, err
}
}
}
return healInfo, nil
}
func selfhealInfoHandler(w http.ResponseWriter, r *http.Request) {
var option string
p := mux.Vars(r)
volname := p["volname"]
if val, ok := p["opts"]; ok {
option = val
}
ctx := r.Context()
logger := gdctx.GetReqLogger(ctx)
txn, err := transaction.NewTxnWithLocks(ctx, volname)
if err != nil {
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
return
}
defer txn.Done()
// Validate volume existence
volinfo, err := volume.GetVolume(volname)
if err != nil {
if err == gderrors.ErrVolNotFound {
logger.WithError(err).WithField(
"volname", volname).Debug("volume not found")
restutils.SendHTTPError(ctx, w, http.StatusNotFound, err)
} else {
logger.WithError(err).WithField(
"volname", volname).Debug("error occurred while looking for volume")
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
}
return
}
// Validate volume type
if !isVolReplicate(volinfo.Type) {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "invalid operation for this volume type")
return
}
// Validate volume state
if volinfo.State != volume.VolStarted {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrVolNotStarted)
return
}
healInfoOutput, err := getHealInfo(volname, option)
if err != nil {
logger.WithError(err).WithField("volname", volname).Error("heal info operation failed")
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, "heal info operation failed")
return
}
output := []byte(healInfoOutput)
var info glustershdapi.HealInfo
err = xml.Unmarshal(output, &info)
if err != nil {
logger.WithError(err).Error("Error unmarshalling XML output from heal info command")
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
return
}
info, err = filterHealInfo(info)
if err != nil {
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
return
}
restutils.SendHTTPResponse(ctx, w, http.StatusOK, &info.Bricks)
}
func selfHealHandler(w http.ResponseWriter, r *http.Request) {
// Collect inputs from URL
volname := mux.Vars(r)["volname"]
ctx := r.Context()
logger := gdctx.GetReqLogger(ctx)
healType := indexHeal
if heal, ok := r.URL.Query()["type"]; ok {
switch heal[0] {
case "index":
healType = indexHeal
case "full":
healType = fullHeal
default:
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "heal type can only be either index or full")
return
}
}
txn, err := transaction.NewTxnWithLocks(ctx, volname)
if err != nil {
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
return
}
defer txn.Done()
// Validate volume existence
volinfo, err := volume.GetVolume(volname)
if err != nil {
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
return
}
// Check if volume is started
if volinfo.State != volume.VolStarted {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrVolNotStarted)
return
}
// Check if self heal is already enabled
if !isHealEnabled(volinfo) {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "self heal option is disabled for this volume")
return
}
if err := txn.Ctx.Set("volinfo", volinfo); err != nil {
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
return
}
if err := txn.Ctx.Set("healType", healType); err != nil {
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
return
}
txn.Steps = []*transaction.Step{
{
DoFunc: "selfheal.Heal",
Nodes: volinfo.Nodes(),
},
}
if err = txn.Do(); err != nil {
logger.WithError(err).Error("failed to start healing process")
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
return
}
restutils.SendHTTPResponse(ctx, w, http.StatusOK, nil)
}
func splitBrainOperationHandler(w http.ResponseWriter, r *http.Request) {
// Collect inputs from URL
volname := mux.Vars(r)["volname"]
operation := mux.Vars(r)["operation"]
ctx := r.Context()
logger := gdctx.GetReqLogger(ctx)
var req glustershdapi.SplitBrainReq
if err := restutils.UnmarshalRequest(r, &req); err != nil {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrJSONParsingFailed)
return
}
// check if hostname is in form of uuid, if yes, convert uuid to respective ip addr
if uuid.Parse(req.HostName) != nil {
peers, err := peer.GetPeer(req.HostName)
if err != nil {
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, gderrors.ErrPeerNotFound)
return
}
req.HostName = strings.Split(peers.PeerAddresses[0], ":")[0]
}
// Validate volume existence
volinfo, err := volume.GetVolume(volname)
if err != nil {
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
return
}
// Validate volume type
if !isVolReplicate(volinfo.Type) {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrVolTypeNotInReplicateOrDisperse)
return
}
// Check if volume is started
if volinfo.State != volume.VolStarted {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrVolNotStarted)
return
}
var options []string
glusterdSockpath := path.Join(config.GetString("rundir"), "glusterd2.socket")
switch operation {
case "latest-mtime":
fallthrough
case "bigger-file":
if req.FileName == "" {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrFilenameNotFound)
return
}
if !strings.HasPrefix(req.FileName, "/") {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrInvalidFilenameFormat)
return
}
options = append(options, operation, req.FileName, "glusterd-sock", glusterdSockpath)
case "source-brick":
if req.HostName == "" || req.BrickName == "" {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrHostOrBrickNotFound)
return
}
if err := volume.CheckBrickExistence(volinfo, req.HostName, req.BrickName); err != nil {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err)
return
}
brickPath := fmt.Sprintf("%s:%s", req.HostName, req.BrickName)
if req.FileName != "" {
if !strings.HasPrefix(req.FileName, "/") {
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrInvalidFilenameFormat)
return
}
options = append(options, operation, brickPath, req.FileName, "glusterd-sock", glusterdSockpath)
} else {
options = append(options, operation, brickPath, "glusterd-sock", glusterdSockpath)
}
default:
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrInvalidSplitBrainOp)
return
}
_, err = runGlfshealBin(volname, options)
if err != nil {
logger.WithError(err).Error("failed to run glfsheal binary")
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
return
}
restutils.SendHTTPResponse(ctx, w, http.StatusOK, nil)
}