diff --git a/doc/endpoints.md b/doc/endpoints.md index d64ced097..f4da3d983 100644 --- a/doc/endpoints.md +++ b/doc/endpoints.md @@ -54,6 +54,12 @@ AddPeer | POST | /peers | [PeerAddReq](https://godoc.org/github.com/gluster/glus EditPeer | POST | /peers/{peerid} | [PeerEditReq](https://godoc.org/github.com/gluster/glusterd2/pkg/api#PeerEditReq) | [PeerEditResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#PeerEditResp) SetGlobalOptions | POST | /cluster/options | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) GetGlobalOptions | GET | /cluster/options | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) +LabelCreate | POST | /labels | [LabelCreateReq](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelCreateReq) | [LabelCreateResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelCreateResp) +LabelInfo | GET | /labels/{labelname} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [LabelGetResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelGetResp) +LabelListAll | GET | /labels | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [LabelListResp](https://godoc.org/github.com/gluster/glusterd2/pkg/api#LabelListResp) +LabelDelete | DELETE | /labels/{labelname} | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) | [](https://godoc.org/github.com/gluster/glusterd2/pkg/api#) +LabelConfigSet | POST | /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 | /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) GeoReplicationCreate | POST | /geo-replication/{mastervolid}/{remotevolid} | [GeorepCreateReq](https://godoc.org/github.com/gluster/glusterd2/plugins/georeplication/api#GeorepCreateReq) | [GeorepSession](https://godoc.org/github.com/gluster/glusterd2/plugins/georeplication/api#GeorepSession) GeoReplicationStart | POST | /geo-replication/{mastervolid}/{remotevolid}/start | [GeorepCommandsReq](https://godoc.org/github.com/gluster/glusterd2/plugins/georeplication/api#GeorepCommandsReq) | [GeorepSession](https://godoc.org/github.com/gluster/glusterd2/plugins/georeplication/api#GeorepSession) GeoReplicationStop | POST | /geo-replication/{mastervolid}/{remotevolid}/stop | [GeorepCommandsReq](https://godoc.org/github.com/gluster/glusterd2/plugins/georeplication/api#GeorepCommandsReq) | [GeorepSession](https://godoc.org/github.com/gluster/glusterd2/plugins/georeplication/api#GeorepSession) diff --git a/glusterd2/commands/command.go b/glusterd2/commands/command.go index 995d0b7be..7de3e70ec 100644 --- a/glusterd2/commands/command.go +++ b/glusterd2/commands/command.go @@ -3,6 +3,7 @@ package commands import ( "github.com/gluster/glusterd2/glusterd2/commands/global" + "github.com/gluster/glusterd2/glusterd2/commands/labels" "github.com/gluster/glusterd2/glusterd2/commands/peers" "github.com/gluster/glusterd2/glusterd2/commands/snapshot" "github.com/gluster/glusterd2/glusterd2/commands/version" @@ -25,4 +26,5 @@ var Commands = []Command{ &snapshotcommands.Command{}, &peercommands.Command{}, &globalcommands.Command{}, + &labelcommands.Command{}, } diff --git a/glusterd2/commands/labels/commands.go b/glusterd2/commands/labels/commands.go new file mode 100644 index 000000000..182fba387 --- /dev/null +++ b/glusterd2/commands/labels/commands.go @@ -0,0 +1,71 @@ +package labelcommands + +import ( + "github.com/gluster/glusterd2/glusterd2/servers/rest/route" + "github.com/gluster/glusterd2/pkg/api" + "github.com/gluster/glusterd2/pkg/utils" +) + +// Command is a structure which implements GlusterD Command interface +type Command struct { +} + +// Routes returns list of REST API routes to register with Glusterd +func (c *Command) Routes() route.Routes { + return route.Routes{ + route.Route{ + Name: "LabelCreate", + Method: "POST", + Pattern: "/labels", + Version: 1, + RequestType: utils.GetTypeString((*api.LabelCreateReq)(nil)), + ResponseType: utils.GetTypeString((*api.LabelCreateResp)(nil)), + HandlerFunc: labelCreateHandler}, + route.Route{ + Name: "LabelInfo", + Method: "GET", + Pattern: "/labels/{labelname}", + Version: 1, + ResponseType: utils.GetTypeString((*api.LabelGetResp)(nil)), + HandlerFunc: labelInfoHandler}, + route.Route{ + Name: "LabelListAll", + Method: "GET", + Pattern: "/labels", + Version: 1, + ResponseType: utils.GetTypeString((*api.LabelListResp)(nil)), + HandlerFunc: labelListHandler}, + route.Route{ + Name: "LabelDelete", + Method: "DELETE", + Pattern: "/labels/{labelname}", + Version: 1, + HandlerFunc: labelDeleteHandler}, + route.Route{ + Name: "LabelConfigSet", + Method: "POST", + Pattern: "/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: "/labels/{labelname}/config", + Version: 1, + RequestType: utils.GetTypeString((*api.LabelResetReq)(nil)), + ResponseType: utils.GetTypeString((*api.LabelConfigResp)(nil)), + HandlerFunc: labelConfigResetHandler}, + } +} + +// RegisterStepFuncs registers transaction step functions with +// Glusterd Transaction framework +func (c *Command) RegisterStepFuncs() { + registerLabelCreateStepFuncs() + registerLabelDeleteStepFuncs() + registerLabelConfigSetStepFuncs() + registerLabelConfigResetStepFuncs() + return +} diff --git a/glusterd2/commands/labels/label-create.go b/glusterd2/commands/labels/label-create.go new file mode 100644 index 000000000..ce7abc766 --- /dev/null +++ b/glusterd2/commands/labels/label-create.go @@ -0,0 +1,131 @@ +package labelcommands + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + "github.com/gluster/glusterd2/glusterd2/label" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "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/labels/label-delete.go b/glusterd2/commands/labels/label-delete.go new file mode 100644 index 000000000..36d419d87 --- /dev/null +++ b/glusterd2/commands/labels/label-delete.go @@ -0,0 +1,83 @@ +package labelcommands + +import ( + "fmt" + "net/http" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + "github.com/gluster/glusterd2/glusterd2/label" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "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/labels/label-info.go b/glusterd2/commands/labels/label-info.go new file mode 100644 index 000000000..203ccacd8 --- /dev/null +++ b/glusterd2/commands/labels/label-info.go @@ -0,0 +1,30 @@ +package labelcommands + +import ( + "net/http" + + "github.com/gluster/glusterd2/glusterd2/label" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "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/labels/label-list.go b/glusterd2/commands/labels/label-list.go new file mode 100644 index 000000000..b43050aa8 --- /dev/null +++ b/glusterd2/commands/labels/label-list.go @@ -0,0 +1,34 @@ +package labelcommands + +import ( + "net/http" + + "github.com/gluster/glusterd2/glusterd2/label" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "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/labels/label-reset.go b/glusterd2/commands/labels/label-reset.go new file mode 100644 index 000000000..398acae59 --- /dev/null +++ b/glusterd2/commands/labels/label-reset.go @@ -0,0 +1,112 @@ +package labelcommands + +import ( + "fmt" + "net/http" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + "github.com/gluster/glusterd2/glusterd2/label" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "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/labels/label-set.go b/glusterd2/commands/labels/label-set.go new file mode 100644 index 000000000..a90782928 --- /dev/null +++ b/glusterd2/commands/labels/label-set.go @@ -0,0 +1,139 @@ +package labelcommands + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gluster/glusterd2/glusterd2/gdctx" + "github.com/gluster/glusterd2/glusterd2/label" + restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils" + "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/label/store-utils.go b/glusterd2/label/store-utils.go new file mode 100644 index 000000000..1b8c100a6 --- /dev/null +++ b/glusterd2/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/label/structs.go b/glusterd2/label/structs.go new file mode 100644 index 000000000..5e9bbd3c1 --- /dev/null +++ b/glusterd2/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/label/utils.go b/glusterd2/label/utils.go new file mode 100644 index 000000000..6a1cc019f --- /dev/null +++ b/glusterd2/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/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/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 013084f1b..6c4d1a2bd 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -54,4 +54,6 @@ var ( ErrFetchingVolfileContent = errors.New("unable to fetch volfile content") ErrPidFileNotFound = errors.New("pid file not found") ErrInvalidSnapName = errors.New("invalid snapshot name") + ErrLabelNotFound = errors.New("label not found") + ErrLabelExists = errors.New("label already exists") )