Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hostpath: Add block volume support #6

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ FROM alpine
LABEL maintainers="Kubernetes Authors"
LABEL description="HostPath Driver"

# Add util-linux to get a new version of losetup.
RUN apk add util-linux
COPY ./bin/hostpathplugin /hostpathplugin
ENTRYPOINT ["/hostpathplugin"]
11 changes: 8 additions & 3 deletions Gopkg.lock

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

9 changes: 9 additions & 0 deletions deploy/hostpath/csi-hostpath-plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ spec:
- --v=5
- --csi-address=/csi/csi.sock
- --kubelet-registration-path=/var/lib/kubelet/plugins/csi-hostpath/csi.sock
securityContext:
privileged: true
env:
- name: KUBE_NODE_NAME
valueFrom:
Expand Down Expand Up @@ -59,6 +61,9 @@ spec:
- mountPath: /var/lib/kubelet/pods
mountPropagation: Bidirectional
name: mountpoint-dir
- mountPath: /var/lib/kubelet/plugins
mountPropagation: Bidirectional
name: plugins-dir
volumes:
- hostPath:
path: /var/lib/kubelet/plugins/csi-hostpath
Expand All @@ -72,3 +77,7 @@ spec:
path: /var/lib/kubelet/plugins_registry
type: Directory
name: registration-dir
- hostPath:
path: /var/lib/kubelet/plugins
type: Directory
name: plugins-dir
106 changes: 96 additions & 10 deletions pkg/hostpath/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"google.golang.org/grpc/status"

"github.com/container-storage-interface/spec/lib/go/csi"
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
utilexec "k8s.io/utils/exec"
)

Expand All @@ -42,6 +43,13 @@ const (
maxStorageCapacity = tib
)

type accessType int

const (
mountAccess accessType = iota
blockAccess
)

type controllerServer struct {
caps []*csi.ControllerServiceCapability
}
Expand All @@ -67,9 +75,41 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
if len(req.GetName()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Name missing in request")
}
if req.GetVolumeCapabilities() == nil {
caps := req.GetVolumeCapabilities()
if caps == nil {
return nil, status.Error(codes.InvalidArgument, "Volume Capabilities missing in request")
}

// Keep a record of the requested access types.
var accessTypeMount, accessTypeBlock bool

for _, cap := range caps {
if cap.GetBlock() != nil {
accessTypeBlock = true
}
if cap.GetMount() != nil {
accessTypeMount = true
}
}
// A real driver would also need to check that the other
// fields in VolumeCapabilities are sane. The check above is
// just enough to pass the "[Testpattern: Dynamic PV (block
// volmode)] volumeMode should fail in binding dynamic
// provisioned PV to PVC" storage E2E test.

if accessTypeBlock && accessTypeMount {
return nil, status.Error(codes.InvalidArgument, "cannot have both block and mount access type")
}

var requestedAccessType accessType

if accessTypeBlock {
requestedAccessType = blockAccess
} else {
// Default to mount.
requestedAccessType = mountAccess
}

// Need to check for already existing volume name, and if found
// check for the requested capacity and already allocated capacity
if exVol, err := getVolumeByName(req.GetName()); err == nil {
Expand All @@ -94,13 +134,35 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
if capacity >= maxStorageCapacity {
return nil, status.Errorf(codes.OutOfRange, "Requested capacity %d exceeds maximum allowed %d", capacity, maxStorageCapacity)
}

volumeID := uuid.NewUUID().String()
path := provisionRoot + volumeID
err := os.MkdirAll(path, 0777)
if err != nil {
glog.V(3).Infof("failed to create volume: %v", err)
return nil, err

switch requestedAccessType {
case blockAccess:
executor := utilexec.New()
size := fmt.Sprintf("%dM", capacity/mib)
// Create a block file.
out, err := executor.Command("fallocate", "-l", size, path).CombinedOutput()
if err != nil {
glog.V(3).Infof("failed to create block device: %v", string(out))
return nil, err
}

// Associate block file with the loop device.
volPathHandler := volumepathhandler.VolumePathHandler{}
_, err = volPathHandler.AttachFileDevice(path)
if err != nil {
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to attach device: %v", err))
}
case mountAccess:
err := os.MkdirAll(path, 0777)
if err != nil {
glog.V(3).Infof("failed to create volume: %v", err)
return nil, err
}
}

if req.GetVolumeContentSource() != nil {
contentSource := req.GetVolumeContentSource()
if contentSource.GetSnapshot() != nil {
Expand All @@ -127,6 +189,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
hostPathVol.VolID = volumeID
hostPathVol.VolSize = capacity
hostPathVol.VolPath = path
hostPathVol.VolAccessType = requestedAccessType
hostPathVolumes[volumeID] = hostPathVol
return &csi.CreateVolumeResponse{
Volume: &csi.Volume{
Expand All @@ -148,11 +211,34 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
glog.V(3).Infof("invalid delete volume req: %v", req)
return nil, err
}
volumeID := req.VolumeId
glog.V(4).Infof("deleting volume %s", volumeID)
path := provisionRoot + volumeID
os.RemoveAll(path)
delete(hostPathVolumes, volumeID)

vol, err := getVolumeByID(req.GetVolumeId())
if err != nil {
// Return OK if the volume is not found.
return &csi.DeleteVolumeResponse{}, nil
}
glog.V(4).Infof("deleting volume %s", vol.VolID)

if vol.VolAccessType == blockAccess {

volPathHandler := volumepathhandler.VolumePathHandler{}
// Get the associated loop device.
device, err := volPathHandler.GetLoopDevice(provisionRoot + vol.VolID)
if err != nil {
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get the loop device: %v", err))
}

if device != "" {
// Remove any associated loop device.
glog.V(4).Infof("deleting loop device %s", device)
if err := volPathHandler.RemoveLoopDevice(device); err != nil {
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to remove loop device: %v", err))
}
}
}

os.RemoveAll(vol.VolPath)
delete(hostPathVolumes, vol.VolID)
return &csi.DeleteVolumeResponse{}, nil
}

Expand Down
9 changes: 5 additions & 4 deletions pkg/hostpath/hostpath.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ type hostPath struct {
}

type hostPathVolume struct {
VolName string `json:"volName"`
VolID string `json:"volID"`
VolSize int64 `json:"volSize"`
VolPath string `json:"volPath"`
VolName string `json:"volName"`
VolID string `json:"volID"`
VolSize int64 `json:"volSize"`
VolPath string `json:"volPath"`
VolAccessType accessType `json:"volAccessType"`
}

type hostPathSnapshot struct {
Expand Down
Loading