Initial commit
This commit is contained in:
		
						commit
						f8b9c4f748
					
				
					 20 changed files with 1494 additions and 0 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					tmp/
 | 
				
			||||||
 | 
					__debug*
 | 
				
			||||||
 | 
					main
 | 
				
			||||||
							
								
								
									
										54
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // Use IntelliSense to learn about possible attributes.
 | 
				
			||||||
 | 
					    // Hover to view descriptions of existing attributes.
 | 
				
			||||||
 | 
					    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 | 
				
			||||||
 | 
					    "version": "0.2.0",
 | 
				
			||||||
 | 
					    "configurations": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "Master",
 | 
				
			||||||
 | 
					            "type": "go",
 | 
				
			||||||
 | 
					            "request": "launch",
 | 
				
			||||||
 | 
					            "mode": "auto",
 | 
				
			||||||
 | 
					            "program": "${workspaceFolder}/cli",
 | 
				
			||||||
 | 
					            "env": {
 | 
				
			||||||
 | 
					                "DUMP_PATH": "../tmp/nodes.json",
 | 
				
			||||||
 | 
					                "CONFIG_PATH": "../tmp/config.json",
 | 
				
			||||||
 | 
					                "NODE_DIR_PATH": "../tmp/node",
 | 
				
			||||||
 | 
					                "TOKEN": "abcd"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "args": [
 | 
				
			||||||
 | 
					                "master"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "Node",
 | 
				
			||||||
 | 
					            "type": "go",
 | 
				
			||||||
 | 
					            "request": "launch",
 | 
				
			||||||
 | 
					            "mode": "auto",
 | 
				
			||||||
 | 
					            "program": "${workspaceFolder}/cli",
 | 
				
			||||||
 | 
					            "env": {
 | 
				
			||||||
 | 
					                "DUMP_PATH": "../tmp/nodes.json",
 | 
				
			||||||
 | 
					                "CONFIG_PATH": "../tmp/config.json",
 | 
				
			||||||
 | 
					                "NODE_DIR_PATH": "../tmp/node"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "args": [
 | 
				
			||||||
 | 
					                "node"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            "name": "Print",
 | 
				
			||||||
 | 
					            "type": "go",
 | 
				
			||||||
 | 
					            "request": "launch",
 | 
				
			||||||
 | 
					            "mode": "auto",
 | 
				
			||||||
 | 
					            "program": "${workspaceFolder}/cli",
 | 
				
			||||||
 | 
					            "env": {
 | 
				
			||||||
 | 
					                "DUMP_PATH": "../tmp/nodes.json",
 | 
				
			||||||
 | 
					                "CONFIG_PATH": "../tmp/config.json",
 | 
				
			||||||
 | 
					                "NODE_DIR_PATH": "../tmp/node"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "args": [
 | 
				
			||||||
 | 
					                "print"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										5
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					# Lobby 2 - simple service discovery
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is second version of my Lobby projects that doesn't require NATS. All clients uses single service discovery server that keeps track of their presence.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each instance, except its liveness can share labels which describe what's hosted on this instance and that can used by others instances for various things. There is also a simple KV store for additional info.
 | 
				
			||||||
							
								
								
									
										9
									
								
								Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					# https://taskfile.dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					version: '3'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tasks:
 | 
				
			||||||
 | 
					  docs:
 | 
				
			||||||
 | 
					    cmds:
 | 
				
			||||||
 | 
					      - swag i --parseDependency --dir api
 | 
				
			||||||
 | 
					    silent: true
 | 
				
			||||||
							
								
								
									
										41
									
								
								api/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								api/auth.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/labstack/echo/v4"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func tokenMiddlware(configuredToken string) echo.MiddlewareFunc {
 | 
				
			||||||
 | 
						return func(next echo.HandlerFunc) echo.HandlerFunc {
 | 
				
			||||||
 | 
							return func(c echo.Context) error {
 | 
				
			||||||
 | 
								// Ignore token check for swagger URLs
 | 
				
			||||||
 | 
								if strings.HasPrefix(c.Request().URL.Path, "/swagger") || c.Request().URL.Path == "/" {
 | 
				
			||||||
 | 
									return next(c)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check for token in the Authorization header
 | 
				
			||||||
 | 
								authHeader := c.Request().Header.Get("Authorization")
 | 
				
			||||||
 | 
								if authHeader == "" {
 | 
				
			||||||
 | 
									return echo.NewHTTPError(http.StatusUnauthorized, "please provide valid token")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// The Authorization header should be in the format "Bearer <token>"
 | 
				
			||||||
 | 
								parts := strings.Split(authHeader, " ")
 | 
				
			||||||
 | 
								if len(parts) == 1 && parts[0] == configuredToken {
 | 
				
			||||||
 | 
									return next(c)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if len(parts) != 2 || parts[0] != "Bearer" {
 | 
				
			||||||
 | 
									return echo.NewHTTPError(http.StatusUnauthorized, "please provide valid token")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if parts[1] != configuredToken {
 | 
				
			||||||
 | 
									return echo.NewHTTPError(http.StatusUnauthorized, "please provide valid token")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return next(c)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										113
									
								
								api/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								api/main.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_ "gitea.ceperka.net/rosti/lobby2/docs" // This line is necessary for swag to find your docs!
 | 
				
			||||||
 | 
						"gitea.ceperka.net/rosti/lobby2/nodes"
 | 
				
			||||||
 | 
						"github.com/labstack/echo/v4"
 | 
				
			||||||
 | 
						"github.com/labstack/echo/v4/middleware"
 | 
				
			||||||
 | 
						echoSwagger "github.com/swaggo/echo-swagger"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @title Lobby2 API
 | 
				
			||||||
 | 
					// @version 2.0
 | 
				
			||||||
 | 
					// @description API of Lobby 2 project that helps to discover and connect to other nodes and their services.
 | 
				
			||||||
 | 
					// @BasePath /
 | 
				
			||||||
 | 
					// @securityDefinitions.apikey Bearer
 | 
				
			||||||
 | 
					// @in header
 | 
				
			||||||
 | 
					// @name Authorization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type API struct {
 | 
				
			||||||
 | 
						listen string
 | 
				
			||||||
 | 
						token  string
 | 
				
			||||||
 | 
						np     *nodes.NodesProcessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e *echo.Echo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewAPI(np *nodes.NodesProcessor, listen string, token string) *API {
 | 
				
			||||||
 | 
						return &API{
 | 
				
			||||||
 | 
							listen: listen,
 | 
				
			||||||
 | 
							token:  token,
 | 
				
			||||||
 | 
							np:     np,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *API) Run() error {
 | 
				
			||||||
 | 
						if a.token == "" {
 | 
				
			||||||
 | 
							log.Fatalln("TOKEN is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.e = echo.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.e.Use(middleware.Logger())
 | 
				
			||||||
 | 
						a.e.Use(tokenMiddlware(a.token))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.e.GET("/", func(c echo.Context) error {
 | 
				
			||||||
 | 
							return c.Redirect(http.StatusTemporaryRedirect, "/swagger/index.html")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						a.e.GET("/swagger/*", echoSwagger.WrapHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.e.GET("/nodes", a.listHandler)
 | 
				
			||||||
 | 
						a.e.GET("/nodes/:hostname", a.getHandler)
 | 
				
			||||||
 | 
						a.e.POST("/nodes/:hostname", a.refreshHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start the server in a goroutine so that it doesn't block the signal listening
 | 
				
			||||||
 | 
						return a.e.Start(a.listen)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @Summary List of nodes
 | 
				
			||||||
 | 
					// @Description List of all discovered nodes and their labels.
 | 
				
			||||||
 | 
					// @Produce  application/json
 | 
				
			||||||
 | 
					// @Success 200 {object} nodes.Nodes "List of nodes"
 | 
				
			||||||
 | 
					// @Failure 401 {object} Message "Forbidden access"
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					// @Router /nodes [get]
 | 
				
			||||||
 | 
					func (a *API) listHandler(c echo.Context) error {
 | 
				
			||||||
 | 
						return c.JSON(http.StatusOK, a.np.List())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @Summary Get node
 | 
				
			||||||
 | 
					// @Description Return one nodes based on given hostname
 | 
				
			||||||
 | 
					// @Produce  application/json
 | 
				
			||||||
 | 
					// @Param hostname path string true "Node hostname"
 | 
				
			||||||
 | 
					// @Success 200 {array} nodes.Node "Node details"
 | 
				
			||||||
 | 
					// @Failure 401 {object} Message "Forbidden access"
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					// @Router /nodes/{hostname} [get]
 | 
				
			||||||
 | 
					func (a *API) getHandler(c echo.Context) error {
 | 
				
			||||||
 | 
						hostname := c.Param("hostname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node, ok := a.np.Get(hostname)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return echo.NewHTTPError(http.StatusNotFound, "node not found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.JSON(http.StatusOK, node)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// @Summary Refresh node
 | 
				
			||||||
 | 
					// @Description Send new data or update existing data about a node
 | 
				
			||||||
 | 
					// @Produce  application/json
 | 
				
			||||||
 | 
					// @Param hostname path string true "Node hostname"
 | 
				
			||||||
 | 
					// @Param    labels   body   nodes.Labels   true   "Node labels"
 | 
				
			||||||
 | 
					// @Param    kv   body   nodes.KV   true   "Key-value"
 | 
				
			||||||
 | 
					// @Success  200 {array} nodes.Node "Node details"
 | 
				
			||||||
 | 
					// @Failure  401 {object} Message "Forbidden access"
 | 
				
			||||||
 | 
					// @Security Bearer
 | 
				
			||||||
 | 
					// @Router /nodes/{hostname} [post]
 | 
				
			||||||
 | 
					func (a *API) refreshHandler(c echo.Context) error {
 | 
				
			||||||
 | 
						hostname := c.Param("hostname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params := params{}
 | 
				
			||||||
 | 
						err := c.Bind(¶ms)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return c.JSON(http.StatusBadRequest, Message{Message: err.Error()})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a.np.Refresh(hostname, params.Labels, params.KV)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return c.NoContent(http.StatusNoContent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								api/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								api/types.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					package api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "gitea.ceperka.net/rosti/lobby2/nodes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Message struct {
 | 
				
			||||||
 | 
						Message string `json:"message"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type params struct {
 | 
				
			||||||
 | 
						Labels nodes.Labels `json:"labels"`
 | 
				
			||||||
 | 
						KV     nodes.KV     `json:"kv"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								cli/actions.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								cli/actions.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitea.ceperka.net/rosti/lobby2/api"
 | 
				
			||||||
 | 
						"gitea.ceperka.net/rosti/lobby2/nodes"
 | 
				
			||||||
 | 
						"gitea.ceperka.net/rosti/lobby2/refresher"
 | 
				
			||||||
 | 
						"github.com/urfave/cli/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func masterAction(c *cli.Context) error {
 | 
				
			||||||
 | 
						cfg := GetConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np := nodes.NewNodesProcessor(cfg.DumpPath, cfg.DropAfterSeconds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go np.DumpLoop()
 | 
				
			||||||
 | 
						go np.GarbageCollection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						api := api.NewAPI(np, cfg.APIListen, cfg.APIToken)
 | 
				
			||||||
 | 
						api.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func nodeAction(c *cli.Context) error {
 | 
				
			||||||
 | 
						cfg := GetConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r := refresher.NewRefresher(cfg.NodeDirPath, cfg.ConfigPath)
 | 
				
			||||||
 | 
						r.Loop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func printAction(c *cli.Context) error {
 | 
				
			||||||
 | 
						cfg := GetConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np := nodes.NewNodesProcessor(cfg.DumpPath, cfg.DropAfterSeconds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodes := np.List()
 | 
				
			||||||
 | 
						for _, node := range nodes {
 | 
				
			||||||
 | 
							body, err := json.MarshalIndent(node, "", "  ")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								fmt.Printf("failed to marshal node: %v\n", err)
 | 
				
			||||||
 | 
								os.Exit(1)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fmt.Println(string(body))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								cli/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								cli/config.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kelseyhightower/envconfig"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						APIListen string `envconfig:"LISTEN" default:"0.0.0.0:1352"`
 | 
				
			||||||
 | 
						APIToken  string `envconfig:"TOKEN" default:""`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DumpPath    string `envconfig:"DUMP_PATH" default:"/var/lib/lobby2/nodes.json"`
 | 
				
			||||||
 | 
						ConfigPath  string `envconfig:"CONFIG_PATH" default:"/var/lib/lobby2/config.json"`
 | 
				
			||||||
 | 
						NodeDirPath string `envconfig:"NODE_DIR_PATH" default:"/var/lib/lobby2/node"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DropAfterSeconds int64 `envconfig:"DROP_AFTER_SECONDS" default:"60"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetConfig() Config {
 | 
				
			||||||
 | 
						var cfg Config
 | 
				
			||||||
 | 
						err := envconfig.Process("", &cfg)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return cfg
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								cli/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								cli/main.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/urfave/cli/v2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						app := &cli.App{
 | 
				
			||||||
 | 
							Name:  "",
 | 
				
			||||||
 | 
							Usage: "",
 | 
				
			||||||
 | 
							Commands: []*cli.Command{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:   "master",
 | 
				
			||||||
 | 
									Usage:  "Runs master node API",
 | 
				
			||||||
 | 
									Action: masterAction,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:   "node",
 | 
				
			||||||
 | 
									Usage:  "Runs node on local machine",
 | 
				
			||||||
 | 
									Action: nodeAction,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Name:   "print",
 | 
				
			||||||
 | 
									Usage:  "Prints all discovered nodes",
 | 
				
			||||||
 | 
									Action: printAction,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := app.Run(os.Args)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										209
									
								
								docs/docs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								docs/docs.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,209 @@
 | 
				
			||||||
 | 
					// Package docs GENERATED BY SWAG; DO NOT EDIT
 | 
				
			||||||
 | 
					// This file was generated by swaggo/swag
 | 
				
			||||||
 | 
					package docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "github.com/swaggo/swag"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const docTemplate = `{
 | 
				
			||||||
 | 
					    "schemes": {{ marshal .Schemes }},
 | 
				
			||||||
 | 
					    "swagger": "2.0",
 | 
				
			||||||
 | 
					    "info": {
 | 
				
			||||||
 | 
					        "description": "{{escape .Description}}",
 | 
				
			||||||
 | 
					        "title": "{{.Title}}",
 | 
				
			||||||
 | 
					        "contact": {},
 | 
				
			||||||
 | 
					        "version": "{{.Version}}"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "host": "{{.Host}}",
 | 
				
			||||||
 | 
					    "basePath": "{{.BasePath}}",
 | 
				
			||||||
 | 
					    "paths": {
 | 
				
			||||||
 | 
					        "/nodes": {
 | 
				
			||||||
 | 
					            "get": {
 | 
				
			||||||
 | 
					                "security": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "Bearer": []
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "description": "List of all discovered nodes and their labels.",
 | 
				
			||||||
 | 
					                "produces": [
 | 
				
			||||||
 | 
					                    "application/json"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "summary": "List of nodes",
 | 
				
			||||||
 | 
					                "responses": {
 | 
				
			||||||
 | 
					                    "200": {
 | 
				
			||||||
 | 
					                        "description": "List of nodes",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "$ref": "#/definitions/nodes.Node"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "401": {
 | 
				
			||||||
 | 
					                        "description": "Forbidden access",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/api.Message"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "/nodes/{hostname}": {
 | 
				
			||||||
 | 
					            "get": {
 | 
				
			||||||
 | 
					                "security": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "Bearer": []
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "description": "Return one nodes based on given hostname",
 | 
				
			||||||
 | 
					                "produces": [
 | 
				
			||||||
 | 
					                    "application/json"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "summary": "Get node",
 | 
				
			||||||
 | 
					                "parameters": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "type": "string",
 | 
				
			||||||
 | 
					                        "description": "Node hostname",
 | 
				
			||||||
 | 
					                        "name": "hostname",
 | 
				
			||||||
 | 
					                        "in": "path",
 | 
				
			||||||
 | 
					                        "required": true
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "responses": {
 | 
				
			||||||
 | 
					                    "200": {
 | 
				
			||||||
 | 
					                        "description": "Node details",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "$ref": "#/definitions/nodes.Node"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "401": {
 | 
				
			||||||
 | 
					                        "description": "Forbidden access",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/api.Message"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "post": {
 | 
				
			||||||
 | 
					                "security": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "Bearer": []
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "description": "Send new data or update existing data about a node",
 | 
				
			||||||
 | 
					                "produces": [
 | 
				
			||||||
 | 
					                    "application/json"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "summary": "Refresh node",
 | 
				
			||||||
 | 
					                "parameters": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "type": "string",
 | 
				
			||||||
 | 
					                        "description": "Node hostname",
 | 
				
			||||||
 | 
					                        "name": "hostname",
 | 
				
			||||||
 | 
					                        "in": "path",
 | 
				
			||||||
 | 
					                        "required": true
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "description": "Node labels",
 | 
				
			||||||
 | 
					                        "name": "labels",
 | 
				
			||||||
 | 
					                        "in": "body",
 | 
				
			||||||
 | 
					                        "required": true,
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "type": "string"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "description": "Key-value",
 | 
				
			||||||
 | 
					                        "name": "kv",
 | 
				
			||||||
 | 
					                        "in": "body",
 | 
				
			||||||
 | 
					                        "required": true,
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/nodes.KV"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "responses": {
 | 
				
			||||||
 | 
					                    "200": {
 | 
				
			||||||
 | 
					                        "description": "Node details",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "$ref": "#/definitions/nodes.Node"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "401": {
 | 
				
			||||||
 | 
					                        "description": "Forbidden access",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/api.Message"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "definitions": {
 | 
				
			||||||
 | 
					        "api.Message": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "message": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "nodes.KV": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "additionalProperties": {
 | 
				
			||||||
 | 
					                "type": "string"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "nodes.Node": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "hostname": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "kv": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/nodes.KV"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "labels": {
 | 
				
			||||||
 | 
					                    "type": "array",
 | 
				
			||||||
 | 
					                    "items": {
 | 
				
			||||||
 | 
					                        "type": "string"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "last_update": {
 | 
				
			||||||
 | 
					                    "type": "integer"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "securityDefinitions": {
 | 
				
			||||||
 | 
					        "Bearer": {
 | 
				
			||||||
 | 
					            "type": "apiKey",
 | 
				
			||||||
 | 
					            "name": "Authorization",
 | 
				
			||||||
 | 
					            "in": "header"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SwaggerInfo holds exported Swagger Info so clients can modify it
 | 
				
			||||||
 | 
					var SwaggerInfo = &swag.Spec{
 | 
				
			||||||
 | 
						Version:          "2.0",
 | 
				
			||||||
 | 
						Host:             "",
 | 
				
			||||||
 | 
						BasePath:         "/",
 | 
				
			||||||
 | 
						Schemes:          []string{},
 | 
				
			||||||
 | 
						Title:            "Lobby2 API",
 | 
				
			||||||
 | 
						Description:      "API of Lobby 2 project that helps to discover and connect to other nodes and their services.",
 | 
				
			||||||
 | 
						InfoInstanceName: "swagger",
 | 
				
			||||||
 | 
						SwaggerTemplate:  docTemplate,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										185
									
								
								docs/swagger.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								docs/swagger.json
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,185 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    "swagger": "2.0",
 | 
				
			||||||
 | 
					    "info": {
 | 
				
			||||||
 | 
					        "description": "API of Lobby 2 project that helps to discover and connect to other nodes and their services.",
 | 
				
			||||||
 | 
					        "title": "Lobby2 API",
 | 
				
			||||||
 | 
					        "contact": {},
 | 
				
			||||||
 | 
					        "version": "2.0"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "basePath": "/",
 | 
				
			||||||
 | 
					    "paths": {
 | 
				
			||||||
 | 
					        "/nodes": {
 | 
				
			||||||
 | 
					            "get": {
 | 
				
			||||||
 | 
					                "security": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "Bearer": []
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "description": "List of all discovered nodes and their labels.",
 | 
				
			||||||
 | 
					                "produces": [
 | 
				
			||||||
 | 
					                    "application/json"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "summary": "List of nodes",
 | 
				
			||||||
 | 
					                "responses": {
 | 
				
			||||||
 | 
					                    "200": {
 | 
				
			||||||
 | 
					                        "description": "List of nodes",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "$ref": "#/definitions/nodes.Node"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "401": {
 | 
				
			||||||
 | 
					                        "description": "Forbidden access",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/api.Message"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "/nodes/{hostname}": {
 | 
				
			||||||
 | 
					            "get": {
 | 
				
			||||||
 | 
					                "security": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "Bearer": []
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "description": "Return one nodes based on given hostname",
 | 
				
			||||||
 | 
					                "produces": [
 | 
				
			||||||
 | 
					                    "application/json"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "summary": "Get node",
 | 
				
			||||||
 | 
					                "parameters": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "type": "string",
 | 
				
			||||||
 | 
					                        "description": "Node hostname",
 | 
				
			||||||
 | 
					                        "name": "hostname",
 | 
				
			||||||
 | 
					                        "in": "path",
 | 
				
			||||||
 | 
					                        "required": true
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "responses": {
 | 
				
			||||||
 | 
					                    "200": {
 | 
				
			||||||
 | 
					                        "description": "Node details",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "$ref": "#/definitions/nodes.Node"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "401": {
 | 
				
			||||||
 | 
					                        "description": "Forbidden access",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/api.Message"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "post": {
 | 
				
			||||||
 | 
					                "security": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "Bearer": []
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "description": "Send new data or update existing data about a node",
 | 
				
			||||||
 | 
					                "produces": [
 | 
				
			||||||
 | 
					                    "application/json"
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "summary": "Refresh node",
 | 
				
			||||||
 | 
					                "parameters": [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "type": "string",
 | 
				
			||||||
 | 
					                        "description": "Node hostname",
 | 
				
			||||||
 | 
					                        "name": "hostname",
 | 
				
			||||||
 | 
					                        "in": "path",
 | 
				
			||||||
 | 
					                        "required": true
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "description": "Node labels",
 | 
				
			||||||
 | 
					                        "name": "labels",
 | 
				
			||||||
 | 
					                        "in": "body",
 | 
				
			||||||
 | 
					                        "required": true,
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "type": "string"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        "description": "Key-value",
 | 
				
			||||||
 | 
					                        "name": "kv",
 | 
				
			||||||
 | 
					                        "in": "body",
 | 
				
			||||||
 | 
					                        "required": true,
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/nodes.KV"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					                "responses": {
 | 
				
			||||||
 | 
					                    "200": {
 | 
				
			||||||
 | 
					                        "description": "Node details",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "type": "array",
 | 
				
			||||||
 | 
					                            "items": {
 | 
				
			||||||
 | 
					                                "$ref": "#/definitions/nodes.Node"
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    "401": {
 | 
				
			||||||
 | 
					                        "description": "Forbidden access",
 | 
				
			||||||
 | 
					                        "schema": {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/api.Message"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "definitions": {
 | 
				
			||||||
 | 
					        "api.Message": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "message": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "nodes.KV": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "additionalProperties": {
 | 
				
			||||||
 | 
					                "type": "string"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "nodes.Node": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "hostname": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "kv": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/nodes.KV"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "labels": {
 | 
				
			||||||
 | 
					                    "type": "array",
 | 
				
			||||||
 | 
					                    "items": {
 | 
				
			||||||
 | 
					                        "type": "string"
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "last_update": {
 | 
				
			||||||
 | 
					                    "type": "integer"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "securityDefinitions": {
 | 
				
			||||||
 | 
					        "Bearer": {
 | 
				
			||||||
 | 
					            "type": "apiKey",
 | 
				
			||||||
 | 
					            "name": "Authorization",
 | 
				
			||||||
 | 
					            "in": "header"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										119
									
								
								docs/swagger.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								docs/swagger.yaml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,119 @@
 | 
				
			||||||
 | 
					basePath: /
 | 
				
			||||||
 | 
					definitions:
 | 
				
			||||||
 | 
					  api.Message:
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      message:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					  nodes.KV:
 | 
				
			||||||
 | 
					    additionalProperties:
 | 
				
			||||||
 | 
					      type: string
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					  nodes.Node:
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      hostname:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      kv:
 | 
				
			||||||
 | 
					        $ref: '#/definitions/nodes.KV'
 | 
				
			||||||
 | 
					      labels:
 | 
				
			||||||
 | 
					        items:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					        type: array
 | 
				
			||||||
 | 
					      last_update:
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					info:
 | 
				
			||||||
 | 
					  contact: {}
 | 
				
			||||||
 | 
					  description: API of Lobby 2 project that helps to discover and connect to other
 | 
				
			||||||
 | 
					    nodes and their services.
 | 
				
			||||||
 | 
					  title: Lobby2 API
 | 
				
			||||||
 | 
					  version: "2.0"
 | 
				
			||||||
 | 
					paths:
 | 
				
			||||||
 | 
					  /nodes:
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      description: List of all discovered nodes and their labels.
 | 
				
			||||||
 | 
					      produces:
 | 
				
			||||||
 | 
					      - application/json
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        "200":
 | 
				
			||||||
 | 
					          description: List of nodes
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            items:
 | 
				
			||||||
 | 
					              $ref: '#/definitions/nodes.Node'
 | 
				
			||||||
 | 
					            type: array
 | 
				
			||||||
 | 
					        "401":
 | 
				
			||||||
 | 
					          description: Forbidden access
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            $ref: '#/definitions/api.Message'
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					      - Bearer: []
 | 
				
			||||||
 | 
					      summary: List of nodes
 | 
				
			||||||
 | 
					  /nodes/{hostname}:
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      description: Return one nodes based on given hostname
 | 
				
			||||||
 | 
					      parameters:
 | 
				
			||||||
 | 
					      - description: Node hostname
 | 
				
			||||||
 | 
					        in: path
 | 
				
			||||||
 | 
					        name: hostname
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      produces:
 | 
				
			||||||
 | 
					      - application/json
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        "200":
 | 
				
			||||||
 | 
					          description: Node details
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            items:
 | 
				
			||||||
 | 
					              $ref: '#/definitions/nodes.Node'
 | 
				
			||||||
 | 
					            type: array
 | 
				
			||||||
 | 
					        "401":
 | 
				
			||||||
 | 
					          description: Forbidden access
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            $ref: '#/definitions/api.Message'
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					      - Bearer: []
 | 
				
			||||||
 | 
					      summary: Get node
 | 
				
			||||||
 | 
					    post:
 | 
				
			||||||
 | 
					      description: Send new data or update existing data about a node
 | 
				
			||||||
 | 
					      parameters:
 | 
				
			||||||
 | 
					      - description: Node hostname
 | 
				
			||||||
 | 
					        in: path
 | 
				
			||||||
 | 
					        name: hostname
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      - description: Node labels
 | 
				
			||||||
 | 
					        in: body
 | 
				
			||||||
 | 
					        name: labels
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        schema:
 | 
				
			||||||
 | 
					          items:
 | 
				
			||||||
 | 
					            type: string
 | 
				
			||||||
 | 
					          type: array
 | 
				
			||||||
 | 
					      - description: Key-value
 | 
				
			||||||
 | 
					        in: body
 | 
				
			||||||
 | 
					        name: kv
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        schema:
 | 
				
			||||||
 | 
					          $ref: '#/definitions/nodes.KV'
 | 
				
			||||||
 | 
					      produces:
 | 
				
			||||||
 | 
					      - application/json
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        "200":
 | 
				
			||||||
 | 
					          description: Node details
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            items:
 | 
				
			||||||
 | 
					              $ref: '#/definitions/nodes.Node'
 | 
				
			||||||
 | 
					            type: array
 | 
				
			||||||
 | 
					        "401":
 | 
				
			||||||
 | 
					          description: Forbidden access
 | 
				
			||||||
 | 
					          schema:
 | 
				
			||||||
 | 
					            $ref: '#/definitions/api.Message'
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					      - Bearer: []
 | 
				
			||||||
 | 
					      summary: Refresh node
 | 
				
			||||||
 | 
					securityDefinitions:
 | 
				
			||||||
 | 
					  Bearer:
 | 
				
			||||||
 | 
					    in: header
 | 
				
			||||||
 | 
					    name: Authorization
 | 
				
			||||||
 | 
					    type: apiKey
 | 
				
			||||||
 | 
					swagger: "2.0"
 | 
				
			||||||
							
								
								
									
										46
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					module gitea.ceperka.net/rosti/lobby2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/kelseyhightower/envconfig v1.4.0
 | 
				
			||||||
 | 
						github.com/labstack/echo/v4 v4.12.0
 | 
				
			||||||
 | 
						github.com/puzpuzpuz/xsync/v3 v3.4.0
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.8.4
 | 
				
			||||||
 | 
						github.com/swaggo/echo-swagger v1.4.1
 | 
				
			||||||
 | 
						github.com/swaggo/swag v1.16.3
 | 
				
			||||||
 | 
						github.com/urfave/cli/v2 v2.3.0
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/KyleBanks/depth v1.2.1 // indirect
 | 
				
			||||||
 | 
						github.com/PuerkitoBio/purell v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
 | 
				
			||||||
 | 
						github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
 | 
				
			||||||
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/ghodss/yaml v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/jsonpointer v0.19.5 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/jsonreference v0.19.6 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/spec v0.20.4 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag v0.19.15 // indirect
 | 
				
			||||||
 | 
						github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
 | 
				
			||||||
 | 
						github.com/josharian/intern v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/labstack/gommon v0.4.2 // indirect
 | 
				
			||||||
 | 
						github.com/mailru/easyjson v0.7.7 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-isatty v0.0.20 // indirect
 | 
				
			||||||
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/russross/blackfriday/v2 v2.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/swaggo/files/v2 v2.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/valyala/bytebufferpool v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/valyala/fasttemplate v1.2.2 // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.22.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/net v0.24.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.19.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.14.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/time v0.5.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/tools v0.7.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										111
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,111 @@
 | 
				
			||||||
 | 
					github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
				
			||||||
 | 
					github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
 | 
				
			||||||
 | 
					github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
 | 
				
			||||||
 | 
					github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
 | 
				
			||||||
 | 
					github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 | 
				
			||||||
 | 
					github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 | 
				
			||||||
 | 
					github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 | 
				
			||||||
 | 
					github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 | 
				
			||||||
 | 
					github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 | 
				
			||||||
 | 
					github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 | 
				
			||||||
 | 
					github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
 | 
				
			||||||
 | 
					github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
 | 
				
			||||||
 | 
					github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
 | 
				
			||||||
 | 
					github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
 | 
				
			||||||
 | 
					github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 | 
				
			||||||
 | 
					github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 | 
				
			||||||
 | 
					github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
				
			||||||
 | 
					github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
 | 
				
			||||||
 | 
					github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
				
			||||||
 | 
					github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
				
			||||||
 | 
					github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
				
			||||||
 | 
					github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
 | 
				
			||||||
 | 
					github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
 | 
				
			||||||
 | 
					github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
 | 
				
			||||||
 | 
					github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
				
			||||||
 | 
					github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 | 
				
			||||||
 | 
					github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
 | 
				
			||||||
 | 
					github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
 | 
				
			||||||
 | 
					github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 | 
				
			||||||
 | 
					github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
				
			||||||
 | 
					github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 | 
				
			||||||
 | 
					github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
				
			||||||
 | 
					github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
 | 
				
			||||||
 | 
					github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
 | 
				
			||||||
 | 
					github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
 | 
				
			||||||
 | 
					github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
 | 
				
			||||||
 | 
					github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
 | 
				
			||||||
 | 
					github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
 | 
				
			||||||
 | 
					github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 | 
				
			||||||
 | 
					github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 | 
				
			||||||
 | 
					github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 | 
				
			||||||
 | 
					github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 | 
				
			||||||
 | 
					github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
 | 
				
			||||||
 | 
					github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 | 
				
			||||||
 | 
					golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
 | 
				
			||||||
 | 
					golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
				
			||||||
 | 
					golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 | 
				
			||||||
 | 
					golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
							
								
								
									
										176
									
								
								nodes/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								nodes/main.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,176 @@
 | 
				
			||||||
 | 
					package nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/puzpuzpuz/xsync/v3"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dumpIntervalSeconds = 120
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Base nodes structure that covers all operations above nodes.
 | 
				
			||||||
 | 
					type NodesProcessor struct {
 | 
				
			||||||
 | 
						nodes                      *xsync.MapOf[string, Node]
 | 
				
			||||||
 | 
						dumpPath                   string
 | 
				
			||||||
 | 
						garbageCollectAfterSeconds int64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewNodesProcessor(dumpPath string, garbageCollectAfterSeconds int64) *NodesProcessor {
 | 
				
			||||||
 | 
						p := &NodesProcessor{
 | 
				
			||||||
 | 
							dumpPath:                   dumpPath,
 | 
				
			||||||
 | 
							garbageCollectAfterSeconds: garbageCollectAfterSeconds,
 | 
				
			||||||
 | 
							nodes:                      xsync.NewMapOf[string, Node](),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := p.Load()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("failed to load nodes: %v\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (np *NodesProcessor) DumpLoop() {
 | 
				
			||||||
 | 
						log.Println(".. starting dump loop")
 | 
				
			||||||
 | 
						time.Sleep(time.Second * dumpIntervalSeconds)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							log.Println(".. dumping nodes")
 | 
				
			||||||
 | 
							err := np.Dump()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("failed to dump nodes: %v\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							time.Sleep(time.Second * dumpIntervalSeconds)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GarbageCollection removes nodes that haven't been updated for more than garbageCollectAfterSeconds.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) GarbageCollection() {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							np.garbageCollect()
 | 
				
			||||||
 | 
							time.Sleep(time.Second * 10)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (np *NodesProcessor) garbageCollect() {
 | 
				
			||||||
 | 
						np.nodes.Range(func(key string, value Node) bool {
 | 
				
			||||||
 | 
							if time.Now().Unix()-value.LastUpdate > np.garbageCollectAfterSeconds {
 | 
				
			||||||
 | 
								np.nodes.Delete(value.HostName)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Dumps content of np.nodes to np.dumpPath.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) Dump() error {
 | 
				
			||||||
 | 
						ns := []Node{}
 | 
				
			||||||
 | 
						np.nodes.Range(func(key string, value Node) bool {
 | 
				
			||||||
 | 
							ns = append(ns, value)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, err := json.Marshal(ns)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to marshal nodes: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create directory if it doesn't exist.
 | 
				
			||||||
 | 
						if strings.Contains(np.dumpPath, "/") {
 | 
				
			||||||
 | 
							path := np.dumpPath[:strings.LastIndex(np.dumpPath, "/")]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(path) != 0 {
 | 
				
			||||||
 | 
								err = os.MkdirAll(path, 0755)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return fmt.Errorf("failed to create directory for nodes.json: %w", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = os.WriteFile(np.dumpPath, body, 0644)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to write nodes.json: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Loads content of np.dumpPath to np.nodes.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) Load() error {
 | 
				
			||||||
 | 
						filePath := path.Join(np.dumpPath)
 | 
				
			||||||
 | 
						_, err := os.Stat(filePath)
 | 
				
			||||||
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						body, err := os.ReadFile(np.dumpPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to read nodes.json: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ns := []Node{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = json.Unmarshal(body, &ns)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to unmarshal nodes: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, n := range ns {
 | 
				
			||||||
 | 
							np.nodes.Store(n.HostName, n)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (np *NodesProcessor) Reset() {
 | 
				
			||||||
 | 
						np.nodes.Clear()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns string with Prometheus metrics
 | 
				
			||||||
 | 
					// TODO: finish this
 | 
				
			||||||
 | 
					func (np *NodesProcessor) GetMetrics() string {
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// List returns all nodes.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) List() Nodes {
 | 
				
			||||||
 | 
						nodes := Nodes{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.nodes.Range(func(key string, value Node) bool {
 | 
				
			||||||
 | 
							nodes = append(nodes, value)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nodes
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get returns a node by hostname.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) Get(hostname string) (Node, bool) {
 | 
				
			||||||
 | 
						node, ok := np.nodes.Load(hostname)
 | 
				
			||||||
 | 
						return node, ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Refresh updates node with hostname and labels. If it doesn't exists it's created.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) Refresh(hostname string, labels Labels, kv KV) {
 | 
				
			||||||
 | 
						node, ok := np.nodes.Load(hostname)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							node = Node{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						node.LastUpdate = time.Now().Unix()
 | 
				
			||||||
 | 
						node.HostName = hostname
 | 
				
			||||||
 | 
						node.Labels = labels
 | 
				
			||||||
 | 
						node.KV = kv
 | 
				
			||||||
 | 
						np.nodes.Store(hostname, node)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Drop removes node with given hostname.
 | 
				
			||||||
 | 
					func (np *NodesProcessor) Drop(hostname string) {
 | 
				
			||||||
 | 
						np.nodes.Delete(hostname)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								nodes/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								nodes/main_test.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,92 @@
 | 
				
			||||||
 | 
					package nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var np *NodesProcessor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const testDumpFile = "nodes.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMain(m *testing.M) {
 | 
				
			||||||
 | 
						np = NewNodesProcessor(testDumpFile, 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						os.Exit(m.Run())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodesProcessor_GarbageCollect(t *testing.T) {
 | 
				
			||||||
 | 
						np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
 | 
				
			||||||
 | 
						assert.Equal(t, 1, len(np.List()))
 | 
				
			||||||
 | 
						time.Sleep(5 * time.Second)
 | 
				
			||||||
 | 
						np.garbageCollect()
 | 
				
			||||||
 | 
						assert.Equal(t, 0, len(np.List()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodesProcessor_Refresh(t *testing.T) {
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
 | 
				
			||||||
 | 
						np.Refresh("test2", Labels{"mylabel3"}, KV{"mykey3": "my value3"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodes := np.List()
 | 
				
			||||||
 | 
						assert.Equal(t, "test", nodes[0].HostName)
 | 
				
			||||||
 | 
						assert.Contains(t, nodes[0].Labels, "mylabel")
 | 
				
			||||||
 | 
						assert.Equal(t, "test2", nodes[1].HostName)
 | 
				
			||||||
 | 
						assert.Contains(t, nodes[1].Labels, "mylabel3")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodesProcessor_DumpLoad(t *testing.T) {
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
 | 
				
			||||||
 | 
						np.Refresh("test2", Labels{"mylabel3"}, KV{"mykey3": "my value3"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						os.Remove(testDumpFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Dump()
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
						assert.Equal(t, 0, len(np.List()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Load()
 | 
				
			||||||
 | 
						assert.Equal(t, 2, len(np.List()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodes := np.List()
 | 
				
			||||||
 | 
						assert.Len(t, nodes, 2)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodesProcessor_List(t *testing.T) {
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
 | 
				
			||||||
 | 
						nodes := np.List()
 | 
				
			||||||
 | 
						assert.Equal(t, "test", nodes[0].HostName)
 | 
				
			||||||
 | 
						assert.Contains(t, nodes[0].Labels, "mylabel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodesProcessor_Get(t *testing.T) {
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
						np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
 | 
				
			||||||
 | 
						node, _ := np.Get("test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Contains(t, node.Labels, "mylabel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNodesProcessor_Drop(t *testing.T) {
 | 
				
			||||||
 | 
						np.Reset()
 | 
				
			||||||
 | 
						np.Refresh("test", Labels{"mylabel"}, KV{"mykey": "my value"})
 | 
				
			||||||
 | 
						node, _ := np.Get("test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Contains(t, node.Labels, "mylabel")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						np.Drop("test")
 | 
				
			||||||
 | 
						assert.Zero(t, len(np.List()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: TestNodesProcessor_GetMetrics
 | 
				
			||||||
							
								
								
									
										14
									
								
								nodes/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								nodes/types.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					package nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type KV map[string]string
 | 
				
			||||||
 | 
					type Labels []string
 | 
				
			||||||
 | 
					type JSONData map[string]interface{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Node struct {
 | 
				
			||||||
 | 
						LastUpdate int64  `json:"last_update,omitempty"`
 | 
				
			||||||
 | 
						HostName   string `json:"hostname"`
 | 
				
			||||||
 | 
						Labels     Labels `json:"labels,omitempty"`
 | 
				
			||||||
 | 
						KV         KV     `json:"kv,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Nodes []Node
 | 
				
			||||||
							
								
								
									
										178
									
								
								refresher/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								refresher/main.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,178 @@
 | 
				
			||||||
 | 
					package refresher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitea.ceperka.net/rosti/lobby2/nodes"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const refreshIntervalSeconds = 15
 | 
				
			||||||
 | 
					const nodeFileName = "node.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Refresher loads local node info and sends them to the master node.
 | 
				
			||||||
 | 
					type Refresher struct {
 | 
				
			||||||
 | 
						configPath  string
 | 
				
			||||||
 | 
						nodeDirPath string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewRefresher(nodeDirPath string, configPath string) *Refresher {
 | 
				
			||||||
 | 
						return &Refresher{
 | 
				
			||||||
 | 
							nodeDirPath: nodeDirPath,
 | 
				
			||||||
 | 
							configPath:  configPath,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run loop the process that updates node in the master node.
 | 
				
			||||||
 | 
					func (r *Refresher) Loop() {
 | 
				
			||||||
 | 
						log.Println("Start refresher loop")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							log.Println("Refreshing node")
 | 
				
			||||||
 | 
							err := r.Refresh()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("failed to refresh node: %v\n", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							time.Sleep(time.Second * refreshIntervalSeconds)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Refresh loads labels from the local filesystem and sends them to the master node.
 | 
				
			||||||
 | 
					func (r *Refresher) Refresh() error {
 | 
				
			||||||
 | 
						// Load config
 | 
				
			||||||
 | 
						cfg, err := r.getConfig()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load labels
 | 
				
			||||||
 | 
						node, err := r.loadNode()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodeBytes, err := json.Marshal(node)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to marshal labels: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send labels to master
 | 
				
			||||||
 | 
						req, err := http.NewRequest("POST", fmt.Sprintf("%s://%s:%d/nodes/%s", cfg.MasterProto, cfg.MasterHost, cfg.MasterPort, node.HostName), bytes.NewBuffer(nodeBytes))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req.Header.Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
						req.Header.Set("Authorization", "Bearer "+cfg.MasterToken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client := &http.Client{}
 | 
				
			||||||
 | 
						resp, err := client.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer resp.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if resp.StatusCode != http.StatusNoContent {
 | 
				
			||||||
 | 
							return fmt.Errorf("unexpected status: %s", resp.Status)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns the node config.
 | 
				
			||||||
 | 
					func (r *Refresher) getConfig() (NodeConfig, error) {
 | 
				
			||||||
 | 
						cfg := NodeConfig{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content, err := os.ReadFile(r.configPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return cfg, fmt.Errorf("failed to read config file: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = json.Unmarshal(content, &cfg)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return cfg, fmt.Errorf("failed to unmarshal config: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cfg.MasterProto == "" {
 | 
				
			||||||
 | 
							cfg.MasterProto = "http"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cfg.MasterPort == 0 {
 | 
				
			||||||
 | 
							cfg.MasterPort = 1352
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return cfg, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: rewrite this to load Node structure
 | 
				
			||||||
 | 
					func (r *Refresher) loadNode() (*nodes.Node, error) {
 | 
				
			||||||
 | 
						filePath := path.Join(r.nodeDirPath, nodeFileName)
 | 
				
			||||||
 | 
						_, err := os.Stat(filePath)
 | 
				
			||||||
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							err = r.initNodeFile()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						content, err := os.ReadFile(path.Join(r.nodeDirPath, nodeFileName))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to read file: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node := &nodes.Node{}
 | 
				
			||||||
 | 
						err = json.Unmarshal(content, &node)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to unmarshal node: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return node, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Refresher) initNodeFile() error {
 | 
				
			||||||
 | 
						err := r.createNodePath()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to create node path: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hostname, err := os.Hostname()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to get hostname: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						node := &nodes.Node{
 | 
				
			||||||
 | 
							HostName: hostname,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nodeBytes, err := json.MarshalIndent(node, "", "  ")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to marshal node: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = os.WriteFile(path.Join(r.nodeDirPath, nodeFileName), nodeBytes, 0640)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("failed to write node file: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Refresher) createNodePath() error {
 | 
				
			||||||
 | 
						_, err := os.Stat(r.nodeDirPath)
 | 
				
			||||||
 | 
						if os.IsNotExist(err) {
 | 
				
			||||||
 | 
							err = os.MkdirAll(r.nodeDirPath, 0755)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("failed to create node dir: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										8
									
								
								refresher/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								refresher/types.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					package refresher
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type NodeConfig struct {
 | 
				
			||||||
 | 
						MasterHost  string `json:"master_host"`
 | 
				
			||||||
 | 
						MasterProto string `json:"master_proto"`
 | 
				
			||||||
 | 
						MasterToken string `json:"master_token"`
 | 
				
			||||||
 | 
						MasterPort  int    `json:"master_port"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in a new issue