Skip to content
This repository was archived by the owner on Mar 26, 2020. It is now read-only.

Commit 4707158

Browse files
committed
Volume shrink api
1 parent 7436b73 commit 4707158

File tree

4 files changed

+305
-0
lines changed

4 files changed

+305
-0
lines changed

glusterd2/commands/volumes/commands.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ func (c *Command) Routes() route.Routes {
3030
RequestType: utils.GetTypeString((*api.VolExpandReq)(nil)),
3131
ResponseType: utils.GetTypeString((*api.VolumeExpandResp)(nil)),
3232
HandlerFunc: volumeExpandHandler},
33+
route.Route{
34+
Name: "VolumeShrink",
35+
Method: "POST",
36+
Pattern: "/volumes/{volname}/shrink",
37+
Version: 1,
38+
RequestType: utils.GetTypeString((*api.VolShrinkReq)(nil)),
39+
ResponseType: utils.GetTypeString((*api.VolumeShrinkResp)(nil)),
40+
HandlerFunc: volumeShrinkHandler},
3341
// TODO: Implmement volume reset as
3442
// DELETE /volumes/{volname}/options
3543
route.Route{
@@ -160,6 +168,7 @@ func (c *Command) RegisterStepFuncs() {
160168
registerVolStopStepFuncs()
161169
registerBricksStatusStepFuncs()
162170
registerVolExpandStepFuncs()
171+
registerVolShrinkStepFuncs()
163172
registerVolOptionStepFuncs()
164173
registerVolStatedumpFuncs()
165174
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
package volumecommands
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/gluster/glusterd2/glusterd2/daemon"
10+
"github.com/gluster/glusterd2/glusterd2/gdctx"
11+
restutils "github.com/gluster/glusterd2/glusterd2/servers/rest/utils"
12+
"github.com/gluster/glusterd2/glusterd2/transaction"
13+
"github.com/gluster/glusterd2/glusterd2/volume"
14+
"github.com/gluster/glusterd2/pkg/api"
15+
"github.com/gluster/glusterd2/plugins/rebalance"
16+
rebalanceapi "github.com/gluster/glusterd2/plugins/rebalance/api"
17+
18+
"github.com/gorilla/mux"
19+
"github.com/pborman/uuid"
20+
)
21+
22+
func registerVolShrinkStepFuncs() {
23+
transaction.RegisterStepFunc(storeVolume, "vol-shrink.UpdateVolinfo")
24+
transaction.RegisterStepFunc(notifyVolfileChange, "vol-shrink.NotifyClients")
25+
transaction.RegisterStepFunc(startRebalance, "vol-shrink.StartRebalance")
26+
}
27+
28+
func startRebalance(c transaction.TxnCtx) error {
29+
var rinfo rebalanceapi.RebalanceInfo
30+
err := c.Get("rinfo", &rinfo)
31+
if err != nil {
32+
return err
33+
}
34+
35+
rebalanceProcess, err := rebalance.NewRebalanceProcess(rinfo)
36+
if err != nil {
37+
return err
38+
}
39+
40+
err = daemon.Start(rebalanceProcess, true)
41+
42+
return err
43+
}
44+
45+
func validateVolumeShrinkReq(req api.VolShrinkReq) error {
46+
dupEntry := map[string]bool{}
47+
48+
for _, brick := range req.Bricks {
49+
if dupEntry[brick.PeerID+filepath.Clean(brick.Path)] == true {
50+
return errors.ErrDuplicateBrickPath
51+
}
52+
dupEntry[brick.PeerID+filepath.Clean(brick.Path)] = true
53+
54+
}
55+
56+
return nil
57+
58+
}
59+
60+
func volumeShrinkHandler(w http.ResponseWriter, r *http.Request) {
61+
62+
ctx := r.Context()
63+
logger := gdctx.GetReqLogger(ctx)
64+
65+
volname := mux.Vars(r)["volname"]
66+
67+
var req api.VolShrinkReq
68+
if err := restutils.UnmarshalRequest(r, &req); err != nil {
69+
restutils.SendHTTPError(ctx, w, http.StatusUnprocessableEntity, err.Error(), api.ErrCodeDefault)
70+
return
71+
}
72+
73+
if err := validateVolumeShrinkReq(req); err != nil {
74+
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, err)
75+
return
76+
}
77+
78+
volinfo, err := volume.GetVolume(volname)
79+
if err != nil {
80+
restutils.SendHTTPError(ctx, w, http.StatusNotFound, err.Error(), api.ErrCodeDefault)
81+
return
82+
}
83+
84+
for index := range req.Bricks {
85+
for _, b := range req.Bricks {
86+
isPresent = false
87+
for _, brick := range volinfo.Subvols[index].Bricks {
88+
if brick.PeerID.String() == b.PeerID && brick.Path == filepath.Clean(b.Path) {
89+
flag = true
90+
break
91+
}
92+
}
93+
if !isPresent {
94+
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "One or more brick is not part of given volume")
95+
return
96+
}
97+
}
98+
}
99+
100+
switch volinfo.Type {
101+
case volume.Distribute:
102+
case volume.Replicate:
103+
case volume.DistReplicate:
104+
if len(req.Bricks)%volinfo.Subvols[0].ReplicaCount != 0 {
105+
err := errors.New("wrong number of bricks to remove")
106+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err.Error(), api.ErrCodeDefault)
107+
return
108+
}
109+
default:
110+
err := errors.New("not implemented: " + volinfo.Type.String())
111+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err.Error(), api.ErrCodeDefault)
112+
return
113+
114+
}
115+
116+
nodes, err := req.Nodes()
117+
if err != nil {
118+
logger.WithError(err).Error("could not prepare node list")
119+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
120+
return
121+
}
122+
123+
txn := transaction.NewTxn(ctx)
124+
defer txn.Cleanup()
125+
126+
txn, err := transaction.NewTxnWithLocks(ctx, volname)
127+
if err != nil {
128+
status, err := restutils.ErrToStatusCode(err)
129+
restutils.SendHTTPError(ctx, w, status, err)
130+
return
131+
}
132+
defer txn.Done()
133+
134+
volinfo, err := volume.GetVolume(volname)
135+
if err != nil {
136+
restutils.SendHTTPError(ctx, w, http.StatusNotFound, err.Error(), api.ErrCodeDefault)
137+
return
138+
}
139+
140+
for index := range req.Bricks {
141+
for _, b := range req.Bricks {
142+
isPresent = false
143+
for _, brick := range volinfo.Subvols[index].Bricks {
144+
if brick.PeerID.String() == b.PeerID && brick.Path == filepath.Clean(b.Path) {
145+
flag = true
146+
break
147+
}
148+
}
149+
if !isPresent {
150+
restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "One or more brick is not part of given volume")
151+
return
152+
}
153+
}
154+
}
155+
156+
switch volinfo.Type {
157+
case volume.Distribute:
158+
case volume.Replicate:
159+
case volume.DistReplicate:
160+
if len(req.Bricks)%volinfo.Subvols[0].ReplicaCount != 0 {
161+
err := errors.New("wrong number of bricks to remove")
162+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
163+
return
164+
}
165+
default:
166+
err := errors.New("not implemented: " + volinfo.Type.String())
167+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
168+
return
169+
170+
}
171+
172+
nodes, err := req.Nodes()
173+
if err != nil {
174+
logger.WithError(err).Error("could not prepare node list")
175+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
176+
return
177+
}
178+
179+
txn.Steps = []*transaction.Step{
180+
lock,
181+
{
182+
DoFunc: "vol-shrink.UpdateVolinfo",
183+
Nodes: []uuid.UUID{gdctx.MyUUID},
184+
},
185+
{
186+
DoFunc: "vol-shrink.NotifyClients",
187+
Nodes: nodes,
188+
},
189+
{
190+
DoFunc: "vol-shrink.StartRebalance",
191+
Nodes: nodes,
192+
},
193+
194+
unlock,
195+
}
196+
197+
decommissionedSubvols, err := findDecommissioned(req.Bricks, volinfo)
198+
if err != nil {
199+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err)
200+
return
201+
202+
}
203+
204+
// The following line is for testing purposes.
205+
// It seems that there is no other way to include this information in the rebalance volfile right now.
206+
volinfo.Options["distribute.decommissioned-bricks"] = strings.TrimSpace(decommissionedSubvols)
207+
208+
var rinfo rebalanceapi.RebalanceInfo
209+
var commit uint64
210+
rinfo.Volname = volname
211+
rinfo.RebalanceID = uuid.NewRandom()
212+
rinfo.Cmd = rebalanceapi.GfDefragCmdStartForce
213+
rinfo.Status = rebalanceapi.GfDefragStatusNotStarted
214+
rinfo.CommitHash = rebalance.SetCommitHash(commit)
215+
if err := txn.Ctx.Set("rinfo", rinfo); err != nil {
216+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err.Error(), api.ErrCodeDefault)
217+
return
218+
}
219+
220+
if err := txn.Ctx.Set("volinfo", volinfo); err != nil {
221+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err.Error(), api.ErrCodeDefault)
222+
return
223+
}
224+
225+
if err = txn.Do(); err != nil {
226+
logger.WithError(err).Error("remove bricks start transaction failed")
227+
if err == transaction.ErrLockTimeout {
228+
restutils.SendHTTPError(ctx, w, http.StatusConflict, err.Error(), api.ErrCodeDefault)
229+
} else {
230+
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err.Error(), api.ErrCodeDefault)
231+
}
232+
return
233+
}
234+
235+
restutils.SendHTTPResponse(ctx, w, http.StatusOK, decommissionedSubvols)
236+
237+
}
238+
239+
func findDecommissioned(bricks []api.BrickReq, volinfo *volume.Volinfo) (string, error) {
240+
brickSet := make(map[string]bool)
241+
for _, brick := range bricks {
242+
u := uuid.Parse(brick.NodeID)
243+
if u == nil {
244+
return "", errors.New("Invalid nodeid")
245+
}
246+
path, err := filepath.Abs(brick.Path)
247+
if err != nil {
248+
return "", err
249+
}
250+
brickSet[brick.NodeID+":"+path] = true
251+
}
252+
253+
var subvolMap = make(map[string]int)
254+
for _, subvol := range volinfo.Subvols {
255+
for _, b := range subvol.Bricks {
256+
if brickSet[b.NodeID.String()+":"+b.Path] {
257+
if count, ok := subvolMap[subvol.Name]; !ok {
258+
subvolMap[subvol.Name] = 1
259+
} else {
260+
subvolMap[subvol.Name] = count + 1
261+
}
262+
}
263+
264+
}
265+
}
266+
267+
var base int
268+
switch volinfo.Type {
269+
case volume.Distribute:
270+
base = 1
271+
case volume.Replicate:
272+
base = len(bricks)
273+
case volume.DistReplicate:
274+
base = volinfo.Subvols[0].ReplicaCount
275+
default:
276+
return "", errors.New("not implemented: " + volinfo.Type.String())
277+
}
278+
279+
decommissioned := ""
280+
for subvol, count := range subvolMap {
281+
if count != base {
282+
return "", errors.New("Wrong number of bricks in the subvolume")
283+
}
284+
decommissioned = decommissioned + subvol + " "
285+
}
286+
287+
return decommissioned, nil
288+
}

pkg/api/volume_req.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ type VolExpandReq struct {
6767
Flags map[string]bool `json:"flags,omitempty"`
6868
}
6969

70+
// VolShrinkReq represents a request to remove bricks from a volume
71+
type VolShrinkReq struct {
72+
Bricks []BrickReq `json:"bricks"`
73+
}
74+
7075
// VolumeOption represents an option that is part of a profile
7176
type VolumeOption struct {
7277
Name string `json:"name"`

pkg/api/volume_resp.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ type VolumeGetResp VolumeInfo
9292
// VolumeExpandResp is the response sent for a volume expand request.
9393
type VolumeExpandResp VolumeInfo
9494

95+
// VolumeShrinkResp is the response sent for a volume expand request.
96+
type VolumeShrinkResp VolumeInfo
97+
9598
// VolumeStartResp is the response sent for a volume start request.
9699
type VolumeStartResp VolumeInfo
97100

0 commit comments

Comments
 (0)