forked from storj/storj
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
V3-1152 Node bootstrap web backend (storj#1327)
* V3-1152 Node bootstrap
- Loading branch information
Showing
14 changed files
with
447 additions
and
28 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
bootstrap/bootstrapweb/bootstrapserver/bootstrapql/query.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright (C) 2019 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package bootstrapql | ||
|
||
import ( | ||
"github.com/graphql-go/graphql" | ||
|
||
"storj.io/storj/bootstrap/bootstrapweb" | ||
"storj.io/storj/pkg/storj" | ||
) | ||
|
||
const ( | ||
// Query is immutable graphql request | ||
Query = "query" | ||
// IsNodeUpQuery is a query name for checking if node is up | ||
IsNodeUpQuery = "isNodeUp" | ||
|
||
// NodeID is a field name for nodeID | ||
NodeID = "nodeID" | ||
) | ||
|
||
// rootQuery creates query for graphql | ||
func rootQuery(service *bootstrapweb.Service, types Types) *graphql.Object { | ||
return graphql.NewObject(graphql.ObjectConfig{ | ||
Name: Query, | ||
Fields: graphql.Fields{ | ||
IsNodeUpQuery: &graphql.Field{ | ||
Type: graphql.Boolean, | ||
Args: graphql.FieldConfigArgument{ | ||
NodeID: &graphql.ArgumentConfig{ | ||
Type: graphql.String, | ||
}, | ||
}, | ||
Resolve: func(p graphql.ResolveParams) (interface{}, error) { | ||
inputNodeID, _ := p.Args[NodeID].(string) | ||
|
||
nodeID, err := storj.NodeIDFromString(inputNodeID) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return service.IsNodeAvailable(p.Context, nodeID) | ||
}, | ||
}, | ||
}, | ||
}) | ||
} |
29 changes: 29 additions & 0 deletions
29
bootstrap/bootstrapweb/bootstrapserver/bootstrapql/schema.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright (C) 2018 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package bootstrapql | ||
|
||
import ( | ||
"github.com/graphql-go/graphql" | ||
|
||
"storj.io/storj/bootstrap/bootstrapweb" | ||
"storj.io/storj/internal/storjql" | ||
) | ||
|
||
// CreateSchema creates a schema for bootstrap graphql api | ||
func CreateSchema(service *bootstrapweb.Service) (schema graphql.Schema, err error) { | ||
storjql.WithLock(func() { | ||
creator := TypeCreator{} | ||
|
||
err = creator.Create(service) | ||
if err != nil { | ||
return | ||
} | ||
|
||
schema, err = graphql.NewSchema(graphql.SchemaConfig{ | ||
Query: creator.RootQuery(), | ||
}) | ||
}) | ||
|
||
return schema, err | ||
} |
38 changes: 38 additions & 0 deletions
38
bootstrap/bootstrapweb/bootstrapserver/bootstrapql/typecreator.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Copyright (C) 2019 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package bootstrapql | ||
|
||
import ( | ||
"github.com/graphql-go/graphql" | ||
|
||
"storj.io/storj/bootstrap/bootstrapweb" | ||
) | ||
|
||
// Types return graphql type objects | ||
type Types interface { | ||
RootQuery() *graphql.Object | ||
} | ||
|
||
// TypeCreator handles graphql type creation and error checking | ||
type TypeCreator struct { | ||
query *graphql.Object | ||
} | ||
|
||
// Create create types and check for error | ||
func (c *TypeCreator) Create(service *bootstrapweb.Service) error { | ||
// root objects | ||
c.query = rootQuery(service, c) | ||
|
||
err := c.query.Error() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// RootQuery returns instance of query *graphql.Object | ||
func (c *TypeCreator) RootQuery() *graphql.Object { | ||
return c.query | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copyright (C) 2018 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package bootstrapserver | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net" | ||
"net/http" | ||
"path/filepath" | ||
|
||
"github.com/graphql-go/graphql" | ||
"github.com/zeebo/errs" | ||
"go.uber.org/zap" | ||
"golang.org/x/sync/errgroup" | ||
|
||
"storj.io/storj/bootstrap/bootstrapweb" | ||
"storj.io/storj/bootstrap/bootstrapweb/bootstrapserver/bootstrapql" | ||
) | ||
|
||
const ( | ||
contentType = "Content-Type" | ||
|
||
applicationJSON = "application/json" | ||
applicationGraphql = "application/graphql" | ||
) | ||
|
||
// Error is bootstrap web error type | ||
var Error = errs.Class("bootstrap web error") | ||
|
||
// Config contains configuration for bootstrap web server | ||
type Config struct { | ||
Address string `help:"server address of the graphql api gateway and frontend app" default:"127.0.0.1:8082"` | ||
StaticDir string `help:"path to static resources" default:""` | ||
} | ||
|
||
// Server represents bootstrap web server | ||
type Server struct { | ||
log *zap.Logger | ||
|
||
config Config | ||
service *bootstrapweb.Service | ||
listener net.Listener | ||
|
||
schema graphql.Schema | ||
server http.Server | ||
} | ||
|
||
// NewServer creates new instance of bootstrap web server | ||
func NewServer(logger *zap.Logger, config Config, service *bootstrapweb.Service, listener net.Listener) *Server { | ||
server := Server{ | ||
log: logger, | ||
service: service, | ||
config: config, | ||
listener: listener, | ||
} | ||
|
||
mux := http.NewServeMux() | ||
fs := http.FileServer(http.Dir(server.config.StaticDir)) | ||
|
||
mux.Handle("/api/graphql/v0", http.HandlerFunc(server.grapqlHandler)) | ||
|
||
if server.config.StaticDir != "" { | ||
mux.Handle("/", http.HandlerFunc(server.appHandler)) | ||
mux.Handle("/static/", http.StripPrefix("/static", fs)) | ||
} | ||
|
||
server.server = http.Server{ | ||
Handler: mux, | ||
} | ||
|
||
return &server | ||
} | ||
|
||
// appHandler is web app http handler function | ||
func (s *Server) appHandler(w http.ResponseWriter, req *http.Request) { | ||
http.ServeFile(w, req, filepath.Join(s.config.StaticDir, "dist", "public", "index.html")) | ||
} | ||
|
||
// grapqlHandler is graphql endpoint http handler function | ||
func (s *Server) grapqlHandler(w http.ResponseWriter, req *http.Request) { | ||
w.Header().Set(contentType, applicationJSON) | ||
|
||
query, err := getQuery(req) | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
result := graphql.Do(graphql.Params{ | ||
Schema: s.schema, | ||
Context: context.Background(), | ||
RequestString: query.Query, | ||
VariableValues: query.Variables, | ||
OperationName: query.OperationName, | ||
RootObject: make(map[string]interface{}), | ||
}) | ||
|
||
err = json.NewEncoder(w).Encode(result) | ||
if err != nil { | ||
s.log.Error(err.Error()) | ||
return | ||
} | ||
|
||
sugar := s.log.Sugar() | ||
sugar.Debug(result) | ||
} | ||
|
||
// Run starts the server that host webapp and api endpoint | ||
func (s *Server) Run(ctx context.Context) error { | ||
var err error | ||
|
||
s.schema, err = bootstrapql.CreateSchema(s.service) | ||
if err != nil { | ||
return Error.Wrap(err) | ||
} | ||
|
||
ctx, cancel := context.WithCancel(ctx) | ||
var group errgroup.Group | ||
group.Go(func() error { | ||
<-ctx.Done() | ||
return s.server.Shutdown(nil) | ||
}) | ||
group.Go(func() error { | ||
defer cancel() | ||
return s.server.Serve(s.listener) | ||
}) | ||
|
||
return group.Wait() | ||
} | ||
|
||
// Close closes server and underlying listener | ||
func (s *Server) Close() error { | ||
return s.server.Close() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// Copyright (C) 2018 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package bootstrapserver | ||
|
||
import ( | ||
"encoding/json" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/zeebo/errs" | ||
|
||
"storj.io/storj/bootstrap/bootstrapweb/bootstrapserver/bootstrapql" | ||
"storj.io/storj/pkg/utils" | ||
) | ||
|
||
// JSON request from graphql clients | ||
type graphqlJSON struct { | ||
Query string | ||
OperationName string | ||
Variables map[string]interface{} | ||
} | ||
|
||
// getQuery retrieves graphql query from request | ||
func getQuery(req *http.Request) (query graphqlJSON, err error) { | ||
switch req.Method { | ||
case http.MethodGet: | ||
query.Query = req.URL.Query().Get(bootstrapql.Query) | ||
return query, nil | ||
case http.MethodPost: | ||
return queryPOST(req) | ||
default: | ||
return query, errs.New("wrong http request type") | ||
} | ||
} | ||
|
||
// queryPOST retrieves graphql query from POST request | ||
func queryPOST(req *http.Request) (query graphqlJSON, err error) { | ||
switch typ := req.Header.Get(contentType); typ { | ||
case applicationGraphql: | ||
body, err := ioutil.ReadAll(req.Body) | ||
query.Query = string(body) | ||
return query, utils.CombineErrors(err, req.Body.Close()) | ||
case applicationJSON: | ||
err := json.NewDecoder(req.Body).Decode(&query) | ||
return query, utils.CombineErrors(err, req.Body.Close()) | ||
default: | ||
return query, errs.New("can't parse request body of type %s", typ) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// Copyright (C) 2018 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package bootstrapweb | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/zeebo/errs" | ||
"go.uber.org/zap" | ||
|
||
"storj.io/storj/pkg/kademlia" | ||
"storj.io/storj/pkg/pb" | ||
) | ||
|
||
// Service is handling bootstrap related logic | ||
type Service struct { | ||
log *zap.Logger | ||
kademlia *kademlia.Kademlia | ||
} | ||
|
||
// NewService returns new instance of Service | ||
func NewService(log *zap.Logger, kademlia *kademlia.Kademlia) (*Service, error) { | ||
if log == nil { | ||
return nil, errs.New("log can't be nil") | ||
} | ||
|
||
if kademlia == nil { | ||
return nil, errs.New("kademlia can't be nil") | ||
} | ||
|
||
return &Service{log: log, kademlia: kademlia}, nil | ||
} | ||
|
||
// IsNodeAvailable is a method for checking if node is up | ||
func (s *Service) IsNodeAvailable(ctx context.Context, nodeID pb.NodeID) (bool, error) { | ||
_, err := s.kademlia.FetchPeerIdentity(ctx, nodeID) | ||
|
||
isNodeAvailable := err == nil | ||
|
||
return isNodeAvailable, err | ||
} |
Oops, something went wrong.