Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(GraphQL): Update GraphQL schema only on Group-1 leader (#5739) #5829

Merged
merged 1 commit into from
Jul 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 113 additions & 68 deletions edgraph/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,45 +127,140 @@ func PeriodicallyPostTelemetry() {
}
}

// Alter handles requests to change the schema or remove parts or all of the data.
func (s *Server) Alter(ctx context.Context, op *api.Operation) (*api.Payload, error) {
ctx, span := otrace.StartSpan(ctx, "Server.Alter")
defer span.End()
span.Annotatef(nil, "Alter operation: %+v", op)
// UpdateGQLSchema updates the GraphQL and Dgraph schemas using the given inputs.
// It first validates and parses the dgraphSchema given in input. If that fails,
// it returns an error. All this is done on the alpha on which the update request is received.
// Then it sends an update request to the worker, which is executed only on Group-1 leader.
func UpdateGQLSchema(ctx context.Context, gqlSchema,
dgraphSchema string) (*pb.UpdateGraphQLSchemaResponse, error) {
var err error
parsedDgraphSchema := &schema.ParsedSchema{}

// Always print out Alter operations because they are important and rare.
glog.Infof("Received ALTER op: %+v", op)
// The schema could be empty if it only has custom types/queries/mutations.
if dgraphSchema != "" {
op := &api.Operation{Schema: dgraphSchema}
if err = validateAlterOperation(ctx, op); err != nil {
return nil, err
}
if parsedDgraphSchema, err = parseSchemaFromAlterOperation(op); err != nil {
return nil, err
}
}

return worker.UpdateGQLSchemaOverNetwork(ctx, &pb.UpdateGraphQLSchemaRequest{
StartTs: worker.State.GetTimestamp(false),
GraphqlSchema: gqlSchema,
DgraphPreds: parsedDgraphSchema.Preds,
DgraphTypes: parsedDgraphSchema.Types,
})
}

// validateAlterOperation validates the given operation for alter.
func validateAlterOperation(ctx context.Context, op *api.Operation) error {
// The following code block checks if the operation should run or not.
if op.Schema == "" && op.DropAttr == "" && !op.DropAll && op.DropOp == api.Operation_NONE {
// Must have at least one field set. This helps users if they attempt
// to set a field but use the wrong name (could be decoded from JSON).
return nil, errors.Errorf("Operation must have at least one field set")
return errors.Errorf("Operation must have at least one field set")
}
empty := &api.Payload{}
if err := x.HealthCheck(); err != nil {
return empty, err
return err
}

if isDropAll(op) && op.DropOp == api.Operation_DATA {
return nil, errors.Errorf("Only one of DropAll and DropData can be true")
return errors.Errorf("Only one of DropAll and DropData can be true")
}

if !isMutationAllowed(ctx) {
return nil, errors.Errorf("No mutations allowed by server.")
return errors.Errorf("No mutations allowed by server.")
}
if err := isAlterAllowed(ctx); err != nil {
glog.Warningf("Alter denied with error: %v\n", err)
return nil, err
return err
}

if err := authorizeAlter(ctx, op); err != nil {
glog.Warningf("Alter denied with error: %v\n", err)
return err
}

return nil
}

// parseSchemaFromAlterOperation parses the string schema given in input operation to a Go
// struct, and performs some checks to make sure that the schema is valid.
func parseSchemaFromAlterOperation(op *api.Operation) (*schema.ParsedSchema, error) {
// If a background task is already running, we should reject all the new alter requests.
if schema.State().IndexingInProgress() {
return nil, errIndexingInProgress
}

result, err := schema.Parse(op.Schema)
if err != nil {
return nil, err
}

for _, update := range result.Preds {
// Pre-defined predicates cannot be altered but let the update go through
// if the update is equal to the existing one.
if schema.IsPreDefPredChanged(update) {
return nil, errors.Errorf("predicate %s is pre-defined and is not allowed to be"+
" modified", update.Predicate)
}

if err := validatePredName(update.Predicate); err != nil {
return nil, err
}
// Users are not allowed to create a predicate under the reserved `dgraph.` namespace. But,
// there are pre-defined predicates (subset of reserved predicates), and for them we allow
// the schema update to go through if the update is equal to the existing one.
// So, here we check if the predicate is reserved but not pre-defined to block users from
// creating predicates in reserved namespace.
if x.IsReservedPredicate(update.Predicate) && !x.IsPreDefinedPredicate(update.Predicate) {
return nil, errors.Errorf("Can't alter predicate `%s` as it is prefixed with `dgraph.`"+
" which is reserved as the namespace for dgraph's internal types/predicates.",
update.Predicate)
}
}

for _, typ := range result.Types {
// Pre-defined types cannot be altered but let the update go through
// if the update is equal to the existing one.
if schema.IsPreDefTypeChanged(typ) {
return nil, errors.Errorf("type %s is pre-defined and is not allowed to be modified",
typ.TypeName)
}

// Users are not allowed to create types in reserved namespace. But, there are pre-defined
// types for which the update should go through if the update is equal to the existing one.
if x.IsReservedType(typ.TypeName) && !x.IsPreDefinedType(typ.TypeName) {
return nil, errors.Errorf("Can't alter type `%s` as it is prefixed with `dgraph.` "+
"which is reserved as the namespace for dgraph's internal types/predicates.",
typ.TypeName)
}
}

return result, nil
}

// Alter handles requests to change the schema or remove parts or all of the data.
func (s *Server) Alter(ctx context.Context, op *api.Operation) (*api.Payload, error) {
ctx, span := otrace.StartSpan(ctx, "Server.Alter")
defer span.End()
span.Annotatef(nil, "Alter operation: %+v", op)

// Always print out Alter operations because they are important and rare.
glog.Infof("Received ALTER op: %+v", op)

// check if the operation is valid
if err := validateAlterOperation(ctx, op); err != nil {
return nil, err
}

defer glog.Infof("ALTER op: %+v done", op)

empty := &api.Payload{}

// StartTs is not needed if the predicate to be dropped lies on this server but is required
// if it lies on some other machine. Let's get it for safety.
m := &pb.Mutations{StartTs: worker.State.GetTimestamp(false)}
Expand Down Expand Up @@ -246,54 +341,9 @@ func (s *Server) Alter(ctx context.Context, op *api.Operation) (*api.Payload, er
return empty, err
}

// If a background task is already running, we should reject all the new alter requests.
if schema.State().IndexingInProgress() {
return nil, errIndexingInProgress
}

result, err := schema.Parse(op.Schema)
result, err := parseSchemaFromAlterOperation(op)
if err != nil {
return empty, err
}

for _, update := range result.Preds {
// Pre-defined predicates cannot be altered but let the update go through
// if the update is equal to the existing one.
if schema.IsPreDefPredChanged(update) {
return nil, errors.Errorf("predicate %s is pre-defined and is not allowed to be"+
" modified", update.Predicate)
}

if err := validatePredName(update.Predicate); err != nil {
return nil, err
}
// Users are not allowed to create a predicate under the reserved `dgraph.` namespace. But,
// there are pre-defined predicates (subset of reserved predicates), and for them we allow
// the schema update to go through if the update is equal to the existing one.
// So, here we check if the predicate is reserved but not pre-defined to block users from
// creating predicates in reserved namespace.
if x.IsReservedPredicate(update.Predicate) && !x.IsPreDefinedPredicate(update.Predicate) {
return nil, errors.Errorf("Can't alter predicate `%s` as it is prefixed with `dgraph.`"+
" which is reserved as the namespace for dgraph's internal types/predicates.",
update.Predicate)
}
}

for _, typ := range result.Types {
// Pre-defined types cannot be altered but let the update go through
// if the update is equal to the existing one.
if schema.IsPreDefTypeChanged(typ) {
return nil, errors.Errorf("type %s is pre-defined and is not allowed to be modified",
typ.TypeName)
}

// Users are not allowed to create types in reserved namespace. But, there are pre-defined
// types for which the update should go through if the update is equal to the existing one.
if x.IsReservedType(typ.TypeName) && !x.IsPreDefinedType(typ.TypeName) {
return nil, errors.Errorf("Can't alter type `%s` as it is prefixed with `dgraph.` "+
"which is reserved as the namespace for dgraph's internal types/predicates.",
typ.TypeName)
}
return nil, err
}

glog.Infof("Got schema: %+v\n", result)
Expand All @@ -306,15 +356,10 @@ func (s *Server) Alter(ctx context.Context, op *api.Operation) (*api.Payload, er
}

// wait for indexing to complete or context to be canceled.
for !op.RunInBackground {
if ctx.Err() != nil {
return empty, ctx.Err()
}
if !schema.State().IndexingInProgress() {
break
}
time.Sleep(time.Second * 2)
if err = worker.WaitForIndexingOrCtxError(ctx, !op.RunInBackground); err != nil {
return empty, err
}

return empty, nil
}

Expand Down
Loading