From 7bd9d75c3a477611e4cdd5adeead99c50fcab84e Mon Sep 17 00:00:00 2001 From: Victor Yves Crispim Date: Fri, 22 Sep 2023 18:04:40 -0300 Subject: [PATCH] feat: add graphql-server to the cartesi-node binary --- cmd/cartesi-node/full.go | 52 ------------------ cmd/cartesi-node/no-backend.go | 5 +- cmd/cartesi-node/reader.go | 5 +- cmd/cartesi-node/root.go | 6 +-- cmd/cartesi-node/validator.go | 22 +++++--- internal/pkg/services/graphql-service.go | 45 ++++++++++++++++ internal/pkg/services/service.go | 69 ++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 65 deletions(-) delete mode 100644 cmd/cartesi-node/full.go create mode 100644 internal/pkg/services/graphql-service.go create mode 100644 internal/pkg/services/service.go diff --git a/cmd/cartesi-node/full.go b/cmd/cartesi-node/full.go deleted file mode 100644 index 8a18a41c3..000000000 --- a/cmd/cartesi-node/full.go +++ /dev/null @@ -1,52 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -package main - -import ( - "io" - "log" - "os" - "os/exec" - - "github.com/spf13/cobra" -) - -var full = &cobra.Command{ - Use: "full", - Short: "Starts the node in full mode with reader and validator capabilities", - Run: runFullNode, -} - -func runFullNode(cmd *cobra.Command, args []string) { - proc := exec.Command("cartesi-rollups-graphql-server") - - stdoutPipe, err := proc.StdoutPipe() - if err != nil { - log.Fatal("Error creating stdout pipe:", err) - } - - stderrPipe, err := proc.StderrPipe() - if err != nil { - log.Fatal("Error creating stderr pipe:", err) - } - - if err := proc.Start(); err != nil { - log.Fatal("Error starting sub-process:", err) - } - - // Create goroutines to display the sub-process's stdout and stderr. - go func() { - io.Copy(os.Stdout, stdoutPipe) - }() - - go func() { - io.Copy(os.Stderr, stderrPipe) - }() - - if err := proc.Wait(); err != nil { - log.Fatal("Error waiting for sub-process:", err) - } - - log.Println("Sub-process finished") -} diff --git a/cmd/cartesi-node/no-backend.go b/cmd/cartesi-node/no-backend.go index ffb000f47..cc846832d 100644 --- a/cmd/cartesi-node/no-backend.go +++ b/cmd/cartesi-node/no-backend.go @@ -6,8 +6,9 @@ package main import "github.com/spf13/cobra" var noBackend = &cobra.Command{ - Use: "no-backend", - Short: "Starts the node in no-backend mode", + Use: "no-backend", + Short: "Starts the node in no-backend mode", + DisableFlagsInUseLine: true, Run: func(cmd *cobra.Command, args []string) { println("TODO") }, diff --git a/cmd/cartesi-node/reader.go b/cmd/cartesi-node/reader.go index 52df50e46..dc4f1642c 100644 --- a/cmd/cartesi-node/reader.go +++ b/cmd/cartesi-node/reader.go @@ -6,8 +6,9 @@ package main import "github.com/spf13/cobra" var reader = &cobra.Command{ - Use: "reader", - Short: "Starts the node in reader mode", + Use: "reader", + Short: "Starts the node in reader mode", + DisableFlagsInUseLine: true, Run: func(cmd *cobra.Command, args []string) { println("TODO") }, diff --git a/cmd/cartesi-node/root.go b/cmd/cartesi-node/root.go index c7ea9d345..80a1c29be 100644 --- a/cmd/cartesi-node/root.go +++ b/cmd/cartesi-node/root.go @@ -6,13 +6,13 @@ package main import "github.com/spf13/cobra" var rootCmd = &cobra.Command{ - Use: "cartesi-node [reader|validator|full|no-backend]", - Run: func(cmd *cobra.Command, args []string) { cmd.Usage() }, + Use: "cartesi-node", + CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true}, + DisableFlagsInUseLine: true, } func init() { rootCmd.AddCommand(reader) rootCmd.AddCommand(validator) - rootCmd.AddCommand(full) rootCmd.AddCommand(noBackend) } diff --git a/cmd/cartesi-node/validator.go b/cmd/cartesi-node/validator.go index 87f7ae18a..fb57660fb 100644 --- a/cmd/cartesi-node/validator.go +++ b/cmd/cartesi-node/validator.go @@ -3,12 +3,22 @@ package main -import "github.com/spf13/cobra" +import ( + "github.com/cartesi/rollups-node/internal/pkg/services" + "github.com/spf13/cobra" +) var validator = &cobra.Command{ - Use: "validator", - Short: "Starts the node in validator mode", - Run: func(cmd *cobra.Command, args []string) { - println("TODO") - }, + Use: "validator", + Short: "Starts the node in validator mode", + DisableFlagsInUseLine: true, + Run: runValidatorNode, +} + +func runValidatorNode(cmd *cobra.Command, args []string) { + validatorServices := []services.Service{ + services.GraphQLService{}, + } + + services.Run(validatorServices) } diff --git a/internal/pkg/services/graphql-service.go b/internal/pkg/services/graphql-service.go new file mode 100644 index 000000000..3f8808105 --- /dev/null +++ b/internal/pkg/services/graphql-service.go @@ -0,0 +1,45 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package services + +import ( + "context" + "fmt" + "os" + "os/exec" + "syscall" +) + +const ( + serviceName = "graphql-server" + binaryName = "cartesi-rollups-graphql-server" +) + +type GraphQLService struct{} + +func (g GraphQLService) Start(ctx context.Context) error { + cmd := exec.Command(binaryName) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + + if err := cmd.Start(); err != nil { + return err + } + + go func() { + <-ctx.Done() + fmt.Printf("%v: %v\n", g.String(), ctx.Err()) + cmd.Process.Signal(syscall.SIGTERM) + }() + + err := cmd.Wait() + if err != nil && cmd.ProcessState.ExitCode() != int(syscall.SIGTERM) { + return err + } + return nil +} + +func (g GraphQLService) String() string { + return serviceName +} diff --git a/internal/pkg/services/service.go b/internal/pkg/services/service.go new file mode 100644 index 000000000..b7c3b36ab --- /dev/null +++ b/internal/pkg/services/service.go @@ -0,0 +1,69 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Provide mechanisms to start multiple services in the background +package services + +import ( + "context" + "fmt" + "time" +) + +// A service that runs in the background endlessly until the context is canceled +type Service interface { + fmt.Stringer + + // Start a service that will run until completion or until the context is + // canceled + Start(ctx context.Context) error +} + +const DefaultServiceTimeout = 15 * time.Second + +// The Run function serves as a very simple supervisor: it will start all the +// services provided to it and will run until the first of them finishes. Next +// it will try to stop the remaining services or timeout if they take too long +func Run(services []Service) { + if len(services) == 0 { + panic("there are no services to run") + } + + // start services + ctx, cancel := context.WithCancel(context.Background()) + exit := make(chan struct{}) + for _, service := range services { + service := service + go func() { + if err := service.Start(ctx); err != nil { + msg := "main: service '%v' exited with error: %v\n" + fmt.Printf(msg, service.String(), err) + } else { + msg := "main: service '%v' exited successfully\n" + fmt.Printf(msg, service.String()) + } + exit <- struct{}{} + }() + } + + // wait for first service to exit + <-exit + + // send stop message to all other services and wait for them to finish + // or timeout + wait := make(chan struct{}) + go func() { + cancel() + for i := 0; i < len(services)-1; i++ { + <-exit + } + wait <- struct{}{} + }() + + select { + case <-wait: + fmt.Println("main: all services exited") + case <-time.After(DefaultServiceTimeout): + fmt.Println("main: exited after timeout") + } +}