Skip to content

Commit b5c760f

Browse files
mcanevetsmira
authored andcommitted
feat: add ProbeConfig for network connectivity probes
This commit introduces ProbeConfig, a new network configuration document type that allows users to configure TCP connectivity probes to monitor network endpoints. Features: - ProbeConfig document type with TCP probe support - ProbeSpec and ProbeStatus resources for probe management - ProbeConfigController to translate ProbeConfig into ProbeSpec - ProbeController to execute probes and update ProbeStatus - Configurable probe interval, timeout, and failure threshold - Integration tests for API functionality Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch> Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent 4b274f7 commit b5c760f

File tree

16 files changed

+1192
-3
lines changed

16 files changed

+1192
-3
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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
6+
7+
import (
8+
"context"
9+
"fmt"
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+
"github.com/cosi-project/runtime/pkg/state"
15+
"github.com/siderolabs/gen/optional"
16+
"go.uber.org/zap"
17+
18+
configconfig "github.com/siderolabs/talos/pkg/machinery/config/config"
19+
"github.com/siderolabs/talos/pkg/machinery/resources/config"
20+
"github.com/siderolabs/talos/pkg/machinery/resources/network"
21+
)
22+
23+
// ProbeConfigController manages network.ProbeSpec based on ProbeConfig documents in machine configuration.
24+
type ProbeConfigController struct{}
25+
26+
// Name implements controller.Controller interface.
27+
func (ctrl *ProbeConfigController) Name() string {
28+
return "network.ProbeConfigController"
29+
}
30+
31+
// Inputs implements controller.Controller interface.
32+
func (ctrl *ProbeConfigController) Inputs() []controller.Input {
33+
return []controller.Input{
34+
{
35+
Namespace: config.NamespaceName,
36+
Type: config.MachineConfigType,
37+
ID: optional.Some(config.ActiveID),
38+
Kind: controller.InputWeak,
39+
},
40+
}
41+
}
42+
43+
// Outputs implements controller.Controller interface.
44+
func (ctrl *ProbeConfigController) Outputs() []controller.Output {
45+
return []controller.Output{
46+
{
47+
Type: network.ProbeSpecType,
48+
Kind: controller.OutputShared,
49+
},
50+
}
51+
}
52+
53+
// Run implements controller.Controller interface.
54+
func (ctrl *ProbeConfigController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
55+
for {
56+
select {
57+
case <-ctx.Done():
58+
return nil
59+
case <-r.EventCh():
60+
}
61+
62+
r.StartTrackingOutputs()
63+
64+
cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.ActiveID)
65+
if err != nil {
66+
if !state.IsNotFoundError(err) {
67+
return fmt.Errorf("error getting config: %w", err)
68+
}
69+
}
70+
71+
var specs []network.ProbeSpecSpec
72+
73+
// parse machine configuration for probe config documents
74+
if cfg != nil {
75+
configSpecs := ctrl.parseMachineConfiguration(cfg)
76+
specs = append(specs, configSpecs...)
77+
}
78+
79+
if err = ctrl.apply(ctx, r, specs); err != nil {
80+
return fmt.Errorf("error applying specs: %w", err)
81+
}
82+
83+
if err = r.CleanupOutputs(ctx,
84+
resource.NewMetadata(network.NamespaceName, network.ProbeSpecType, "", resource.VersionUndefined),
85+
); err != nil {
86+
return fmt.Errorf("error cleaning up outputs: %w", err)
87+
}
88+
89+
r.ResetRestartBackoff()
90+
}
91+
}
92+
93+
func (ctrl *ProbeConfigController) apply(ctx context.Context, r controller.Runtime, specs []network.ProbeSpecSpec) error {
94+
for _, spec := range specs {
95+
id, err := spec.ID()
96+
if err != nil {
97+
return fmt.Errorf("error getting probe spec ID: %w", err)
98+
}
99+
100+
if err := safe.WriterModify(
101+
ctx,
102+
r,
103+
network.NewProbeSpec(network.NamespaceName, id),
104+
func(r *network.ProbeSpec) error {
105+
*r.TypedSpec() = spec
106+
107+
return nil
108+
},
109+
); err != nil {
110+
return fmt.Errorf("error modifying probe spec: %w", err)
111+
}
112+
}
113+
114+
return nil
115+
}
116+
117+
func (ctrl *ProbeConfigController) parseMachineConfiguration(cfg *config.MachineConfig) []network.ProbeSpecSpec {
118+
probeConfigs := cfg.Config().NetworkProbeConfigs()
119+
specs := make([]network.ProbeSpecSpec, 0, len(probeConfigs))
120+
121+
for _, probeConfig := range probeConfigs {
122+
spec := network.ProbeSpecSpec{
123+
Interval: probeConfig.Interval(),
124+
FailureThreshold: probeConfig.FailureThreshold(),
125+
ConfigLayer: network.ConfigMachineConfiguration,
126+
}
127+
128+
switch probeConfig := probeConfig.(type) {
129+
case configconfig.NetworkTCPProbeConfig:
130+
spec.TCP = network.TCPProbeSpec{
131+
Endpoint: probeConfig.Endpoint(),
132+
Timeout: probeConfig.Timeout(),
133+
}
134+
default:
135+
panic(fmt.Sprintf("unsupported probe config type: %T", probeConfig))
136+
}
137+
138+
specs = append(specs, spec)
139+
}
140+
141+
return specs
142+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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/rtestutils"
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/config/container"
18+
networkcfg "github.com/siderolabs/talos/pkg/machinery/config/types/network"
19+
"github.com/siderolabs/talos/pkg/machinery/resources/config"
20+
"github.com/siderolabs/talos/pkg/machinery/resources/network"
21+
)
22+
23+
type ProbeConfigSuite struct {
24+
ctest.DefaultSuite
25+
}
26+
27+
func (suite *ProbeConfigSuite) TestNoConfig() {
28+
// With no config, no ProbeSpec resources should be created
29+
ctest.AssertNoResource[*network.ProbeSpec](suite, "tcp:proxy.example.com:3128", rtestutils.WithNamespace(network.NamespaceName))
30+
}
31+
32+
func (suite *ProbeConfigSuite) TestSingleProbe() {
33+
probeConfig := networkcfg.NewTCPProbeConfigV1Alpha1("proxy-check")
34+
probeConfig.ProbeInterval = time.Second
35+
probeConfig.ProbeFailureThreshold = 3
36+
probeConfig.TCPEndpoint = "proxy.example.com:3128"
37+
probeConfig.TCPTimeout = 10 * time.Second
38+
39+
ctr, err := container.New(probeConfig)
40+
suite.Require().NoError(err)
41+
42+
cfg := config.NewMachineConfig(ctr)
43+
suite.Create(cfg)
44+
45+
ctest.AssertResources(
46+
suite,
47+
[]string{
48+
"tcp:proxy.example.com:3128",
49+
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
50+
asrt.Equal(time.Second, r.TypedSpec().Interval)
51+
asrt.Equal(3, r.TypedSpec().FailureThreshold)
52+
asrt.Equal("proxy.example.com:3128", r.TypedSpec().TCP.Endpoint)
53+
asrt.Equal(10*time.Second, r.TypedSpec().TCP.Timeout)
54+
asrt.Equal(network.ConfigMachineConfiguration, r.TypedSpec().ConfigLayer)
55+
},
56+
rtestutils.WithNamespace(network.NamespaceName),
57+
)
58+
59+
// Update the probe config
60+
ctest.UpdateWithConflicts(suite, cfg, func(r *config.MachineConfig) error {
61+
docs := r.Container().Documents()
62+
probeDoc := docs[0].(*networkcfg.TCPProbeConfigV1Alpha1)
63+
probeDoc.ProbeFailureThreshold = 5
64+
65+
return nil
66+
})
67+
68+
ctest.AssertResources(
69+
suite,
70+
[]string{
71+
"tcp:proxy.example.com:3128",
72+
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
73+
asrt.Equal(5, r.TypedSpec().FailureThreshold)
74+
},
75+
rtestutils.WithNamespace(network.NamespaceName),
76+
)
77+
78+
// Remove the config
79+
suite.Destroy(cfg)
80+
81+
ctest.AssertNoResource[*network.ProbeSpec](suite, "tcp:proxy.example.com:3128", rtestutils.WithNamespace(network.NamespaceName))
82+
}
83+
84+
func (suite *ProbeConfigSuite) TestMultipleProbes() {
85+
// Create first probe
86+
probeConfig1 := networkcfg.NewTCPProbeConfigV1Alpha1("proxy-check")
87+
probeConfig1.ProbeInterval = time.Second
88+
probeConfig1.ProbeFailureThreshold = 3
89+
probeConfig1.TCPEndpoint = "proxy.example.com:3128"
90+
probeConfig1.TCPTimeout = 10 * time.Second
91+
92+
// Create second probe
93+
probeConfig2 := networkcfg.NewTCPProbeConfigV1Alpha1("dns-check")
94+
probeConfig2.ProbeInterval = 5 * time.Second
95+
probeConfig2.ProbeFailureThreshold = 2
96+
probeConfig2.TCPEndpoint = "8.8.8.8:53"
97+
probeConfig2.TCPTimeout = 5 * time.Second
98+
99+
ctr, err := container.New(probeConfig1, probeConfig2)
100+
suite.Require().NoError(err)
101+
102+
cfg := config.NewMachineConfig(ctr)
103+
suite.Create(cfg)
104+
105+
// Verify both probes are created
106+
ctest.AssertResources(
107+
suite,
108+
[]string{
109+
"tcp:proxy.example.com:3128",
110+
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
111+
asrt.Equal("proxy.example.com:3128", r.TypedSpec().TCP.Endpoint)
112+
asrt.Equal(3, r.TypedSpec().FailureThreshold)
113+
},
114+
rtestutils.WithNamespace(network.NamespaceName),
115+
)
116+
117+
ctest.AssertResources(
118+
suite,
119+
[]string{
120+
"tcp:8.8.8.8:53",
121+
}, func(r *network.ProbeSpec, asrt *assert.Assertions) {
122+
asrt.Equal("8.8.8.8:53", r.TypedSpec().TCP.Endpoint)
123+
asrt.Equal(2, r.TypedSpec().FailureThreshold)
124+
},
125+
rtestutils.WithNamespace(network.NamespaceName),
126+
)
127+
128+
suite.Destroy(cfg)
129+
130+
// 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))
133+
}
134+
135+
func TestProbeConfigSuite(t *testing.T) {
136+
t.Parallel()
137+
138+
suite.Run(t, &ProbeConfigSuite{
139+
DefaultSuite: ctest.DefaultSuite{
140+
Timeout: 10 * time.Second,
141+
AfterSetup: func(suite *ctest.DefaultSuite) {
142+
suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.ProbeConfigController{}))
143+
},
144+
},
145+
})
146+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
369369
&network.StatusController{
370370
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
371371
},
372+
&network.ProbeConfigController{},
372373
&network.TimeServerConfigController{
373374
Cmdline: procfs.ProcCmdline(),
374375
},

0 commit comments

Comments
 (0)