mirror of
https://github.com/siderolabs/talos.git
synced 2025-09-09 16:01:14 +02:00
feat: inject platform network configuration as network resources
Instead of patching in the machine config, provide a new interface in the platforms to supply platform-specific network configuration. Configuration will be cached in the `/state` across reboots, and configuration might be even updated in real time (e.g. on GCP). Network configuration on platform level can be overridden with user-supplied machine configuration. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
parent
907f8cbfb8
commit
9ad5a67d21
@ -155,6 +155,7 @@ func (ctrl *AddressConfigController) Run(ctx context.Context, r controller.Runti
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func (ctrl *AddressConfigController) apply(ctx context.Context, r controller.Runtime, addresses []network.AddressSpecSpec) ([]resource.ID, error) {
|
||||
ids := make([]string, 0, len(addresses))
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package network provides controllers which manage network resources.
|
||||
//
|
||||
//nolint:dupl
|
||||
package network
|
||||
|
||||
import (
|
||||
|
@ -99,7 +99,7 @@ func (suite *AddressMergeSuite) assertAddresses(requiredIDs []string, check func
|
||||
}
|
||||
|
||||
func (suite *AddressMergeSuite) assertNoAddress(id string) error {
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.AddressStatusType, "", resource.VersionUndefined))
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.AddressSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -47,6 +47,11 @@ func (ctrl *OperatorConfigController) Inputs() []controller.Input {
|
||||
Type: network.LinkStatusType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: network.ConfigNamespaceName,
|
||||
Type: network.LinkSpecType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +60,7 @@ func (ctrl *OperatorConfigController) Outputs() []controller.Output {
|
||||
return []controller.Output{
|
||||
{
|
||||
Type: network.OperatorSpecType,
|
||||
Kind: controller.OutputExclusive,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -85,7 +90,6 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
}
|
||||
|
||||
ignoredInterfaces := map[string]struct{}{}
|
||||
configuredInterfaces := map[string]struct{}{}
|
||||
|
||||
if ctrl.Cmdline != nil {
|
||||
var settings CmdlineNetworking
|
||||
@ -98,10 +102,6 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
for _, link := range settings.IgnoreInterfaces {
|
||||
ignoredInterfaces[link] = struct{}{}
|
||||
}
|
||||
|
||||
if settings.LinkName != "" {
|
||||
configuredInterfaces[settings.LinkName] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@ -112,22 +112,14 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
// operators from the config
|
||||
if cfgProvider != nil {
|
||||
for _, device := range cfgProvider.Machine().Network().Devices() {
|
||||
configuredInterfaces[device.Interface()] = struct{}{}
|
||||
|
||||
if device.Ignore() {
|
||||
continue
|
||||
ignoredInterfaces[device.Interface()] = struct{}{}
|
||||
}
|
||||
|
||||
if _, ignore := ignoredInterfaces[device.Interface()]; ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
if device.Bond() != nil {
|
||||
for _, link := range device.Bond().Interfaces() {
|
||||
configuredInterfaces[link] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if device.DHCP() && device.DHCPOptions().IPv4() {
|
||||
routeMetric := device.DHCPOptions().RouteMetric()
|
||||
if routeMetric == 0 {
|
||||
@ -141,6 +133,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: routeMetric,
|
||||
},
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
})
|
||||
}
|
||||
|
||||
@ -157,6 +150,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: routeMetric,
|
||||
},
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
})
|
||||
}
|
||||
|
||||
@ -177,6 +171,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: DefaultRouteMetric,
|
||||
},
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
})
|
||||
}
|
||||
|
||||
@ -192,10 +187,33 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
}
|
||||
}
|
||||
|
||||
// operators from defaults
|
||||
list, err := r.List(ctx, resource.NewMetadata(network.NamespaceName, network.LinkStatusType, "", resource.VersionUndefined))
|
||||
// build configuredInterfaces from linkSpecs in `network-config` namespace
|
||||
// any link which has any configuration derived from the machine configuration or platform configuration should be ignored
|
||||
configuredInterfaces := map[string]struct{}{}
|
||||
|
||||
list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.LinkSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing link statuses")
|
||||
return fmt.Errorf("error listing link specs: %w", err)
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
linkSpec := item.(*network.LinkSpec).TypedSpec()
|
||||
|
||||
switch linkSpec.ConfigLayer {
|
||||
case network.ConfigDefault:
|
||||
// ignore default link specs
|
||||
case network.ConfigOperator:
|
||||
// specs produced by operators, ignore
|
||||
case network.ConfigCmdline, network.ConfigMachineConfiguration, network.ConfigPlatform:
|
||||
// interface is configured explicitly, don't run default dhcp4
|
||||
configuredInterfaces[linkSpec.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// operators from defaults
|
||||
list, err = r.List(ctx, resource.NewMetadata(network.NamespaceName, network.LinkStatusType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing link statuses: %w", err)
|
||||
}
|
||||
|
||||
for _, item := range list.Items {
|
||||
@ -212,6 +230,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: DefaultRouteMetric,
|
||||
},
|
||||
ConfigLayer: network.ConfigDefault,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -230,7 +249,7 @@ func (ctrl *OperatorConfigController) Run(ctx context.Context, r controller.Runt
|
||||
}
|
||||
|
||||
// list specs for cleanup
|
||||
list, err = r.List(ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
list, err = r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing resources: %w", err)
|
||||
}
|
||||
@ -261,11 +280,11 @@ func (ctrl *OperatorConfigController) apply(ctx context.Context, r controller.Ru
|
||||
|
||||
for _, spec := range specs {
|
||||
spec := spec
|
||||
id := network.OperatorID(spec.Operator, spec.LinkName)
|
||||
id := network.LayeredID(spec.ConfigLayer, network.OperatorID(spec.Operator, spec.LinkName))
|
||||
|
||||
if err := r.Modify(
|
||||
ctx,
|
||||
network.NewOperatorSpec(network.NamespaceName, id),
|
||||
network.NewOperatorSpec(network.ConfigNamespaceName, id),
|
||||
func(r resource.Resource) error {
|
||||
*r.(*network.OperatorSpec).TypedSpec() = spec
|
||||
|
||||
@ -299,6 +318,7 @@ func handleVIP(ctx context.Context, vlanConfig talosconfig.VIPConfig, deviceName
|
||||
IP: sharedIP,
|
||||
GratuitousARP: true,
|
||||
},
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
|
||||
switch {
|
||||
|
@ -73,7 +73,7 @@ func (suite *OperatorConfigSuite) assertOperators(requiredIDs []string, check fu
|
||||
missingIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -105,7 +105,7 @@ func (suite *OperatorConfigSuite) assertNoOperators(unexpectedIDs []string) erro
|
||||
unexpIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -138,17 +138,17 @@ func (suite *OperatorConfigSuite) TestDefaultDHCP() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp4/eth0",
|
||||
"dhcp4/eth1",
|
||||
"default/dhcp4/eth0",
|
||||
"default/dhcp4/eth1",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
suite.Assert().Equal(network.OperatorDHCP4, r.TypedSpec().Operator)
|
||||
suite.Assert().True(r.TypedSpec().RequireUp)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().DHCP4.RouteMetric)
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "dhcp4/eth0":
|
||||
case "default/dhcp4/eth0":
|
||||
suite.Assert().Equal("eth0", r.TypedSpec().LinkName)
|
||||
case "dhcp4/eth1":
|
||||
case "default/dhcp4/eth1":
|
||||
suite.Assert().Equal("eth1", r.TypedSpec().LinkName)
|
||||
}
|
||||
|
||||
@ -175,17 +175,17 @@ func (suite *OperatorConfigSuite) TestDefaultDHCPCmdline() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp4/eth0",
|
||||
"dhcp4/eth2",
|
||||
"default/dhcp4/eth0",
|
||||
"default/dhcp4/eth2",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
suite.Assert().Equal(network.OperatorDHCP4, r.TypedSpec().Operator)
|
||||
suite.Assert().True(r.TypedSpec().RequireUp)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().DHCP4.RouteMetric)
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "dhcp4/eth0":
|
||||
case "default/dhcp4/eth0":
|
||||
suite.Assert().Equal("eth0", r.TypedSpec().LinkName)
|
||||
case "dhcp4/eth2":
|
||||
case "default/dhcp4/eth2":
|
||||
suite.Assert().Equal("eth2", r.TypedSpec().LinkName)
|
||||
}
|
||||
|
||||
@ -199,7 +199,7 @@ func (suite *OperatorConfigSuite) TestDefaultDHCPCmdline() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperators([]string{
|
||||
"dhcp4/eth2",
|
||||
"default/dhcp4/eth2",
|
||||
})
|
||||
}))
|
||||
}
|
||||
@ -208,6 +208,10 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.OperatorConfigController{
|
||||
Cmdline: procfs.NewCmdline("talos.network.interface.ignore=eth5"),
|
||||
}))
|
||||
// add LinkConfig controller to produce link specs based on machine configuration
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.LinkConfigController{
|
||||
Cmdline: procfs.NewCmdline("talos.network.interface.ignore=eth5"),
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
@ -280,21 +284,21 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp4/eth1",
|
||||
"dhcp4/eth3",
|
||||
"dhcp4/eth4.25",
|
||||
"configuration/dhcp4/eth1",
|
||||
"configuration/dhcp4/eth3",
|
||||
"configuration/dhcp4/eth4.25",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
suite.Assert().Equal(network.OperatorDHCP4, r.TypedSpec().Operator)
|
||||
suite.Assert().True(r.TypedSpec().RequireUp)
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "dhcp4/eth1":
|
||||
case "configuration/dhcp4/eth1":
|
||||
suite.Assert().Equal("eth1", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().DHCP4.RouteMetric)
|
||||
case "dhcp4/eth3":
|
||||
case "configuration/dhcp4/eth3":
|
||||
suite.Assert().Equal("eth3", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(256, r.TypedSpec().DHCP4.RouteMetric)
|
||||
case "dhcp4/eth4.25":
|
||||
case "configuration/dhcp4/eth4.25":
|
||||
suite.Assert().Equal("eth4.25", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().DHCP4.RouteMetric)
|
||||
}
|
||||
@ -306,9 +310,11 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP4() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperators([]string{
|
||||
"dhcp4/eth0",
|
||||
"dhcp4/eth2",
|
||||
"dhcp4/eth4.26",
|
||||
"configuration/dhcp4/eth0",
|
||||
"default/dhcp4/eth0",
|
||||
"configuration/dhcp4/eth2",
|
||||
"default/dhcp4/eth2",
|
||||
"configuration/dhcp4/eth4.26",
|
||||
})
|
||||
}))
|
||||
}
|
||||
@ -365,17 +371,17 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP6() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp6/eth2",
|
||||
"dhcp6/eth3",
|
||||
"configuration/dhcp6/eth2",
|
||||
"configuration/dhcp6/eth3",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
suite.Assert().Equal(network.OperatorDHCP6, r.TypedSpec().Operator)
|
||||
suite.Assert().True(r.TypedSpec().RequireUp)
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "dhcp6/eth2":
|
||||
case "configuration/dhcp6/eth2":
|
||||
suite.Assert().Equal("eth2", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(netctrl.DefaultRouteMetric, r.TypedSpec().DHCP6.RouteMetric)
|
||||
case "dhcp6/eth3":
|
||||
case "configuration/dhcp6/eth3":
|
||||
suite.Assert().Equal("eth3", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(512, r.TypedSpec().DHCP6.RouteMetric)
|
||||
}
|
||||
@ -387,7 +393,7 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationDHCP6() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperators([]string{
|
||||
"dhcp6/eth1",
|
||||
"configuration/dhcp6/eth1",
|
||||
})
|
||||
}))
|
||||
}
|
||||
@ -448,21 +454,21 @@ func (suite *OperatorConfigSuite) TestMachineConfigurationVIP() {
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"vip/eth1",
|
||||
"vip/eth2",
|
||||
"vip/eth3.26",
|
||||
"configuration/vip/eth1",
|
||||
"configuration/vip/eth2",
|
||||
"configuration/vip/eth3.26",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
suite.Assert().Equal(network.OperatorVIP, r.TypedSpec().Operator)
|
||||
suite.Assert().True(r.TypedSpec().RequireUp)
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "vip/eth1":
|
||||
case "configuration/vip/eth1":
|
||||
suite.Assert().Equal("eth1", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(netaddr.MustParseIP("2.3.4.5"), r.TypedSpec().VIP.IP)
|
||||
case "vip/eth2":
|
||||
case "configuration/vip/eth2":
|
||||
suite.Assert().Equal("eth2", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(netaddr.MustParseIP("fd7a:115c:a1e0:ab12:4843:cd96:6277:2302"), r.TypedSpec().VIP.IP)
|
||||
case "vip/eth3.26":
|
||||
case "configuration/vip/eth3.26":
|
||||
suite.Assert().Equal("eth3.26", r.TypedSpec().LinkName)
|
||||
suite.Assert().EqualValues(netaddr.MustParseIP("5.5.4.4"), r.TypedSpec().VIP.IP)
|
||||
}
|
||||
@ -490,7 +496,7 @@ func (suite *OperatorConfigSuite) TearDownTest() {
|
||||
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkStatus(network.NamespaceName, "bar")))
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewLinkStatus(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestOperatorConfigSuite(t *testing.T) {
|
||||
|
140
internal/app/machined/pkg/controllers/network/operator_merge.go
Normal file
140
internal/app/machined/pkg/controllers/network/operator_merge.go
Normal file
@ -0,0 +1,140 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package network provides controllers which manage network resources.
|
||||
//
|
||||
//nolint:dupl
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
// OperatorMergeController merges network.OperatorSpec in network.ConfigNamespace and produces final network.OperatorSpec in network.Namespace.
|
||||
type OperatorMergeController struct{}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
func (ctrl *OperatorMergeController) Name() string {
|
||||
return "network.OperatorMergeController"
|
||||
}
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *OperatorMergeController) Inputs() []controller.Input {
|
||||
return []controller.Input{
|
||||
{
|
||||
Namespace: network.ConfigNamespaceName,
|
||||
Type: network.OperatorSpecType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
{
|
||||
Namespace: network.NamespaceName,
|
||||
Type: network.OperatorSpecType,
|
||||
Kind: controller.InputDestroyReady,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *OperatorMergeController) Outputs() []controller.Output {
|
||||
return []controller.Output{
|
||||
{
|
||||
Type: network.OperatorSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *OperatorMergeController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-r.EventCh():
|
||||
}
|
||||
|
||||
// list source network configuration resources
|
||||
list, err := r.List(ctx, resource.NewMetadata(network.ConfigNamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing source network operators: %w", err)
|
||||
}
|
||||
|
||||
// operator is allowed as long as it's not duplicate, for duplicate higher layer takes precedence
|
||||
operators := map[string]*network.OperatorSpec{}
|
||||
|
||||
for _, res := range list.Items {
|
||||
operator := res.(*network.OperatorSpec) //nolint:errcheck,forcetypeassert
|
||||
id := network.OperatorID(operator.TypedSpec().Operator, operator.TypedSpec().LinkName)
|
||||
|
||||
existing, ok := operators[id]
|
||||
if ok && existing.TypedSpec().ConfigLayer > operator.TypedSpec().ConfigLayer {
|
||||
// skip this operator, as existing one is higher layer
|
||||
continue
|
||||
}
|
||||
|
||||
operators[id] = operator
|
||||
}
|
||||
|
||||
conflictsDetected := 0
|
||||
|
||||
for id, operator := range operators {
|
||||
operator := operator
|
||||
|
||||
if err = r.Modify(ctx, network.NewOperatorSpec(network.NamespaceName, id), func(res resource.Resource) error {
|
||||
op := res.(*network.OperatorSpec) //nolint:errcheck,forcetypeassert
|
||||
|
||||
*op.TypedSpec() = *operator.TypedSpec()
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
if state.IsPhaseConflictError(err) {
|
||||
// phase conflict, resource is being torn down, skip updating it and trigger reconcile
|
||||
// later by failing the loop after all processing is done
|
||||
conflictsDetected++
|
||||
|
||||
delete(operators, id)
|
||||
} else {
|
||||
return fmt.Errorf("error updating resource: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// list operators for cleanup
|
||||
list, err = r.List(ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing resources: %w", err)
|
||||
}
|
||||
|
||||
for _, res := range list.Items {
|
||||
if _, ok := operators[res.Metadata().ID()]; !ok {
|
||||
var okToDestroy bool
|
||||
|
||||
okToDestroy, err = r.Teardown(ctx, res.Metadata())
|
||||
if err != nil {
|
||||
return fmt.Errorf("error cleaning up operators: %w", err)
|
||||
}
|
||||
|
||||
if okToDestroy {
|
||||
if err = r.Destroy(ctx, res.Metadata()); err != nil {
|
||||
return fmt.Errorf("error cleaning up operators: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if conflictsDetected > 0 {
|
||||
return fmt.Errorf("%d conflict(s) detected", conflictsDetected)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//nolint:dupl
|
||||
package network_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller/runtime"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
netctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/talos-systems/talos/pkg/logging"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
type OperatorMergeSuite struct {
|
||||
suite.Suite
|
||||
|
||||
state state.State
|
||||
|
||||
runtime *runtime.Runtime
|
||||
wg sync.WaitGroup
|
||||
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (suite *OperatorMergeSuite) SetupTest() {
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
|
||||
suite.state = state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
|
||||
var err error
|
||||
|
||||
suite.runtime, err = runtime.NewRuntime(suite.state, logging.Wrap(log.Writer()))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.OperatorMergeController{}))
|
||||
|
||||
suite.startRuntime()
|
||||
}
|
||||
|
||||
func (suite *OperatorMergeSuite) startRuntime() {
|
||||
suite.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer suite.wg.Done()
|
||||
|
||||
suite.Assert().NoError(suite.runtime.Run(suite.ctx))
|
||||
}()
|
||||
}
|
||||
|
||||
func (suite *OperatorMergeSuite) assertOperators(requiredIDs []string, check func(*network.OperatorSpec) error) error {
|
||||
missingIDs := make(map[string]struct{}, len(requiredIDs))
|
||||
|
||||
for _, id := range requiredIDs {
|
||||
missingIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
_, required := missingIDs[res.Metadata().ID()]
|
||||
if !required {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(missingIDs, res.Metadata().ID())
|
||||
|
||||
if err = check(res.(*network.OperatorSpec)); err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingIDs) > 0 {
|
||||
return retry.ExpectedError(fmt.Errorf("some resources are missing: %q", missingIDs))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *OperatorMergeSuite) assertNoOperator(id string) error {
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
if res.Metadata().ID() == id {
|
||||
return retry.ExpectedError(fmt.Errorf("operator %q is still there", id))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *OperatorMergeSuite) TestMerge() {
|
||||
dhcp1 := network.NewOperatorSpec(network.ConfigNamespaceName, "default/dhcp4/eth0")
|
||||
*dhcp1.TypedSpec() = network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: "eth0",
|
||||
ConfigLayer: network.ConfigDefault,
|
||||
}
|
||||
|
||||
dhcp2 := network.NewOperatorSpec(network.ConfigNamespaceName, "configuration/dhcp4/eth0")
|
||||
*dhcp2.TypedSpec() = network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: "eth0",
|
||||
RequireUp: true,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
|
||||
dhcp6 := network.NewOperatorSpec(network.ConfigNamespaceName, "configuration/dhcp6/eth0")
|
||||
*dhcp6.TypedSpec() = network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP6,
|
||||
LinkName: "eth0",
|
||||
RequireUp: true,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
|
||||
for _, res := range []resource.Resource{dhcp1, dhcp2, dhcp6} {
|
||||
suite.Require().NoError(suite.state.Create(suite.ctx, res), "%v", res.Spec())
|
||||
}
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp4/eth0",
|
||||
"dhcp6/eth0",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
switch r.Metadata().ID() {
|
||||
case "dhcp4/eth0":
|
||||
suite.Assert().Equal(*dhcp2.TypedSpec(), *r.TypedSpec())
|
||||
case "dhcp6/eth0":
|
||||
suite.Assert().Equal(*dhcp6.TypedSpec(), *r.TypedSpec())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
|
||||
suite.Require().NoError(suite.state.Destroy(suite.ctx, dhcp6.Metadata()))
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp4/eth0",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoOperator("dhcp6/eth0")
|
||||
}))
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (suite *OperatorMergeSuite) TestMergeFlapping() {
|
||||
// simulate two conflicting operator definitions which are getting removed/added constantly
|
||||
dhcp := network.NewOperatorSpec(network.ConfigNamespaceName, "default/dhcp4/eth0")
|
||||
*dhcp.TypedSpec() = network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: "eth0",
|
||||
ConfigLayer: network.ConfigDefault,
|
||||
}
|
||||
|
||||
override := network.NewOperatorSpec(network.ConfigNamespaceName, "configuration/dhcp4/eth0")
|
||||
*override.TypedSpec() = network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: "eth0",
|
||||
RequireUp: true,
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
|
||||
resources := []resource.Resource{dhcp, override}
|
||||
|
||||
flipflop := func(idx int) func() error {
|
||||
return func() error {
|
||||
for i := 0; i < 500; i++ {
|
||||
if err := suite.state.Create(suite.ctx, resources[idx]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := suite.state.Destroy(suite.ctx, resources[idx].Metadata()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
return suite.state.Create(suite.ctx, resources[idx])
|
||||
}
|
||||
}
|
||||
|
||||
var eg errgroup.Group
|
||||
|
||||
eg.Go(flipflop(0))
|
||||
eg.Go(flipflop(1))
|
||||
eg.Go(func() error {
|
||||
// add/remove finalizer to the merged resource
|
||||
for i := 0; i < 1000; i++ {
|
||||
if err := suite.state.AddFinalizer(suite.ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "dhcp4/eth0", resource.VersionUndefined), "foo"); err != nil {
|
||||
if !state.IsNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
} else {
|
||||
suite.T().Log("finalizer added")
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
if err := suite.state.RemoveFinalizer(suite.ctx, resource.NewMetadata(network.NamespaceName, network.OperatorSpecType, "dhcp4/eth0", resource.VersionUndefined), "foo"); err != nil {
|
||||
if err != nil && !state.IsNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
suite.Require().NoError(eg.Wait())
|
||||
|
||||
suite.Assert().NoError(retry.Constant(15*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertOperators([]string{
|
||||
"dhcp4/eth0",
|
||||
}, func(r *network.OperatorSpec) error {
|
||||
if r.Metadata().Phase() != resource.PhaseRunning {
|
||||
return retry.ExpectedErrorf("resource phase is %s", r.Metadata().Phase())
|
||||
}
|
||||
|
||||
if *override.TypedSpec() != *r.TypedSpec() {
|
||||
// using retry here, as it might not be reconciled immediately
|
||||
return retry.ExpectedError(fmt.Errorf("not equal yet"))
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *OperatorMergeSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
suite.ctxCancel()
|
||||
|
||||
suite.wg.Wait()
|
||||
|
||||
// trigger updates in resources to stop watch loops
|
||||
suite.Assert().NoError(suite.state.Create(context.Background(), network.NewOperatorSpec(network.ConfigNamespaceName, "bar")))
|
||||
}
|
||||
|
||||
func TestOperatorMergeSuite(t *testing.T) {
|
||||
suite.Run(t, new(OperatorMergeSuite))
|
||||
}
|
@ -5,19 +5,29 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"inet.af/netaddr"
|
||||
|
||||
v1alpha1runtime "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
platformerrors "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/v1alpha1"
|
||||
)
|
||||
|
||||
// Virtual link name for external IPs.
|
||||
@ -26,6 +36,7 @@ const externalLink = "external"
|
||||
// PlatformConfigController manages updates hostnames and addressstatuses based on platform information.
|
||||
type PlatformConfigController struct {
|
||||
V1alpha1Platform v1alpha1runtime.Platform
|
||||
StatePath string
|
||||
}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
@ -35,27 +46,62 @@ func (ctrl *PlatformConfigController) Name() string {
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *PlatformConfigController) Inputs() []controller.Input {
|
||||
return nil
|
||||
return []controller.Input{
|
||||
{
|
||||
Namespace: v1alpha1.NamespaceName,
|
||||
Type: runtimeres.MountStatusType,
|
||||
ID: pointer.ToString(constants.StatePartitionLabel),
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *PlatformConfigController) Outputs() []controller.Output {
|
||||
return []controller.Output{
|
||||
{
|
||||
Type: network.AddressSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.LinkSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.RouteSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.HostnameSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.ResolverSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.TimeServerSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.AddressStatusType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
{
|
||||
Type: network.OperatorSpecType,
|
||||
Kind: controller.OutputShared,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
//nolint:gocyclo,cyclop
|
||||
func (ctrl *PlatformConfigController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
if ctrl.StatePath == "" {
|
||||
ctrl.StatePath = constants.StateMountPoint
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
@ -67,87 +113,421 @@ func (ctrl *PlatformConfigController) Run(ctx context.Context, r controller.Runt
|
||||
return nil
|
||||
}
|
||||
|
||||
// platform is fetched only once (but controller might fail and restart if fetching platform fails)
|
||||
hostname, err := ctrl.V1alpha1Platform.Hostname(ctx)
|
||||
if err != nil {
|
||||
if !errors.Is(err, platformerrors.ErrNoHostname) {
|
||||
return fmt.Errorf("error getting hostname: %w", err)
|
||||
platformCtx, platformCtxCancel := context.WithCancel(ctx)
|
||||
defer platformCtxCancel()
|
||||
|
||||
platformCh := make(chan *v1alpha1runtime.PlatformNetworkConfig, 1)
|
||||
|
||||
var platformWg sync.WaitGroup
|
||||
|
||||
platformWg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer platformWg.Done()
|
||||
|
||||
ctrl.runWithRestarts(platformCtx, logger, func() error {
|
||||
return ctrl.V1alpha1Platform.NetworkConfiguration(platformCtx, platformCh)
|
||||
})
|
||||
}()
|
||||
|
||||
defer platformWg.Wait()
|
||||
|
||||
r.QueueReconcile()
|
||||
|
||||
var cachedNetworkConfig, networkConfig *v1alpha1runtime.PlatformNetworkConfig
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-r.EventCh():
|
||||
case networkConfig = <-platformCh:
|
||||
}
|
||||
}
|
||||
|
||||
if len(hostname) > 0 {
|
||||
id := network.LayeredID(network.ConfigPlatform, network.HostnameID)
|
||||
var stateMounted bool
|
||||
|
||||
if err = r.Modify(
|
||||
ctx,
|
||||
network.NewHostnameSpec(network.ConfigNamespaceName, id),
|
||||
func(r resource.Resource) error {
|
||||
r.(*network.HostnameSpec).TypedSpec().ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return r.(*network.HostnameSpec).TypedSpec().ParseFQDN(string(hostname))
|
||||
},
|
||||
); err != nil {
|
||||
return fmt.Errorf("error modifying hostname resource: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
externalIPs, err := ctrl.V1alpha1Platform.ExternalIPs(ctx)
|
||||
if err != nil {
|
||||
if !errors.Is(err, platformerrors.ErrNoExternalIPs) {
|
||||
return fmt.Errorf("error getting external IPs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
touchedIDs := make(map[resource.ID]struct{})
|
||||
|
||||
for _, addr := range externalIPs {
|
||||
addr := addr
|
||||
|
||||
ipAddr, _ := netaddr.FromStdIP(addr)
|
||||
ipPrefix := netaddr.IPPrefixFrom(ipAddr, ipAddr.BitLen())
|
||||
id := network.AddressID(externalLink, ipPrefix)
|
||||
|
||||
if err = r.Modify(ctx, network.NewAddressStatus(network.NamespaceName, id), func(r resource.Resource) error {
|
||||
status := r.(*network.AddressStatus).TypedSpec()
|
||||
|
||||
status.Address = ipPrefix
|
||||
status.LinkName = externalLink
|
||||
|
||||
if ipAddr.Is4() {
|
||||
status.Family = nethelpers.FamilyInet4
|
||||
if _, err := r.Get(ctx, resource.NewMetadata(v1alpha1.NamespaceName, runtimeres.MountStatusType, constants.StatePartitionLabel, resource.VersionUndefined)); err == nil {
|
||||
stateMounted = true
|
||||
} else {
|
||||
if state.IsNotFoundError(err) {
|
||||
// in container mode STATE is always mounted
|
||||
if ctrl.V1alpha1Platform.Mode() == v1alpha1runtime.ModeContainer {
|
||||
stateMounted = true
|
||||
}
|
||||
} else {
|
||||
status.Family = nethelpers.FamilyInet6
|
||||
return fmt.Errorf("error reading mount status: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if stateMounted && cachedNetworkConfig == nil {
|
||||
var err error
|
||||
|
||||
cachedNetworkConfig, err = ctrl.loadConfig(filepath.Join(ctrl.StatePath, constants.PlatformNetworkConfigFilename))
|
||||
if err != nil {
|
||||
logger.Warn("ignored failure loading cached platform network config", zap.Error(err))
|
||||
} else if cachedNetworkConfig != nil {
|
||||
logger.Debug("loaded cached platform network config")
|
||||
}
|
||||
}
|
||||
|
||||
if stateMounted && networkConfig != nil {
|
||||
if err := ctrl.storeConfig(filepath.Join(ctrl.StatePath, constants.PlatformNetworkConfigFilename), networkConfig); err != nil {
|
||||
return fmt.Errorf("error saving config: %w", err)
|
||||
}
|
||||
|
||||
status.Scope = nethelpers.ScopeGlobal
|
||||
logger.Debug("stored cached platform network config")
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error modifying resource: %w", err)
|
||||
cachedNetworkConfig = networkConfig
|
||||
}
|
||||
|
||||
touchedIDs[id] = struct{}{}
|
||||
switch {
|
||||
// prefer live network config over cached config always
|
||||
case networkConfig != nil:
|
||||
if err := ctrl.apply(ctx, r, networkConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
// cached network is only used as last resort
|
||||
case cachedNetworkConfig != nil:
|
||||
if err := ctrl.apply(ctx, r, cachedNetworkConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// list resources for cleanup
|
||||
list, err := r.List(ctx, resource.NewMetadata(network.NamespaceName, network.AddressStatusType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing resources: %w", err)
|
||||
}
|
||||
//nolint:dupl,gocyclo
|
||||
func (ctrl *PlatformConfigController) apply(ctx context.Context, r controller.Runtime, networkConfig *v1alpha1runtime.PlatformNetworkConfig) error {
|
||||
// handle all network specs in a loop as all specs can be handled in a similar way
|
||||
for _, specType := range []struct {
|
||||
length int
|
||||
getter func(i int) interface{}
|
||||
idBuilder func(spec interface{}) resource.ID
|
||||
resourceBuilder func(id string) resource.Resource
|
||||
resourceModifier func(newSpec interface{}) func(r resource.Resource) error
|
||||
}{
|
||||
// AddressSpec
|
||||
{
|
||||
length: len(networkConfig.Addresses),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.Addresses[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
addressSpec := spec.(network.AddressSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
|
||||
for _, res := range list.Items {
|
||||
if res.Metadata().Owner() != ctrl.Name() {
|
||||
return network.LayeredID(network.ConfigPlatform, network.AddressID(addressSpec.LinkName, addressSpec.Address))
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewAddressSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.AddressSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.AddressSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// LinkSpec
|
||||
{
|
||||
length: len(networkConfig.Links),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.Links[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
linkSpec := spec.(network.LinkSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
|
||||
return network.LayeredID(network.ConfigPlatform, network.LinkID(linkSpec.Name))
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewLinkSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.LinkSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.LinkSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// RouteSpec
|
||||
{
|
||||
length: len(networkConfig.Routes),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.Routes[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
routeSpec := spec.(network.RouteSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
|
||||
return network.LayeredID(network.ConfigPlatform, network.RouteID(routeSpec.Table, routeSpec.Family, routeSpec.Destination, routeSpec.Gateway, routeSpec.Priority))
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewRouteSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.RouteSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.RouteSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// HostnameSpec
|
||||
{
|
||||
length: len(networkConfig.Hostnames),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.Hostnames[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
return network.LayeredID(network.ConfigPlatform, network.HostnameID)
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewHostnameSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.HostnameSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.HostnameSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// ResolverSpec
|
||||
{
|
||||
length: len(networkConfig.Resolvers),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.Resolvers[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
return network.LayeredID(network.ConfigPlatform, network.ResolverID)
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewResolverSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.ResolverSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.ResolverSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// TimeServerSpec
|
||||
{
|
||||
length: len(networkConfig.TimeServers),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.TimeServers[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
return network.LayeredID(network.ConfigPlatform, network.TimeServerID)
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewTimeServerSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.TimeServerSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.TimeServerSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// OperatorSpec
|
||||
{
|
||||
length: len(networkConfig.Operators),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.Operators[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
operatorSpec := spec.(network.OperatorSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
|
||||
return network.LayeredID(network.ConfigPlatform, network.OperatorID(operatorSpec.Operator, operatorSpec.LinkName))
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewOperatorSpec(network.ConfigNamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
spec := r.(*network.OperatorSpec).TypedSpec()
|
||||
|
||||
*spec = newSpec.(network.OperatorSpecSpec) //nolint:errcheck,forcetypeassert
|
||||
spec.ConfigLayer = network.ConfigPlatform
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
// ExternalIPs
|
||||
{
|
||||
length: len(networkConfig.ExternalIPs),
|
||||
getter: func(i int) interface{} {
|
||||
return networkConfig.ExternalIPs[i]
|
||||
},
|
||||
idBuilder: func(spec interface{}) resource.ID {
|
||||
ipAddr := spec.(netaddr.IP) //nolint:errcheck,forcetypeassert
|
||||
ipPrefix := netaddr.IPPrefixFrom(ipAddr, ipAddr.BitLen())
|
||||
|
||||
return network.AddressID(externalLink, ipPrefix)
|
||||
},
|
||||
resourceBuilder: func(id string) resource.Resource {
|
||||
return network.NewAddressStatus(network.NamespaceName, id)
|
||||
},
|
||||
resourceModifier: func(newSpec interface{}) func(r resource.Resource) error {
|
||||
return func(r resource.Resource) error {
|
||||
ipAddr := newSpec.(netaddr.IP) //nolint:errcheck,forcetypeassert
|
||||
ipPrefix := netaddr.IPPrefixFrom(ipAddr, ipAddr.BitLen())
|
||||
|
||||
status := r.(*network.AddressStatus).TypedSpec()
|
||||
|
||||
status.Address = ipPrefix
|
||||
status.LinkName = externalLink
|
||||
|
||||
if ipAddr.Is4() {
|
||||
status.Family = nethelpers.FamilyInet4
|
||||
} else {
|
||||
status.Family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
status.Scope = nethelpers.ScopeGlobal
|
||||
|
||||
return nil
|
||||
}
|
||||
},
|
||||
},
|
||||
} {
|
||||
if specType.length == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := touchedIDs[res.Metadata().ID()]; ok {
|
||||
continue
|
||||
touchedIDs := make(map[resource.ID]struct{}, specType.length)
|
||||
|
||||
resourceEmpty := specType.resourceBuilder("")
|
||||
resourceNamespace := resourceEmpty.Metadata().Namespace()
|
||||
resourceType := resourceEmpty.Metadata().Type()
|
||||
|
||||
for i := 0; i < specType.length; i++ {
|
||||
spec := specType.getter(i)
|
||||
id := specType.idBuilder(spec)
|
||||
|
||||
if err := r.Modify(ctx, specType.resourceBuilder(id), specType.resourceModifier(spec)); err != nil {
|
||||
return fmt.Errorf("error modifying resource %s: %w", resourceType, err)
|
||||
}
|
||||
|
||||
touchedIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
if err = r.Destroy(ctx, res.Metadata()); err != nil {
|
||||
return fmt.Errorf("error deleting address status %s: %w", res, err)
|
||||
list, err := r.List(ctx, resource.NewMetadata(resourceNamespace, resourceType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing resources: %w", err)
|
||||
}
|
||||
|
||||
for _, res := range list.Items {
|
||||
if res.Metadata().Owner() != ctrl.Name() {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := touchedIDs[res.Metadata().ID()]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = r.Destroy(ctx, res.Metadata()); err != nil {
|
||||
return fmt.Errorf("error deleting %s: %w", res, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *PlatformConfigController) runWithRestarts(ctx context.Context, logger *zap.Logger, f func() error) {
|
||||
backoff := backoff.NewExponentialBackOff()
|
||||
|
||||
// disable number of retries limit
|
||||
backoff.MaxElapsedTime = 0
|
||||
|
||||
for ctx.Err() == nil {
|
||||
var err error
|
||||
|
||||
if err = ctrl.runWithPanicHandler(logger, f); err == nil {
|
||||
// operator finished without an error
|
||||
return
|
||||
}
|
||||
|
||||
interval := backoff.NextBackOff()
|
||||
|
||||
logger.Error("restarting platform network config", zap.Duration("interval", interval), zap.Error(err))
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.After(interval):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *PlatformConfigController) runWithPanicHandler(logger *zap.Logger, f func() error) (err error) {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
err = fmt.Errorf("panic: %v", p)
|
||||
|
||||
logger.Error("platform panicked", zap.Stack("stack"), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
err = f()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctrl *PlatformConfigController) loadConfig(path string) (*v1alpha1runtime.PlatformNetworkConfig, error) {
|
||||
marshaled, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var networkConfig v1alpha1runtime.PlatformNetworkConfig
|
||||
|
||||
if err = yaml.Unmarshal(marshaled, &networkConfig); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling network config: %w", err)
|
||||
}
|
||||
|
||||
return &networkConfig, nil
|
||||
}
|
||||
|
||||
func (ctrl *PlatformConfigController) storeConfig(path string, networkConfig *v1alpha1runtime.PlatformNetworkConfig) error {
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling network config: %w", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
existing, err := os.ReadFile(path)
|
||||
if err == nil && bytes.Equal(marshaled, existing) {
|
||||
// existing contents are identical, skip writing to avoid no-op writes
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return os.WriteFile(path, marshaled, 0o400)
|
||||
}
|
||||
|
@ -9,7 +9,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -22,12 +23,16 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/talos-systems/go-retry/retry"
|
||||
"inet.af/netaddr"
|
||||
|
||||
netctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
v1alpha1runtime "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/pkg/logging"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
runtimeres "github.com/talos-systems/talos/pkg/machinery/resources/runtime"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/v1alpha1"
|
||||
)
|
||||
|
||||
type PlatformConfigSuite struct {
|
||||
@ -35,6 +40,8 @@ type PlatformConfigSuite struct {
|
||||
|
||||
state state.State
|
||||
|
||||
statePath string
|
||||
|
||||
runtime *runtime.Runtime
|
||||
wg sync.WaitGroup
|
||||
|
||||
@ -51,6 +58,8 @@ func (suite *PlatformConfigSuite) SetupTest() {
|
||||
|
||||
suite.runtime, err = runtime.NewRuntime(suite.state, logging.Wrap(log.Writer()))
|
||||
suite.Require().NoError(err)
|
||||
|
||||
suite.statePath = suite.T().TempDir()
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) startRuntime() {
|
||||
@ -63,29 +72,26 @@ func (suite *PlatformConfigSuite) startRuntime() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) assertHostnames(requiredIDs []string, check func(*network.HostnameSpec) error) error {
|
||||
func (suite *PlatformConfigSuite) assertResources(resourceNamespace resource.Namespace, resourceType resource.Type, requiredIDs []string, check func(resource.Resource) error) error {
|
||||
missingIDs := make(map[string]struct{}, len(requiredIDs))
|
||||
|
||||
for _, id := range requiredIDs {
|
||||
missingIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, network.HostnameSpecType, "", resource.VersionUndefined))
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(resourceNamespace, resourceType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
_, required := missingIDs[res.Metadata().ID()]
|
||||
if !required {
|
||||
continue
|
||||
if _, ok := missingIDs[res.Metadata().ID()]; ok {
|
||||
if err = check(res); err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
}
|
||||
|
||||
delete(missingIDs, res.Metadata().ID())
|
||||
|
||||
if err = check(res.(*network.HostnameSpec)); err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingIDs) > 0 {
|
||||
@ -95,8 +101,8 @@ func (suite *PlatformConfigSuite) assertHostnames(requiredIDs []string, check fu
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) assertNoHostname(id string) error {
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, network.HostnameSpecType, "", resource.VersionUndefined))
|
||||
func (suite *PlatformConfigSuite) assertNoResource(resourceType resource.Type, id string) error {
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.ConfigNamespaceName, resourceType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -110,38 +116,6 @@ func (suite *PlatformConfigSuite) assertNoHostname(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) assertAddresses(requiredIDs []string, check func(*network.AddressStatus) error) error {
|
||||
missingIDs := make(map[string]struct{}, len(requiredIDs))
|
||||
|
||||
for _, id := range requiredIDs {
|
||||
missingIDs[id] = struct{}{}
|
||||
}
|
||||
|
||||
resources, err := suite.state.List(suite.ctx, resource.NewMetadata(network.NamespaceName, network.AddressStatusType, "", resource.VersionUndefined))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, res := range resources.Items {
|
||||
_, required := missingIDs[res.Metadata().ID()]
|
||||
if !required {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(missingIDs, res.Metadata().ID())
|
||||
|
||||
if err = check(res.(*network.AddressStatus)); err != nil {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingIDs) > 0 {
|
||||
return retry.ExpectedError(fmt.Errorf("some resources are missing: %q", missingIDs))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestNoPlatform() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{}))
|
||||
|
||||
@ -149,46 +123,213 @@ func (suite *PlatformConfigSuite) TestNoPlatform() {
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertNoHostname("platform/hostname")
|
||||
return suite.assertNoResource(network.HostnameSpecType, "platform/hostname")
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMock() {
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockHostname() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{hostname: []byte("talos-e2e-897b4e49-gcp-controlplane-jvcnl.c.talos-testbed.internal")},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertHostnames([]string{
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.HostnameSpecType, []string{
|
||||
"platform/hostname",
|
||||
}, func(r *network.HostnameSpec) error {
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", r.TypedSpec().Hostname)
|
||||
suite.Assert().Equal("c.talos-testbed.internal", r.TypedSpec().Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, r.TypedSpec().ConfigLayer)
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.HostnameSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", spec.Hostname)
|
||||
suite.Assert().Equal("c.talos-testbed.internal", spec.Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockNoDomain() {
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockHostnameNoDomain() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{hostname: []byte("talos-e2e-897b4e49-gcp-controlplane-jvcnl")},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertHostnames([]string{
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.HostnameSpecType, []string{
|
||||
"platform/hostname",
|
||||
}, func(r *network.HostnameSpec) error {
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", r.TypedSpec().Hostname)
|
||||
suite.Assert().Equal("", r.TypedSpec().Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, r.TypedSpec().ConfigLayer)
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.HostnameSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", spec.Hostname)
|
||||
suite.Assert().Equal("", spec.Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockAddresses() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
addresses: []netaddr.IPPrefix{netaddr.MustParseIPPrefix("192.168.1.24/24"), netaddr.MustParseIPPrefix("2001:fd::3/64")},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.AddressSpecType, []string{
|
||||
"platform/eth0/192.168.1.24/24",
|
||||
"platform/eth0/2001:fd::3/64",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.AddressSpec).TypedSpec()
|
||||
|
||||
switch r.Metadata().ID() {
|
||||
case "platform/eth0/192.168.1.24/24":
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, spec.Family)
|
||||
suite.Assert().Equal("192.168.1.24/24", spec.Address.String())
|
||||
case "platform/eth0/2001:fd::3/64":
|
||||
suite.Assert().Equal(nethelpers.FamilyInet6, spec.Family)
|
||||
suite.Assert().Equal("2001:fd::3/64", spec.Address.String())
|
||||
}
|
||||
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockLinks() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
linksUp: []string{"eth0", "eth1"},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.LinkSpecType, []string{
|
||||
"platform/eth0",
|
||||
"platform/eth1",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.LinkSpec).TypedSpec()
|
||||
|
||||
suite.Assert().True(spec.Up)
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockRoutes() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
defaultRoutes: []netaddr.IP{netaddr.MustParseIP("10.0.0.1")},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.RouteSpecType, []string{
|
||||
"platform/inet4/10.0.0.1//1024",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.RouteSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("10.0.0.1", spec.Gateway.String())
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockOperators() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
dhcp4Links: []string{"eth1", "eth2"},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.OperatorSpecType, []string{
|
||||
"platform/dhcp4/eth1",
|
||||
"platform/dhcp4/eth2",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.OperatorSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal(network.OperatorDHCP4, spec.Operator)
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockResolvers() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
resolvers: []netaddr.IP{netaddr.MustParseIP("1.1.1.1")},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.ResolverSpecType, []string{
|
||||
"platform/resolvers",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.ResolverSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("[1.1.1.1]", fmt.Sprintf("%s", spec.DNSServers))
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockTimeServers() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
timeServers: []string{"pool.ntp.org"},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.TimeServerSpecType, []string{
|
||||
"platform/timeservers",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.TimeServerSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("[pool.ntp.org]", fmt.Sprintf("%s", spec.NTPServers))
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -197,24 +338,27 @@ func (suite *PlatformConfigSuite) TestPlatformMockNoDomain() {
|
||||
|
||||
func (suite *PlatformConfigSuite) TestPlatformMockExternalIPs() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{ipAddresses: []net.IP{net.ParseIP("10.3.4.5"), net.ParseIP("2001:470:6d:30e:96f4:4219:5733:b860")}},
|
||||
V1alpha1Platform: &platformMock{externalIPs: []netaddr.IP{netaddr.MustParseIP("10.3.4.5"), netaddr.MustParseIP("2001:470:6d:30e:96f4:4219:5733:b860")}},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertAddresses([]string{
|
||||
return suite.assertResources(network.NamespaceName, network.AddressStatusType, []string{
|
||||
"external/10.3.4.5/32",
|
||||
"external/2001:470:6d:30e:96f4:4219:5733:b860/128",
|
||||
}, func(r *network.AddressStatus) error {
|
||||
suite.Assert().Equal("external", r.TypedSpec().LinkName)
|
||||
suite.Assert().Equal(nethelpers.ScopeGlobal, r.TypedSpec().Scope)
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.AddressStatus).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("external", spec.LinkName)
|
||||
suite.Assert().Equal(nethelpers.ScopeGlobal, spec.Scope)
|
||||
|
||||
if r.Metadata().ID() == "external/10.3.4.5/32" {
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, r.TypedSpec().Family)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, spec.Family)
|
||||
} else {
|
||||
suite.Assert().Equal(nethelpers.FamilyInet6, r.TypedSpec().Family)
|
||||
suite.Assert().Equal(nethelpers.FamilyInet6, spec.Family)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -222,6 +366,94 @@ func (suite *PlatformConfigSuite) TestPlatformMockExternalIPs() {
|
||||
}))
|
||||
}
|
||||
|
||||
const sampleStoredConfig = "addresses: []\nlinks: []\nroutes: []\nhostnames:\n - hostname: talos-e2e-897b4e49-gcp-controlplane-jvcnl\n domainname: \"\"\n layer: default\nresolvers: []\ntimeServers: []\noperators: []\nexternalIPs:\n - 10.3.4.5\n - 2001:470:6d:30e:96f4:4219:5733:b860\n" //nolint:lll
|
||||
|
||||
func (suite *PlatformConfigSuite) TestStoreConfig() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
hostname: []byte("talos-e2e-897b4e49-gcp-controlplane-jvcnl"),
|
||||
externalIPs: []netaddr.IP{netaddr.MustParseIP("10.3.4.5"), netaddr.MustParseIP("2001:470:6d:30e:96f4:4219:5733:b860")},
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
stateMount := runtimeres.NewMountStatus(v1alpha1.NamespaceName, constants.StatePartitionLabel)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(suite.ctx, stateMount))
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
contents, err := os.ReadFile(filepath.Join(suite.statePath, constants.PlatformNetworkConfigFilename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return retry.ExpectedError(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
suite.Assert().Equal(sampleStoredConfig, string(contents))
|
||||
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TestLoadConfig() {
|
||||
suite.Require().NoError(suite.runtime.RegisterController(&netctrl.PlatformConfigController{
|
||||
V1alpha1Platform: &platformMock{
|
||||
noData: true,
|
||||
},
|
||||
StatePath: suite.statePath,
|
||||
}))
|
||||
|
||||
suite.startRuntime()
|
||||
|
||||
suite.Require().NoError(os.WriteFile(filepath.Join(suite.statePath, constants.PlatformNetworkConfigFilename), []byte(sampleStoredConfig), 0o400))
|
||||
|
||||
stateMount := runtimeres.NewMountStatus(v1alpha1.NamespaceName, constants.StatePartitionLabel)
|
||||
|
||||
suite.Assert().NoError(suite.state.Create(suite.ctx, stateMount))
|
||||
|
||||
// controller should pick up cached network configuration
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.NamespaceName, network.AddressStatusType, []string{
|
||||
"external/10.3.4.5/32",
|
||||
"external/2001:470:6d:30e:96f4:4219:5733:b860/128",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.AddressStatus).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("external", spec.LinkName)
|
||||
suite.Assert().Equal(nethelpers.ScopeGlobal, spec.Scope)
|
||||
|
||||
if r.Metadata().ID() == "external/10.3.4.5/32" {
|
||||
suite.Assert().Equal(nethelpers.FamilyInet4, spec.Family)
|
||||
} else {
|
||||
suite.Assert().Equal(nethelpers.FamilyInet6, spec.Family)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
|
||||
suite.Assert().NoError(retry.Constant(3*time.Second, retry.WithUnits(100*time.Millisecond)).Retry(
|
||||
func() error {
|
||||
return suite.assertResources(network.ConfigNamespaceName, network.HostnameSpecType, []string{
|
||||
"platform/hostname",
|
||||
}, func(r resource.Resource) error {
|
||||
spec := r.(*network.HostnameSpec).TypedSpec()
|
||||
|
||||
suite.Assert().Equal("talos-e2e-897b4e49-gcp-controlplane-jvcnl", spec.Hostname)
|
||||
suite.Assert().Equal("", spec.Domainname)
|
||||
suite.Assert().Equal(network.ConfigPlatform, spec.ConfigLayer)
|
||||
|
||||
return nil
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (suite *PlatformConfigSuite) TearDownTest() {
|
||||
suite.T().Log("tear down")
|
||||
|
||||
@ -235,8 +467,16 @@ func TestPlatformConfigSuite(t *testing.T) {
|
||||
}
|
||||
|
||||
type platformMock struct {
|
||||
hostname []byte
|
||||
ipAddresses []net.IP
|
||||
noData bool
|
||||
|
||||
hostname []byte
|
||||
externalIPs []netaddr.IP
|
||||
addresses []netaddr.IPPrefix
|
||||
defaultRoutes []netaddr.IP
|
||||
linksUp []string
|
||||
resolvers []netaddr.IP
|
||||
timeServers []string
|
||||
dhcp4Links []string
|
||||
}
|
||||
|
||||
func (mock *platformMock) Name() string {
|
||||
@ -247,18 +487,108 @@ func (mock *platformMock) Configuration(context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (mock *platformMock) Hostname(context.Context) ([]byte, error) {
|
||||
return mock.hostname, nil
|
||||
}
|
||||
|
||||
func (mock *platformMock) Mode() v1alpha1runtime.Mode {
|
||||
return v1alpha1runtime.ModeCloud
|
||||
}
|
||||
|
||||
func (mock *platformMock) ExternalIPs(context.Context) ([]net.IP, error) {
|
||||
return mock.ipAddresses, nil
|
||||
}
|
||||
|
||||
func (mock *platformMock) KernelArgs() procfs.Parameters {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (mock *platformMock) NetworkConfiguration(ctx context.Context, ch chan<- *v1alpha1runtime.PlatformNetworkConfig) error {
|
||||
if mock.noData {
|
||||
return nil
|
||||
}
|
||||
|
||||
networkConfig := &v1alpha1runtime.PlatformNetworkConfig{
|
||||
ExternalIPs: mock.externalIPs,
|
||||
}
|
||||
|
||||
if mock.hostname != nil {
|
||||
hostnameSpec := network.HostnameSpecSpec{}
|
||||
if err := hostnameSpec.ParseFQDN(string(mock.hostname)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = []network.HostnameSpecSpec{hostnameSpec}
|
||||
}
|
||||
|
||||
for _, addr := range mock.addresses {
|
||||
family := nethelpers.FamilyInet4
|
||||
if addr.IP().Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: "eth0",
|
||||
Address: addr,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
})
|
||||
}
|
||||
|
||||
for _, gw := range mock.defaultRoutes {
|
||||
family := nethelpers.FamilyInet4
|
||||
if gw.Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: "eth0",
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
|
||||
for _, link := range mock.linksUp {
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Name: link,
|
||||
Up: true,
|
||||
})
|
||||
}
|
||||
|
||||
if len(mock.resolvers) > 0 {
|
||||
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
DNSServers: mock.resolvers,
|
||||
})
|
||||
}
|
||||
|
||||
if len(mock.timeServers) > 0 {
|
||||
networkConfig.TimeServers = append(networkConfig.TimeServers, network.TimeServerSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
NTPServers: mock.timeServers,
|
||||
})
|
||||
}
|
||||
|
||||
for _, link := range mock.dhcp4Links {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: link,
|
||||
Operator: network.OperatorDHCP4,
|
||||
DHCP4: network.DHCP4OperatorSpec{},
|
||||
})
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,679 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package runtime_test
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "net"
|
||||
// "testing"
|
||||
|
||||
// "github.com/talos-systems/go-procfs/procfs"
|
||||
|
||||
// "github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
// )
|
||||
|
||||
// type MockSuccessfulSequencer struct{}
|
||||
|
||||
// // Boot is a mock method that overrides the embedded sequencer's Boot method.
|
||||
// func (s *MockSuccessfulSequencer) Boot() []Phase {
|
||||
// return []Phase{
|
||||
// &MockSuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Initialize is a mock method that overrides the embedded sequencer's Initialize method.
|
||||
// func (s *MockSuccessfulSequencer) Initialize() []Phase {
|
||||
// return []Phase{
|
||||
// &MockSuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Shutdown is a mock method that overrides the embedded sequencer's Shutdown method.
|
||||
// func (s *MockSuccessfulSequencer) Shutdown() []Phase {
|
||||
// return []Phase{
|
||||
// &MockSuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Upgrade is a mock method that overrides the embedded sequencer's Upgrade method.
|
||||
// func (s *MockSuccessfulSequencer) Upgrade(req *machine.UpgradeRequest) []Phase {
|
||||
// return []Phase{
|
||||
// &MockSuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Reboot is a mock method that overrides the embedded sequencer's Reboot method.
|
||||
// func (s *MockSuccessfulSequencer) Reboot() []Phase {
|
||||
// return []Phase{
|
||||
// &MockSuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Reset is a mock method that overrides the embedded sequencer's Reset method.
|
||||
// func (s *MockSuccessfulSequencer) Reset(req *machine.ResetRequest) []Phase {
|
||||
// return []Phase{
|
||||
// &MockSuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// type MockUnsuccessfulSequencer struct{}
|
||||
|
||||
// // Boot is a mock method that overrides the embedded sequencer's Boot method.
|
||||
// func (s *MockUnsuccessfulSequencer) Boot() []Phase {
|
||||
// return []Phase{
|
||||
// &MockUnsuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Initialize is a mock method that overrides the embedded sequencer's Initialize method.
|
||||
// func (s *MockUnsuccessfulSequencer) Initialize() []Phase {
|
||||
// return []Phase{
|
||||
// &MockUnsuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Shutdown is a mock method that overrides the embedded sequencer's Shutdown method.
|
||||
// func (s *MockUnsuccessfulSequencer) Shutdown() []Phase {
|
||||
// return []Phase{
|
||||
// &MockUnsuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Upgrade is a mock method that overrides the embedded sequencer's Upgrade method.
|
||||
// func (s *MockUnsuccessfulSequencer) Upgrade(req *machine.UpgradeRequest) []Phase {
|
||||
// return []Phase{
|
||||
// &MockUnsuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Reboot is a mock method that overrides the embedded sequencer's Reboot method.
|
||||
// func (s *MockUnsuccessfulSequencer) Reboot() []Phase {
|
||||
// return []Phase{
|
||||
// &MockUnsuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Reset is a mock method that overrides the embedded sequencer's Reset method.
|
||||
// func (s *MockUnsuccessfulSequencer) Reset(req *machine.ResetRequest) []Phase {
|
||||
// return []Phase{
|
||||
// &MockUnsuccessfulPhase{},
|
||||
// }
|
||||
// }
|
||||
|
||||
// type MockSuccessfulPhase struct{}
|
||||
|
||||
// func (*MockSuccessfulPhase) Tasks() []TaskSetupFunc {
|
||||
// return []TaskSetupFunc{&MockSuccessfulTask{}}
|
||||
// }
|
||||
|
||||
// type MockUnsuccessfulPhase struct{}
|
||||
|
||||
// func (*MockUnsuccessfulPhase) Tasks() []TaskSetupFunc {
|
||||
// return []TaskSetupFunc{&MockUnsuccessfulTask{}}
|
||||
// }
|
||||
|
||||
// type MockSuccessfulTask struct{}
|
||||
|
||||
// func (*MockSuccessfulTask) Func(Mode) TaskSetupFunc {
|
||||
// return func(Runtime) error {
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
|
||||
// type MockUnsuccessfulTask struct{}
|
||||
|
||||
// func (*MockUnsuccessfulTask) Func(Mode) TaskSetupFunc {
|
||||
// return func(Runtime) error { return fmt.Errorf("error") }
|
||||
// }
|
||||
|
||||
// type MockPlatform struct{}
|
||||
|
||||
// func (*MockPlatform) Name() string {
|
||||
// return "mock"
|
||||
// }
|
||||
|
||||
// func (*MockPlatform) Configuration() ([]byte, error) {
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
// func (*MockPlatform) ExternalIPs() ([]net.IP, error) {
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
// func (*MockPlatform) Hostname() ([]byte, error) {
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
// func (*MockPlatform) Mode() Mode {
|
||||
// return Metal
|
||||
// }
|
||||
|
||||
// func (*MockPlatform) KernelArgs() procfs.Parameters {
|
||||
// return procfs.Parameters{}
|
||||
// }
|
||||
|
||||
// type MockConfigurator struct{}
|
||||
|
||||
// func (*MockConfigurator) Version() string {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) Debug() bool {
|
||||
// return false
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) Persist() bool {
|
||||
// return false
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) Machine() Machine {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) Cluster() Cluster {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) Validate(Mode) error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) String() (string, error) {
|
||||
// return "", nil
|
||||
// }
|
||||
|
||||
// func (*MockConfigurator) Bytes() ([]byte, error) {
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
// type MockRuntime struct{}
|
||||
|
||||
// func (*MockRuntime) Platform() Platform {
|
||||
// return &MockPlatform{}
|
||||
// }
|
||||
|
||||
// func (*MockRuntime) Config() Configurator {
|
||||
// return &MockConfigurator{}
|
||||
// }
|
||||
|
||||
// func (*MockRuntime) Sequence() Sequence {
|
||||
// return Noop
|
||||
// }
|
||||
|
||||
// func TestController_Run(t *testing.T) {
|
||||
// type fields struct {
|
||||
// Sequencer Sequencer
|
||||
// Runtime Runtime
|
||||
// semaphore int32
|
||||
// }
|
||||
|
||||
// type args struct {
|
||||
// seq Sequence
|
||||
// data interface{}
|
||||
// }
|
||||
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// fields fields
|
||||
// args args
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "boot",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Boot,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "initialize",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Initialize,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "shutdown",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Shutdown,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "upgrade with valid data",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Upgrade,
|
||||
// data: &machine.UpgradeRequest{},
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "upgrade with invalid data",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Upgrade,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// {
|
||||
// name: "upgrade with lock",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 1,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Upgrade,
|
||||
// data: &machine.UpgradeRequest{},
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// {
|
||||
// name: "reset with valid data",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Reset,
|
||||
// data: &machine.ResetRequest{},
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "reset with invalid data",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Reset,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// {
|
||||
// name: "unsuccessful phase",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockUnsuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Boot,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// {
|
||||
// name: "undefined runtime",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: nil,
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// seq: Boot,
|
||||
// data: nil,
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// c := &Controller{
|
||||
// Sequencer: tt.fields.Sequencer,
|
||||
// Runtime: tt.fields.Runtime,
|
||||
// semaphore: tt.fields.semaphore,
|
||||
// }
|
||||
// t.Logf("c.Sequencer: %v", c.Sequencer)
|
||||
// if err := c.Run(tt.args.seq, tt.args.data); (err != nil) != tt.wantErr {
|
||||
// t.Errorf("Controller.Run() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestController_runPhase(t *testing.T) {
|
||||
// type fields struct {
|
||||
// Sequencer Sequencer
|
||||
// Runtime Runtime
|
||||
// semaphore int32
|
||||
// }
|
||||
|
||||
// type args struct {
|
||||
// phase Phase
|
||||
// }
|
||||
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// fields fields
|
||||
// args args
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "successful phase",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// phase: &MockSuccessfulPhase{},
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "unsuccessful phase",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// phase: &MockUnsuccessfulPhase{},
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// c := &Controller{
|
||||
// Sequencer: tt.fields.Sequencer,
|
||||
// Runtime: tt.fields.Runtime,
|
||||
// semaphore: tt.fields.semaphore,
|
||||
// }
|
||||
// if err := c.runPhase(tt.args.phase); (err != nil) != tt.wantErr {
|
||||
// t.Errorf("Controller.runPhase() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestController_runTask(t *testing.T) {
|
||||
// type fields struct {
|
||||
// Sequencer Sequencer
|
||||
// Runtime Runtime
|
||||
// semaphore int32
|
||||
// }
|
||||
|
||||
// type args struct {
|
||||
// t TaskSetupFunc
|
||||
// }
|
||||
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// fields fields
|
||||
// args args
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "successful task",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// t: &MockSuccessfulTask{},
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "unsuccessful task",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// Runtime: &MockRuntime{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// args: args{
|
||||
// t: &MockUnsuccessfulTask{},
|
||||
// },
|
||||
// wantErr: true,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// c := &Controller{
|
||||
// Sequencer: tt.fields.Sequencer,
|
||||
// Runtime: tt.fields.Runtime,
|
||||
// semaphore: tt.fields.semaphore,
|
||||
// }
|
||||
|
||||
// if err := c.runTask(tt.args.t); (err != nil) != tt.wantErr {
|
||||
// t.Errorf("Controller.runTask() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestController_TryLock(t *testing.T) {
|
||||
// type fields struct {
|
||||
// Sequencer Sequencer
|
||||
// Runtime Runtime
|
||||
// semaphore int32
|
||||
// }
|
||||
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// fields fields
|
||||
// want bool
|
||||
// }{
|
||||
// {
|
||||
// name: "is locked",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// want: false,
|
||||
// },
|
||||
// {
|
||||
// name: "is unlocked",
|
||||
// fields: fields{
|
||||
// Sequencer: &MockSuccessfulSequencer{},
|
||||
// semaphore: 1,
|
||||
// },
|
||||
// want: true,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// c := &Controller{
|
||||
// Sequencer: tt.fields.Sequencer,
|
||||
// Runtime: tt.fields.Runtime,
|
||||
// semaphore: tt.fields.semaphore,
|
||||
// }
|
||||
// if got := c.TryLock(); got != tt.want {
|
||||
// t.Errorf("Controller.TryLock() = %v, want %v", got, tt.want)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestController_Unlock(t *testing.T) {
|
||||
// type fields struct {
|
||||
// Sequencer Sequencer
|
||||
// Runtime Runtime
|
||||
// semaphore int32
|
||||
// }
|
||||
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// fields fields
|
||||
// want bool
|
||||
// }{
|
||||
// {
|
||||
// name: "did not unlock",
|
||||
// fields: fields{
|
||||
// semaphore: 0,
|
||||
// },
|
||||
// want: false,
|
||||
// },
|
||||
// {
|
||||
// name: "did unlock",
|
||||
// fields: fields{
|
||||
// semaphore: 1,
|
||||
// },
|
||||
// want: true,
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// c := &Controller{
|
||||
// Sequencer: tt.fields.Sequencer,
|
||||
// Runtime: tt.fields.Runtime,
|
||||
// semaphore: tt.fields.semaphore,
|
||||
// }
|
||||
// if got := c.Unlock(); got != tt.want {
|
||||
// t.Errorf("Controller.Unlock() = %v, want %v", got, tt.want)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// import (
|
||||
// "errors"
|
||||
// "os"
|
||||
// "testing"
|
||||
|
||||
// "github.com/stretchr/testify/suite"
|
||||
|
||||
// "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
// "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/phase"
|
||||
// )
|
||||
|
||||
// type PhaseSuite struct {
|
||||
// suite.Suite
|
||||
|
||||
// platformExists bool
|
||||
// platformValue string
|
||||
// }
|
||||
|
||||
// type regularTask struct {
|
||||
// errCh <-chan error
|
||||
// }
|
||||
|
||||
// func (t *regularTask) TaskFunc(runtime.Mode) TaskFunc {
|
||||
// return func(runtime.Runtime) error {
|
||||
// return <-t.errCh
|
||||
// }
|
||||
// }
|
||||
|
||||
// type nilTask struct{}
|
||||
|
||||
// func (t *nilTask) TaskFunc(runtime.Mode) TaskFunc {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// type panicTask struct{}
|
||||
|
||||
// func (t *panicTask) TaskFunc(runtime.Mode) TaskFunc {
|
||||
// return func(runtime.Runtime) error {
|
||||
// panic("in task")
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (suite *PhaseSuite) SetupSuite() {
|
||||
// suite.platformValue, suite.platformExists = os.LookupEnv("PLATFORM")
|
||||
// suite.Require().NoError(os.Setenv("PLATFORM", "container"))
|
||||
// }
|
||||
|
||||
// func (suite *PhaseSuite) TearDownSuite() {
|
||||
// if !suite.platformExists {
|
||||
// suite.Require().NoError(os.Unsetenv("PLATFORM"))
|
||||
// } else {
|
||||
// suite.Require().NoError(os.Setenv("PLATFORM", suite.platformValue))
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (suite *PhaseSuite) TestRunSuccess() {
|
||||
// r, err := phase.NewRunner(nil, runtime.Noop)
|
||||
// suite.Require().NoError(err)
|
||||
|
||||
// taskErr := make(chan error)
|
||||
|
||||
// r.Add(phase.NewPhase("empty"))
|
||||
// r.Add(phase.NewPhase("phase1", ®ularTask{errCh: taskErr}, ®ularTask{errCh: taskErr}))
|
||||
// r.Add(phase.NewPhase("phase2", ®ularTask{errCh: taskErr}, &nilTask{}))
|
||||
|
||||
// errCh := make(chan error)
|
||||
|
||||
// go func() {
|
||||
// errCh <- r.Run()
|
||||
// }()
|
||||
|
||||
// taskErr <- nil
|
||||
// taskErr <- nil
|
||||
|
||||
// select {
|
||||
// case <-errCh:
|
||||
// suite.Require().Fail("should be still running")
|
||||
// default:
|
||||
// }
|
||||
|
||||
// taskErr <- nil
|
||||
|
||||
// suite.Require().NoError(<-errCh)
|
||||
// }
|
||||
|
||||
// func (suite *PhaseSuite) TestRunFailures() {
|
||||
// r, err := phase.NewRunner(nil, runtime.Noop)
|
||||
// suite.Require().NoError(err)
|
||||
|
||||
// taskErr := make(chan error, 1)
|
||||
|
||||
// r.Add(phase.NewPhase("empty"))
|
||||
// r.Add(phase.NewPhase("failphase", &panicTask{}, ®ularTask{errCh: taskErr}, &nilTask{}))
|
||||
// r.Add(phase.NewPhase("neverreached",
|
||||
// ®ularTask{}, // should never be reached
|
||||
// ))
|
||||
|
||||
// taskErr <- errors.New("test error")
|
||||
|
||||
// err = r.Run()
|
||||
// suite.Require().Error(err)
|
||||
// suite.Assert().Contains(err.Error(), "2 errors occurred")
|
||||
// suite.Assert().Contains(err.Error(), "test error")
|
||||
// suite.Assert().Contains(err.Error(), "panic recovered: in task")
|
||||
// }
|
||||
|
||||
// func TestPhaseSuite(t *testing.T) {
|
||||
// suite.Run(t, new(PhaseSuite))
|
||||
// }
|
@ -6,17 +6,52 @@ package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
// Platform defines the requirements for a platform.
|
||||
type Platform interface {
|
||||
// Name returns platform name.
|
||||
Name() string
|
||||
Configuration(context.Context) ([]byte, error)
|
||||
Hostname(context.Context) ([]byte, error)
|
||||
|
||||
// Mode returns platform mode (metal, cloud or container).
|
||||
Mode() Mode
|
||||
ExternalIPs(context.Context) ([]net.IP, error)
|
||||
|
||||
// Configuration fetches the machine configuration from platform-specific location.
|
||||
//
|
||||
// On cloud-like platform it is user-data in metadata service.
|
||||
// For metal platform that is either `talos.config=` URL or mounted ISO image.
|
||||
Configuration(context.Context) ([]byte, error)
|
||||
|
||||
// KernelArgs returns additional kernel arguments which should be injected for the kernel boot.
|
||||
KernelArgs() procfs.Parameters
|
||||
|
||||
// NetworkConfiguration fetches network configuration from the platform metadata.
|
||||
//
|
||||
// Controller will run this in function a separate goroutine, restarting it
|
||||
// on error. Platform is expected to deliver network configuration over the channel,
|
||||
// including updates to the configuration over time.
|
||||
NetworkConfiguration(context.Context, chan<- *PlatformNetworkConfig) error
|
||||
}
|
||||
|
||||
// PlatformNetworkConfig describes the network configuration produced by the platform.
|
||||
//
|
||||
// This structure is marshaled to STATE partition to persist cached network configuration across
|
||||
// reboots.
|
||||
type PlatformNetworkConfig struct {
|
||||
Addresses []network.AddressSpecSpec `yaml:"addresses"`
|
||||
Links []network.LinkSpecSpec `yaml:"links"`
|
||||
Routes []network.RouteSpecSpec `yaml:"routes"`
|
||||
|
||||
Hostnames []network.HostnameSpecSpec `yaml:"hostnames"`
|
||||
Resolvers []network.ResolverSpecSpec `yaml:"resolvers"`
|
||||
TimeServers []network.TimeServerSpecSpec `yaml:"timeServers"`
|
||||
|
||||
Operators []network.OperatorSpecSpec `yaml:"operators"`
|
||||
|
||||
ExternalIPs []netaddr.IP `yaml:"externalIPs"`
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
@ -17,9 +16,11 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const notFoundError = "NotFoundError"
|
||||
@ -65,9 +66,7 @@ func (a *AWS) Configuration(ctx context.Context) ([]byte, error) {
|
||||
return nil, fmt.Errorf("failed to fetch EC2 userdata: %w", err)
|
||||
}
|
||||
|
||||
userdata = strings.TrimSpace(userdata)
|
||||
|
||||
if userdata == "" {
|
||||
if strings.TrimSpace(userdata) == "" {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
@ -79,45 +78,70 @@ func (a *AWS) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (a *AWS) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
host, err := a.metadataClient.GetMetadataWithContext(ctx, "hostname")
|
||||
if err != nil {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == notFoundError {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to fetch hostname from IMDS: %w", err)
|
||||
}
|
||||
|
||||
return []byte(host), nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (a *AWS) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
publicIP, err := a.metadataClient.GetMetadataWithContext(ctx, "public-ipv4")
|
||||
if err != nil {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == notFoundError {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to fetch public IPv4 from IMDS: %w", err)
|
||||
}
|
||||
|
||||
if addr := net.ParseIP(publicIP); addr != nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (a *AWS) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("tty1").Append("ttyS0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (a *AWS) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
getMetadataKey := func(key string) (string, error) {
|
||||
v, err := a.metadataClient.GetMetadataWithContext(ctx, key)
|
||||
if err != nil {
|
||||
if awsErr, ok := err.(awserr.Error); ok {
|
||||
if awsErr.Code() == notFoundError {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to fetch %q from IMDS: %w", key, err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
hostname, err := getMetadataKey("hostname")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err = hostnameSpec.ParseFQDN(hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
externalIP, err := getMetadataKey("public-ipv4")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if externalIP != "" {
|
||||
ip, err := netaddr.ParseIP(externalIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -9,24 +9,22 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"golang.org/x/sys/unix"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -71,50 +69,61 @@ func (a *Azure) Name() string {
|
||||
return "azure"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
func (a *Azure) ConfigurationNetwork(metadataNetworkConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var machineConfig *v1alpha1.Config
|
||||
// ParseMetadata parses Azure network metadata into the platform network config.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (a *Azure) ParseMetadata(interfaceAddresses []NetworkConfig, host []byte) (*runtime.PlatformNetworkConfig, error) {
|
||||
var networkConfig runtime.PlatformNetworkConfig
|
||||
|
||||
machineConfig, ok := confProvider.Raw().(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
// hostname
|
||||
if len(host) > 0 {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(string(host)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
|
||||
var interfaceAddresses []NetworkConfig
|
||||
|
||||
if err := json.Unmarshal(metadataNetworkConfig, &interfaceAddresses); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
|
||||
for idx, iface := range interfaceAddresses {
|
||||
device := &v1alpha1.Device{
|
||||
DeviceInterface: fmt.Sprintf("eth%d", idx),
|
||||
DeviceDHCP: true,
|
||||
DeviceDHCPOptions: &v1alpha1.DHCPOptions{DHCPIPv6: pointer.ToBool(true)},
|
||||
// external IP
|
||||
for _, iface := range interfaceAddresses {
|
||||
for _, ipv4addr := range iface.IPv4.IPAddresses {
|
||||
if ip, err := netaddr.ParseIP(ipv4addr.PublicIPAddress); err == nil {
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
}
|
||||
|
||||
ipv6 := false
|
||||
|
||||
for _, ipv6addr := range iface.IPv6.IPAddresses {
|
||||
ipv6 = ipv6addr.PublicIPAddress != "" || ipv6addr.PrivateIPAddress != ""
|
||||
}
|
||||
|
||||
if ipv6 {
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, device)
|
||||
for _, ipv6addr := range iface.IPv6.IPAddresses {
|
||||
if ip, err := netaddr.ParseIP(ipv6addr.PublicIPAddress); err == nil {
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return machineConfig, nil
|
||||
// DHCP6 for enabled interfaces
|
||||
for idx, iface := range interfaceAddresses {
|
||||
ipv6 := false
|
||||
|
||||
for _, ipv6addr := range iface.IPv6.IPAddresses {
|
||||
ipv6 = ipv6addr.PublicIPAddress != "" || ipv6addr.PrivateIPAddress != ""
|
||||
}
|
||||
|
||||
if ipv6 {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP6,
|
||||
LinkName: fmt.Sprintf("eth%d", idx),
|
||||
RequireUp: true,
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return &networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the platform.Platform interface.
|
||||
@ -125,35 +134,10 @@ func (a *Azure) Configuration(ctx context.Context) ([]byte, error) {
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("fetching network config from %q", AzureInterfacesEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, AzureInterfacesEndpoint,
|
||||
download.WithHeaders(map[string]string{"Metadata": "true"}))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch network config from metadata service")
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from ovf-env.xml")
|
||||
|
||||
// Custom data is not available in IMDS, so trying to find it on CDROM.
|
||||
machineConfig, err := a.configFromCD()
|
||||
if err != nil {
|
||||
log.Printf("fetching machine config from cdrom failed, err: %s", err.Error())
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing machine config: %w", err)
|
||||
}
|
||||
|
||||
confProvider, err = a.ConfigurationNetwork(metadataNetworkConfig, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
return a.configFromCD()
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
@ -161,41 +145,6 @@ func (a *Azure) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (a *Azure) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", AzureHostnameEndpoint)
|
||||
|
||||
host, err := download.Download(ctx, AzureHostnameEndpoint,
|
||||
download.WithHeaders(map[string]string{"Metadata": "true"}),
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (a *Azure) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
log.Printf("fetching externalIP from: %q", AzureInterfacesEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, AzureInterfacesEndpoint,
|
||||
download.WithHeaders(map[string]string{"Metadata": "true"}),
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addrs, err = a.getPublicIPs(metadataNetworkConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (a *Azure) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
@ -259,27 +208,42 @@ func (a *Azure) configFromCD() ([]byte, error) {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
// getPublicIPs parced network metadata response.
|
||||
func (a *Azure) getPublicIPs(metadataNetworkConfig []byte) (addrs []net.IP, err error) {
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (a *Azure) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching network config from %q", AzureInterfacesEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, AzureInterfacesEndpoint,
|
||||
download.WithHeaders(map[string]string{"Metadata": "true"}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch network config from metadata service: %w", err)
|
||||
}
|
||||
|
||||
var interfaceAddresses []NetworkConfig
|
||||
|
||||
if err = json.Unmarshal(metadataNetworkConfig, &interfaceAddresses); err != nil {
|
||||
return nil, errors.ErrNoExternalIPs
|
||||
return err
|
||||
}
|
||||
|
||||
for _, iface := range interfaceAddresses {
|
||||
for _, ipv4addr := range iface.IPv4.IPAddresses {
|
||||
if ip := net.ParseIP(ipv4addr.PublicIPAddress); ip != nil {
|
||||
addrs = append(addrs, ip)
|
||||
}
|
||||
}
|
||||
log.Printf("fetching hostname from: %q", AzureHostnameEndpoint)
|
||||
|
||||
for _, ipv6addr := range iface.IPv6.IPAddresses {
|
||||
if ip := net.ParseIP(ipv6addr.PublicIPAddress); ip != nil {
|
||||
addrs = append(addrs, ip)
|
||||
}
|
||||
}
|
||||
host, err := download.Download(ctx, AzureHostnameEndpoint,
|
||||
download.WithHeaders(map[string]string{"Metadata": "true"}),
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
|
||||
return err
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
networkConfig, err := a.ParseMetadata(interfaceAddresses, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,73 +5,35 @@
|
||||
package azure_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/azure"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
//go:embed metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
cfg := []byte(`
|
||||
[
|
||||
{
|
||||
"ipv4": {
|
||||
"ipAddress": [
|
||||
{
|
||||
"privateIpAddress": "172.18.1.10",
|
||||
"publicIpAddress": "1.2.3.4"
|
||||
}
|
||||
],
|
||||
"subnet": [
|
||||
{
|
||||
"address": "172.18.1.0",
|
||||
"prefix": "24"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ipv6": {
|
||||
"ipAddress": [
|
||||
{
|
||||
"privateIpAddress": "fd00::10",
|
||||
"publicIpAddress": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"macAddress": "000D3AD631EE"
|
||||
}
|
||||
]
|
||||
`)
|
||||
//go:embed expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
a := &azure.Azure{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
var m []azure.NetworkConfig
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
DeviceDHCPOptions: &v1alpha1.DHCPOptions{DHCPIPv6: pointer.ToBool(true)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
result, err := a.ConfigurationNetwork(cfg, defaultMachineConfig)
|
||||
networkConfig, err := a.ParseMetadata(m, []byte("some.fqdn"))
|
||||
require.NoError(t, err)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
addresses: []
|
||||
links: []
|
||||
routes: []
|
||||
hostnames:
|
||||
- hostname: some
|
||||
domainname: fqdn
|
||||
layer: platform
|
||||
resolvers: []
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp6
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp6:
|
||||
routeMetric: 1024
|
||||
layer: default
|
||||
externalIPs:
|
||||
- 1.2.3.4
|
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"ipv4": {
|
||||
"ipAddress": [
|
||||
{
|
||||
"privateIpAddress": "172.18.1.10",
|
||||
"publicIpAddress": "1.2.3.4"
|
||||
}
|
||||
],
|
||||
"subnet": [
|
||||
{
|
||||
"address": "172.18.1.0",
|
||||
"prefix": "24"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ipv6": {
|
||||
"ipAddress": [
|
||||
{
|
||||
"privateIpAddress": "fd00::10",
|
||||
"publicIpAddress": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"macAddress": "000D3AD631EE"
|
||||
}
|
||||
]
|
@ -10,13 +10,13 @@ import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
// Container is a platform for installing Talos via an Container image.
|
||||
@ -44,28 +44,40 @@ func (c *Container) Configuration(context.Context) ([]byte, error) {
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (c *Container) Hostname(context.Context) (hostname []byte, err error) {
|
||||
hostname, err = ioutil.ReadFile("/etc/hostname")
|
||||
|
||||
if err == nil {
|
||||
hostname = bytes.TrimSpace(hostname)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
func (c *Container) Mode() runtime.Mode {
|
||||
return runtime.ModeContainer
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (c *Container) ExternalIPs(context.Context) (addrs []net.IP, err error) {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (c *Container) KernelArgs() procfs.Parameters {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (c *Container) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
hostname, err := ioutil.ReadFile("/etc/hostname")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(string(bytes.TrimSpace(hostname))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,17 +6,16 @@ package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
stderrors "errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -50,65 +49,56 @@ func (d *DigitalOcean) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (d *DigitalOcean) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, DigitalOceanHostnameEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//nolint:errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return hostname, fmt.Errorf("failed to fetch hostname from metadata service: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (d *DigitalOcean) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
var (
|
||||
body []byte
|
||||
req *http.Request
|
||||
resp *http.Response
|
||||
)
|
||||
|
||||
if req, err = http.NewRequestWithContext(ctx, "GET", DigitalOceanExternalIPEndpoint, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
if resp, err = client.Do(req); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return addrs, fmt.Errorf("failed to retrieve external addresses for instance")
|
||||
}
|
||||
|
||||
if body, err = ioutil.ReadAll(resp.Body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if addr := net.ParseIP(string(body)); addr != nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (d *DigitalOcean) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("ttyS0").Append("tty0").Append("tty1"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (d *DigitalOcean) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
host, err := download.Download(ctx, DigitalOceanHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
|
||||
return err
|
||||
}
|
||||
|
||||
extIP, err := download.Download(ctx, DigitalOceanExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
if len(host) > 0 {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(string(host)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
if len(extIP) > 0 {
|
||||
if ip, err := netaddr.ParseIP(string(extIP)); err == nil {
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,14 +6,15 @@ package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
// GCP is the concrete type that implements the platform.Platform interface.
|
||||
@ -35,51 +36,67 @@ func (g *GCP) Configuration(ctx context.Context) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userdata = strings.TrimSpace(userdata)
|
||||
|
||||
if userdata == "" {
|
||||
if strings.TrimSpace(userdata) == "" {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
return []byte(userdata), nil
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (g *GCP) Hostname(context.Context) (hostname []byte, err error) {
|
||||
host, err := metadata.Hostname()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(host), nil
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
func (g *GCP) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (g *GCP) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
extIP, err := metadata.ExternalIP()
|
||||
if err != nil {
|
||||
if _, ok := err.(metadata.NotDefinedError); ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if addr := net.ParseIP(extIP); addr != nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (g *GCP) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("ttyS0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (g *GCP) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
hostname, err := metadata.Hostname()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err = hostnameSpec.ParseFQDN(hostname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
externalIP, err := metadata.ExternalIP()
|
||||
if err != nil {
|
||||
if _, ok := err.(metadata.NotDefinedError); !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if externalIP != "" {
|
||||
ip, err := netaddr.ParseIP(externalIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,19 +6,19 @@ package hcloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"gopkg.in/yaml.v3"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -61,104 +61,109 @@ func (h *Hcloud) Name() string {
|
||||
return "hcloud"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
// ParseMetadata converts HCloud metadata to platform network configuration.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (h *Hcloud) ConfigurationNetwork(metadataNetworkConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var unmarshalledNetworkConfig NetworkConfig
|
||||
func (h *Hcloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, host, extIP []byte) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
if err := yaml.Unmarshal(metadataNetworkConfig, &unmarshalledNetworkConfig); err != nil {
|
||||
return nil, err
|
||||
if len(host) > 0 {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(string(host)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
if unmarshalledNetworkConfig.Version != 1 {
|
||||
return nil, fmt.Errorf("network-config metadata version=%d is not supported", unmarshalledNetworkConfig.Version)
|
||||
if len(extIP) > 0 {
|
||||
if ip, err := netaddr.ParseIP(string(extIP)); err == nil {
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
}
|
||||
|
||||
var machineConfig *v1alpha1.Config
|
||||
|
||||
machineConfig, ok := confProvider.Raw().(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
|
||||
for _, network := range unmarshalledNetworkConfig.Config {
|
||||
if network.Type != "physical" {
|
||||
for _, ntwrk := range unmarshalledNetworkConfig.Config {
|
||||
if ntwrk.Type != "physical" {
|
||||
continue
|
||||
}
|
||||
|
||||
iface := v1alpha1.Device{
|
||||
DeviceInterface: network.Interfaces,
|
||||
DeviceDHCP: false,
|
||||
}
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
Name: ntwrk.Interfaces,
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
|
||||
for _, subnet := range network.Subnets {
|
||||
for _, subnet := range ntwrk.Subnets {
|
||||
if subnet.Type == "dhcp" && subnet.Ipv4 {
|
||||
iface.DeviceDHCP = true
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: ntwrk.Interfaces,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
if subnet.Type == "static" {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses,
|
||||
subnet.Address,
|
||||
ipAddr, err := netaddr.ParseIPPrefix(subnet.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
if ipAddr.IP().Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: ntwrk.Interfaces,
|
||||
Address: ipAddr,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if subnet.Gateway != "" && subnet.Ipv6 {
|
||||
iface.DeviceRoutes = []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: subnet.Gateway,
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
gw, err := netaddr.ParseIP(subnet.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: ntwrk.Interfaces,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: nethelpers.FamilyInet6,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces,
|
||||
&iface,
|
||||
)
|
||||
}
|
||||
|
||||
return machineConfig, nil
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
func (h *Hcloud) Configuration(ctx context.Context) ([]byte, error) {
|
||||
log.Printf("fetching hcloud network config from: %q", HCloudNetworkEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, HCloudNetworkEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch network config from metadata service")
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", HCloudUserDataEndpoint)
|
||||
|
||||
machineConfigDl, err := download.Download(ctx, HCloudUserDataEndpoint,
|
||||
return download.Download(ctx, HCloudUserDataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfigDl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err = h.ConfigurationNetwork(metadataNetworkConfig, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
@ -166,41 +171,62 @@ func (h *Hcloud) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (h *Hcloud) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", HCloudHostnameEndpoint)
|
||||
|
||||
host, err := download.Download(ctx, HCloudHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (h *Hcloud) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
log.Printf("fetching externalIP from: %q", HCloudExternalIPEndpoint)
|
||||
|
||||
exIP, err := download.Download(ctx, HCloudExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(string(exIP)); ip != nil {
|
||||
addrs = append(addrs, ip)
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (h *Hcloud) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("tty1").Append("ttyS0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (h *Hcloud) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching hcloud network config from: %q", HCloudNetworkEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, HCloudNetworkEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch network config from metadata service: %w", err)
|
||||
}
|
||||
|
||||
var unmarshalledNetworkConfig NetworkConfig
|
||||
|
||||
if err = yaml.Unmarshal(metadataNetworkConfig, &unmarshalledNetworkConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if unmarshalledNetworkConfig.Version != 1 {
|
||||
return fmt.Errorf("network-config metadata version=%d is not supported", unmarshalledNetworkConfig.Version)
|
||||
}
|
||||
|
||||
log.Printf("fetching hostname from: %q", HCloudHostnameEndpoint)
|
||||
|
||||
host, err := download.Download(ctx, HCloudHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNoHostname) {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("fetching externalIP from: %q", HCloudExternalIPEndpoint)
|
||||
|
||||
extIP, err := download.Download(ctx, HCloudExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig, err := h.ParseMetadata(&unmarshalledNetworkConfig, host, extIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,69 +5,37 @@
|
||||
package hcloud_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
cfg := []byte(`
|
||||
config:
|
||||
- mac_address: 96:00:00:1:2:3
|
||||
name: eth0
|
||||
subnets:
|
||||
- ipv4: true
|
||||
type: dhcp
|
||||
- address: 2a01:4f8:1:2::1/64
|
||||
gateway: fe80::1
|
||||
ipv6: true
|
||||
type: static
|
||||
type: physical
|
||||
- address:
|
||||
- 185.12.64.2
|
||||
- 185.12.64.1
|
||||
interface: eth0
|
||||
type: nameserver
|
||||
version: 1
|
||||
`)
|
||||
p := &hcloud.Hcloud{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
DeviceAddresses: []string{"2a01:4f8:1:2::1/64"},
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: "fe80::1",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.ConfigurationNetwork(cfg, defaultMachineConfig)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
//go:embed testdata/metadata.yaml
|
||||
var rawMetadata []byte
|
||||
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
h := &hcloud.Hcloud{}
|
||||
|
||||
var m hcloud.NetworkConfig
|
||||
|
||||
require.NoError(t, yaml.Unmarshal(rawMetadata, &m))
|
||||
|
||||
networkConfig, err := h.ParseMetadata(&m, []byte("some.fqdn"), []byte("1.2.3.4"))
|
||||
require.NoError(t, err)
|
||||
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Print(string(marshaled))
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
42
internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/expected.yaml
vendored
Normal file
42
internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
addresses:
|
||||
- address: 2a01:4f8:1:2::1/64
|
||||
linkName: eth0
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: fe80::1
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: some
|
||||
domainname: fqdn
|
||||
layer: platform
|
||||
resolvers: []
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: false
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs:
|
||||
- 1.2.3.4
|
17
internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/metadata.yaml
vendored
Normal file
17
internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud/testdata/metadata.yaml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
config:
|
||||
- mac_address: 96:00:00:1:2:3
|
||||
name: eth0
|
||||
subnets:
|
||||
- ipv4: true
|
||||
type: dhcp
|
||||
- address: 2a01:4f8:1:2::1/64
|
||||
gateway: fe80::1
|
||||
ipv6: true
|
||||
type: static
|
||||
type: physical
|
||||
- address:
|
||||
- 185.12.64.2
|
||||
- 185.12.64.1
|
||||
interface: eth0
|
||||
type: nameserver
|
||||
version: 1
|
@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -105,21 +104,11 @@ func getSystemUUID() (uuid.UUID, error) {
|
||||
return s.SystemInformation().UUID()
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (m *Metal) Hostname(context.Context) (hostname []byte, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
func (m *Metal) Mode() runtime.Mode {
|
||||
return runtime.ModeMetal
|
||||
}
|
||||
|
||||
// ExternalIPs implements the platform.Platform interface.
|
||||
func (m *Metal) ExternalIPs(context.Context) (addrs []net.IP, err error) {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
func readConfigFromISO() (b []byte, err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
|
||||
@ -162,3 +151,8 @@ func (m *Metal) KernelArgs() procfs.Parameters {
|
||||
procfs.NewParameter("console").Append("ttyS0").Append("tty0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (m *Metal) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -0,0 +1,468 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package nocloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/filesystem"
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
|
||||
"github.com/talos-systems/go-smbios/smbios"
|
||||
"golang.org/x/sys/unix"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
configISOLabel = "cidata"
|
||||
configNetworkConfigPath = "network-config"
|
||||
configMetaDataPath = "meta-data"
|
||||
configUserDataPath = "user-data"
|
||||
mnt = "/mnt"
|
||||
)
|
||||
|
||||
// NetworkConfig holds network-config info.
|
||||
type NetworkConfig struct {
|
||||
Version int `yaml:"version"`
|
||||
Config []struct {
|
||||
Mac string `yaml:"mac_address,omitempty"`
|
||||
Interfaces string `yaml:"name,omitempty"`
|
||||
MTU string `yaml:"mtu,omitempty"`
|
||||
Subnets []struct {
|
||||
Address string `yaml:"address,omitempty"`
|
||||
Netmask string `yaml:"netmask,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
} `yaml:"subnets,omitempty"`
|
||||
Address []string `yaml:"address,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
} `yaml:"config,omitempty"`
|
||||
Ethernets map[string]Ethernet `yaml:"ethernets,omitempty"`
|
||||
Bonds map[string]Bonds `yaml:"bonds,omitempty"`
|
||||
}
|
||||
|
||||
// Ethernet holds network interface info.
|
||||
type Ethernet struct {
|
||||
Match struct {
|
||||
Name []string `yaml:"name,omitempty"`
|
||||
HWAddr []string `yaml:"macaddress,omitempty"`
|
||||
} `yaml:"match,omitempty"`
|
||||
DHCPv4 bool `yaml:"dhcp4,omitempty"`
|
||||
DHCPv6 bool `yaml:"dhcp6,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
Gateway4 string `yaml:"gateway4,omitempty"`
|
||||
Gateway6 string `yaml:"gateway6,omitempty"`
|
||||
MTU int `yaml:"mtu,omitempty"`
|
||||
NameServers struct {
|
||||
Search []string `yaml:"search,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
} `yaml:"nameservers,omitempty"`
|
||||
}
|
||||
|
||||
// Bonds holds bonding interface info.
|
||||
type Bonds struct {
|
||||
Interfaces []string `yaml:"interfaces,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
NameServers struct {
|
||||
Search []string `yaml:"search,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
} `yaml:"nameservers,omitempty"`
|
||||
Params []struct {
|
||||
Mode string `yaml:"mode,omitempty"`
|
||||
LACPRate string `yaml:"lacp-rate,omitempty"`
|
||||
HashPolicy string `yaml:"transmit-hash-policy,omitempty"`
|
||||
} `yaml:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// MetadataConfig holds meta info.
|
||||
type MetadataConfig struct {
|
||||
Hostname string `yaml:"hostname,omitempty"`
|
||||
InstanceID string `yaml:"instance-id,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Nocloud) configFromNetwork(ctx context.Context, metaBaseURL string) (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
log.Printf("fetching meta config from: %q", metaBaseURL+configMetaDataPath)
|
||||
|
||||
metaConfig, err = download.Download(ctx, metaBaseURL+configMetaDataPath)
|
||||
if err != nil {
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: %q", metaBaseURL+configNetworkConfigPath)
|
||||
|
||||
networkConfig, err = download.Download(ctx, metaBaseURL+configNetworkConfigPath)
|
||||
if err != nil {
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", metaBaseURL+configUserDataPath)
|
||||
|
||||
machineConfig, err = download.Download(ctx, metaBaseURL+configUserDataPath,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, err
|
||||
}
|
||||
|
||||
func (n *Nocloud) configFromCD() (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
|
||||
dev, err = probe.GetDevWithFileSystemLabel(strings.ToLower(configISOLabel))
|
||||
if err != nil {
|
||||
dev, err = probe.GetDevWithFileSystemLabel(strings.ToUpper(configISOLabel))
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer dev.Close()
|
||||
|
||||
sb, err := filesystem.Probe(dev.Path)
|
||||
if err != nil || sb == nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("found config disk (cidata) at %s", dev.Path)
|
||||
|
||||
if err = unix.Mount(dev.Path, mnt, sb.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("fetching meta config from: cidata/%s", configMetaDataPath)
|
||||
|
||||
metaConfig, err = ioutil.ReadFile(filepath.Join(mnt, configMetaDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configMetaDataPath)
|
||||
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: cidata/%s", configNetworkConfigPath)
|
||||
|
||||
networkConfig, err = ioutil.ReadFile(filepath.Join(mnt, configNetworkConfigPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configNetworkConfigPath)
|
||||
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: cidata/%s", configUserDataPath)
|
||||
|
||||
machineConfig, err = ioutil.ReadFile(filepath.Join(mnt, configUserDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configUserDataPath)
|
||||
|
||||
machineConfig = nil
|
||||
}
|
||||
|
||||
if err = unix.Unmount(mnt, 0); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to unmount: %w", err)
|
||||
}
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) acquireConfig(ctx context.Context) (metadataConfigDl, metadataNetworkConfigDl, machineConfigDl []byte, hostname string, err error) {
|
||||
s, err := smbios.New()
|
||||
if err != nil {
|
||||
return nil, nil, nil, "", err
|
||||
}
|
||||
|
||||
metaBaseURL := ""
|
||||
networkSource := false
|
||||
|
||||
options := strings.Split(s.SystemInformation().SerialNumber(), ";")
|
||||
for _, option := range options {
|
||||
parts := strings.SplitN(option, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
switch parts[0] {
|
||||
case "ds":
|
||||
if parts[1] == "nocloud-net" {
|
||||
networkSource = true
|
||||
}
|
||||
case "s":
|
||||
var u *url.URL
|
||||
|
||||
u, err = url.Parse(parts[1])
|
||||
if err == nil && strings.HasPrefix(u.Scheme, "http") {
|
||||
if strings.HasSuffix(u.Path, "/") {
|
||||
metaBaseURL = parts[1]
|
||||
} else {
|
||||
metaBaseURL = parts[1] + "/"
|
||||
}
|
||||
}
|
||||
case "h":
|
||||
hostname = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if networkSource && metaBaseURL != "" {
|
||||
metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, err = n.configFromNetwork(ctx, metaBaseURL)
|
||||
} else {
|
||||
metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, err = n.configFromCD()
|
||||
}
|
||||
|
||||
return metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, hostname, err
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) applyNetworkConfigV1(config *NetworkConfig, networkConfig *runtime.PlatformNetworkConfig) error {
|
||||
for _, ntwrk := range config.Config {
|
||||
switch ntwrk.Type {
|
||||
case "nameserver":
|
||||
dnsIPs := make([]netaddr.IP, 0, len(ntwrk.Address))
|
||||
|
||||
for i := range ntwrk.Address {
|
||||
if ip, err := netaddr.ParseIP(ntwrk.Address[i]); err == nil {
|
||||
dnsIPs = append(dnsIPs, ip)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
|
||||
DNSServers: dnsIPs,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
case "physical":
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
Name: ntwrk.Interfaces,
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
|
||||
for _, subnet := range ntwrk.Subnets {
|
||||
switch subnet.Type {
|
||||
case "dhcp", "dhcp4":
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: ntwrk.Interfaces,
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
case "static", "static6":
|
||||
family := nethelpers.FamilyInet4
|
||||
|
||||
if subnet.Type == "static6" {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
ipPrefix, err := netaddr.ParseIPPrefix(subnet.Address)
|
||||
if err != nil {
|
||||
ip, err := netaddr.ParseIP(subnet.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
netmask, err := netaddr.ParseIP(subnet.Netmask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mask := netmask.As4()
|
||||
|
||||
ipPrefix, err = ip.Netmask(mask[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: ntwrk.Interfaces,
|
||||
Address: ipPrefix,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
},
|
||||
)
|
||||
|
||||
if subnet.Gateway != "" {
|
||||
gw, err := netaddr.ParseIP(subnet.Gateway)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: ntwrk.Interfaces,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
if family == nethelpers.FamilyInet6 {
|
||||
route.Priority = 2048
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
case "ipv6_dhcpv6-stateful":
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP6,
|
||||
LinkName: ntwrk.Interfaces,
|
||||
RequireUp: true,
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) applyNetworkConfigV2(config *NetworkConfig, networkConfig *runtime.PlatformNetworkConfig) error {
|
||||
var dnsIPs []netaddr.IP
|
||||
|
||||
for name, eth := range config.Ethernets {
|
||||
if !strings.HasPrefix(name, "eth") {
|
||||
continue
|
||||
}
|
||||
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
Name: name,
|
||||
Up: true,
|
||||
MTU: uint32(eth.MTU),
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
|
||||
if eth.DHCPv4 {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: name,
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
if eth.DHCPv6 {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP6,
|
||||
LinkName: name,
|
||||
RequireUp: true,
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
for _, addr := range eth.Address {
|
||||
ipPrefix, err := netaddr.ParseIPPrefix(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
|
||||
if ipPrefix.IP().Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: name,
|
||||
Address: ipPrefix,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if eth.Gateway4 != "" {
|
||||
gw, err := netaddr.ParseIP(eth.Gateway4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: name,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
|
||||
if eth.Gateway6 != "" {
|
||||
gw, err := netaddr.ParseIP(eth.Gateway6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: name,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: nethelpers.FamilyInet6,
|
||||
Priority: 2048,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
|
||||
for _, addr := range eth.NameServers.Address {
|
||||
if ip, err := netaddr.ParseIP(addr); err == nil {
|
||||
dnsIPs = append(dnsIPs, ip)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(dnsIPs) > 0 {
|
||||
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
|
||||
DNSServers: dnsIPs,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -7,97 +7,17 @@ package nocloud
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/filesystem"
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/talos-systems/go-smbios/smbios"
|
||||
"golang.org/x/sys/unix"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
configISOLabel = "cidata"
|
||||
configNetworkConfigPath = "network-config"
|
||||
configMetaDataPath = "meta-data"
|
||||
configUserDataPath = "user-data"
|
||||
mnt = "/mnt"
|
||||
)
|
||||
|
||||
// NetworkConfig holds network-config info.
|
||||
type NetworkConfig struct {
|
||||
Version int `yaml:"version"`
|
||||
Config []struct {
|
||||
Mac string `yaml:"mac_address,omitempty"`
|
||||
Interfaces string `yaml:"name,omitempty"`
|
||||
MTU string `yaml:"mtu,omitempty"`
|
||||
Subnets []struct {
|
||||
Address string `yaml:"address,omitempty"`
|
||||
Netmask string `yaml:"netmask,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
} `yaml:"subnets,omitempty"`
|
||||
Address []string `yaml:"address,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
} `yaml:"config,omitempty"`
|
||||
Ethernets map[string]Ethernet `yaml:"ethernets,omitempty"`
|
||||
Bonds map[string]Bonds `yaml:"bonds,omitempty"`
|
||||
}
|
||||
|
||||
// Ethernet holds network interface info.
|
||||
type Ethernet struct {
|
||||
Match struct {
|
||||
Name []string `yaml:"name,omitempty"`
|
||||
HWAddr []string `yaml:"macaddress,omitempty"`
|
||||
} `yaml:"match,omitempty"`
|
||||
DHCPv4 bool `yaml:"dhcp4,omitempty"`
|
||||
DHCPv6 bool `yaml:"dhcp6,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
Gateway4 string `yaml:"gateway4,omitempty"`
|
||||
Gateway6 string `yaml:"gateway6,omitempty"`
|
||||
MTU int `yaml:"mtu,omitempty"`
|
||||
NameServers struct {
|
||||
Search []string `yaml:"search,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
} `yaml:"nameservers,omitempty"`
|
||||
}
|
||||
|
||||
// Bonds holds bonding interface info.
|
||||
type Bonds struct {
|
||||
Interfaces []string `yaml:"interfaces,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
NameServers struct {
|
||||
Search []string `yaml:"search,omitempty"`
|
||||
Address []string `yaml:"addresses,omitempty"`
|
||||
} `yaml:"nameservers,omitempty"`
|
||||
Params []struct {
|
||||
Mode string `yaml:"mode,omitempty"`
|
||||
LACPRate string `yaml:"lacp-rate,omitempty"`
|
||||
HashPolicy string `yaml:"transmit-hash-policy,omitempty"`
|
||||
} `yaml:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// MetadataConfig holds meta info.
|
||||
type MetadataConfig struct {
|
||||
Hostname string `yaml:"hostname,omitempty"`
|
||||
InstanceID string `yaml:"instance-id,omitempty"`
|
||||
}
|
||||
|
||||
// Nocloud is the concrete type that implements the runtime.Platform interface.
|
||||
type Nocloud struct{}
|
||||
|
||||
@ -106,135 +26,50 @@ func (n *Nocloud) Name() string {
|
||||
return "nocloud"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) ConfigurationNetwork(metadataNetworkConfig []byte, metadataConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var (
|
||||
unmarshalledMetadataConfig MetadataConfig
|
||||
machineConfig *v1alpha1.Config
|
||||
)
|
||||
// ParseMetadata converts nocloud metadata to platform network config.
|
||||
func (n *Nocloud) ParseMetadata(unmarshalledNetworkConfig *NetworkConfig, hostname string) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
if err := yaml.Unmarshal(metadataConfig, &unmarshalledMetadataConfig); err != nil {
|
||||
unmarshalledMetadataConfig = MetadataConfig{}
|
||||
}
|
||||
if hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
machineConfig, ok := confProvider.(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkHostname == "" && unmarshalledMetadataConfig.Hostname != "" {
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkHostname = unmarshalledMetadataConfig.Hostname
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
|
||||
var unmarshalledNetworkConfig NetworkConfig
|
||||
|
||||
if err := yaml.Unmarshal(metadataNetworkConfig, &unmarshalledNetworkConfig); err != nil {
|
||||
if err := hostnameSpec.ParseFQDN(hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch unmarshalledNetworkConfig.Version {
|
||||
case 1:
|
||||
n.applyNetworkConfigV1(unmarshalledNetworkConfig, machineConfig)
|
||||
case 2:
|
||||
n.applyNetworkConfigV2(unmarshalledNetworkConfig, machineConfig)
|
||||
default:
|
||||
return nil, fmt.Errorf("network-config metadata version=%d is not supported", unmarshalledNetworkConfig.Version)
|
||||
}
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
return confProvider, nil
|
||||
switch unmarshalledNetworkConfig.Version {
|
||||
case 1:
|
||||
if err := n.applyNetworkConfigV1(unmarshalledNetworkConfig, networkConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case 2:
|
||||
if err := n.applyNetworkConfigV2(unmarshalledNetworkConfig, networkConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("network-config metadata version=%d is not supported", unmarshalledNetworkConfig.Version)
|
||||
}
|
||||
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) Configuration(ctx context.Context) ([]byte, error) {
|
||||
s, err := smbios.New()
|
||||
_, _, machineConfigDl, _, err := n.acquireConfig(ctx) //nolint:dogsled
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostname := ""
|
||||
metaBaseURL := ""
|
||||
networkSource := false
|
||||
|
||||
options := strings.Split(s.SystemInformation().SerialNumber(), ";")
|
||||
for _, option := range options {
|
||||
parts := strings.SplitN(option, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
switch parts[0] {
|
||||
case "ds":
|
||||
if parts[1] == "nocloud-net" {
|
||||
networkSource = true
|
||||
}
|
||||
case "s":
|
||||
var u *url.URL
|
||||
|
||||
u, err = url.Parse(parts[1])
|
||||
if err == nil && strings.HasPrefix(u.Scheme, "http") {
|
||||
if strings.HasSuffix(u.Path, "/") {
|
||||
metaBaseURL = parts[1]
|
||||
} else {
|
||||
metaBaseURL = parts[1] + "/"
|
||||
}
|
||||
}
|
||||
case "h":
|
||||
hostname = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
metadataConfigDl []byte
|
||||
metadataNetworkConfigDl []byte
|
||||
machineConfigDl []byte
|
||||
)
|
||||
|
||||
if networkSource && metaBaseURL != "" {
|
||||
metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, err = n.configFromNetwork(ctx, metaBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
metadataConfigDl, metadataNetworkConfigDl, machineConfigDl, err = n.configFromCD()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if hostname != "" && metadataConfigDl == nil {
|
||||
meta := &MetadataConfig{
|
||||
Hostname: hostname,
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
metadataConfigDl, _ = yaml.Marshal(meta)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(machineConfigDl, []byte("#cloud-config")) {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfigDl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err = n.ConfigurationNetwork(metadataNetworkConfigDl, metadataConfigDl, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
return machineConfigDl, nil
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
@ -242,16 +77,6 @@ func (n *Nocloud) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (n *Nocloud) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (n *Nocloud) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (n *Nocloud) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
@ -259,219 +84,53 @@ func (n *Nocloud) KernelArgs() procfs.Parameters {
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) applyNetworkConfigV1(networkConfig NetworkConfig, machineConfig *v1alpha1.Config) {
|
||||
for _, network := range networkConfig.Config {
|
||||
switch network.Type {
|
||||
case "nameserver":
|
||||
if machineConfig.MachineConfig.MachineNetwork.NameServers == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork.NameServers = network.Address
|
||||
}
|
||||
case "physical":
|
||||
iface := v1alpha1.Device{
|
||||
DeviceInterface: network.Interfaces,
|
||||
DeviceDHCP: false,
|
||||
}
|
||||
func (n *Nocloud) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
metadataConfigDl, metadataNetworkConfigDl, _, hostname, err := n.acquireConfig(ctx)
|
||||
if stderrors.Is(err, errors.ErrNoConfigSource) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
for _, subnet := range network.Subnets {
|
||||
switch subnet.Type {
|
||||
case "dhcp", "dhcp4":
|
||||
iface.DeviceDHCP = true
|
||||
case "static":
|
||||
cidr := strings.SplitN(subnet.Address, "/", 2)
|
||||
if len(cidr) == 2 {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses,
|
||||
subnet.Address,
|
||||
)
|
||||
} else {
|
||||
mask, _ := net.IPMask(net.ParseIP(subnet.Netmask).To4()).Size()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses,
|
||||
fmt.Sprintf("%s/%d", subnet.Address, mask),
|
||||
)
|
||||
}
|
||||
if metadataConfigDl == nil && metadataNetworkConfigDl == nil && hostname == "" {
|
||||
// no data, use cached network configuration if available
|
||||
return nil
|
||||
}
|
||||
|
||||
if subnet.Gateway != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: subnet.Gateway,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
case "static6":
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses,
|
||||
subnet.Address,
|
||||
)
|
||||
if subnet.Gateway != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: subnet.Gateway,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
case "ipv6_dhcpv6-stateful":
|
||||
iface.DeviceDHCPOptions = &v1alpha1.DHCPOptions{
|
||||
DHCPIPv6: pointer.ToBool(true),
|
||||
DHCPRouteMetric: 1024,
|
||||
}
|
||||
}
|
||||
}
|
||||
var (
|
||||
unmarshalledMetadataConfig MetadataConfig
|
||||
unmarshalledNetworkConfig NetworkConfig
|
||||
)
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces,
|
||||
&iface,
|
||||
)
|
||||
if metadataConfigDl != nil {
|
||||
_ = yaml.Unmarshal(metadataConfigDl, &unmarshalledMetadataConfig) //nolint:errcheck
|
||||
}
|
||||
|
||||
if metadataNetworkConfigDl != nil {
|
||||
if err = yaml.Unmarshal(metadataNetworkConfigDl, &unmarshalledNetworkConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) applyNetworkConfigV2(networkConfig NetworkConfig, machineConfig *v1alpha1.Config) {
|
||||
var ns []string
|
||||
|
||||
for name, eth := range networkConfig.Ethernets {
|
||||
if !strings.HasPrefix(name, "eth") {
|
||||
continue
|
||||
}
|
||||
|
||||
iface := v1alpha1.Device{
|
||||
DeviceInterface: name,
|
||||
DeviceDHCP: eth.DHCPv4,
|
||||
}
|
||||
|
||||
if eth.DHCPv6 {
|
||||
iface.DeviceDHCPOptions = &v1alpha1.DHCPOptions{
|
||||
DHCPIPv6: pointer.ToBool(true),
|
||||
DHCPRouteMetric: 1024,
|
||||
}
|
||||
}
|
||||
|
||||
if eth.Address != nil {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, eth.Address...)
|
||||
}
|
||||
|
||||
if eth.Gateway4 != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: eth.Gateway4,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
|
||||
if eth.Gateway6 != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: eth.Gateway6,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
|
||||
if eth.MTU != 0 {
|
||||
iface.DeviceMTU = eth.MTU
|
||||
}
|
||||
|
||||
if eth.NameServers.Address != nil {
|
||||
ns = append(ns, eth.NameServers.Address...)
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces,
|
||||
&iface,
|
||||
)
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NameServers == nil && ns != nil {
|
||||
machineConfig.MachineConfig.MachineNetwork.NameServers = ns
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Nocloud) configFromNetwork(ctx context.Context, metaBaseURL string) (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
log.Printf("fetching meta config from: %q", metaBaseURL+configMetaDataPath)
|
||||
|
||||
metaConfig, err = download.Download(ctx, metaBaseURL+configMetaDataPath)
|
||||
if err != nil {
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: %q", metaBaseURL+configNetworkConfigPath)
|
||||
|
||||
networkConfig, err = download.Download(ctx, metaBaseURL+configNetworkConfigPath)
|
||||
if err != nil {
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", metaBaseURL+configUserDataPath)
|
||||
|
||||
machineConfig, err = download.Download(ctx, metaBaseURL+configUserDataPath,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (n *Nocloud) configFromCD() (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
|
||||
dev, err = probe.GetDevWithFileSystemLabel(strings.ToLower(configISOLabel))
|
||||
if err != nil {
|
||||
dev, err = probe.GetDevWithFileSystemLabel(strings.ToUpper(configISOLabel))
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer dev.Close()
|
||||
|
||||
sb, err := filesystem.Probe(dev.Path)
|
||||
if err != nil || sb == nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("found config disk (cidata) at %s", dev.Path)
|
||||
|
||||
if err = unix.Mount(dev.Path, mnt, sb.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("fetching meta config from: cidata/%s", configMetaDataPath)
|
||||
|
||||
metaConfig, err = ioutil.ReadFile(filepath.Join(mnt, configMetaDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configMetaDataPath)
|
||||
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: cidata/%s", configNetworkConfigPath)
|
||||
|
||||
networkConfig, err = ioutil.ReadFile(filepath.Join(mnt, configNetworkConfigPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configNetworkConfigPath)
|
||||
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: cidata/%s", configUserDataPath)
|
||||
|
||||
machineConfig, err = ioutil.ReadFile(filepath.Join(mnt, configUserDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configUserDataPath)
|
||||
|
||||
machineConfig = nil
|
||||
}
|
||||
|
||||
if err = unix.Unmount(mnt, 0); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to unmount: %w", err)
|
||||
}
|
||||
|
||||
if machineConfig == nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, nil
|
||||
|
||||
if hostname == "" {
|
||||
hostname = unmarshalledMetadataConfig.Hostname
|
||||
}
|
||||
|
||||
networkConfig, err := n.ParseMetadata(&unmarshalledNetworkConfig, hostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,131 +5,66 @@
|
||||
package nocloud_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
//go:embed testdata/metadata-v1.yaml
|
||||
var rawMetadataV1 []byte
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfigV1() {
|
||||
cfg := []byte(`
|
||||
version: 1
|
||||
config:
|
||||
- type: physical
|
||||
name: eth0
|
||||
mac_address: 'ae:71:9e:61:d0:ad'
|
||||
subnets:
|
||||
- type: static
|
||||
address: '192.168.1.11'
|
||||
netmask: '255.255.255.0'
|
||||
gateway: '192.168.1.1'
|
||||
- type: static6
|
||||
address: '2001:2:3:4:5:6:7:8/64'
|
||||
gateway: 'fe80::1'
|
||||
- type: nameserver
|
||||
address:
|
||||
- '192.168.1.1'
|
||||
search:
|
||||
- 'lan'
|
||||
`)
|
||||
p := &nocloud.Nocloud{}
|
||||
//go:embed testdata/metadata-v2.yaml
|
||||
var rawMetadataV2 []byte
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
//go:embed testdata/expected-v1.yaml
|
||||
var expectedNetworkConfigV1 string
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkHostname: "talos",
|
||||
NameServers: []string{"192.168.1.1"},
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: false,
|
||||
DeviceAddresses: []string{"192.168.1.11/24", "2001:2:3:4:5:6:7:8/64"},
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: "192.168.1.1",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: "fe80::1",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
//go:embed testdata/expected-v2.yaml
|
||||
var expectedNetworkConfigV2 string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
raw []byte
|
||||
hostname string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "V1",
|
||||
raw: rawMetadataV1,
|
||||
hostname: "talos",
|
||||
expected: expectedNetworkConfigV1,
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.ConfigurationNetwork(cfg, []byte("hostname: talos"), defaultMachineConfig)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
// Network configs-v2 examples https://github.com/canonical/netplan/tree/main/examples
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfigV2() {
|
||||
cfg := []byte(`
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
addresses:
|
||||
- 192.168.14.2/24
|
||||
- 2001:1::1/64
|
||||
gateway4: 192.168.14.1
|
||||
gateway6: 2001:1::2
|
||||
nameservers:
|
||||
search: [foo.local, bar.local]
|
||||
addresses: [8.8.8.8]
|
||||
`)
|
||||
p := &nocloud.Nocloud{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NameServers: []string{"8.8.8.8"},
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
DeviceAddresses: []string{"192.168.14.2/24", "2001:1::1/64"},
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: "192.168.14.1",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: "2001:1::2",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "V2",
|
||||
raw: rawMetadataV2,
|
||||
expected: expectedNetworkConfigV2,
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
n := &nocloud.Nocloud{}
|
||||
|
||||
var m nocloud.NetworkConfig
|
||||
|
||||
require.NoError(t, yaml.Unmarshal(tt.raw, &m))
|
||||
|
||||
networkConfig, err := n.ParseMetadata(&m, tt.hostname)
|
||||
require.NoError(t, err)
|
||||
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Print(string(marshaled))
|
||||
|
||||
assert.Equal(t, tt.expected, string(marshaled))
|
||||
})
|
||||
}
|
||||
|
||||
result, err := p.ConfigurationNetwork(cfg, []byte{}, defaultMachineConfig)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
}
|
||||
|
57
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/expected-v1.yaml
vendored
Normal file
57
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/expected-v1.yaml
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
addresses:
|
||||
- address: 192.168.1.0/24
|
||||
linkName: eth0
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
- address: 2001:2:3:4:5:6:7:8/64
|
||||
linkName: eth0
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet4
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 192.168.1.1
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: fe80::1
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 2048
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: talos
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers:
|
||||
- dnsServers:
|
||||
- 192.168.1.1
|
||||
layer: platform
|
||||
timeServers: []
|
||||
operators: []
|
||||
externalIPs: []
|
60
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/expected-v2.yaml
vendored
Normal file
60
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/expected-v2.yaml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
addresses:
|
||||
- address: 192.168.14.2/24
|
||||
linkName: eth0
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
- address: 2001:1::1/64
|
||||
linkName: eth0
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet4
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 192.168.14.1
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 2001:1::2
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 2048
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames: []
|
||||
resolvers:
|
||||
- dnsServers:
|
||||
- 8.8.8.8
|
||||
layer: platform
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs: []
|
18
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/metadata-v1.yaml
vendored
Normal file
18
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/metadata-v1.yaml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
version: 1
|
||||
config:
|
||||
- type: physical
|
||||
name: eth0
|
||||
mac_address: 'ae:71:9e:61:d0:ad'
|
||||
subnets:
|
||||
- type: static
|
||||
address: '192.168.1.11'
|
||||
netmask: '255.255.255.0'
|
||||
gateway: '192.168.1.1'
|
||||
- type: static6
|
||||
address: '2001:2:3:4:5:6:7:8/64'
|
||||
gateway: 'fe80::1'
|
||||
- type: nameserver
|
||||
address:
|
||||
- '192.168.1.1'
|
||||
search:
|
||||
- 'lan'
|
12
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/metadata-v2.yaml
vendored
Normal file
12
internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/testdata/metadata-v2.yaml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
version: 2
|
||||
ethernets:
|
||||
eth0:
|
||||
dhcp4: true
|
||||
addresses:
|
||||
- 192.168.14.2/24
|
||||
- 2001:1::1/64
|
||||
gateway4: 192.168.14.1
|
||||
gateway6: 2001:1::2
|
||||
nameservers:
|
||||
search: [foo.local, bar.local]
|
||||
addresses: [8.8.8.8]
|
@ -0,0 +1,90 @@
|
||||
addresses:
|
||||
- address: 2000:0:100::/56
|
||||
linkName: eth0
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
- address: 2000:0:ff00::/56
|
||||
linkName: eth1
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 1450
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
- name: eth1
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 9000
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 2000:0:100:2fff:ff:ff:ff:ff
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 2048
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
- family: inet6
|
||||
dst: 2000:0:100:2f00::/58
|
||||
src: ""
|
||||
gateway: 2000:0:100:2fff:ff:ff:ff:f0
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: '2000:0:ff00::'
|
||||
outLinkName: eth1
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: talos
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers:
|
||||
- dnsServers:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
layer: platform
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
- operator: dhcp4
|
||||
linkName: eth1
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs:
|
||||
- 1.2.3.4
|
@ -0,0 +1,194 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package openstack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/filesystem"
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
|
||||
"golang.org/x/sys/unix"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
)
|
||||
|
||||
const (
|
||||
// mnt is folder to mount config drive.
|
||||
mnt = "/mnt"
|
||||
|
||||
// config-drive configs path.
|
||||
configISOLabel = "config-2"
|
||||
configMetadataPath = "openstack/latest/meta_data.json"
|
||||
configNetworkDataPath = "openstack/latest/network_data.json"
|
||||
configUserDataPath = "openstack/latest/user_data"
|
||||
|
||||
// OpenstackExternalIPEndpoint is the local Openstack endpoint for the external IP.
|
||||
OpenstackExternalIPEndpoint = "http://169.254.169.254/latest/meta-data/public-ipv4"
|
||||
// OpenstackHostnameEndpoint is the local Openstack endpoint for the hostname.
|
||||
OpenstackHostnameEndpoint = "http://169.254.169.254/latest/meta-data/hostname"
|
||||
// OpenstackMetaDataEndpoint is the local Openstack endpoint for the meta config.
|
||||
OpenstackMetaDataEndpoint = "http://169.254.169.254/" + configMetadataPath
|
||||
// OpenstackNetworkDataEndpoint is the local Openstack endpoint for the network config.
|
||||
OpenstackNetworkDataEndpoint = "http://169.254.169.254/" + configNetworkDataPath
|
||||
// OpenstackUserDataEndpoint is the local Openstack endpoint for the config.
|
||||
OpenstackUserDataEndpoint = "http://169.254.169.254/" + configUserDataPath
|
||||
)
|
||||
|
||||
// NetworkConfig holds NetworkData config.
|
||||
type NetworkConfig struct {
|
||||
Links []struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Mac string `json:"ethernet_mac_address,omitempty"`
|
||||
MTU int `json:"mtu,omitempty"`
|
||||
} `json:"links"`
|
||||
Networks []struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Link string `json:"link"`
|
||||
Type string `json:"type"`
|
||||
Address string `json:"ip_address,omitempty"`
|
||||
Netmask string `json:"netmask,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
Routes []struct {
|
||||
Network string `json:"network,omitempty"`
|
||||
Netmask string `json:"netmask,omitempty"`
|
||||
Gateway string `json:"gateway,omitempty"`
|
||||
} `json:"routes,omitempty"`
|
||||
} `json:"networks"`
|
||||
Services []struct {
|
||||
Type string `json:"type"`
|
||||
Address string `json:"address"`
|
||||
} `json:"services,omitempty"`
|
||||
}
|
||||
|
||||
// MetadataConfig holds meta info.
|
||||
type MetadataConfig struct {
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
func (o *Openstack) configFromNetwork(ctx context.Context) (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
log.Printf("fetching meta config from: %q", OpenstackMetaDataEndpoint)
|
||||
|
||||
metaConfig, err = download.Download(ctx, OpenstackMetaDataEndpoint)
|
||||
if err != nil {
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: %q", OpenstackNetworkDataEndpoint)
|
||||
|
||||
networkConfig, err = download.Download(ctx, OpenstackNetworkDataEndpoint)
|
||||
if err != nil {
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", OpenstackUserDataEndpoint)
|
||||
|
||||
machineConfig, err = download.Download(ctx, OpenstackUserDataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, err
|
||||
}
|
||||
|
||||
func (o *Openstack) configFromCD() (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
|
||||
dev, err = probe.GetDevWithFileSystemLabel(configISOLabel)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer dev.Close()
|
||||
|
||||
sb, err := filesystem.Probe(dev.Path)
|
||||
if err != nil || sb == nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("found config disk (config-drive) at %s", dev.Path)
|
||||
|
||||
if err = unix.Mount(dev.Path, mnt, sb.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("fetching meta config from: config-drive/%s", configMetadataPath)
|
||||
|
||||
metaConfig, err = ioutil.ReadFile(filepath.Join(mnt, configMetadataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configMetadataPath)
|
||||
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: config-drive/%s", configNetworkDataPath)
|
||||
|
||||
networkConfig, err = ioutil.ReadFile(filepath.Join(mnt, configNetworkDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configNetworkDataPath)
|
||||
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: config-drive/%s", configUserDataPath)
|
||||
|
||||
machineConfig, err = ioutil.ReadFile(filepath.Join(mnt, configUserDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configUserDataPath)
|
||||
|
||||
machineConfig = nil
|
||||
}
|
||||
|
||||
if err = unix.Unmount(mnt, 0); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to unmount: %w", err)
|
||||
}
|
||||
|
||||
if machineConfig == nil {
|
||||
return metaConfig, networkConfig, machineConfig, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, nil
|
||||
}
|
||||
|
||||
func (o *Openstack) hostname(ctx context.Context) []byte {
|
||||
log.Printf("fetching hostname from: %q", OpenstackHostnameEndpoint)
|
||||
|
||||
hostname, err := download.Download(ctx, OpenstackHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
// Platform cannot support this endpoint, or returns timeout.
|
||||
log.Printf("failed to fetch hostname, ignored: %s", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return hostname
|
||||
}
|
||||
|
||||
func (o *Openstack) externalIPs(ctx context.Context) (addrs []netaddr.IP) {
|
||||
log.Printf("fetching externalIP from: %q", OpenstackExternalIPEndpoint)
|
||||
|
||||
exIP, err := download.Download(ctx, OpenstackExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
log.Printf("failed to fetch external IPs, ignored: %s", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if addr, err := netaddr.ParseIP(string(exIP)); err == nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"availability_zone": "nova",
|
||||
"devices": [],
|
||||
"hostname": "talos",
|
||||
"keys": [],
|
||||
"launch_index": 0,
|
||||
"name": "talos",
|
||||
"project_id": "39073b0a-1234-1234-1234-5e76a4bd64b2",
|
||||
"public_keys": {},
|
||||
"uuid": "39073b0a-1234-1234-1234-5e76a4bd64b2"
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"ethernet_mac_address": "A4:BF:00:10:20:30",
|
||||
"id": "aae16046-6c74-4f33-acf2-a16e9ab093eb",
|
||||
"type": "phy",
|
||||
"mtu": 1450,
|
||||
"vif_id": "7607af2d-c24d-4bfb-909e-c447b119f4e2"
|
||||
},
|
||||
{
|
||||
"ethernet_mac_address": "A4:BF:00:10:20:31",
|
||||
"id": "aae16046-6c74-4f33-acf2-a16e9ab093ec",
|
||||
"type": "ovs",
|
||||
"mtu": 9000,
|
||||
"vif_id": "c816df7e-7bcc-45ca-9eb2-3d3d3dca0639"
|
||||
}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"id": "publicnet-ipv4",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093eb",
|
||||
"network_id": "66374c4d-5123-4f11-8fa9-8a6dea2b4fe7",
|
||||
"type": "ipv4_dhcp"
|
||||
},
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"network": "2000:0:100:2f00::",
|
||||
"gateway": "2000:0:100:2fff:ff:ff:ff:f0",
|
||||
"netmask": "ffff:ffff:ffff:ffc0::"
|
||||
}
|
||||
],
|
||||
"dns_nameservers": [
|
||||
"2000:0:100::1"
|
||||
],
|
||||
"gateway": "2000:0:100:2fff:ff:ff:ff:ff",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093eb",
|
||||
"ip_address": "2000:0:100::/56",
|
||||
"network_id": "39b48637-d98a-4dfc-a05b-d61e8d88fafe",
|
||||
"id": "publicnet-ipv6",
|
||||
"type": "ipv6"
|
||||
},
|
||||
{
|
||||
"id": "privatnet-ipv4",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093ec",
|
||||
"network_id": "66374c4d-5123-4f11-8fa9-8a6dea2b4fe7",
|
||||
"type": "ipv4_dhcp"
|
||||
},
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"network": "::",
|
||||
"netmask": "::",
|
||||
"gateway": "2000:0:ff00::"
|
||||
}
|
||||
],
|
||||
"id": "privatnet-ipv6",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093ec",
|
||||
"ip_address": "2000:0:ff00::1",
|
||||
"netmask": "ffff:ffff:ffff:ff00::",
|
||||
"network_id": "66374c4d-5123-4f11-8fa9-8a6dea2b4fe7",
|
||||
"type": "ipv6"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"address": "8.8.8.8",
|
||||
"type": "dns"
|
||||
},
|
||||
{
|
||||
"address": "1.1.1.1",
|
||||
"type": "dns"
|
||||
}
|
||||
]
|
||||
}
|
@ -7,83 +7,21 @@ package openstack
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/filesystem"
|
||||
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"golang.org/x/sys/unix"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
// mnt is folder to mount config drive.
|
||||
mnt = "/mnt"
|
||||
|
||||
// config-drive configs path.
|
||||
configISOLabel = "config-2"
|
||||
configMetadataPath = "openstack/latest/meta_data.json"
|
||||
configNetworkDataPath = "openstack/latest/network_data.json"
|
||||
configUserDataPath = "openstack/latest/user_data"
|
||||
|
||||
// OpenstackExternalIPEndpoint is the local Openstack endpoint for the external IP.
|
||||
OpenstackExternalIPEndpoint = "http://169.254.169.254/latest/meta-data/public-ipv4"
|
||||
// OpenstackHostnameEndpoint is the local Openstack endpoint for the hostname.
|
||||
OpenstackHostnameEndpoint = "http://169.254.169.254/latest/meta-data/hostname"
|
||||
// OpenstackMetaDataEndpoint is the local Openstack endpoint for the meta config.
|
||||
OpenstackMetaDataEndpoint = "http://169.254.169.254/" + configMetadataPath
|
||||
// OpenstackNetworkDataEndpoint is the local Openstack endpoint for the network config.
|
||||
OpenstackNetworkDataEndpoint = "http://169.254.169.254/" + configNetworkDataPath
|
||||
// OpenstackUserDataEndpoint is the local Openstack endpoint for the config.
|
||||
OpenstackUserDataEndpoint = "http://169.254.169.254/" + configUserDataPath
|
||||
)
|
||||
|
||||
// NetworkConfig holds NetworkData config.
|
||||
type NetworkConfig struct {
|
||||
Links []struct {
|
||||
ID string `yaml:"id,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Mac string `yaml:"ethernet_mac_address,omitempty"`
|
||||
MTU int `yaml:"mtu,omitempty"`
|
||||
} `yaml:"links"`
|
||||
Networks []struct {
|
||||
ID string `yaml:"id,omitempty"`
|
||||
Link string `yaml:"link"`
|
||||
Type string `yaml:"type"`
|
||||
Address string `yaml:"ip_address,omitempty"`
|
||||
Netmask string `yaml:"netmask,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
Routes []struct {
|
||||
Network string `yaml:"network,omitempty"`
|
||||
Netmask string `yaml:"netmask,omitempty"`
|
||||
Gateway string `yaml:"gateway,omitempty"`
|
||||
} `yaml:"routes,omitempty"`
|
||||
} `yaml:"networks"`
|
||||
Services []struct {
|
||||
Type string `yaml:"type"`
|
||||
Address string `yaml:"address"`
|
||||
} `yaml:"services,omitempty"`
|
||||
}
|
||||
|
||||
// MetadataConfig holds meta info.
|
||||
type MetadataConfig struct {
|
||||
Hostname string `yaml:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
// Openstack is the concrete type that implements the runtime.Platform interface.
|
||||
type Openstack struct{}
|
||||
|
||||
@ -92,168 +30,228 @@ func (o *Openstack) Name() string {
|
||||
return "openstack"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
// ParseMetadata converts OpenStack metadata to platform network configuration.
|
||||
//nolint:gocyclo,cyclop
|
||||
func (o *Openstack) ConfigurationNetwork(metadataNetworkConfig []byte, metadataConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var unmarshalledMetadataConfig MetadataConfig
|
||||
func (o *Openstack) ParseMetadata(unmarshalledMetadataConfig *MetadataConfig, unmarshalledNetworkConfig *NetworkConfig, hostname string, extIPs []netaddr.IP) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
if err := yaml.Unmarshal(metadataConfig, &unmarshalledMetadataConfig); err != nil {
|
||||
unmarshalledMetadataConfig = MetadataConfig{}
|
||||
if hostname == "" {
|
||||
hostname = unmarshalledMetadataConfig.Hostname
|
||||
}
|
||||
|
||||
machineConfig, ok := confProvider.Raw().(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
}
|
||||
if hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkHostname == "" && unmarshalledMetadataConfig.Hostname != "" {
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkHostname = unmarshalledMetadataConfig.Hostname
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
|
||||
var unmarshalledNetworkConfig NetworkConfig
|
||||
|
||||
if err := yaml.Unmarshal(metadataNetworkConfig, &unmarshalledNetworkConfig); err != nil {
|
||||
if err := hostnameSpec.ParseFQDN(hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nameServers := []string{}
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
for _, netsvc := range unmarshalledNetworkConfig.Services {
|
||||
if netsvc.Type == "dns" {
|
||||
nameServers = append(nameServers, netsvc.Address)
|
||||
networkConfig.ExternalIPs = extIPs
|
||||
|
||||
var dnsIPs []netaddr.IP
|
||||
|
||||
for _, netsvc := range unmarshalledNetworkConfig.Services {
|
||||
if netsvc.Type == "dns" {
|
||||
if ip, err := netaddr.ParseIP(netsvc.Address); err == nil {
|
||||
dnsIPs = append(dnsIPs, ip)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NameServers == nil && len(nameServers) > 0 {
|
||||
machineConfig.MachineConfig.MachineNetwork.NameServers = nameServers
|
||||
}
|
||||
|
||||
ifaces := make(map[string]*v1alpha1.Device)
|
||||
|
||||
for idx, netLinks := range unmarshalledNetworkConfig.Links {
|
||||
switch netLinks.Type {
|
||||
case "phy", "vif", "ovs":
|
||||
iface := &v1alpha1.Device{
|
||||
// We need to define name of interface by MAC
|
||||
// I hope it will solve after https://github.com/talos-systems/talos/issues/4203, https://github.com/talos-systems/talos/issues/3265
|
||||
DeviceInterface: fmt.Sprintf("eth%d", idx),
|
||||
DeviceMTU: netLinks.MTU,
|
||||
}
|
||||
ifaces[netLinks.ID] = iface
|
||||
}
|
||||
}
|
||||
|
||||
for _, network := range unmarshalledNetworkConfig.Networks {
|
||||
if network.ID == "" || ifaces[network.Link] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
iface := ifaces[network.Link]
|
||||
|
||||
switch network.Type {
|
||||
case "ipv4_dhcp":
|
||||
iface.DeviceDHCP = true
|
||||
case "ipv4":
|
||||
cidr := strings.SplitN(network.Address, "/", 2)
|
||||
if len(cidr) == 1 {
|
||||
mask, err := strconv.Atoi(network.Netmask)
|
||||
if err != nil {
|
||||
mask, _ = net.IPMask(network.Netmask).Size()
|
||||
}
|
||||
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, fmt.Sprintf("%s/%d", network.Address, mask))
|
||||
} else {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, network.Address)
|
||||
}
|
||||
|
||||
if network.Gateway != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: network.Gateway,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
case "ipv6":
|
||||
cidr := strings.SplitN(network.Address, "/", 2)
|
||||
if len(cidr) == 1 {
|
||||
mask, err := strconv.Atoi(network.Netmask)
|
||||
if err != nil {
|
||||
mask, _ = net.IPMask(net.ParseIP(network.Netmask).To16()).Size()
|
||||
}
|
||||
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, fmt.Sprintf("%s/%d", network.Address, mask))
|
||||
} else {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, network.Address)
|
||||
}
|
||||
|
||||
if network.Gateway != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: network.Gateway,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, route := range network.Routes {
|
||||
mask, err := strconv.Atoi(route.Netmask)
|
||||
if err != nil {
|
||||
gw := net.ParseIP(route.Network)
|
||||
|
||||
if len(gw) == net.IPv4len {
|
||||
mask, _ = net.IPMask(net.ParseIP(route.Netmask).To4()).Size()
|
||||
} else {
|
||||
mask, _ = net.IPMask(net.ParseIP(route.Netmask).To16()).Size()
|
||||
}
|
||||
}
|
||||
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: fmt.Sprintf("%s/%d", route.Network, mask),
|
||||
RouteGateway: route.Gateway,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ifaceNames := make([]string, 0, len(ifaces))
|
||||
|
||||
for ifaceName := range ifaces {
|
||||
ifaceNames = append(ifaceNames, ifaceName)
|
||||
}
|
||||
|
||||
sort.Strings(ifaceNames)
|
||||
|
||||
for _, ifaceName := range ifaceNames {
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, ifaces[ifaceName])
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NameServers == nil && len(nameServers) > 0 {
|
||||
machineConfig.MachineConfig.MachineNetwork.NameServers = nameServers
|
||||
}
|
||||
}
|
||||
|
||||
return machineConfig, nil
|
||||
if len(dnsIPs) > 0 {
|
||||
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
|
||||
DNSServers: dnsIPs,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
ifaces := make(map[string]string)
|
||||
|
||||
for idx, netLinks := range unmarshalledNetworkConfig.Links {
|
||||
switch netLinks.Type {
|
||||
case "phy", "vif", "ovs":
|
||||
// We need to define name of interface by MAC
|
||||
// I hope it will solve after https://github.com/talos-systems/talos/issues/4203, https://github.com/talos-systems/talos/issues/3265
|
||||
ifaces[netLinks.ID] = fmt.Sprintf("eth%d", idx)
|
||||
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
Name: ifaces[netLinks.ID],
|
||||
Up: true,
|
||||
MTU: uint32(netLinks.MTU),
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, ntwrk := range unmarshalledNetworkConfig.Networks {
|
||||
if ntwrk.ID == "" || ifaces[ntwrk.Link] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
iface := ifaces[ntwrk.Link]
|
||||
|
||||
switch ntwrk.Type {
|
||||
case "ipv4_dhcp":
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: iface,
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
case "ipv4", "ipv6":
|
||||
var ipPrefix netaddr.IPPrefix
|
||||
|
||||
cidr := strings.SplitN(ntwrk.Address, "/", 2)
|
||||
if len(cidr) == 1 {
|
||||
ip, err := netaddr.ParseIP(ntwrk.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bits, err := strconv.Atoi(ntwrk.Netmask)
|
||||
if err != nil {
|
||||
maskIP, err := netaddr.ParseIP(ntwrk.Netmask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mask, _ := maskIP.MarshalBinary() //nolint:errcheck // never fails
|
||||
|
||||
ipPrefix, err = ip.Netmask(mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
ipPrefix = netaddr.IPPrefixFrom(ip, uint8(bits))
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
|
||||
ipPrefix, err = netaddr.ParseIPPrefix(ntwrk.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
if ntwrk.Type == "ipv6" {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: iface,
|
||||
Address: ipPrefix,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
},
|
||||
)
|
||||
|
||||
if ntwrk.Gateway != "" {
|
||||
gw, err := netaddr.ParseIP(ntwrk.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: iface,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
if family == nethelpers.FamilyInet6 {
|
||||
route.Priority = 2048
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
}
|
||||
|
||||
for _, route := range ntwrk.Routes {
|
||||
gw, err := netaddr.ParseIP(route.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destIP, err := netaddr.ParseIP(route.Network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dest netaddr.IPPrefix
|
||||
|
||||
bits, err := strconv.Atoi(route.Netmask)
|
||||
if err != nil {
|
||||
var maskIP netaddr.IP
|
||||
|
||||
maskIP, err = netaddr.ParseIP(route.Netmask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mask, _ := maskIP.MarshalBinary() //nolint:errcheck
|
||||
|
||||
dest, err = destIP.Netmask(mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dest, err = destIP.Prefix(uint8(bits))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
if destIP.Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Destination: dest,
|
||||
Gateway: gw,
|
||||
OutLinkName: iface,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
}
|
||||
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
func (o *Openstack) Configuration(ctx context.Context) (machineConfig []byte, err error) {
|
||||
var (
|
||||
metadataConfigDl []byte
|
||||
metadataNetworkConfigDl []byte
|
||||
)
|
||||
|
||||
metadataConfigDl, metadataNetworkConfigDl, machineConfig, err = o.configFromCD()
|
||||
_, _, machineConfig, err = o.configFromCD()
|
||||
if err != nil {
|
||||
metadataConfigDl, metadataNetworkConfigDl, machineConfig, err = o.configFromNetwork(ctx)
|
||||
_, _, machineConfig, err = o.configFromNetwork(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -265,17 +263,7 @@ func (o *Openstack) Configuration(ctx context.Context) (machineConfig []byte, er
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err = o.ConfigurationNetwork(metadataNetworkConfigDl, metadataConfigDl, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
return machineConfig, nil
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
@ -283,41 +271,6 @@ func (o *Openstack) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (o *Openstack) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", OpenstackHostnameEndpoint)
|
||||
|
||||
hostname, err = download.Download(ctx, OpenstackHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
// Platform cannot support this endpoint, or return timeout.
|
||||
log.Printf("failed to fetch hostname, ignored: %s", err)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return hostname, nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (o *Openstack) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
log.Printf("fetching externalIP from: %q", OpenstackExternalIPEndpoint)
|
||||
|
||||
exIP, err := download.Download(ctx, OpenstackExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if addr := net.ParseIP(string(exIP)); addr != nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (o *Openstack) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
@ -325,89 +278,42 @@ func (o *Openstack) KernelArgs() procfs.Parameters {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Openstack) configFromNetwork(ctx context.Context) (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
log.Printf("fetching meta config from: %q", OpenstackMetaDataEndpoint)
|
||||
|
||||
metaConfig, err = download.Download(ctx, OpenstackMetaDataEndpoint)
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (o *Openstack) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
metadataConfigDl, metadataNetworkConfigDl, _, err := o.configFromCD()
|
||||
if err != nil {
|
||||
metaConfig = nil
|
||||
metadataConfigDl, metadataNetworkConfigDl, _, err = o.configFromNetwork(ctx)
|
||||
if stderrors.Is(err, errors.ErrNoConfigSource) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: %q", OpenstackNetworkDataEndpoint)
|
||||
hostname := o.hostname(ctx)
|
||||
extIPs := o.externalIPs(ctx)
|
||||
|
||||
networkConfig, err = download.Download(ctx, OpenstackNetworkDataEndpoint)
|
||||
var (
|
||||
unmarshalledMetadataConfig MetadataConfig
|
||||
unmarshalledNetworkConfig NetworkConfig
|
||||
)
|
||||
|
||||
// ignore errors unmarshaling, empty configs work just fine as empty default
|
||||
_ = yaml.Unmarshal(metadataConfigDl, &unmarshalledMetadataConfig) //nolint:errcheck
|
||||
_ = yaml.Unmarshal(metadataNetworkConfigDl, &unmarshalledNetworkConfig) //nolint:errcheck
|
||||
|
||||
networkConfig, err := o.ParseMetadata(&unmarshalledMetadataConfig, &unmarshalledNetworkConfig, string(hostname), extIPs)
|
||||
if err != nil {
|
||||
networkConfig = nil
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", OpenstackUserDataEndpoint)
|
||||
|
||||
machineConfig, err = download.Download(ctx, OpenstackUserDataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, nil
|
||||
}
|
||||
|
||||
func (o *Openstack) configFromCD() (metaConfig []byte, networkConfig []byte, machineConfig []byte, err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
|
||||
dev, err = probe.GetDevWithFileSystemLabel(configISOLabel)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer dev.Close()
|
||||
|
||||
sb, err := filesystem.Probe(dev.Path)
|
||||
if err != nil || sb == nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("found config disk (config-drive) at %s", dev.Path)
|
||||
|
||||
if err = unix.Mount(dev.Path, mnt, sb.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("fetching meta config from: config-drive/%s", configMetadataPath)
|
||||
|
||||
metaConfig, err = ioutil.ReadFile(filepath.Join(mnt, configMetadataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configMetadataPath)
|
||||
|
||||
metaConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching network config from: config-drive/%s", configNetworkDataPath)
|
||||
|
||||
networkConfig, err = ioutil.ReadFile(filepath.Join(mnt, configNetworkDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configNetworkDataPath)
|
||||
|
||||
networkConfig = nil
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: config-drive/%s", configUserDataPath)
|
||||
|
||||
machineConfig, err = ioutil.ReadFile(filepath.Join(mnt, configUserDataPath))
|
||||
if err != nil {
|
||||
log.Printf("failed to read %s", configUserDataPath)
|
||||
|
||||
machineConfig = nil
|
||||
}
|
||||
|
||||
if err = unix.Unmount(mnt, 0); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to unmount: %w", err)
|
||||
}
|
||||
|
||||
if machineConfig == nil {
|
||||
return nil, nil, nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
return metaConfig, networkConfig, machineConfig, nil
|
||||
return nil
|
||||
}
|
||||
|
@ -5,161 +5,43 @@
|
||||
package openstack_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
// https://specs.openstack.org/openstack/nova-specs/specs/liberty/implemented/metadata-service-network-info.html
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
cfg := []byte(`{
|
||||
"links": [
|
||||
{
|
||||
"ethernet_mac_address": "A4:BF:00:10:20:30",
|
||||
"id": "aae16046-6c74-4f33-acf2-a16e9ab093eb",
|
||||
"type": "phy",
|
||||
"mtu": 1450,
|
||||
"vif_id": "7607af2d-c24d-4bfb-909e-c447b119f4e2"
|
||||
},
|
||||
{
|
||||
"ethernet_mac_address": "A4:BF:00:10:20:31",
|
||||
"id": "aae16046-6c74-4f33-acf2-a16e9ab093ec",
|
||||
"type": "ovs",
|
||||
"mtu": 9000,
|
||||
"vif_id": "c816df7e-7bcc-45ca-9eb2-3d3d3dca0639"
|
||||
}
|
||||
],
|
||||
"networks": [
|
||||
{
|
||||
"id": "publicnet-ipv4",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093eb",
|
||||
"network_id": "66374c4d-5123-4f11-8fa9-8a6dea2b4fe7",
|
||||
"type": "ipv4_dhcp"
|
||||
},
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"network": "2000:0:100:2f00::",
|
||||
"gateway": "2000:0:100:2fff:ff:ff:ff:f0",
|
||||
"netmask": "ffff:ffff:ffff:ffc0::"
|
||||
}
|
||||
],
|
||||
"dns_nameservers": [
|
||||
"2000:0:100::1"
|
||||
],
|
||||
"gateway": "2000:0:100:2fff:ff:ff:ff:ff",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093eb",
|
||||
"ip_address": "2000:0:100::/56",
|
||||
"network_id": "39b48637-d98a-4dfc-a05b-d61e8d88fafe",
|
||||
"id": "publicnet-ipv6",
|
||||
"type": "ipv6"
|
||||
},
|
||||
{
|
||||
"id": "privatnet-ipv4",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093ec",
|
||||
"network_id": "66374c4d-5123-4f11-8fa9-8a6dea2b4fe7",
|
||||
"type": "ipv4_dhcp"
|
||||
},
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"network": "::",
|
||||
"netmask": "::",
|
||||
"gateway": "2000:0:ff00::"
|
||||
}
|
||||
],
|
||||
"id": "privatnet-ipv6",
|
||||
"link": "aae16046-6c74-4f33-acf2-a16e9ab093ec",
|
||||
"ip_address": "2000:0:ff00::1",
|
||||
"netmask": "ffff:ffff:ffff:ff00::",
|
||||
"network_id": "66374c4d-5123-4f11-8fa9-8a6dea2b4fe7",
|
||||
"type": "ipv6"
|
||||
},
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"address": "8.8.8.8",
|
||||
"type": "dns"
|
||||
},
|
||||
{
|
||||
"address": "1.1.1.1",
|
||||
"type": "dns"
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
meta := []byte(`{
|
||||
"availability_zone": "nova",
|
||||
"devices": [],
|
||||
"hostname": "talos",
|
||||
"keys": [],
|
||||
"launch_index": 0,
|
||||
"name": "talos",
|
||||
"project_id": "39073b0a-1234-1234-1234-5e76a4bd64b2",
|
||||
"public_keys": {},
|
||||
"uuid": "39073b0a-1234-1234-1234-5e76a4bd64b2"
|
||||
}`)
|
||||
|
||||
p := &openstack.Openstack{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkHostname: "talos",
|
||||
NameServers: []string{"8.8.8.8", "1.1.1.1"},
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceMTU: 1450,
|
||||
DeviceDHCP: true,
|
||||
DeviceAddresses: []string{"2000:0:100::/56"},
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: "2000:0:100:2fff:ff:ff:ff:ff",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
{
|
||||
RouteNetwork: "2000:0:100:2f00::/58",
|
||||
RouteGateway: "2000:0:100:2fff:ff:ff:ff:f0",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DeviceInterface: "eth1",
|
||||
DeviceMTU: 9000,
|
||||
DeviceDHCP: true,
|
||||
DeviceAddresses: []string{"2000:0:ff00::1/56"},
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: "2000:0:ff00::",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, err := p.ConfigurationNetwork(cfg, meta, defaultMachineConfig)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
//go:embed metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
//go:embed network.json
|
||||
var rawNetwork []byte
|
||||
|
||||
//go:embed expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
o := &openstack.Openstack{}
|
||||
|
||||
var m openstack.MetadataConfig
|
||||
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
var n openstack.NetworkConfig
|
||||
|
||||
require.NoError(t, json.Unmarshal(rawNetwork, &n))
|
||||
|
||||
networkConfig, err := o.ParseMetadata(&m, &n, "", []netaddr.IP{netaddr.MustParseIP("1.2.3.4")})
|
||||
require.NoError(t, err)
|
||||
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
@ -10,17 +10,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
// Ref: https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm
|
||||
@ -51,58 +47,43 @@ func (o *Oracle) Name() string {
|
||||
return "oracle"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
func (o *Oracle) ConfigurationNetwork(metadataNetworkConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var machineConfig *v1alpha1.Config
|
||||
// ParseMetadata converts Oracle Cloud metadata into platform network configuration.
|
||||
func (o *Oracle) ParseMetadata(interfaceAddresses []NetworkConfig, hostname string) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
machineConfig, ok := confProvider.(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
if hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
for idx, iface := range interfaceAddresses {
|
||||
ipv6 := iface.Ipv6SubnetCidrBlock != "" && iface.Ipv6VirtualRouterIP != ""
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
|
||||
var interfaceAddresses []NetworkConfig
|
||||
|
||||
if err := json.Unmarshal(metadataNetworkConfig, &interfaceAddresses); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
|
||||
for idx, iface := range interfaceAddresses {
|
||||
ipv6 := iface.Ipv6SubnetCidrBlock != "" && iface.Ipv6VirtualRouterIP != ""
|
||||
|
||||
if ipv6 {
|
||||
device := &v1alpha1.Device{
|
||||
DeviceInterface: fmt.Sprintf("eth%d", idx),
|
||||
DeviceDHCP: true,
|
||||
DeviceDHCPOptions: &v1alpha1.DHCPOptions{DHCPIPv6: pointer.ToBool(true)},
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, device)
|
||||
}
|
||||
if ipv6 {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP6,
|
||||
LinkName: fmt.Sprintf("eth%d", idx),
|
||||
RequireUp: true,
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return confProvider, nil
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the platform.Platform interface.
|
||||
func (o *Oracle) Configuration(ctx context.Context) ([]byte, error) {
|
||||
log.Printf("fetching network config from %q", OracleNetworkEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, OracleNetworkEndpoint,
|
||||
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch network config from metadata service")
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", OracleUserDataEndpoint)
|
||||
|
||||
machineConfigDl, err := download.Download(ctx, OracleUserDataEndpoint,
|
||||
@ -118,32 +99,7 @@ func (o *Oracle) Configuration(ctx context.Context) ([]byte, error) {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing machine config: %w", err)
|
||||
}
|
||||
|
||||
confProvider, err = o.ConfigurationNetwork(metadataNetworkConfig, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (o *Oracle) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", OracleHostnameEndpoint)
|
||||
|
||||
hostname, err = download.Download(ctx, OracleHostnameEndpoint,
|
||||
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}),
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hostname, nil
|
||||
return machineConfig, nil
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
@ -151,14 +107,46 @@ func (o *Oracle) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (o *Oracle) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (o *Oracle) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("tty1").Append("ttyS0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (o *Oracle) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching network config from %q", OracleNetworkEndpoint)
|
||||
|
||||
metadataNetworkConfig, err := download.Download(ctx, OracleNetworkEndpoint,
|
||||
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch network config from metadata service: %w", err)
|
||||
}
|
||||
|
||||
var interfaceAddresses []NetworkConfig
|
||||
|
||||
if err = json.Unmarshal(metadataNetworkConfig, &interfaceAddresses); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("fetching hostname from: %q", OracleHostnameEndpoint)
|
||||
|
||||
hostname, _ := download.Download(ctx, OracleHostnameEndpoint, //nolint:errcheck
|
||||
download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}),
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
|
||||
networkConfig, err := o.ParseMetadata(interfaceAddresses, string(hostname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,56 +5,35 @@
|
||||
package oracle_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/AlekSi/pointer"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
cfg := []byte(`
|
||||
[ {
|
||||
"vnicId" : "ocid1.vnic.oc1.eu-amsterdam-1.asdasd",
|
||||
"privateIp" : "172.16.1.11",
|
||||
"vlanTag" : 1,
|
||||
"macAddr" : "02:00:17:00:00:00",
|
||||
"virtualRouterIp" : "172.16.1.1",
|
||||
"subnetCidrBlock" : "172.16.1.0/24",
|
||||
"ipv6SubnetCidrBlock" : "2603:a:b:c::/64",
|
||||
"ipv6VirtualRouterIp" : "fe80::a:b:c:d"
|
||||
} ]
|
||||
`)
|
||||
a := &oracle.Oracle{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
DeviceDHCPOptions: &v1alpha1.DHCPOptions{DHCPIPv6: pointer.ToBool(true)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, err := a.ConfigurationNetwork(cfg, defaultMachineConfig)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
//go:embed testdata/metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
o := &oracle.Oracle{}
|
||||
|
||||
var m []oracle.NetworkConfig
|
||||
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
networkConfig, err := o.ParseMetadata(m, "talos")
|
||||
require.NoError(t, err)
|
||||
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
17
internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/testdata/expected.yaml
vendored
Normal file
17
internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
addresses: []
|
||||
links: []
|
||||
routes: []
|
||||
hostnames:
|
||||
- hostname: talos
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers: []
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp6
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp6:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs: []
|
12
internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/testdata/metadata.json
vendored
Normal file
12
internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/testdata/metadata.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"vnicId": "ocid1.vnic.oc1.eu-amsterdam-1.asdasd",
|
||||
"privateIp": "172.16.1.11",
|
||||
"vlanTag": 1,
|
||||
"macAddr": "02:00:17:00:00:00",
|
||||
"virtualRouterIp": "172.16.1.1",
|
||||
"subnetCidrBlock": "172.16.1.0/24",
|
||||
"ipv6SubnetCidrBlock": "2603:a:b:c::/64",
|
||||
"ipv6VirtualRouterIp": "fe80::a:b:c:d"
|
||||
}
|
||||
]
|
@ -12,14 +12,15 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
networkadapter "github.com/talos-systems/talos/internal/app/machined/pkg/adapters/network"
|
||||
networkctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/network"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
// Metadata holds packet metadata info.
|
||||
@ -50,14 +51,15 @@ type Interface struct {
|
||||
|
||||
// Address holds address info from the packet metadata.
|
||||
type Address struct {
|
||||
Public bool `json:"public"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CIDR int `json:"cidr"`
|
||||
Family int `json:"address_family"`
|
||||
Netmask string `json:"netmask"`
|
||||
Network string `json:"network"`
|
||||
Address string `json:"address"`
|
||||
Gateway string `json:"gateway"`
|
||||
Public bool `json:"public"`
|
||||
Management bool `json:"management"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CIDR int `json:"cidr"`
|
||||
Family int `json:"address_family"`
|
||||
Netmask string `json:"netmask"`
|
||||
Network string `json:"network"`
|
||||
Address string `json:"address"`
|
||||
Gateway string `json:"gateway"`
|
||||
}
|
||||
|
||||
const (
|
||||
@ -67,7 +69,7 @@ const (
|
||||
PacketMetaDataEndpoint = "https://metadata.platformequinix.com/metadata"
|
||||
)
|
||||
|
||||
// Packet is a discoverer for non-cloud environments.
|
||||
// Packet is a platform for Equinix Metal cloud.
|
||||
type Packet struct{}
|
||||
|
||||
// Name implements the platform.Platform interface.
|
||||
@ -76,143 +78,12 @@ func (p *Packet) Name() string {
|
||||
}
|
||||
|
||||
// Configuration implements the platform.Platform interface.
|
||||
//nolint:gocyclo,cyclop
|
||||
func (p *Packet) Configuration(ctx context.Context) ([]byte, error) {
|
||||
// Fetch and unmarshal both the talos machine config and the
|
||||
// metadata about the instance from packet's metadata server
|
||||
log.Printf("fetching machine config from: %q", PacketUserDataEndpoint)
|
||||
|
||||
machineConfigDl, err := download.Download(ctx, PacketUserDataEndpoint,
|
||||
return download.Download(ctx, PacketUserDataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("fetching equinix network config from: %q", PacketMetaDataEndpoint)
|
||||
|
||||
metadataConfig, err := download.Download(ctx, PacketMetaDataEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var unmarshalledMetadataConfig Metadata
|
||||
if err = json.Unmarshal(metadataConfig, &unmarshalledMetadataConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfigDl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var machineConfig *v1alpha1.Config
|
||||
|
||||
machineConfig, ok := confProvider.Raw().(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
}
|
||||
|
||||
// translate the int returned from bond mode metadata to the type needed by networkd
|
||||
bondMode := nethelpers.BondMode(uint8(unmarshalledMetadataConfig.Network.Bonding.Mode))
|
||||
|
||||
// determine bond name and build list of interfaces enslaved by the bond
|
||||
devicesInBond := []string{}
|
||||
bondName := ""
|
||||
|
||||
hostInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing host interfaces: %w", err)
|
||||
}
|
||||
|
||||
for _, iface := range unmarshalledMetadataConfig.Network.Interfaces {
|
||||
if iface.Bond == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if bondName != "" && iface.Bond != bondName {
|
||||
return nil, fmt.Errorf("encountered multiple bonds. this is unexpected in the equinix metal platform")
|
||||
}
|
||||
|
||||
found := false
|
||||
|
||||
for _, hostIf := range hostInterfaces {
|
||||
if hostIf.HardwareAddr.String() == iface.MAC {
|
||||
found = true
|
||||
|
||||
devicesInBond = append(devicesInBond, hostIf.Name)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Printf("interface with MAC %q wasn't found on the host, skipping", iface.MAC)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
bondName = iface.Bond
|
||||
}
|
||||
|
||||
bondDev := v1alpha1.Device{
|
||||
DeviceInterface: bondName,
|
||||
DeviceDHCP: false,
|
||||
DeviceBond: &v1alpha1.Bond{
|
||||
BondMode: bondMode.String(),
|
||||
BondDownDelay: 200,
|
||||
BondMIIMon: 100,
|
||||
BondUpDelay: 200,
|
||||
BondHashPolicy: "layer3+4",
|
||||
BondInterfaces: devicesInBond,
|
||||
},
|
||||
}
|
||||
|
||||
for _, addr := range unmarshalledMetadataConfig.Network.Addresses {
|
||||
bondDev.DeviceAddresses = append(bondDev.DeviceAddresses,
|
||||
fmt.Sprintf("%s/%d", addr.Address, addr.CIDR),
|
||||
)
|
||||
|
||||
if addr.Public {
|
||||
// for "Public" address add the default route
|
||||
switch addr.Family {
|
||||
case 4:
|
||||
bondDev.DeviceRoutes = append(bondDev.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "0.0.0.0/0",
|
||||
RouteGateway: addr.Gateway,
|
||||
})
|
||||
case 6:
|
||||
bondDev.DeviceRoutes = append(bondDev.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: addr.Gateway,
|
||||
RouteMetric: 2 * network.DefaultRouteMetric,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// for "Private" addresses, we add a route that goes out the gateway for the private subnets.
|
||||
for _, privSubnet := range unmarshalledMetadataConfig.PrivateSubnets {
|
||||
bondDev.DeviceRoutes = append(bondDev.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: privSubnet,
|
||||
RouteGateway: addr.Gateway,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces,
|
||||
&bondDev,
|
||||
)
|
||||
|
||||
return machineConfig.Bytes()
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
@ -220,31 +91,237 @@ func (p *Packet) Mode() runtime.Mode {
|
||||
return runtime.ModeMetal
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (p *Packet) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching equinix metadata from: %q", PacketMetaDataEndpoint)
|
||||
|
||||
metadataConfig, err := download.Download(ctx, PacketMetaDataEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var unmarshalledMetadataConfig Metadata
|
||||
if err = json.Unmarshal(metadataConfig, &unmarshalledMetadataConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(unmarshalledMetadataConfig.Hostname), nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (p *Packet) ExternalIPs(context.Context) (addrs []net.IP, err error) {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (p *Packet) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("ttyS1,115200n8"),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseMetadata converts Equinix Metal (Packet) metadata into Talos network configuration.
|
||||
//
|
||||
//nolint:gocyclo,cyclop
|
||||
func (p *Packet) ParseMetadata(packetMetadata *Metadata) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
// 1. Links
|
||||
|
||||
// translate the int returned from bond mode metadata to the type needed by network resources
|
||||
bondMode := nethelpers.BondMode(uint8(packetMetadata.Network.Bonding.Mode))
|
||||
|
||||
// determine bond name and build list of interfaces enslaved by the bond
|
||||
bondName := ""
|
||||
|
||||
hostInterfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing host interfaces: %w", err)
|
||||
}
|
||||
|
||||
for _, iface := range packetMetadata.Network.Interfaces {
|
||||
if iface.Bond == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if bondName != "" && iface.Bond != bondName {
|
||||
return nil, fmt.Errorf("encountered multiple bonds. this is unexpected in the equinix metal platform")
|
||||
}
|
||||
|
||||
bondName = iface.Bond
|
||||
|
||||
found := false
|
||||
|
||||
for _, hostIf := range hostInterfaces {
|
||||
if hostIf.HardwareAddr.String() == iface.MAC {
|
||||
found = true
|
||||
|
||||
networkConfig.Links = append(networkConfig.Links,
|
||||
network.LinkSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Name: hostIf.Name,
|
||||
Up: true,
|
||||
MasterName: bondName,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.Printf("interface with MAC %q wasn't found on the host, adding with the name from metadata", iface.MAC)
|
||||
|
||||
networkConfig.Links = append(networkConfig.Links,
|
||||
network.LinkSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Name: iface.Name,
|
||||
Up: true,
|
||||
MasterName: bondName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bondLink := network.LinkSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Name: bondName,
|
||||
Logical: true,
|
||||
Up: true,
|
||||
Kind: network.LinkKindBond,
|
||||
Type: nethelpers.LinkEther,
|
||||
BondMaster: network.BondMasterSpec{
|
||||
Mode: bondMode,
|
||||
DownDelay: 200,
|
||||
MIIMon: 100,
|
||||
UpDelay: 200,
|
||||
HashPolicy: nethelpers.BondXmitPolicyLayer34,
|
||||
},
|
||||
}
|
||||
|
||||
networkadapter.BondMasterSpec(&bondLink.BondMaster).FillDefaults()
|
||||
|
||||
networkConfig.Links = append(networkConfig.Links, bondLink)
|
||||
|
||||
// 2. addresses
|
||||
|
||||
for _, addr := range packetMetadata.Network.Addresses {
|
||||
if !(addr.Enabled && addr.Management) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipAddr, err := netaddr.ParseIPPrefix(fmt.Sprintf("%s/%d", addr.Address, addr.CIDR))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
if ipAddr.IP().Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: bondName,
|
||||
Address: ipAddr,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// 3. routes
|
||||
|
||||
for _, addr := range packetMetadata.Network.Addresses {
|
||||
if !(addr.Enabled && addr.Management) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipAddr, err := netaddr.ParseIPPrefix(fmt.Sprintf("%s/%d", addr.Address, addr.CIDR))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
if ipAddr.IP().Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
if addr.Public {
|
||||
// for "Public" address add the default route
|
||||
gw, err := netaddr.ParseIP(addr.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: bondName,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
Priority: networkctrl.DefaultRouteMetric,
|
||||
}
|
||||
|
||||
if addr.Family == 6 {
|
||||
route.Priority = 2 * networkctrl.DefaultRouteMetric
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
} else {
|
||||
// for "Private" addresses, we add a route that goes out the gateway for the private subnets.
|
||||
for _, privSubnet := range packetMetadata.PrivateSubnets {
|
||||
gw, err := netaddr.ParseIP(addr.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dest, err := netaddr.ParseIPPrefix(privSubnet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
Destination: dest,
|
||||
OutLinkName: bondName,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. hostname
|
||||
|
||||
if packetMetadata.Hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(packetMetadata.Hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (p *Packet) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching equinix network config from: %q", PacketMetaDataEndpoint)
|
||||
|
||||
metadataConfig, err := download.Download(ctx, PacketMetaDataEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var packetMetadata Metadata
|
||||
if err = json.Unmarshal(metadataConfig, &packetMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig, err := p.ParseMetadata(&packetMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case ch <- networkConfig:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,11 +4,36 @@
|
||||
|
||||
package packet_test
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
// added for accurate coverage estimation
|
||||
//
|
||||
// please remove it once any unit-test is added
|
||||
// for this package
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/packet"
|
||||
)
|
||||
|
||||
//go:embed testdata/metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
p := &packet.Packet{}
|
||||
|
||||
var m packet.Metadata
|
||||
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
networkConfig, err := p.ParseMetadata(&m)
|
||||
require.NoError(t, err)
|
||||
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
104
internal/app/machined/pkg/runtime/v1alpha1/platform/packet/testdata/expected.yaml
vendored
Normal file
104
internal/app/machined/pkg/runtime/v1alpha1/platform/packet/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
addresses:
|
||||
- address: 147.75.78.41/31
|
||||
linkName: bond0
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
- address: 2604:1380:45d1:fd00::11/127
|
||||
linkName: bond0
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
- address: 10.66.142.17/31
|
||||
linkName: bond0
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
masterName: bond0
|
||||
layer: platform
|
||||
- name: eth1
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
masterName: bond0
|
||||
layer: platform
|
||||
- name: bond0
|
||||
logical: true
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: bond
|
||||
type: ether
|
||||
bondMaster:
|
||||
mode: 802.3ad
|
||||
xmitHashPolicy: layer3+4
|
||||
lacpRate: slow
|
||||
arpValidate: none
|
||||
arpAllTargets: any
|
||||
primaryReselect: always
|
||||
failOverMac: 0
|
||||
miimon: 100
|
||||
updelay: 200
|
||||
downdelay: 200
|
||||
resendIgmp: 1
|
||||
lpInterval: 1
|
||||
packetsPerSlave: 1
|
||||
numPeerNotif: 1
|
||||
tlbLogicalLb: 1
|
||||
adActorSysPrio: 65535
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet4
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 147.75.78.40
|
||||
outLinkName: bond0
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 2604:1380:45d1:fd00::10
|
||||
outLinkName: bond0
|
||||
table: main
|
||||
priority: 2048
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
- family: inet4
|
||||
dst: 10.0.0.0/8
|
||||
src: ""
|
||||
gateway: 10.66.142.16
|
||||
outLinkName: bond0
|
||||
table: main
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: infra-green-ci
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers: []
|
||||
timeServers: []
|
||||
operators: []
|
||||
externalIPs: []
|
117
internal/app/machined/pkg/runtime/v1alpha1/platform/packet/testdata/metadata.json
vendored
Normal file
117
internal/app/machined/pkg/runtime/v1alpha1/platform/packet/testdata/metadata.json
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"id": "X",
|
||||
"hostname": "infra-green-ci",
|
||||
"plan": "c3.medium.x86",
|
||||
"reserved": false,
|
||||
"class": "c3.medium.x86",
|
||||
"facility": "ny5",
|
||||
"metro": "ny",
|
||||
"private_subnets": [
|
||||
"10.0.0.0/8"
|
||||
],
|
||||
"tags": [],
|
||||
"ssh_keys": [
|
||||
],
|
||||
"customdata": {},
|
||||
"network": {
|
||||
"bonding": {
|
||||
"mode": 4,
|
||||
"link_aggregation": "mlag_ha",
|
||||
"mac": "68:05:ca:b8:f1:f8"
|
||||
},
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "eth0",
|
||||
"mac": "68:05:ca:b8:f1:f8",
|
||||
"bond": "bond0"
|
||||
},
|
||||
{
|
||||
"name": "eth1",
|
||||
"mac": "68:05:ca:b8:f1:f9",
|
||||
"bond": "bond0"
|
||||
}
|
||||
],
|
||||
"addresses": [
|
||||
{
|
||||
"id": "d6be5d63-50f8-452c-b5cd-6cba42fbd5b3",
|
||||
"address_family": 4,
|
||||
"netmask": "255.255.255.254",
|
||||
"created_at": "2021-11-24T20:24:54Z",
|
||||
"public": true,
|
||||
"cidr": 31,
|
||||
"management": true,
|
||||
"enabled": true,
|
||||
"network": "147.75.78.40",
|
||||
"address": "147.75.78.41",
|
||||
"gateway": "147.75.78.40",
|
||||
"parent_block": {
|
||||
"network": "147.75.78.40",
|
||||
"netmask": "255.255.255.254",
|
||||
"cidr": 31,
|
||||
"href": "/ips/e5cc5a4e-1d80-42c8-b5ea-39644effb407"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "09c743e9-52e4-4125-9e88-da56c8e62ae4",
|
||||
"address_family": 6,
|
||||
"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
|
||||
"created_at": "2021-11-24T20:24:54Z",
|
||||
"public": true,
|
||||
"cidr": 127,
|
||||
"management": true,
|
||||
"enabled": true,
|
||||
"network": "2604:1380:45d1:fd00::10",
|
||||
"address": "2604:1380:45d1:fd00::11",
|
||||
"gateway": "2604:1380:45d1:fd00::10",
|
||||
"parent_block": {
|
||||
"network": "2604:1380:45d1:fd00:0000:0000:0000:0000",
|
||||
"netmask": "ffff:ffff:ffff:ff00:0000:0000:0000:0000",
|
||||
"cidr": 56,
|
||||
"href": "/ips/a76e6dd1-a22a-4f8a-a04d-7b68b4f358e5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "c7d3cd31-beae-460a-b008-29776c95562b",
|
||||
"address_family": 4,
|
||||
"netmask": "255.255.255.255",
|
||||
"created_at": "2021-12-10T13:41:14Z",
|
||||
"public": true,
|
||||
"cidr": 32,
|
||||
"management": false,
|
||||
"enabled": true,
|
||||
"network": "147.75.195.143",
|
||||
"address": "147.75.195.143",
|
||||
"gateway": "147.75.195.143",
|
||||
"parent_block": {
|
||||
"network": "147.75.195.143",
|
||||
"netmask": "255.255.255.255",
|
||||
"cidr": 32,
|
||||
"href": "/ips/77e054ac-cd40-473a-9c59-8f6c322c5a20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5e0bc796-9e7f-46a5-9472-ced35b8acb6d",
|
||||
"address_family": 4,
|
||||
"netmask": "255.255.255.254",
|
||||
"created_at": "2021-11-24T20:24:54Z",
|
||||
"public": false,
|
||||
"cidr": 31,
|
||||
"management": true,
|
||||
"enabled": true,
|
||||
"network": "10.66.142.16",
|
||||
"address": "10.66.142.17",
|
||||
"gateway": "10.66.142.16",
|
||||
"parent_block": {
|
||||
"network": "10.66.142.0",
|
||||
"netmask": "255.255.255.128",
|
||||
"cidr": 25,
|
||||
"href": "/ips/045b5dd5-6a32-48e6-870d-8ea9a39169d6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"metal_gateways": []
|
||||
},
|
||||
"api_url": "https://metadata.packet.net",
|
||||
"phone_home_url": "http://tinkerbell.ny5.packet.net/phone-home",
|
||||
"user_state_url": "http://tinkerbell.ny5.packet.net/events"
|
||||
}
|
@ -7,19 +7,18 @@ package scaleway
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -35,66 +34,97 @@ func (s *Scaleway) Name() string {
|
||||
return "scaleway"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
func (s *Scaleway) ConfigurationNetwork(metadataConfig *instance.Metadata, confProvider config.Provider) (config.Provider, error) {
|
||||
var machineConfig *v1alpha1.Config
|
||||
// ParseMetadata converts Scaleway met.
|
||||
func (s *Scaleway) ParseMetadata(metadataConfig *instance.Metadata) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
machineConfig, ok := confProvider.(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
if metadataConfig.Hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(metadataConfig.Hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
if metadataConfig.PublicIP.Address != "" {
|
||||
ip, err := netaddr.ParseIP(metadataConfig.PublicIP.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
Name: "eth0",
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
|
||||
iface := v1alpha1.Device{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
}
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: "eth0",
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
|
||||
if metadataConfig.IPv6.Address != "" {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses,
|
||||
fmt.Sprintf("%s/%s", metadataConfig.IPv6.Address, metadataConfig.IPv6.Netmask),
|
||||
bits, err := strconv.Atoi(metadataConfig.IPv6.Netmask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := netaddr.ParseIP(metadataConfig.IPv6.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := netaddr.IPPrefixFrom(ip, uint8(bits))
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: "eth0",
|
||||
Address: addr,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: nethelpers.FamilyInet6,
|
||||
},
|
||||
)
|
||||
|
||||
iface.DeviceRoutes = []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: metadataConfig.IPv6.Gateway,
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
gw, err := netaddr.ParseIP(metadataConfig.IPv6.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
OutLinkName: "eth0",
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: nethelpers.FamilyInet6,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces,
|
||||
&iface,
|
||||
)
|
||||
|
||||
return confProvider, nil
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
func (s *Scaleway) Configuration(ctx context.Context) ([]byte, error) {
|
||||
log.Printf("fetching scaleway instance config from: %q ", ScalewayMetadataEndpoint)
|
||||
|
||||
metadataDl, err := download.Download(ctx, ScalewayMetadataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
metadata := &instance.Metadata{}
|
||||
if err = json.Unmarshal(metadataDl, metadata); err != nil {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from scaleway metadata server")
|
||||
|
||||
instanceAPI := instance.NewMetadataAPI()
|
||||
@ -104,17 +134,7 @@ func (s *Scaleway) Configuration(ctx context.Context) ([]byte, error) {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfigDl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err = s.ConfigurationNetwork(metadata, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
return machineConfigDl, nil
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
@ -122,49 +142,26 @@ func (s *Scaleway) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (s *Scaleway) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", ScalewayMetadataEndpoint)
|
||||
|
||||
metadataDl, err := download.Download(ctx, ScalewayMetadataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metadata := &instance.Metadata{}
|
||||
if err = json.Unmarshal(metadataDl, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(metadata.Hostname), nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (s *Scaleway) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
log.Printf("fetching external IP from: %q", ScalewayMetadataEndpoint)
|
||||
|
||||
metadataDl, err := download.Download(ctx, ScalewayMetadataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
metadata := &instance.Metadata{}
|
||||
if err = json.Unmarshal(metadataDl, metadata); err != nil {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
addrs = append(addrs, net.ParseIP(metadata.PublicIP.Address))
|
||||
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (s *Scaleway) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("tty1").Append("ttyS0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (s *Scaleway) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching scaleway instance config from: %q ", ScalewayMetadataEndpoint)
|
||||
|
||||
metadataDl, err := download.Download(ctx, ScalewayMetadataEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
metadata := &instance.Metadata{}
|
||||
if err = json.Unmarshal(metadataDl, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,76 +5,36 @@
|
||||
package scaleway_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
//go:embed testdata/metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
cfg := []byte(`{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"name": "scw-talos",
|
||||
"commercial_type": "DEV1-S",
|
||||
"hostname": "scw-talos",
|
||||
"tags": [],
|
||||
"state_detail": "booted",
|
||||
"public_ip": {
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"address": "11.22.222.222",
|
||||
"dynamic": false
|
||||
},
|
||||
"private_ip": "10.00.222.222",
|
||||
"ipv6": {
|
||||
"address": "2001:111:222:3333::1",
|
||||
"gateway": "2001:111:222:3333::",
|
||||
"netmask": "64"
|
||||
}
|
||||
}`)
|
||||
|
||||
metadata := &instance.Metadata{}
|
||||
err := json.Unmarshal(cfg, &metadata)
|
||||
suite.Require().NoError(err)
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
p := &scaleway.Scaleway{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
var m instance.Metadata
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
DeviceAddresses: []string{"2001:111:222:3333::1/64"},
|
||||
DeviceRoutes: []*v1alpha1.Route{
|
||||
{
|
||||
RouteNetwork: "::/0",
|
||||
RouteGateway: "2001:111:222:3333::",
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
result, err := p.ConfigurationNetwork(metadata, defaultMachineConfig)
|
||||
networkConfig, err := p.ParseMetadata(&m)
|
||||
require.NoError(t, err)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
43
internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/testdata/expected.yaml
vendored
Normal file
43
internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
addresses:
|
||||
- address: 2001:111:222:3333::1/64
|
||||
linkName: eth0
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet6
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: '2001:111:222:3333::'
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: scw-talos
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers: []
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs:
|
||||
- 11.22.222.222
|
19
internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/testdata/metadata.json
vendored
Normal file
19
internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway/testdata/metadata.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"name": "scw-talos",
|
||||
"commercial_type": "DEV1-S",
|
||||
"hostname": "scw-talos",
|
||||
"tags": [],
|
||||
"state_detail": "booted",
|
||||
"public_ip": {
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"address": "11.22.222.222",
|
||||
"dynamic": false
|
||||
},
|
||||
"private_ip": "10.00.222.222",
|
||||
"ipv6": {
|
||||
"address": "2001:111:222:3333::1",
|
||||
"gateway": "2001:111:222:3333::",
|
||||
"netmask": "64"
|
||||
}
|
||||
}
|
75
internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/testdata/expected.yaml
vendored
Normal file
75
internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
addresses:
|
||||
- address: 185.70.197.3/32
|
||||
linkName: eth0
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
- address: 2a04:3544:8000:1000:0:1111:2222:3333/64
|
||||
linkName: eth2
|
||||
family: inet6
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
- name: eth1
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
- name: eth2
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet6
|
||||
dst: 2a04:3544:8000:1000::/64
|
||||
src: ""
|
||||
gateway: 2a04:3544:8000:1000::1
|
||||
outLinkName: eth2
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: talos
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers:
|
||||
- dnsServers:
|
||||
- 94.237.127.9
|
||||
- 94.237.40.9
|
||||
- 2a04:3540:53::1
|
||||
- 2a04:3544:53::1
|
||||
layer: platform
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
- operator: dhcp4
|
||||
linkName: eth1
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs:
|
||||
- 185.70.197.2
|
83
internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/testdata/metadata.json
vendored
Normal file
83
internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/testdata/metadata.json
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"cloud_name": "upcloud",
|
||||
"instance_id": "00123456-1111-2222-3333-123456789012",
|
||||
"hostname": "talos",
|
||||
"network": {
|
||||
"interfaces": [
|
||||
{
|
||||
"index": 1,
|
||||
"ip_addresses": [
|
||||
{
|
||||
"address": "185.70.197.2",
|
||||
"dhcp": true,
|
||||
"dns": [
|
||||
"94.237.127.9",
|
||||
"94.237.40.9"
|
||||
],
|
||||
"family": "IPv4",
|
||||
"floating": false,
|
||||
"gateway": "185.70.196.1",
|
||||
"network": "185.70.196.0/22"
|
||||
},
|
||||
{
|
||||
"address": "185.70.197.3",
|
||||
"dhcp": false,
|
||||
"dns": null,
|
||||
"family": "IPv4",
|
||||
"floating": true,
|
||||
"gateway": "",
|
||||
"network": "185.70.197.3/32"
|
||||
}
|
||||
],
|
||||
"mac": "5e:bf:5e:02:28:07",
|
||||
"network_id": "035ef879-1111-2222-3333-123456789012",
|
||||
"type": "public"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"ip_addresses": [
|
||||
{
|
||||
"address": "10.11.0.2",
|
||||
"dhcp": true,
|
||||
"dns": null,
|
||||
"family": "IPv4",
|
||||
"floating": false,
|
||||
"gateway": "10.11.0.1",
|
||||
"network": "10.11.0.0/22"
|
||||
}
|
||||
],
|
||||
"mac": "5e:bf:5e:02:cd:e0",
|
||||
"network_id": "031c9f9c-1111-2222-3333-123456789012",
|
||||
"type": "utility"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"ip_addresses": [
|
||||
{
|
||||
"address": "2a04:3544:8000:1000:0000:1111:2222:3333",
|
||||
"dhcp": false,
|
||||
"dns": [
|
||||
"2a04:3540:53::1",
|
||||
"2a04:3544:53::1"
|
||||
],
|
||||
"family": "IPv6",
|
||||
"floating": false,
|
||||
"gateway": "2a04:3544:8000:1000::1",
|
||||
"network": "2a04:3544:8000:1000::/64"
|
||||
}
|
||||
],
|
||||
"mac": "5e:bf:5e:02:78:a4",
|
||||
"network_id": "03b326a2-1111-2222-3333-123456789012",
|
||||
"type": "public"
|
||||
}
|
||||
],
|
||||
"dns": [
|
||||
"94.237.127.9",
|
||||
"94.237.40.9"
|
||||
]
|
||||
},
|
||||
"storage": {},
|
||||
"tags": [],
|
||||
"user_data": "",
|
||||
"vendor_data": ""
|
||||
}
|
@ -9,28 +9,21 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
// UpCloudMetadataEndpoint is the local UpCloud endpoint.
|
||||
UpCloudMetadataEndpoint = "http://169.254.169.254/metadata/v1.json"
|
||||
|
||||
// UpCloudExternalIPEndpoint is the local UpCloud endpoint for the external IP.
|
||||
UpCloudExternalIPEndpoint = "http://169.254.169.254/metadata/v1/network/interfaces/1/ip_addresses/1/address"
|
||||
|
||||
// UpCloudHostnameEndpoint is the local UpCloud endpoint for the hostname.
|
||||
UpCloudHostnameEndpoint = "http://169.254.169.254/metadata/v1/hostname"
|
||||
|
||||
// UpCloudUserDataEndpoint is the local UpCloud endpoint for the config.
|
||||
UpCloudUserDataEndpoint = "http://169.254.169.254/metadata/v1/user_data"
|
||||
)
|
||||
@ -70,97 +63,143 @@ func (u *UpCloud) Name() string {
|
||||
return "upcloud"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
// ParseMetadata converts Upcloud metadata into platform network configuration.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (u *UpCloud) ConfigurationNetwork(metadataConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var machineConfig *v1alpha1.Config
|
||||
func (u *UpCloud) ParseMetadata(meta *MetaData) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
machineConfig, ok := confProvider.Raw().(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
if meta.Hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(meta.Hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
meta := &MetaData{}
|
||||
if err := json.Unmarshal(metadataConfig, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dnsIPs []netaddr.IP
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
firstIP := true
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
for _, addr := range meta.Network.Interfaces {
|
||||
if addr.Index <= 0 { // protect from negative interface name
|
||||
continue
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
|
||||
for _, addr := range meta.Network.Interfaces {
|
||||
if addr.Index <= 0 { // protect from negative interface name
|
||||
continue
|
||||
}
|
||||
iface := fmt.Sprintf("eth%d", addr.Index-1)
|
||||
|
||||
iface := &v1alpha1.Device{
|
||||
DeviceInterface: fmt.Sprintf("eth%d", addr.Index-1),
|
||||
}
|
||||
networkConfig.Links = append(networkConfig.Links, network.LinkSpecSpec{
|
||||
Name: iface,
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
|
||||
for _, ip := range addr.IPAddresses {
|
||||
if ip.DHCP && ip.Family == "IPv4" {
|
||||
iface.DeviceDHCP = true
|
||||
for _, ip := range addr.IPAddresses {
|
||||
if firstIP {
|
||||
ipAddr, err := netaddr.ParseIP(ip.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ip.DHCP {
|
||||
if ip.Floating {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, ip.Network)
|
||||
} else {
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses, ip.Address)
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ipAddr)
|
||||
|
||||
if ip.Gateway != "" {
|
||||
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
|
||||
RouteNetwork: ip.Network,
|
||||
RouteGateway: ip.Gateway,
|
||||
RouteMetric: 1024,
|
||||
})
|
||||
}
|
||||
firstIP = false
|
||||
}
|
||||
|
||||
for _, addr := range ip.DNS {
|
||||
if ipAddr, err := netaddr.ParseIP(addr); err == nil {
|
||||
dnsIPs = append(dnsIPs, ipAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if ip.DHCP && ip.Family == "IPv4" {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: iface,
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
if !ip.DHCP {
|
||||
ntwrk, err := netaddr.ParseIPPrefix(ip.Network)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, err := netaddr.ParseIP(ip.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipPrefix := netaddr.IPPrefixFrom(addr, ntwrk.Bits())
|
||||
|
||||
family := nethelpers.FamilyInet4
|
||||
if addr.Is6() {
|
||||
family = nethelpers.FamilyInet6
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: iface,
|
||||
Address: ipPrefix,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: family,
|
||||
},
|
||||
)
|
||||
|
||||
if ip.Gateway != "" {
|
||||
gw, err := netaddr.ParseIP(ip.Gateway)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gw,
|
||||
Destination: ntwrk,
|
||||
OutLinkName: iface,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: family,
|
||||
Priority: 1024,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
}
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, iface)
|
||||
}
|
||||
}
|
||||
|
||||
return machineConfig, nil
|
||||
if len(dnsIPs) > 0 {
|
||||
networkConfig.Resolvers = append(networkConfig.Resolvers, network.ResolverSpecSpec{
|
||||
DNSServers: dnsIPs,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
}
|
||||
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
func (u *UpCloud) Configuration(ctx context.Context) ([]byte, error) {
|
||||
log.Printf("fetching UpCloud instance config from: %q ", UpCloudMetadataEndpoint)
|
||||
|
||||
metaConfigDl, err := download.Download(ctx, UpCloudMetadataEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch network config from metadata service")
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", UpCloudUserDataEndpoint)
|
||||
|
||||
machineConfigDl, err := download.Download(ctx, UpCloudUserDataEndpoint,
|
||||
return download.Download(ctx, UpCloudUserDataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfigDl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err = u.ConfigurationNetwork(metaConfigDl, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
@ -168,37 +207,35 @@ func (u *UpCloud) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (u *UpCloud) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", UpCloudHostnameEndpoint)
|
||||
|
||||
host, err := download.Download(ctx, UpCloudHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (u *UpCloud) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
log.Printf("fetching external IP from: %q", UpCloudExternalIPEndpoint)
|
||||
|
||||
exIP, err := download.Download(ctx, UpCloudExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
addrs = append(addrs, net.ParseIP(string(exIP)))
|
||||
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (u *UpCloud) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (u *UpCloud) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching UpCloud instance config from: %q ", UpCloudMetadataEndpoint)
|
||||
|
||||
metaConfigDl, err := download.Download(ctx, UpCloudMetadataEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch network config from metadata service: %w", err)
|
||||
}
|
||||
|
||||
meta := &MetaData{}
|
||||
if err = json.Unmarshal(metaConfigDl, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig, err := u.ParseMetadata(meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,135 +5,35 @@
|
||||
package upcloud_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
//go:embed testdata/metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
cfg := []byte(`{
|
||||
"cloud_name": "upcloud",
|
||||
"instance_id": "00123456-1111-2222-3333-123456789012",
|
||||
"hostname": "talos",
|
||||
"network": {
|
||||
"interfaces": [
|
||||
{
|
||||
"index": 1,
|
||||
"ip_addresses": [
|
||||
{
|
||||
"address": "185.70.197.2",
|
||||
"dhcp": true,
|
||||
"dns": [
|
||||
"94.237.127.9",
|
||||
"94.237.40.9"
|
||||
],
|
||||
"family": "IPv4",
|
||||
"floating": false,
|
||||
"gateway": "185.70.196.1",
|
||||
"network": "185.70.196.0/22"
|
||||
},
|
||||
{
|
||||
"address": "185.70.197.3",
|
||||
"dhcp": false,
|
||||
"dns": null,
|
||||
"family": "IPv4",
|
||||
"floating": true,
|
||||
"gateway": "",
|
||||
"network": "185.70.197.3/32"
|
||||
}
|
||||
],
|
||||
"mac": "5e:bf:5e:02:28:07",
|
||||
"network_id": "035ef879-1111-2222-3333-123456789012",
|
||||
"type": "public"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"ip_addresses": [
|
||||
{
|
||||
"address": "10.11.0.2",
|
||||
"dhcp": true,
|
||||
"dns": null,
|
||||
"family": "IPv4",
|
||||
"floating": false,
|
||||
"gateway": "10.11.0.1",
|
||||
"network": "10.11.0.0/22"
|
||||
}
|
||||
],
|
||||
"mac": "5e:bf:5e:02:cd:e0",
|
||||
"network_id": "031c9f9c-1111-2222-3333-123456789012",
|
||||
"type": "utility"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"ip_addresses": [
|
||||
{
|
||||
"address": "2a04:3544:8000:1000:0000:1111:2222:3333",
|
||||
"dhcp": true,
|
||||
"dns": [
|
||||
"2a04:3540:53::1",
|
||||
"2a04:3544:53::1"
|
||||
],
|
||||
"family": "IPv6",
|
||||
"floating": false,
|
||||
"gateway": "2a04:3544:8000:1000::1",
|
||||
"network": "2a04:3544:8000:1000::/64"
|
||||
}
|
||||
],
|
||||
"mac": "5e:bf:5e:02:78:a4",
|
||||
"network_id": "03b326a2-1111-2222-3333-123456789012",
|
||||
"type": "public"
|
||||
}
|
||||
],
|
||||
"dns": [
|
||||
"94.237.127.9",
|
||||
"94.237.40.9"
|
||||
]
|
||||
},
|
||||
"storage": {},
|
||||
"tags": [],
|
||||
"user_data": "",
|
||||
"vendor_data": ""
|
||||
}`)
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
p := &upcloud.UpCloud{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
var m upcloud.MetaData
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceAddresses: []string{"185.70.197.3/32"},
|
||||
DeviceDHCP: true,
|
||||
},
|
||||
{
|
||||
DeviceInterface: "eth1",
|
||||
DeviceDHCP: true,
|
||||
},
|
||||
{
|
||||
DeviceInterface: "eth2",
|
||||
DeviceDHCP: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
result, err := p.ConfigurationNetwork(cfg, defaultMachineConfig)
|
||||
networkConfig, err := p.ParseMetadata(&m)
|
||||
require.NoError(t, err)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/vmware/govmomi/ovf"
|
||||
@ -186,21 +185,11 @@ func (v *VMware) Configuration(context.Context) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (v *VMware) Hostname(context.Context) (hostname []byte, err error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
func (v *VMware) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (v *VMware) ExternalIPs(context.Context) (addrs []net.IP, err error) {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (v *VMware) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
@ -208,3 +197,8 @@ func (v *VMware) KernelArgs() procfs.Parameters {
|
||||
procfs.NewParameter("earlyprintk").Append("ttyS0,115200"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (v *VMware) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ package vmware
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
|
||||
@ -30,22 +29,17 @@ func (v *VMware) Configuration(context.Context) ([]byte, error) {
|
||||
return nil, fmt.Errorf("arch not supported")
|
||||
}
|
||||
|
||||
// Hostname implements the platform.Platform interface.
|
||||
func (v *VMware) Hostname(context.Context) (hostname []byte, err error) {
|
||||
return nil, fmt.Errorf("arch not supported")
|
||||
}
|
||||
|
||||
// Mode implements the platform.Platform interface.
|
||||
func (v *VMware) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (v *VMware) ExternalIPs(context.Context) (addrs []net.IP, err error) {
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (v *VMware) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (v *VMware) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
38
internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/testdata/expected.yaml
vendored
Normal file
38
internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
addresses:
|
||||
- address: 10.7.96.0/20
|
||||
linkName: eth1
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
- name: eth1
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 1450
|
||||
kind: ""
|
||||
type: netrom
|
||||
layer: platform
|
||||
routes: []
|
||||
hostnames:
|
||||
- hostname: talos
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers: []
|
||||
timeServers: []
|
||||
operators:
|
||||
- operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
layer: platform
|
||||
externalIPs:
|
||||
- 1.2.3.4
|
61
internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/testdata/metadata.json
vendored
Normal file
61
internal/app/machined/pkg/runtime/v1alpha1/platform/vultr/testdata/metadata.json
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"bgp": {
|
||||
"ipv4": {
|
||||
"my-address": "",
|
||||
"my-asn": "",
|
||||
"peer-address": "",
|
||||
"peer-asn": ""
|
||||
},
|
||||
"ipv6": {
|
||||
"my-address": "",
|
||||
"my-asn": "",
|
||||
"peer-address": "",
|
||||
"peer-asn": ""
|
||||
}
|
||||
},
|
||||
"hostname": "talos",
|
||||
"instance-v2-id": "91b07056-af72-4551-b15b-d57d34071be9",
|
||||
"instanceid": "50190000",
|
||||
"interfaces": [
|
||||
{
|
||||
"ipv4": {
|
||||
"additional": [],
|
||||
"address": "95.111.222.111",
|
||||
"gateway": "95.111.222.1",
|
||||
"netmask": "255.255.254.0"
|
||||
},
|
||||
"ipv6": {
|
||||
"additional": [],
|
||||
"address": "2001:19f0:5001:2095:1111:2222:3333:4444",
|
||||
"network": "2001:19f0:5001:2095::",
|
||||
"prefix": "64"
|
||||
},
|
||||
"mac": "56:00:03:89:53:e0",
|
||||
"network-type": "public"
|
||||
},
|
||||
{
|
||||
"ipv4": {
|
||||
"additional": [],
|
||||
"address": "10.7.96.3",
|
||||
"gateway": "",
|
||||
"netmask": "255.255.240.0"
|
||||
},
|
||||
"ipv6": {
|
||||
"additional": [],
|
||||
"network": "",
|
||||
"prefix": ""
|
||||
},
|
||||
"mac": "5a:00:03:89:53:e0",
|
||||
"network-type": "private",
|
||||
"network-v2-id": "dadc2b30-0b55-4fa1-8c29-f67215bd5ac4",
|
||||
"networkid": "net6126811851cd7"
|
||||
}
|
||||
],
|
||||
"public-keys": [
|
||||
"ssh-ed25519"
|
||||
],
|
||||
"region": {
|
||||
"regioncode": "AMS"
|
||||
},
|
||||
"user-defined": []
|
||||
}
|
@ -7,19 +7,19 @@ package vultr
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/talos-systems/go-procfs/procfs"
|
||||
"github.com/vultr/metadata"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
"github.com/talos-systems/talos/pkg/download"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -41,87 +41,96 @@ func (v *Vultr) Name() string {
|
||||
return "vultr"
|
||||
}
|
||||
|
||||
// ConfigurationNetwork implements the network configuration interface.
|
||||
func (v *Vultr) ConfigurationNetwork(metadataConfig []byte, confProvider config.Provider) (config.Provider, error) {
|
||||
var machineConfig *v1alpha1.Config
|
||||
// ParseMetadata converts Vultr platform metadata into platform network config.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (v *Vultr) ParseMetadata(meta *metadata.MetaData, extIP []byte) (*runtime.PlatformNetworkConfig, error) {
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
machineConfig, ok := confProvider.(*v1alpha1.Config)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unable to determine machine config type")
|
||||
if ip, err := netaddr.ParseIP(string(extIP)); err == nil {
|
||||
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
|
||||
}
|
||||
|
||||
meta := &metadata.MetaData{}
|
||||
if err := json.Unmarshal(metadataConfig, meta); err != nil {
|
||||
return nil, err
|
||||
if meta.Hostname != "" {
|
||||
hostnameSpec := network.HostnameSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if err := hostnameSpec.ParseFQDN(meta.Hostname); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig == nil {
|
||||
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
|
||||
}
|
||||
for i, addr := range meta.Interfaces {
|
||||
iface := fmt.Sprintf("eth%d", i)
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork == nil {
|
||||
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
|
||||
}
|
||||
link := network.LinkSpecSpec{
|
||||
Name: iface,
|
||||
Up: true,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
}
|
||||
|
||||
if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
|
||||
for i, addr := range meta.Interfaces {
|
||||
iface := &v1alpha1.Device{
|
||||
DeviceInterface: fmt.Sprintf("eth%d", i),
|
||||
}
|
||||
if addr.NetworkType == "private" {
|
||||
link.MTU = 1450
|
||||
}
|
||||
|
||||
if addr.IPv4.Address != "" {
|
||||
iface.DeviceDHCP = true
|
||||
}
|
||||
networkConfig.Links = append(networkConfig.Links, link)
|
||||
|
||||
if addr.NetworkType == "private" {
|
||||
iface.DeviceMTU = 1450
|
||||
|
||||
if addr.IPv4.Address != "" {
|
||||
mask, _ := net.IPMask(net.ParseIP(addr.IPv4.Netmask).To4()).Size()
|
||||
|
||||
iface.DeviceDHCP = false
|
||||
iface.DeviceAddresses = append(iface.DeviceAddresses,
|
||||
fmt.Sprintf("%s/%d", addr.IPv4.Address, mask),
|
||||
)
|
||||
if addr.IPv4.Address != "" {
|
||||
if addr.NetworkType != "private" {
|
||||
networkConfig.Operators = append(networkConfig.Operators, network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: iface,
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
})
|
||||
} else {
|
||||
maskIP, err := netaddr.ParseIP(addr.IPv4.Netmask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, iface)
|
||||
mask, _ := maskIP.MarshalBinary() //nolint:errcheck // never fails
|
||||
|
||||
ip, err := netaddr.ParseIP(addr.IPv4.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipAddr, err := ip.Netmask(mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
LinkName: iface,
|
||||
Address: ipAddr,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
Family: nethelpers.FamilyInet4,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return confProvider, nil
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
func (v *Vultr) Configuration(ctx context.Context) ([]byte, error) {
|
||||
log.Printf("fetching Vultr instance config from: %q ", VultrMetadataEndpoint)
|
||||
|
||||
metaConfigDl, err := download.Download(ctx, VultrMetadataEndpoint)
|
||||
if err != nil {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("fetching machine config from: %q", VultrUserDataEndpoint)
|
||||
|
||||
machineConfigDl, err := download.Download(ctx, VultrUserDataEndpoint,
|
||||
return download.Download(ctx, VultrUserDataEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err := configloader.NewFromBytes(machineConfigDl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confProvider, err = v.ConfigurationNetwork(metaConfigDl, confProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return confProvider.Bytes()
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
@ -129,39 +138,42 @@ func (v *Vultr) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// Hostname implements the runtime.Platform interface.
|
||||
func (v *Vultr) Hostname(ctx context.Context) (hostname []byte, err error) {
|
||||
log.Printf("fetching hostname from: %q", VultrHostnameEndpoint)
|
||||
|
||||
hostname, err = download.Download(ctx, VultrHostnameEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoHostname),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return hostname, nil
|
||||
}
|
||||
|
||||
// ExternalIPs implements the runtime.Platform interface.
|
||||
func (v *Vultr) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
|
||||
log.Printf("fetching external IP from: %q", VultrExternalIPEndpoint)
|
||||
|
||||
exIP, err := download.Download(ctx, VultrExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if addr := net.ParseIP(string(exIP)); addr != nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
|
||||
return addrs, err
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (v *Vultr) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (v *Vultr) NetworkConfiguration(ctx context.Context, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
log.Printf("fetching Vultr instance config from: %q ", VultrMetadataEndpoint)
|
||||
|
||||
metaConfigDl, err := download.Download(ctx, VultrMetadataEndpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching metadata: %w", err)
|
||||
}
|
||||
|
||||
meta := &metadata.MetaData{}
|
||||
if err = json.Unmarshal(metaConfigDl, meta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
extIP, err := download.Download(ctx, VultrExternalIPEndpoint,
|
||||
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
|
||||
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
|
||||
if err != nil && !stderrors.Is(err, errors.ErrNoExternalIPs) {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig, err := v.ParseMetadata(meta, extIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,53 +5,36 @@
|
||||
package vultr_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vultr/metadata"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/vultr"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
)
|
||||
|
||||
type ConfigSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
//go:embed testdata/metadata.json
|
||||
var rawMetadata []byte
|
||||
|
||||
func (suite *ConfigSuite) TestNetworkConfig() {
|
||||
//nolint:lll
|
||||
cfg := []byte(`{
|
||||
"bgp":{"ipv4":{"my-address":"","my-asn":"","peer-address":"","peer-asn":""},"ipv6":{"my-address":"","my-asn":"","peer-address":"","peer-asn":""}},"hostname":"talos","instance-v2-id":"91b07056-af72-4551-b15b-d57d34071be9","instanceid":"50190000","interfaces":[{"ipv4":{"additional":[],"address":"95.111.222.111","gateway":"95.111.222.1","netmask":"255.255.254.0"},"ipv6":{"additional":[],"address":"2001:19f0:5001:2095:1111:2222:3333:4444","network":"2001:19f0:5001:2095::","prefix":"64"},"mac":"56:00:03:89:53:e0","network-type":"public"},{"ipv4":{"additional":[],"address":"10.7.96.3","gateway":"","netmask":"255.255.240.0"},"ipv6":{"additional":[],"network":"","prefix":""},"mac":"5a:00:03:89:53:e0","network-type":"private","network-v2-id":"dadc2b30-0b55-4fa1-8c29-f67215bd5ac4","networkid":"net6126811851cd7"}],"public-keys":["ssh-ed25519"],"region":{"regioncode":"AMS"},"user-defined":[]
|
||||
}`)
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
p := &vultr.Vultr{}
|
||||
|
||||
defaultMachineConfig := &v1alpha1.Config{}
|
||||
var m metadata.MetaData
|
||||
|
||||
machineConfig := &v1alpha1.Config{
|
||||
MachineConfig: &v1alpha1.MachineConfig{
|
||||
MachineNetwork: &v1alpha1.NetworkConfig{
|
||||
NetworkInterfaces: []*v1alpha1.Device{
|
||||
{
|
||||
DeviceInterface: "eth0",
|
||||
DeviceDHCP: true,
|
||||
},
|
||||
{
|
||||
DeviceInterface: "eth1",
|
||||
DeviceAddresses: []string{"10.7.96.3/20"},
|
||||
DeviceDHCP: false,
|
||||
DeviceMTU: 1450,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rawMetadata, &m))
|
||||
|
||||
result, err := p.ConfigurationNetwork(cfg, defaultMachineConfig)
|
||||
networkConfig, err := p.ParseMetadata(&m, []byte("1.2.3.4"))
|
||||
require.NoError(t, err)
|
||||
|
||||
suite.Require().NoError(err)
|
||||
suite.Assert().Equal(machineConfig, result)
|
||||
}
|
||||
|
||||
func TestConfigSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigSuite))
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
||||
|
@ -154,6 +154,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
|
||||
&network.OperatorConfigController{
|
||||
Cmdline: procfs.ProcCmdline(),
|
||||
},
|
||||
&network.OperatorMergeController{},
|
||||
&network.OperatorSpecController{
|
||||
V1alpha1Platform: ctrl.v1alpha1Runtime.State().Platform(),
|
||||
State: ctrl.v1alpha1Runtime.State().V1Alpha2().Resources(),
|
||||
|
@ -1,16 +0,0 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
// DynamicConfigProvider provides additional configuration which is overlaid on top of existing configuration.
|
||||
type DynamicConfigProvider interface {
|
||||
Hostname(context.Context) ([]byte, error)
|
||||
ExternalIPs(context.Context) ([]net.IP, error)
|
||||
}
|
@ -568,6 +568,9 @@ const (
|
||||
|
||||
// SideroLinkDefaultPeerKeepalive is the interval at which Wireguard Peer Keepalives should be sent.
|
||||
SideroLinkDefaultPeerKeepalive = 25 * time.Second
|
||||
|
||||
// PlatformNetworkConfigFilename is the filename to cache platform network configuration reboots.
|
||||
PlatformNetworkConfigFilename = "platform-network.yaml"
|
||||
)
|
||||
|
||||
// See https://linux.die.net/man/3/klogctl
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
package network
|
||||
|
||||
//go:generate stringer -type=Operator -linecomment
|
||||
//go:generate enumer -type=Operator -linecomment -text
|
||||
|
||||
// Operator enumerates Talos network operators.
|
||||
type Operator int
|
||||
@ -15,8 +15,3 @@ const (
|
||||
OperatorDHCP6 // dhcp6
|
||||
OperatorVIP // vip
|
||||
)
|
||||
|
||||
// MarshalYAML implements yaml.Marshaler.
|
||||
func (operator Operator) MarshalYAML() (interface{}, error) {
|
||||
return operator.String(), nil
|
||||
}
|
||||
|
63
pkg/machinery/resources/network/operator_enumer.go
Normal file
63
pkg/machinery/resources/network/operator_enumer.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Code generated by "enumer -type=Operator -linecomment -text"; DO NOT EDIT.
|
||||
|
||||
//
|
||||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const _OperatorName = "dhcp4dhcp6vip"
|
||||
|
||||
var _OperatorIndex = [...]uint8{0, 5, 10, 13}
|
||||
|
||||
func (i Operator) String() string {
|
||||
if i < 0 || i >= Operator(len(_OperatorIndex)-1) {
|
||||
return fmt.Sprintf("Operator(%d)", i)
|
||||
}
|
||||
return _OperatorName[_OperatorIndex[i]:_OperatorIndex[i+1]]
|
||||
}
|
||||
|
||||
var _OperatorValues = []Operator{0, 1, 2}
|
||||
|
||||
var _OperatorNameToValueMap = map[string]Operator{
|
||||
_OperatorName[0:5]: 0,
|
||||
_OperatorName[5:10]: 1,
|
||||
_OperatorName[10:13]: 2,
|
||||
}
|
||||
|
||||
// OperatorString retrieves an enum value from the enum constants string name.
|
||||
// Throws an error if the param is not part of the enum.
|
||||
func OperatorString(s string) (Operator, error) {
|
||||
if val, ok := _OperatorNameToValueMap[s]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return 0, fmt.Errorf("%s does not belong to Operator values", s)
|
||||
}
|
||||
|
||||
// OperatorValues returns all values of the enum
|
||||
func OperatorValues() []Operator {
|
||||
return _OperatorValues
|
||||
}
|
||||
|
||||
// IsAOperator returns "true" if the value is listed in the enum definition. "false" otherwise
|
||||
func (i Operator) IsAOperator() bool {
|
||||
for _, v := range _OperatorValues {
|
||||
if i == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface for Operator
|
||||
func (i Operator) MarshalText() ([]byte, error) {
|
||||
return []byte(i.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface for Operator
|
||||
func (i *Operator) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
*i, err = OperatorString(string(text))
|
||||
return err
|
||||
}
|
@ -30,6 +30,8 @@ type OperatorSpecSpec struct {
|
||||
DHCP4 DHCP4OperatorSpec `yaml:"dhcp4,omitempty"`
|
||||
DHCP6 DHCP6OperatorSpec `yaml:"dhcp6,omitempty"`
|
||||
VIP VIPOperatorSpec `yaml:"vip,omitempty"`
|
||||
|
||||
ConfigLayer ConfigLayer `yaml:"layer"`
|
||||
}
|
||||
|
||||
// DHCP4OperatorSpec describes DHCP4 operator options.
|
||||
|
78
pkg/machinery/resources/network/operator_spec_test.go
Normal file
78
pkg/machinery/resources/network/operator_spec_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package network_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
"inet.af/netaddr"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/resources/network"
|
||||
)
|
||||
|
||||
func TestOperatorSpecMarshalYAML(t *testing.T) {
|
||||
spec := network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: "eth0",
|
||||
RequireUp: true,
|
||||
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
DHCP6: network.DHCP6OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
},
|
||||
VIP: network.VIPOperatorSpec{
|
||||
IP: netaddr.MustParseIP("192.168.1.1"),
|
||||
GratuitousARP: true,
|
||||
EquinixMetal: network.VIPEquinixMetalSpec{
|
||||
ProjectID: "a",
|
||||
DeviceID: "b",
|
||||
APIToken: "c",
|
||||
},
|
||||
HCloud: network.VIPHCloudSpec{
|
||||
DeviceID: 3,
|
||||
NetworkID: 4,
|
||||
APIToken: "d",
|
||||
},
|
||||
},
|
||||
ConfigLayer: network.ConfigMachineConfiguration,
|
||||
}
|
||||
|
||||
marshaled, err := yaml.Marshal(spec)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
`operator: dhcp4
|
||||
linkName: eth0
|
||||
requireUp: true
|
||||
dhcp4:
|
||||
routeMetric: 1024
|
||||
dhcp6:
|
||||
routeMetric: 1024
|
||||
vip:
|
||||
ip: 192.168.1.1
|
||||
gratuitousARP: true
|
||||
equinixMetal:
|
||||
projectID: a
|
||||
deviceID: b
|
||||
apiToken: c
|
||||
hcloud:
|
||||
deviceID: 3
|
||||
networkID: 4
|
||||
apiToken: d
|
||||
layer: configuration
|
||||
`,
|
||||
string(marshaled))
|
||||
|
||||
var spec2 network.OperatorSpecSpec
|
||||
|
||||
require.NoError(t, yaml.Unmarshal(marshaled, &spec2))
|
||||
|
||||
assert.Equal(t, spec, spec2)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Code generated by "stringer -type=Operator -linecomment"; DO NOT EDIT.
|
||||
|
||||
package network
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[OperatorDHCP4-0]
|
||||
_ = x[OperatorDHCP6-1]
|
||||
_ = x[OperatorVIP-2]
|
||||
}
|
||||
|
||||
const _Operator_name = "dhcp4dhcp6vip"
|
||||
|
||||
var _Operator_index = [...]uint8{0, 5, 10, 13}
|
||||
|
||||
func (i Operator) String() string {
|
||||
if i < 0 || i >= Operator(len(_Operator_index)-1) {
|
||||
return "Operator(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Operator_name[_Operator_index[i]:_Operator_index[i+1]]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user