Skip to content

Commit

Permalink
add grpc API for creating and dropping namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
mangalaman93 committed Jan 6, 2025
1 parent 4cb13f8 commit 144db7c
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 13 deletions.
12 changes: 12 additions & 0 deletions dgraphapi/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,18 @@ func (gc *GrpcClient) DropPredicate(pred string) error {
return gc.Alter(ctx, &api.Operation{DropAttr: pred})
}

func (gc *GrpcClient) CreateNamespace(namespace string) error {
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
return gc.Dgraph.CreateNamespace(ctx, namespace)
}

func (gc *GrpcClient) DropNamespace(namespace string) error {
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
defer cancel()
return gc.Dgraph.DropNamespace(ctx, namespace)
}

// Mutate performs a given mutation in a txn
func (gc *GrpcClient) Mutate(mu *api.Mutation) (*api.Response, error) {
txn := gc.NewTxn()
Expand Down
7 changes: 2 additions & 5 deletions dgraphtest/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package dgraphtest
import (
"log"
"os"
"path"
"path/filepath"
"runtime"
"strings"
Expand All @@ -45,12 +46,8 @@ func init() {
basePath := strings.ReplaceAll(thisFilePath, "/paths.go", "")
baseRepoDir = strings.ReplaceAll(basePath, "/dgraphtest", "")
binariesPath = filepath.Join(basePath, "binaries")
repoDir = path.Join(os.TempDir(), "temp-dgraph-repo")

var err error
repoDir, err = os.MkdirTemp("", "dgraph-repo")
if err != nil {
panic(err)
}
if err := ensureDgraphClone(); err != nil {
panic(err)
}
Expand Down
8 changes: 8 additions & 0 deletions edgraph/access_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ func (s *Server) authenticateLogin(ctx context.Context, request *api.LoginReques
return user, nil
}

if request.NamespaceName != "" {
ns, err := getNamespaceID(ctx, request.NamespaceName)
if err != nil {
return nil, err
}
request.Namespace = ns
}

// In case of login, we can't extract namespace from JWT because we have not yet given JWT
// to the user, so the login request should contain the namespace, which is then set to ctx.
ctx = x.AttachNamespace(ctx, request.Namespace)
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
95 changes: 95 additions & 0 deletions edgraph/namepsace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//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/dgraph-io/dgraph/v24/dgraphapi"
"github.com/dgraph-io/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
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"))
t.Logf("Creating namespace ns2")
require.NoError(t, client.CreateNamespace("ns2"))

// namespace 1
t.Logf("Logging into namespace ns1")
require.NoError(t, client.LoginIntoNamespaceWithName(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.LoginIntoNamespaceWithName(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")
client.LoginIntoNamespaceWithName(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.LoginIntoNamespaceWithName(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?
92 changes: 92 additions & 0 deletions edgraph/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,98 @@ func (s *Server) CheckVersion(ctx context.Context, c *api.Check) (v *api.Version
return v, nil
}

func (s *Server) CreateNamespace(ctx context.Context, in *api.CreateNamespaceRequest) (
*api.CreateNamespaceResponse, error) {

if err := AuthGuardianOfTheGalaxy(ctx); err != nil {
s := status.Convert(err)
return nil, status.Error(s.Code(),
"Non guardian of galaxy user cannot create namespace. "+s.Message())
}

if _, err := getNamespaceID(ctx, in.Name); err == nil {
return nil, errors.Errorf("namespace %q already exists", in.Name)
} else if !strings.Contains(err.Error(), "not found") {
return nil, err
}

password := "password"
if len(in.Password) != 0 {
password = in.Password
}

ns, err := (&Server{}).CreateNamespaceInternal(ctx, password)
if err != nil {
return nil, err
}

// If we crash at this point, it is possible that namespaces is created
// but no entry has been added to dgraph.namespace predicate. This is alright
// because we have not let the user know that namespace has been created.
// The user would have to try again and another namespace then would be
// assigned to the provided name here.

ictx := x.AttachNamespace(ctx, 0)
_, err = (&Server{}).QueryNoGrpc(ictx, &api.Request{
Mutations: []*api.Mutation{{
Set: []*api.NQuad{
{
Subject: "_:ns",
Predicate: "dgraph.namespace.name",
ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: in.Name}},
},
{
Subject: "_:ns",
Predicate: "dgraph.namespace.id",
ObjectValue: &api.Value{Val: &api.Value_IntVal{IntVal: int64(ns)}},

Check failure on line 1941 in edgraph/server.go

View workflow job for this annotation

GitHub Actions / lint

G115: integer overflow conversion uint64 -> int64 (gosec)
},
},
}},
CommitNow: true,
})
if err != nil {
return nil, err
}

return &api.CreateNamespaceResponse{Namespace: ns}, nil
}

func (s *Server) DropNamespace(ctx context.Context, in *api.DropNamespaceRequest) (
*api.DropNamespaceResponse, error) {

ns, err := getNamespaceID(ctx, in.Name)
if err != nil {
return nil, err
}

return &api.DropNamespaceResponse{}, (&Server{}).DeleteNamespace(ctx, ns)
}

func getNamespaceID(ctx context.Context, namespaceName string) (uint64, error) {
ctx = x.AttachNamespace(ctx, 0)

q := `{q(func: eq(dgraph.namespace.name, "` + namespaceName + `")) { dgraph.namespace.id }}`
resp, err := (&Server{}).doQuery(ctx, &Request{req: &api.Request{Query: q}, doAuth: NoAuthorize})
if err != nil {
return 0, err
}

var data struct {
Data []struct {
ID int64 `json:"dgraph.namespace.id"`
} `json:"q"`
}
if err := json.Unmarshal(resp.GetJson(), &data); err != nil {
return 0, err
}

if len(data.Data) == 0 {
return 0, errors.Errorf("namespace %q not found", namespaceName)
}

return uint64(data.Data[0].ID), nil

Check failure on line 1987 in edgraph/server.go

View workflow job for this annotation

GitHub Actions / lint

G115: integer overflow conversion int64 -> uint64 (gosec)
}

// -------------------------------------------------------------------------------------------------
// HELPER FUNCTIONS
// -------------------------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.1
github.com/blevesearch/bleve/v2 v2.4.4
github.com/dgraph-io/badger/v4 v4.5.0
github.com/dgraph-io/dgo/v240 v240.1.0
github.com/dgraph-io/dgo/v240 v240.1.1-0.20250106104422-238f3f6096e5
github.com/dgraph-io/gqlgen v0.13.2
github.com/dgraph-io/gqlparser/v2 v2.2.2
github.com/dgraph-io/graphql-transport-ws v0.0.0-20210511143556-2cef522f1f15
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.5.0 h1:TeJE3I1pIWLBjYhIYCA1+uxrjWEoJXImFBMEBVSm16g=
github.com/dgraph-io/badger/v4 v4.5.0/go.mod h1:ysgYmIeG8dS/E8kwxT7xHyc7MkmwNYLRoYnFbr7387A=
github.com/dgraph-io/dgo/v240 v240.1.0 h1:xd8z9kEXDWOAblaLJ2HLg2tXD6ngMQwq3ehLUS7GKNg=
github.com/dgraph-io/dgo/v240 v240.1.0/go.mod h1:r8WASETKfodzKqThSAhhTNIzcEMychArKKlZXQufWuA=
github.com/dgraph-io/dgo/v240 v240.1.1-0.20250106104422-238f3f6096e5 h1:A8mnedEBi5QgtTgCWN4IMeMo9c6d6ubxamLXNfMsnfg=
github.com/dgraph-io/dgo/v240 v240.1.1-0.20250106104422-238f3f6096e5/go.mod h1:W5xagB6LrVixmmq+hZH1SsLr/6Z0BS5j39pJNmqprAo=
github.com/dgraph-io/gqlgen v0.13.2 h1:TNhndk+eHKj5qE7BenKKSYdSIdOGhLqxR1rCiMso9KM=
github.com/dgraph-io/gqlgen v0.13.2/go.mod h1:iCOrOv9lngN7KAo+jMgvUPVDlYHdf7qDwsTkQby2Sis=
github.com/dgraph-io/gqlparser/v2 v2.1.1/go.mod h1:MYS4jppjyx8b9tuUtjV7jU1UFZK6P9fvO8TsIsQtRKU=
Expand Down
2 changes: 1 addition & 1 deletion graphql/admin/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func resolveAddNamespace(ctx context.Context, m schema.Mutation) (*resolve.Resol
req.Password = "password"
}
var ns uint64
if ns, err = (&edgraph.Server{}).CreateNamespace(ctx, req.Password); err != nil {
if ns, err = (&edgraph.Server{}).CreateNamespaceInternal(ctx, req.Password); err != nil {
return resolve.EmptyResult(m, err), false
}
return resolve.DataResult(
Expand Down
15 changes: 14 additions & 1 deletion schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,20 @@ func initialSchemaInternal(namespace uint64, all bool) []*pb.SchemaUpdate {
ValueType: pb.Posting_STRING,
Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"sha256"},
})
}, &pb.SchemaUpdate{
Predicate: "dgraph.namespace.name",
ValueType: pb.Posting_STRING,
Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"exact"},
Unique: true,
}, &pb.SchemaUpdate{
Predicate: "dgraph.namespace.id",
ValueType: pb.Posting_INT,
Directive: pb.SchemaUpdate_INDEX,
Tokenizer: []string{"int"},
Unique: true,
},
)

if all || x.WorkerConfig.AclEnabled {
// propose the schema update for acl predicates
Expand Down

0 comments on commit 144db7c

Please sign in to comment.