Skip to content

Commit

Permalink
feat: provide platform network config for 'metal' in META
Browse files Browse the repository at this point in the history
A special META key might contain optional platform network config for
the `METAL` platform.

It is completely optional, but if present, it works same way as in the
clouds: it is applied with low priority (can be overridden with machine
config), but provides some initial defaults for the machine.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira committed Mar 15, 2023
1 parent 442cb9c commit 64e3d24
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,6 @@ func (ctrl *PlatformConfigController) apply(ctx context.Context, r controller.Ru
},
},
} {
if specType.length == 0 {
continue
}

touchedIDs := make(map[resource.ID]struct{}, specType.length)

resourceEmpty := specType.resourceBuilder("")
Expand Down
74 changes: 61 additions & 13 deletions internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ import (
"path/filepath"

"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/gen/channel"
"github.com/siderolabs/go-blockdevice/blockdevice/filesystem"
"github.com/siderolabs/go-blockdevice/blockdevice/probe"
"github.com/siderolabs/go-procfs/procfs"
"golang.org/x/sys/unix"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/netutils"
"github.com/siderolabs/talos/internal/pkg/meta"
"github.com/siderolabs/talos/pkg/download"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)

Expand Down Expand Up @@ -119,28 +123,72 @@ func (m *Metal) KernelArgs() procfs.Parameters {
}

// NetworkConfiguration implements the runtime.Platform interface.
//
//nolint:gocyclo
func (m *Metal) NetworkConfiguration(ctx context.Context, st state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
var metadata runtimeres.PlatformMetadataSpec
ctx, cancel := context.WithCancel(ctx)
defer cancel()

watchCh := make(chan state.Event)

if err := st.Watch(ctx, hardware.NewSystemInformation(hardware.SystemInformationID).Metadata(), watchCh); err != nil {
return err
}

if err := st.Watch(ctx, runtimeres.NewMetaKey(runtimeres.NamespaceName, runtimeres.MetaKeyTagToID(meta.MetalNetworkPlatformConfig)).Metadata(), watchCh); err != nil {
return err
}

// network config from META partition
var metaCfg runtime.PlatformNetworkConfig

// fixed metadata filled by this function
metadata := &runtimeres.PlatformMetadataSpec{}
metadata.Platform = m.Name()

if option := procfs.ProcCmdline().Get(constants.KernelParamHostname).First(); option != nil {
metadata.Hostname = *option
}

if uuid, err := getSystemUUID(ctx, st); err == nil {
metadata.InstanceID = uuid
}
for {
var event state.Event

networkConfig := &runtime.PlatformNetworkConfig{
Metadata: &metadata,
}
select {
case <-ctx.Done():
return ctx.Err()
case event = <-watchCh:
}

select {
case <-ctx.Done():
return ctx.Err()
case ch <- networkConfig:
}
switch event.Type {
case state.Errored:
return fmt.Errorf("watch failed: %w", event.Error)
case state.Bootstrapped:
// ignored, should not happen
case state.Created, state.Updated:
switch r := event.Resource.(type) {
case *hardware.SystemInformation:
metadata.InstanceID = r.TypedSpec().UUID
case *runtimeres.MetaKey:
metaCfg = runtime.PlatformNetworkConfig{}

if err := yaml.Unmarshal([]byte(r.TypedSpec().Value), &metaCfg); err != nil {
return fmt.Errorf("failed to unmarshal metal network config from META: %w", err)
}
}
case state.Destroyed:
switch event.Resource.(type) {
case *hardware.SystemInformation:
metadata.InstanceID = ""
case *runtimeres.MetaKey:
metaCfg = runtime.PlatformNetworkConfig{}
}
}

return nil
cfg := metaCfg
cfg.Metadata = metadata

if !channel.SendWithContext(ctx, ch, &cfg) {
return ctx.Err()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package metal_test

import (
"context"
"fmt"
"testing"
"time"

Expand All @@ -17,9 +18,12 @@ import (

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal"
"github.com/siderolabs/talos/internal/pkg/meta"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)

//nolint:gocyclo
func TestNetworkConfig(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
t.Cleanup(cancel)
Expand All @@ -30,18 +34,86 @@ func TestNetworkConfig(t *testing.T) {

st := state.WrapCore(namespaced.NewState(inmem.Build))

uuid := hardware.NewSystemInformation("test")
uuid := hardware.NewSystemInformation(hardware.SystemInformationID)
uuid.TypedSpec().UUID = "0123-4567-89ab-cdef"
require.NoError(t, st.Create(ctx, uuid))

err := p.NetworkConfiguration(ctx, st, ch)
require.NoError(t, err)
errCh := make(chan error)

go func() {
errCh <- p.NetworkConfiguration(ctx, st, ch)
}()

// platform might see updates coming in different order, so we need to wait a bit for the final state
outerLoop:
for {
select {
case <-ctx.Done():
require.FailNow(t, "timed out waiting for network config")
case cfg := <-ch:
assert.Equal(t, "metal", cfg.Metadata.Platform)

if cfg.Metadata.InstanceID == "" {
continue
}

assert.Equal(t, uuid.TypedSpec().UUID, cfg.Metadata.InstanceID)

break outerLoop
}
}

metaKey := runtimeres.NewMetaKey(runtimeres.NamespaceName, runtimeres.MetaKeyTagToID(meta.MetalNetworkPlatformConfig))
metaKey.TypedSpec().Value = `{"externalIPs": ["1.2.3.4"]}`
require.NoError(t, st.Create(ctx, metaKey))

// platform might see updates coming in different order, so we need to wait a bit for the final state
outerLoop2:
for {
select {
case <-ctx.Done():
require.FailNow(t, "timed out waiting for network config")
case cfg := <-ch:
assert.Equal(t, "metal", cfg.Metadata.Platform)
assert.Equal(t, uuid.TypedSpec().UUID, cfg.Metadata.InstanceID)

if len(cfg.ExternalIPs) == 0 {
continue
}

assert.Equal(t, "[1.2.3.4]", fmt.Sprintf("%v", cfg.ExternalIPs))

break outerLoop2
}
}

metaKey.TypedSpec().Value = `{"hostnames": [{"hostname": "talos", "domainname": "fqdn", "layer": "platform"}]}`
require.NoError(t, st.Update(ctx, metaKey))

select {
case <-ctx.Done():
require.FailNow(t, "timed out waiting for network config")
case cfg := <-ch:
assert.Equal(t, "metal", cfg.Metadata.Platform)
assert.Equal(t, uuid.TypedSpec().UUID, cfg.Metadata.InstanceID)

assert.Equal(t, "[]", fmt.Sprintf("%v", cfg.ExternalIPs))
assert.Equal(t, "[{talos fqdn platform}]", fmt.Sprintf("%v", cfg.Hostnames))
}

require.NoError(t, st.Destroy(ctx, metaKey.Metadata()))

select {
case <-ctx.Done():
t.Error("timeout")
require.FailNow(t, "timed out waiting for network config")
case cfg := <-ch:
assert.Equal(t, "metal", cfg.Metadata.Platform)
assert.Equal(t, uuid.TypedSpec().UUID, cfg.Metadata.InstanceID)

assert.Equal(t, "[]", fmt.Sprintf("%v", cfg.ExternalIPs))
assert.Equal(t, "[]", fmt.Sprintf("%v", cfg.Hostnames))
}

cancel()
require.ErrorIs(t, <-errCh, context.Canceled)
}
2 changes: 2 additions & 0 deletions internal/pkg/meta/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ const (
StagedUpgradeInstallOptions
// StateEncryptionConfig stores JSON-serialized v1alpha1.Encryption.
StateEncryptionConfig
// MetalNetworkPlatformConfig stores serialized NetworkPlatformConfig for the `metal` platform.
MetalNetworkPlatformConfig
)
12 changes: 4 additions & 8 deletions internal/pkg/meta/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (meta *Meta) syncState(ctx context.Context) error {
existingTags := make(map[resource.ID]struct{})

for _, t := range meta.talos.ListTags() {
existingTags[tagToID(t)] = struct{}{}
existingTags[runtime.MetaKeyTagToID(t)] = struct{}{}
val, _ := meta.talos.ReadTag(t)

if err := updateTagResource(ctx, meta.state, t, val); err != nil {
Expand Down Expand Up @@ -303,24 +303,20 @@ func (meta *Meta) DeleteTag(ctx context.Context, t uint8) (bool, error) {
return ok, nil
}

err := meta.state.Destroy(ctx, runtime.NewMetaKey(runtime.NamespaceName, tagToID(t)).Metadata()) //nolint:errcheck
err := meta.state.Destroy(ctx, runtime.NewMetaKey(runtime.NamespaceName, runtime.MetaKeyTagToID(t)).Metadata()) //nolint:errcheck
if state.IsNotFoundError(err) {
err = nil
}

return ok, err
}

func tagToID(t uint8) resource.ID {
return fmt.Sprintf("0x%02x", t)
}

func updateTagResource(ctx context.Context, st state.State, t uint8, val string) error {
if st == nil {
return nil
}

_, err := safe.StateUpdateWithConflicts(ctx, st, runtime.NewMetaKey(runtime.NamespaceName, tagToID(t)).Metadata(), func(r *runtime.MetaKey) error {
_, err := safe.StateUpdateWithConflicts(ctx, st, runtime.NewMetaKey(runtime.NamespaceName, runtime.MetaKeyTagToID(t)).Metadata(), func(r *runtime.MetaKey) error {
r.TypedSpec().Value = val

return nil
Expand All @@ -331,7 +327,7 @@ func updateTagResource(ctx context.Context, st state.State, t uint8, val string)
}

if state.IsNotFoundError(err) {
r := runtime.NewMetaKey(runtime.NamespaceName, tagToID(t))
r := runtime.NewMetaKey(runtime.NamespaceName, runtime.MetaKeyTagToID(t))
r.TypedSpec().Value = val

return st.Create(ctx, r)
Expand Down
7 changes: 7 additions & 0 deletions pkg/machinery/resources/runtime/meta_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package runtime

import (
"fmt"

"github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/resource/protobuf"
Expand Down Expand Up @@ -52,6 +54,11 @@ func (MetaKeyExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
}
}

// MetaKeyTagToID converts a tag to ID for MetaKey resource.
func MetaKeyTagToID(t uint8) resource.ID {
return fmt.Sprintf("0x%02x", t)
}

func init() {
proto.RegisterDefaultTypes()

Expand Down

0 comments on commit 64e3d24

Please sign in to comment.