Skip to content

Commit 3946036

Browse files
committed
feat: implement layering for ProbeSpec
Same as for any other resource - layering per source, and proper merge across layers, so we can see where it comes from. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent b5c760f commit 3946036

File tree

8 files changed

+174
-19
lines changed

8 files changed

+174
-19
lines changed

hack/release.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ For example:
160160
description = """\
161161
The nameservers configuration in machine configuration now overwrites any previous layers (defaults, platform, etc.) when specified.
162162
Previously a smart merge was performed to keep IPv4/IPv6 nameservers from lower layers if the machine configuration specified only one type.
163+
"""
164+
165+
[notes.probe_config]
166+
title = "ProbeConfig"
167+
description = """\
168+
The TCPProbeConfig configuration document allows to configure TCP probes for network reachability checks.
169+
This allows to define a custom connectivity condition.
163170
"""
164171

165172
[make_deps]

internal/app/machined/pkg/controllers/network/platform_config_apply.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,10 +378,15 @@ func (ctrl *PlatformConfigApplyController) apply(ctx context.Context, r controll
378378
idBuilder: func(spec any) (resource.ID, error) {
379379
probeSpec := spec.(network.ProbeSpecSpec) //nolint:forcetypeassert
380380

381-
return probeSpec.ID()
381+
id, err := probeSpec.ID()
382+
if err != nil {
383+
return "", err
384+
}
385+
386+
return network.LayeredID(network.ConfigPlatform, id), nil
382387
},
383388
resourceBuilder: func(id string) resource.Resource {
384-
return network.NewProbeSpec(network.NamespaceName, id)
389+
return network.NewProbeSpec(network.ConfigNamespaceName, id)
385390
},
386391
resourceModifier: func(newSpec any) func(r resource.Resource) error {
387392
return func(r resource.Resource) error {

internal/app/machined/pkg/controllers/network/platform_config_apply_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,16 @@ func (suite *PlatformConfigApplySuite) TestProbes() {
248248
suite.Create(platformConfig)
249249

250250
ctest.AssertResources(suite, []string{
251-
"tcp:example.com:80",
252-
"tcp:example.com:443",
251+
"platform/tcp:example.com:80",
252+
"platform/tcp:example.com:443",
253253
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
254254
spec := r.TypedSpec()
255255

256256
asrt.Equal(time.Second, spec.Interval)
257257
asrt.Equal(network.ConfigPlatform, spec.ConfigLayer)
258-
})
258+
},
259+
rtestutils.WithNamespace(network.ConfigNamespaceName),
260+
)
259261
}
260262

261263
func (suite *PlatformConfigApplySuite) TestExternalIPs() {

internal/app/machined/pkg/controllers/network/probe_config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (ctrl *ProbeConfigController) Run(ctx context.Context, r controller.Runtime
8181
}
8282

8383
if err = r.CleanupOutputs(ctx,
84-
resource.NewMetadata(network.NamespaceName, network.ProbeSpecType, "", resource.VersionUndefined),
84+
resource.NewMetadata(network.ConfigNamespaceName, network.ProbeSpecType, "", resource.VersionUndefined),
8585
); err != nil {
8686
return fmt.Errorf("error cleaning up outputs: %w", err)
8787
}
@@ -100,7 +100,7 @@ func (ctrl *ProbeConfigController) apply(ctx context.Context, r controller.Runti
100100
if err := safe.WriterModify(
101101
ctx,
102102
r,
103-
network.NewProbeSpec(network.NamespaceName, id),
103+
network.NewProbeSpec(network.ConfigNamespaceName, network.LayeredID(spec.ConfigLayer, id)),
104104
func(r *network.ProbeSpec) error {
105105
*r.TypedSpec() = spec
106106

internal/app/machined/pkg/controllers/network/probe_config_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ func (suite *ProbeConfigSuite) TestSingleProbe() {
4545
ctest.AssertResources(
4646
suite,
4747
[]string{
48-
"tcp:proxy.example.com:3128",
48+
"configuration/tcp:proxy.example.com:3128",
4949
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
5050
asrt.Equal(time.Second, r.TypedSpec().Interval)
5151
asrt.Equal(3, r.TypedSpec().FailureThreshold)
5252
asrt.Equal("proxy.example.com:3128", r.TypedSpec().TCP.Endpoint)
5353
asrt.Equal(10*time.Second, r.TypedSpec().TCP.Timeout)
5454
asrt.Equal(network.ConfigMachineConfiguration, r.TypedSpec().ConfigLayer)
5555
},
56-
rtestutils.WithNamespace(network.NamespaceName),
56+
rtestutils.WithNamespace(network.ConfigNamespaceName),
5757
)
5858

5959
// Update the probe config
@@ -68,17 +68,17 @@ func (suite *ProbeConfigSuite) TestSingleProbe() {
6868
ctest.AssertResources(
6969
suite,
7070
[]string{
71-
"tcp:proxy.example.com:3128",
71+
"configuration/tcp:proxy.example.com:3128",
7272
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
7373
asrt.Equal(5, r.TypedSpec().FailureThreshold)
7474
},
75-
rtestutils.WithNamespace(network.NamespaceName),
75+
rtestutils.WithNamespace(network.ConfigNamespaceName),
7676
)
7777

7878
// Remove the config
7979
suite.Destroy(cfg)
8080

81-
ctest.AssertNoResource[*network.ProbeSpec](suite, "tcp:proxy.example.com:3128", rtestutils.WithNamespace(network.NamespaceName))
81+
ctest.AssertNoResource[*network.ProbeSpec](suite, "configuration/tcp:proxy.example.com:3128", rtestutils.WithNamespace(network.ConfigNamespaceName))
8282
}
8383

8484
func (suite *ProbeConfigSuite) TestMultipleProbes() {
@@ -106,30 +106,30 @@ func (suite *ProbeConfigSuite) TestMultipleProbes() {
106106
ctest.AssertResources(
107107
suite,
108108
[]string{
109-
"tcp:proxy.example.com:3128",
109+
"configuration/tcp:proxy.example.com:3128",
110110
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
111111
asrt.Equal("proxy.example.com:3128", r.TypedSpec().TCP.Endpoint)
112112
asrt.Equal(3, r.TypedSpec().FailureThreshold)
113113
},
114-
rtestutils.WithNamespace(network.NamespaceName),
114+
rtestutils.WithNamespace(network.ConfigNamespaceName),
115115
)
116116

117117
ctest.AssertResources(
118118
suite,
119119
[]string{
120-
"tcp:8.8.8.8:53",
120+
"configuration/tcp:8.8.8.8:53",
121121
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
122122
asrt.Equal("8.8.8.8:53", r.TypedSpec().TCP.Endpoint)
123123
asrt.Equal(2, r.TypedSpec().FailureThreshold)
124124
},
125-
rtestutils.WithNamespace(network.NamespaceName),
125+
rtestutils.WithNamespace(network.ConfigNamespaceName),
126126
)
127127

128128
suite.Destroy(cfg)
129129

130130
// Verify both probes are removed
131-
ctest.AssertNoResource[*network.ProbeSpec](suite, "tcp:proxy.example.com:3128", rtestutils.WithNamespace(network.NamespaceName))
132-
ctest.AssertNoResource[*network.ProbeSpec](suite, "tcp:8.8.8.8:53", rtestutils.WithNamespace(network.NamespaceName))
131+
ctest.AssertNoResource[*network.ProbeSpec](suite, "configuration/tcp:proxy.example.com:3128", rtestutils.WithNamespace(network.ConfigNamespaceName))
132+
ctest.AssertNoResource[*network.ProbeSpec](suite, "configuration/tcp:8.8.8.8:53", rtestutils.WithNamespace(network.ConfigNamespaceName))
133133
}
134134

135135
func TestProbeConfigSuite(t *testing.T) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// Package network provides controllers which manage network resources.
6+
package network
7+
8+
import (
9+
"cmp"
10+
11+
"github.com/cosi-project/runtime/pkg/controller"
12+
"github.com/cosi-project/runtime/pkg/resource"
13+
"github.com/cosi-project/runtime/pkg/safe"
14+
"go.uber.org/zap"
15+
16+
"github.com/siderolabs/talos/pkg/machinery/resources/network"
17+
)
18+
19+
// NewProbeMergeController initializes a ProbeMergeController.
20+
//
21+
// ProbeMergeController merges network.ProbeSpec in network.ConfigNamespace and produces final network.ProbeSpec in network.Namespace.
22+
func NewProbeMergeController() controller.Controller {
23+
return GenericMergeController(
24+
network.ConfigNamespaceName,
25+
network.NamespaceName,
26+
func(logger *zap.Logger, list safe.List[*network.ProbeSpec]) map[resource.ID]*network.ProbeSpecSpec {
27+
// sort by link name, configuration layer
28+
list.SortFunc(func(left, right *network.ProbeSpec) int {
29+
return cmp.Compare(left.TypedSpec().ConfigLayer, right.TypedSpec().ConfigLayer)
30+
})
31+
32+
// build final probe definition merging multiple layers
33+
probes := make(map[string]*network.ProbeSpecSpec, list.Len())
34+
35+
for probe := range list.All() {
36+
id, err := probe.TypedSpec().ID()
37+
if err != nil {
38+
logger.Warn("error getting probe ID", zap.Error(err))
39+
40+
continue
41+
}
42+
43+
// no way to actually have multiple probes with the same ID in different layers,
44+
// so we can just merge them one by one
45+
probes[id] = probe.TypedSpec()
46+
}
47+
48+
return probes
49+
},
50+
)
51+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package network_test
6+
7+
import (
8+
"testing"
9+
"time"
10+
11+
"github.com/cosi-project/runtime/pkg/resource"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/suite"
14+
15+
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
16+
netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network"
17+
"github.com/siderolabs/talos/pkg/machinery/resources/network"
18+
)
19+
20+
type ProbeMergeSuite struct {
21+
ctest.DefaultSuite
22+
}
23+
24+
func (suite *ProbeMergeSuite) TestMerge() {
25+
p1 := network.NewProbeSpec(network.ConfigNamespaceName, "configuration/tcp:proxy.example.com:3128")
26+
*p1.TypedSpec() = network.ProbeSpecSpec{
27+
Interval: time.Second,
28+
FailureThreshold: 3,
29+
TCP: network.TCPProbeSpec{
30+
Endpoint: "proxy.example.com:3128",
31+
Timeout: 10 * time.Second,
32+
},
33+
ConfigLayer: network.ConfigMachineConfiguration,
34+
}
35+
36+
p2 := network.NewProbeSpec(network.ConfigNamespaceName, "platform/tcp:proxy.example.com:3128")
37+
*p2.TypedSpec() = network.ProbeSpecSpec{
38+
Interval: 5 * time.Second,
39+
FailureThreshold: 5,
40+
TCP: network.TCPProbeSpec{
41+
Endpoint: "proxy.example.com:3128",
42+
Timeout: 5 * time.Second,
43+
},
44+
ConfigLayer: network.ConfigPlatform,
45+
}
46+
47+
p3 := network.NewProbeSpec(network.ConfigNamespaceName, "configuration/tcp:google.com:80")
48+
*p3.TypedSpec() = network.ProbeSpecSpec{
49+
Interval: 2 * time.Second,
50+
FailureThreshold: 4,
51+
TCP: network.TCPProbeSpec{
52+
Endpoint: "google.com:80",
53+
Timeout: 3 * time.Second,
54+
},
55+
ConfigLayer: network.ConfigMachineConfiguration,
56+
}
57+
58+
for _, res := range []resource.Resource{p1, p2, p3} {
59+
suite.Create(res)
60+
}
61+
62+
ctest.AssertResources(suite, []resource.ID{"tcp:proxy.example.com:3128", "tcp:google.com:80"},
63+
func(p *network.ProbeSpec, asrt *assert.Assertions) {
64+
if p.Metadata().ID() == "tcp:proxy.example.com:3128" {
65+
asrt.Equal(time.Second, p.TypedSpec().Interval)
66+
asrt.Equal(3, p.TypedSpec().FailureThreshold)
67+
asrt.Equal("proxy.example.com:3128", p.TypedSpec().TCP.Endpoint)
68+
asrt.Equal(10*time.Second, p.TypedSpec().TCP.Timeout)
69+
}
70+
},
71+
)
72+
73+
suite.Destroy(p3)
74+
75+
ctest.AssertNoResource[*network.ProbeSpec](suite, "tcp:google.com:80")
76+
}
77+
78+
func TestProbeMergeSuite(t *testing.T) {
79+
t.Parallel()
80+
81+
suite.Run(t, &ProbeMergeSuite{
82+
DefaultSuite: ctest.DefaultSuite{
83+
Timeout: 5 * time.Second,
84+
AfterSetup: func(s *ctest.DefaultSuite) {
85+
s.Require().NoError(s.Runtime().RegisterController(netctrl.NewProbeMergeController()))
86+
},
87+
},
88+
})
89+
}

internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,8 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
355355
&network.PlatformConfigLoadController{},
356356
&network.PlatformConfigStoreController{},
357357
&network.ProbeController{},
358+
&network.ProbeConfigController{},
359+
network.NewProbeMergeController(),
358360
&network.ResolverConfigController{
359361
Cmdline: procfs.ProcCmdline(),
360362
},
@@ -369,7 +371,6 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
369371
&network.StatusController{
370372
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
371373
},
372-
&network.ProbeConfigController{},
373374
&network.TimeServerConfigController{
374375
Cmdline: procfs.ProcCmdline(),
375376
},

0 commit comments

Comments
 (0)