Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKim Carlbäcker <kim.carlbacker@gmail.com>2017-09-12 18:43:28 +0300
committerKim Carlbäcker <kim.carlbacker@gmail.com>2017-09-12 18:43:28 +0300
commite3a261ed820c2d407229ecf2b8aeb97a2087f6fb (patch)
tree158b388cd476fa01830c7b14185dfd080efad83c
parentafd5929cad5a99628ba7c667eaf21876d5ee9b88 (diff)
parentf731bd65927487d1a71d5518664915b45f251c56 (diff)
Merge branch 'zj-namespace-crud' into 'master'
Namespace CRUD methods Closes #552 See merge request !340
-rw-r--r--internal/helper/repo.go17
-rw-r--r--internal/service/namespace/namespace.go100
-rw-r--r--internal/service/namespace/namespace_test.go284
-rw-r--r--internal/service/namespace/server.go12
-rw-r--r--internal/service/namespace/testhelper_test.go47
-rw-r--r--internal/service/register.go4
6 files changed, 461 insertions, 3 deletions
diff --git a/internal/helper/repo.go b/internal/helper/repo.go
index 9e4999248..d0badd3be 100644
--- a/internal/helper/repo.go
+++ b/internal/helper/repo.go
@@ -40,9 +40,9 @@ func GetRepoPath(repo *pb.Repository) (string, error) {
// returned when either the storage can't be found or the path includes
// constructs trying to perform directory traversal.
func GetPath(repo *pb.Repository) (string, error) {
- storagePath, ok := config.StoragePath(repo.GetStorageName())
- if !ok {
- return "", grpc.Errorf(codes.InvalidArgument, "GetPath: invalid storage name '%s'", repo.GetStorageName())
+ storagePath, err := GetStorageByName(repo.GetStorageName())
+ if err != nil {
+ return "", err
}
if _, err := os.Stat(storagePath); err != nil {
@@ -66,6 +66,17 @@ func GetPath(repo *pb.Repository) (string, error) {
return path.Join(storagePath, relativePath), nil
}
+// GetStorageByName will return the path for the storage, which is fetched by
+// its key. An error is return if it cannot be found.
+func GetStorageByName(storageName string) (string, error) {
+ storagePath, ok := config.StoragePath(storageName)
+ if !ok {
+ return "", grpc.Errorf(codes.InvalidArgument, "Storage can not be found by name '%s'", storageName)
+ }
+
+ return storagePath, nil
+}
+
// IsGitDirectory checks if the directory passed as first argument looks like
// a valid git directory.
func IsGitDirectory(dir string) bool {
diff --git a/internal/service/namespace/namespace.go b/internal/service/namespace/namespace.go
new file mode 100644
index 000000000..0f8c6d645
--- /dev/null
+++ b/internal/service/namespace/namespace.go
@@ -0,0 +1,100 @@
+package namespace
+
+import (
+ "os"
+ "path"
+
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+ "gitlab.com/gitlab-org/gitaly/internal/helper"
+ "golang.org/x/net/context"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+)
+
+var noNameError = grpc.Errorf(codes.InvalidArgument, "Name: cannot be empty")
+
+func (s *server) NamespaceExists(ctx context.Context, in *pb.NamespaceExistsRequest) (*pb.NamespaceExistsResponse, error) {
+ storagePath, err := helper.GetStorageByName(in.GetStorageName())
+ if err != nil {
+ return nil, err
+ }
+
+ // This case should return an error, as else we'd actually say the path exists as the
+ // storage exists
+ if in.GetName() == "" {
+ return nil, noNameError
+ }
+
+ if fi, err := os.Stat(namespacePath(storagePath, in.GetName())); os.IsNotExist(err) {
+ return &pb.NamespaceExistsResponse{Exists: false}, nil
+ } else if err != nil {
+ return nil, grpc.Errorf(codes.Internal, "could not stat the directory: %v", err)
+ } else {
+ return &pb.NamespaceExistsResponse{Exists: fi.IsDir()}, nil
+ }
+}
+
+func (s *server) AddNamespace(ctx context.Context, in *pb.AddNamespaceRequest) (*pb.AddNamespaceResponse, error) {
+ storagePath, err := helper.GetStorageByName(in.GetStorageName())
+ if err != nil {
+ return nil, err
+ }
+
+ // Make idempotent, as it's called through Sidekiq
+ // Exists check will return an err if in.GetName() == ""
+ existsRequest := &pb.NamespaceExistsRequest{StorageName: in.StorageName, Name: in.Name}
+ if exists, err := s.NamespaceExists(ctx, existsRequest); err != nil {
+ return nil, err
+ } else if exists.Exists {
+ return &pb.AddNamespaceResponse{}, nil
+ }
+
+ if err = os.MkdirAll(namespacePath(storagePath, in.GetName()), 0770); err != nil {
+ return nil, grpc.Errorf(codes.Internal, "create directory: %v", err)
+ }
+
+ return &pb.AddNamespaceResponse{}, nil
+}
+
+func (s *server) RenameNamespace(ctx context.Context, in *pb.RenameNamespaceRequest) (*pb.RenameNamespaceResponse, error) {
+ storagePath, err := helper.GetStorageByName(in.GetStorageName())
+ if err != nil {
+ return nil, err
+ }
+
+ if in.GetFrom() == "" || in.GetTo() == "" {
+ return nil, grpc.Errorf(codes.InvalidArgument, "from and to cannot be empty")
+ }
+
+ err = os.Rename(namespacePath(storagePath, in.GetFrom()), namespacePath(storagePath, in.GetTo()))
+ if _, ok := err.(*os.LinkError); ok {
+ return nil, grpc.Errorf(codes.InvalidArgument, "from directory not found")
+ } else if err != nil {
+ return nil, grpc.Errorf(codes.Internal, "rename: %v", err)
+ }
+
+ return &pb.RenameNamespaceResponse{}, nil
+}
+
+func (s *server) RemoveNamespace(ctx context.Context, in *pb.RemoveNamespaceRequest) (*pb.RemoveNamespaceResponse, error) {
+ storagePath, err := helper.GetStorageByName(in.GetStorageName())
+ if err != nil {
+ return nil, err
+ }
+
+ // Needed as else we might destroy the whole storage
+ if in.GetName() == "" {
+ return nil, noNameError
+ }
+
+ // os.RemoveAll is idempotent by itself
+ // No need to check if the directory exists, or not
+ if err = os.RemoveAll(namespacePath(storagePath, in.GetName())); err != nil {
+ return nil, grpc.Errorf(codes.Internal, "removal: %v", err)
+ }
+ return &pb.RemoveNamespaceResponse{}, nil
+}
+
+func namespacePath(storage, ns string) string {
+ return path.Join(storage, ns)
+}
diff --git a/internal/service/namespace/namespace_test.go b/internal/service/namespace/namespace_test.go
new file mode 100644
index 000000000..ca8875ddc
--- /dev/null
+++ b/internal/service/namespace/namespace_test.go
@@ -0,0 +1,284 @@
+package namespace
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+ "gitlab.com/gitlab-org/gitaly/internal/config"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+ "golang.org/x/net/context"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+)
+
+func TestMain(m *testing.M) {
+ os.Exit(testMain(m))
+}
+
+func testMain(m *testing.M) int {
+ storageOtherDir, _ := ioutil.TempDir("", "gitaly-repository-exists-test")
+ defer os.Remove(storageOtherDir)
+
+ oldStorages := config.Config.Storages
+ config.Config.Storages = []config.Storage{
+ {Name: "default", Path: testhelper.GitlabTestStoragePath()},
+ {Name: "other", Path: storageOtherDir},
+ }
+ defer func() { config.Config.Storages = oldStorages }()
+
+ return m.Run()
+}
+
+func TestNamespaceExists(t *testing.T) {
+ server := runNamespaceServer(t)
+ defer server.Stop()
+
+ client, conn := newNamespaceClient(t)
+ defer conn.Close()
+
+ // Create one namespace for testing it exists
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ _, err := client.AddNamespace(ctx, &pb.AddNamespaceRequest{StorageName: "default", Name: "existing"})
+ require.NoError(t, err, "Namespace creation failed")
+ defer client.RemoveNamespace(ctx, &pb.RemoveNamespaceRequest{StorageName: "default", Name: "existing"})
+
+ queries := []struct {
+ desc string
+ request *pb.NamespaceExistsRequest
+ errorCode codes.Code
+ exists bool
+ }{
+ {
+ desc: "empty name",
+ request: &pb.NamespaceExistsRequest{
+ StorageName: "default",
+ Name: "",
+ },
+ errorCode: codes.InvalidArgument,
+ },
+ {
+ desc: "Namespace doesn't exists",
+ request: &pb.NamespaceExistsRequest{
+ StorageName: "default",
+ Name: "not-existing",
+ },
+ errorCode: codes.OK,
+ exists: false,
+ },
+ {
+ desc: "Wrong storage path",
+ request: &pb.NamespaceExistsRequest{
+ StorageName: "other",
+ Name: "existing",
+ },
+ errorCode: codes.OK,
+ exists: false,
+ },
+ {
+ desc: "Namespace exists",
+ request: &pb.NamespaceExistsRequest{
+ StorageName: "default",
+ Name: "existing",
+ },
+ errorCode: codes.OK,
+ exists: true,
+ },
+ }
+
+ for _, tc := range queries {
+ t.Run(tc.desc, func(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ response, err := client.NamespaceExists(ctx, tc.request)
+
+ require.Equal(t, tc.errorCode, grpc.Code(err))
+
+ if tc.errorCode == codes.OK {
+ require.Equal(t, tc.exists, response.Exists)
+ }
+ })
+ }
+}
+
+func TestAddNamespace(t *testing.T) {
+ server := runNamespaceServer(t)
+ defer server.Stop()
+
+ client, conn := newNamespaceClient(t)
+ defer conn.Close()
+
+ queries := []struct {
+ desc string
+ request *pb.AddNamespaceRequest
+ errorCode codes.Code
+ }{
+ {
+ desc: "No name",
+ request: &pb.AddNamespaceRequest{
+ StorageName: "default",
+ Name: "",
+ },
+ errorCode: codes.InvalidArgument,
+ },
+ {
+ desc: "Namespace is successfully created",
+ request: &pb.AddNamespaceRequest{
+ StorageName: "default",
+ Name: "create-me",
+ },
+ errorCode: codes.OK,
+ },
+ {
+ desc: "Idempotent on creation",
+ request: &pb.AddNamespaceRequest{
+ StorageName: "default",
+ Name: "create-me",
+ },
+ errorCode: codes.OK,
+ },
+ {
+ desc: "no storage",
+ request: &pb.AddNamespaceRequest{
+ StorageName: "",
+ Name: "mepmep",
+ },
+ errorCode: codes.InvalidArgument,
+ },
+ }
+
+ for _, tc := range queries {
+ t.Run(tc.desc, func(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ _, err := client.AddNamespace(ctx, tc.request)
+
+ require.Equal(t, tc.errorCode, grpc.Code(err))
+
+ // Clean up
+ if tc.errorCode == codes.OK {
+ client.RemoveNamespace(ctx, &pb.RemoveNamespaceRequest{
+ StorageName: tc.request.StorageName,
+ Name: tc.request.Name,
+ })
+ }
+ })
+ }
+}
+
+func TestRemoveNamespace(t *testing.T) {
+ server := runNamespaceServer(t)
+ defer server.Stop()
+
+ client, conn := newNamespaceClient(t)
+ defer conn.Close()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ queries := []struct {
+ desc string
+ request *pb.RemoveNamespaceRequest
+ errorCode codes.Code
+ }{
+ {
+ desc: "Namespace is successfully removed",
+ request: &pb.RemoveNamespaceRequest{
+ StorageName: "default",
+ Name: "created",
+ },
+ errorCode: codes.OK,
+ },
+ {
+ desc: "Idempotent on deletion",
+ request: &pb.RemoveNamespaceRequest{
+ StorageName: "default",
+ Name: "not-there",
+ },
+ errorCode: codes.OK,
+ },
+ {
+ desc: "no storage",
+ request: &pb.RemoveNamespaceRequest{
+ StorageName: "",
+ Name: "mepmep",
+ },
+ errorCode: codes.InvalidArgument,
+ },
+ }
+
+ for _, tc := range queries {
+ t.Run(tc.desc, func(t *testing.T) {
+ _, err := client.AddNamespace(ctx, &pb.AddNamespaceRequest{StorageName: "default", Name: "created"})
+ require.NoError(t, err, "setup failed")
+
+ _, err = client.RemoveNamespace(ctx, tc.request)
+ require.Equal(t, tc.errorCode, grpc.Code(err))
+ })
+ }
+}
+
+func TestRenameNamespace(t *testing.T) {
+ server := runNamespaceServer(t)
+ defer server.Stop()
+
+ client, conn := newNamespaceClient(t)
+ defer conn.Close()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ queries := []struct {
+ desc string
+ request *pb.RenameNamespaceRequest
+ errorCode codes.Code
+ }{
+ {
+ desc: "Renaming an existing namespace",
+ request: &pb.RenameNamespaceRequest{
+ From: "existing",
+ To: "new-path",
+ StorageName: "default",
+ },
+ errorCode: codes.OK,
+ },
+ {
+ desc: "No from given",
+ request: &pb.RenameNamespaceRequest{
+ From: "",
+ To: "new-path",
+ StorageName: "default",
+ },
+ errorCode: codes.InvalidArgument,
+ },
+ {
+ desc: "non-existing namespace",
+ request: &pb.RenameNamespaceRequest{
+ From: "non-existing",
+ To: "new-path",
+ StorageName: "default",
+ },
+ errorCode: codes.InvalidArgument,
+ },
+ }
+
+ _, err := client.AddNamespace(ctx, &pb.AddNamespaceRequest{"default", "existing"})
+ require.NoError(t, err)
+
+ for _, tc := range queries {
+ t.Run(tc.desc, func(t *testing.T) {
+ _, err := client.RenameNamespace(ctx, tc.request)
+
+ require.Equal(t, tc.errorCode, grpc.Code(err))
+
+ if tc.errorCode == codes.OK {
+ client.RemoveNamespace(ctx, &pb.RemoveNamespaceRequest{"default", "new-path"})
+ }
+ })
+ }
+}
diff --git a/internal/service/namespace/server.go b/internal/service/namespace/server.go
new file mode 100644
index 000000000..5735feb37
--- /dev/null
+++ b/internal/service/namespace/server.go
@@ -0,0 +1,12 @@
+package namespace
+
+import (
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+)
+
+type server struct{}
+
+// NewServer creates a new instance of a gRPC namespace server
+func NewServer() pb.NamespaceServiceServer {
+ return &server{}
+}
diff --git a/internal/service/namespace/testhelper_test.go b/internal/service/namespace/testhelper_test.go
new file mode 100644
index 000000000..a276c533b
--- /dev/null
+++ b/internal/service/namespace/testhelper_test.go
@@ -0,0 +1,47 @@
+package namespace
+
+import (
+ "net"
+ "testing"
+ "time"
+
+ pb "gitlab.com/gitlab-org/gitaly-proto/go"
+ "gitlab.com/gitlab-org/gitaly/internal/testhelper"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/reflection"
+)
+
+var (
+ serverSocketPath = testhelper.GetTemporaryGitalySocketFileName()
+)
+
+func runNamespaceServer(t *testing.T) *grpc.Server {
+ server := testhelper.NewTestGrpcServer(t, nil, nil)
+ listener, err := net.Listen("unix", serverSocketPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pb.RegisterNamespaceServiceServer(server, NewServer())
+ reflection.Register(server)
+
+ go server.Serve(listener)
+
+ return server
+}
+
+func newNamespaceClient(t *testing.T) (pb.NamespaceServiceClient, *grpc.ClientConn) {
+ connOpts := []grpc.DialOption{
+ grpc.WithInsecure(),
+ grpc.WithDialer(func(addr string, _ time.Duration) (net.Conn, error) {
+ return net.Dial("unix", addr)
+ }),
+ }
+ conn, err := grpc.Dial(serverSocketPath, connOpts...)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ return pb.NewNamespaceServiceClient(conn), conn
+}
diff --git a/internal/service/register.go b/internal/service/register.go
index 140bf5113..6c708215c 100644
--- a/internal/service/register.go
+++ b/internal/service/register.go
@@ -6,6 +6,7 @@ import (
"gitlab.com/gitlab-org/gitaly/internal/service/blob"
"gitlab.com/gitlab-org/gitaly/internal/service/commit"
"gitlab.com/gitlab-org/gitaly/internal/service/diff"
+ "gitlab.com/gitlab-org/gitaly/internal/service/namespace"
"gitlab.com/gitlab-org/gitaly/internal/service/notifications"
"gitlab.com/gitlab-org/gitaly/internal/service/ref"
"gitlab.com/gitlab-org/gitaly/internal/service/renameadapter"
@@ -45,6 +46,9 @@ func RegisterAll(grpcServer *grpc.Server, rubyServer *rubyserver.Server) {
repositoryService := repository.NewServer()
pb.RegisterRepositoryServiceServer(grpcServer, repositoryService)
+ namespaceService := namespace.NewServer()
+ pb.RegisterNamespaceServiceServer(grpcServer, namespaceService)
+
// Deprecated Services
pb.RegisterNotificationsServer(grpcServer, renameadapter.NewNotificationAdapter(notificationsService))
pb.RegisterRefServer(grpcServer, renameadapter.NewRefAdapter(refService))