Skip to content

Commit

Permalink
Merge pull request kubernetes-csi#11 from ddebroy/filesystem1
Browse files Browse the repository at this point in the history
FileSystem APIs
  • Loading branch information
k8s-ci-robot committed Nov 18, 2019
2 parents 8d8b555 + 6d186b7 commit 3cd6943
Show file tree
Hide file tree
Showing 16 changed files with 1,609 additions and 108 deletions.
601 changes: 555 additions & 46 deletions client/api/filesystem/v1alpha1/api.pb.go

Large diffs are not rendered by default.

148 changes: 136 additions & 12 deletions client/api/filesystem/v1alpha1/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,151 @@ syntax = "proto3";

package v1alpha1;

import "github.com/kubernetes-csi/csi-proxy/client/api/errors.proto";

// FIXME: this is just an almost empty service, not actually implemented; it's just
// here as an example of what API group and versions will look like. It will actually
// be implemented in a later patch.

service Filesystem {
// PathExists checks if the given path exists on the host.
// PathExists checks if the requested path exists in the host's filesystem
rpc PathExists(PathExistsRequest) returns (PathExistsResponse) {}

// Mkdir creates a directory at the requested path in the host's filesystem
rpc Mkdir(MkdirRequest) returns (MkdirResponse) {}

// Rmdir removes the directory at the requested path in the host's filesystem.
// This may be used for unlinking a symlink created through LinkPath
rpc Rmdir(RmdirRequest) returns (RmdirResponse) {}

// LinkPath creates a local directory symbolic link between a source path
// and target path in the host's filesystem
rpc LinkPath(LinkPathRequest) returns (LinkPathResponse) {}
}

// Context of the paths used for path prefix validation
enum PathContext {
// Indicates the kubelet-csi-plugins-path parameter of csi-proxy be used as
// the path context. This may be used while handling NodeStageVolume where
// a volume may need to be mounted at a plugin-specific path like:
// kubelet\plugins\kubernetes.io\csi\pv\<pv-name>\globalmount
PLUGIN = 0;
// Indicates the kubelet-pod-path parameter of csi-proxy be used as the path
// context. This may be used while handling NodePublishVolume where a staged
// volume may be need to be symlinked to a pod-specific path like:
// kubelet\pods\<pod-uuid>\volumes\kubernetes.io~csi\<pvc-name>\mount
POD = 1;
}

message PathExistsRequest {
// The path to check in the host file system.
// The path whose existence we want to check in the host's filesystem
string path = 1;

// Context of the path parameter.
// This is used to validate prefix for absolute paths passed
PathContext context = 2;
}

message PathExistsResponse {
bool success = 1;
// Error message if any. Empty string indicates success
string error = 1;

// Indicates whether the path in PathExistsRequest exists in the host's filesystem
bool exists = 2;
}

// present iff success is false
api.CmdletError cmdlet_error = 2;
message MkdirRequest {
// The path to create in the host's filesystem.
// All special characters allowed by Windows in path names will be allowed
// except for restrictions noted below. For details, please check:
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
// Non-existent parent directories in the path will be automatically created.
// Directories will be created with Read and Write privileges of the Windows
// User account under which csi-proxy is started (typically LocalSystem).
//
// Restrictions:
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
// Depending on the context parameter of this function, the path prefix needs
// to match the paths specified either as kubelet-csi-plugins-path
// or as kubelet-pod-path parameters of csi-proxy.
// The path parameter cannot already exist in the host's filesystem.
// UNC paths of the form "\\server\share\path\file" are not allowed.
// All directory separators need to be backslash character: "\".
// Characters: .. / : | ? * in the path are not allowed.
// Maximum path length will be capped to 260 characters.
string path = 1;

// Context of the path parameter.
// This is used to validate prefix for absolute paths passed
PathContext context = 2;
}

message MkdirResponse {
// Error message if any. Empty string indicates success
string error = 1;
}

message RmdirRequest {
// The path to remove in the host's filesystem.
// All special characters allowed by Windows in path names will be allowed
// except for restrictions noted below. For details, please check:
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
//
// Restrictions:
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
// Depending on the context parameter of this function, the path prefix needs
// to match the paths specified either as kubelet-csi-plugins-path
// or as kubelet-pod-path parameters of csi-proxy.
// UNC paths of the form "\\server\share\path\file" are not allowed.
// All directory separators need to be backslash character: "\".
// Characters: .. / : | ? * in the path are not allowed.
// Path cannot be a file of type symlink.
// Maximum path length will be capped to 260 characters.
string path = 1;

// Context of the path parameter.
// This is used to validate prefix for absolute paths passed
PathContext context = 2;

// Force remove all contents under path (if any).
bool force = 3;
}

message RmdirResponse {
// Error message if any. Empty string indicates success
string error = 1;
}

message LinkPathRequest {
// The path where the symlink is created in the host's filesystem.
// All special characters allowed by Windows in path names will be allowed
// except for restrictions noted below. For details, please check:
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
//
// Restrictions:
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
// The path prefix needs needs to match the paths specified as
// kubelet-csi-plugins-path parameter of csi-proxy.
// UNC paths of the form "\\server\share\path\file" are not allowed.
// All directory separators need to be backslash character: "\".
// Characters: .. / : | ? * in the path are not allowed.
// source_path cannot already exist in the host filesystem.
// Maximum path length will be capped to 260 characters.
string source_path = 1;

// Target path in the host's filesystem used for the symlink creation.
// All special characters allowed by Windows in path names will be allowed
// except for restrictions noted below. For details, please check:
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
//
// Restrictions:
// Only absolute path (indicated by a drive letter prefix: e.g. "C:\") is accepted.
// The path prefix needs to match the paths specified as
// kubelet-pod-path parameter of csi-proxy.
// UNC paths of the form "\\server\share\path\file" are not allowed.
// All directory separators need to be backslash character: "\".
// Characters: .. / : | ? * in the path are not allowed.
// target_path needs to exist as a directory in the host that is empty.
// target_path cannot be a symbolic link.
// Maximum path length will be capped to 260 characters.
string target_path = 2;
}

bool exists = 4;
message LinkPathResponse {
// Error message if any. Empty string indicates success
string error = 1;
}
12 changes: 12 additions & 0 deletions client/groups/filesystem/v1alpha1/client_generated.go

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

27 changes: 22 additions & 5 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
package main

import (
filesystemapi "github.com/kubernetes-csi/csi-proxy/internal/os/filesystem"
"github.com/kubernetes-csi/csi-proxy/internal/server"
filesystem "github.com/kubernetes-csi/csi-proxy/internal/server/filesystem"
filesystemsrv "github.com/kubernetes-csi/csi-proxy/internal/server/filesystem"
srvtypes "github.com/kubernetes-csi/csi-proxy/internal/server/types"
flag "github.com/spf13/pflag"
)

var (
kubeletCSIPluginsPath = flag.String("kubelet-csi-plugins-path", `C:\var\lib\kubelet\plugins`, "Absolute path of the Kubelet plugin directory in the host file system")
kubeletPodPath = flag.String("kubelet-pod-path", `C:\var\lib\kubelet\pods`, "Absolute path of the kubelet pod directory in the host file system")
)

func main() {
s := server.NewServer(apiGroups()...)
flag.Parse()
apiGroups, err := apiGroups()
if err != nil {
panic(err)
}
s := server.NewServer(apiGroups...)
if err := s.Start(nil); err != nil {
panic(err)
}
}

// apiGroups returns the list of enabled API groups.
func apiGroups() []server.APIGroup {
return []server.APIGroup{
&filesystem.Server{},
func apiGroups() ([]srvtypes.APIGroup, error) {
fssrv, err := filesystemsrv.NewServer(*kubeletCSIPluginsPath, *kubeletPodPath, filesystemapi.New())
if err != nil {
return []srvtypes.APIGroup{}, err
}
return []srvtypes.APIGroup{
fssrv,
}, nil
}
95 changes: 80 additions & 15 deletions integrationtests/filesystem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,102 @@ package integrationtests

import (
"context"
"fmt"
"math/rand"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/kubernetes-csi/csi-proxy/client/api/filesystem/v1alpha1"
v1alpha1client "github.com/kubernetes-csi/csi-proxy/client/groups/filesystem/v1alpha1"
filesystem "github.com/kubernetes-csi/csi-proxy/internal/server/filesystem"
)

func TestFilesystemAPIGroup(t *testing.T) {
defer startServer(t, &filesystem.Server{})()
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

t.Run("it works", func(t *testing.T) {
func TestFilesystemAPIGroup(t *testing.T) {
t.Run("PathExists positive", func(t *testing.T) {
client, err := v1alpha1client.NewClient()
require.Nil(t, err)
defer close(t, client)
defer client.Close()

path := "/dummy/path"
request := &v1alpha1.PathExistsRequest{
Path: path,
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)

// simulate FS operations around staging a volume on a node
stagepath := fmt.Sprintf("C:\\var\\lib\\kubelet\\plugins\\testplugin-%d.csi.io\\volume%d", r1.Intn(100), r1.Intn(100))
mkdirReq := &v1alpha1.MkdirRequest{
Path: stagepath,
Context: v1alpha1.PathContext_PLUGIN,
}
response, err := client.PathExists(context.Background(), request)
mkdirRsp, err := client.Mkdir(context.Background(), mkdirReq)
if assert.Nil(t, err) {
assert.False(t, response.Success)
assert.Equal(t, "", mkdirRsp.Error)
}

exists, err := pathExists(stagepath)
assert.True(t, exists, err)

if assert.NotNil(t, response.CmdletError) {
assert.Equal(t, "dummy", response.CmdletError.CmdletName)
assert.Equal(t, uint32(12), response.CmdletError.Code)
assert.Equal(t, "hey there "+path, response.CmdletError.Message)
}
// simulate operations around publishing a volume to a pod
podpath := fmt.Sprintf("C:\\var\\lib\\kubelet\\pods\\test-pod-id\\volumes\\kubernetes.io~csi\\pvc-test%d", r1.Intn(100))
mkdirReq = &v1alpha1.MkdirRequest{
Path: podpath,
Context: v1alpha1.PathContext_POD,
}
mkdirRsp, err = client.Mkdir(context.Background(), mkdirReq)
if assert.Nil(t, err) {
assert.Equal(t, "", mkdirRsp.Error)
}
linkReq := &v1alpha1.LinkPathRequest{
SourcePath: podpath + "\\rootvol",
TargetPath: stagepath,
}
linkRsp, err := client.LinkPath(context.Background(), linkReq)
if assert.Nil(t, err) {
assert.Equal(t, "", linkRsp.Error)
}

exists, err = pathExists(podpath + "\\rootvol")
assert.True(t, exists, err)

// cleanup pvpath
rmdirReq := &v1alpha1.RmdirRequest{
Path: podpath,
Context: v1alpha1.PathContext_POD,
Force: true,
}
rmdirRsp, err := client.Rmdir(context.Background(), rmdirReq)
if assert.Nil(t, err) {
assert.Equal(t, "", rmdirRsp.Error)
}

exists, err = pathExists(podpath)
assert.False(t, exists, err)

// cleanup plugin path
rmdirReq = &v1alpha1.RmdirRequest{
Path: stagepath,
Context: v1alpha1.PathContext_PLUGIN,
Force: true,
}
rmdirRsp, err = client.Rmdir(context.Background(), rmdirReq)
if assert.Nil(t, err) {
assert.Equal(t, "", rmdirRsp.Error)
}

exists, err = pathExists(stagepath)
assert.False(t, exists, err)

})
}
45 changes: 45 additions & 0 deletions internal/os/filesystem/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package filesystem

import (
// "fmt"
"os"
// "os/exec"
// "runtime"
)

// Implements the Filesystem OS API calls. All code here should be very simple
// pass-through to the OS APIs. Any logic around the APIs should go in
// internal/server/filesystem/server.go so that logic can be easily unit-tested
// without requiring specific OS environments.

type APIImplementor struct{}

func New() APIImplementor {
return APIImplementor{}
}

func (APIImplementor) PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

func (APIImplementor) Mkdir(path string) error {
return os.MkdirAll(path, 0755)
}

func (APIImplementor) Rmdir(path string, force bool) error {
if force {
return os.RemoveAll(path)
}
return os.Remove(path)
}

func (APIImplementor) LinkPath(tgt string, src string) error {
return os.Symlink(tgt, src)
}

0 comments on commit 3cd6943

Please sign in to comment.