Skip to content

Commit 3c42449

Browse files
authored
feat(multi-tenancy): Make GraphQL work with multiple namespaces (#7400)
Add ability to set a GraphQL schema per namespace and do queries, mutations, and subscriptions specific to the namespace. Some CORS tests and ClosedByDefault tests need to be fixed which will happen later.
1 parent cd81510 commit 3c42449

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+958
-618
lines changed

dgraph/cmd/alpha/http.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,9 @@ func resolveWithAdminServer(gqlReq *schema.Request, r *http.Request,
642642
ctx = x.AttachAccessJwt(ctx, r)
643643
ctx = x.AttachRemoteIP(ctx, r)
644644
ctx = x.AttachAuthToken(ctx, r)
645+
ctx = x.AttachJWTNamespace(ctx)
645646

646-
return adminServer.Resolve(ctx, gqlReq)
647+
return adminServer.ResolveWithNs(ctx, x.GalaxyNamespace, gqlReq)
647648
}
648649

649650
func writeSuccessResponse(w http.ResponseWriter, r *http.Request) {

dgraph/cmd/alpha/run.go

+35-12
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"net/url"
3131
"os"
3232
"os/signal"
33+
"strconv"
3334
"strings"
3435
"sync/atomic"
3536
"syscall"
@@ -462,14 +463,23 @@ func setupServer(closer *z.Closer) {
462463
// Implementation for server exit:
463464
// The global epoch is set to maxUint64 while exiting the server.
464465
// By using this information polling goroutine terminates the subscription.
465-
globalEpoch := uint64(0)
466+
globalEpoch := make(map[uint64]*uint64)
467+
e := new(uint64)
468+
atomic.StoreUint64(e, 0)
469+
globalEpoch[x.GalaxyNamespace] = e
466470
var mainServer web.IServeGraphQL
467471
var gqlHealthStore *admin.GraphQLHealthStore
468472
// Do not use := notation here because adminServer is a global variable.
469-
mainServer, adminServer, gqlHealthStore = admin.NewServers(introspection, &globalEpoch, closer)
470-
baseMux.Handle("/graphql", mainServer.HTTPHandler())
471-
baseMux.HandleFunc("/probe/graphql", func(w http.ResponseWriter,
472-
r *http.Request) {
473+
mainServer, adminServer, gqlHealthStore = admin.NewServers(introspection,
474+
globalEpoch, closer)
475+
baseMux.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
476+
namespace := x.ExtractNamespaceHTTP(r)
477+
r.Header.Set("resolver", strconv.FormatUint(namespace, 10))
478+
admin.LazyLoadSchema(namespace)
479+
mainServer.HTTPHandler().ServeHTTP(w, r)
480+
})
481+
482+
baseMux.HandleFunc("/probe/graphql", func(w http.ResponseWriter, r *http.Request) {
473483
healthStatus := gqlHealthStore.GetHealth()
474484
httpStatusCode := http.StatusOK
475485
if !healthStatus.Healthy {
@@ -478,14 +488,25 @@ func setupServer(closer *z.Closer) {
478488
w.Header().Set("Content-Type", "application/json")
479489
x.AddCorsHeaders(w)
480490
w.WriteHeader(httpStatusCode)
491+
e = globalEpoch[x.ExtractNamespaceHTTP(r)]
492+
var counter uint64
493+
if e != nil {
494+
counter = atomic.LoadUint64(e)
495+
}
481496
x.Check2(w.Write([]byte(fmt.Sprintf(`{"status":"%s","schemaUpdateCounter":%d}`,
482-
healthStatus.StatusMsg, atomic.LoadUint64(&globalEpoch)))))
497+
healthStatus.StatusMsg, counter))))
498+
})
499+
baseMux.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
500+
r.Header.Set("resolver", "0")
501+
// We don't need to load the schema for all the admin operations.
502+
// Only a few like getUser, queryGroup require this. So, this can be optimized.
503+
admin.LazyLoadSchema(x.ExtractNamespaceHTTP(r))
504+
allowedMethodsHandler(allowedMethods{
505+
http.MethodGet: true,
506+
http.MethodPost: true,
507+
http.MethodOptions: true,
508+
}, adminAuthHandler(adminServer.HTTPHandler())).ServeHTTP(w, r)
483509
})
484-
baseMux.Handle("/admin", allowedMethodsHandler(allowedMethods{
485-
http.MethodGet: true,
486-
http.MethodPost: true,
487-
http.MethodOptions: true,
488-
}, adminAuthHandler(adminServer.HTTPHandler())))
489510

490511
baseMux.Handle("/admin/schema", adminAuthHandler(http.HandlerFunc(func(
491512
w http.ResponseWriter,
@@ -559,7 +580,9 @@ func setupServer(closer *z.Closer) {
559580
defer admin.ServerCloser.Done()
560581

561582
<-admin.ServerCloser.HasBeenClosed()
562-
atomic.StoreUint64(&globalEpoch, math.MaxUint64)
583+
// TODO - Verify why do we do this and does it have to be done for all namespaces.
584+
e = globalEpoch[x.GalaxyNamespace]
585+
atomic.StoreUint64(e, math.MaxUint64)
563586

564587
// Stops grpc/http servers; Already accepted connections are not closed.
565588
if err := grpcListener.Close(); err != nil {

edgraph/access.go

+12
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,15 @@ func AuthorizeGuardians(ctx context.Context) error {
7575
// always allow access
7676
return nil
7777
}
78+
79+
func AuthGuardianOfTheGalaxy(ctx context.Context) error {
80+
return nil
81+
}
82+
83+
func upsertGuardian(ctx context.Context) error {
84+
return nil
85+
}
86+
87+
func upsertGroot(ctx context.Context) error {
88+
return nil
89+
}

edgraph/access_ee.go

+3
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,9 @@ func authorizeSchemaQuery(ctx context.Context, er *query.ExecutionResult) error
10401040
}
10411041

10421042
func AuthGuardianOfTheGalaxy(ctx context.Context) error {
1043+
if !x.WorkerConfig.AclEnabled {
1044+
return nil
1045+
}
10431046
ns, err := x.ExtractJWTNamespace(ctx)
10441047
if err != nil {
10451048
return errors.Wrap(err, "Authorize guradian of the galaxy, extracting jwt token, error:")

edgraph/acl_cache.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func (cache *aclCache) update(ns uint64, groups []acl.Group) {
109109

110110
func (cache *aclCache) authorizePredicate(groups []string, predicate string,
111111
operation *acl.Operation) error {
112-
if x.IsAclPredicate(predicate) {
112+
if x.IsAclPredicate(x.ParseAttr(predicate)) {
113113
return errors.Errorf("only groot is allowed to access the ACL predicate: %s", predicate)
114114
}
115115

edgraph/acl_cache_test.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"testing"
1717

1818
"github.com/dgraph-io/dgraph/ee/acl"
19+
"github.com/dgraph-io/dgraph/x"
1920
"github.com/stretchr/testify/require"
2021
)
2122

@@ -26,13 +27,14 @@ func TestAclCache(t *testing.T) {
2627

2728
var emptyGroups []string
2829
group := "dev"
29-
predicate := "friend"
30+
predicate := x.GalaxyAttr("friend")
3031
require.Error(t, aclCachePtr.authorizePredicate(emptyGroups, predicate, acl.Read),
3132
"the anonymous user should not have access when the acl cache is empty")
3233

3334
acls := []acl.Acl{
3435
{
35-
Predicate: predicate,
36+
// update operation on acl cache needs predicate without namespace.
37+
Predicate: x.ParseAttr(predicate),
3638
Perm: 4,
3739
},
3840
}
@@ -42,15 +44,15 @@ func TestAclCache(t *testing.T) {
4244
Rules: acls,
4345
},
4446
}
45-
aclCachePtr.update(groups)
47+
aclCachePtr.update(x.GalaxyNamespace, groups)
4648
// after a rule is defined, the anonymous user should no longer have access
4749
require.Error(t, aclCachePtr.authorizePredicate(emptyGroups, predicate, acl.Read),
4850
"the anonymous user should not have access when the predicate has acl defined")
4951
require.NoError(t, aclCachePtr.authorizePredicate([]string{group}, predicate, acl.Read),
5052
"the user with group authorized should have access")
5153

5254
// update the cache with empty acl list in order to clear the cache
53-
aclCachePtr.update([]acl.Group{})
55+
aclCachePtr.update(x.GalaxyNamespace, []acl.Group{})
5456
// the anonymous user should have access again
5557
require.Error(t, aclCachePtr.authorizePredicate(emptyGroups, predicate, acl.Read),
5658
"the anonymous user should not have access when the acl cache is empty")

edgraph/graphql.go

-34
Original file line numberDiff line numberDiff line change
@@ -179,40 +179,6 @@ func GetCorsOrigins(ctx context.Context) (string, []string, error) {
179179
return corsLast.Uid, corsLast.DgraphCors, nil
180180
}
181181

182-
// UpdateSchemaHistory updates graphql schema history.
183-
func UpdateSchemaHistory(ctx context.Context, schema string) error {
184-
req := &Request{
185-
req: &api.Request{
186-
Mutations: []*api.Mutation{
187-
{
188-
Set: []*api.NQuad{
189-
{
190-
Subject: "_:a",
191-
Predicate: "dgraph.graphql.schema_history",
192-
ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: schema}},
193-
},
194-
{
195-
Subject: "_:a",
196-
Predicate: "dgraph.type",
197-
ObjectValue: &api.Value{Val: &api.Value_StrVal{
198-
StrVal: "dgraph.graphql.history"}},
199-
},
200-
},
201-
SetNquads: []byte(fmt.Sprintf(`_:a <dgraph.graphql.schema_created_at> "%s" .`,
202-
time.Now().Format(time.RFC3339))),
203-
},
204-
},
205-
CommitNow: true,
206-
},
207-
doAuth: NoAuthorize,
208-
}
209-
//TODO(Pawan): Make this use right namespace.
210-
ctx = context.WithValue(ctx, IsGraphql, true)
211-
ctx = x.AttachNamespace(ctx, x.GalaxyNamespace)
212-
_, err := (&Server{}).doQuery(ctx, req)
213-
return err
214-
}
215-
216182
// ProcessPersistedQuery stores and retrieves persisted queries by following waterfall logic:
217183
// 1. If sha256Hash is not provided process queries without persisting
218184
// 2. If sha256Hash is provided try retrieving persisted queries

edgraph/server.go

+4-7
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (s *Server) DeleteNamespace(ctx context.Context, namespace uint64) error {
163163
glog.Info("Deleting namespace", namespace)
164164
ctx = x.AttachJWTNamespace(ctx)
165165
if err := AuthGuardianOfTheGalaxy(ctx); err != nil {
166-
return errors.Wrapf(err, "Creating namespace, got error: ")
166+
return errors.Wrapf(err, "Deleting namespace, got error: ")
167167
}
168168
// TODO(Ahsan): We have to ban the pstore for all the groups.
169169
ps := worker.State.Pstore
@@ -203,12 +203,9 @@ func PeriodicallyPostTelemetry() {
203203

204204
// GetGQLSchema queries for the GraphQL schema node, and returns the uid and the GraphQL schema.
205205
// If multiple schema nodes were found, it returns an error.
206-
func GetGQLSchema() (uid, graphQLSchema string, err error) {
206+
func GetGQLSchema(namespace uint64) (uid, graphQLSchema string, err error) {
207207
ctx := context.WithValue(context.Background(), Authorize, false)
208-
//TODO(Ahsan): There should be a way to getGQLSchema for all the namespaces and reinsert them
209-
// after dropAll. Need to think about what should be the behaviour of drop operations.
210-
ctx = x.AttachNamespace(ctx, x.GalaxyNamespace)
211-
208+
ctx = x.AttachNamespace(ctx, namespace)
212209
resp, err := (&Server{}).Query(ctx,
213210
&api.Request{
214211
Query: `
@@ -457,7 +454,7 @@ func (s *Server) Alter(ctx context.Context, op *api.Operation) (*api.Payload, er
457454
}
458455

459456
// query the GraphQL schema and keep it in memory, so it can be inserted again
460-
_, graphQLSchema, err := GetGQLSchema()
457+
_, graphQLSchema, err := GetGQLSchema(namespace)
461458
if err != nil {
462459
return empty, err
463460
}

go.mod

-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ module github.com/dgraph-io/dgraph
22

33
go 1.12
44

5-
// replace github.com/dgraph-io/badger/v2 => /home/mrjn/go/src/github.com/dgraph-io/badger
6-
// replace github.com/dgraph-io/ristretto => /home/mrjn/go/src/github.com/dgraph-io/ristretto
7-
85
require (
96
contrib.go.opencensus.io/exporter/jaeger v0.1.0
107
contrib.go.opencensus.io/exporter/prometheus v0.1.0

go.sum

-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ github.com/dgraph-io/badger v1.6.0 h1:DshxFxZWXUcO0xX476VJC07Xsr6ZCBVRHKZ93Oh7Ev
117117
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
118118
github.com/dgraph-io/badger/v3 v3.0.0-20210208122220-162b5787192b h1:nD2CjktZ78EClTWWwFhMvVuBxYunKEFLClNkFHGr+aU=
119119
github.com/dgraph-io/badger/v3 v3.0.0-20210208122220-162b5787192b/go.mod h1:ag1DYFcc5xVWtMbbwnl9ESgk1dE2ukWO4hdvzENQnAw=
120-
github.com/dgraph-io/dgo/v200 v200.0.0-20210208072308-4dd991b9b20e h1:3K0Wg/IMUTp4vyCRyD5SlmCYbmVxAz5o6eJvSiS72Gs=
121-
github.com/dgraph-io/dgo/v200 v200.0.0-20210208072308-4dd991b9b20e/go.mod h1:ky1IOcEAlOxmk89KxXGECgRAEkdJrNHVymvCmixaVuM=
122120
github.com/dgraph-io/dgo/v200 v200.0.0-20210208110130-c589adec3d8f h1:XZRsTllGQWUu0SpAb8mGW9motF0KmD7f6UEKa7y2grE=
123121
github.com/dgraph-io/dgo/v200 v200.0.0-20210208110130-c589adec3d8f/go.mod h1:ky1IOcEAlOxmk89KxXGECgRAEkdJrNHVymvCmixaVuM=
124122
github.com/dgraph-io/gqlgen v0.13.2 h1:TNhndk+eHKj5qE7BenKKSYdSIdOGhLqxR1rCiMso9KM=

0 commit comments

Comments
 (0)