Skip to content

Commit

Permalink
feat: implement APIs to write to META
Browse files Browse the repository at this point in the history
This allows to put keys to META partition.

META contents can be viewed with `talosctl get metakeys`.

There is not real usecase for it yet, but the next PRs will introduce
two special keys which can be written:

* platform network config for `metal`
* `${code}` variable

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira committed Mar 15, 2023
1 parent 9e07832 commit 442cb9c
Show file tree
Hide file tree
Showing 12 changed files with 2,424 additions and 570 deletions.
30 changes: 30 additions & 0 deletions api/machine/machine.proto
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ service MachineService {
rpc GenerateClientConfiguration(GenerateClientConfigurationRequest) returns (GenerateClientConfigurationResponse);
// PacketCapture performs packet capture and streams back pcap file.
rpc PacketCapture(PacketCaptureRequest) returns (stream common.Data);
// Netstat provides information about network connections.
rpc Netstat(NetstatRequest) returns (NetstatResponse);
// MetaWrite writes a META key-value pair.
rpc MetaWrite(MetaWriteRequest) returns (MetaWriteResponse);
// MetaDelete deletes a META key.
rpc MetaDelete(MetaDeleteRequest) returns (MetaDeleteResponse);
}

// rpc applyConfiguration
Expand Down Expand Up @@ -1273,3 +1278,28 @@ message Netstat {
message NetstatResponse {
repeated Netstat messages = 1;
}

message MetaWriteRequest {
uint32 key = 1;
bytes value = 2;
}

message MetaWrite {
common.Metadata metadata = 1;
}

message MetaWriteResponse {
repeated MetaWrite messages = 1;
}

message MetaDeleteRequest {
uint32 key = 1;
}

message MetaDelete {
common.Metadata metadata = 1;
}

message MetaDeleteResponse {
repeated MetaDelete messages = 1;
}
61 changes: 61 additions & 0 deletions cmd/talosctl/cmd/talos/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 talos

import (
"context"
"strconv"

"github.com/spf13/cobra"

"github.com/siderolabs/talos/pkg/machinery/client"
)

var metaCmd = &cobra.Command{
Use: "meta",
Short: "Write and delete keys in the META partition",
Long: ``,
Args: cobra.NoArgs,
}

var metaWriteCmd = &cobra.Command{
Use: "write key value",
Short: "Write a key-value pair to the META partition.",
Long: ``,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return WithClient(func(ctx context.Context, c *client.Client) error {
key, err := strconv.ParseUint(args[0], 0, 8)
if err != nil {
return err
}

return c.MetaWrite(ctx, uint8(key), []byte(args[1]))
})
},
}

var metaDeleteCmd = &cobra.Command{
Use: "delete key",
Short: "Delete a key from the META partition.",
Long: ``,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return WithClient(func(ctx context.Context, c *client.Client) error {
key, err := strconv.ParseUint(args[0], 0, 8)
if err != nil {
return err
}

return c.MetaDelete(ctx, uint8(key))
})
},
}

func init() {
metaCmd.AddCommand(metaWriteCmd)
metaCmd.AddCommand(metaDeleteCmd)
addCommand(metaCmd)
}
82 changes: 82 additions & 0 deletions internal/app/machined/internal/server/v1alpha1/v1alpha1_meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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

import (
"context"
"os"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/pkg/machinery/api/machine"
)

// MetaWrite implements the machine.MachineServer interface.
func (s *Server) MetaWrite(ctx context.Context, req *machine.MetaWriteRequest) (*machine.MetaWriteResponse, error) {
if err := s.checkSupported(runtime.MetaKV); err != nil {
return nil, err
}

if uint32(uint8(req.Key)) != req.Key {
return nil, status.Errorf(codes.InvalidArgument, "key must be a uint8")
}

ok, err := s.Controller.Runtime().State().Machine().Meta().SetTagBytes(ctx, uint8(req.Key), req.Value)
if err != nil {
return nil, err
}

if !ok {
// META overflowed
return nil, status.Errorf(codes.ResourceExhausted, "meta write failed")
}

err = s.Controller.Runtime().State().Machine().Meta().Flush()
if err != nil && !os.IsNotExist(err) {
// ignore not exist error, as it's possible that the meta partition is not created yet
return nil, err
}

return &machine.MetaWriteResponse{
Messages: []*machine.MetaWrite{
{},
},
}, nil
}

// MetaDelete implements the machine.MachineServer interface.
func (s *Server) MetaDelete(ctx context.Context, req *machine.MetaDeleteRequest) (*machine.MetaDeleteResponse, error) {
if err := s.checkSupported(runtime.MetaKV); err != nil {
return nil, err
}

if uint32(uint8(req.Key)) != req.Key {
return nil, status.Errorf(codes.InvalidArgument, "key must be a uint8")
}

ok, err := s.Controller.Runtime().State().Machine().Meta().DeleteTag(ctx, uint8(req.Key))
if err != nil {
return nil, err
}

if !ok {
// META key not found
return nil, status.Errorf(codes.NotFound, "meta key not found")
}

err = s.Controller.Runtime().State().Machine().Meta().Flush()
if err != nil && !os.IsNotExist(err) {
// ignore not exist error, as it's possible that the meta partition is not created yet
return nil, err
}

return &machine.MetaDeleteResponse{
Messages: []*machine.MetaDelete{
{},
},
}, nil
}
4 changes: 3 additions & 1 deletion internal/app/machined/pkg/runtime/mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (
Shutdown
// Upgrade node upgrade.
Upgrade
// MetaKV is META partition.
MetaKV
)

const (
Expand Down Expand Up @@ -78,7 +80,7 @@ func (m Mode) capabilities() uint64 {
// metal
all,
// container
all ^ uint64(Reboot|Shutdown|Upgrade|Rollback),
all ^ uint64(Reboot|Shutdown|Upgrade|Rollback|MetaKV),
// cloud
all,
}[m]
Expand Down
2 changes: 2 additions & 0 deletions internal/app/machined/pkg/system/services/machined.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ var rules = map[string]role.Set{
"/machine.MachineService/LoadAvg": role.MakeSet(role.Admin, role.Operator, role.Reader),
"/machine.MachineService/Logs": role.MakeSet(role.Admin, role.Operator, role.Reader),
"/machine.MachineService/Memory": role.MakeSet(role.Admin, role.Operator, role.Reader),
"/machine.MachineService/MetaWrite": role.MakeSet(role.Admin),
"/machine.MachineService/MetaDelete": role.MakeSet(role.Admin),
"/machine.MachineService/Mounts": role.MakeSet(role.Admin, role.Operator, role.Reader),
"/machine.MachineService/NetworkDeviceStats": role.MakeSet(role.Admin, role.Operator, role.Reader),
"/machine.MachineService/Netstat": role.MakeSet(role.Admin, role.Operator, role.Reader),
Expand Down
60 changes: 60 additions & 0 deletions internal/integration/cli/meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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/.

//go:build integration_cli

package cli

import (
"regexp"
"strings"

"github.com/google/uuid"

"github.com/siderolabs/talos/internal/integration/base"
)

// MetaSuite verifies meta sub-commands.
type MetaSuite struct {
base.CLISuite
}

// SuiteName ...
func (suite *MetaSuite) SuiteName() string {
return "cli.MetaSuite"
}

// TestKey writes a META key, deletes it, verifies via resources.
func (suite *MetaSuite) TestKey() {
node := suite.RandomDiscoveredNodeInternalIP()

// detect docker platform and skip the test
stdout, _ := suite.RunCLI([]string{"--nodes", node, "get", "platformmetadata"})
if strings.Contains(stdout, "container") {
suite.T().Skip("skipping on container platform")
}

key := "0x05" // unused/reserved key
value := uuid.New().String()

suite.RunCLI([]string{"--nodes", node, "meta", "write", key, value},
base.StdoutEmpty())

suite.RunCLI([]string{"--nodes", node, "get", "metakeys", key},
base.StdoutShouldMatch(regexp.MustCompile(key)),
base.StdoutShouldMatch(regexp.MustCompile(value)),
)

suite.RunCLI([]string{"--nodes", node, "meta", "delete", key},
base.StdoutEmpty())

suite.RunCLI([]string{"--nodes", node, "get", "metakeys", key},
base.ShouldFail(),
base.StderrShouldMatch(regexp.MustCompile("NotFound")),
)
}

func init() {
allSuites = append(allSuites, new(MetaSuite))
}

0 comments on commit 442cb9c

Please sign in to comment.