From b1e9d22492fba2a6e8ceb3d6cb3019bf584a8ef8 Mon Sep 17 00:00:00 2001 From: Mohammed Rafi KC Date: Tue, 4 Sep 2018 21:54:08 +0530 Subject: [PATCH 1/5] Labels: Implement Labels Labels are used to tag different object, and perform collective operation based on label configuration values More information on #issue:1094 Signed-off-by: Mohammed Rafi KC --- doc/endpoints.md | 9 +- glusterd2/commands/snapshot/commands.go | 74 ++++++----- glusterd2/commands/snapshot/label-create.go | 131 ++++++++++++++++++ glusterd2/commands/snapshot/label-delete.go | 83 ++++++++++++ glusterd2/commands/snapshot/label-info.go | 30 +++++ glusterd2/commands/snapshot/label-list.go | 34 +++++ glusterd2/commands/snapshot/label-reset.go | 112 ++++++++++++++++ glusterd2/commands/snapshot/label-set.go | 139 ++++++++++++++++++++ glusterd2/servers/rest/utils/utils.go | 2 + glusterd2/snapshot/label/store-utils.go | 125 ++++++++++++++++++ glusterd2/snapshot/label/structs.go | 37 ++++++ glusterd2/snapshot/label/utils.go | 20 +++ pkg/api/labels_req.go | 21 +++ pkg/api/labels_resp.go | 24 ++++ pkg/errors/error.go | 2 + 15 files changed, 809 insertions(+), 34 deletions(-) create mode 100644 glusterd2/commands/snapshot/label-create.go create mode 100644 glusterd2/commands/snapshot/label-delete.go create mode 100644 glusterd2/commands/snapshot/label-info.go create mode 100644 glusterd2/commands/snapshot/label-list.go create mode 100644 glusterd2/commands/snapshot/label-reset.go create mode 100644 glusterd2/commands/snapshot/label-set.go create mode 100644 glusterd2/snapshot/label/store-utils.go create mode 100644 glusterd2/snapshot/label/structs.go create mode 100644 glusterd2/snapshot/label/utils.go create mode 100644 pkg/api/labels_req.go create mode 100644 pkg/api/labels_resp.go diff --git a/doc/endpoints.md b/doc/endpoints.md index dd69067d7..15511049a 100644 --- a/doc/endpoints.md +++ b/doc/endpoints.md @@ -44,9 +44,12 @@ SnapshotInfo | GET | /snapshots/{snapname} | [](https://godoc.org/github.com/glu SnapshotListAll | GET | /snapshots | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [SnapListResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#SnapListResp) SnapshotStatus | GET | /snapshots/{snapname}/status | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [SnapStatusResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#SnapStatusResp) SnapshotDelete | DELETE | /snapshots/{snapname} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) -SnapshotConfigGet | GET | /snapshots/config | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) -SnapshotConfigSet | POST | /snapshots/config | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) -SnapshotConfigReset | DELETE | /snapshots/config | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) +LabelCreate | POST | /snapshots/labels/create | [LabelCreateReq](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelCreateReq) | [LabelCreateResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelCreateResp) +LabelInfo | GET | snapshots/labels/{labelname} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [LabelGetResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelGetResp) +LabelListAll | GET | snapshots/labels/list | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [LabelListResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelListResp) +LabelDelete | DELETE | snapshots/labels/{labelname} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) +LabelConfigSet | POST | snapshots/labels/{labelname}/config | [LabelSetReq](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelSetReq) | [LabelConfigResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelConfigResp) +LabelConfigReset | DELETE | snapshots/labels/{labelname}/config | [LabelResetReq](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelResetReq) | [LabelConfigResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelConfigResp) GetPeer | GET | /peers/{peerid} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [PeerGetResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#PeerGetResp) GetPeers | GET | /peers | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [PeerListResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#PeerListResp) DeletePeer | DELETE | /peers/{peerid} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) diff --git a/glusterd2/commands/snapshot/commands.go b/glusterd2/commands/snapshot/commands.go index ede9eee3a..7607dae45 100644 --- a/glusterd2/commands/snapshot/commands.go +++ b/glusterd2/commands/snapshot/commands.go @@ -1,10 +1,7 @@ package snapshotcommands import ( - "net/http" - "github.com/gluster/glusterd2/glusterd2/servers/rest/route" - restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" "github.com/gluster/glusterd2/pkg/api" "github.com/gluster/glusterd2/pkg/utils" ) @@ -81,41 +78,52 @@ func (c *Command) Routes() route.Routes { Version: 1, HandlerFunc: snapshotDeleteHandler}, route.Route{ - Name: "SnapshotConfigGet", - Method: "GET", - Pattern: "/snapshots/config", - Version: 1, - HandlerFunc: snapshotConfigGetHandler}, + Name: "LabelCreate", + Method: "POST", + Pattern: "/snapshots/labels/create", + Version: 1, + RequestType: utils.GetTypeString((*api.LabelCreateReq)(nil)), + ResponseType: utils.GetTypeString((*api.LabelCreateResp)(nil)), + HandlerFunc: labelCreateHandler}, route.Route{ - Name: "SnapshotConfigSet", - Method: "POST", - Pattern: "/snapshots/config", - Version: 1, - HandlerFunc: snapshotConfigSetHandler}, + Name: "LabelInfo", + Method: "GET", + Pattern: "snapshots/labels/{labelname}", + Version: 1, + ResponseType: utils.GetTypeString((*api.LabelGetResp)(nil)), + HandlerFunc: labelInfoHandler}, + route.Route{ + Name: "LabelListAll", + Method: "GET", + Pattern: "snapshots/labels/list", + Version: 1, + ResponseType: utils.GetTypeString((*api.LabelListResp)(nil)), + HandlerFunc: labelListHandler}, route.Route{ - Name: "SnapshotConfigReset", + Name: "LabelDelete", Method: "DELETE", - Pattern: "/snapshots/config", + Pattern: "snapshots/labels/{labelname}", Version: 1, - HandlerFunc: snapshotConfigResetHandler}, + HandlerFunc: labelDeleteHandler}, + route.Route{ + Name: "LabelConfigSet", + Method: "POST", + Pattern: "snapshots/labels/{labelname}/config", + Version: 1, + RequestType: utils.GetTypeString((*api.LabelSetReq)(nil)), + ResponseType: utils.GetTypeString((*api.LabelConfigResp)(nil)), + HandlerFunc: labelConfigSetHandler}, + route.Route{ + Name: "LabelConfigReset", + Method: "DELETE", + Pattern: "snapshots/labels/{labelname}/config", + Version: 1, + RequestType: utils.GetTypeString((*api.LabelResetReq)(nil)), + ResponseType: utils.GetTypeString((*api.LabelConfigResp)(nil)), + HandlerFunc: labelConfigResetHandler}, } } -func snapshotConfigGetHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - restutils.SendHTTPResponse(ctx, w, http.StatusNotImplemented, "Snapshot Config Get") -} - -func snapshotConfigSetHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - restutils.SendHTTPResponse(ctx, w, http.StatusNotImplemented, "Snapshot Config Set") -} - -func snapshotConfigResetHandler(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - restutils.SendHTTPResponse(ctx, w, http.StatusNotImplemented, "Snapshot Config Reset") -} - // RegisterStepFuncs registers transaction step functions with // Glusterd Transaction framework func (c *Command) RegisterStepFuncs() { @@ -126,5 +134,9 @@ func (c *Command) RegisterStepFuncs() { registerSnapshotStatusStepFuncs() registerSnapRestoreStepFuncs() registerSnapCloneStepFuncs() + registerLabelCreateStepFuncs() + registerLabelDeleteStepFuncs() + registerLabelConfigSetStepFuncs() + registerLabelConfigResetStepFuncs() return } diff --git a/glusterd2/commands/snapshot/label-create.go b/glusterd2/commands/snapshot/label-create.go new file mode 100644 index 000000000..2f42dcf86 --- /dev/null +++ b/glusterd2/commands/snapshot/label-create.go @@ -0,0 +1,131 @@ +package snapshotcommands + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "github.com/gluster/glusterd2/glusterd2/snapshot/label" + "github.com/gluster/glusterd2/glusterd2/transaction" + "github.com/gluster/glusterd2/pkg/api" + gderrors "github.com/gluster/glusterd2/pkg/errors" + + "github.com/pborman/uuid" +) + +const maxSnapCount = 256 + +func validateLabel(info *label.Info) error { + + if info.SnapMaxHardLimit > maxSnapCount { + return fmt.Errorf("Snap-max-hard-limit count cannot exceed more than %d", maxSnapCount) + } + if info.SnapMaxSoftLimit > info.SnapMaxHardLimit { + return errors.New("snap-soft-limit cannot exceed more than snap-max-hard-limit") + } + return nil +} + +func newLabelInfo(req *api.LabelCreateReq) *label.Info { + var labelInfo label.Info + + labelInfo.Name = req.Name + labelInfo.SnapMaxHardLimit = req.SnapMaxHardLimit + labelInfo.SnapMaxSoftLimit = req.SnapMaxSoftLimit + labelInfo.ActivateOnCreate = req.ActivateOnCreate + labelInfo.AutoDelete = req.AutoDelete + labelInfo.Description = req.Description + + return &labelInfo +} + +func storeLabel(c transaction.TxnCtx) error { + + var labelInfo label.Info + + if err := c.Get("label", &labelInfo); err != nil { + return err + } + if err := label.AddOrUpdateLabelFunc(&labelInfo); err != nil { + c.Logger().WithError(err).WithField( + "label", labelInfo.Name).Debug("storeLabel: failed to store label info") + return err + } + + return nil +} + +func registerLabelCreateStepFuncs() { + transaction.RegisterStepFunc(storeLabel, "label-create.Store") +} + +func labelCreateHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := gdctx.GetReqLogger(ctx) + var req api.LabelCreateReq + + if err := restutils.UnmarshalRequest(r, &req); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrJSONParsingFailed) + return + } + if label.ExistsFunc(req.Name) { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrLabelExists) + return + } + + /* + TODO : label name validation + */ + + labelInfo := newLabelInfo(&req) + if err := validateLabel(labelInfo); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err) + return + } + + txn, err := transaction.NewTxnWithLocks(ctx, req.Name) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + defer txn.Done() + + txn.Steps = []*transaction.Step{ + { + DoFunc: "label-create.Store", + Nodes: []uuid.UUID{gdctx.MyUUID}, + }, + } + + if err = txn.Ctx.Set("label", &labelInfo); err != nil { + logger.WithError(err).Error("failed to set request in transaction context") + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + if err = txn.Do(); err != nil { + logger.WithError(err).Error("label create transaction failed") + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + labelInfo, err = label.GetLabel(req.Name) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + txn.Ctx.Logger().WithField("LabelName", req.Name).Info("new label created") + + resp := createLabelCreateResp(labelInfo) + restutils.SetLocationHeader(r, w, labelInfo.Name) + restutils.SendHTTPResponse(ctx, w, http.StatusCreated, resp) +} + +func createLabelCreateResp(info *label.Info) *api.LabelCreateResp { + return (*api.LabelCreateResp)(label.CreateLabelInfoResp(info)) +} diff --git a/glusterd2/commands/snapshot/label-delete.go b/glusterd2/commands/snapshot/label-delete.go new file mode 100644 index 000000000..8fc6a76a9 --- /dev/null +++ b/glusterd2/commands/snapshot/label-delete.go @@ -0,0 +1,83 @@ +package snapshotcommands + +import ( + "fmt" + "net/http" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "github.com/gluster/glusterd2/glusterd2/snapshot/label" + "github.com/gluster/glusterd2/glusterd2/transaction" + "github.com/gorilla/mux" + "github.com/pborman/uuid" +) + +func registerLabelDeleteStepFuncs() { + transaction.RegisterStepFunc(deleteLabel, "label-delete.Store") +} + +func deleteLabel(c transaction.TxnCtx) error { + + var labelInfo label.Info + if err := c.Get("labelinfo", &labelInfo); err != nil { + return err + } + + err := label.DeleteLabel(&labelInfo) + return err +} + +func labelDeleteHandler(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + logger := gdctx.GetReqLogger(ctx) + + labelname := mux.Vars(r)["labelname"] + labelInfo, err := label.GetLabel(labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + txn, err := transaction.NewTxnWithLocks(ctx, labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + defer txn.Done() + + if labelname == (label.DefaultLabel).Name { + errMsg := "Default label cannot be deleted." + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, errMsg) + return + } + + if len(labelInfo.SnapList) > 0 { + errMsg := fmt.Sprintf("Cannot delete Label %s ,as it has %d snapshots tagged.", labelname, len(labelInfo.SnapList)) + restutils.SendHTTPError(ctx, w, http.StatusFailedDependency, errMsg) + return + } + txn.Steps = []*transaction.Step{ + { + DoFunc: "label-delete.Store", + Nodes: []uuid.UUID{gdctx.MyUUID}, + }, + } + + if err := txn.Ctx.Set("labelinfo", labelInfo); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + if err := txn.Do(); err != nil { + logger.WithError(err).WithField( + "label", labelname).Error("transaction to delete label failed") + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + logger.WithField("label-name", labelname).Info("label deleted") + restutils.SendHTTPResponse(ctx, w, http.StatusNoContent, nil) +} diff --git a/glusterd2/commands/snapshot/label-info.go b/glusterd2/commands/snapshot/label-info.go new file mode 100644 index 000000000..faec441fe --- /dev/null +++ b/glusterd2/commands/snapshot/label-info.go @@ -0,0 +1,30 @@ +package snapshotcommands + +import ( + "net/http" + + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "github.com/gluster/glusterd2/glusterd2/snapshot/label" + "github.com/gluster/glusterd2/pkg/api" + "github.com/gorilla/mux" +) + +func labelInfoHandler(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + labelname := mux.Vars(r)["labelname"] + labelInfo, err := label.GetLabel(labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + resp := createLabelGetResp(labelInfo) + restutils.SendHTTPResponse(ctx, w, http.StatusOK, resp) +} + +func createLabelGetResp(info *label.Info) *api.LabelGetResp { + return (*api.LabelGetResp)(label.CreateLabelInfoResp(info)) +} diff --git a/glusterd2/commands/snapshot/label-list.go b/glusterd2/commands/snapshot/label-list.go new file mode 100644 index 000000000..58bb737a4 --- /dev/null +++ b/glusterd2/commands/snapshot/label-list.go @@ -0,0 +1,34 @@ +package snapshotcommands + +import ( + "net/http" + + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "github.com/gluster/glusterd2/glusterd2/snapshot/label" + "github.com/gluster/glusterd2/pkg/api" +) + +func labelListHandler(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + labelInfos, err := label.GetLabels() + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + resp := createLabelListResp(labelInfos) + restutils.SendHTTPResponse(ctx, w, http.StatusOK, resp) +} + +func createLabelListResp(infos []*label.Info) *api.LabelListResp { + var resp = make(api.LabelListResp, len(infos)) + + for index, v := range infos { + resp[index] = *(createLabelGetResp(v)) + } + + return &resp +} diff --git a/glusterd2/commands/snapshot/label-reset.go b/glusterd2/commands/snapshot/label-reset.go new file mode 100644 index 000000000..4b0843fa8 --- /dev/null +++ b/glusterd2/commands/snapshot/label-reset.go @@ -0,0 +1,112 @@ +package snapshotcommands + +import ( + "fmt" + "net/http" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "github.com/gluster/glusterd2/glusterd2/snapshot/label" + "github.com/gluster/glusterd2/glusterd2/transaction" + "github.com/gluster/glusterd2/pkg/api" + gderrors "github.com/gluster/glusterd2/pkg/errors" + "github.com/gorilla/mux" + + "github.com/pborman/uuid" +) + +func updateResetLabel(labelInfo *label.Info, req *api.LabelResetReq) (*label.Info, error) { + + for _, v := range req.Configurations { + switch label.Options(v) { + case label.SnapMaxHardLimitKey: + labelInfo.SnapMaxHardLimit = label.DefaultLabel.SnapMaxHardLimit + case label.SnapMaxSoftLimitKey: + labelInfo.SnapMaxSoftLimit = label.DefaultLabel.SnapMaxSoftLimit + case label.ActivateOnCreateKey: + labelInfo.ActivateOnCreate = label.DefaultLabel.ActivateOnCreate + case label.AutoDeleteKey: + labelInfo.AutoDelete = label.DefaultLabel.AutoDelete + default: + return labelInfo, fmt.Errorf("%s is not a comptable ioption", v) + + } + + } + return labelInfo, nil +} + +func registerLabelConfigResetStepFuncs() { + transaction.RegisterStepFunc(storeLabel, "label-config.Store") +} + +func labelConfigResetHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := gdctx.GetReqLogger(ctx) + var req api.LabelResetReq + + if err := restutils.UnmarshalRequest(r, &req); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrJSONParsingFailed) + return + } + + labelname := mux.Vars(r)["labelname"] + + txn, err := transaction.NewTxnWithLocks(ctx, labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + defer txn.Done() + + labelInfo, err := label.GetLabel(labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + labelInfo, err = updateResetLabel(labelInfo, &req) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err) + return + } + + if err := validateLabel(labelInfo); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err) + return + } + + txn.Steps = []*transaction.Step{ + { + DoFunc: "label-config.Store", + Nodes: []uuid.UUID{gdctx.MyUUID}, + }, + } + + if err = txn.Ctx.Set("label", &labelInfo); err != nil { + logger.WithError(err).Error("failed to set request in transaction context") + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + if err = txn.Do(); err != nil { + logger.WithError(err).Error("label config transaction failed") + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + labelInfo, err = label.GetLabel(labelname) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + txn.Ctx.Logger().WithField("LabelName", labelname).Info("label modfied") + + resp := createLabelConfigResp(labelInfo) + restutils.SetLocationHeader(r, w, labelInfo.Name) + restutils.SendHTTPResponse(ctx, w, http.StatusCreated, resp) +} diff --git a/glusterd2/commands/snapshot/label-set.go b/glusterd2/commands/snapshot/label-set.go new file mode 100644 index 000000000..5ad7a22bc --- /dev/null +++ b/glusterd2/commands/snapshot/label-set.go @@ -0,0 +1,139 @@ +package snapshotcommands + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "github.com/gluster/glusterd2/glusterd2/snapshot/label" + "github.com/gluster/glusterd2/glusterd2/transaction" + "github.com/gluster/glusterd2/pkg/api" + gderrors "github.com/gluster/glusterd2/pkg/errors" + "github.com/gorilla/mux" + + "github.com/pborman/uuid" +) + +func updateSetLabel(labelInfo *label.Info, req *api.LabelSetReq) (*label.Info, error) { + + for k, v := range req.Configurations { + switch label.Options(k) { + case label.SnapMaxHardLimitKey: + value, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return labelInfo, fmt.Errorf("%s is not a comptable value for option %s", v, k) + } + labelInfo.SnapMaxHardLimit = value + case label.SnapMaxSoftLimitKey: + value, err := strconv.ParseUint(v, 10, 64) + if err != nil { + return labelInfo, fmt.Errorf("%s is not a comptable value for option %s", v, k) + } + labelInfo.SnapMaxSoftLimit = value + case label.ActivateOnCreateKey: + value, err := strconv.ParseBool(v) + if err != nil { + return labelInfo, fmt.Errorf("%s is not a comptable value for option %s", v, k) + } + labelInfo.ActivateOnCreate = value + case label.AutoDeleteKey: + value, err := strconv.ParseBool(v) + if err != nil { + return labelInfo, fmt.Errorf("%s is not a comptable value for option %s", v, k) + } + labelInfo.AutoDelete = value + default: + return labelInfo, fmt.Errorf("%s is not a comptable option", k) + + } + + } + return labelInfo, nil +} + +func registerLabelConfigSetStepFuncs() { + transaction.RegisterStepFunc(storeLabel, "label-config.Store") +} + +func labelConfigSetHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := gdctx.GetReqLogger(ctx) + var req api.LabelSetReq + + if err := restutils.UnmarshalRequest(r, &req); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, gderrors.ErrJSONParsingFailed) + return + } + + labelname := mux.Vars(r)["labelname"] + + txn, err := transaction.NewTxnWithLocks(ctx, labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + defer txn.Done() + + if labelname == (label.DefaultLabel).Name { + errMsg := "Default label cannot be edited." + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, errMsg) + return + } + + labelInfo, err := label.GetLabel(labelname) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + labelInfo, err = updateSetLabel(labelInfo, &req) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err) + return + } + + if err := validateLabel(labelInfo); err != nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err) + return + } + + txn.Steps = []*transaction.Step{ + { + DoFunc: "label-config.Store", + Nodes: []uuid.UUID{gdctx.MyUUID}, + }, + } + + if err = txn.Ctx.Set("label", labelInfo); err != nil { + logger.WithError(err).Error("failed to set request in transaction context") + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + if err = txn.Do(); err != nil { + logger.WithError(err).Error("label config transaction failed") + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + + labelInfo, err = label.GetLabel(labelname) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + txn.Ctx.Logger().WithField("LabelName", labelname).Info("label modfied") + + resp := createLabelConfigResp(labelInfo) + restutils.SetLocationHeader(r, w, labelInfo.Name) + restutils.SendHTTPResponse(ctx, w, http.StatusOK, resp) +} + +func createLabelConfigResp(info *label.Info) *api.LabelConfigResp { + return (*api.LabelConfigResp)(label.CreateLabelInfoResp(info)) +} diff --git a/glusterd2/servers/rest/utils/utils.go b/glusterd2/servers/rest/utils/utils.go index 8eea0e697..1100db675 100644 --- a/glusterd2/servers/rest/utils/utils.go +++ b/glusterd2/servers/rest/utils/utils.go @@ -100,6 +100,8 @@ func ErrToStatusCode(err error) (int, error) { statuscode = http.StatusNotFound case gderrors.ErrSnapNotFound: statuscode = http.StatusNotFound + case gderrors.ErrLabelNotFound: + statuscode = http.StatusNotFound case transaction.ErrLockTimeout: statuscode = http.StatusConflict default: diff --git a/glusterd2/snapshot/label/store-utils.go b/glusterd2/snapshot/label/store-utils.go new file mode 100644 index 000000000..1b8c100a6 --- /dev/null +++ b/glusterd2/snapshot/label/store-utils.go @@ -0,0 +1,125 @@ +package label + +import ( + "context" + "encoding/json" + + gdstore "github.com/gluster/glusterd2/glusterd2/store" + gderror "github.com/gluster/glusterd2/pkg/errors" + + "github.com/coreos/etcd/clientv3" + log "github.com/sirupsen/logrus" +) + +const ( + labelPrefix string = "labels/" +) + +var ( + //ExistsFunc check whether a given label exist or not + ExistsFunc = Exists + // AddOrUpdateLabelFunc marshals to label object and passes to store to add/update + AddOrUpdateLabelFunc = AddOrUpdateLabel +) + +//Exists check whether a given label exist or not +func Exists(name string) bool { + if name == DefaultLabel.Name { + return true + } + + resp, e := gdstore.Get(context.TODO(), labelPrefix+name) + if e != nil { + return false + } + + return resp.Count == 1 +} + +//GetLabels retrives the json objects from the store and converts them into +//respective Info objects +func GetLabels() ([]*Info, error) { + resp, e := gdstore.Get(context.TODO(), labelPrefix, clientv3.WithPrefix()) + if e != nil { + return nil, e + } + + labels := make([]*Info, len(resp.Kvs)+1) + labels[0] = &DefaultLabel + + for i, kv := range resp.Kvs { + var label Info + + if err := json.Unmarshal(kv.Value, &label); err != nil { + log.WithError(err).WithField("Label", string(kv.Key)).Error("Failed to unmarshal label") + continue + } + + labels[i+1] = &label + } + + return labels, nil +} + +// AddOrUpdateLabel marshals to Label object and passes to store to add/update +func AddOrUpdateLabel(labelInfo *Info) error { + json, e := json.Marshal(labelInfo) + if e != nil { + log.WithError(e).Error("Failed to marshal the labelinfo object") + return e + } + + _, e = gdstore.Put(context.TODO(), GetStorePath(labelInfo), string(json)) + if e != nil { + log.WithError(e).Error("Couldn't add label to store") + return e + } + return nil +} + +// GetLabel fetches the json object from the store and unmarshalls it into +// Info object +func GetLabel(name string) (*Info, error) { + var labelinfo Info + + if name == DefaultLabel.Name { + labelinfo = DefaultLabel + return &labelinfo, nil + } + + resp, e := gdstore.Get(context.TODO(), labelPrefix+name) + if e != nil { + log.WithError(e).Error("Couldn't retrive volume from store") + return nil, e + } + + if resp.Count != 1 { + log.WithField("label", name).Error("label not found") + return nil, gderror.ErrLabelNotFound + } + + if e = json.Unmarshal(resp.Kvs[0].Value, &labelinfo); e != nil { + log.WithError(e).Error("Failed to unmarshal the data into labelinfo object") + return nil, e + } + return &labelinfo, nil +} + +//DeleteLabel passes the label path to store to delete the label object +func DeleteLabel(labelInfo *Info) error { + _, e := gdstore.Delete(context.TODO(), GetStorePath(labelInfo)) + if e != nil { + return e + } + + /* + TODO + Delete all object tagged to this label + */ + return e +} + +//GetStorePath return label path for etcd store +func GetStorePath(labelInfo *Info) string { + return labelPrefix + labelInfo.Name +} diff --git a/glusterd2/snapshot/label/structs.go b/glusterd2/snapshot/label/structs.go new file mode 100644 index 000000000..5e9bbd3c1 --- /dev/null +++ b/glusterd2/snapshot/label/structs.go @@ -0,0 +1,37 @@ +//Package label that contains struct for label +package label + +//Options is used as key to set Label info values +type Options string + +const ( + //SnapMaxHardLimitKey is used to set the value SnapMaxHardLimit + SnapMaxHardLimitKey Options = "snap-max-hard-limit" + //SnapMaxSoftLimitKey is used to set the value SnapMaxSoftLimit + SnapMaxSoftLimitKey Options = "snap-max-soft-limit" + //ActivateOnCreateKey is used to set the value ActivateOnCreate + ActivateOnCreateKey Options = "activate-on-create" + //AutoDeleteKey is used to set the value AutoDelete + AutoDeleteKey Options = "auto-delete" +) + +//Info is used to represent a label +type Info struct { + Name string + SnapMaxHardLimit uint64 + SnapMaxSoftLimit uint64 + ActivateOnCreate bool + AutoDelete bool + Description string + SnapList []string +} + +//DefaultLabel contains default values for a label +var DefaultLabel = Info{ + ActivateOnCreate: false, + AutoDelete: false, + Description: "This is a default label", + Name: "defaultLabel", + SnapMaxHardLimit: 256, + SnapMaxSoftLimit: 230, +} diff --git a/glusterd2/snapshot/label/utils.go b/glusterd2/snapshot/label/utils.go new file mode 100644 index 000000000..6a1cc019f --- /dev/null +++ b/glusterd2/snapshot/label/utils.go @@ -0,0 +1,20 @@ +package label + +import ( + "github.com/gluster/glusterd2/pkg/api" +) + +//CreateLabelInfoResp parses volume information for response +func CreateLabelInfoResp(info *Info) *api.LabelInfo { + + resp := &api.LabelInfo{ + Name: info.Name, + SnapMaxHardLimit: info.SnapMaxHardLimit, + SnapMaxSoftLimit: info.SnapMaxSoftLimit, + ActivateOnCreate: info.ActivateOnCreate, + AutoDelete: info.AutoDelete, + Description: info.Description, + SnapList: info.SnapList, + } + return resp +} diff --git a/pkg/api/labels_req.go b/pkg/api/labels_req.go new file mode 100644 index 000000000..7df355392 --- /dev/null +++ b/pkg/api/labels_req.go @@ -0,0 +1,21 @@ +package api + +// LabelCreateReq represents a lebel Create Request +type LabelCreateReq struct { + Name string `json:"labelname"` + SnapMaxHardLimit uint64 `json:"snap-max-hard-limit"` + SnapMaxSoftLimit uint64 `json:"snap-max-soft-limit"` + ActivateOnCreate bool `json:"activate-on-create,omitempty"` + AutoDelete bool `json:"auto-delete,omitempty"` + Description string `json:"description,omitempty"` +} + +// LabelSetReq represents a lebel Create Request +type LabelSetReq struct { + Configurations map[string]string `json:"configurations"` +} + +// LabelResetReq represents a lebel Create Request +type LabelResetReq struct { + Configurations []string `json:"configurations"` +} diff --git a/pkg/api/labels_resp.go b/pkg/api/labels_resp.go new file mode 100644 index 000000000..232433f48 --- /dev/null +++ b/pkg/api/labels_resp.go @@ -0,0 +1,24 @@ +package api + +// LabelInfo contains static information of a label +type LabelInfo struct { + Name string `json:"labelname"` + SnapMaxHardLimit uint64 `json:"snap-max-hard-limit"` + SnapMaxSoftLimit uint64 `json:"snap-max-soft-limit"` + ActivateOnCreate bool `json:"activate-on-create"` + AutoDelete bool `json:"auto-delete"` + Description string `json:"description"` + SnapList []string `json:"snap-list"` +} + +//LabelCreateResp is the response sent for a label get request. +type LabelCreateResp LabelInfo + +//LabelGetResp is the response sent for a label get request. +type LabelGetResp LabelInfo + +//LabelListResp is the response sent for a label list request. +type LabelListResp []LabelGetResp + +//LabelConfigResp is the response sent for a label config request. +type LabelConfigResp LabelInfo diff --git a/pkg/errors/error.go b/pkg/errors/error.go index 92dee920d..58157e9e5 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -59,4 +59,6 @@ var ( ErrInvalidVolFileTmplNamespace = errors.New("invalid template namespace") ErrInvalidVolFileTmplName = errors.New("invalid template name") ErrDeviceNameNotFound = errors.New("device name not found") + ErrLabelNotFound = errors.New("label not found") + ErrLabelExists = errors.New("label already exists") ) From e9fe68c38639e4d68b54e82d700970336be6a438 Mon Sep 17 00:00:00 2001 From: Mohammed Rafi KC Date: Fri, 7 Sep 2018 13:02:58 +0530 Subject: [PATCH 2/5] Labels: Add support for glustercli This PR contains glustercli and client implementation for labels Signed-off-by: Mohammed Rafi KC --- glustercli/cmd/label-create.go | 66 ++++++++++++++++++++++++++++++ glustercli/cmd/label-delete.go | 38 ++++++++++++++++++ glustercli/cmd/label-info.go | 62 +++++++++++++++++++++++++++++ glustercli/cmd/label-list.go | 62 +++++++++++++++++++++++++++++ glustercli/cmd/label-reset.go | 46 +++++++++++++++++++++ glustercli/cmd/label-set.go | 73 ++++++++++++++++++++++++++++++++++ glustercli/cmd/labels.go | 18 +++++++++ pkg/restclient/label.go | 50 +++++++++++++++++++++++ 8 files changed, 415 insertions(+) create mode 100644 glustercli/cmd/label-create.go create mode 100644 glustercli/cmd/label-delete.go create mode 100644 glustercli/cmd/label-info.go create mode 100644 glustercli/cmd/label-list.go create mode 100644 glustercli/cmd/label-reset.go create mode 100644 glustercli/cmd/label-set.go create mode 100644 glustercli/cmd/labels.go create mode 100644 pkg/restclient/label.go diff --git a/glustercli/cmd/label-create.go b/glustercli/cmd/label-create.go new file mode 100644 index 000000000..c30ad9523 --- /dev/null +++ b/glustercli/cmd/label-create.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "fmt" + + "github.com/gluster/glusterd2/pkg/api" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + labelCreateHelpShort = "Create a label" + labelCreateHelpLong = "Create a label that can use to tag different objects. Label values will be created with default values if choose to omit, specific label values should provide using relevant flags." +) + +var ( + flagSnapMaxHardLimit uint64 + flagSnapMaxSoftLimit uint64 + flagActivateOnCreate bool + flagAutoDelete bool + flagDescription string + + labelCreateCmd = &cobra.Command{ + Use: "create ", + Short: labelCreateHelpShort, + Long: labelCreateHelpLong, + Args: cobra.MinimumNArgs(1), + Run: labelCreateCmdRun, + } +) + +func init() { + labelCreateCmd.Flags().Uint64Var(&flagSnapMaxHardLimit, "snap-max-hard-limit", 256, "Snapshot maximum hard limit count") + labelCreateCmd.Flags().Uint64Var(&flagSnapMaxSoftLimit, "snap-max-soft-limit", 230, "Snapshot maximum soft limit count") + labelCreateCmd.Flags().BoolVar(&flagActivateOnCreate, "activate-on-create", false, "If enabled, Further snapshots will be activated after creation") + labelCreateCmd.Flags().BoolVar(&flagAutoDelete, "auto-delete", false, "If enabled, Snapshots will be deleted upon reaching snap-max-soft-limit. If disabled A warning log will be generated") + labelCreateCmd.Flags().StringVar(&flagDescription, "description", "", "Label description") + + labelCmd.AddCommand(labelCreateCmd) +} + +func labelCreateCmdRun(cmd *cobra.Command, args []string) { + labelname := args[0] + + req := api.LabelCreateReq{ + Name: labelname, + SnapMaxHardLimit: flagSnapMaxHardLimit, + SnapMaxSoftLimit: flagSnapMaxSoftLimit, + ActivateOnCreate: flagActivateOnCreate, + AutoDelete: flagAutoDelete, + Description: flagDescription, + } + + info, err := client.LabelCreate(req) + if err != nil { + if GlobalFlag.Verbose { + log.WithError(err).WithFields( + log.Fields{ + "labelname": labelname, + }).Error("label creation failed") + } + failure("Label creation failed", err, 1) + } + fmt.Printf("%s Label created successfully\n", info.Name) +} diff --git a/glustercli/cmd/label-delete.go b/glustercli/cmd/label-delete.go new file mode 100644 index 000000000..95aceb22f --- /dev/null +++ b/glustercli/cmd/label-delete.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + labelDeleteHelpShort = "Delete labels" +) + +var ( + labelDeleteCmd = &cobra.Command{ + Use: "delete ", + Short: labelDeleteHelpShort, + Args: cobra.ExactArgs(1), + Run: labelDeleteCmdRun, + } +) + +func init() { + labelCmd.AddCommand(labelDeleteCmd) +} + +func labelDeleteCmdRun(cmd *cobra.Command, args []string) { + labelname := args[0] + + if err := client.LabelDelete(labelname); err != nil { + if GlobalFlag.Verbose { + log.WithError(err).WithField( + "label", labelname).Error("label delete failed") + } + failure("Label delete failed", err, 1) + } + fmt.Printf("%s Label deleted successfully\n", labelname) +} diff --git a/glustercli/cmd/label-info.go b/glustercli/cmd/label-info.go new file mode 100644 index 000000000..fce58267d --- /dev/null +++ b/glustercli/cmd/label-info.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "fmt" + + "github.com/gluster/glusterd2/pkg/api" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + labelInfoHelpShort = "Get Gluster Label Info" +) + +var ( + labelInfoCmd = &cobra.Command{ + Use: "info ", + Short: labelInfoHelpShort, + Args: cobra.ExactArgs(1), + Run: labelInfoCmdRun, + } +) + +func init() { + labelCmd.AddCommand(labelInfoCmd) +} + +func labelInfoDisplay(info *api.LabelGetResp) { + fmt.Println() + fmt.Println("Label Name:", info.Name) + fmt.Println("Snap Max Hard Limit:", info.SnapMaxHardLimit) + fmt.Println("Snap Max Soft Limit:", info.SnapMaxSoftLimit) + fmt.Println("Auto Delete:", info.AutoDelete) + fmt.Println("Activate On Create:", info.ActivateOnCreate) + fmt.Println("Snapshot List:", info.SnapList) + fmt.Println("Description:", info.Description) + fmt.Println() + + return +} + +func labelInfoHandler(cmd *cobra.Command) error { + var info api.LabelGetResp + var err error + + labelname := cmd.Flags().Args()[0] + info, err = client.LabelInfo(labelname) + if err != nil { + return err + } + labelInfoDisplay(&info) + return err +} + +func labelInfoCmdRun(cmd *cobra.Command, args []string) { + if err := labelInfoHandler(cmd); err != nil { + if GlobalFlag.Verbose { + log.WithError(err).Error("error getting label info") + } + failure("Error getting Label info", err, 1) + } +} diff --git a/glustercli/cmd/label-list.go b/glustercli/cmd/label-list.go new file mode 100644 index 000000000..a633a40b4 --- /dev/null +++ b/glustercli/cmd/label-list.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/gluster/glusterd2/pkg/api" + "github.com/olekukonko/tablewriter" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + helpLabelListCmd = "List all Gluster Labels" +) + +func init() { + + labelCmd.AddCommand(labelListCmd) + +} + +func labelListHandler(cmd *cobra.Command) error { + var infos api.LabelListResp + var err error + labelname := cmd.Flags().Args()[0] + + infos, err = client.LabelList(labelname) + if err != nil { + return err + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetAutoMergeCells(true) + table.SetRowLine(true) + if len(infos) == 0 { + fmt.Println("There are no labels in the system") + return nil + } + table.SetHeader([]string{"Name"}) + for _, info := range infos { + table.Append([]string{info.Name}) + } + table.Render() + return err +} + +var labelListCmd = &cobra.Command{ + Use: "list", + Short: helpLabelListCmd, + Args: cobra.ExactArgs(1), + Run: labelListCmdRun, +} + +func labelListCmdRun(cmd *cobra.Command, args []string) { + if err := labelListHandler(cmd); err != nil { + if GlobalFlag.Verbose { + log.WithError(err).Error("error getting label list") + } + failure("Error getting Label list", err, 1) + } +} diff --git a/glustercli/cmd/label-reset.go b/glustercli/cmd/label-reset.go new file mode 100644 index 000000000..6fb458880 --- /dev/null +++ b/glustercli/cmd/label-reset.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "errors" + "fmt" + + "github.com/gluster/glusterd2/pkg/api" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + labelResetCmdHelpShort = "Reset value of label configuratio options" + labelResetCmdHelpLong = "Reset options on a specified gluster snapshot label. Needs a label name and at least one option" +) + +var labelResetCmd = &cobra.Command{ + Use: "reset ", + Short: labelResetCmdHelpShort, + Long: labelResetCmdHelpLong, + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + var req api.LabelResetReq + labelname := args[0] + options := args[1:] + if len(args) < 2 { + failure("Specify atleast one label option to reset", errors.New("Specify atleast one label option to reset"), 1) + } else { + req.Configurations = options + } + + err := client.LabelReset(req, labelname) + if err != nil { + if GlobalFlag.Verbose { + log.WithError(err).WithField("label", labelname).Error("label reset failed") + } + failure("Snapshot label reset failed", err, 1) + } + fmt.Printf("Snapshot label options reset successfully\n") + }, +} + +func init() { + labelCmd.AddCommand(labelResetCmd) +} diff --git a/glustercli/cmd/label-set.go b/glustercli/cmd/label-set.go new file mode 100644 index 000000000..e7079ebeb --- /dev/null +++ b/glustercli/cmd/label-set.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "errors" + "fmt" + + "github.com/gluster/glusterd2/pkg/api" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +const ( + labelSetHelpShort = "Set a label value" + labelSetHelpLong = "Modify one or more label value ." +) + +var ( + labelSetCmd = &cobra.Command{ + Use: "set