From 4c5cb1bd85792505bf0ca07111ed99cb2a7191e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20=C5=A0trauch?= Date: Sat, 4 Sep 2021 14:16:14 +0200 Subject: [PATCH] Refactoring Discovery creation move into server package Tests for server package --- .gitignore | 1 + daemon/config.go | 27 +++++++------- daemon/main.go | 4 +-- daemon/prometheus.go | 6 ++-- go.mod | 1 + go.sum | 1 + server/{main.go => discovery.go} | 15 ++++---- server/discovery_test.go | 38 ++++++++++++++++++++ {daemon => server}/identification.go | 53 ++++++++++++---------------- server/identification_test.go | 48 +++++++++++++++++++++++++ server/types.go | 11 ++++++ 11 files changed, 149 insertions(+), 56 deletions(-) rename server/{main.go => discovery.go} (91%) create mode 100644 server/discovery_test.go rename {daemon => server}/identification.go (50%) create mode 100644 server/identification_test.go create mode 100644 server/types.go diff --git a/.gitignore b/.gitignore index 87bdbf1..4ac8f40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Files with secrets *secret* +tmp/ # Binaries lobby_* diff --git a/daemon/config.go b/daemon/config.go index 87d4fa7..0c76431 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -4,23 +4,24 @@ import ( "log" "github.com/kelseyhightower/envconfig" + "github.com/rosti-cz/server_lobby/server" ) // Config keeps info about configuration of this daemon type Config struct { - Token string `envconfig:"TOKEN" required:"false"` // Authentication token, if empty auth is disabled - Host string `envconfig:"HOST" required:"false" default:"127.0.0.1"` // IP address used for the REST server to listen - 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 - 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 - 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 - 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 - KeepAlive uint `envconfig:"KEEP_ALIVE" required:"false" default:"5"` // how often to send the keepalive message with all availabel information [secs] - TTL uint `envconfig:"TTL" required:"false" default:"30"` // After how many secs is discovery record considered as invalid - NodeExporterPort uint `envconfig:"NODE_EXPORTER_PORT" required:"false" default:"9100"` // Default port where node_exporter listens on all registered servers - Register bool `envconfig:"REGISTER" required:"false" default:"true"` // If true (default) then local instance is registered with other instance (discovery packet is sent regularly) + Token string `envconfig:"TOKEN" required:"false"` // Authentication token, if empty auth is disabled + Host string `envconfig:"HOST" required:"false" default:"127.0.0.1"` // IP address used for the REST server to listen + 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 + NATSDiscoveryChannel string `envconfig:"NATS_DISCOVERY_CHANNEL" required:"false" default:"lobby.discovery"` // Channel where the kepp alive packets are sent + 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 + 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 + KeepAlive uint `envconfig:"KEEP_ALIVE" required:"false" default:"5"` // how often to send the keepalive message with all availabel information [secs] + TTL uint `envconfig:"TTL" required:"false" default:"30"` // After how many secs is discovery record considered as invalid + NodeExporterPort uint `envconfig:"NODE_EXPORTER_PORT" required:"false" default:"9100"` // Default port where node_exporter listens on all registered servers + Register bool `envconfig:"REGISTER" required:"false" default:"true"` // If true (default) then local instance is registered with other instance (discovery packet is sent regularly) } // GetConfig return configuration created based on environment variables diff --git a/daemon/main.go b/daemon/main.go index 062f56a..ab52ca3 100644 --- a/daemon/main.go +++ b/daemon/main.go @@ -52,7 +52,7 @@ func cleanDiscoveryPool() { // 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. func sendGoodbyePacket() { - discovery, err := getIdentification() + discovery, err := server.GetIdentification(config.HostName, config.Labels, config.LabelsPath) if err != nil { 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 func sendDiscoveryPacket() { for { - discovery, err := getIdentification() + discovery, err := server.GetIdentification(config.HostName, config.Labels, config.LabelsPath) if err != nil { log.Printf("sending discovery identification error: %v\n", err) } diff --git a/daemon/prometheus.go b/daemon/prometheus.go index 8b8f2fa..2d9755c 100644 --- a/daemon/prometheus.go +++ b/daemon/prometheus.go @@ -39,8 +39,8 @@ func preparePrometheusOutput(name string, discoveries []server.Discovery) Promet labels := map[string]string{} - for _, label := range discovery.FindLabels("prometheus:" + name) { - trimmed := strings.TrimPrefix(label, "prometheus:"+name+":") + for _, label := range discovery.FindLabels("prometheus:" + name + ":") { + trimmed := strings.TrimPrefix(label.String(), "prometheus:"+name+":") parts := strings.SplitN(trimmed, ":", 2) if len(parts) == 2 { 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. if !add { for _, label := range discovery.Labels { - if label == "prometheus:"+name { + if label.String() == "prometheus:"+name { add = true break } diff --git a/go.mod b/go.mod index 602c0df..e5fcaab 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/nats-io/nats-server/v2 v2.4.0 // indirect github.com/nats-io/nats.go v1.12.0 github.com/shirou/gopsutil/v3 v3.21.7 + github.com/stretchr/testify v1.7.0 github.com/valyala/fasttemplate v1.2.1 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/go.sum b/go.sum index 4d6eba0..b949430 100644 --- a/go.sum +++ b/go.sum @@ -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.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/server/main.go b/server/discovery.go similarity index 91% rename from server/main.go rename to server/discovery.go index 06d8fef..f42df26 100644 --- a/server/main.go +++ b/server/discovery.go @@ -15,8 +15,8 @@ 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 type Discovery struct { - Hostname string `json:"hostname"` - Labels []string `json:"labels"` + Hostname string `json:"hostname"` + Labels Labels `json:"labels"` // For internal use to check if the server is still alive. // Contains timestamp of the last check. @@ -44,11 +44,12 @@ func (d *Discovery) Bytes() ([]byte, error) { return data, err } -// FindLabels returns list of labels with given prefix. For example "service:ns" has prefix "service" -func (d *Discovery) FindLabels(prefix string) []string { - labels := []string{} +// FindLabels returns list of labels with given prefix. For example "service:ns" has prefix "service" or "service:". +// It doesn't have to be prefix, but for example "service:test" will match "service:test" and also "service:test2". +func (d *Discovery) FindLabels(prefix string) Labels { + labels := Labels{} for _, label := range d.Labels { - if strings.HasPrefix(label, prefix+":") { + if strings.HasPrefix(label.String(), prefix) { labels = append(labels, label) } } @@ -156,7 +157,7 @@ func (d *Discoveries) Filter(labelsFilter []string) []Discovery { found = false for _, label := range discovery.Labels { for _, labelFilter := range labelsFilter { - if label == labelFilter { + if label.String() == labelFilter { newSet = append(newSet, discovery) found = true break diff --git a/server/discovery_test.go b/server/discovery_test.go new file mode 100644 index 0000000..b0186ea --- /dev/null +++ b/server/discovery_test.go @@ -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) +} diff --git a/daemon/identification.go b/server/identification.go similarity index 50% rename from daemon/identification.go rename to server/identification.go index bf369c7..00af0c9 100644 --- a/daemon/identification.go +++ b/server/identification.go @@ -1,89 +1,80 @@ -package main +package server import ( - "bufio" "fmt" - "io" "io/ioutil" "os" "path" "strings" - "github.com/rosti-cz/server_lobby/server" "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. -func getIdentification() (server.Discovery, error) { - discovery := server.Discovery{} +// Parameter initialLabels usually coming from configuration of the app. +// 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 { return discovery, err } - if len(config.HostName) == 0 { + if len(hostname) == 0 { info, err := host.Info() if err != nil { return discovery, err } discovery.Hostname = info.Hostname } else { - discovery.Hostname = config.HostName + discovery.Hostname = hostname } - discovery.Labels = append(config.Labels, localLabels...) + discovery.Labels = append(initialLabels, localLabels...) return discovery, nil } // 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. -func loadLocalLabels(skipLabels []string) ([]string, error) { - labels := []string{} +func loadLocalLabels(skipLabels Labels, labelsPath string) (Labels, error) { + labels := Labels{} var found bool - if _, err := os.Stat(config.LabelsPath); !os.IsNotExist(err) { - files, err := ioutil.ReadDir(config.LabelsPath) + if _, err := os.Stat(labelsPath); !os.IsNotExist(err) { + files, err := ioutil.ReadDir(labelsPath) if err != nil { return labels, err } for _, filename := range files { - fullPath := path.Join(config.LabelsPath, filename.Name()) - fp, err := os.OpenFile(fullPath, os.O_RDONLY, os.ModePerm) + fullPath := path.Join(labelsPath, filename.Name()) + + content, err := os.ReadFile(fullPath) 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, err := rd.ReadString('\n') - if err != nil { - if err == io.EOF { - break - } - - return labels, fmt.Errorf("read file line error: %v", err) - } + for _, line := range strings.Split(string(content), "\n") { line = strings.TrimSpace(line) if len(line) > 0 { found = false for _, skipLabel := range skipLabels { - if skipLabel == line { + if skipLabel == Label(line) { found = true break } } if !found { - labels = append(labels, line) + labels = append(labels, Label(line)) } } } } } - + fmt.Println("LABELS", labels) return labels, nil } diff --git a/server/identification_test.go b/server/identification_test.go new file mode 100644 index 0000000..a45b3c7 --- /dev/null +++ b/server/identification_test.go @@ -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) +} diff --git a/server/types.go b/server/types.go new file mode 100644 index 0000000..322c1ee --- /dev/null +++ b/server/types.go @@ -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