diff --git a/nodebalancer/README.md b/nodebalancer/README.md index f7f42c23..c96950d6 100644 --- a/nodebalancer/README.md +++ b/nodebalancer/README.md @@ -3,37 +3,36 @@ ## Installation - Prepare environment variables, according to `sample.env`. -- Build application +- Build an application ```bash go build -o nodebalancer . ``` -## Work with nodebalancer +## CLI **IMPORTANT** Do not use flag `-debug` in production. -### add-access +Node balancer access manipulation requires an administration token to create and modify resources within the Bugout moonstream application. + +### add new access Add new access for user: ```bash -nodebalancer add-access \ - --user-id "" \ - --access-id "" \ +./nodebalancer access add \ + --access-token "" --name "Access name" \ - --description "Description of access" \ - --extended-methods false \ - --blockchain--access true + --description "Description of access" ``` -### delete-access +### delete access Delete user access: ```bash -nodebalancer delete-access \ - --user-id "" \ +./nodebalancer access delete \ + --access-token "" --access-id "" ``` @@ -42,10 +41,10 @@ If `access-id` not specified, all user accesses will be deleted. ### users ```bash -nodebalancer users | jq . +./nodebalancer access list --access-token "" | jq . ``` -This command will return a list of bugout resources of registered users to access node balancer with their `crawlers/app/project` (in our project we will call it `crawlers`). +This command will return a list of bugout resources of registered users to access node balancer. ```json [ @@ -72,7 +71,7 @@ This command will return a list of bugout resources of registered users to acces ### server ```bash -nodebalancer server -host 0.0.0.0 -port 8544 -healthcheck +./nodebalancer server --host 0.0.0.0 --port 8544 --healthcheck ``` Flag `--healthcheck` will execute background process to ping-pong available nodes to keep their status and current block number. diff --git a/nodebalancer/cmd/nodebalancer/cli.go b/nodebalancer/cmd/nodebalancer/cli.go index f33699e5..264a89dc 100644 --- a/nodebalancer/cmd/nodebalancer/cli.go +++ b/nodebalancer/cmd/nodebalancer/cli.go @@ -2,519 +2,584 @@ package main import ( "encoding/json" - "flag" "fmt" - "log" - "os" + "strings" + "sync" "time" bugout "github.com/bugout-dev/bugout-go/pkg" + "github.com/bugout-dev/bugout-go/pkg/brood" "github.com/google/uuid" + "github.com/urfave/cli/v2" ) -var ( - // Storing CLI definitions at server startup - stateCLI StateCLI - - bugoutClient bugout.BugoutClient - - DEFAULT_ACCESS_NAME = "" - DEFAULT_ACCESS_DESCRIPTION = "" - DEFAULT_BLOCKCHAIN_ACCESS = true - DEFAULT_EXTENDED_METHODS = true - DEFAULT_PERIOD_DURATION = int64(86400) // 1 day - DEFAULT_MAX_CALLS_PER_PERIOD = int64(10000) -) - -// Command Line Interface state -type StateCLI struct { - addAccessCmd *flag.FlagSet - updateAccessCmd *flag.FlagSet - generateConfigCmd *flag.FlagSet - deleteAccessCmd *flag.FlagSet - serverCmd *flag.FlagSet - usersCmd *flag.FlagSet - versionCmd *flag.FlagSet - - // Common flags - configPathFlag string - helpFlag bool - - // Add/update user access flags - userIDFlag string - accessIDFlag string - accessNameFlag string - accessDescriptionFlag string - - blockchainAccessFlag bool - extendedMethodsFlag bool - - PeriodDurationFlag int64 - MaxCallsPerPeriodFlag int64 - - // Update user access flags - PeriodStartTsFlag int64 - CallsPerPeriodFlag int64 - - // Server flags - listeningAddrFlag string - listeningPortFlag string - enableHealthCheckFlag bool - enableDebugFlag bool - - // Users list flags - limitFlag int - offsetFlag int -} - -func (s *StateCLI) usage() { - fmt.Printf(`usage: nodebalancer [-h] {%[1]s,%[2]s,%[3]s,%[4]s,%[5]s,%[6]s} ... - -Moonstream node balancer CLI -optional arguments: - -h, --help show this help message and exit - -subcommands: - {%[1]s,%[2]s,%[3]s,%[4]s,%[5]s,%[6]s,%[7]s} -`, s.addAccessCmd.Name(), s.updateAccessCmd.Name(), s.generateConfigCmd.Name(), s.deleteAccessCmd.Name(), s.serverCmd.Name(), s.usersCmd.Name(), s.versionCmd.Name()) -} - -// Check if required flags are set -func (s *StateCLI) checkRequirements() { - if s.helpFlag { - switch { - case s.addAccessCmd.Parsed(): - fmt.Printf("Add new user access resource\n\n") - s.addAccessCmd.PrintDefaults() - os.Exit(0) - case s.updateAccessCmd.Parsed(): - fmt.Printf("Update user access resource\n\n") - s.updateAccessCmd.PrintDefaults() - os.Exit(0) - case s.generateConfigCmd.Parsed(): - fmt.Printf("Generate new configuration\n\n") - s.generateConfigCmd.PrintDefaults() - os.Exit(0) - case s.deleteAccessCmd.Parsed(): - fmt.Printf("Delete user access resource\n\n") - s.deleteAccessCmd.PrintDefaults() - os.Exit(0) - case s.serverCmd.Parsed(): - fmt.Printf("Start nodebalancer server\n\n") - s.serverCmd.PrintDefaults() - os.Exit(0) - case s.usersCmd.Parsed(): - fmt.Printf("List user access tokens\n\n") - s.usersCmd.PrintDefaults() - os.Exit(0) - case s.versionCmd.Parsed(): - fmt.Printf("Show version\n\n") - s.versionCmd.PrintDefaults() - os.Exit(0) - default: - s.usage() - os.Exit(0) - } - } - - switch { - case s.addAccessCmd.Parsed(): - if s.userIDFlag == "" { - fmt.Printf("User ID should be specified\n\n") - s.addAccessCmd.PrintDefaults() - os.Exit(1) - } - if s.accessIDFlag == "" { - s.accessIDFlag = uuid.New().String() - } - if s.accessNameFlag == "" { - fmt.Printf("Access name should be specified\n\n") - s.addAccessCmd.PrintDefaults() - os.Exit(1) - } - case s.updateAccessCmd.Parsed(): - if s.userIDFlag == "" && s.accessIDFlag == "" { - fmt.Printf("User ID or access ID should be specified\n\n") - s.updateAccessCmd.PrintDefaults() - os.Exit(1) - } - case s.deleteAccessCmd.Parsed(): - if s.userIDFlag == "" && s.accessIDFlag == "" { - fmt.Printf("User or access ID flag should be specified\n\n") - s.deleteAccessCmd.PrintDefaults() - os.Exit(1) - } - case s.usersCmd.Parsed(): - if s.offsetFlag < 0 || s.limitFlag < 0 { - fmt.Printf("Offset and limit flags should be greater then zero\n\n") - s.usersCmd.PrintDefaults() - os.Exit(1) - } - } - - // Load configuration - config, err := GetConfigPath(s.configPathFlag) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - if !config.ConfigExists { - if err := GenerateDefaultConfig(config); err != nil { - fmt.Println(err) - os.Exit(1) - } - } else { - log.Printf("Loaded configuration from %s", config.ConfigPath) - } - s.configPathFlag = config.ConfigPath -} - -func (s *StateCLI) populateCLI() { - // Subcommands setup - s.addAccessCmd = flag.NewFlagSet("add-access", flag.ExitOnError) - s.updateAccessCmd = flag.NewFlagSet("update-access", flag.ExitOnError) - s.generateConfigCmd = flag.NewFlagSet("generate-config", flag.ExitOnError) - s.deleteAccessCmd = flag.NewFlagSet("delete-access", flag.ExitOnError) - s.serverCmd = flag.NewFlagSet("server", flag.ExitOnError) - s.usersCmd = flag.NewFlagSet("users", flag.ExitOnError) - s.versionCmd = flag.NewFlagSet("version", flag.ExitOnError) - - // Common flag pointers - for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd, s.generateConfigCmd, s.deleteAccessCmd, s.serverCmd, s.usersCmd, s.versionCmd} { - fs.BoolVar(&s.helpFlag, "help", false, "Show help message") - fs.StringVar(&s.configPathFlag, "config", "", "Path to configuration file (default: ~/.nodebalancer/config.json)") - } - - // Add, delete and list user access subcommand flag pointers - for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd, s.deleteAccessCmd, s.usersCmd} { - fs.StringVar(&s.userIDFlag, "user-id", "", "Bugout user ID") - fs.StringVar(&s.accessIDFlag, "access-id", "", "UUID for access identification") - } - - // Add/update user access subcommand flag pointers - for _, fs := range []*flag.FlagSet{s.addAccessCmd, s.updateAccessCmd} { - fs.StringVar(&s.accessNameFlag, "name", DEFAULT_ACCESS_NAME, fmt.Sprintf("Name of access (default: %s)", DEFAULT_ACCESS_NAME)) - fs.StringVar(&s.accessDescriptionFlag, "description", DEFAULT_ACCESS_DESCRIPTION, fmt.Sprintf("Description of access (default: %s)", DEFAULT_ACCESS_DESCRIPTION)) - fs.BoolVar(&s.blockchainAccessFlag, "blockchain-access", DEFAULT_BLOCKCHAIN_ACCESS, fmt.Sprintf("Specify this flag to grant direct access to blockchain nodes (default: %t)", DEFAULT_BLOCKCHAIN_ACCESS)) - fs.BoolVar(&s.extendedMethodsFlag, "extended-methods", DEFAULT_EXTENDED_METHODS, fmt.Sprintf("Specify this flag to grant execution availability to not whitelisted methods (default: %t)", DEFAULT_EXTENDED_METHODS)) - fs.Int64Var(&s.PeriodDurationFlag, "period-duration", DEFAULT_PERIOD_DURATION, fmt.Sprintf("Access period duration in seconds (default: %d)", DEFAULT_PERIOD_DURATION)) - fs.Int64Var(&s.MaxCallsPerPeriodFlag, "max-calls-per-period", DEFAULT_MAX_CALLS_PER_PERIOD, fmt.Sprintf("Max available calls to node during the period (default: %d)", DEFAULT_MAX_CALLS_PER_PERIOD)) - } +var CommonCommands = []*cli.Command{ + { + Name: "access", + Usage: "Operations with access IDs as Brood resource", + Flags: []cli.Flag{}, + Subcommands: []*cli.Command{ + { + Name: "add", + Usage: "Add new user's access ID", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to create resources in Moonstream Bugout application and sharing read permissions to nodebalancer application user", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "UUID for access identification", + }, + &cli.StringFlag{ + Name: "user-id", + Aliases: []string{"u"}, + Usage: "Bugout user ID", + }, + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Usage: "Name of the user or application to work with nodebalancer", + Required: true, + }, + &cli.StringFlag{ + Name: "description", + Aliases: []string{"d"}, + }, + &cli.BoolFlag{ + Name: "blockchain-access", + Aliases: []string{"b"}, + Usage: "Specify this flag to grant direct access to blockchain nodes", + Value: true, + }, + &cli.BoolFlag{ + Name: "extended-methods", + Aliases: []string{"e"}, + Usage: "Specify this flag to grant execution availability to not whitelisted methods", + Value: true, + }, + &cli.UintFlag{ + Name: "period-duration", + Aliases: []string{"p"}, + Usage: "Access period duration in seconds", + Value: 2592000, + }, + &cli.UintFlag{ + Name: "max-calls-per-period", + Aliases: []string{"m"}, + Usage: "Max available calls to node during the period", + Value: 10000, + }, + }, + Before: func(c *cli.Context) error { + accessIdFlag := c.String("access-id") + if accessIdFlag != "" { + _, uuidErr := uuid.Parse(accessIdFlag) + if uuidErr != nil { + return fmt.Errorf("provided --access-id should be valid UUID string") + } + } + + periodDurationFlag := c.Uint("period-duration") + + if periodDurationFlag < 3600 { + return fmt.Errorf("time for --period-duration should be greater then 1 hour") + } + + return nil + }, + Action: func(c *cli.Context) error { + accessToken := c.String("access-token") + + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return clientErr + } + + newAccess, newErr := AddNewAccess(accessToken, c.String("access-id"), c.String("user-id"), c.String("name"), c.String("description"), c.Bool("blockchain-access"), c.Bool("extended-methods"), c.Uint("period-duration"), c.Uint("max-calls-per-period")) + if newErr != nil { + return newErr + } + + _, shareErr := ShareAccess(accessToken, newAccess.ResourceID, NB_CONTROLLER_USER_ID, "user", DEFAULT_AUTOGENERATED_USER_PERMISSIONS) + if shareErr != nil { + return shareErr + } + + newAccessJson, err := json.Marshal(newAccess) + if err != nil { + return fmt.Errorf("unable to encode resource %s data interface to json, err: %v", newAccess.ResourceID, err) + } + fmt.Println(string(newAccessJson)) + + return nil + }, + }, + { + Name: "update", + Usage: "Update user's access", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to updated resources in Moonstream Bugout application", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "UUID for access identification", + }, + &cli.StringFlag{ + Name: "resource-id", + Aliases: []string{"r"}, + Usage: "UUID of Bugout resource for access identification", + }, + &cli.BoolFlag{ + Name: "reset-period-start-ts", + Aliases: []string{"s"}, + Usage: "Resets access unix period start timestamp to time now", + }, + &cli.UintFlag{ + Name: "period-duration", + Aliases: []string{"p"}, + Usage: "Access period duration in seconds", + }, + &cli.UintFlag{ + Name: "max-calls-per-period", + Aliases: []string{"m"}, + Usage: "Max available calls to node during the period", + }, + }, + Before: func(c *cli.Context) error { + accessIdFlag := c.String("access-id") + if accessIdFlag != "" { + _, uuidErr := uuid.Parse(accessIdFlag) + if uuidErr != nil { + return fmt.Errorf("provided --access-id should be valid UUID string") + } + } + + if accessIdFlag == "" && c.String("resource-id") == "" { + return fmt.Errorf("at least one of --access-id or --resource-id should be set") + } + + if !c.Bool("reset-period-start-ts") && c.Uint("period-duration") == 0 && c.Uint("max-calls-per-period") == 0 { + return fmt.Errorf("no updated parameters provided, at least one of --reset-period-start-ts or --max-calls-per-period or --period-duration should be set") + } + + return nil + }, + Action: func(c *cli.Context) error { + accessToken := c.String("access-token") + + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return clientErr + } + + resourceIdFlag := c.String("resource-id") + + var resources *brood.Resources + var getResErr error + if resourceIdFlag == "" { + resources, getResErr = GetResources(accessToken, c.String("access-id"), "") + if getResErr != nil { + return getResErr + } + } else { + var resource brood.Resource + resource, getResErr = bugoutClient.Brood.GetResource(accessToken, resourceIdFlag) + if getResErr != nil { + return fmt.Errorf("unable to get Bugout resource, err: %v", getResErr) + } + resources = &brood.Resources{Resources: []brood.Resource{resource}} + } + + if len(resources.Resources) > 1 { + return fmt.Errorf("too many resources to update") + } else if len(resources.Resources) == 0 { + return fmt.Errorf("there are no resources with such parameters") + } + + clientAccess, parseErr := ParseResourceDataToClientAccess(resources.Resources[0]) + if parseErr != nil { + return parseErr + } + + resourceId := clientAccess.ResourceID + updatedClientAccessResourceData := clientAccess.ClientResourceData + if c.Bool("reset-period-start-ts") { + updatedClientAccessResourceData.PeriodStartTs = int64(time.Now().Unix()) + } + if c.Uint("period-duration") != 0 { + updatedClientAccessResourceData.PeriodDuration = int64(c.Uint("period-duration")) + } + if c.Uint("max-calls-per-period") != 0 { + updatedClientAccessResourceData.MaxCallsPerPeriod = int64(c.Uint("max-calls-per-period")) + } + + updatedResource, updErr := bugoutClient.Brood.UpdateResource(accessToken, resourceId, updatedClientAccessResourceData, []string{}) + if updErr != nil { + return fmt.Errorf("unable to update Bugout resource, err: %v", updErr) + } + + fmt.Printf("Updated resource %s\n", updatedResource.Id) + + return nil + }, + }, + { + Name: "delete", + Usage: "Delete user's access", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to delete resources in Moonstream Bugout application", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "UUID for access identification", + }, + &cli.StringFlag{ + Name: "user-id", + Aliases: []string{"u"}, + Usage: "Filter by user_id", + }, + &cli.StringFlag{ + Name: "resource-id", + Aliases: []string{"r"}, + Usage: "UUID of Bugout resource for access identification", + }, + }, + Before: func(c *cli.Context) error { + accessIdFlag := c.String("access-id") + if accessIdFlag != "" { + _, uuidErr := uuid.Parse(accessIdFlag) + if uuidErr != nil { + return fmt.Errorf("provided --access-id should be valid UUID string") + } + } + + userIdFlag := c.String("user-id") + resourceIdFlag := c.String("resource-id") + + if accessIdFlag == "" && userIdFlag == "" && resourceIdFlag == "" { + return fmt.Errorf("at least one of --access-id or --user-id or --resource-id should be set") + } + + return nil + }, + Action: func(c *cli.Context) error { + accessToken := c.String("access-token") + + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return clientErr + } + + var resources *brood.Resources + var getResErr error + + resourceIdFlag := c.String("resource-id") + if resourceIdFlag == "" { + resources, getResErr = GetResources(accessToken, c.String("access-id"), c.String("user-id")) + if getResErr != nil { + return getResErr + } + } else { + var resource brood.Resource + resource, getResErr = bugoutClient.Brood.GetResource(accessToken, resourceIdFlag) + if getResErr != nil { + return fmt.Errorf("unable to get Bugout resource, err: %v", getResErr) + } + resources = &brood.Resources{Resources: []brood.Resource{resource}} + + } + + fmt.Printf("Found %d resources to delete\n", len(resources.Resources)) + if len(resources.Resources) == 0 { + return nil + } + + var clientAccesses []ClientAccess + for _, resource := range resources.Resources { + clientAccess, parseErr := ParseResourceDataToClientAccess(resource) + if parseErr != nil { + fmt.Println(parseErr) + continue + } + + clientAccesses = append(clientAccesses, *clientAccess) + } + + for _, access := range clientAccesses { + fmt.Printf("Deleting resource ID %s with name %s in 3 seconds..\n", access.ResourceID, access.ClientResourceData.Name) + time.Sleep(3 * time.Second) + + _, delErr := bugoutClient.Brood.DeleteResource(accessToken, access.ResourceID) + if delErr != nil { + fmt.Printf("Failed to delete resource with ID %s err: %v\n", access.ResourceID, delErr) + continue + } + } + + return nil + }, + }, + { + Name: "list", + Usage: "List user accesses", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to get resources in Moonstream Bugout application", + Required: true, + }, + &cli.StringFlag{ + Name: "access-id", + Aliases: []string{"a"}, + Usage: "Filter by access_id", + }, + &cli.StringFlag{ + Name: "user-id", + Aliases: []string{"u"}, + Usage: "Filter by user_id", + }, + }, + Action: func(c *cli.Context) error { + accessToken := c.String("access-token") + + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return clientErr + } + + resources, getResErr := GetResources(accessToken, c.String("access-id"), c.String("user-id")) + if getResErr != nil { + return getResErr + } + + var clientAccesses []ClientAccess + for _, resource := range resources.Resources { + clientAccess, parseErr := ParseResourceDataToClientAccess(resource) + if parseErr != nil { + fmt.Println(parseErr) + continue + } + + clientAccesses = append(clientAccesses, *clientAccess) + } + + userAccessesJson, marErr := json.Marshal(clientAccesses) + if marErr != nil { + return fmt.Errorf("unable to marshal user accesses struct, err: %v", marErr) + } + fmt.Println(string(userAccessesJson)) + + return nil + }, + }, + { + Name: "verify", + Usage: "Verify accesses in correct state", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "access-token", + Aliases: []string{"t"}, + Usage: "Authorized user access token with granted privileges to get resources in Moonstream Bugout application", + Required: true, + }, + }, + Action: func(c *cli.Context) error { + accessToken := c.String("access-token") + + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return clientErr + } + + resources, getResErr := GetResources(accessToken, "", "") + if getResErr != nil { + return getResErr + } + + if len(resources.Resources) == 0 { + fmt.Println("[]") + return nil + } + + var wg sync.WaitGroup + sem := make(chan struct{}, 3) + errChan := make(chan error, len(resources.Resources)) + + var modifiedClientAccesses []ClientAccess + var deleteClientAccesses []ClientAccess + shareResourceIds := make(map[string]bool) + for _, resource := range resources.Resources { + wg.Add(1) + go func(resourceId string) { + defer wg.Done() + sem <- struct{}{} + + var isShared bool + + holders, holdErr := bugoutClient.Brood.GetResourceHolders(accessToken, resourceId) + if holdErr != nil { + errChan <- fmt.Errorf("failed get holders for resource ID %s with error %v", resourceId, holdErr) + } + + for _, h := range holders.Holders { + if h.Id == NB_CONTROLLER_USER_ID { + isShared = true + } + } + + if !isShared { + shareResourceIds[resourceId] = true + } + + <-sem + }(resource.Id) + + var isModified bool + clientAccess, parseErr := ParseResourceDataToClientAccess(resource) + if parseErr != nil { + fmt.Println(parseErr) + continue + } + + if clientAccess.ClientResourceData.Name == "" || clientAccess.ClientResourceData.AccessID == "" { + deleteClientAccesses = append(deleteClientAccesses, *clientAccess) + continue + } + + if clientAccess.ClientResourceData.PeriodStartTs == 0 { + clientAccess.ClientResourceData.PeriodStartTs = int64(time.Now().Unix()) + isModified = true + } + + if clientAccess.ClientResourceData.PeriodDuration < 3600 { + clientAccess.ClientResourceData.PeriodDuration = 3600 + isModified = true + } + + if isModified { + modifiedClientAccesses = append(modifiedClientAccesses, *clientAccess) + continue + } + } + + fmt.Printf("There are %d accesses to modify\n", len(modifiedClientAccesses)) + for _, a := range modifiedClientAccesses { + fmt.Printf(" - resource ID %s with access ID %s\n", a.ResourceID, a.ClientResourceData.AccessID) + } + fmt.Printf("There are %d accesses to delete\n", len(deleteClientAccesses)) + for _, a := range deleteClientAccesses { + fmt.Printf(" - resource ID %s with access ID %s\n", a.ResourceID, a.ClientResourceData.AccessID) + } + + wg.Wait() + close(sem) + close(errChan) + + var errorMessages []string + for err := range errChan { + errorMessages = append(errorMessages, err.Error()) + } + + if len(errorMessages) > 0 { + fmt.Printf("errors occurred during verification:\n%s", strings.Join(errorMessages, "\n")) + } + + fmt.Printf("There are %d accesses not shared with nodebalancer application user\n", len(shareResourceIds)) + for a := range shareResourceIds { + fmt.Printf(" - resource ID %s\n", a) + } + + return nil + }, + }, + }, + }, + { + Name: "server", + Usage: "Start nodebalancer server", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Path to configuration file", + Required: true, + }, + &cli.StringFlag{ + Name: "host", + Usage: "Server listening address", + Value: "127.0.0.1", + }, + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Usage: "Server listening port", + Value: "8544", + }, + &cli.BoolFlag{ + Name: "healthcheck", + Usage: "Repeatedly send ping requests to the node to verify its availability", + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Show extended logs", + }, + }, + Action: func(c *cli.Context) error { + NB_ENABLE_DEBUG = c.Bool("debug") + + var clientErr error + bugoutClient, clientErr = CreateBugoutClient() + if clientErr != nil { + return clientErr + } - s.updateAccessCmd.Int64Var(&s.PeriodStartTsFlag, "period-start-ts", 0, "When period starts in unix timestamp format (default: now)") - s.updateAccessCmd.Int64Var(&s.CallsPerPeriodFlag, "calls-per-period", 0, "Current number of calls to node during the period (default: 0)") + CheckEnvVarSet() - // Server subcommand flag pointers - s.serverCmd.StringVar(&s.listeningAddrFlag, "host", "127.0.0.1", "Server listening address") - s.serverCmd.StringVar(&s.listeningPortFlag, "port", "8544", "Server listening port") - s.serverCmd.BoolVar(&s.enableHealthCheckFlag, "healthcheck", false, "To enable healthcheck set healthcheck flag") - s.serverCmd.BoolVar(&s.enableDebugFlag, "debug", false, "To enable debug mode with extended log set debug flag") + servErr := Server(c.String("config"), c.String("host"), c.String("port"), c.Bool("healthcheck")) + if servErr != nil { + return servErr + } - // Users list subcommand flag pointers - s.usersCmd.IntVar(&s.limitFlag, "limit", 10, "Output result limit") - s.usersCmd.IntVar(&s.offsetFlag, "offset", 0, "Result output offset") + return nil + }, + }, + { + Name: "version", + Usage: "Shows nodebalancer package version", + Action: func(cCtx *cli.Context) error { + fmt.Printf("Node Balancer version: %s\n", NB_VERSION) + fmt.Printf("Bugout version: %s\n", bugout.Version) + return nil + }, + }, } -func cli() { - stateCLI.populateCLI() - if len(os.Args) < 2 { - stateCLI.usage() - os.Exit(1) - } - - // Init bugout client - bc, err := CreateBugoutClient() - if err != nil { - log.Printf("An error occurred during Bugout client creation: %v", err) - os.Exit(1) - } - bugoutClient = bc - - // Parse subcommands and appropriate FlagSet - switch os.Args[1] { - case "generate-config": - stateCLI.generateConfigCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - case "add-access": - stateCLI.addAccessCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - proposedClientResourceData := ClientResourceData{ - UserID: stateCLI.userIDFlag, - AccessID: stateCLI.accessIDFlag, - Name: stateCLI.accessNameFlag, - Description: stateCLI.accessDescriptionFlag, - BlockchainAccess: stateCLI.blockchainAccessFlag, - ExtendedMethods: stateCLI.extendedMethodsFlag, - - PeriodDuration: stateCLI.PeriodDurationFlag, - PeriodStartTs: time.Now().Unix(), - MaxCallsPerPeriod: stateCLI.MaxCallsPerPeriodFlag, - CallsPerPeriod: 0, - - Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, - } - _, err := bugoutClient.Brood.FindUser( - NB_CONTROLLER_TOKEN, - map[string]string{ - "user_id": proposedClientResourceData.UserID, - "application_id": MOONSTREAM_APPLICATION_ID, - }, - ) - if err != nil { - fmt.Printf("User does not exists, err: %v\n", err) - os.Exit(1) - } - resource, err := bugoutClient.Brood.CreateResource(NB_CONTROLLER_TOKEN, MOONSTREAM_APPLICATION_ID, proposedClientResourceData) - if err != nil { - fmt.Printf("Unable to create user access, err: %v\n", err) - os.Exit(1) - } - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - os.Exit(1) - } - var newUserAccess ClientAccess - err = json.Unmarshal(resourceData, &newUserAccess) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - os.Exit(1) - } - newUserAccess.ResourceID = resource.Id - userAccessJson, err := json.Marshal(newUserAccess) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v", resource.Id, err) - os.Exit(1) - } - fmt.Println(string(userAccessJson)) - - case "update-access": - stateCLI.updateAccessCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - queryParameters := make(map[string]string) - if stateCLI.userIDFlag != "" { - queryParameters["user_id"] = stateCLI.userIDFlag - } - if stateCLI.accessIDFlag != "" { - queryParameters["access_id"] = stateCLI.accessIDFlag - } - resources, err := bugoutClient.Brood.GetResources( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - queryParameters, - ) - if err != nil { - fmt.Printf("Unable to get Bugout resources, err: %v\n", err) - os.Exit(1) - } - - resourcesLen := len(resources.Resources) - if resourcesLen == 0 { - fmt.Printf("There are no access resource with provided user-id %s or access-id %s\n", stateCLI.userIDFlag, stateCLI.accessIDFlag) - os.Exit(1) - } - if resourcesLen > 1 { - fmt.Printf("There are several %d access resources with provided user-id %s or access-id %s\n", resourcesLen, stateCLI.userIDFlag, stateCLI.accessIDFlag) - os.Exit(1) - } - - resource := resources.Resources[0] - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - os.Exit(1) - } - - var currentClientAccess ClientAccess - currentClientAccess.ResourceID = resource.Id - err = json.Unmarshal(resourceData, ¤tClientAccess.ClientResourceData) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - os.Exit(1) - } - - // TODO(kompotkot): Since we are using bool flags I moved with ugly solution. - // Let's find better one when have free time or will re-write flag Set. - update := make(map[string]interface{}) - if stateCLI.accessNameFlag != currentClientAccess.ClientResourceData.Name && stateCLI.accessNameFlag != DEFAULT_ACCESS_NAME { - update["name"] = stateCLI.accessNameFlag - } - if stateCLI.accessDescriptionFlag != currentClientAccess.ClientResourceData.Description && stateCLI.accessDescriptionFlag != DEFAULT_ACCESS_DESCRIPTION { - update["description"] = stateCLI.accessDescriptionFlag - } - if stateCLI.blockchainAccessFlag != currentClientAccess.ClientResourceData.BlockchainAccess && stateCLI.blockchainAccessFlag != DEFAULT_BLOCKCHAIN_ACCESS { - update["blockchain_access"] = stateCLI.blockchainAccessFlag - } - if stateCLI.extendedMethodsFlag != currentClientAccess.ClientResourceData.ExtendedMethods && stateCLI.extendedMethodsFlag != DEFAULT_EXTENDED_METHODS { - update["extended_methods"] = stateCLI.extendedMethodsFlag - } - if stateCLI.PeriodDurationFlag != currentClientAccess.ClientResourceData.PeriodDuration && stateCLI.PeriodDurationFlag != DEFAULT_PERIOD_DURATION { - update["period_duration"] = stateCLI.PeriodDurationFlag - } - if stateCLI.MaxCallsPerPeriodFlag != currentClientAccess.ClientResourceData.MaxCallsPerPeriod && stateCLI.MaxCallsPerPeriodFlag != DEFAULT_MAX_CALLS_PER_PERIOD { - update["max_calls_per_period"] = stateCLI.MaxCallsPerPeriodFlag - } - if stateCLI.PeriodStartTsFlag != currentClientAccess.ClientResourceData.PeriodStartTs && stateCLI.PeriodStartTsFlag != 0 { - update["period_start_ts"] = stateCLI.PeriodStartTsFlag - } - if stateCLI.CallsPerPeriodFlag != currentClientAccess.ClientResourceData.CallsPerPeriod && stateCLI.CallsPerPeriodFlag != 0 { - update["calls_per_period"] = stateCLI.CallsPerPeriodFlag - } - - updatedResource, err := bugoutClient.Brood.UpdateResource( - NB_CONTROLLER_TOKEN, - resource.Id, - update, - []string{}, - ) - if err != nil { - fmt.Printf("Unable to update Bugout resource, err: %v\n", err) - os.Exit(1) - } - - updatedResourceData, err := json.Marshal(updatedResource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - os.Exit(1) - } - var updatedUserAccess ClientAccess - err = json.Unmarshal(updatedResourceData, &updatedUserAccess) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - os.Exit(1) - } - updatedUserAccess.ResourceID = updatedResource.Id - userAccessJson, err := json.Marshal(updatedUserAccess) - if err != nil { - fmt.Printf("Unable to marshal user access struct, err: %v\n", err) - os.Exit(1) - } - fmt.Println(string(userAccessJson)) - - case "delete-access": - stateCLI.deleteAccessCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - queryParameters := make(map[string]string) - if stateCLI.userIDFlag != "" { - queryParameters["user_id"] = stateCLI.userIDFlag - } - if stateCLI.accessIDFlag != "" { - queryParameters["access_id"] = stateCLI.accessIDFlag - } - resources, err := bugoutClient.Brood.GetResources( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - queryParameters, - ) - if err != nil { - fmt.Printf("Unable to get Bugout resources, err: %v\n", err) - os.Exit(1) - } - - var userAccesses []ClientAccess - for _, resource := range resources.Resources { - deletedResource, err := bugoutClient.Brood.DeleteResource(NB_CONTROLLER_TOKEN, resource.Id) - if err != nil { - fmt.Printf("Unable to delete resource %s, err: %v\n", resource.Id, err) - continue - } - deletedResourceData, err := json.Marshal(deletedResource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - continue - } - var deletedUserAccess ClientAccess - err = json.Unmarshal(deletedResourceData, &deletedUserAccess) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - continue - } - deletedUserAccess.ResourceID = deletedResource.Id - userAccesses = append(userAccesses, deletedUserAccess) - } - - userAccessesJson, err := json.Marshal(userAccesses) - if err != nil { - fmt.Printf("Unable to marshal user access struct, err: %v\n", err) - os.Exit(1) - } - fmt.Println(string(userAccessesJson)) - - case "server": - stateCLI.serverCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - CheckEnvVarSet() - - Server() - - case "users": - stateCLI.usersCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - queryParameters := make(map[string]string) - if stateCLI.userIDFlag != "" { - queryParameters["user_id"] = stateCLI.userIDFlag - } - if stateCLI.accessIDFlag != "" { - queryParameters["access_id"] = stateCLI.accessIDFlag - } - resources, err := bugoutClient.Brood.GetResources( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - queryParameters, - ) - if err != nil { - fmt.Printf("Unable to get Bugout resources, err: %v\n", err) - os.Exit(1) - } - - var clientAccesses []ClientAccess - - offset := stateCLI.offsetFlag - if stateCLI.offsetFlag > len(resources.Resources) { - offset = len(resources.Resources) - } - limit := stateCLI.offsetFlag + stateCLI.limitFlag - if limit > len(resources.Resources) { - limit = len(resources.Resources[offset:]) + offset - } - - for _, resource := range resources.Resources[offset:limit] { - resourceData, err := json.Marshal(resource.ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource %s data interface to json, err: %v\n", resource.Id, err) - continue - } - var clientAccess ClientAccess - clientAccess.ResourceID = resource.Id - err = json.Unmarshal(resourceData, &clientAccess.ClientResourceData) - if err != nil { - fmt.Printf("Unable to decode resource %s data json to structure, err: %v\n", resource.Id, err) - continue - } - clientAccesses = append(clientAccesses, clientAccess) - } - userAccessesJson, err := json.Marshal(clientAccesses) - if err != nil { - fmt.Printf("Unable to marshal user accesses struct, err: %v\n", err) - os.Exit(1) - } - fmt.Println(string(userAccessesJson)) - - case "version": - stateCLI.versionCmd.Parse(os.Args[2:]) - stateCLI.checkRequirements() - - fmt.Printf("v%s\n", NB_VERSION) - - default: - stateCLI.usage() - os.Exit(1) +func NodebalancerAppCli() *cli.App { + return &cli.App{ + Name: "nodebalancer", + Version: fmt.Sprintf("v%s", NB_VERSION), + Usage: "Web3 node balancer", + EnableBashCompletion: true, + Commands: CommonCommands, } } diff --git a/nodebalancer/cmd/nodebalancer/clients.go b/nodebalancer/cmd/nodebalancer/clients.go index 56cb1e17..adcfd89c 100644 --- a/nodebalancer/cmd/nodebalancer/clients.go +++ b/nodebalancer/cmd/nodebalancer/clients.go @@ -1,10 +1,15 @@ package main import ( + "encoding/json" + "fmt" "log" "reflect" "sync" "time" + + "github.com/bugout-dev/bugout-go/pkg/brood" + "github.com/google/uuid" ) var ( @@ -55,7 +60,7 @@ func (ca *ClientAccess) CheckClientCallPeriodLimits(tsNow int64) bool { } } else { // Client period should be refreshed - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { log.Printf("Refresh client's period_start_ts with time.now() and reset calls_per_period") } ca.ClientResourceData.CallsPerPeriod = 0 @@ -188,3 +193,111 @@ func (cpool *ClientPool) CleanInactiveClientNodes() int { return cnt } + +// Creates new Bugout resource according to nodebalancer type to grant user or application access to call JSON RPC nodes +func AddNewAccess(accessToken, accessId, userId, name, description string, blockchainAccess, extendedMethods bool, periodDuration, maxCallsPerPeriod uint) (*ClientAccess, error) { + if userId == "" { + userId = NB_CONTROLLER_USER_ID + } else { + _, findErr := bugoutClient.Brood.FindUser( + accessToken, + map[string]string{ + "user_id": userId, + "application_id": MOONSTREAM_APPLICATION_ID, + }, + ) + if findErr != nil { + return nil, fmt.Errorf("user does not exists, err: %v", findErr) + } + } + + if accessId == "" { + accessId = uuid.NewString() + } + + proposedClientResourceData := ClientResourceData{ + AccessID: accessId, + UserID: userId, + Name: name, + Description: description, + BlockchainAccess: blockchainAccess, + ExtendedMethods: extendedMethods, + + PeriodDuration: int64(periodDuration), + PeriodStartTs: int64(time.Now().Unix()), + MaxCallsPerPeriod: int64(maxCallsPerPeriod), + CallsPerPeriod: 0, + + Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, + } + + resource, err := bugoutClient.Brood.CreateResource(accessToken, MOONSTREAM_APPLICATION_ID, proposedClientResourceData) + if err != nil { + return nil, fmt.Errorf("unable to create user access, err: %v", err) + } + resourceData, err := json.Marshal(resource.ResourceData) + if err != nil { + return nil, fmt.Errorf("unable to encode resource %s data interface to json, err: %v", resource.Id, err) + } + var newUserAccess ClientAccess + err = json.Unmarshal(resourceData, &newUserAccess) + if err != nil { + return nil, fmt.Errorf("unable to decode resource %s data json to structure, err: %v", resource.Id, err) + } + newUserAccess.ResourceID = resource.Id + + return &newUserAccess, nil +} + +// Share access represented as Brood resource with new holder. Mostly used to share with nodebalancer application user +func ShareAccess(accessToken, resourceId, userId, holderType string, permissions []string) (*brood.ResourceHolders, error) { + resourceHolderPermissions, holdErr := bugoutClient.Brood.AddResourceHolderPermissions( + accessToken, resourceId, brood.ResourceHolder{ + Id: userId, + HolderType: holderType, + Permissions: permissions, + }, + ) + if holdErr != nil { + return nil, fmt.Errorf("unable to grant permissions to user with ID %s at resource with ID %s, err: %v", userId, resourceId, holdErr) + } + + return &resourceHolderPermissions, nil +} + +// Get resource with nodebalancer access type +func GetResources(accessToken, accessId, userId string) (*brood.Resources, error) { + queryParameters := map[string]string{ + "type": BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, + } + if userId != "" { + queryParameters["user_id"] = userId + } + if accessId != "" { + queryParameters["access_id"] = accessId + } + + resources, getResErr := bugoutClient.Brood.GetResources(accessToken, MOONSTREAM_APPLICATION_ID, queryParameters) + if getResErr != nil { + return nil, fmt.Errorf("unable to get Bugout resources, err: %v", getResErr) + } + + return &resources, nil +} + +// Parse Brood resource to nodebalancer client access representation +func ParseResourceDataToClientAccess(resource brood.Resource) (*ClientAccess, error) { + resourceData, marErr := json.Marshal(resource.ResourceData) + if marErr != nil { + return nil, fmt.Errorf("unable to encode resource %s data interface to json, err: %v", resource.Id, marErr) + } + + var clientAccess ClientAccess + clientAccess.ResourceID = resource.Id + unmarErr := json.Unmarshal(resourceData, &clientAccess.ClientResourceData) + if unmarErr != nil { + return nil, fmt.Errorf("unable to decode resource %s data json to structure, err: %v", resource.Id, unmarErr) + } + + return &clientAccess, nil +} diff --git a/nodebalancer/cmd/nodebalancer/configs.go b/nodebalancer/cmd/nodebalancer/configs.go index d3991328..e3378bdd 100644 --- a/nodebalancer/cmd/nodebalancer/configs.go +++ b/nodebalancer/cmd/nodebalancer/configs.go @@ -23,6 +23,8 @@ var ( supportedBlockchains map[string]bool + bugoutClient *bugout.BugoutClient + // Bugout client // TODO(kompotkot): Find out why it cuts out the port BUGOUT_BROOD_URL = "https://auth.bugout.dev" @@ -32,11 +34,14 @@ var ( // Bugout and application configuration BUGOUT_AUTH_CALL_TIMEOUT = time.Second * 5 MOONSTREAM_APPLICATION_ID = os.Getenv("MOONSTREAM_APPLICATION_ID") + NB_CONTROLLER_USER_ID = os.Getenv("NB_CONTROLLER_USER_ID") NB_CONTROLLER_TOKEN = os.Getenv("NB_CONTROLLER_TOKEN") NB_CONTROLLER_ACCESS_ID = os.Getenv("NB_CONTROLLER_ACCESS_ID") MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS") CORS_WHITELIST_MAP = make(map[string]bool) + NB_ENABLE_DEBUG = false + NB_CONNECTION_RETRIES = 2 NB_CONNECTION_RETRIES_INTERVAL = time.Millisecond * 10 NB_HEALTH_CHECK_INTERVAL = os.Getenv("NB_HEALTH_CHECK_INTERVAL") @@ -64,15 +69,15 @@ var ( DEFAULT_AUTOGENERATED_MAX_CALLS_PER_PERIOD = int64(1000) ) -func CreateBugoutClient() (bugout.BugoutClient, error) { +func CreateBugoutClient() (*bugout.BugoutClient, error) { bugoutTimeoutSeconds, err := strconv.Atoi(NB_BUGOUT_TIMEOUT_SECONDS_RAW) if err != nil { - return bugout.BugoutClient{}, fmt.Errorf("unable to parse environment variable as integer: %v", err) + return nil, fmt.Errorf("unable to parse environment variable as integer: %v", err) } NB_BUGOUT_TIMEOUT_SECONDS := time.Duration(bugoutTimeoutSeconds) * time.Second - broodClient := bugout.ClientBrood(BUGOUT_BROOD_URL, NB_BUGOUT_TIMEOUT_SECONDS) - return broodClient, nil + bugoutClient := bugout.ClientBrood(BUGOUT_BROOD_URL, NB_BUGOUT_TIMEOUT_SECONDS) + return &bugoutClient, nil } func CheckEnvVarSet() { diff --git a/nodebalancer/cmd/nodebalancer/main.go b/nodebalancer/cmd/nodebalancer/main.go index cee85c6c..c6dad82c 100644 --- a/nodebalancer/cmd/nodebalancer/main.go +++ b/nodebalancer/cmd/nodebalancer/main.go @@ -1,5 +1,13 @@ package main +import ( + "log" + "os" +) + func main() { - cli() + app := NodebalancerAppCli() + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } } diff --git a/nodebalancer/cmd/nodebalancer/middleware.go b/nodebalancer/cmd/nodebalancer/middleware.go index 14881312..1df8cdec 100644 --- a/nodebalancer/cmd/nodebalancer/middleware.go +++ b/nodebalancer/cmd/nodebalancer/middleware.go @@ -236,50 +236,12 @@ func fetchClientAccessFromResources(accessID, authorizationToken string, tsNow i if len(resources.Resources) == 0 { if authorizationToken != "" { // Generate new autogenerated access resource with default parameters and grant user permissions to work with it - user, err := bugoutClient.Brood.GetUser(authorizationToken) - if err != nil { - log.Printf("Unable to get user, err: %v", err) - return nil, fmt.Errorf("unable to find user with provided authorization token") - } - newResource, err := bugoutClient.Brood.CreateResource( - NB_CONTROLLER_TOKEN, MOONSTREAM_APPLICATION_ID, ClientResourceData{ - UserID: user.Id, - AccessID: uuid.New().String(), - Name: user.Username, - Description: "Autogenerated access ID", - BlockchainAccess: true, - ExtendedMethods: false, - - PeriodDuration: DEFAULT_AUTOGENERATED_PERIOD_DURATION, - PeriodStartTs: tsNow, - MaxCallsPerPeriod: DEFAULT_AUTOGENERATED_MAX_CALLS_PER_PERIOD, - CallsPerPeriod: 0, - - Type: BUGOUT_RESOURCE_TYPE_NODEBALANCER_ACCESS, - }, - ) - if err != nil { - log.Printf("Unable to create resource with autogenerated access for user with ID %s, err: %v", user.Id, err) - return nil, fmt.Errorf("unable to create resource with autogenerated access for user") - } - - resourceHolderPermissions, err := bugoutClient.Brood.AddResourceHolderPermissions( - NB_CONTROLLER_TOKEN, newResource.Id, brood.ResourceHolder{ - Id: user.Id, - HolderType: "user", - Permissions: DEFAULT_AUTOGENERATED_USER_PERMISSIONS, - }, - ) - if err != nil { - log.Printf("Unable to grant permissions to user with ID %s at resource with ID %s, err: %v", newResource.Id, user.Id, err) - return nil, fmt.Errorf("unable to create resource with autogenerated access for user") - } - - log.Printf("Created new resource with ID %s with autogenerated access for user with ID %s", resourceHolderPermissions.ResourceId, user.Id) - resources.Resources = append(resources.Resources, newResource) + // TODO(kompotkot): Not working because of permissions models changed at Brood layer + return nil, fmt.Errorf("unsupported authentication method") } else { return nil, fmt.Errorf("there are no provided access identifier") } + } else if len(resources.Resources) > 1 { // TODO(kompotkot): Write support of multiple resources, be careful, because NB_CONTROLLER has several resources return nil, fmt.Errorf("there are no provided access identifier") @@ -479,7 +441,7 @@ func logMiddleware(next http.Handler) http.Handler { } } - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { if r.URL.RawQuery != "" { logStr += fmt.Sprintf(" %s", r.URL.RawQuery) } @@ -526,14 +488,14 @@ func accessMiddleware(next http.Handler) http.Handler { // If access id does not belong to internal crawlers, then check cache or find it in Bugout resources if accessID != "" && accessID == NB_CONTROLLER_ACCESS_ID { - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { log.Printf("Access ID belongs to internal usage for user with ID %s", currentClientAccess.ClientResourceData.UserID) } currentClientAccess = internalUsageAccess currentClientAccess.LastAccessTs = tsNow currentClientAccess.requestedDataSource = requestedDataSource } else if accessID != "" && accessCache.isAccessIdInCache(accessID) { - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { log.Printf("Access ID found in cache for user with ID %s", currentClientAccess.ClientResourceData.UserID) } currentClientAccess = *accessCache.accessIds[accessID] @@ -545,7 +507,7 @@ func accessMiddleware(next http.Handler) http.Handler { currentClientAccess.requestedDataSource = requestedDataSource accessCache.UpdateAccessAtCache(accessID, authorizationToken, requestedDataSource, tsNow) } else if accessID == "" && accessCache.isAuthorizationTokenInCache(authorizationToken) { - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { log.Printf("Client connected with Authorization token") } currentClientAccess = *accessCache.authorizationTokens[authorizationToken] @@ -557,7 +519,7 @@ func accessMiddleware(next http.Handler) http.Handler { currentClientAccess.requestedDataSource = requestedDataSource accessCache.UpdateAccessAtCache(accessID, authorizationToken, requestedDataSource, tsNow) } else { - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { log.Printf("No access identity found in cache, looking at Brood resources") } @@ -581,7 +543,7 @@ func accessMiddleware(next http.Handler) http.Handler { if authorizationToken != "" && accessCache.isAccessIdInCache(currentClientAccess.ClientResourceData.AccessID) { accessCache.authorizationTokens[authorizationToken] = accessCache.accessIds[currentClientAccess.ClientResourceData.AccessID] } else { - if stateCLI.enableDebugFlag { + if NB_ENABLE_DEBUG { log.Printf("Adding new access identifier in cache") } err := accessCache.AddAccessToCache(currentClientAccess, tsNow) diff --git a/nodebalancer/cmd/nodebalancer/server.go b/nodebalancer/cmd/nodebalancer/server.go index 7dee1aeb..106f85bd 100644 --- a/nodebalancer/cmd/nodebalancer/server.go +++ b/nodebalancer/cmd/nodebalancer/server.go @@ -5,13 +5,11 @@ package main import ( "context" - "encoding/json" "fmt" "log" "net/http" "net/http/httputil" "net/url" - "os" "strconv" "strings" "time" @@ -107,7 +105,7 @@ func proxyErrorHandler(proxy *httputil.ReverseProxy, url *url.URL) { } } -func Server() { +func Server(configPath, listeningHostAddr, listeningPort string, enableHealthCheck bool) error { // Create Access ID cache CreateAccessCache() @@ -117,47 +115,26 @@ func Server() { consent := humbug.CreateHumbugConsent(humbug.True) reporter, err = humbug.CreateHumbugReporter(consent, "moonstream-node-balancer", sessionID, HUMBUG_REPORTER_NB_TOKEN) if err != nil { - fmt.Printf("Invalid Humbug Crash configuration, err: %v\n", err) - os.Exit(1) + return fmt.Errorf("invalid Humbug Crash configuration, err: %v", err) } // Record system information reporter.Publish(humbug.SystemReport()) // Fetch access id for internal usage (crawlers, infrastructure, etc) - resources, err := bugoutClient.Brood.GetResources( - NB_CONTROLLER_TOKEN, - MOONSTREAM_APPLICATION_ID, - map[string]string{"access_id": NB_CONTROLLER_ACCESS_ID}, - ) - if err != nil { - fmt.Printf("Unable to get user with provided access identifier, err: %v\n", err) - os.Exit(1) + resources, getErr := GetResources(NB_CONTROLLER_TOKEN, NB_CONTROLLER_ACCESS_ID, "") + if getErr != nil { + return fmt.Errorf("unable to get user with provided access identifier, err: %v", getErr) } if len(resources.Resources) == 1 { - resourceData, err := json.Marshal(resources.Resources[0].ResourceData) - if err != nil { - fmt.Printf("Unable to encode resource data interface to json, err: %v\n", err) - os.Exit(1) - } - var clientResourceData ClientResourceData - err = json.Unmarshal(resourceData, &clientResourceData) - if err != nil { - fmt.Printf("Unable to decode resource data json to structure, err: %v\n", err) - os.Exit(1) - } - internalUsageAccess = ClientAccess{ - ClientResourceData: ClientResourceData{ - UserID: clientResourceData.UserID, - AccessID: clientResourceData.AccessID, - Name: clientResourceData.Name, - Description: clientResourceData.Description, - BlockchainAccess: clientResourceData.BlockchainAccess, - ExtendedMethods: clientResourceData.ExtendedMethods, - }, + clientAccess, parseErr := ParseResourceDataToClientAccess(resources.Resources[0]) + if parseErr != nil { + return parseErr } + internalUsageAccess = *clientAccess + log.Printf( "Internal crawlers access set, resource id: %s, blockchain access: %t, extended methods: %t", - resources.Resources[0].Id, clientResourceData.BlockchainAccess, clientResourceData.ExtendedMethods, + resources.Resources[0].Id, internalUsageAccess.ClientResourceData.BlockchainAccess, internalUsageAccess.ClientResourceData.ExtendedMethods, ) } else if len(resources.Resources) == 0 { @@ -173,15 +150,13 @@ func Server() { } fmt.Printf("There are no provided NB_CONTROLLER_ACCESS_ID records in Brood resources. Using provided with environment variable or randomly generated\n") } else { - fmt.Printf("User with provided access identifier has wrong number of resources: %d\n", len(resources.Resources)) - os.Exit(1) + return fmt.Errorf("user with provided access identifier has wrong number of resources: %d\n", len(resources.Resources)) } // Fill NodeConfigList with initial nodes from environment variables - err = LoadConfig(stateCLI.configPathFlag) + err = LoadConfig(configPath) if err != nil { - fmt.Println(err) - os.Exit(1) + return err } supportedBlockchains = make(map[string]bool) @@ -189,8 +164,7 @@ func Server() { for i, nodeConfig := range nodeConfigs { endpoint, err := url.Parse(nodeConfig.Endpoint) if err != nil { - fmt.Println(err) - os.Exit(1) + return err } // Append to supported blockchain set @@ -251,7 +225,7 @@ func Server() { commonHandler = panicMiddleware(commonHandler) server := http.Server{ - Addr: fmt.Sprintf("%s:%s", stateCLI.listeningAddrFlag, stateCLI.listeningPortFlag), + Addr: fmt.Sprintf("%s:%s", listeningHostAddr, listeningPort), Handler: commonHandler, ReadTimeout: 40 * time.Second, WriteTimeout: 40 * time.Second, @@ -259,17 +233,18 @@ func Server() { // Start node health checking and current block fetching blockchainPool.HealthCheck() - if stateCLI.enableHealthCheckFlag { - go initHealthCheck(stateCLI.enableDebugFlag) + if enableHealthCheck { + go initHealthCheck(NB_ENABLE_DEBUG) } // Start access id cache cleaning - go initCacheCleaning(stateCLI.enableDebugFlag) + go initCacheCleaning(NB_ENABLE_DEBUG) - log.Printf("Starting node load balancer HTTP server at %s:%s", stateCLI.listeningAddrFlag, stateCLI.listeningPortFlag) + log.Printf("Starting node load balancer HTTP server at %s:%s", listeningHostAddr, listeningPort) err = server.ListenAndServe() if err != nil { - fmt.Printf("Failed to start server listener, err: %v\n", err) - os.Exit(1) + return fmt.Errorf("failed to start server listener, err: %v", err) } + + return nil } diff --git a/nodebalancer/cmd/nodebalancer/version.go b/nodebalancer/cmd/nodebalancer/version.go index 5b04a011..41799620 100644 --- a/nodebalancer/cmd/nodebalancer/version.go +++ b/nodebalancer/cmd/nodebalancer/version.go @@ -1,3 +1,3 @@ package main -var NB_VERSION = "0.2.7" +var NB_VERSION = "0.2.8" diff --git a/nodebalancer/deploy/nodebalancer.service b/nodebalancer/deploy/nodebalancer.service index e353ec58..3a3c691b 100644 --- a/nodebalancer/deploy/nodebalancer.service +++ b/nodebalancer/deploy/nodebalancer.service @@ -10,10 +10,10 @@ EnvironmentFile=/home/ubuntu/nodebalancer-secrets/app.env Restart=on-failure RestartSec=15s ExecStart=/home/ubuntu/api/nodebalancer/nodebalancer server \ - -host "${AWS_LOCAL_IPV4}" \ - -port 8544 \ - -healthcheck \ - -config /home/ubuntu/.nodebalancer/config.json + --host "${AWS_LOCAL_IPV4}" \ + --port 8544 \ + --healthcheck \ + --config /home/ubuntu/.nodebalancer/config.json SyslogIdentifier=nodebalancer [Install] diff --git a/nodebalancer/go.mod b/nodebalancer/go.mod index 5726eb78..f1046536 100644 --- a/nodebalancer/go.mod +++ b/nodebalancer/go.mod @@ -3,7 +3,14 @@ module github.com/bugout-dev/moonstream/nodes/node_balancer go 1.17 require ( - github.com/bugout-dev/bugout-go v0.4.3 - github.com/bugout-dev/humbug/go v0.0.0-20211206230955-57607cd2d205 - github.com/google/uuid v1.3.0 + github.com/bugout-dev/bugout-go v0.4.6 + github.com/bugout-dev/humbug/go v0.0.0-20230713220619-2cd74a2b36d7 + github.com/google/uuid v1.6.0 + github.com/urfave/cli/v2 v2.27.5 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect ) diff --git a/nodebalancer/go.sum b/nodebalancer/go.sum index b8d5c645..22ab4ee7 100644 --- a/nodebalancer/go.sum +++ b/nodebalancer/go.sum @@ -12,6 +12,7 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -23,10 +24,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bugout-dev/bugout-go v0.4.3 h1:sTwMgNDZR8mK0BkPVfXIsFWZAkL8ePKr5Xmj35A2O3o= -github.com/bugout-dev/bugout-go v0.4.3/go.mod h1:P4+788iHtt/32u2wIaRTaiXTWpvSVBYxZ01qQ8N7eB8= -github.com/bugout-dev/humbug/go v0.0.0-20211206230955-57607cd2d205 h1:UQ7XGjvoOVKGRIuTFXgqGtU/UgMOk8+ikpoHWrWefjQ= -github.com/bugout-dev/humbug/go v0.0.0-20211206230955-57607cd2d205/go.mod h1:U/NXHfc3tzGeQz+xVfpifXdPZi7p6VV8xdP/4ZKeWJU= +github.com/bugout-dev/bugout-go v0.4.6 h1:HaXoVNVZYqd6BaPwlQGhWKBYdGc2lhF3BRxIgyL+1SY= +github.com/bugout-dev/bugout-go v0.4.6/go.mod h1:P4+788iHtt/32u2wIaRTaiXTWpvSVBYxZ01qQ8N7eB8= +github.com/bugout-dev/humbug/go v0.0.0-20230713220619-2cd74a2b36d7 h1:Mn6t3HO056/++m5UESl/06FdSxz84S1p7pfQA+NZwVo= +github.com/bugout-dev/humbug/go v0.0.0-20230713220619-2cd74a2b36d7/go.mod h1:U/NXHfc3tzGeQz+xVfpifXdPZi7p6VV8xdP/4ZKeWJU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -35,6 +36,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -65,8 +68,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -142,6 +145,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -163,7 +168,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -282,6 +291,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/nodebalancer/sample.env b/nodebalancer/sample.env index d6458c49..efea6003 100644 --- a/nodebalancer/sample.env +++ b/nodebalancer/sample.env @@ -3,8 +3,9 @@ export BUGOUT_BROOD_URL="https://auth.bugout.dev" export NB_BUGOUT_TIMEOUT_SECONDS=15 export MOONSTREAM_APPLICATION_ID="" export MOONSTREAM_CORS_ALLOWED_ORIGINS="http://localhost:3000,https://moonstream.to,https://portal.moonstream.to" -export NB_CONTROLLER_TOKEN="" -export NB_CONTROLLER_ACCESS_ID="" +export NB_CONTROLLER_USER_ID="" +export NB_CONTROLLER_TOKEN="" +export NB_CONTROLLER_ACCESS_ID="" # Error humbug reporter export HUMBUG_REPORTER_NODE_BALANCER_TOKEN=""