Skip to content

Commit

Permalink
V3-1152 Node bootstrap web backend (storj#1327)
Browse files Browse the repository at this point in the history
* V3-1152 Node bootstrap
  • Loading branch information
crawter authored Mar 5, 2019
1 parent 7e2e4b5 commit 3e2c101
Show file tree
Hide file tree
Showing 14 changed files with 447 additions and 28 deletions.
48 changes: 48 additions & 0 deletions bootstrap/bootstrapweb/bootstrapserver/bootstrapql/query.go
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 bootstrap/bootstrapweb/bootstrapserver/bootstrapql/schema.go
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 bootstrap/bootstrapweb/bootstrapserver/bootstrapql/typecreator.go
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
}
136 changes: 136 additions & 0 deletions bootstrap/bootstrapweb/bootstrapserver/server.go
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()
}
50 changes: 50 additions & 0 deletions bootstrap/bootstrapweb/bootstrapserver/utils.go
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)
}
}
42 changes: 42 additions & 0 deletions bootstrap/bootstrapweb/service.go
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
}
Loading

0 comments on commit 3e2c101

Please sign in to comment.