-
Notifications
You must be signed in to change notification settings - Fork 82
Plugin architecture for brick Provisioners #1256
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very very critical! With this, technically, we are opening up GD2 (and IVP too) many other projects, who wants to use something other than xfs/lvm combination!
Would be great if this can make it in soon!
"github.com/gluster/glusterd2/pkg/lvmutils" | ||
) | ||
|
||
var mountOpts = "rw,inode64,noatime,nouuid" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also need 'discard' as a mount option? So that actual backend gets free'd up ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this option for general purpose or only for loopmount bricks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On LVM it returns space to the TP, and on loop, it'll return space to the outer file system... both are desirable.
pkg/api/volume_req.go
Outdated
Path string `json:"path"` | ||
Name string `json:"name,omitempty"` | ||
Size uint64 `json:"size,omitempty"` | ||
VgID string `json:"vg-id,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need this any more?
Code is refactored to create Provisioner interface and lvm provisioner is now a plugin To create a new Provisioner, implement the following interface and add it to `glusterd2/provisioners/provisioner.go` ``` type Provisioner interface { // Register will be called when user registers a device or directory. // PV and VG will be created in case of lvm plugin Register(devpath string) error // AvailableSize returns available size of the device AvailableSize(devpath string) (uint64, uint64, error) // Unregister will be called when device/directory needs to be removed Unregister(devpath string) error // CreateBrick creates the brick volume CreateBrick(devpath, brickid string, size uint64, bufferFactor float64) error // CreateBrickFs will create the brick filesystem CreateBrickFS(devpath, brickid, fstype string) error // CreateBrickDir will create the brick directory CreateBrickDir(brickPath string) error // MountBrick will mount the brick MountBrick(devpath, brickid, brickPath string) error // UnmountBrick will unmount the brick UnmountBrick(devpath, brickid string) error // RemoveBrick will remove the brick RemoveBrick(devpath, brickid string) error } ``` Signed-off-by: Aravinda VK <[email protected]>
Please review |
@@ -21,74 +20,76 @@ func txnPrepareBricks(c transaction.TxnCtx) error { | |||
return err | |||
} | |||
|
|||
var provisioner provisioners.Provisioner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make a seperate function to avoid code duplication
like
func validateProvisioner(req *api.VolCreateReq)(error,provisioners.Provisioner){
var provisioner provisioners.Provisioner
var err error
if req.Provisioner == "" {
provisioner = provisioners.GetDefault()
} else {
provisioner, err = provisioners.Get(req.Provisioner)
if err != nil {
c.Logger().WithError(err).WithField("name", req.Provisioner).Error("invalid provisioner")
return err,nil
}
}
return nil,provisioner
}
if err != nil { | ||
c.Logger().WithError(err).WithField( | ||
"path", b.Path).Error("failed to create brick directory in mount") | ||
return err | ||
} | ||
|
||
// Update current Vg free size | ||
err = deviceutils.UpdateDeviceFreeSize(gdctx.MyUUID.String(), b.VgName) | ||
availableSize, extentSize, err := provisioner.AvailableSize(b.Device) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same for AvailableSize
and UpdateDeviceFreeSize
I think we can create a separate function which does the functionality for getting the Available size and updating free size
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
@@ -0,0 +1,10 @@ | |||
package deviceutils |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of having a separate file, can we make use of pkg/error/error.go
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not added in pkg/error
since this is in plugins
right now
} | ||
|
||
// UpdateDeviceFreeSize updates the actual available size of VG | ||
func UpdateDeviceFreeSize(peerid, device string, size uint64, extentSize uint64) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size uint64, extentSize uint64
can be made as size , extentSize uint64
if errPV != nil { | ||
c.Logger().WithError(err).WithField("device", device).Error("Failed to remove physical volume") | ||
|
||
var provisioner provisioners.Provisioner |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this also can be avoided if we have this code in separate function
@ansiwen as you are integrating heketi with glusterd2, can you check this PR once as this changes device structure. |
@@ -16,6 +15,16 @@ const ( | |||
chunkSize = "1280k" | |||
) | |||
|
|||
// MbToKb converts Value from Mb to Kb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better place for these two functions would be pkg/utils
as they are used outside of lvmutils
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left it since this will go away once we convert everything to bytes.
"github.com/gluster/glusterd2/pkg/utils" | ||
) | ||
|
||
// MakeXfs creates XFS filesystem |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can be better named MakeXFS
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack
|
||
// Mount mounts the brick LV | ||
func Mount(dev, mountdir, options string) error { | ||
return utils.ExecuteCommandRun("mount", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If https://golang.org/pkg/syscall/#Mount supports mount options, you can use that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack
return | ||
} | ||
|
||
if err != deviceutils.ErrDeviceNotFound { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can return 404 on ErrDeviceNotFound
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
return | ||
} | ||
|
||
// FIXME: Change this to http.StatusCreated when we are able to set | ||
// location header with a unique URL that points to created device. | ||
restutils.SendHTTPResponse(ctx, w, http.StatusOK, peerInfo) | ||
restutils.SendHTTPResponse(ctx, w, http.StatusOK, deviceInfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check if the above FIXME can now be handled. Feel free to address in a later PR or commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
Name string `json:"name,omitempty"` | ||
Size uint64 `json:"size,omitempty"` | ||
Mountdir string `json:"mount-dir,omitempty"` | ||
Device string `json:"device,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between device and device-path ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to think about different naming for this. Device is main device where VG is created in case of lvm. DevicePath is path of LV where brick needs to be mounted. Is RootDevice
or ParentDevice
sounds good instead of just Device
?
@@ -46,6 +47,28 @@ func MountLocalBricks() error { | |||
continue | |||
} | |||
|
|||
if v.Provisioner != "" { | |||
provisioner, err := provisioners.Get(v.Provisioner) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will and should not happen, right ?
May be return error instead of continuing ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently Snapshot Provisioned volumes are not included in this Provisioner interface. Mounting Snapshot provisioned volume or restored volume support needs to be handled.
continue | ||
} | ||
|
||
err = provisioner.MountBrick(b.Device, b.Name, b.Path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a mount
command just below. What case does that handle ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that is to temporarily support mounting snapshot provisioned bricks.
// Unregister will be called when device/directory needs to be removed | ||
Unregister(devpath string) error | ||
// CreateBrick creates the brick volume | ||
CreateBrick(devpath, brickid string, size uint64, bufferFactor float64) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't dived too deep but I think there's an opportunity to merge some of these brick related methods, especially the ones that are always called in order.
For example, can the following for methods be just one instead, say named as CreateBrick
? .
CreateBrick
CreateBrickFS
MountBrick
CreateBrickDir
Do we need that much granularity ? Having less methods will make the provisioner interface more flexible in what it can choose to do internally when creating a brick
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, I will merge the functions into one. MountBrick
is also required as separate since bricks needs to be mounted on glusterd start/node reboot.
Register
AvailableSize
Unregister
CreateBrick
MountBrick
UnmountBrick
RemoveBrick
CreateBrick
also mounts the brick. but exported MountBrick
can be used separately when required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not completely related to this changes. Is it possible to delay the mounting until the brick is actually started? Since the bricks are provisioned by glusterd, we have complete ownership of it. The problem with keeping the mount as soon as the volume creation is that, some application like an antivirus scan or a find, etc could be accessing the mount point when we try to unmount.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah possible to move bricks mount to volume start. With this change, we should only mount the bricks if volume.State=="Started"
during glusterd2 start/restart
@@ -33,6 +33,7 @@ type MountInfo struct { | |||
// Brickinfo is the static information about the brick | |||
type Brickinfo struct { | |||
ID uuid.UUID | |||
Name string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is stored in this variable ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<volname>_s<subvol-number>_b<brick-number>
similar to subvolume name
@rafikc30 Can you please review the LVM related changes ? |
@prashanthpai I will review by eod |
// Unregister will be called when device/directory needs to be removed | ||
Unregister(devpath string) error | ||
// CreateBrick creates the brick volume | ||
CreateBrick(devpath, brickid string, size uint64, bufferFactor float64) error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not completely related to this changes. Is it possible to delay the mounting until the brick is actually started? Since the bricks are provisioned by glusterd, we have complete ownership of it. The problem with keeping the mount as soon as the volume creation is that, some application like an antivirus scan or a find, etc could be accessing the mount point when we try to unmount.
// GetPoolMetadataSize calculates the thin pool metadata size based on the given thin pool size | ||
func GetPoolMetadataSize(poolsize uint64) uint64 { | ||
// GetTpMetadataSize calculates the thin pool metadata size based on the given thin pool size | ||
func GetTpMetadataSize(poolsize uint64) uint64 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should have a minimum value for metadata size, for example, if a user creates with a pool size of 100MB, then metadata will be 0.5MB. We had a recent issue in a user setup where metadata became full because of small size
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will look into that. But this is calculated based on given pool size.
Minimum metadata size required is 0.5% and Max upto 16GB
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please provide more details about the issue. This function is moved from plugins/deviceutils
to pkg/lvmutils
if err != nil { | ||
return err | ||
} | ||
return fsutils.Mount(brickdev, mountdir, mountOpts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think mount can be variable. Different provisioners of the same type may need to mount with different mount options. For example, in the case of a snapshot, we have to mount using the same options that the parent bricks are used. In this case, the default value won't help
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Snapshot mount should be different interface function in my opinion. Bricks of Cloned volume will use this function but not snapshot bricks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason why snapshot should have separate mount interface. Snapshot mount has nothing special comared to the normal mount. IMHO, we can take brickinfo.MountInfo as an argument, because it is a strcture contains xclusive information about mount. And that is common to any provisoner. Something like glusterd2/volume/fs_utils.go:MountDirectory
// Provisioner represents lvm provisioner plugin | ||
type Provisioner struct{} | ||
|
||
func getVgName(devpath string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we store the values like vgname, lvname, device name etc. As of now it return constant values, which is not the case with snapshot.
Or is it possible to move the Mountinfo variable in the brickinfo to provisioner, and return this values based on the values stored
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to keep lvm specific things outside the device info. Non LVM use case will not have Vg. For example, loopmount devices provisioner
|
||
// UnmountBrick unmounts the brick | ||
func (p Provisioner) UnmountBrick(brickPath string) error { | ||
mountdir := path.Dir(brickPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hold true only for smartvol, to extend to the other provisioners, we need to take the mount root from brickpath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some provisioners may not have a directory after mount or it may not be mount. what other possible things after mount root? multiple directory levels?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, we can have multiple levels. Totally depends on how users provisioned it. So we use this function volume.GetBrickMountRoot to find that
@rafikc30 have you managed to find time to review this code? |
if err != nil { | ||
return err | ||
} | ||
return fsutils.Mount(brickdev, mountdir, mountOpts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason why snapshot should have separate mount interface. Snapshot mount has nothing special comared to the normal mount. IMHO, we can take brickinfo.MountInfo as an argument, because it is a strcture contains xclusive information about mount. And that is common to any provisoner. Something like glusterd2/volume/fs_utils.go:MountDirectory
|
||
// UnmountBrick unmounts the brick | ||
func (p Provisioner) UnmountBrick(brickPath string) error { | ||
mountdir := path.Dir(brickPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, we can have multiple levels. Totally depends on how users provisioned it. So we use this function volume.GetBrickMountRoot to find that
Removing GCS tracking label from pull request |
@@ -33,6 +33,7 @@ type MountInfo struct { | |||
// Brickinfo is the static information about the brick | |||
type Brickinfo struct { | |||
ID uuid.UUID | |||
Name string | |||
Hostname string | |||
PeerID uuid.UUID | |||
Path string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this path represents the path where the brick is mounted?
Type string `json:"type"` | ||
PeerID string `json:"peerid"` | ||
Path string `json:"path"` | ||
Name string `json:"name,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we do not store the name of brick in the BrickInfo. Do we really require this?
ExtentSize uint64 `json:"extent-size"` | ||
Used bool `json:"used"` | ||
PeerID string `json:"peer-id"` | ||
Device string `json:"device"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aravindavk I would like to know why Vgname was removed, it would be useful to know the VG of device via the device struct itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VgName is plugin specific, doesn't make sense if it is non LVM plugin
} | ||
|
||
func deviceListHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
ctx := r.Context() | ||
logger := gdctx.GetReqLogger(ctx) | ||
peerID := mux.Vars(r)["peerid"] | ||
if uuid.Parse(peerID) == nil { | ||
if peerID != "" && uuid.Parse(peerID) == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the peerID is empty then uuid.Parse() will return nil, so IMO we don't have to put an additional check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
Updates: #1418 |
Code is refactored to create Provisioner interface and lvm
provisioner is now a plugin
To create a new Provisioner, implement the following interface
and add it to
glusterd2/provisioners/provisioner.go
Signed-off-by: Aravinda VK [email protected]