From 17496a8226d1b9c814bbad2a5c754bc5072344d4 Mon Sep 17 00:00:00 2001 From: rishubhjain Date: Tue, 11 Dec 2018 04:18:13 -0500 Subject: [PATCH] API to remove device --- doc/endpoints.md | 1 + e2e/device_test.go | 155 ++++++++++++++++++++++ e2e/smartvol_ops_test.go | 73 ---------- pkg/restclient/device.go | 7 + plugins/device/deviceutils/store-utils.go | 6 + plugins/device/init.go | 10 +- plugins/device/rest.go | 79 +++++++++++ plugins/device/transaction.go | 53 ++++++++ 8 files changed, 310 insertions(+), 74 deletions(-) create mode 100644 e2e/device_test.go diff --git a/doc/endpoints.md b/doc/endpoints.md index 9b72213bf..e2966001b 100644 --- a/doc/endpoints.md +++ b/doc/endpoints.md @@ -88,6 +88,7 @@ DeviceInfo | GET | /devices/{peerid}/{device:.*} | [](https://godoc.org/github.c DevicesInPeer | GET | /devices/{peerid} | [](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#) | [ListDeviceResp](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#ListDeviceResp) DeviceEdit | POST | /devices/{peerid}/{device:.*} | [EditDeviceReq](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#EditDeviceReq) | [](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#) DevicesList | GET | /devices | [](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#) | [ListDeviceResp](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#ListDeviceResp) +DeviceDelete | DELETE | /devices/{peerid}/{device:.*} | [](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#) | [](https://godoc.org/github.com/gluster/glusterd2/plugins/device/api#) RebalanceStart | POST | /volumes/{volname}/rebalance/start | [StartReq](https://godoc.org/github.com/gluster/glusterd2/plugins/rebalance/api#StartReq) | [](https://godoc.org/github.com/gluster/glusterd2/plugins/rebalance/api#) RebalanceStop | POST | /volumes/{volname}/rebalance/stop | [](https://godoc.org/github.com/gluster/glusterd2/plugins/rebalance/api#) | [](https://godoc.org/github.com/gluster/glusterd2/plugins/rebalance/api#) RebalanceStatus | GET | /volumes/{volname}/rebalance | [](https://godoc.org/github.com/gluster/glusterd2/plugins/rebalance/api#) | [](https://godoc.org/github.com/gluster/glusterd2/plugins/rebalance/api#) diff --git a/e2e/device_test.go b/e2e/device_test.go new file mode 100644 index 000000000..032c88452 --- /dev/null +++ b/e2e/device_test.go @@ -0,0 +1,155 @@ +package e2e + +import ( + "testing" + + "github.com/gluster/glusterd2/pkg/api" + gutils "github.com/gluster/glusterd2/pkg/utils" + deviceapi "github.com/gluster/glusterd2/plugins/device/api" + + "github.com/stretchr/testify/require" +) + +func editDevice(t *testing.T) { + r := require.New(t) + peerList, err := client.Peers() + r.Nil(err) + + var deviceList []deviceapi.Info + var peerID string + for _, peer := range peerList { + deviceList, err = client.DeviceList(peer.ID.String(), "") + if len(deviceList) > 0 { + peerID = peer.ID.String() + break + } + } + + device := deviceList[0] + if device.State == "enabled" { + err = client.DeviceEdit(peerID, device.Device, "disabled") + r.Nil(err) + } else if device.State == "disabled" { + err = client.DeviceEdit(peerID, device.Device, "enabled") + r.Nil(err) + } + newDeviceList, err := client.DeviceList(peerID, "") + r.Nil(err) + for _, newDevice := range newDeviceList { + if newDevice.Device == device.Device { + r.NotEqual(newDevice.State, device.State) + } + } + + for _, peer := range peerList { + deviceList, err := client.DeviceList(peer.ID.String(), "") + r.Nil(err) + for _, device := range deviceList { + if device.State == "enabled" { + err = client.DeviceEdit(peer.ID.String(), device.Device, "disabled") + r.Nil(err) + } + } + } + smartvolname := formatVolName(t.Name()) + + // create Distribute Replicate(2x3) Volume + createReq := api.VolCreateReq{ + Name: smartvolname, + Size: 40 * gutils.MiB, + DistributeCount: 2, + ReplicaCount: 3, + SubvolZonesOverlap: true, + } + _, err = client.VolumeCreate(createReq) + r.NotNil(err) + + for _, peer := range peerList { + deviceList, err := client.DeviceList(peer.ID.String(), "") + r.Nil(err) + for _, device := range deviceList { + if device.State == "disabled" { + err = client.DeviceEdit(peer.ID.String(), device.Device, "enabled") + r.Nil(err) + } + } + } + + _, err = client.VolumeCreate(createReq) + r.Nil(err) + + r.Nil(client.VolumeDelete(smartvolname)) +} + +func testDeviceDelete(t *testing.T) { + r := require.New(t) + peerList, err := client.Peers() + r.Nil(err) + + var deviceList []deviceapi.Info + var peerID string + for _, peer := range peerList { + deviceList, err = client.DeviceList(peer.ID.String(), "") + r.Nil(err) + if len(deviceList) > 0 { + peerID = peer.ID.String() + break + } + } + + err = client.DeviceDelete(peerID, deviceList[0].Device) + r.Nil(err) + + newDeviceList, err := client.DeviceList(peerID, "") + r.Nil(err) + + r.Equal(len(deviceList)-1, len(newDeviceList)) +} + +// TestDevice creates devices in the test environment +// finally deletes the devices +func TestDevice(t *testing.T) { + var err error + + r := require.New(t) + + tc, err := setupCluster(t, "./config/1.toml", "./config/2.toml", "./config/3.toml") + r.Nil(err) + defer teardownCluster(tc) + + client, err = initRestclient(tc.gds[0]) + r.Nil(err) + r.NotNil(client) + + devicesDir := testTempDir(t, "devices") + + // Device Setup + // Around 150MB will be reserved during pv/vg creation, create device with more size + r.Nil(prepareLoopDevice(devicesDir+"/gluster_dev1.img", "1", "250M")) + r.Nil(prepareLoopDevice(devicesDir+"/gluster_dev2.img", "2", "250M")) + r.Nil(prepareLoopDevice(devicesDir+"/gluster_dev3.img", "3", "250M")) + + _, err = client.DeviceAdd(tc.gds[0].PeerID(), "/dev/gluster_loop1") + r.Nil(err) + dev, err := client.DeviceList(tc.gds[0].PeerID(), "/dev/gluster_loop1") + r.Nil(err) + r.Equal(dev[0].Device, "/dev/gluster_loop1") + + _, err = client.DeviceAdd(tc.gds[1].PeerID(), "/dev/gluster_loop2") + r.Nil(err) + dev, err = client.DeviceList(tc.gds[1].PeerID(), "/dev/gluster_loop2") + r.Nil(err) + r.Equal(dev[0].Device, "/dev/gluster_loop2") + + _, err = client.DeviceAdd(tc.gds[2].PeerID(), "/dev/gluster_loop3") + r.Nil(err) + dev, err = client.DeviceList(tc.gds[2].PeerID(), "/dev/gluster_loop3") + r.Nil(err) + r.Equal(dev[0].Device, "/dev/gluster_loop3") + + t.Run("Edit device", editDevice) + t.Run("Delete device", testDeviceDelete) + + // // Device Cleanup + r.Nil(loopDevicesCleanup(t)) +} diff --git a/e2e/smartvol_ops_test.go b/e2e/smartvol_ops_test.go index d3dfaab52..9cfd4e152 100644 --- a/e2e/smartvol_ops_test.go +++ b/e2e/smartvol_ops_test.go @@ -7,7 +7,6 @@ import ( "github.com/gluster/glusterd2/pkg/api" gutils "github.com/gluster/glusterd2/pkg/utils" - deviceapi "github.com/gluster/glusterd2/plugins/device/api" "github.com/pborman/uuid" "github.com/stretchr/testify/require" @@ -407,77 +406,6 @@ func testSmartVolumeDistributeDisperse(t *testing.T) { checkZeroLvs(r) } -func editDevice(t *testing.T) { - r := require.New(t) - peerList, err := client.Peers() - r.Nil(err) - - var deviceList []deviceapi.Info - var peerID string - for _, peer := range peerList { - deviceList, err = client.DeviceList(peer.ID.String(), "") - if len(deviceList) > 0 { - peerID = peer.ID.String() - break - } - } - - device := deviceList[0] - if device.State == "enabled" { - err = client.DeviceEdit(peerID, device.Device, "disabled") - r.Nil(err) - } else if device.State == "disabled" { - err = client.DeviceEdit(peerID, device.Device, "enabled") - r.Nil(err) - } - newDeviceList, err := client.DeviceList(peerID, "") - r.Nil(err) - for _, newDevice := range newDeviceList { - if newDevice.Device == device.Device { - r.NotEqual(newDevice.State, device.State) - } - } - - for _, peer := range peerList { - deviceList, err := client.DeviceList(peer.ID.String(), "") - r.Nil(err) - for _, device := range deviceList { - if device.State == "enabled" { - err = client.DeviceEdit(peer.ID.String(), device.Device, "disabled") - r.Nil(err) - } - } - } - smartvolname := formatVolName(t.Name()) - - // create Distribute Replicate(2x3) Volume - createReq := api.VolCreateReq{ - Name: smartvolname, - Size: 40 * gutils.MiB, - DistributeCount: 2, - ReplicaCount: 3, - SubvolZonesOverlap: true, - } - _, err = client.VolumeCreate(createReq) - r.NotNil(err) - - for _, peer := range peerList { - deviceList, err := client.DeviceList(peer.ID.String(), "") - r.Nil(err) - for _, device := range deviceList { - if device.State == "disabled" { - err = client.DeviceEdit(peer.ID.String(), device.Device, "enabled") - r.Nil(err) - } - } - } - - _, err = client.VolumeCreate(createReq) - r.Nil(err) - - r.Nil(client.VolumeDelete(smartvolname)) -} - // TestSmartVolume creates a volume and starts it, runs further tests on it and // finally deletes the volume func TestSmartVolume(t *testing.T) { @@ -527,7 +455,6 @@ func TestSmartVolume(t *testing.T) { t.Run("Smartvol Distributed-Replicate Volume", testSmartVolumeDistributeReplicate) t.Run("Smartvol Distributed-Disperse Volume", testSmartVolumeDistributeDisperse) t.Run("Replace Brick", testReplaceBrick) - t.Run("Edit device", editDevice) // // Device Cleanup r.Nil(loopDevicesCleanup(t)) diff --git a/pkg/restclient/device.go b/pkg/restclient/device.go index c2e8a4173..194b8f521 100644 --- a/pkg/restclient/device.go +++ b/pkg/restclient/device.go @@ -43,3 +43,10 @@ func (c *Client) DeviceEdit(peerid, device, state string) error { err := c.post(url, req, http.StatusOK, nil) return err } + +// DeviceDelete removes devices +func (c *Client) DeviceDelete(peerid, device string) error { + device = strings.TrimLeft(device, "/") + url := fmt.Sprintf("/v1/devices/%s/%s", peerid, device) + return c.del(url, nil, http.StatusNoContent, nil) +} diff --git a/plugins/device/deviceutils/store-utils.go b/plugins/device/deviceutils/store-utils.go index 5ec1f7901..ff1d0364e 100644 --- a/plugins/device/deviceutils/store-utils.go +++ b/plugins/device/deviceutils/store-utils.go @@ -70,6 +70,12 @@ func GetDevice(peerID, deviceName string) (*deviceapi.Info, error) { return &device, nil } +// DeleteDevice removes device from peer if device is not been used. +func DeleteDevice(peerID, deviceName string) error { + _, e := store.Delete(context.TODO(), devicePrefix+peerID+"/"+deviceName) + return e +} + // SetDeviceState sets device state and updates device state in etcd func SetDeviceState(peerID, deviceName, deviceState string) error { dev, err := GetDevice(peerID, deviceName) diff --git a/plugins/device/init.go b/plugins/device/init.go index 3c2628193..954712bbe 100644 --- a/plugins/device/init.go +++ b/plugins/device/init.go @@ -54,7 +54,14 @@ func (p *Plugin) RestRoutes() route.Routes { Pattern: "/devices", Version: 1, ResponseType: utils.GetTypeString((*deviceapi.ListDeviceResp)(nil)), - HandlerFunc: deviceListHandler}, + HandlerFunc: listAllDevicesHandler}, + route.Route{ + Name: "DeviceDelete", + Method: "DELETE", + Pattern: "/devices/{peerid}/{device:.*}", + Version: 1, + HandlerFunc: deviceDeleteHandler, + }, } } @@ -62,4 +69,5 @@ func (p *Plugin) RestRoutes() route.Routes { // Glusterd Transaction framework func (p *Plugin) RegisterStepFuncs() { transaction.RegisterStepFunc(txnPrepareDevice, "prepare-device") + transaction.RegisterStepFunc(txnDeleteDevice, "delete-device") } diff --git a/plugins/device/rest.go b/plugins/device/rest.go index edf3a2fa7..2b48b5c39 100644 --- a/plugins/device/rest.go +++ b/plugins/device/rest.go @@ -194,3 +194,82 @@ func deviceEditHandler(w http.ResponseWriter, r *http.Request) { restutils.SendHTTPResponse(ctx, w, http.StatusOK, nil) } + +func deviceDeleteHandler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := gdctx.GetReqLogger(ctx) + peerID := mux.Vars(r)["peerid"] + if uuid.Parse(peerID) == nil { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "invalid peer-id passed in url") + return + } + deviceName := mux.Vars(r)["device"] + if deviceName == "" { + restutils.SendHTTPError(ctx, w, http.StatusBadRequest, "Device name not provided in URL") + return + } + + // Adding prefix(/) to device name + deviceName = "/" + deviceName + + txn, err := transaction.NewTxnWithLocks(ctx, peerID+deviceName) + if err != nil { + status, err := restutils.ErrToStatusCode(err) + restutils.SendHTTPError(ctx, w, status, err) + return + } + defer txn.Done() + + peerInfo, err := peer.GetPeer(peerID) + if err != nil { + logger.WithError(err).WithField("peerid", peerID).Error("Peer ID not found in store") + if err == errors.ErrPeerNotFound { + restutils.SendHTTPError(ctx, w, http.StatusNotFound, errors.ErrPeerNotFound) + } else { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, "failed to get peer details from store") + } + return + } + + txn.Nodes = []uuid.UUID{peerInfo.ID} + txn.Steps = []*transaction.Step{ + { + DoFunc: "delete-device", + Nodes: txn.Nodes, + }, + } + + err = txn.Ctx.Set("peerid", &peerID) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + err = txn.Ctx.Set("device", &deviceName) + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + err = txn.Do() + if err != nil { + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, "transaction to prepare device failed") + return + } + + restutils.SendHTTPResponse(ctx, w, http.StatusNoContent, nil) +} + +func listAllDevicesHandler(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + logger := gdctx.GetReqLogger(ctx) + devices, err := deviceutils.GetDevices() + if err != nil { + logger.WithError(err).Error(err) + restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, err) + return + } + + restutils.SendHTTPResponse(ctx, w, http.StatusOK, devices) +} diff --git a/plugins/device/transaction.go b/plugins/device/transaction.go index d056bf614..f387d622d 100644 --- a/plugins/device/transaction.go +++ b/plugins/device/transaction.go @@ -1,6 +1,8 @@ package device import ( + "errors" + "github.com/gluster/glusterd2/glusterd2/transaction" "github.com/gluster/glusterd2/pkg/lvmutils" deviceapi "github.com/gluster/glusterd2/plugins/device/api" @@ -59,3 +61,54 @@ func txnPrepareDevice(c transaction.TxnCtx) error { } return nil } + +func txnDeleteDevice(c transaction.TxnCtx) error { + var peerID string + if err := c.Get("peerid", &peerID); err != nil { + return err + } + + var deviceName string + if err := c.Get("device", &deviceName); err != nil { + return err + } + + devices, err := deviceutils.GetDevices(peerID) + if err != nil { + return err + } + + if len(devices) == 0 { + return errors.New("No devices added in the given peer") + } + + vgName := "" + for _, device := range devices { + if device.Device == deviceName { + vgName = device.VgName() + } + } + + if vgName == "" { + return errors.New("No device found with given device name") + } + + // Remove VG + if err := lvmutils.RemoveVG(vgName); err != nil { + c.Logger().WithError(err).WithField("device", deviceName).Error("Failed to remove volume group") + return err + } + + //Remove PV + if err := lvmutils.RemovePV(deviceName); err != nil { + c.Logger().WithError(err).WithField("device", deviceName).Error("Failed to remove physical volume") + return err + } + + err = deviceutils.DeleteDevice(peerID, deviceName) + if err != nil { + c.Logger().WithError(err).WithField("peerid", peerID).Error("Failed to remove device") + return err + } + return nil +}