Skip to content

Commit

Permalink
add grpc API for creating/deleting/rename/listing namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
mangalaman93 committed Jan 31, 2025
1 parent ab96596 commit f6748ab
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 12 deletions.
77 changes: 77 additions & 0 deletions dgraphapi/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ import (
"fmt"
"io"
"log"
"math/rand"
"net/http"
"os/exec"
"strings"
"time"

"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"

"github.com/dgraph-io/dgo/v240"
"github.com/dgraph-io/dgo/v240/protos/api"
Expand All @@ -55,6 +59,10 @@ type Cluster interface {

type GrpcClient struct {
*dgo.Dgraph

// These are neede for APIs that we have not exposed yet in Dgo
Conns []*grpc.ClientConn
jwt api.Jwt
}

// HttpToken stores credentials for making HTTP request
Expand Down Expand Up @@ -702,6 +710,75 @@ func (hc *HTTPClient) PostDqlQuery(query string) ([]byte, error) {
return DoReq(req)
}

func (gc *GrpcClient) getContext() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
if len(gc.jwt.AccessJwt) > 0 {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
// no metadata key is in the context, add one
md = metadata.New(nil)
}
md.Set("accessJwt", gc.jwt.AccessJwt)
return metadata.NewOutgoingContext(ctx, md), cancel
}

return ctx, cancel
}

func (gc *GrpcClient) CreateNamespace(name, password string) error {
ctx, cancel := gc.getContext()
defer cancel()

client := api.NewDgraphClient(gc.Conns[rand.Intn(len(gc.Conns))])
req := &api.CreateNamespaceRequest{NsName: name, Password: password}
_, err := client.CreateNamespace(ctx, req)
return err
}

func (gc *GrpcClient) DropNamespace(name string) error {
ctx, cancel := gc.getContext()
defer cancel()

client := api.NewDgraphClient(gc.Conns[rand.Intn(len(gc.Conns))])
req := &api.DropNamespaceRequest{NsName: name}
_, err := client.DropNamespace(ctx, req)
return err
}

func (gc *GrpcClient) RenameNamespace(oldName, newName string) error {
ctx, cancel := gc.getContext()
defer cancel()

client := api.NewDgraphClient(gc.Conns[rand.Intn(len(gc.Conns))])
req := &api.RenameNamespaceRequest{FromNs: oldName, ToNs: newName}
_, err := client.RenameNamespace(ctx, req)
return err
}

func (gc *GrpcClient) ListNamespace() (map[string]*api.Namespace, error) {
ctx, cancel := gc.getContext()
defer cancel()

client := api.NewDgraphClient(gc.Conns[rand.Intn(len(gc.Conns))])
resp, err := client.ListNamespaces(ctx, &api.ListNamespacesRequest{})
if err != nil {
return nil, err
}
return resp.NsList, nil
}

func (gc *GrpcClient) LoginUsingNamespaceName(ctx context.Context,
userid string, password string, namespace string) error {

client := api.NewDgraphClient(gc.Conns[rand.Intn(len(gc.Conns))])
req := &api.LoginRequest{Userid: userid, Password: password, NsName: namespace}
resp, err := client.Login(ctx, req)
if err != nil {
return err
}
return proto.Unmarshal(resp.Json, &gc.jwt)
}

func (hc *HTTPClient) Mutate(mutation string, commitNow bool) ([]byte, error) {
url := hc.dqlMutateUrl
if commitNow {
Expand Down
8 changes: 6 additions & 2 deletions dgraphtest/compose_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ package dgraphtest

import (
"github.com/dgraph-io/dgo/v240"
"github.com/dgraph-io/dgo/v240/protos/api"
"github.com/hypermodeinc/dgraph/v24/dgraphapi"
"github.com/hypermodeinc/dgraph/v24/testutil"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)

type ComposeCluster struct{}
Expand All @@ -29,12 +32,13 @@ func NewComposeCluster() *ComposeCluster {
}

func (c *ComposeCluster) Client() (*dgraphapi.GrpcClient, func(), error) {
client, err := testutil.DgraphClient(testutil.SockAddr)
conn, err := grpc.NewClient(testutil.SockAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, nil, err
}

return &dgraphapi.GrpcClient{Dgraph: client}, func() {}, nil
dg := dgo.NewDgraphClient(api.NewDgraphClient(conn))
return &dgraphapi.GrpcClient{Dgraph: dg, Conns: []*grpc.ClientConn{conn}}, func() {}, nil
}

// HTTPClient creates an HTTP client
Expand Down
2 changes: 1 addition & 1 deletion dgraphtest/dcloud_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (c *DCloudCluster) Client() (*dgraphapi.GrpcClient, func(), error) {
}
}
client := dgo.NewDgraphClient(api.NewDgraphClient(conn))
return &dgraphapi.GrpcClient{Dgraph: client}, cleanup, nil
return &dgraphapi.GrpcClient{Dgraph: client, Conns: conns}, cleanup, nil
}

func (c *DCloudCluster) HTTPClient() (*dgraphapi.HTTPClient, error) {
Expand Down
2 changes: 1 addition & 1 deletion dgraphtest/local_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ func (c *LocalCluster) Client() (*dgraphapi.GrpcClient, func(), error) {
}
}
}
return &dgraphapi.GrpcClient{Dgraph: client}, cleanup, nil
return &dgraphapi.GrpcClient{Dgraph: client, Conns: conns}, cleanup, nil
}

func (c *LocalCluster) AlphaClient(id int) (*dgraphapi.GrpcClient, func(), error) {
Expand Down
2 changes: 1 addition & 1 deletion edgraph/multi_tenancy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type ResetPasswordInput struct {
Namespace uint64
}

func (s *Server) CreateNamespace(ctx context.Context, passwd string) (uint64, error) {
func (s *Server) CreateNamespaceInternal(ctx context.Context, passwd string) (uint64, error) {
return 0, nil
}

Expand Down
4 changes: 2 additions & 2 deletions edgraph/multi_tenancy_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ func (s *Server) ResetPassword(ctx context.Context, inp *ResetPasswordInput) err
return nil
}

// CreateNamespace creates a new namespace. Only guardian of galaxy is authorized to do so.
// CreateNamespaceInternal creates a new namespace. Only guardian of galaxy is authorized to do so.
// Authorization is handled by middlewares.
func (s *Server) CreateNamespace(ctx context.Context, passwd string) (uint64, error) {
func (s *Server) CreateNamespaceInternal(ctx context.Context, passwd string) (uint64, error) {
glog.V(2).Info("Got create namespace request.")

num := &pb.Num{Val: 1, Type: pb.Num_NS_ID}
Expand Down
115 changes: 115 additions & 0 deletions edgraph/namepsace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//go:build integration

/*
* Copyright 2017-2023 Dgraph Labs, Inc. and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package edgraph

import (
"context"
"testing"

"github.com/dgraph-io/dgo/v240/protos/api"
"github.com/hypermodeinc/dgraph/v24/dgraphapi"
"github.com/hypermodeinc/dgraph/v24/dgraphtest"
"github.com/stretchr/testify/require"
)

func TestCreateNamespace(t *testing.T) {
dc := dgraphtest.NewComposeCluster()
client, cleanup, err := dc.Client()
require.NoError(t, err)
defer cleanup()

// Drop all data
require.NoError(t, client.DropAll())

// Create two namespaces
require.NoError(t, client.LoginIntoNamespace(context.Background(),
dgraphapi.DefaultUser, dgraphapi.DefaultPassword, 0))
t.Logf("Creating namespace ns1")
require.NoError(t, client.CreateNamespace("ns1", dgraphapi.DefaultPassword))
t.Logf("Creating namespace ns2")
require.NoError(t, client.CreateNamespace("ns2", dgraphapi.DefaultPassword))

// namespace 1
t.Logf("Logging into namespace ns1")
require.NoError(t, client.LoginUsingNamespaceName(context.Background(),
dgraphapi.DefaultUser, dgraphapi.DefaultPassword, "ns1"))

t.Logf("Setting up schema in namespace ns1")
require.NoError(t, client.SetupSchema(`name: string @index(exact) .`))

t.Logf("Mutating data in namespace ns1")
_, err = client.Mutate(&api.Mutation{
SetNquads: []byte(`_:a <name> "Alice" .`),
CommitNow: true,
})
require.NoError(t, err)

t.Logf("Querying data in namespace ns1")
resp, err := client.Query(`{ q(func: has(name)) { name } }`)
require.NoError(t, err)
require.JSONEq(t, `{"q":[{"name":"Alice"}]}`, string(resp.GetJson()))

// setup schema in namespace 2
t.Logf("Logging into namespace ns2")
require.NoError(t, client.LoginUsingNamespaceName(context.Background(),
dgraphapi.DefaultUser, dgraphapi.DefaultPassword, "ns2"))

t.Logf("Setting up schema in namespace ns2")
require.NoError(t, client.SetupSchema(`name: string @index(exact) .`))

t.Logf("Mutating data in namespace ns2")
require.NoError(t, client.LoginUsingNamespaceName(context.Background(),
dgraphapi.DefaultUser, dgraphapi.DefaultPassword, "ns2"))
_, err = client.Mutate(&api.Mutation{
SetNquads: []byte(`_:a <name> "Bob" .`),
CommitNow: true,
})
require.NoError(t, err)

t.Logf("Querying data in namespace ns2")
require.NoError(t, client.LoginUsingNamespaceName(context.Background(),
dgraphapi.DefaultUser, dgraphapi.DefaultPassword, "ns2"))
resp, err = client.Query(`{ q(func: has(name)) { name } }`)
require.NoError(t, err)
require.JSONEq(t, `{"q":[{"name":"Bob"}]}`, string(resp.GetJson()))
}

// A test where we are creating a lots of namespaces constantly and in parallel

// What if I create the same namespace again?

// wrong auth

func TestCreateSameNamespace(t *testing.T) {
dc := dgraphtest.NewComposeCluster()
client, cleanup, err := dc.Client()
require.NoError(t, err)
defer cleanup()

// Drop all data
require.NoError(t, client.DropAll())

// Create two namespaces
require.NoError(t, client.LoginIntoNamespace(context.Background(),
dgraphapi.DefaultUser, dgraphapi.DefaultPassword, 0))
t.Logf("Creating namespace ns4")
require.NoError(t, client.CreateNamespace("ns4", dgraphapi.DefaultPassword))
t.Logf("Creating namespace ns4 again")
require.ErrorContains(t, client.CreateNamespace("ns4", dgraphapi.DefaultPassword), `namespace "ns4" already exists`)
}
Loading

0 comments on commit f6748ab

Please sign in to comment.