From a13cf76a3415f458ff3235981c1be8202e1800bb Mon Sep 17 00:00:00 2001 From: Dmitriy Matrenichev Date: Thu, 24 Oct 2024 02:55:41 +0300 Subject: [PATCH] chore: simplify `DNSUpstreamController` and `DNSUpstream` resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR does those things: - Fixes race condition where controller could potentially modify upstream, while other controller is copying its internals to the slice. - Simplifies `run` function in `DNSUpstreamController` by removing all `Idx` handling. - Removes `Idx` field from `DNSUpstream`. Upstreams are now sorted by their id with №X prefix. - `Proxy` Stop is now called from the finalizer. In combination with iterators, this ensures that we only stop upstream when it's fully unreachable. Signed-off-by: Dmitriy Matrenichev --- go.mod | 2 +- go.sum | 4 +- hack/cloud-image-uploader/go.mod | 2 +- hack/cloud-image-uploader/go.sum | 4 +- hack/docgen/go.mod | 2 +- hack/docgen/go.sum | 4 +- .../controllers/network/dns_resolve_cache.go | 35 ++++---- .../network/dns_resolve_cache_test.go | 40 +++++++--- .../pkg/controllers/network/dns_upstream.go | 80 ++++++++++++------- internal/pkg/dns/dns.go | 27 ++++--- internal/pkg/dns/dns_test.go | 4 +- pkg/machinery/go.mod | 4 +- pkg/machinery/go.sum | 17 +--- .../resources/network/dns_upstream.go | 66 +++++++++++---- 14 files changed, 180 insertions(+), 111 deletions(-) diff --git a/go.mod b/go.mod index 91933454fd..7452a3d490 100644 --- a/go.mod +++ b/go.mod @@ -142,7 +142,7 @@ require ( github.com/siderolabs/crypto v0.5.0 github.com/siderolabs/discovery-api v0.1.4 github.com/siderolabs/discovery-client v0.1.10 - github.com/siderolabs/gen v0.5.0 + github.com/siderolabs/gen v0.6.1 github.com/siderolabs/go-api-signature v0.3.6 github.com/siderolabs/go-blockdevice v0.4.8 github.com/siderolabs/go-blockdevice/v2 v2.0.3 diff --git a/go.sum b/go.sum index 1cf04f6cfa..42932ff32a 100644 --- a/go.sum +++ b/go.sum @@ -593,8 +593,8 @@ github.com/siderolabs/discovery-api v0.1.4 h1:2fMEFSMiWaD1zDiBDY5md8VxItvL1rDQRS github.com/siderolabs/discovery-api v0.1.4/go.mod h1:kaBy+G42v2xd/uAF/NIe383sjNTBE2AhxPTyi9SZI0s= github.com/siderolabs/discovery-client v0.1.10 h1:bTAvFLiISSzVXyYL1cIgAz8cPYd9ZfvhxwdebgtxARA= github.com/siderolabs/discovery-client v0.1.10/go.mod h1:Ew1z07eyJwqNwum84IKYH4S649KEKK5WUmRW49HlXS8= -github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= -github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= +github.com/siderolabs/gen v0.6.1 h1:Mex6Q41Tlw3e+4cGvlju2x4UwULD5WMo/D82n7IxV0Y= +github.com/siderolabs/gen v0.6.1/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU= github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c= diff --git a/hack/cloud-image-uploader/go.mod b/hack/cloud-image-uploader/go.mod index 759b0e6237..45e2075fa2 100644 --- a/hack/cloud-image-uploader/go.mod +++ b/hack/cloud-image-uploader/go.mod @@ -16,7 +16,7 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/google/uuid v1.6.0 github.com/klauspost/compress v1.17.11 - github.com/siderolabs/gen v0.5.0 + github.com/siderolabs/gen v0.6.1 github.com/siderolabs/go-retry v0.3.3 github.com/spf13/pflag v1.0.5 golang.org/x/sync v0.8.0 diff --git a/hack/cloud-image-uploader/go.sum b/hack/cloud-image-uploader/go.sum index 12ac6ce2f3..acc762d550 100644 --- a/hack/cloud-image-uploader/go.sum +++ b/hack/cloud-image-uploader/go.sum @@ -179,8 +179,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= -github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= -github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= +github.com/siderolabs/gen v0.6.1 h1:Mex6Q41Tlw3e+4cGvlju2x4UwULD5WMo/D82n7IxV0Y= +github.com/siderolabs/gen v0.6.1/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= github.com/siderolabs/go-retry v0.3.3 h1:zKV+S1vumtO72E6sYsLlmIdV/G/GcYSBLiEx/c9oCEg= github.com/siderolabs/go-retry v0.3.3/go.mod h1:Ff/VGc7v7un4uQg3DybgrmOWHEmJ8BzZds/XNn/BqMI= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/hack/docgen/go.mod b/hack/docgen/go.mod index c642914e29..8d07bff669 100644 --- a/hack/docgen/go.mod +++ b/hack/docgen/go.mod @@ -11,7 +11,7 @@ require ( github.com/invopop/jsonschema v0.12.0 github.com/microcosm-cc/bluemonday v1.0.27 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 - github.com/siderolabs/gen v0.5.0 + github.com/siderolabs/gen v0.6.1 github.com/wk8/go-ordered-map/v2 v2.1.8 gopkg.in/yaml.v3 v3.0.1 mvdan.cc/gofumpt v0.7.0 diff --git a/hack/docgen/go.sum b/hack/docgen/go.sum index f563c642d1..e9475d7b5b 100644 --- a/hack/docgen/go.sum +++ b/hack/docgen/go.sum @@ -31,8 +31,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= -github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= -github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= +github.com/siderolabs/gen v0.6.1 h1:Mex6Q41Tlw3e+4cGvlju2x4UwULD5WMo/D82n7IxV0Y= +github.com/siderolabs/gen v0.6.1/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/unix4ever/yaml v0.0.0-20220527175918-f17b0f05cf2c h1:Vn6nVVu9MdOYvXPkJP83iX5jVIfvxFC9v9xIKb+DlaQ= diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go index 4925944e07..33ec6ac6e3 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go @@ -5,7 +5,6 @@ package network import ( - "cmp" "context" "errors" "fmt" @@ -23,7 +22,9 @@ import ( dnssrv "github.com/miekg/dns" "github.com/siderolabs/gen/optional" "github.com/siderolabs/gen/pair" + "github.com/siderolabs/gen/xiter" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/siderolabs/talos/internal/pkg/dns" "github.com/siderolabs/talos/pkg/machinery/resources/cluster" @@ -161,10 +162,15 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run return fmt.Errorf("error getting resolver status: %w", err) } - prxs, addrs := SortedProxies(upstreams) + prxs := xiter.Map( + upstreams.All(), + // We are using iterator here to preserve finalizer on + func(upstream *network.DNSUpstream) *proxy.Proxy { + return upstream.TypedSpec().Value.Conn.Proxy().(*proxy.Proxy) + }) if ctrl.handler.SetProxy(prxs) { - ctrl.Logger.Info("updated dns server nameservers", zap.Strings("addrs", addrs)) + ctrl.Logger.Info("updated dns server nameservers", zap.Array("addrs", addrsArr(upstreams))) } if err = safe.CleanupOutputs[*network.DNSResolveCache](ctx, r); err != nil { @@ -173,17 +179,6 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run } } -// SortedProxies returns sorted list of proxies and their addresses. -func SortedProxies(upstreams safe.List[*network.DNSUpstream]) ([]*proxy.Proxy, []string) { - upstreams.SortFunc(func(a, b *network.DNSUpstream) int { - return cmp.Compare(a.TypedSpec().Value.Idx, b.TypedSpec().Value.Idx) - }) - - //nolint:forcetypeassert - return safe.ToSlice(upstreams, func(d *network.DNSUpstream) *proxy.Proxy { return d.TypedSpec().Value.Prx.(*proxy.Proxy) }), - safe.ToSlice(upstreams, func(d *network.DNSUpstream) string { return d.TypedSpec().Value.Prx.Addr() }) -} - func (ctrl *DNSResolveCacheController) writeDNSStatus(ctx context.Context, r controller.Runtime, config runnerConfig) error { return safe.WriterModify(ctx, r, network.NewDNSResolveCache(fmt.Sprintf("%s-%s", config.net, config.addr)), func(drc *network.DNSResolveCache) error { drc.TypedSpec().Status = "running" @@ -353,3 +348,15 @@ func fqdnMatch(what, where string) bool { return what == first } + +type addrsArr safe.List[*network.DNSUpstream] + +func (a addrsArr) MarshalLogArray(encoder zapcore.ArrayEncoder) error { + list := safe.List[*network.DNSUpstream](a) + + for u := range list.All() { + encoder.AppendString(u.TypedSpec().Value.Conn.Addr()) + } + + return nil +} diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go index 48ede3ae2f..bc71657701 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go @@ -8,6 +8,8 @@ import ( "errors" "net" "net/netip" + "slices" + "strings" "sync" "testing" "time" @@ -306,23 +308,37 @@ func (suite *DNSUpstreams) TestOrder() { {"1.1.1.1", "8.8.8.8", "1.0.0.1", "8.0.0.8"}, {"192.168.0.1"}, } { - resolverSpec.TypedSpec().DNSServers = xslices.Map(addrs, netip.MustParseAddr) + if !suite.Run(strings.Join(addrs, ","), func() { + resolverSpec.TypedSpec().DNSServers = xslices.Map(addrs, netip.MustParseAddr) - switch i { - case 0: - suite.Require().NoError(suite.State().Create(suite.Ctx(), resolverSpec)) - default: - suite.Require().NoError(suite.State().Update(suite.Ctx(), resolverSpec)) - } + switch i { + case 0: + suite.Require().NoError(suite.State().Create(suite.Ctx(), resolverSpec)) + default: + suite.Require().NoError(suite.State().Update(suite.Ctx(), resolverSpec)) + } + + expected := xslices.Map(addrs, func(t string) string { return t + ":53" }) + + rtestutils.AssertLength[*network.DNSUpstream](suite.Ctx(), suite.T(), suite.State(), len(addrs)) - rtestutils.AssertLength[*network.DNSUpstream](suite.Ctx(), suite.T(), suite.State(), len(addrs)) + var actual []string - upstreams, err := safe.ReaderListAll[*network.DNSUpstream](suite.Ctx(), suite.State()) - suite.Require().NoError(err) + defer func() { suite.Require().Equal(expected, actual) }() - _, upstreamAddrs := netctrl.SortedProxies(upstreams) + for suite.Ctx().Err() == nil { + upstreams, err := safe.ReaderListAll[*network.DNSUpstream](suite.Ctx(), suite.State()) + suite.Require().NoError(err) - suite.Require().Equal(xslices.Map(addrs, func(t string) string { return t + ":53" }), upstreamAddrs) + actual = safe.ToSlice(upstreams, func(u *network.DNSUpstream) string { return u.TypedSpec().Value.Conn.Addr() }) + + if slices.Equal(expected, actual) { + break + } + } + }) { + break + } } } diff --git a/internal/app/machined/pkg/controllers/network/dns_upstream.go b/internal/app/machined/pkg/controllers/network/dns_upstream.go index 522477aa68..5f1bc85097 100644 --- a/internal/app/machined/pkg/controllers/network/dns_upstream.go +++ b/internal/app/machined/pkg/controllers/network/dns_upstream.go @@ -6,8 +6,8 @@ package network import ( "context" + "fmt" "net" - "time" "github.com/coredns/coredns/plugin/pkg/proxy" "github.com/cosi-project/runtime/pkg/controller" @@ -58,7 +58,7 @@ func (ctrl *DNSUpstreamController) Outputs() []controller.Output { // Run implements controller.Controller interface. func (ctrl *DNSUpstreamController) Run(ctx context.Context, r controller.Runtime, l *zap.Logger) error { - defer ctrl.cleanupUpstream(context.Background(), r, nil, l) + defer cleanupUpstream(context.Background(), r, nil, l) for { select { @@ -78,7 +78,7 @@ func (ctrl *DNSUpstreamController) Run(ctx context.Context, r controller.Runtime func (ctrl *DNSUpstreamController) run(ctx context.Context, r controller.Runtime, l *zap.Logger) error { touchedIDs := map[resource.ID]struct{}{} - defer ctrl.cleanupUpstream(ctx, r, touchedIDs, l) + defer cleanupUpstream(ctx, r, touchedIDs, l) cfg, err := safe.ReaderGetByID[*network.HostDNSConfig](ctx, r, network.HostDNSConfigID) if err != nil { @@ -103,36 +103,22 @@ func (ctrl *DNSUpstreamController) run(ctx context.Context, r controller.Runtime return err } - for i, s := range rs.TypedSpec().DNSServers { - remoteAddr := s.String() + initConn, err := existingConnections(ctx, r) + if err != nil { + return err + } + + for i, srv := range rs.TypedSpec().DNSServers { + remoteHost := srv.String() if err = safe.WriterModify[*network.DNSUpstream]( ctx, r, - network.NewDNSUpstream(remoteAddr), + network.NewDNSUpstream(fmt.Sprintf("#%03d %s", i, remoteHost)), func(u *network.DNSUpstream) error { touchedIDs[u.Metadata().ID()] = struct{}{} - if u.TypedSpec().Value.Prx != nil { - // Found upstream, update index - if u.TypedSpec().Value.Idx != i { - old := u.TypedSpec().Value.Idx - u.TypedSpec().Value.Idx = i - - l.Info("updated dns upstream idx", zap.String("addr", remoteAddr), zap.Int("was", old), zap.Int("now", i)) - } - - return nil - } - - prx := proxy.NewProxy(remoteAddr, net.JoinHostPort(remoteAddr, "53"), "dns") - - prx.Start(500 * time.Millisecond) - - u.TypedSpec().Value.Prx = prx - u.TypedSpec().Value.Idx = i - - l.Info("created dns upstream", zap.String("addr", remoteAddr), zap.Int("idx", i)) + initConn(&u.TypedSpec().Value, remoteHost, l) return nil }, @@ -144,7 +130,43 @@ func (ctrl *DNSUpstreamController) run(ctx context.Context, r controller.Runtime return nil } -func (ctrl *DNSUpstreamController) cleanupUpstream(ctx context.Context, r controller.Runtime, touchedIDs map[resource.ID]struct{}, l *zap.Logger) { +func existingConnections(ctx context.Context, r controller.Runtime) (func(*network.DNSUpstreamSpecSpec, string, *zap.Logger), error) { + upstream, err := safe.ReaderListAll[*network.DNSUpstream](ctx, r) + if err != nil { + return nil, err + } + + existingConn := make(map[string]*network.DNSConn, upstream.Len()) + + for u := range upstream.All() { + existingConn[u.TypedSpec().Value.Conn.Addr()] = u.TypedSpec().Value.Conn + } + + return func(spec *network.DNSUpstreamSpecSpec, remoteHost string, l *zap.Logger) { + remoteAddr := net.JoinHostPort(remoteHost, "53") + if spec.Conn != nil && spec.Conn.Addr() == remoteAddr { + l.Debug("reusing existing upstream spec", zap.String("addr", remoteAddr)) + + return + } + + if conn, ok := existingConn[remoteAddr]; ok { + spec.Conn = conn + + l.Debug("reusing existing upstream connection", zap.String("addr", remoteAddr)) + + return + } + + spec.Conn = network.NewDNSConn(proxy.NewProxy(remoteHost, remoteAddr, "dns"), l) + + l.Debug("created new upstream connection", zap.String("addr", remoteAddr)) + + existingConn[remoteAddr] = spec.Conn + }, nil +} + +func cleanupUpstream(ctx context.Context, r controller.Runtime, touchedIDs map[resource.ID]struct{}, l *zap.Logger) { list, err := safe.ReaderListAll[*network.DNSUpstream](ctx, r) if err != nil { l.Error("error listing upstreams", zap.Error(err)) @@ -156,15 +178,13 @@ func (ctrl *DNSUpstreamController) cleanupUpstream(ctx context.Context, r contro md := val.Metadata() if _, ok := touchedIDs[md.ID()]; !ok { - val.TypedSpec().Value.Prx.Stop() - if err = r.Destroy(ctx, md); err != nil { l.Error("error destroying upstream", zap.Error(err), zap.String("id", md.ID())) return } - l.Info("destroyed dns upstream", zap.String("addr", md.ID())) + l.Debug("destroyed dns upstream", zap.String("addr", md.ID())) } } } diff --git a/internal/pkg/dns/dns.go b/internal/pkg/dns/dns.go index adb79ace75..6a8be932fc 100644 --- a/internal/pkg/dns/dns.go +++ b/internal/pkg/dns/dns.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "iter" "net" "net/netip" "slices" @@ -25,6 +26,7 @@ import ( "github.com/coredns/coredns/plugin/pkg/proxy" "github.com/coredns/coredns/request" "github.com/miekg/dns" + "github.com/siderolabs/gen/xiter" "go.uber.org/zap" "golang.org/x/sys/unix" ) @@ -90,7 +92,7 @@ func clientWrite(rcode int) bool { // Handler is a dns proxy selector. type Handler struct { mx sync.RWMutex - dests []*proxy.Proxy + dests iter.Seq[*proxy.Proxy] logger *zap.Logger } @@ -98,6 +100,7 @@ type Handler struct { func NewHandler(logger *zap.Logger) *Handler { return &Handler{ logger: logger, + dests: xiter.Empty[*proxy.Proxy], } } @@ -117,16 +120,14 @@ func (h *Handler) ServeDNS(ctx context.Context, wrt dns.ResponseWriter, msg *dns h.logger.Debug("dns request", zap.Stringer("data", msg)) - if len(h.dests) == 0 { - return dns.RcodeServerFailure, errors.New("no destination available") - } - var ( - resp *dns.Msg - err error + called bool + resp *dns.Msg + err error ) - for _, ups := range h.dests { + for ups := range h.dests { + called = true opts := proxy.Options{} for { @@ -155,6 +156,10 @@ func (h *Handler) ServeDNS(ctx context.Context, wrt dns.ResponseWriter, msg *dns continue } + if !called { + return dns.RcodeServerFailure, errors.New("no destination available") + } + if ctx.Err() != nil { return dns.RcodeServerFailure, ctx.Err() } else if err != nil { @@ -179,11 +184,11 @@ func (h *Handler) ServeDNS(ctx context.Context, wrt dns.ResponseWriter, msg *dns } // SetProxy sets destination dns proxy servers. -func (h *Handler) SetProxy(prxs []*proxy.Proxy) bool { +func (h *Handler) SetProxy(prxs iter.Seq[*proxy.Proxy]) bool { h.mx.Lock() defer h.mx.Unlock() - if slices.Equal(h.dests, prxs) { + if xiter.Equal(h.dests, prxs) { return false } @@ -193,7 +198,7 @@ func (h *Handler) SetProxy(prxs []*proxy.Proxy) bool { } // Stop stops and clears dns proxy selector. -func (h *Handler) Stop() { h.SetProxy(nil) } +func (h *Handler) Stop() { h.SetProxy(xiter.Empty) } // NewNodeHandler creates a new NodeHandler. func NewNodeHandler(next plugin.Handler, hostMapper HostMapper, logger *zap.Logger) *NodeHandler { diff --git a/internal/pkg/dns/dns_test.go b/internal/pkg/dns/dns_test.go index 922c673213..e15ac7992e 100644 --- a/internal/pkg/dns/dns_test.go +++ b/internal/pkg/dns/dns_test.go @@ -8,12 +8,14 @@ import ( "context" "net" "net/netip" + "slices" "testing" "time" "github.com/coredns/coredns/plugin/pkg/proxy" dnssrv "github.com/miekg/dns" "github.com/siderolabs/gen/ensure" + "github.com/siderolabs/gen/xiter" "github.com/siderolabs/gen/xslices" "github.com/siderolabs/gen/xtesting/check" "github.com/stretchr/testify/require" @@ -125,7 +127,7 @@ func newServer(t *testing.T, nameservers ...string) func() { return p }) - handler.SetProxy(pxs) + handler.SetProxy(xiter.Values(slices.All(pxs))) pc, err := dns.NewUDPPacketConn("udp", "127.0.0.53:10700", ensure.Value(dns.MakeControl("udp", false))) require.NoError(t, err) diff --git a/pkg/machinery/go.mod b/pkg/machinery/go.mod index 751e99574e..1ffa583e11 100644 --- a/pkg/machinery/go.mod +++ b/pkg/machinery/go.mod @@ -24,7 +24,7 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/siderolabs/crypto v0.5.0 - github.com/siderolabs/gen v0.5.0 + github.com/siderolabs/gen v0.6.1 github.com/siderolabs/go-api-signature v0.3.6 github.com/siderolabs/go-blockdevice v0.4.8 github.com/siderolabs/go-blockdevice/v2 v2.0.2 @@ -32,6 +32,7 @@ require ( github.com/siderolabs/net v0.4.0 github.com/siderolabs/protoenc v0.2.1 github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 google.golang.org/grpc v1.67.1 // do not update to 1.68.0 until we find a way around https://github.com/grpc/grpc-go/pull/7535 @@ -64,7 +65,6 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/net v0.30.0 // indirect diff --git a/pkg/machinery/go.sum b/pkg/machinery/go.sum index e66c52e7d9..88e4545b8f 100644 --- a/pkg/machinery/go.sum +++ b/pkg/machinery/go.sum @@ -23,8 +23,6 @@ github.com/containerd/go-cni v1.1.10 h1:c2U73nld7spSWfiJwSh/8W9DK+/qQwYM2rngIhCy github.com/containerd/go-cni v1.1.10/go.mod h1:/Y/sL8yqYQn1ZG1om1OncJB1W4zN3YmjfP/ShCzG/OY= github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= -github.com/cosi-project/runtime v0.5.5 h1:GFoHnngpg4QVZluAUDwUbCe/sYOYBXKULxL/6DD99pU= -github.com/cosi-project/runtime v0.5.5/go.mod h1:m+bkfUzKYeUyoqYAQBxdce3bfgncG8BsqcbfKRbvJKs= github.com/cosi-project/runtime v0.6.4 h1:roifc5e+Q1+72EI36BYSRT9aXyskU+coiKHeoBBWkMg= github.com/cosi-project/runtime v0.6.4/go.mod h1:EMLs8a55tJ6zA4UyDbRsTvXBd6UIlNwZfCVGvCyiXK8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -75,8 +73,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mdlayher/ethtool v0.1.0 h1:XAWHsmKhyPOo42qq/yTPb0eFBGUKKTR1rE0dVrWVQ0Y= -github.com/mdlayher/ethtool v0.1.0/go.mod h1:fBMLn2UhfRGtcH5ZFjr+6GUiHEjZsItFD7fSn7jbZVQ= github.com/mdlayher/ethtool v0.2.0 h1:akcA4WZVWozzirPASeMq8qgLkxpF3ykftVXwnrMKrhY= github.com/mdlayher/ethtool v0.2.0/go.mod h1:W0pIBrNPK1TslIN4Z9wt1EVbay66Kbvek2z2f29VBfw= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= @@ -107,12 +103,10 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/siderolabs/crypto v0.5.0 h1:+Sox0aYLCcD0PAH2cbEcx557zUrONLtuj1Ws+2MFXGc= github.com/siderolabs/crypto v0.5.0/go.mod h1:hsR3tJ3aaeuhCChsLF4dBd9vlJVPvmhg4vvx2ez4aD4= -github.com/siderolabs/gen v0.5.0 h1:Afdjx+zuZDf53eH5DB+E+T2JeCwBXGinV66A6osLgQI= -github.com/siderolabs/gen v0.5.0/go.mod h1:1GUMBNliW98Xeq8GPQeVMYqQE09LFItE8enR3wgMh3Q= +github.com/siderolabs/gen v0.6.1 h1:Mex6Q41Tlw3e+4cGvlju2x4UwULD5WMo/D82n7IxV0Y= +github.com/siderolabs/gen v0.6.1/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU= github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= -github.com/siderolabs/go-blockdevice v0.4.7 h1:2bk4WpEEflGxjrNwp57ye24Pr+cYgAiAeNMWiQOuWbQ= -github.com/siderolabs/go-blockdevice v0.4.7/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA= github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c= github.com/siderolabs/go-blockdevice v0.4.8/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA= github.com/siderolabs/go-blockdevice/v2 v2.0.2 h1:GIdOBrCLQ7X9jbr0P/+7paw5SIfp/LL+dx9mTOzmw8w= @@ -203,19 +197,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= -google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/machinery/resources/network/dns_upstream.go b/pkg/machinery/resources/network/dns_upstream.go index 0a1913185a..9cc16e471b 100644 --- a/pkg/machinery/resources/network/dns_upstream.go +++ b/pkg/machinery/resources/network/dns_upstream.go @@ -5,6 +5,7 @@ package network import ( + "runtime" "strconv" "time" @@ -12,6 +13,7 @@ import ( "github.com/cosi-project/runtime/pkg/resource/handle" "github.com/cosi-project/runtime/pkg/resource/meta" "github.com/cosi-project/runtime/pkg/resource/typed" + "go.uber.org/zap" ) // DNSUpstreamType is type of DNSUpstream resource. @@ -21,25 +23,20 @@ const DNSUpstreamType = resource.Type("DNSUpstreams.net.talos.dev") type DNSUpstream = typed.Resource[DNSUpstreamSpec, DNSUpstreamExtension] // DNSUpstreamSpec describes DNS upstreams status. -type DNSUpstreamSpec = handle.ResourceSpec[*DNSUpstreamSpecSpec] +type DNSUpstreamSpec = handle.ResourceSpec[DNSUpstreamSpecSpec] // DNSUpstreamSpecSpec describes DNS upstreams status. type DNSUpstreamSpecSpec struct { - // Proxy is essentially a *proxy.Proxy interface. It's here because we don't want machinery to depend on coredns. - // We could use a generic struct here, but without generic aliases the usage would look ugly. - // Once generic aliases are here, redo the type above as `type DNSUpstream[P Proxy] = typed.Resource[...]`. - Prx Proxy - Idx int + Conn *DNSConn } // MarshalYAML implements yaml.Marshaler interface. -func (d *DNSUpstreamSpecSpec) MarshalYAML() (any, error) { - d.Prx.Healthcheck() +func (d DNSUpstreamSpecSpec) MarshalYAML() (any, error) { + d.Conn.Healthcheck() return map[string]string{ - "healthy": strconv.FormatBool(d.Prx.Fails() == 0), - "addr": d.Prx.Addr(), - "idx": strconv.Itoa(d.Idx), + "healthy": strconv.FormatBool(d.Conn.Fails() == 0), + "addr": d.Conn.Addr(), }, nil } @@ -47,7 +44,7 @@ func (d *DNSUpstreamSpecSpec) MarshalYAML() (any, error) { func NewDNSUpstream(id resource.ID) *DNSUpstream { return typed.NewResource[DNSUpstreamSpec, DNSUpstreamExtension]( resource.NewMetadata(NamespaceName, DNSUpstreamType, id, resource.VersionUndefined), - DNSUpstreamSpec{Value: &DNSUpstreamSpecSpec{}}, + DNSUpstreamSpec{Value: DNSUpstreamSpecSpec{}}, ) } @@ -69,10 +66,6 @@ func (DNSUpstreamExtension) ResourceDefinition() meta.ResourceDefinitionSpec { Name: "Address", JSONPath: "{.addr}", }, - { - Name: "Idx", - JSONPath: "{.idx}", - }, }, } } @@ -84,5 +77,44 @@ type Proxy interface { Fails() uint32 Healthcheck() Stop() - Start(duration time.Duration) + Start(time.Duration) +} + +// DNSConn is a wrapper around a Proxy. +type DNSConn struct { + // Proxy is essentially a *proxy.Proxy interface. It's here because we don't want machinery to depend on coredns. + // We could use a generic struct here, but without generic aliases the usage would look ugly. + // Once generic aliases are here, redo the type above as `type DNSUpstream[P Proxy] = typed.Resource[...]`. + proxy Proxy } + +// NewDNSConn initializes a new DNSConn. +func NewDNSConn(proxy Proxy, l *zap.Logger) *DNSConn { + proxy.Start(500 * time.Millisecond) + + conn := &DNSConn{proxy: proxy} + + // Set the finalizer to stop the proxy when the DNSConn is garbage collected. Since the proxy already uses a finalizer + // to stop the actual connections, this will not carry any noticeable performance overhead. + // + // TODO: replace with runtime.AddCleanup once https://github.com/golang/go/issues/67535 lands + runtime.SetFinalizer(conn, func(conn *DNSConn) { + conn.proxy.Stop() + + l.Info("dns connection garbage collected", zap.String("addr", conn.proxy.Addr())) + }) + + return conn +} + +// Addr returns the address of the DNSConn. +func (u *DNSConn) Addr() string { return u.proxy.Addr() } + +// Fails returns the number of fails of the DNSConn. +func (u *DNSConn) Fails() uint32 { return u.proxy.Fails() } + +// Proxy returns the Proxy field of the DNSConn. +func (u *DNSConn) Proxy() Proxy { return u.proxy } + +// Healthcheck kicks of a round of health checks for this DNSConn. +func (u *DNSConn) Healthcheck() { u.proxy.Healthcheck() }