chore: support getting multiple endpoints from the Provision rpc call

The code will rotate through the endpoints, until it reaches the end, and only then it will try to do the provisioning again.

Closes #7973

Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
This commit is contained in:
Dmitriy Matrenichev 2023-11-24 19:19:34 +03:00
parent dd45dd06cf
commit ba827bf8b8
No known key found for this signature in database
GPG Key ID: D3363CF894E68892
4 changed files with 161 additions and 91 deletions

2
go.mod
View File

@ -124,7 +124,7 @@ require (
github.com/siderolabs/grpc-proxy v0.4.0 github.com/siderolabs/grpc-proxy v0.4.0
github.com/siderolabs/kms-client v0.1.0 github.com/siderolabs/kms-client v0.1.0
github.com/siderolabs/net v0.4.0 github.com/siderolabs/net v0.4.0
github.com/siderolabs/siderolink v0.3.2 github.com/siderolabs/siderolink v0.3.3
github.com/siderolabs/talos/pkg/machinery v1.6.0-alpha.2 github.com/siderolabs/talos/pkg/machinery v1.6.0-alpha.2
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5

4
go.sum
View File

@ -691,8 +691,8 @@ github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I=
github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM= github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM=
github.com/siderolabs/protoenc v0.2.1 h1:BqxEmeWQeMpNP3R6WrPqDatX8sM/r4t97OP8mFmg6GA= github.com/siderolabs/protoenc v0.2.1 h1:BqxEmeWQeMpNP3R6WrPqDatX8sM/r4t97OP8mFmg6GA=
github.com/siderolabs/protoenc v0.2.1/go.mod h1:StTHxjet1g11GpNAWiATgc8K0HMKiFSEVVFOa/H0otc= github.com/siderolabs/protoenc v0.2.1/go.mod h1:StTHxjet1g11GpNAWiATgc8K0HMKiFSEVVFOa/H0otc=
github.com/siderolabs/siderolink v0.3.2 h1:ULFHQAgxtVCU7Sd+GLP7bDSQBXrwTtppaI4TKl/YqZc= github.com/siderolabs/siderolink v0.3.3 h1:rnsN4K4TPtk38Ygs/oKQsiVe8iYUi9RRS8gh4U7mbGM=
github.com/siderolabs/siderolink v0.3.2/go.mod h1:juxlSF9cBzeBHsOjS7hVS3s0NDpC034i/OZunVReqmo= github.com/siderolabs/siderolink v0.3.3/go.mod h1:juxlSF9cBzeBHsOjS7hVS3s0NDpC034i/OZunVReqmo=
github.com/siderolabs/tcpproxy v0.1.0 h1:IbkS9vRhjMOscc1US3M5P1RnsGKFgB6U5IzUk+4WkKA= github.com/siderolabs/tcpproxy v0.1.0 h1:IbkS9vRhjMOscc1US3M5P1RnsGKFgB6U5IzUk+4WkKA=
github.com/siderolabs/tcpproxy v0.1.0/go.mod h1:onn6CPPj/w1UNqQ0U97oRPF0CqbrgEApYCw4P9IiCW8= github.com/siderolabs/tcpproxy v0.1.0/go.mod h1:onn6CPPj/w1UNqQ0U97oRPF0CqbrgEApYCw4P9IiCW8=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=

View File

@ -44,6 +44,7 @@ import (
// ManagerController interacts with SideroLink API and brings up the SideroLink Wireguard interface. // ManagerController interacts with SideroLink API and brings up the SideroLink Wireguard interface.
type ManagerController struct { type ManagerController struct {
nodeKey wgtypes.Key nodeKey wgtypes.Key
pd provisionData
} }
// Name implements controller.Controller interface. // Name implements controller.Controller interface.
@ -146,28 +147,139 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
case <-r.EventCh(): case <-r.EventCh():
} }
if ctrl.pd.IsEmpty() {
provision, err := ctrl.provision(ctx, r, logger)
if err != nil {
return fmt.Errorf("error provisioning: %w", err)
}
if !provision.IsPresent() {
continue
}
ctrl.pd = provision.ValueOrZero()
}
serverAddress, err := netip.ParseAddr(ctrl.pd.ServerAddress)
if err != nil {
return fmt.Errorf("error parsing server address: %w", err)
}
nodeAddress, err := netip.ParsePrefix(ctrl.pd.NodeAddressPrefix)
if err != nil {
return fmt.Errorf("error parsing node address: %w", err)
}
linkSpec := network.NewLinkSpec(network.ConfigNamespaceName, network.LayeredID(network.ConfigOperator, network.LinkID(constants.SideroLinkName)))
addressSpec := network.NewAddressSpec(network.ConfigNamespaceName, network.LayeredID(network.ConfigOperator, network.AddressID(constants.SideroLinkName, nodeAddress)))
// Rotate through the endpoints.
ep, ok := ctrl.pd.TakeEndpoint()
if !ok {
return errors.New("host returned no endpoints")
}
logger.Info(
"configuring siderolink connection",
zap.String("peer_endpoint", ep),
zap.String("next_peer_endpoint", ctrl.pd.PeekNextEndpoint()),
)
if err := safe.WriterModify(ctx, r, linkSpec,
func(res *network.LinkSpec) error {
spec := res.TypedSpec()
spec.ConfigLayer = network.ConfigOperator
spec.Name = constants.SideroLinkName
spec.Type = nethelpers.LinkNone
spec.Kind = "wireguard"
spec.Up = true
spec.Logical = true
spec.MTU = wireguard.LinkMTU
spec.Wireguard = network.WireguardSpec{
PrivateKey: ctrl.nodeKey.String(),
Peers: []network.WireguardPeer{
{
PublicKey: ctrl.pd.ServerPublicKey,
Endpoint: ep,
AllowedIPs: []netip.Prefix{
netip.PrefixFrom(serverAddress, serverAddress.BitLen()),
},
// make sure Talos pings SideroLink endpoint, so that tunnel is established:
// SideroLink doesn't know Talos endpoint.
PersistentKeepaliveInterval: constants.SideroLinkDefaultPeerKeepalive,
},
},
}
spec.Wireguard.Sort()
return nil
}); err != nil {
return fmt.Errorf("error creating siderolink spec: %w", err)
}
if err := safe.WriterModify(ctx, r, addressSpec,
func(res *network.AddressSpec) error {
spec := res.TypedSpec()
spec.ConfigLayer = network.ConfigOperator
spec.Address = nodeAddress
spec.Family = nethelpers.FamilyInet6
spec.Flags = nethelpers.AddressFlags(nethelpers.AddressPermanent)
spec.LinkName = constants.SideroLinkName
spec.Scope = nethelpers.ScopeGlobal
return nil
}); err != nil {
return fmt.Errorf("error creating address spec: %w", err)
}
keepLinkSpecSet := map[resource.ID]struct{}{
linkSpec.Metadata().ID(): {},
}
keepAddressSpecSet := map[resource.ID]struct{}{
addressSpec.Metadata().ID(): {},
}
if err := ctrl.cleanup(ctx, r, keepLinkSpecSet, keepAddressSpecSet, logger); err != nil {
return err
}
logger.Info(
"siderolink connection configured",
zap.String("endpoint", ctrl.pd.apiEndpont),
zap.String("node_uuid", ctrl.pd.nodeUUID),
zap.String("node_address", nodeAddress.String()),
)
}
}
//nolint:gocyclo
func (ctrl *ManagerController) provision(ctx context.Context, r controller.Runtime, logger *zap.Logger) (optional.Optional[provisionData], error) {
cfg, err := safe.ReaderGetByID[*siderolink.Config](ctx, r, siderolink.ConfigID) cfg, err := safe.ReaderGetByID[*siderolink.Config](ctx, r, siderolink.ConfigID)
if err != nil { if err != nil {
if state.IsNotFoundError(err) { if state.IsNotFoundError(err) {
if cleanupErr := ctrl.cleanup(ctx, r, nil, nil, logger); cleanupErr != nil { if cleanupErr := ctrl.cleanup(ctx, r, nil, nil, logger); cleanupErr != nil {
return fmt.Errorf("failed to do cleanup: %w", cleanupErr) return optional.None[provisionData](), fmt.Errorf("failed to do cleanup: %w", cleanupErr)
} }
// no config // no config
continue return optional.None[provisionData](), nil
} }
return fmt.Errorf("failed to get siderolink config: %w", err) return optional.None[provisionData](), fmt.Errorf("failed to get siderolink config: %w", err)
} }
sysInfo, err := safe.ReaderGetByID[*hardware.SystemInformation](ctx, r, hardware.SystemInformationID) sysInfo, err := safe.ReaderGetByID[*hardware.SystemInformation](ctx, r, hardware.SystemInformationID)
if err != nil { if err != nil {
if state.IsNotFoundError(err) { if state.IsNotFoundError(err) {
// no system information // no system information
continue return optional.None[provisionData](), nil
} }
return fmt.Errorf("failed to get system information: %w", err) return optional.None[provisionData](), fmt.Errorf("failed to get system information: %w", err)
} }
nodeUUID := sysInfo.TypedSpec().UUID nodeUUID := sysInfo.TypedSpec().UUID
@ -175,7 +287,7 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
parsedEndpoint, err := endpoint.Parse(stringEndpoint) parsedEndpoint, err := endpoint.Parse(stringEndpoint)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse siderolink endpoint: %w", err) return optional.None[provisionData](), fmt.Errorf("failed to parse siderolink endpoint: %w", err)
} }
var transportCredentials credentials.TransportCredentials var transportCredentials credentials.TransportCredentials
@ -230,91 +342,49 @@ func (ctrl *ManagerController) Run(ctx context.Context, r controller.Runtime, lo
resp, err := provision() resp, err := provision()
if err != nil { if err != nil {
return err return optional.None[provisionData](), err
} }
serverAddress, err := netip.ParseAddr(resp.ServerAddress) return optional.Some(provisionData{
if err != nil { nodeUUID: nodeUUID,
return fmt.Errorf("error parsing server address: %w", err) apiEndpont: stringEndpoint,
ServerAddress: resp.ServerAddress,
ServerPublicKey: resp.ServerPublicKey,
NodeAddressPrefix: resp.NodeAddressPrefix,
endpoints: resp.GetEndpoints(),
}), nil
}
type provisionData struct {
nodeUUID string
apiEndpont string
ServerAddress string
ServerPublicKey string
NodeAddressPrefix string
endpoints []string
}
func (d *provisionData) IsEmpty() bool {
return d == nil || len(d.endpoints) == 0
}
func (d *provisionData) TakeEndpoint() (string, bool) {
if d.IsEmpty() {
return "", false
} }
nodeAddress, err := netip.ParsePrefix(resp.NodeAddressPrefix) ep := d.endpoints[0]
if err != nil { d.endpoints = d.endpoints[1:]
return fmt.Errorf("error parsing node address: %w", err)
return ep, true
}
func (d *provisionData) PeekNextEndpoint() string {
if d.IsEmpty() {
return ""
} }
linkSpec := network.NewLinkSpec(network.ConfigNamespaceName, network.LayeredID(network.ConfigOperator, network.LinkID(constants.SideroLinkName))) return d.endpoints[0]
addressSpec := network.NewAddressSpec(network.ConfigNamespaceName, network.LayeredID(network.ConfigOperator, network.AddressID(constants.SideroLinkName, nodeAddress)))
if err := safe.WriterModify(ctx, r, linkSpec,
func(res *network.LinkSpec) error {
spec := res.TypedSpec()
spec.ConfigLayer = network.ConfigOperator
spec.Name = constants.SideroLinkName
spec.Type = nethelpers.LinkNone
spec.Kind = "wireguard"
spec.Up = true
spec.Logical = true
spec.MTU = wireguard.LinkMTU
spec.Wireguard = network.WireguardSpec{
PrivateKey: ctrl.nodeKey.String(),
Peers: []network.WireguardPeer{
{
PublicKey: resp.ServerPublicKey,
Endpoint: resp.ServerEndpoint,
AllowedIPs: []netip.Prefix{
netip.PrefixFrom(serverAddress, serverAddress.BitLen()),
},
// make sure Talos pings SideroLink endpoint, so that tunnel is established:
// SideroLink doesn't know Talos endpoint.
PersistentKeepaliveInterval: constants.SideroLinkDefaultPeerKeepalive,
},
},
}
spec.Wireguard.Sort()
return nil
}); err != nil {
return fmt.Errorf("error creating siderolink spec: %w", err)
}
if err := safe.WriterModify(ctx, r, addressSpec,
func(res *network.AddressSpec) error {
spec := res.TypedSpec()
spec.ConfigLayer = network.ConfigOperator
spec.Address = nodeAddress
spec.Family = nethelpers.FamilyInet6
spec.Flags = nethelpers.AddressFlags(nethelpers.AddressPermanent)
spec.LinkName = constants.SideroLinkName
spec.Scope = nethelpers.ScopeGlobal
return nil
}); err != nil {
return fmt.Errorf("error creating address spec: %w", err)
}
keepLinkSpecSet := map[resource.ID]struct{}{
linkSpec.Metadata().ID(): {},
}
keepAddressSpecSet := map[resource.ID]struct{}{
addressSpec.Metadata().ID(): {},
}
if err := ctrl.cleanup(ctx, r, keepLinkSpecSet, keepAddressSpecSet, logger); err != nil {
return err
}
logger.Info(
"siderolink connection configured",
zap.String("endpoint", stringEndpoint),
zap.String("node_uuid", nodeUUID),
zap.String("node_address", nodeAddress.String()),
)
}
} }
func (ctrl *ManagerController) cleanup( func (ctrl *ManagerController) cleanup(

View File

@ -72,7 +72,7 @@ const (
func (srv mockServer) Provision(_ context.Context, _ *pb.ProvisionRequest) (*pb.ProvisionResponse, error) { func (srv mockServer) Provision(_ context.Context, _ *pb.ProvisionRequest) (*pb.ProvisionResponse, error) {
return &pb.ProvisionResponse{ return &pb.ProvisionResponse{
ServerEndpoint: mockServerEndpoint, ServerEndpoint: pb.MakeEndpoints(mockServerEndpoint),
ServerAddress: mockServerAddress, ServerAddress: mockServerAddress,
ServerPublicKey: mockServerPublicKey, ServerPublicKey: mockServerPublicKey,
NodeAddressPrefix: mockNodeAddressPrefix, NodeAddressPrefix: mockNodeAddressPrefix,