Skip to content

Commit ea76c5b

Browse files
committed
hostpath: Add block volume support
This change adds block volume support to hostpath driver. When a block volume request is received, a block file is created at provisionRoot with the requested capacity as size and a loop device is created associated with the block file. At node publish, a bind mount of the loop device is created at the publish target path. At node unpublish, the target path is unmounted and deleted. At volume delete, loop device is disassociated and the block file is deleted. Add plugins-dir to hostpath plugin daemonset The volume publish target path for block devices are usually under /var/lib/kubelet/plugins directory. Hence, adding plugins directory to the pod volumes with bidirectional mount propagation. Run the plugin as privileged to use loop devices In order to share loop devices with the host, the plugin container must be run as a privileged container.
1 parent c44a77d commit ea76c5b

File tree

15 files changed

+797
-49
lines changed

15 files changed

+797
-49
lines changed

Dockerfile

+2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ FROM alpine
22
LABEL maintainers="Kubernetes Authors"
33
LABEL description="HostPath Driver"
44

5+
# Add util-linux to get a new version of losetup.
6+
RUN apk add util-linux
57
COPY ./bin/hostpathplugin /hostpathplugin
68
ENTRYPOINT ["/hostpathplugin"]

Gopkg.lock

+8-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deploy/hostpath/csi-hostpath-plugin.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ spec:
2424
- --v=5
2525
- --csi-address=/csi/csi.sock
2626
- --kubelet-registration-path=/var/lib/kubelet/plugins/csi-hostpath/csi.sock
27+
securityContext:
28+
privileged: true
2729
env:
2830
- name: KUBE_NODE_NAME
2931
valueFrom:
@@ -59,6 +61,9 @@ spec:
5961
- mountPath: /var/lib/kubelet/pods
6062
mountPropagation: Bidirectional
6163
name: mountpoint-dir
64+
- mountPath: /var/lib/kubelet/plugins
65+
mountPropagation: Bidirectional
66+
name: plugins-dir
6267
volumes:
6368
- hostPath:
6469
path: /var/lib/kubelet/plugins/csi-hostpath
@@ -72,3 +77,7 @@ spec:
7277
path: /var/lib/kubelet/plugins_registry
7378
type: Directory
7479
name: registration-dir
80+
- hostPath:
81+
path: /var/lib/kubelet/plugins
82+
type: Directory
83+
name: plugins-dir

pkg/hostpath/controllerserver.go

+96-10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"google.golang.org/grpc/status"
3333

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

@@ -42,6 +43,13 @@ const (
4243
maxStorageCapacity = tib
4344
)
4445

46+
type accessType int
47+
48+
const (
49+
mountAccess accessType = iota
50+
blockAccess
51+
)
52+
4553
type controllerServer struct {
4654
caps []*csi.ControllerServiceCapability
4755
}
@@ -67,9 +75,41 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
6775
if len(req.GetName()) == 0 {
6876
return nil, status.Error(codes.InvalidArgument, "Name missing in request")
6977
}
70-
if req.GetVolumeCapabilities() == nil {
78+
caps := req.GetVolumeCapabilities()
79+
if caps == nil {
7180
return nil, status.Error(codes.InvalidArgument, "Volume Capabilities missing in request")
7281
}
82+
83+
// Keep a record of the requested access types.
84+
var accessTypeMount, accessTypeBlock bool
85+
86+
for _, cap := range caps {
87+
if cap.GetBlock() != nil {
88+
accessTypeBlock = true
89+
}
90+
if cap.GetMount() != nil {
91+
accessTypeMount = true
92+
}
93+
}
94+
// A real driver would also need to check that the other
95+
// fields in VolumeCapabilities are sane. The check above is
96+
// just enough to pass the "[Testpattern: Dynamic PV (block
97+
// volmode)] volumeMode should fail in binding dynamic
98+
// provisioned PV to PVC" storage E2E test.
99+
100+
if accessTypeBlock && accessTypeMount {
101+
return nil, status.Error(codes.InvalidArgument, "cannot have both block and mount access type")
102+
}
103+
104+
var requestedAccessType accessType
105+
106+
if accessTypeBlock {
107+
requestedAccessType = blockAccess
108+
} else {
109+
// Default to mount.
110+
requestedAccessType = mountAccess
111+
}
112+
73113
// Need to check for already existing volume name, and if found
74114
// check for the requested capacity and already allocated capacity
75115
if exVol, err := getVolumeByName(req.GetName()); err == nil {
@@ -94,13 +134,35 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
94134
if capacity >= maxStorageCapacity {
95135
return nil, status.Errorf(codes.OutOfRange, "Requested capacity %d exceeds maximum allowed %d", capacity, maxStorageCapacity)
96136
}
137+
97138
volumeID := uuid.NewUUID().String()
98139
path := provisionRoot + volumeID
99-
err := os.MkdirAll(path, 0777)
100-
if err != nil {
101-
glog.V(3).Infof("failed to create volume: %v", err)
102-
return nil, err
140+
141+
switch requestedAccessType {
142+
case blockAccess:
143+
executor := utilexec.New()
144+
size := fmt.Sprintf("%dM", capacity/mib)
145+
// Create a block file.
146+
out, err := executor.Command("fallocate", "-l", size, path).CombinedOutput()
147+
if err != nil {
148+
glog.V(3).Infof("failed to create block device: %v", string(out))
149+
return nil, err
150+
}
151+
152+
// Associate block file with the loop device.
153+
volPathHandler := volumepathhandler.VolumePathHandler{}
154+
_, err = volPathHandler.AttachFileDevice(path)
155+
if err != nil {
156+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to attach device: %v", err))
157+
}
158+
case mountAccess:
159+
err := os.MkdirAll(path, 0777)
160+
if err != nil {
161+
glog.V(3).Infof("failed to create volume: %v", err)
162+
return nil, err
163+
}
103164
}
165+
104166
if req.GetVolumeContentSource() != nil {
105167
contentSource := req.GetVolumeContentSource()
106168
if contentSource.GetSnapshot() != nil {
@@ -127,6 +189,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
127189
hostPathVol.VolID = volumeID
128190
hostPathVol.VolSize = capacity
129191
hostPathVol.VolPath = path
192+
hostPathVol.VolAccessType = requestedAccessType
130193
hostPathVolumes[volumeID] = hostPathVol
131194
return &csi.CreateVolumeResponse{
132195
Volume: &csi.Volume{
@@ -148,11 +211,34 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
148211
glog.V(3).Infof("invalid delete volume req: %v", req)
149212
return nil, err
150213
}
151-
volumeID := req.VolumeId
152-
glog.V(4).Infof("deleting volume %s", volumeID)
153-
path := provisionRoot + volumeID
154-
os.RemoveAll(path)
155-
delete(hostPathVolumes, volumeID)
214+
215+
vol, err := getVolumeByID(req.GetVolumeId())
216+
if err != nil {
217+
// Return OK if the volume is not found.
218+
return &csi.DeleteVolumeResponse{}, nil
219+
}
220+
glog.V(4).Infof("deleting volume %s", vol.VolID)
221+
222+
if vol.VolAccessType == blockAccess {
223+
224+
volPathHandler := volumepathhandler.VolumePathHandler{}
225+
// Get the associated loop device.
226+
device, err := volPathHandler.GetLoopDevice(provisionRoot + vol.VolID)
227+
if err != nil {
228+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get the loop device: %v", err))
229+
}
230+
231+
if device != "" {
232+
// Remove any associated loop device.
233+
glog.V(4).Infof("deleting loop device %s", device)
234+
if err := volPathHandler.RemoveLoopDevice(device); err != nil {
235+
return nil, status.Error(codes.Internal, fmt.Sprintf("failed to remove loop device: %v", err))
236+
}
237+
}
238+
}
239+
240+
os.RemoveAll(vol.VolPath)
241+
delete(hostPathVolumes, vol.VolID)
156242
return &csi.DeleteVolumeResponse{}, nil
157243
}
158244

pkg/hostpath/hostpath.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ type hostPath struct {
4545
}
4646

4747
type hostPathVolume struct {
48-
VolName string `json:"volName"`
49-
VolID string `json:"volID"`
50-
VolSize int64 `json:"volSize"`
51-
VolPath string `json:"volPath"`
48+
VolName string `json:"volName"`
49+
VolID string `json:"volID"`
50+
VolSize int64 `json:"volSize"`
51+
VolPath string `json:"volPath"`
52+
VolAccessType accessType `json:"volAccessType"`
5253
}
5354

5455
type hostPathSnapshot struct {

0 commit comments

Comments
 (0)