Skip to content

Commit 3cd6943

Browse files
authored
Merge pull request #11 from ddebroy/filesystem1
FileSystem APIs
2 parents 8d8b555 + 6d186b7 commit 3cd6943

File tree

16 files changed

+1609
-108
lines changed

16 files changed

+1609
-108
lines changed

client/api/filesystem/v1alpha1/api.pb.go

Lines changed: 555 additions & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/api/filesystem/v1alpha1/api.proto

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,151 @@ syntax = "proto3";
22

33
package v1alpha1;
44

5-
import "github.com/kubernetes-csi/csi-proxy/client/api/errors.proto";
6-
7-
// FIXME: this is just an almost empty service, not actually implemented; it's just
8-
// here as an example of what API group and versions will look like. It will actually
9-
// be implemented in a later patch.
10-
115
service Filesystem {
12-
// PathExists checks if the given path exists on the host.
6+
// PathExists checks if the requested path exists in the host's filesystem
137
rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {}
8+
9+
// Mkdir creates a directory at the requested path in the host's filesystem
10+
rpc Mkdir(MkdirRequest) returns (MkdirResponse) {}
11+
12+
// Rmdir removes the directory at the requested path in the host's filesystem.
13+
// This may be used for unlinking a symlink created through LinkPath
14+
rpc Rmdir(RmdirRequest) returns (RmdirResponse) {}
15+
16+
// LinkPath creates a local directory symbolic link between a source path
17+
// and target path in the host's filesystem
18+
rpc LinkPath(LinkPathRequest) returns (LinkPathResponse) {}
19+
}
20+
21+
// Context of the paths used for path prefix validation
22+
enum PathContext {
23+
// Indicates the kubelet-csi-plugins-path parameter of csi-proxy be used as
24+
// the path context. This may be used while handling NodeStageVolume where
25+
// a volume may need to be mounted at a plugin-specific path like:
26+
// kubelet\plugins\kubernetes.io\csi\pv\<pv-name>\globalmount
27+
PLUGIN = 0;
28+
// Indicates the kubelet-pod-path parameter of csi-proxy be used as the path
29+
// context. This may be used while handling NodePublishVolume where a staged
30+
// volume may be need to be symlinked to a pod-specific path like:
31+
// kubelet\pods\<pod-uuid>\volumes\kubernetes.io~csi\<pvc-name>\mount
32+
POD = 1;
1433
}
1534

1635
message PathExistsRequest {
17-
// The path to check in the host file system.
36+
// The path whose existence we want to check in the host's filesystem
1837
string path = 1;
38+
39+
// Context of the path parameter.
40+
// This is used to validate prefix for absolute paths passed
41+
PathContext context = 2;
1942
}
2043

2144
message PathExistsResponse {
22-
bool success = 1;
45+
// Error message if any. Empty string indicates success
46+
string error = 1;
47+
48+
// Indicates whether the path in PathExistsRequest exists in the host's filesystem
49+
bool exists = 2;
50+
}
2351

24-
// present iff success is false
25-
api.CmdletError cmdlet_error = 2;
52+
message MkdirRequest {
53+
// The path to create in the host's filesystem.
54+
// All special characters allowed by Windows in path names will be allowed
55+
// except for restrictions noted below. For details, please check:
56+
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
57+
// Non-existent parent directories in the path will be automatically created.
58+
// Directories will be created with Read and Write privileges of the Windows
59+
// User account under which csi-proxy is started (typically LocalSystem).
60+
//
61+
// Restrictions:
62+
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
63+
// Depending on the context parameter of this function, the path prefix needs
64+
// to match the paths specified either as kubelet-csi-plugins-path
65+
// or as kubelet-pod-path parameters of csi-proxy.
66+
// The path parameter cannot already exist in the host's filesystem.
67+
// UNC paths of the form "\\server\share\path\file" are not allowed.
68+
// All directory separators need to be backslash character: "\".
69+
// Characters: .. / : | ? * in the path are not allowed.
70+
// Maximum path length will be capped to 260 characters.
71+
string path = 1;
72+
73+
// Context of the path parameter.
74+
// This is used to validate prefix for absolute paths passed
75+
PathContext context = 2;
76+
}
77+
78+
message MkdirResponse {
79+
// Error message if any. Empty string indicates success
80+
string error = 1;
81+
}
82+
83+
message RmdirRequest {
84+
// The path to remove in the host's filesystem.
85+
// All special characters allowed by Windows in path names will be allowed
86+
// except for restrictions noted below. For details, please check:
87+
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
88+
//
89+
// Restrictions:
90+
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
91+
// Depending on the context parameter of this function, the path prefix needs
92+
// to match the paths specified either as kubelet-csi-plugins-path
93+
// or as kubelet-pod-path parameters of csi-proxy.
94+
// UNC paths of the form "\\server\share\path\file" are not allowed.
95+
// All directory separators need to be backslash character: "\".
96+
// Characters: .. / : | ? * in the path are not allowed.
97+
// Path cannot be a file of type symlink.
98+
// Maximum path length will be capped to 260 characters.
99+
string path = 1;
100+
101+
// Context of the path parameter.
102+
// This is used to validate prefix for absolute paths passed
103+
PathContext context = 2;
104+
105+
// Force remove all contents under path (if any).
106+
bool force = 3;
107+
}
108+
109+
message RmdirResponse {
110+
// Error message if any. Empty string indicates success
111+
string error = 1;
112+
}
113+
114+
message LinkPathRequest {
115+
// The path where the symlink is created in the host's filesystem.
116+
// All special characters allowed by Windows in path names will be allowed
117+
// except for restrictions noted below. For details, please check:
118+
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
119+
//
120+
// Restrictions:
121+
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
122+
// The path prefix needs needs to match the paths specified as
123+
// kubelet-csi-plugins-path parameter of csi-proxy.
124+
// UNC paths of the form "\\server\share\path\file" are not allowed.
125+
// All directory separators need to be backslash character: "\".
126+
// Characters: .. / : | ? * in the path are not allowed.
127+
// source_path cannot already exist in the host filesystem.
128+
// Maximum path length will be capped to 260 characters.
129+
string source_path = 1;
130+
131+
// Target path in the host's filesystem used for the symlink creation.
132+
// All special characters allowed by Windows in path names will be allowed
133+
// except for restrictions noted below. For details, please check:
134+
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
135+
//
136+
// Restrictions:
137+
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
138+
// The path prefix needs to match the paths specified as
139+
// kubelet-pod-path parameter of csi-proxy.
140+
// UNC paths of the form "\\server\share\path\file" are not allowed.
141+
// All directory separators need to be backslash character: "\".
142+
// Characters: .. / : | ? * in the path are not allowed.
143+
// target_path needs to exist as a directory in the host that is empty.
144+
// target_path cannot be a symbolic link.
145+
// Maximum path length will be capped to 260 characters.
146+
string target_path = 2;
147+
}
26148

27-
bool exists = 4;
149+
message LinkPathResponse {
150+
// Error message if any. Empty string indicates success
151+
string error = 1;
28152
}

client/groups/filesystem/v1alpha1/client_generated.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/server/main.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
package main
22

33
import (
4+
filesystemapi "github.com/kubernetes-csi/csi-proxy/internal/os/filesystem"
45
"github.com/kubernetes-csi/csi-proxy/internal/server"
5-
filesystem "github.com/kubernetes-csi/csi-proxy/internal/server/filesystem"
6+
filesystemsrv "github.com/kubernetes-csi/csi-proxy/internal/server/filesystem"
7+
srvtypes "github.com/kubernetes-csi/csi-proxy/internal/server/types"
8+
flag "github.com/spf13/pflag"
9+
)
10+
11+
var (
12+
kubeletCSIPluginsPath = flag.String("kubelet-csi-plugins-path", `C:\var\lib\kubelet\plugins`, "Absolute path of the Kubelet plugin directory in the host file system")
13+
kubeletPodPath = flag.String("kubelet-pod-path", `C:\var\lib\kubelet\pods`, "Absolute path of the kubelet pod directory in the host file system")
614
)
715

816
func main() {
9-
s := server.NewServer(apiGroups()...)
17+
flag.Parse()
18+
apiGroups, err := apiGroups()
19+
if err != nil {
20+
panic(err)
21+
}
22+
s := server.NewServer(apiGroups...)
1023
if err := s.Start(nil); err != nil {
1124
panic(err)
1225
}
1326
}
1427

1528
// apiGroups returns the list of enabled API groups.
16-
func apiGroups() []server.APIGroup {
17-
return []server.APIGroup{
18-
&filesystem.Server{},
29+
func apiGroups() ([]srvtypes.APIGroup, error) {
30+
fssrv, err := filesystemsrv.NewServer(*kubeletCSIPluginsPath, *kubeletPodPath, filesystemapi.New())
31+
if err != nil {
32+
return []srvtypes.APIGroup{}, err
1933
}
34+
return []srvtypes.APIGroup{
35+
fssrv,
36+
}, nil
2037
}

integrationtests/filesystem_test.go

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,102 @@ package integrationtests
22

33
import (
44
"context"
5+
"fmt"
6+
"math/rand"
7+
"os"
58
"testing"
9+
"time"
610

711
"github.com/stretchr/testify/assert"
812
"github.com/stretchr/testify/require"
913

1014
"github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v1alpha1"
1115
v1alpha1client "github.com/kubernetes-csi/csi-proxy/client/groups/filesystem/v1alpha1"
12-
filesystem "github.com/kubernetes-csi/csi-proxy/internal/server/filesystem"
1316
)
1417

15-
func TestFilesystemAPIGroup(t *testing.T) {
16-
defer startServer(t, &filesystem.Server{})()
18+
func pathExists(path string) (bool, error) {
19+
_, err := os.Stat(path)
20+
if err == nil {
21+
return true, nil
22+
}
23+
if os.IsNotExist(err) {
24+
return false, nil
25+
}
26+
return false, err
27+
}
1728

18-
t.Run("it works", func(t *testing.T) {
29+
func TestFilesystemAPIGroup(t *testing.T) {
30+
t.Run("PathExists positive", func(t *testing.T) {
1931
client, err := v1alpha1client.NewClient()
2032
require.Nil(t, err)
21-
defer close(t, client)
33+
defer client.Close()
2234

23-
path := "/dummy/path"
24-
request := &v1alpha1.PathExistsRequest{
25-
Path: path,
35+
s1 := rand.NewSource(time.Now().UnixNano())
36+
r1 := rand.New(s1)
37+
38+
// simulate FS operations around staging a volume on a node
39+
stagepath := fmt.Sprintf("C:\\var\\lib\\kubelet\\plugins\\testplugin-%d.csi.io\\volume%d", r1.Intn(100), r1.Intn(100))
40+
mkdirReq := &v1alpha1.MkdirRequest{
41+
Path: stagepath,
42+
Context: v1alpha1.PathContext_PLUGIN,
2643
}
27-
response, err := client.PathExists(context.Background(), request)
44+
mkdirRsp, err := client.Mkdir(context.Background(), mkdirReq)
2845
if assert.Nil(t, err) {
29-
assert.False(t, response.Success)
46+
assert.Equal(t, "", mkdirRsp.Error)
47+
}
48+
49+
exists, err := pathExists(stagepath)
50+
assert.True(t, exists, err)
3051

31-
if assert.NotNil(t, response.CmdletError) {
32-
assert.Equal(t, "dummy", response.CmdletError.CmdletName)
33-
assert.Equal(t, uint32(12), response.CmdletError.Code)
34-
assert.Equal(t, "hey there "+path, response.CmdletError.Message)
35-
}
52+
// simulate operations around publishing a volume to a pod
53+
podpath := fmt.Sprintf("C:\\var\\lib\\kubelet\\pods\\test-pod-id\\volumes\\kubernetes.io~csi\\pvc-test%d", r1.Intn(100))
54+
mkdirReq = &v1alpha1.MkdirRequest{
55+
Path: podpath,
56+
Context: v1alpha1.PathContext_POD,
3657
}
58+
mkdirRsp, err = client.Mkdir(context.Background(), mkdirReq)
59+
if assert.Nil(t, err) {
60+
assert.Equal(t, "", mkdirRsp.Error)
61+
}
62+
linkReq := &v1alpha1.LinkPathRequest{
63+
SourcePath: podpath + "\\rootvol",
64+
TargetPath: stagepath,
65+
}
66+
linkRsp, err := client.LinkPath(context.Background(), linkReq)
67+
if assert.Nil(t, err) {
68+
assert.Equal(t, "", linkRsp.Error)
69+
}
70+
71+
exists, err = pathExists(podpath + "\\rootvol")
72+
assert.True(t, exists, err)
73+
74+
// cleanup pvpath
75+
rmdirReq := &v1alpha1.RmdirRequest{
76+
Path: podpath,
77+
Context: v1alpha1.PathContext_POD,
78+
Force: true,
79+
}
80+
rmdirRsp, err := client.Rmdir(context.Background(), rmdirReq)
81+
if assert.Nil(t, err) {
82+
assert.Equal(t, "", rmdirRsp.Error)
83+
}
84+
85+
exists, err = pathExists(podpath)
86+
assert.False(t, exists, err)
87+
88+
// cleanup plugin path
89+
rmdirReq = &v1alpha1.RmdirRequest{
90+
Path: stagepath,
91+
Context: v1alpha1.PathContext_PLUGIN,
92+
Force: true,
93+
}
94+
rmdirRsp, err = client.Rmdir(context.Background(), rmdirReq)
95+
if assert.Nil(t, err) {
96+
assert.Equal(t, "", rmdirRsp.Error)
97+
}
98+
99+
exists, err = pathExists(stagepath)
100+
assert.False(t, exists, err)
101+
37102
})
38103
}

internal/os/filesystem/api.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package filesystem
2+
3+
import (
4+
// "fmt"
5+
"os"
6+
// "os/exec"
7+
// "runtime"
8+
)
9+
10+
// Implements the Filesystem OS API calls. All code here should be very simple
11+
// pass-through to the OS APIs. Any logic around the APIs should go in
12+
// internal/server/filesystem/server.go so that logic can be easily unit-tested
13+
// without requiring specific OS environments.
14+
15+
type APIImplementor struct{}
16+
17+
func New() APIImplementor {
18+
return APIImplementor{}
19+
}
20+
21+
func (APIImplementor) PathExists(path string) (bool, error) {
22+
_, err := os.Stat(path)
23+
if err == nil {
24+
return true, nil
25+
}
26+
if os.IsNotExist(err) {
27+
return false, nil
28+
}
29+
return false, err
30+
}
31+
32+
func (APIImplementor) Mkdir(path string) error {
33+
return os.MkdirAll(path, 0755)
34+
}
35+
36+
func (APIImplementor) Rmdir(path string, force bool) error {
37+
if force {
38+
return os.RemoveAll(path)
39+
}
40+
return os.Remove(path)
41+
}
42+
43+
func (APIImplementor) LinkPath(tgt string, src string) error {
44+
return os.Symlink(tgt, src)
45+
}

0 commit comments

Comments
 (0)