-
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
amarts
left a comment
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 |
| 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
}
|
|
||
| // 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. |
| 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
| // 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?
| } | ||
|
|
||
| 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
| // 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 |
| 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
| 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.goSigned-off-by: Aravinda VK [email protected]