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

API to remove device #1120

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/endpoints.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

155 changes: 155 additions & 0 deletions e2e/device_test.go
Original file line number Diff line number Diff line change
@@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this after device add? even if the device remove or device list fails, these block fo code won't get hit. it's good to do cleanup in defer().
@aravindavk let me know your thoughts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move all the clean up task to defer? Like deleting and stopping volume etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also called in main_test.go, so should be fine

}
73 changes: 0 additions & 73 deletions e2e/smartvol_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -412,77 +411,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) {
Expand Down Expand Up @@ -532,7 +460,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))
Expand Down
7 changes: 7 additions & 0 deletions pkg/restclient/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 6 additions & 0 deletions plugins/device/deviceutils/store-utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 9 additions & 1 deletion plugins/device/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,20 @@ 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,
},
}
}

// RegisterStepFuncs registers transaction step functions with
// Glusterd Transaction framework
func (p *Plugin) RegisterStepFuncs() {
transaction.RegisterStepFunc(txnPrepareDevice, "prepare-device")
transaction.RegisterStepFunc(txnDeleteDevice, "delete-device")
}
85 changes: 83 additions & 2 deletions plugins/device/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ func deviceAddHandler(w http.ResponseWriter, r *http.Request) {

err = txn.Do()
if err != nil {
logger.WithError(err).Error("Transaction to prepare device failed")
restutils.SendHTTPError(ctx, w, http.StatusInternalServerError, "transaction to prepare device failed")
logger.WithError(err).Error("failed to add device")
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
return
}
deviceInfo, err := deviceutils.GetDevice(peerID, req.Device)
Expand Down Expand Up @@ -194,3 +195,83 @@ func deviceEditHandler(w http.ResponseWriter, r *http.Request) {

restutils.SendHTTPResponse(ctx, w, http.StatusOK, nil)
}

func deviceDeleteHandler(w http.ResponseWriter, r *http.Request) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I missed it earlier. We need to remove the device using vgremove and pvremove as a transaction step. Deleting device from etcd should be last step.

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{
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aravindavk don't we need to check any volume present on the device before removing it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vgremove will fail if the device is being used. We don't wipe the device

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no harm in checking any volume present on the device, so that user will get a meaning full message something like bricks present on the device, cannot delete the device,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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 {
logger.WithError(err).Error("failed to delete device")
status, err := restutils.ErrToStatusCode(err)
restutils.SendHTTPError(ctx, w, status, err)
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)
}
Loading