Refactoring

Discovery creation move into server package
Tests for server package
This commit is contained in:
Adam Štrauch 2021-09-04 14:16:14 +02:00
parent 07a70b8285
commit 4c5cb1bd85
Signed by: cx
GPG Key ID: 018304FFA8988F8D
11 changed files with 149 additions and 56 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# Files with secrets # Files with secrets
*secret* *secret*
tmp/
# Binaries # Binaries
lobby_* lobby_*

View File

@ -4,6 +4,7 @@ import (
"log" "log"
"github.com/kelseyhightower/envconfig" "github.com/kelseyhightower/envconfig"
"github.com/rosti-cz/server_lobby/server"
) )
// Config keeps info about configuration of this daemon // Config keeps info about configuration of this daemon
@ -13,7 +14,7 @@ type Config struct {
Port uint16 `envconfig:"PORT" required:"false" default:"1313"` // Port related to the address above Port uint16 `envconfig:"PORT" required:"false" default:"1313"` // Port related to the address above
NATSURL string `envconfig:"NATS_URL" required:"true"` // NATS URL used to connect to the NATS server NATSURL string `envconfig:"NATS_URL" required:"true"` // NATS URL used to connect to the NATS server
NATSDiscoveryChannel string `envconfig:"NATS_DISCOVERY_CHANNEL" required:"false" default:"lobby.discovery"` // Channel where the kepp alive packets are sent NATSDiscoveryChannel string `envconfig:"NATS_DISCOVERY_CHANNEL" required:"false" default:"lobby.discovery"` // Channel where the kepp alive packets are sent
Labels []string `envconfig:"LABELS" required:"false" default:""` // List of labels Labels server.Labels `envconfig:"LABELS" required:"false" default:""` // List of labels
LabelsPath string `envconfig:"LABELS_PATH" required:"false" default:"/etc/lobby/labels"` // Path where filesystem based labels are located LabelsPath string `envconfig:"LABELS_PATH" required:"false" default:"/etc/lobby/labels"` // Path where filesystem based labels are located
HostName string `envconfig:"HOSTNAME" required:"false"` // Overrise local machine's hostname HostName string `envconfig:"HOSTNAME" required:"false"` // Overrise local machine's hostname
CleanEvery uint `envconfig:"CLEAN_EVERY" required:"false" default:"15"` // How often to clean the list of servers to get rid of the not alive ones CleanEvery uint `envconfig:"CLEAN_EVERY" required:"false" default:"15"` // How often to clean the list of servers to get rid of the not alive ones

View File

@ -52,7 +52,7 @@ func cleanDiscoveryPool() {
// sendGoodbyePacket is almost same as sendDiscoveryPacket but it's not running in loop // sendGoodbyePacket is almost same as sendDiscoveryPacket but it's not running in loop
// and it adds goodbye message so other nodes know this node is gonna die. // and it adds goodbye message so other nodes know this node is gonna die.
func sendGoodbyePacket() { func sendGoodbyePacket() {
discovery, err := getIdentification() discovery, err := server.GetIdentification(config.HostName, config.Labels, config.LabelsPath)
if err != nil { if err != nil {
log.Printf("sending discovery identification error: %v\n", err) log.Printf("sending discovery identification error: %v\n", err)
} }
@ -66,7 +66,7 @@ func sendGoodbyePacket() {
// sendDisoveryPacket sends discovery packet regularly so the network know we exist // sendDisoveryPacket sends discovery packet regularly so the network know we exist
func sendDiscoveryPacket() { func sendDiscoveryPacket() {
for { for {
discovery, err := getIdentification() discovery, err := server.GetIdentification(config.HostName, config.Labels, config.LabelsPath)
if err != nil { if err != nil {
log.Printf("sending discovery identification error: %v\n", err) log.Printf("sending discovery identification error: %v\n", err)
} }

View File

@ -39,8 +39,8 @@ func preparePrometheusOutput(name string, discoveries []server.Discovery) Promet
labels := map[string]string{} labels := map[string]string{}
for _, label := range discovery.FindLabels("prometheus:" + name) { for _, label := range discovery.FindLabels("prometheus:" + name + ":") {
trimmed := strings.TrimPrefix(label, "prometheus:"+name+":") trimmed := strings.TrimPrefix(label.String(), "prometheus:"+name+":")
parts := strings.SplitN(trimmed, ":", 2) parts := strings.SplitN(trimmed, ":", 2)
if len(parts) == 2 { if len(parts) == 2 {
if parts[0] == "port" { if parts[0] == "port" {
@ -57,7 +57,7 @@ func preparePrometheusOutput(name string, discoveries []server.Discovery) Promet
// This has to be checked here again because FindLabels adds : at the end of the label name. // This has to be checked here again because FindLabels adds : at the end of the label name.
if !add { if !add {
for _, label := range discovery.Labels { for _, label := range discovery.Labels {
if label == "prometheus:"+name { if label.String() == "prometheus:"+name {
add = true add = true
break break
} }

1
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/nats-io/nats-server/v2 v2.4.0 // indirect github.com/nats-io/nats-server/v2 v2.4.0 // indirect
github.com/nats-io/nats.go v1.12.0 github.com/nats-io/nats.go v1.12.0
github.com/shirou/gopsutil/v3 v3.21.7 github.com/shirou/gopsutil/v3 v3.21.7
github.com/stretchr/testify v1.7.0
github.com/valyala/fasttemplate v1.2.1 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect

1
go.sum
View File

@ -103,6 +103,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

View File

@ -16,7 +16,7 @@ const TimeToLife = 60 // when server won't occur in the discovery channel longer
// Discovery contains information about a single server and is used for server discovery // Discovery contains information about a single server and is used for server discovery
type Discovery struct { type Discovery struct {
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
Labels []string `json:"labels"` Labels Labels `json:"labels"`
// For internal use to check if the server is still alive. // For internal use to check if the server is still alive.
// Contains timestamp of the last check. // Contains timestamp of the last check.
@ -44,11 +44,12 @@ func (d *Discovery) Bytes() ([]byte, error) {
return data, err return data, err
} }
// FindLabels returns list of labels with given prefix. For example "service:ns" has prefix "service" // FindLabels returns list of labels with given prefix. For example "service:ns" has prefix "service" or "service:".
func (d *Discovery) FindLabels(prefix string) []string { // It doesn't have to be prefix, but for example "service:test" will match "service:test" and also "service:test2".
labels := []string{} func (d *Discovery) FindLabels(prefix string) Labels {
labels := Labels{}
for _, label := range d.Labels { for _, label := range d.Labels {
if strings.HasPrefix(label, prefix+":") { if strings.HasPrefix(label.String(), prefix) {
labels = append(labels, label) labels = append(labels, label)
} }
} }
@ -156,7 +157,7 @@ func (d *Discoveries) Filter(labelsFilter []string) []Discovery {
found = false found = false
for _, label := range discovery.Labels { for _, label := range discovery.Labels {
for _, labelFilter := range labelsFilter { for _, labelFilter := range labelsFilter {
if label == labelFilter { if label.String() == labelFilter {
newSet = append(newSet, discovery) newSet = append(newSet, discovery)
found = true found = true
break break

38
server/discovery_test.go Normal file
View File

@ -0,0 +1,38 @@
package server
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestDiscovery(t *testing.T) {
now := time.Now().Unix()
now90 := now - 90
discovery := Discovery{
Hostname: "test.rosti.cz",
Labels: Labels{
Label("service:test"),
Label("test:123"),
Label("public_ip:1.2.3.4"),
},
LastCheck: now,
}
assert.True(t, discovery.IsAlive(), "discovery suppose to be alive")
discovery.LastCheck = now90
assert.False(t, discovery.IsAlive(), "discovery not suppose to be alive")
discovery.LastCheck = now
assert.Equal(t, Labels{Label("service:test")}, discovery.FindLabels("service"))
assert.Equal(t, nil, discovery.Validate()) // TODO: This needs more love
content, err := json.Marshal(&discovery)
assert.Nil(t, err)
content2, err := discovery.Bytes()
assert.Nil(t, err)
assert.Equal(t, content, content2)
}

View File

@ -1,89 +1,80 @@
package main package server
import ( import (
"bufio"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"strings" "strings"
"github.com/rosti-cz/server_lobby/server"
"github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/host"
) )
// getIdentification assembles the discovery packet that contains hotname and set of labels describing a single server, in this case the local server. // getIdentification assembles the discovery packet that contains hotname and set of labels describing a single server, in this case the local server.
func getIdentification() (server.Discovery, error) { // Parameter initialLabels usually coming from configuration of the app.
discovery := server.Discovery{} // If hostname is empty it will be discovered automatically.
func GetIdentification(hostname string, initialLabels Labels, labelsPath string) (Discovery, error) {
discovery := Discovery{}
localLabels, err := loadLocalLabels(config.Labels) localLabels, err := loadLocalLabels(initialLabels, labelsPath)
if err != nil { if err != nil {
return discovery, err return discovery, err
} }
if len(config.HostName) == 0 { if len(hostname) == 0 {
info, err := host.Info() info, err := host.Info()
if err != nil { if err != nil {
return discovery, err return discovery, err
} }
discovery.Hostname = info.Hostname discovery.Hostname = info.Hostname
} else { } else {
discovery.Hostname = config.HostName discovery.Hostname = hostname
} }
discovery.Labels = append(config.Labels, localLabels...) discovery.Labels = append(initialLabels, localLabels...)
return discovery, nil return discovery, nil
} }
// loadLocalLabels scans local directory where labels are stored and adds them to the labels configured as environment variables. // loadLocalLabels scans local directory where labels are stored and adds them to the labels configured as environment variables.
// Filename in LabelsPath is not importent and each file can contain multiple labels, one per each line. // Filename in LabelsPath is not importent and each file can contain multiple labels, one per each line.
func loadLocalLabels(skipLabels []string) ([]string, error) { func loadLocalLabels(skipLabels Labels, labelsPath string) (Labels, error) {
labels := []string{} labels := Labels{}
var found bool var found bool
if _, err := os.Stat(config.LabelsPath); !os.IsNotExist(err) { if _, err := os.Stat(labelsPath); !os.IsNotExist(err) {
files, err := ioutil.ReadDir(config.LabelsPath) files, err := ioutil.ReadDir(labelsPath)
if err != nil { if err != nil {
return labels, err return labels, err
} }
for _, filename := range files { for _, filename := range files {
fullPath := path.Join(config.LabelsPath, filename.Name()) fullPath := path.Join(labelsPath, filename.Name())
fp, err := os.OpenFile(fullPath, os.O_RDONLY, os.ModePerm)
content, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
return labels, fmt.Errorf("open file error: %v", err) return labels, fmt.Errorf("read file error: %v", err)
} }
defer fp.Close() fmt.Println(string(content))
rd := bufio.NewReader(fp) for _, line := range strings.Split(string(content), "\n") {
for {
line, err := rd.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return labels, fmt.Errorf("read file line error: %v", err)
}
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if len(line) > 0 { if len(line) > 0 {
found = false found = false
for _, skipLabel := range skipLabels { for _, skipLabel := range skipLabels {
if skipLabel == line { if skipLabel == Label(line) {
found = true found = true
break break
} }
} }
if !found { if !found {
labels = append(labels, line) labels = append(labels, Label(line))
} }
} }
} }
} }
} }
fmt.Println("LABELS", labels)
return labels, nil return labels, nil
} }

View File

@ -0,0 +1,48 @@
package server
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
const tmpPath = "./tmp"
const testLabelPath = tmpPath + "/labels"
func TestGetIdentification(t *testing.T) {
discovery, err := GetIdentification("test.example.com", Labels{Label("service:test"), Label("test:1")}, testLabelPath)
assert.Nil(t, err)
assert.Equal(t, "test.example.com", discovery.Hostname)
assert.Equal(t, "service:test", discovery.Labels[0].String())
err = os.MkdirAll(testLabelPath, os.ModePerm)
assert.Nil(t, err)
err = os.WriteFile(testLabelPath+"/test", []byte("service:test2\npublic_ip:1.2.3.4"), 0644)
assert.Nil(t, err)
discovery, err = GetIdentification("test.example.com", Labels{Label("service:test"), Label("test:1")}, testLabelPath)
assert.Nil(t, err)
assert.Equal(t, Label("public_ip:1.2.3.4"), discovery.Labels[3])
os.RemoveAll(tmpPath)
}
func TestLoadLocalLabels(t *testing.T) {
err := os.MkdirAll(testLabelPath, os.ModePerm)
assert.Nil(t, err)
err = os.WriteFile(testLabelPath+"/test", []byte("service:test\npublic_ip:1.2.3.4"), 0644)
assert.Nil(t, err)
labels, err := loadLocalLabels(Labels{Label("service:test")}, testLabelPath)
assert.Nil(t, err)
assert.Equal(t, 1, len(labels))
assert.Equal(t, "public_ip:1.2.3.4", labels[0].String())
os.RemoveAll(tmpPath)
}

11
server/types.go Normal file
View File

@ -0,0 +1,11 @@
package server
// Label keeps one piece of information about a single server
type Label string
func (l Label) String() string {
return string(l)
}
// Labels stores multiple Label records
type Labels []Label