Skip to content

Commit bb6cd38

Browse files
authored
Introduce component ping (#104)
1 parent fb5579e commit bb6cd38

59 files changed

Lines changed: 12233 additions & 7383 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

doc/index.html

Lines changed: 7844 additions & 7293 deletions
Large diffs are not rendered by default.

generate/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ go-mocks:
1717
--tmpfs /.cache:uid=$$(id -u),gid=$$(id -g) \
1818
-w /work \
1919
-v ${PWD}:/work \
20-
vektra/mockery:v3.6.3
20+
vektra/mockery:v3.6.4

generate/go_client.tpl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
package client
33

44
import (
5+
"context"
6+
57
"connectrpc.com/connect"
68
compress "github.com/klauspost/connect-compress/v2"
79

@@ -15,6 +17,7 @@ type (
1517
{{ range $name, $api := . -}}
1618
{{ $name | title }}() {{ $name | title }}
1719
{{ end }}
20+
Ping(context.Context, *PingConfig)
1821
}
1922
client struct {
2023
config *DialConfig

go.mod

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
module github.com/metal-stack/api
22

3-
go 1.25
3+
go 1.26
44

55
require (
66
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1
7-
buf.build/go/protovalidate v1.1.0
7+
buf.build/go/protovalidate v1.1.3
88
connectrpc.com/connect v1.19.1
99
github.com/bufbuild/protocompile v0.14.1
1010
github.com/go-task/slim-sprig/v3 v3.0.0
1111
github.com/golang-jwt/jwt/v5 v5.3.1
1212
github.com/google/go-cmp v0.7.0
13+
github.com/google/uuid v1.6.0
1314
github.com/klauspost/connect-compress/v2 v2.1.1
1415
github.com/stretchr/testify v1.11.1
1516
google.golang.org/protobuf v1.36.11
@@ -25,10 +26,10 @@ require (
2526
github.com/minio/minlz v1.0.1 // indirect
2627
github.com/pmezard/go-difflib v1.0.0 // indirect
2728
github.com/stretchr/objx v0.5.3 // indirect
28-
golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect
29+
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
2930
golang.org/x/text v0.34.0 // indirect
30-
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
31-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
31+
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect
32+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
3233
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3334
gopkg.in/yaml.v3 v3.0.1 // indirect
3435
)

go.sum

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1 h1:PMmTMyvHScV9Mn8wc6ASge9uRcHy0jtqPd+fM35LmsQ=
22
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260209202127-80ab13bee0bf.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM=
3-
buf.build/go/protovalidate v1.1.0 h1:pQqEQRpOo4SqS60qkvmhLTTQU9JwzEvdyiqAtXa5SeY=
4-
buf.build/go/protovalidate v1.1.0/go.mod h1:bGZcPiAQDC3ErCHK3t74jSoJDFOs2JH3d7LWuTEIdss=
3+
buf.build/go/protovalidate v1.1.3 h1:m2GVEgQWd7rk+vIoAZ+f0ygGjvQTuqPQapBBdcpWVPE=
4+
buf.build/go/protovalidate v1.1.3/go.mod h1:9XIuohWz+kj+9JVn3WQneHA5LZP50mjvneZMnbLkiIE=
55
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
66
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
77
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
@@ -23,6 +23,8 @@ github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=
2323
github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=
2424
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2525
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
26+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
27+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2628
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
2729
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
2830
github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c=
@@ -49,14 +51,14 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
4951
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
5052
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
5153
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
52-
golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c=
53-
golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8=
54+
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
55+
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
5456
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
5557
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
56-
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
57-
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
58-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
59-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
58+
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s=
59+
google.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=
60+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
61+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
6062
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
6163
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
6264
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

go/client/client.go

Lines changed: 30 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/client/ping.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"os"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
v2 "github.com/metal-stack/api/go/metalstack/api/v2"
10+
infra "github.com/metal-stack/api/go/metalstack/infra/v2"
11+
"google.golang.org/protobuf/types/known/durationpb"
12+
"google.golang.org/protobuf/types/known/timestamppb"
13+
)
14+
15+
var (
16+
minInterval = 5 * time.Second
17+
maxInterval = time.Hour
18+
defaultInterval = 5 * time.Minute
19+
)
20+
21+
// PingConfig is used to configure ping
22+
type PingConfig struct {
23+
// ComponentType should be set to the type of the microservice
24+
ComponentType v2.ComponentType
25+
// Identifier helps to identify multiple instances of a microservice
26+
// Usually the hostname or the podname could be used for that
27+
// If omitted, hostname is used
28+
Identifier *string
29+
// StartedAt contains the starttime when this go process was started
30+
StartedAt time.Time
31+
// Interval at which the ping should happen
32+
// If not specified, or shorter than 1min, or longer than 1h, it defaults to 5min
33+
Interval time.Duration
34+
// Version contains all version details about this microservice
35+
// You should use https://github.com/metal-stack/v to get all information.
36+
Version v2.Version
37+
}
38+
39+
// Ping should be called from every microservice which talks to the metal-apiserver
40+
// It will stay and ping at config.Interval in the background until context is canceled
41+
func (c *client) Ping(ctx context.Context, config *PingConfig) {
42+
if config == nil {
43+
return
44+
}
45+
46+
if config.Interval < minInterval || config.Interval > maxInterval {
47+
config.Interval = defaultInterval
48+
}
49+
50+
var identifier string
51+
if config.Identifier == nil {
52+
hostname, err := os.Hostname()
53+
if err != nil {
54+
suffix := uuid.NewString()
55+
identifier = "unknown-" + suffix
56+
} else {
57+
identifier = hostname
58+
}
59+
} else {
60+
identifier = *config.Identifier
61+
}
62+
63+
req := &infra.ComponentServicePingRequest{
64+
Type: config.ComponentType,
65+
Identifier: identifier,
66+
StartedAt: timestamppb.New(config.StartedAt),
67+
Interval: durationpb.New(config.Interval),
68+
Version: &config.Version,
69+
}
70+
71+
c.config.Log.Debug("ping", "config", config)
72+
73+
ticker := time.NewTicker(config.Interval)
74+
go func() {
75+
for {
76+
select {
77+
case <-ctx.Done():
78+
ticker.Stop()
79+
return
80+
case <-ticker.C:
81+
pingCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
82+
defer cancel()
83+
_, err := c.Infrav2().Component().Ping(pingCtx, req)
84+
if err != nil {
85+
c.config.Log.Error("ping", "error", err)
86+
}
87+
}
88+
}
89+
}()
90+
}

go/client/ping_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package client_test
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"net/http"
7+
"net/http/httptest"
8+
"os"
9+
"testing"
10+
"time"
11+
12+
"github.com/google/go-cmp/cmp"
13+
"github.com/google/go-cmp/cmp/cmpopts"
14+
"github.com/metal-stack/api/go/client"
15+
apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
16+
infra "github.com/metal-stack/api/go/metalstack/infra/v2"
17+
"github.com/metal-stack/api/go/metalstack/infra/v2/infrav2connect"
18+
"github.com/stretchr/testify/require"
19+
"google.golang.org/protobuf/testing/protocmp"
20+
"google.golang.org/protobuf/types/known/durationpb"
21+
"google.golang.org/protobuf/types/known/timestamppb"
22+
)
23+
24+
func Test_Ping(t *testing.T) {
25+
var (
26+
mux = http.NewServeMux()
27+
log = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
28+
cs = &mockComponentService{log: log}
29+
)
30+
31+
mux.Handle(infrav2connect.NewComponentServiceHandler(cs))
32+
server := httptest.NewTLSServer(mux)
33+
server.EnableHTTP2 = true
34+
defer server.Close()
35+
36+
ctx := t.Context()
37+
38+
tokenString, err := generateToken(2 * time.Second)
39+
require.NoError(t, err)
40+
41+
c, err := client.New(&client.DialConfig{
42+
BaseURL: server.URL,
43+
Token: tokenString,
44+
Transport: server.Client().Transport,
45+
Log: log,
46+
})
47+
require.NoError(t, err)
48+
49+
start := time.Now()
50+
config := &client.PingConfig{
51+
ComponentType: apiv2.ComponentType_COMPONENT_TYPE_METAL_CORE,
52+
Identifier: new("server01"),
53+
StartedAt: start,
54+
Interval: 5 * time.Second,
55+
}
56+
57+
want := []*infra.ComponentServicePingRequest{
58+
{Type: apiv2.ComponentType_COMPONENT_TYPE_METAL_CORE, Identifier: "server01", StartedAt: timestamppb.New(start), Interval: durationpb.New(5 * time.Second), Version: &apiv2.Version{}},
59+
{Type: apiv2.ComponentType_COMPONENT_TYPE_METAL_CORE, Identifier: "server01", StartedAt: timestamppb.New(start), Interval: durationpb.New(5 * time.Second), Version: &apiv2.Version{}},
60+
}
61+
62+
c.Ping(ctx, config)
63+
time.Sleep(3 * config.Interval)
64+
if diff := cmp.Diff(
65+
cs.pings, want,
66+
protocmp.Transform(),
67+
cmpopts.IgnoreUnexported(),
68+
); diff != "" {
69+
t.Errorf("%v, want %v diff: %s", cs.pings, want, diff)
70+
}
71+
}
72+
73+
type mockComponentService struct {
74+
log *slog.Logger
75+
pings []*infra.ComponentServicePingRequest
76+
}
77+
78+
func (m *mockComponentService) Ping(_ context.Context, req *infra.ComponentServicePingRequest) (*infra.ComponentServicePingResponse, error) {
79+
m.log.Debug("ping", "req", req)
80+
m.pings = append(m.pings, req)
81+
return nil, nil
82+
}

0 commit comments

Comments
 (0)