From cc768037f8d4bb022e98ddd4762f483ffd2a7a7f Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Mon, 18 Nov 2024 12:58:52 +0400 Subject: [PATCH] feat: implement block device wipe Fixes #9731 The wipe doesn't require a reboot, but it requires the blockdevice not to be used as a volume. Signed-off-by: Andrey Smirnov --- api/storage/storage.proto | 42 ++ cmd/talosctl/cmd/talos/disks.go | 129 +--- cmd/talosctl/cmd/talos/wipe.go | 78 +++ go.mod | 2 +- go.sum | 4 +- hack/release.toml | 12 + .../block/internal/volumes/encrypt.go | 5 + .../machined/pkg/system/services/machined.go | 3 +- internal/app/maintenance/server.go | 7 +- internal/app/storaged/server.go | 215 +++++- internal/integration/api/wipe.go | 190 +++++ internal/integration/cli/disk.go | 34 - pkg/machinery/api/storage/storage.pb.go | 353 +++++++++- pkg/machinery/api/storage/storage_grpc.pb.go | 50 +- .../api/storage/storage_vtproto.pb.go | 659 ++++++++++++++++++ pkg/machinery/client/client.go | 9 + pkg/machinery/go.mod | 2 +- pkg/machinery/go.sum | 4 +- website/content/v1.9/reference/api.md | 88 +++ website/content/v1.9/reference/cli.md | 91 ++- 20 files changed, 1752 insertions(+), 225 deletions(-) create mode 100644 cmd/talosctl/cmd/talos/wipe.go create mode 100644 internal/integration/api/wipe.go delete mode 100644 internal/integration/cli/disk.go diff --git a/api/storage/storage.proto b/api/storage/storage.proto index ff25bb96e..b10e39b96 100644 --- a/api/storage/storage.proto +++ b/api/storage/storage.proto @@ -11,6 +11,12 @@ import "google/protobuf/empty.proto"; // StorageService represents the storage service. service StorageService { rpc Disks(google.protobuf.Empty) returns (DisksResponse); + // BlockDeviceWipe performs a wipe of the blockdevice (partition or disk). + // + // The method doesn't require a reboot, and it can only wipe blockdevices which are not + // being used as volumes at the moment. + // Wiping of volumes requires a different API. + rpc BlockDeviceWipe(BlockDeviceWipeRequest) returns (BlockDeviceWipeResponse); } // Disk represents a disk. @@ -60,3 +66,39 @@ message Disks { message DisksResponse { repeated Disks messages = 1; } + +// rpc BlockDeviceWipe + +message BlockDeviceWipeRequest { + repeated BlockDeviceWipeDescriptor devices = 1; +} + +// BlockDeviceWipeDescriptor represents a single block device to be wiped. +// +// The device can be either a full disk (e.g. vda) or a partition (vda5). +// The device should not be used in any of active volumes. +// The device should not be used as a secondary (e.g. part of LVM). +message BlockDeviceWipeDescriptor { + enum Method { + // Fast wipe - wipe only filesystem signatures. + FAST = 0; + // Zeroes wipe - wipe by overwriting with zeroes (might be slow depending on the disk size and available hardware features). + ZEROES = 1; + } + // Device name to wipe (e.g. sda or sda5). + // + // The name should be submitted without `/dev/` prefix. + string device = 1; + // Wipe method to use. + Method method = 2; + // Skip the volume in use check. + bool skip_volume_check = 3; +} + +message BlockDeviceWipeResponse { + repeated BlockDeviceWipe messages = 1; +} + +message BlockDeviceWipe { + common.Metadata metadata = 1; +} diff --git a/cmd/talosctl/cmd/talos/disks.go b/cmd/talosctl/cmd/talos/disks.go index b5b757cf7..7d9585561 100644 --- a/cmd/talosctl/cmd/talos/disks.go +++ b/cmd/talosctl/cmd/talos/disks.go @@ -5,138 +5,21 @@ package talos import ( - "context" - "fmt" - "os" - "strings" - "text/tabwriter" + "errors" - humanize "github.com/dustin/go-humanize" "github.com/spf13/cobra" - - "github.com/siderolabs/talos/pkg/cli" - "github.com/siderolabs/talos/pkg/machinery/client" ) -var disksCmdFlags struct { - insecure bool -} - var disksCmd = &cobra.Command{ - Use: "disks", - Short: "Get the list of disks from /sys/block on the machine", - Long: ``, + Use: "disks", + Short: "Get the list of disks from /sys/block on the machine", + Long: ``, + Hidden: true, RunE: func(cmd *cobra.Command, args []string) error { - if disksCmdFlags.insecure { - return WithClientMaintenance(nil, printDisks) - } - - return WithClient(printDisks) + return errors.New("`talosctl disks` is deprecated, please use `talosctl get disks`, `talosctl get systemdisk`, `talosctl get discoveredvolumes` instead") }, } -//nolint:gocyclo -func printDisks(ctx context.Context, c *client.Client) error { - response, err := c.Disks(ctx) - if err != nil { - if response == nil { - return fmt.Errorf("error getting disks: %w", err) - } - - cli.Warning("%s", err) - } - - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - node := "" - - labels := strings.Join( - []string{ - "DEV", - "MODEL", - "SERIAL", - "TYPE", - "UUID", - "WWID", - "MODALIAS", - "NAME", - "SIZE", - "BUS_PATH", - "SUBSYSTEM", - "READ_ONLY", - "SYSTEM_DISK", - }, "\t") - - getWithPlaceholder := func(in string) string { - if in == "" { - return "-" - } - - return in - } - - for i, message := range response.Messages { - if message.Metadata != nil && message.Metadata.Hostname != "" { - node = message.Metadata.Hostname - } - - if len(message.Disks) == 0 { - continue - } - - for j, disk := range message.Disks { - if i == 0 && j == 0 { - if node != "" { - fmt.Fprintln(w, "NODE\t"+labels) - } else { - fmt.Fprintln(w, labels) - } - } - - var args []any - - if node != "" { - args = append(args, node) - } - - isReadonly := "" - - if disk.Readonly { - isReadonly = "*" - } - - isSystemDisk := "" - - if disk.SystemDisk { - isSystemDisk = "*" - } - - args = append(args, []any{ - getWithPlaceholder(disk.DeviceName), - getWithPlaceholder(disk.Model), - getWithPlaceholder(disk.Serial), - disk.Type.String(), - getWithPlaceholder(disk.Uuid), - getWithPlaceholder(disk.Wwid), - getWithPlaceholder(disk.Modalias), - getWithPlaceholder(disk.Name), - humanize.Bytes(disk.Size), - getWithPlaceholder(disk.BusPath), - getWithPlaceholder(disk.Subsystem), - isReadonly, - isSystemDisk, - }...) - - pattern := strings.Repeat("%s\t", len(args)) - pattern = strings.TrimSpace(pattern) + "\n" - - fmt.Fprintf(w, pattern, args...) - } - } - - return w.Flush() -} - func init() { - disksCmd.Flags().BoolVarP(&disksCmdFlags.insecure, "insecure", "i", false, "get disks using the insecure (encrypted with no auth) maintenance service") addCommand(disksCmd) } diff --git a/cmd/talosctl/cmd/talos/wipe.go b/cmd/talosctl/cmd/talos/wipe.go new file mode 100644 index 000000000..0fa88a97e --- /dev/null +++ b/cmd/talosctl/cmd/talos/wipe.go @@ -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 talos + +import ( + "context" + "fmt" + + "github.com/siderolabs/gen/xslices" + "github.com/spf13/cobra" + + "github.com/siderolabs/talos/pkg/machinery/api/storage" + "github.com/siderolabs/talos/pkg/machinery/client" +) + +// wipeCmd represents the wipe command. +var wipeCmd = &cobra.Command{ + Use: "wipe", + Short: "Wipe block device or volumes", + Args: cobra.NoArgs, +} + +var wipeDiskCmdFlags struct { + wipeMethod string + skipVolumeCheck bool +} + +// wipeDiskCmd represents the wipe disk command. +var wipeDiskCmd = &cobra.Command{ + Use: "disk ...", + Short: "Wipe a block device (disk or partition) which is not used as a volume", + Long: `Wipe a block device (disk or partition) which is not used as a volume. + +Use device names as arguments, for example: vda or sda5.`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return WithClient(func(ctx context.Context, c *client.Client) error { + method, ok := storage.BlockDeviceWipeDescriptor_Method_value[wipeDiskCmdFlags.wipeMethod] + if !ok { + return fmt.Errorf("invalid wipe method %q", wipeDiskCmdFlags.wipeMethod) + } + + return c.BlockDeviceWipe(ctx, &storage.BlockDeviceWipeRequest{ + Devices: xslices.Map(args, func(devName string) *storage.BlockDeviceWipeDescriptor { + return &storage.BlockDeviceWipeDescriptor{ + Device: devName, + Method: storage.BlockDeviceWipeDescriptor_Method(method), + SkipVolumeCheck: wipeDiskCmdFlags.skipVolumeCheck, + } + }), + }) + }) + }, +} + +func wipeMethodValues() []string { + var method storage.BlockDeviceWipeDescriptor_Method + + values := make([]string, method.Descriptor().Values().Len()) + + for idx := range method.Descriptor().Values().Len() { + values[idx] = storage.BlockDeviceWipeDescriptor_Method_name[int32(idx)] + } + + return values +} + +func init() { + addCommand(wipeCmd) + + wipeDiskCmd.Flags().StringVar(&wipeDiskCmdFlags.wipeMethod, "method", wipeMethodValues()[0], fmt.Sprintf("wipe method to use %s", wipeMethodValues())) + wipeDiskCmd.Flags().BoolVar(&wipeDiskCmdFlags.skipVolumeCheck, "skip-volume-check", false, "skip volume check") + wipeDiskCmd.Flags().MarkHidden("skip-volume-check") //nolint:errcheck + + wipeCmd.AddCommand(wipeDiskCmd) +} diff --git a/go.mod b/go.mod index 70fd738ab..a7cae0d28 100644 --- a/go.mod +++ b/go.mod @@ -140,7 +140,7 @@ require ( github.com/siderolabs/gen v0.7.0 github.com/siderolabs/go-api-signature v0.3.6 github.com/siderolabs/go-blockdevice v0.4.8 - github.com/siderolabs/go-blockdevice/v2 v2.0.4 + github.com/siderolabs/go-blockdevice/v2 v2.0.5 github.com/siderolabs/go-circular v0.2.1 github.com/siderolabs/go-cmd v0.1.3 github.com/siderolabs/go-copy v0.1.0 diff --git a/go.sum b/go.sum index b84772475..d7f69f8a4 100644 --- a/go.sum +++ b/go.sum @@ -648,8 +648,8 @@ github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c= github.com/siderolabs/go-blockdevice v0.4.8/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA= -github.com/siderolabs/go-blockdevice/v2 v2.0.4 h1:+5umLlCtwJL0zkjExVr5liwDQtmK2vM2hALDfQMGSTk= -github.com/siderolabs/go-blockdevice/v2 v2.0.4/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= +github.com/siderolabs/go-blockdevice/v2 v2.0.5 h1:VLmIdDB/1P30Inrpe94FQAz4WUpByGwun5ZeTekxIQc= +github.com/siderolabs/go-blockdevice/v2 v2.0.5/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= github.com/siderolabs/go-circular v0.2.1 h1:a++iVCn9jyhICX3POQZZX8n72p2h5JGdGU6w1ulmpcA= github.com/siderolabs/go-circular v0.2.1/go.mod h1:ZDItzVyXK+B/XuqTBV5MtQtSv06VI+oCmWGRnNCATo8= github.com/siderolabs/go-cmd v0.1.3 h1:JrgZwqhJQeoec3QRON0LK+fv+0y7d0DyY7zsfkO6ciw= diff --git a/hack/release.toml b/hack/release.toml index 3c74cd9a4..97fd6b7bc 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -96,6 +96,18 @@ configuration option `.skipFallback` can be used to disable this behavior both f description = """\ Talos now supports matching on permanent hardware (MAC) address of the network interfaces. This is specifically useful to match bond members, as they change their hardware addresses when they become part of the bond. +""" + + [notes.talosctl-disk] + title = "talosctl disks" + description = """\ +The command `talosctl disks` was removed, please use `talosctl get disks`, `talosctl get systemdisk`, and `talosctl get blockdevices` instead. +""" + + [notes.talosctl-wipe] + title = "talosctl wipe" + description = """\ +The new command `talosctl wipe disk` allows to wipe a disk or a partition which is not used as a volume. """ [make_deps] diff --git a/internal/app/machined/pkg/controllers/block/internal/volumes/encrypt.go b/internal/app/machined/pkg/controllers/block/internal/volumes/encrypt.go index 42e74e9b3..6e85bfc17 100644 --- a/internal/app/machined/pkg/controllers/block/internal/volumes/encrypt.go +++ b/internal/app/machined/pkg/controllers/block/internal/volumes/encrypt.go @@ -107,6 +107,11 @@ func HandleEncryptionWithHandler(ctx context.Context, logger *zap.Logger, volume return xerrors.NewTaggedf[Retryable]("error opening encrypted volume: %w", err) } + encryptedPath, err = filepath.EvalSymlinks(encryptedPath) + if err != nil { + return fmt.Errorf("error resolving symlink: %w", err) + } + volumeContext.Status.Phase = block.VolumePhasePrepared volumeContext.Status.MountLocation = encryptedPath volumeContext.Status.EncryptionProvider = volumeContext.Cfg.TypedSpec().Encryption.Provider diff --git a/internal/app/machined/pkg/system/services/machined.go b/internal/app/machined/pkg/system/services/machined.go index f18cdbbb4..daada4dd8 100644 --- a/internal/app/machined/pkg/system/services/machined.go +++ b/internal/app/machined/pkg/system/services/machined.go @@ -99,7 +99,8 @@ var rules = map[string]role.Set{ "/cosi.resource.State/Update": role.MakeSet(role.Admin), "/cosi.resource.State/Watch": role.MakeSet(role.Admin, role.Operator, role.Reader), - "/storage.StorageService/Disks": role.MakeSet(role.Admin, role.Operator, role.Reader), + "/storage.StorageService/Disks": role.MakeSet(role.Admin, role.Operator, role.Reader), + "/storage.StorageService/BlockDeviceWipe": role.MakeSet(role.Admin), "/time.TimeService/Time": role.MakeSet(role.Admin, role.Operator, role.Reader), "/time.TimeService/TimeCheck": role.MakeSet(role.Admin, role.Operator, role.Reader), diff --git a/internal/app/maintenance/server.go b/internal/app/maintenance/server.go index 00ade1f2d..7fb05424b 100644 --- a/internal/app/maintenance/server.go +++ b/internal/app/maintenance/server.go @@ -70,7 +70,12 @@ func (s *Server) Register(obj *grpc.Server) { resourceState := s.controller.Runtime().State().V1Alpha2().Resources() resourceState = state.WrapCore(state.Filter(resourceState, resources.AccessPolicy(resourceState))) - storage.RegisterStorageServiceServer(obj, &storaged.Server{Controller: s.controller}) + storage.RegisterStorageServiceServer(obj, + &storaged.Server{ + Controller: s.controller, + MaintenanceMode: true, + }, + ) machine.RegisterMachineServiceServer(obj, s) cosiv1alpha1.RegisterStateServer(obj, server.NewState(resourceState)) } diff --git a/internal/app/storaged/server.go b/internal/app/storaged/server.go index 529bedad1..69b23b8a2 100644 --- a/internal/app/storaged/server.go +++ b/internal/app/storaged/server.go @@ -7,15 +7,26 @@ package internal import ( "context" + "fmt" + "log" "path/filepath" + "slices" + "strings" "github.com/cosi-project/runtime/pkg/safe" "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/gen/xslices" + "github.com/siderolabs/go-blockdevice/v2/blkid" + blockdev "github.com/siderolabs/go-blockdevice/v2/block" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" + "github.com/siderolabs/talos/pkg/grpc/middleware/authz" "github.com/siderolabs/talos/pkg/machinery/api/storage" "github.com/siderolabs/talos/pkg/machinery/resources/block" + "github.com/siderolabs/talos/pkg/machinery/role" ) // Server implements storage.StorageService. @@ -23,7 +34,8 @@ import ( // It is only kept here for compatibility purposes, proper API is to query `block.Disk` resources. type Server struct { storage.UnimplementedStorageServiceServer - Controller runtime.Controller + Controller runtime.Controller + MaintenanceMode bool } // Disks implements storage.StorageService. @@ -81,3 +93,204 @@ func (s *Server) Disks(ctx context.Context, in *emptypb.Empty) (reply *storage.D return reply, nil } + +// BlockDeviceWipe implements storage.StorageService. +// +// It allows to wipe unused block devices, for blockdevices in use (volumes), use a different method. +func (s *Server) BlockDeviceWipe(ctx context.Context, req *storage.BlockDeviceWipeRequest) (*storage.BlockDeviceWipeResponse, error) { + // the storage server is included both into machined and maintenance service + // in apid/machined mode, the normal authz checks are used before reaching this method + // in maintenance mode, do the role check, which maps today to SideroLink API connection + if s.MaintenanceMode && !authz.HasRole(ctx, role.Admin) { + return nil, status.Error(codes.Unimplemented, "API is not implemented in maintenance mode") + } + + // validate the list of devices + for _, deviceRequest := range req.GetDevices() { + if err := s.validateDeviceForWipe(ctx, deviceRequest.GetDevice(), deviceRequest.GetSkipVolumeCheck()); err != nil { + return nil, err + } + } + + // perform the actual wipe + for _, deviceRequest := range req.GetDevices() { + if err := s.wipeDevice(deviceRequest.GetDevice(), deviceRequest.GetMethod()); err != nil { + return nil, err + } + } + + return &storage.BlockDeviceWipeResponse{ + Messages: []*storage.BlockDeviceWipe{ + {}, + }, + }, nil +} + +//nolint:gocyclo,cyclop +func (s *Server) validateDeviceForWipe(ctx context.Context, deviceName string, skipVolumeCheck bool) error { + // first, resolve the blockdevice and figure out what type it is + st := s.Controller.Runtime().State().V1Alpha2().Resources() + + blockdevice, err := safe.StateGetByID[*block.Device](ctx, st, deviceName) + if err != nil { + if state.IsNotFoundError(err) { + return status.Errorf(codes.NotFound, "blockdevice %q not found", deviceName) + } + + return err + } + + var parent string + + deviceType := blockdevice.TypedSpec().Type + + switch deviceType { + case "disk": // supported + case "partition": // supported + parent = blockdevice.TypedSpec().Parent + default: + return status.Errorf(codes.InvalidArgument, "blockdevice %q is of unsupported type %q", deviceName, deviceType) + } + + // check the disk (or parent) + var disk *block.Disk + + if parent != "" { + disk, err = safe.StateGetByID[*block.Disk](ctx, st, parent) + } else { + disk, err = safe.StateGetByID[*block.Disk](ctx, st, deviceName) + } + + if err != nil { + return fmt.Errorf("failed to get disk (or parent) for %q: %w", deviceName, err) + } + + if disk.TypedSpec().Readonly { + return status.Errorf(codes.FailedPrecondition, "blockdevice %q is read-only", deviceName) + } + + if disk.TypedSpec().CDROM { + return status.Errorf(codes.FailedPrecondition, "blockdevice %q is a CD-ROM", deviceName) + } + + // secondaries check + switch deviceType { + case "disk": // for disks, check secondaries even if the partition is used as secondary (track via Disk resource) + disks, err := safe.StateListAll[*block.Disk](ctx, st) + if err != nil { + return err + } + + for disk := range disks.All() { + if slices.Index(disk.TypedSpec().SecondaryDisks, deviceName) != -1 { + return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by disk %q", deviceName, disk.Metadata().ID()) + } + } + case "partition": // for partitions, check secondaries only if the partition is used as a secondary + blockdevices, err := safe.StateListAll[*block.Device](ctx, st) + if err != nil { + return err + } + + for blockdevice := range blockdevices.All() { + if slices.Index(blockdevice.TypedSpec().Secondaries, deviceName) != -1 { + return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by blockdevice %q", deviceName, blockdevice.Metadata().ID()) + } + } + } + + if skipVolumeCheck { + return nil + } + + // volume in use checks + volumeStatuses, err := safe.StateListAll[*block.VolumeStatus](ctx, st) + if err != nil { + return err + } + + for volumeStatus := range volumeStatuses.All() { + for _, location := range []string{ + filepath.Base(volumeStatus.TypedSpec().Location), + filepath.Base(volumeStatus.TypedSpec().MountLocation), + } { + for _, dev := range []string{deviceName, parent} { + if dev == "" || location == "" { + continue + } + + if location == dev { + return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by volume %q", dev, volumeStatus.Metadata().ID()) + } + } + } + + if filepath.Base(volumeStatus.TypedSpec().ParentLocation) == deviceName { + return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by volume %q", deviceName, volumeStatus.Metadata().ID()) + } + } + + return nil +} + +// wipeDevice wipes the block device with the given method. +// +//nolint:gocyclo +func (s *Server) wipeDevice(deviceName string, method storage.BlockDeviceWipeDescriptor_Method) error { + bd, err := blockdev.NewFromPath(filepath.Join("/dev", deviceName), blockdev.OpenForWrite()) + if err != nil { + return status.Errorf(codes.Internal, "failed to open block device %q: %v", deviceName, err) + } + + defer bd.Close() //nolint:errcheck + + if err = bd.Lock(true); err != nil { + return status.Errorf(codes.Internal, "failed to lock block device %q: %v", deviceName, err) + } + + defer bd.Unlock() //nolint:errcheck + + switch method { + case storage.BlockDeviceWipeDescriptor_ZEROES: + log.Printf("wiping block device %q with zeroes", deviceName) + + if method, err := bd.Wipe(); err != nil { + return status.Errorf(codes.Internal, "failed to wipe block device %q: %v", deviceName, err) + } else { + log.Printf("block device %q wiped with method %q", deviceName, method) + } + case storage.BlockDeviceWipeDescriptor_FAST: + log.Printf("wiping block device %q with fast method", deviceName) + + info, err := blkid.Probe(bd.File(), blkid.WithSkipLocking(true)) + if err == nil && info != nil && len(info.SignatureRanges) > 0 { // probe successful, wipe by signatures + if err = bd.FastWipe(xslices.Map(info.SignatureRanges, func(r blkid.SignatureRange) blockdev.Range { + return blockdev.Range(r) + })...); err != nil { + return status.Errorf(codes.Internal, "failed to wipe block device %q: %v", deviceName, err) + } + + log.Printf("block device %q wiped by ranges: %s", + deviceName, + strings.Join( + xslices.Map(info.SignatureRanges, + func(r blkid.SignatureRange) string { + return fmt.Sprintf("%d-%d", r.Offset, r.Offset+r.Size) + }, + ), + ", ", + ), + ) + } else { // probe failed, use default fast wipe + if err = bd.FastWipe(); err != nil { + return status.Errorf(codes.Internal, "failed to wipe block device %q: %v", deviceName, err) + } + + log.Printf("block device %q wiped with fast method", deviceName) + } + default: + return status.Errorf(codes.InvalidArgument, "unsupported wipe method %s", method) + } + + return nil +} diff --git a/internal/integration/api/wipe.go b/internal/integration/api/wipe.go new file mode 100644 index 000000000..338daa642 --- /dev/null +++ b/internal/integration/api/wipe.go @@ -0,0 +1,190 @@ +// 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_api + +package api + +import ( + "context" + "fmt" + "path/filepath" + "testing" + "time" + + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "google.golang.org/grpc/codes" + + "github.com/siderolabs/talos/internal/integration/base" + "github.com/siderolabs/talos/pkg/machinery/api/storage" + "github.com/siderolabs/talos/pkg/machinery/client" + "github.com/siderolabs/talos/pkg/machinery/config/machine" + "github.com/siderolabs/talos/pkg/machinery/resources/block" +) + +// WipeSuite ... +type WipeSuite struct { + base.K8sSuite + + ctx context.Context //nolint:containedctx + ctxCancel context.CancelFunc +} + +// SuiteName ... +func (suite *WipeSuite) SuiteName() string { + return "api.WipeSuite" +} + +// SetupTest ... +func (suite *WipeSuite) SetupTest() { + suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), 5*time.Minute) + + if !suite.Capabilities().SupportsVolumes { + suite.T().Skip("cluster doesn't support volumes") + } +} + +// TearDownTest ... +func (suite *WipeSuite) TearDownTest() { + if suite.ctxCancel != nil { + suite.ctxCancel() + } +} + +// TestWipeBlockDeviceInvalid verifies that invalid wipe requests are rejected. +func (suite *WipeSuite) TestWipeBlockDeviceInvalid() { + node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker) + nodeCtx := client.WithNode(suite.ctx, node) + + disks, err := safe.StateListAll[*block.Disk](nodeCtx, suite.Client.COSI) + suite.Require().NoError(err) + + for disk := range disks.All() { + if disk.TypedSpec().Readonly || disk.TypedSpec().CDROM { + suite.T().Logf("invalid wipe request for %s at %s", disk.Metadata().ID(), node) + + err = suite.Client.BlockDeviceWipe(nodeCtx, &storage.BlockDeviceWipeRequest{ + Devices: []*storage.BlockDeviceWipeDescriptor{ + { + Device: disk.Metadata().ID(), + }, + }, + }) + suite.Require().Error(err) + suite.Assert().Equal(codes.FailedPrecondition, client.StatusCode(err)) + } + } + + err = suite.Client.BlockDeviceWipe(nodeCtx, &storage.BlockDeviceWipeRequest{ + Devices: []*storage.BlockDeviceWipeDescriptor{ + { + Device: "nosuchdevice", + }, + }, + }) + suite.Require().Error(err) + suite.Assert().Equal(codes.NotFound, client.StatusCode(err)) + + // try to wipe a system disk + systemDisk, err := safe.StateGetByID[*block.SystemDisk](nodeCtx, suite.Client.COSI, block.SystemDiskID) + suite.Require().NoError(err) + + suite.T().Logf("invalid wipe request for %s at %s", systemDisk.TypedSpec().DiskID, node) + err = suite.Client.BlockDeviceWipe(nodeCtx, &storage.BlockDeviceWipeRequest{ + Devices: []*storage.BlockDeviceWipeDescriptor{ + { + Device: systemDisk.TypedSpec().DiskID, + }, + }, + }) + suite.Require().Error(err) + suite.Assert().Equal(codes.FailedPrecondition, client.StatusCode(err)) +} + +// TestWipeFilesystem verifies that the filesystem can be wiped. +func (suite *WipeSuite) TestWipeFilesystem() { + if testing.Short() { + suite.T().Skip("skipping test in short mode.") + } + + if suite.Cluster == nil || suite.Cluster.Provisioner() != base.ProvisionerQEMU { + suite.T().Skip("skipping test for non-qemu provisioner") + } + + node := suite.RandomDiscoveredNodeInternalIP(machine.TypeWorker) + + k8sNode, err := suite.GetK8sNodeByInternalIP(suite.ctx, node) + suite.Require().NoError(err) + + nodeName := k8sNode.Name + + suite.T().Logf("creating filesystem on %s/%s", node, nodeName) + + userDisks, err := suite.UserDisks(suite.ctx, node) + suite.Require().NoError(err) + + if len(userDisks) < 1 { + suite.T().Skipf("skipping test, not enough user disks available on node %s/%s: %q", node, nodeName, userDisks) + } + + userDisk := userDisks[0] + + podDef, err := suite.NewPrivilegedPod("fs-format") + suite.Require().NoError(err) + + podDef = podDef.WithNodeName(nodeName) + + suite.Require().NoError(podDef.Create(suite.ctx, 5*time.Minute)) + + defer podDef.Delete(suite.ctx) //nolint:errcheck + + _, _, err = podDef.Exec( + suite.ctx, + fmt.Sprintf("nsenter --mount=/proc/1/ns/mnt -- mkfs.xfs %s", userDisk), + ) + suite.Require().NoError(err) + + // now Talos should report the disk as xfs formatted + deviceName := filepath.Base(userDisk) + + nodeCtx := client.WithNode(suite.ctx, node) + + // wait for Talos to discover xfs + _, err = suite.Client.COSI.WatchFor(nodeCtx, + block.NewDiscoveredVolume(block.NamespaceName, deviceName).Metadata(), + state.WithEventTypes(state.Created, state.Updated), + state.WithCondition(func(r resource.Resource) (bool, error) { + return r.(*block.DiscoveredVolume).TypedSpec().Name == "xfs", nil + }), + ) + suite.Require().NoError(err) + + suite.T().Logf("xfs filesystem created on %s/%s", node, nodeName) + + // wipe the filesystem + err = suite.Client.BlockDeviceWipe(nodeCtx, &storage.BlockDeviceWipeRequest{ + Devices: []*storage.BlockDeviceWipeDescriptor{ + { + Device: deviceName, + }, + }, + }) + suite.Require().NoError(err) + + // wait for Talos to discover that the disk is wiped + _, err = suite.Client.COSI.WatchFor(nodeCtx, + block.NewDiscoveredVolume(block.NamespaceName, deviceName).Metadata(), + state.WithEventTypes(state.Created, state.Updated), + state.WithCondition(func(r resource.Resource) (bool, error) { + return r.(*block.DiscoveredVolume).TypedSpec().Name == "", nil + }), + ) + suite.Require().NoError(err) +} + +func init() { + allSuites = append(allSuites, new(WipeSuite)) +} diff --git a/internal/integration/cli/disk.go b/internal/integration/cli/disk.go deleted file mode 100644 index 41d5005cc..000000000 --- a/internal/integration/cli/disk.go +++ /dev/null @@ -1,34 +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/. - -//go:build integration_cli - -package cli - -import ( - "github.com/siderolabs/talos/internal/integration/base" -) - -// DisksSuite verifies dmesg command. -type DisksSuite struct { - base.CLISuite -} - -// SuiteName ... -func (suite *DisksSuite) SuiteName() string { - return "cli.DisksSuite" -} - -// TestSuccess runs comand with success. -func (suite *DisksSuite) TestSuccess() { - if suite.Cluster != nil && suite.Cluster.Provisioner() == "docker" { - suite.T().Skip("docker provisioner doesn't support disks command") - } - - suite.RunCLI([]string{"disks", "--nodes", suite.RandomDiscoveredNodeInternalIP()}) -} - -func init() { - allSuites = append(allSuites, new(DisksSuite)) -} diff --git a/pkg/machinery/api/storage/storage.pb.go b/pkg/machinery/api/storage/storage.pb.go index 72cbd56f6..48a2b91f8 100644 --- a/pkg/machinery/api/storage/storage.pb.go +++ b/pkg/machinery/api/storage/storage.pb.go @@ -82,6 +82,54 @@ func (Disk_DiskType) EnumDescriptor() ([]byte, []int) { return file_storage_storage_proto_rawDescGZIP(), []int{0, 0} } +type BlockDeviceWipeDescriptor_Method int32 + +const ( + // Fast wipe - wipe only filesystem signatures. + BlockDeviceWipeDescriptor_FAST BlockDeviceWipeDescriptor_Method = 0 + // Zeroes wipe - wipe by overwriting with zeroes (might be slow depending on the disk size and available hardware features). + BlockDeviceWipeDescriptor_ZEROES BlockDeviceWipeDescriptor_Method = 1 +) + +// Enum value maps for BlockDeviceWipeDescriptor_Method. +var ( + BlockDeviceWipeDescriptor_Method_name = map[int32]string{ + 0: "FAST", + 1: "ZEROES", + } + BlockDeviceWipeDescriptor_Method_value = map[string]int32{ + "FAST": 0, + "ZEROES": 1, + } +) + +func (x BlockDeviceWipeDescriptor_Method) Enum() *BlockDeviceWipeDescriptor_Method { + p := new(BlockDeviceWipeDescriptor_Method) + *p = x + return p +} + +func (x BlockDeviceWipeDescriptor_Method) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BlockDeviceWipeDescriptor_Method) Descriptor() protoreflect.EnumDescriptor { + return file_storage_storage_proto_enumTypes[1].Descriptor() +} + +func (BlockDeviceWipeDescriptor_Method) Type() protoreflect.EnumType { + return &file_storage_storage_proto_enumTypes[1] +} + +func (x BlockDeviceWipeDescriptor_Method) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use BlockDeviceWipeDescriptor_Method.Descriptor instead. +func (BlockDeviceWipeDescriptor_Method) EnumDescriptor() ([]byte, []int) { + return file_storage_storage_proto_rawDescGZIP(), []int{4, 0} +} + // Disk represents a disk. type Disk struct { state protoimpl.MessageState @@ -336,6 +384,212 @@ func (x *DisksResponse) GetMessages() []*Disks { return nil } +type BlockDeviceWipeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Devices []*BlockDeviceWipeDescriptor `protobuf:"bytes,1,rep,name=devices,proto3" json:"devices,omitempty"` +} + +func (x *BlockDeviceWipeRequest) Reset() { + *x = BlockDeviceWipeRequest{} + mi := &file_storage_storage_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockDeviceWipeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockDeviceWipeRequest) ProtoMessage() {} + +func (x *BlockDeviceWipeRequest) ProtoReflect() protoreflect.Message { + mi := &file_storage_storage_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockDeviceWipeRequest.ProtoReflect.Descriptor instead. +func (*BlockDeviceWipeRequest) Descriptor() ([]byte, []int) { + return file_storage_storage_proto_rawDescGZIP(), []int{3} +} + +func (x *BlockDeviceWipeRequest) GetDevices() []*BlockDeviceWipeDescriptor { + if x != nil { + return x.Devices + } + return nil +} + +// BlockDeviceWipeDescriptor represents a single block device to be wiped. +// +// The device can be either a full disk (e.g. vda) or a partition (vda5). +// The device should not be used in any of active volumes. +// The device should not be used as a secondary (e.g. part of LVM). +type BlockDeviceWipeDescriptor struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Device name to wipe (e.g. sda or sda5). + // + // The name should be submitted without `/dev/` prefix. + Device string `protobuf:"bytes,1,opt,name=device,proto3" json:"device,omitempty"` + // Wipe method to use. + Method BlockDeviceWipeDescriptor_Method `protobuf:"varint,2,opt,name=method,proto3,enum=storage.BlockDeviceWipeDescriptor_Method" json:"method,omitempty"` + // Skip the volume in use check. + SkipVolumeCheck bool `protobuf:"varint,3,opt,name=skip_volume_check,json=skipVolumeCheck,proto3" json:"skip_volume_check,omitempty"` +} + +func (x *BlockDeviceWipeDescriptor) Reset() { + *x = BlockDeviceWipeDescriptor{} + mi := &file_storage_storage_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockDeviceWipeDescriptor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockDeviceWipeDescriptor) ProtoMessage() {} + +func (x *BlockDeviceWipeDescriptor) ProtoReflect() protoreflect.Message { + mi := &file_storage_storage_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockDeviceWipeDescriptor.ProtoReflect.Descriptor instead. +func (*BlockDeviceWipeDescriptor) Descriptor() ([]byte, []int) { + return file_storage_storage_proto_rawDescGZIP(), []int{4} +} + +func (x *BlockDeviceWipeDescriptor) GetDevice() string { + if x != nil { + return x.Device + } + return "" +} + +func (x *BlockDeviceWipeDescriptor) GetMethod() BlockDeviceWipeDescriptor_Method { + if x != nil { + return x.Method + } + return BlockDeviceWipeDescriptor_FAST +} + +func (x *BlockDeviceWipeDescriptor) GetSkipVolumeCheck() bool { + if x != nil { + return x.SkipVolumeCheck + } + return false +} + +type BlockDeviceWipeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Messages []*BlockDeviceWipe `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` +} + +func (x *BlockDeviceWipeResponse) Reset() { + *x = BlockDeviceWipeResponse{} + mi := &file_storage_storage_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockDeviceWipeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockDeviceWipeResponse) ProtoMessage() {} + +func (x *BlockDeviceWipeResponse) ProtoReflect() protoreflect.Message { + mi := &file_storage_storage_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockDeviceWipeResponse.ProtoReflect.Descriptor instead. +func (*BlockDeviceWipeResponse) Descriptor() ([]byte, []int) { + return file_storage_storage_proto_rawDescGZIP(), []int{5} +} + +func (x *BlockDeviceWipeResponse) GetMessages() []*BlockDeviceWipe { + if x != nil { + return x.Messages + } + return nil +} + +type BlockDeviceWipe struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Metadata *common.Metadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"` +} + +func (x *BlockDeviceWipe) Reset() { + *x = BlockDeviceWipe{} + mi := &file_storage_storage_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlockDeviceWipe) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockDeviceWipe) ProtoMessage() {} + +func (x *BlockDeviceWipe) ProtoReflect() protoreflect.Message { + mi := &file_storage_storage_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockDeviceWipe.ProtoReflect.Descriptor instead. +func (*BlockDeviceWipe) Descriptor() ([]byte, []int) { + return file_storage_storage_proto_rawDescGZIP(), []int{6} +} + +func (x *BlockDeviceWipe) GetMetadata() *common.Metadata { + if x != nil { + return x.Metadata + } + return nil +} + var File_storage_storage_proto protoreflect.FileDescriptor var file_storage_storage_proto_rawDesc = []byte{ @@ -380,17 +634,49 @@ var file_storage_storage_proto_rawDesc = []byte{ 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x52, 0x08, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x32, 0x49, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0x56, 0x0a, 0x16, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x3c, 0x0a, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x52, 0x07, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x22, 0xc2, + 0x01, 0x0a, 0x19, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, + 0x70, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, + 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6b, 0x69, 0x70, 0x5f, + 0x76, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x22, 0x1e, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, + 0x04, 0x46, 0x41, 0x53, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x5a, 0x45, 0x52, 0x4f, 0x45, + 0x53, 0x10, 0x01, 0x22, 0x4f, 0x0a, 0x17, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, 0x69, + 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, + 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x22, 0x3f, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x32, 0x9f, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x44, 0x69, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x4e, 0x0a, 0x15, 0x64, 0x65, 0x76, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, - 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, - 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x57, 0x69, 0x70, 0x65, 0x12, 0x1f, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x57, 0x69, 0x70, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x4e, 0x0a, 0x15, 0x64, 0x65, 0x76, 0x2e, 0x74, + 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, + 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2f, 0x70, 0x6b, + 0x67, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x72, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -405,28 +691,39 @@ func file_storage_storage_proto_rawDescGZIP() []byte { return file_storage_storage_proto_rawDescData } -var file_storage_storage_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_storage_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_storage_storage_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_storage_storage_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_storage_storage_proto_goTypes = []any{ - (Disk_DiskType)(0), // 0: storage.Disk.DiskType - (*Disk)(nil), // 1: storage.Disk - (*Disks)(nil), // 2: storage.Disks - (*DisksResponse)(nil), // 3: storage.DisksResponse - (*common.Metadata)(nil), // 4: common.Metadata - (*emptypb.Empty)(nil), // 5: google.protobuf.Empty + (Disk_DiskType)(0), // 0: storage.Disk.DiskType + (BlockDeviceWipeDescriptor_Method)(0), // 1: storage.BlockDeviceWipeDescriptor.Method + (*Disk)(nil), // 2: storage.Disk + (*Disks)(nil), // 3: storage.Disks + (*DisksResponse)(nil), // 4: storage.DisksResponse + (*BlockDeviceWipeRequest)(nil), // 5: storage.BlockDeviceWipeRequest + (*BlockDeviceWipeDescriptor)(nil), // 6: storage.BlockDeviceWipeDescriptor + (*BlockDeviceWipeResponse)(nil), // 7: storage.BlockDeviceWipeResponse + (*BlockDeviceWipe)(nil), // 8: storage.BlockDeviceWipe + (*common.Metadata)(nil), // 9: common.Metadata + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty } var file_storage_storage_proto_depIdxs = []int32{ - 0, // 0: storage.Disk.type:type_name -> storage.Disk.DiskType - 4, // 1: storage.Disks.metadata:type_name -> common.Metadata - 1, // 2: storage.Disks.disks:type_name -> storage.Disk - 2, // 3: storage.DisksResponse.messages:type_name -> storage.Disks - 5, // 4: storage.StorageService.Disks:input_type -> google.protobuf.Empty - 3, // 5: storage.StorageService.Disks:output_type -> storage.DisksResponse - 5, // [5:6] is the sub-list for method output_type - 4, // [4:5] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 0, // 0: storage.Disk.type:type_name -> storage.Disk.DiskType + 9, // 1: storage.Disks.metadata:type_name -> common.Metadata + 2, // 2: storage.Disks.disks:type_name -> storage.Disk + 3, // 3: storage.DisksResponse.messages:type_name -> storage.Disks + 6, // 4: storage.BlockDeviceWipeRequest.devices:type_name -> storage.BlockDeviceWipeDescriptor + 1, // 5: storage.BlockDeviceWipeDescriptor.method:type_name -> storage.BlockDeviceWipeDescriptor.Method + 8, // 6: storage.BlockDeviceWipeResponse.messages:type_name -> storage.BlockDeviceWipe + 9, // 7: storage.BlockDeviceWipe.metadata:type_name -> common.Metadata + 10, // 8: storage.StorageService.Disks:input_type -> google.protobuf.Empty + 5, // 9: storage.StorageService.BlockDeviceWipe:input_type -> storage.BlockDeviceWipeRequest + 4, // 10: storage.StorageService.Disks:output_type -> storage.DisksResponse + 7, // 11: storage.StorageService.BlockDeviceWipe:output_type -> storage.BlockDeviceWipeResponse + 10, // [10:12] is the sub-list for method output_type + 8, // [8:10] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_storage_storage_proto_init() } @@ -439,8 +736,8 @@ func file_storage_storage_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_storage_storage_proto_rawDesc, - NumEnums: 1, - NumMessages: 3, + NumEnums: 2, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/machinery/api/storage/storage_grpc.pb.go b/pkg/machinery/api/storage/storage_grpc.pb.go index 3c379e2b2..3dd13a8b0 100644 --- a/pkg/machinery/api/storage/storage_grpc.pb.go +++ b/pkg/machinery/api/storage/storage_grpc.pb.go @@ -21,7 +21,8 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - StorageService_Disks_FullMethodName = "/storage.StorageService/Disks" + StorageService_Disks_FullMethodName = "/storage.StorageService/Disks" + StorageService_BlockDeviceWipe_FullMethodName = "/storage.StorageService/BlockDeviceWipe" ) // StorageServiceClient is the client API for StorageService service. @@ -31,6 +32,12 @@ const ( // StorageService represents the storage service. type StorageServiceClient interface { Disks(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DisksResponse, error) + // BlockDeviceWipe performs a wipe of the blockdevice (partition or disk). + // + // The method doesn't require a reboot, and it can only wipe blockdevices which are not + // being used as volumes at the moment. + // Wiping of volumes requires a different API. + BlockDeviceWipe(ctx context.Context, in *BlockDeviceWipeRequest, opts ...grpc.CallOption) (*BlockDeviceWipeResponse, error) } type storageServiceClient struct { @@ -51,6 +58,16 @@ func (c *storageServiceClient) Disks(ctx context.Context, in *emptypb.Empty, opt return out, nil } +func (c *storageServiceClient) BlockDeviceWipe(ctx context.Context, in *BlockDeviceWipeRequest, opts ...grpc.CallOption) (*BlockDeviceWipeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BlockDeviceWipeResponse) + err := c.cc.Invoke(ctx, StorageService_BlockDeviceWipe_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // StorageServiceServer is the server API for StorageService service. // All implementations must embed UnimplementedStorageServiceServer // for forward compatibility. @@ -58,6 +75,12 @@ func (c *storageServiceClient) Disks(ctx context.Context, in *emptypb.Empty, opt // StorageService represents the storage service. type StorageServiceServer interface { Disks(context.Context, *emptypb.Empty) (*DisksResponse, error) + // BlockDeviceWipe performs a wipe of the blockdevice (partition or disk). + // + // The method doesn't require a reboot, and it can only wipe blockdevices which are not + // being used as volumes at the moment. + // Wiping of volumes requires a different API. + BlockDeviceWipe(context.Context, *BlockDeviceWipeRequest) (*BlockDeviceWipeResponse, error) mustEmbedUnimplementedStorageServiceServer() } @@ -71,6 +94,9 @@ type UnimplementedStorageServiceServer struct{} func (UnimplementedStorageServiceServer) Disks(context.Context, *emptypb.Empty) (*DisksResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Disks not implemented") } +func (UnimplementedStorageServiceServer) BlockDeviceWipe(context.Context, *BlockDeviceWipeRequest) (*BlockDeviceWipeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BlockDeviceWipe not implemented") +} func (UnimplementedStorageServiceServer) mustEmbedUnimplementedStorageServiceServer() {} func (UnimplementedStorageServiceServer) testEmbeddedByValue() {} @@ -110,6 +136,24 @@ func _StorageService_Disks_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _StorageService_BlockDeviceWipe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BlockDeviceWipeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StorageServiceServer).BlockDeviceWipe(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: StorageService_BlockDeviceWipe_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StorageServiceServer).BlockDeviceWipe(ctx, req.(*BlockDeviceWipeRequest)) + } + return interceptor(ctx, in, info, handler) +} + // StorageService_ServiceDesc is the grpc.ServiceDesc for StorageService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -121,6 +165,10 @@ var StorageService_ServiceDesc = grpc.ServiceDesc{ MethodName: "Disks", Handler: _StorageService_Disks_Handler, }, + { + MethodName: "BlockDeviceWipe", + Handler: _StorageService_BlockDeviceWipe_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "storage/storage.proto", diff --git a/pkg/machinery/api/storage/storage_vtproto.pb.go b/pkg/machinery/api/storage/storage_vtproto.pb.go index baf5f37f2..be1bbfa51 100644 --- a/pkg/machinery/api/storage/storage_vtproto.pb.go +++ b/pkg/machinery/api/storage/storage_vtproto.pb.go @@ -260,6 +260,206 @@ func (m *DisksResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *BlockDeviceWipeRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BlockDeviceWipeRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *BlockDeviceWipeRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Devices) > 0 { + for iNdEx := len(m.Devices) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Devices[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *BlockDeviceWipeDescriptor) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BlockDeviceWipeDescriptor) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *BlockDeviceWipeDescriptor) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.SkipVolumeCheck { + i-- + if m.SkipVolumeCheck { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.Method != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Method)) + i-- + dAtA[i] = 0x10 + } + if len(m.Device) > 0 { + i -= len(m.Device) + copy(dAtA[i:], m.Device) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Device))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *BlockDeviceWipeResponse) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BlockDeviceWipeResponse) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *BlockDeviceWipeResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Messages) > 0 { + for iNdEx := len(m.Messages) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Messages[iNdEx].MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *BlockDeviceWipe) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *BlockDeviceWipe) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *BlockDeviceWipe) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.Metadata != nil { + if vtmsg, ok := interface{}(m.Metadata).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := vtmsg.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.Metadata) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *Disk) SizeVT() (n int) { if m == nil { return 0 @@ -360,6 +560,78 @@ func (m *DisksResponse) SizeVT() (n int) { return n } +func (m *BlockDeviceWipeRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *BlockDeviceWipeDescriptor) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Device) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Method != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Method)) + } + if m.SkipVolumeCheck { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *BlockDeviceWipeResponse) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Messages) > 0 { + for _, e := range m.Messages { + l = e.SizeVT() + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } + n += len(m.unknownFields) + return n +} + +func (m *BlockDeviceWipe) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Metadata != nil { + if size, ok := interface{}(m.Metadata).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(m.Metadata) + } + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *Disk) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -991,3 +1263,390 @@ func (m *DisksResponse) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *BlockDeviceWipeRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BlockDeviceWipeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BlockDeviceWipeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Devices = append(m.Devices, &BlockDeviceWipeDescriptor{}) + if err := m.Devices[len(m.Devices)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BlockDeviceWipeDescriptor) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BlockDeviceWipeDescriptor: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BlockDeviceWipeDescriptor: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Device = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Method", wireType) + } + m.Method = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Method |= BlockDeviceWipeDescriptor_Method(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SkipVolumeCheck", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.SkipVolumeCheck = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BlockDeviceWipeResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BlockDeviceWipeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BlockDeviceWipeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Messages", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Messages = append(m.Messages, &BlockDeviceWipe{}) + if err := m.Messages[len(m.Messages)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BlockDeviceWipe) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BlockDeviceWipe: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BlockDeviceWipe: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &common.Metadata{} + } + if unmarshal, ok := interface{}(m.Metadata).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Metadata); err != nil { + return err + } + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := protohelpers.Skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protohelpers.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/pkg/machinery/client/client.go b/pkg/machinery/client/client.go index 4ab33dda8..878691fbf 100644 --- a/pkg/machinery/client/client.go +++ b/pkg/machinery/client/client.go @@ -1002,3 +1002,12 @@ func (c *Client) ImagePull(ctx context.Context, namespace common.ContainerdNames return err } + +// BlockDeviceWipe wipes a block device which is not used as a volume. +func (c *Client) BlockDeviceWipe(ctx context.Context, req *storageapi.BlockDeviceWipeRequest, callOptions ...grpc.CallOption) error { + resp, err := c.StorageClient.BlockDeviceWipe(ctx, req, callOptions...) + + _, err = FilterMessages(resp, err) + + return err +} diff --git a/pkg/machinery/go.mod b/pkg/machinery/go.mod index bf63d98c3..376c3c8ec 100644 --- a/pkg/machinery/go.mod +++ b/pkg/machinery/go.mod @@ -27,7 +27,7 @@ require ( github.com/siderolabs/crypto v0.5.0 github.com/siderolabs/gen v0.7.0 github.com/siderolabs/go-api-signature v0.3.6 - github.com/siderolabs/go-blockdevice/v2 v2.0.4 + github.com/siderolabs/go-blockdevice/v2 v2.0.5 github.com/siderolabs/go-pointer v1.0.0 github.com/siderolabs/net v0.4.0 github.com/siderolabs/protoenc v0.2.1 diff --git a/pkg/machinery/go.sum b/pkg/machinery/go.sum index e08ec6785..48c2b684c 100644 --- a/pkg/machinery/go.sum +++ b/pkg/machinery/go.sum @@ -113,8 +113,8 @@ github.com/siderolabs/gen v0.7.0 h1:uHAt3WD0dof28NHFuguWBbDokaXQraR/HyVxCLw2QCU= github.com/siderolabs/gen v0.7.0/go.mod h1:an3a2Y53O7kUjnnK8Bfu3gewtvnIOu5RTU6HalFtXQQ= github.com/siderolabs/go-api-signature v0.3.6 h1:wDIsXbpl7Oa/FXvxB6uz4VL9INA9fmr3EbmjEZYFJrU= github.com/siderolabs/go-api-signature v0.3.6/go.mod h1:hoH13AfunHflxbXfh+NoploqV13ZTDfQ1mQJWNVSW9U= -github.com/siderolabs/go-blockdevice/v2 v2.0.4 h1:+5umLlCtwJL0zkjExVr5liwDQtmK2vM2hALDfQMGSTk= -github.com/siderolabs/go-blockdevice/v2 v2.0.4/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= +github.com/siderolabs/go-blockdevice/v2 v2.0.5 h1:VLmIdDB/1P30Inrpe94FQAz4WUpByGwun5ZeTekxIQc= +github.com/siderolabs/go-blockdevice/v2 v2.0.5/go.mod h1:74htzCV913UzaLZ4H+NBXkwWlYnBJIq5m/379ZEcu8w= github.com/siderolabs/go-pointer v1.0.0 h1:6TshPKep2doDQJAAtHUuHWXbca8ZfyRySjSBT/4GsMU= github.com/siderolabs/go-pointer v1.0.0/go.mod h1:HTRFUNYa3R+k0FFKNv11zgkaCLzEkWVzoYZ433P3kHc= github.com/siderolabs/go-retry v0.3.3 h1:zKV+S1vumtO72E6sYsLlmIdV/G/GcYSBLiEx/c9oCEg= diff --git a/website/content/v1.9/reference/api.md b/website/content/v1.9/reference/api.md index 557900dfe..717cf3c26 100644 --- a/website/content/v1.9/reference/api.md +++ b/website/content/v1.9/reference/api.md @@ -493,10 +493,15 @@ description: Talos gRPC API reference. - [SecurityService](#securityapi.SecurityService) - [storage/storage.proto](#storage/storage.proto) + - [BlockDeviceWipe](#storage.BlockDeviceWipe) + - [BlockDeviceWipeDescriptor](#storage.BlockDeviceWipeDescriptor) + - [BlockDeviceWipeRequest](#storage.BlockDeviceWipeRequest) + - [BlockDeviceWipeResponse](#storage.BlockDeviceWipeResponse) - [Disk](#storage.Disk) - [Disks](#storage.Disks) - [DisksResponse](#storage.DisksResponse) + - [BlockDeviceWipeDescriptor.Method](#storage.BlockDeviceWipeDescriptor.Method) - [Disk.DiskType](#storage.Disk.DiskType) - [StorageService](#storage.StorageService) @@ -8488,6 +8493,74 @@ The security service definition. + + +### BlockDeviceWipe + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| metadata | [common.Metadata](#common.Metadata) | | | + + + + + + + + +### BlockDeviceWipeDescriptor +BlockDeviceWipeDescriptor represents a single block device to be wiped. + +The device can be either a full disk (e.g. vda) or a partition (vda5). +The device should not be used in any of active volumes. +The device should not be used as a secondary (e.g. part of LVM). + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| device | [string](#string) | | Device name to wipe (e.g. sda or sda5). + +The name should be submitted without `/dev/` prefix. | +| method | [BlockDeviceWipeDescriptor.Method](#storage.BlockDeviceWipeDescriptor.Method) | | Wipe method to use. | +| skip_volume_check | [bool](#bool) | | Skip the volume in use check. | + + + + + + + + +### BlockDeviceWipeRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| devices | [BlockDeviceWipeDescriptor](#storage.BlockDeviceWipeDescriptor) | repeated | | + + + + + + + + +### BlockDeviceWipeResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| messages | [BlockDeviceWipe](#storage.BlockDeviceWipe) | repeated | | + + + + + + ### Disk @@ -8548,6 +8621,18 @@ DisksResponse represents the response of the `Disks` RPC. + + +### BlockDeviceWipeDescriptor.Method + + +| Name | Number | Description | +| ---- | ------ | ----------- | +| FAST | 0 | Fast wipe - wipe only filesystem signatures. | +| ZEROES | 1 | Zeroes wipe - wipe by overwriting with zeroes (might be slow depending on the disk size and available hardware features). | + + + ### Disk.DiskType @@ -8576,6 +8661,9 @@ StorageService represents the storage service. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | Disks | [.google.protobuf.Empty](#google.protobuf.Empty) | [DisksResponse](#storage.DisksResponse) | | +| BlockDeviceWipe | [BlockDeviceWipeRequest](#storage.BlockDeviceWipeRequest) | [BlockDeviceWipeResponse](#storage.BlockDeviceWipeResponse) | BlockDeviceWipe performs a wipe of the blockdevice (partition or disk). + +The method doesn't require a reboot, and it can only wipe blockdevices which are not being used as volumes at the moment. Wiping of volumes requires a different API. | diff --git a/website/content/v1.9/reference/cli.md b/website/content/v1.9/reference/cli.md index 3692faa75..d6c8b2f16 100644 --- a/website/content/v1.9/reference/cli.md +++ b/website/content/v1.9/reference/cli.md @@ -872,35 +872,6 @@ talosctl dashboard [flags] * [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos -## talosctl disks - -Get the list of disks from /sys/block on the machine - -``` -talosctl disks [flags] -``` - -### Options - -``` - -h, --help help for disks - -i, --insecure get disks using the insecure (encrypted with no auth) maintenance service -``` - -### Options inherited from parent commands - -``` - --cluster string Cluster to connect to if a proxy endpoint is used. - --context string Context to be used in command - -e, --endpoints strings override default endpoints in Talos configuration - -n, --nodes strings target the specified nodes - --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. -``` - -### SEE ALSO - -* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos - ## talosctl dmesg Retrieve kernel logs @@ -3162,6 +3133,66 @@ talosctl version [flags] * [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos +## talosctl wipe disk + +Wipe a block device (disk or partition) which is not used as a volume + +### Synopsis + +Wipe a block device (disk or partition) which is not used as a volume. + +Use device names as arguments, for example: vda or sda5. + +``` +talosctl wipe disk ... [flags] +``` + +### Options + +``` + -h, --help help for disk + --method string wipe method to use [FAST ZEROES] (default "FAST") +``` + +### Options inherited from parent commands + +``` + --cluster string Cluster to connect to if a proxy endpoint is used. + --context string Context to be used in command + -e, --endpoints strings override default endpoints in Talos configuration + -n, --nodes strings target the specified nodes + --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. +``` + +### SEE ALSO + +* [talosctl wipe](#talosctl-wipe) - Wipe block device or volumes + +## talosctl wipe + +Wipe block device or volumes + +### Options + +``` + -h, --help help for wipe +``` + +### Options inherited from parent commands + +``` + --cluster string Cluster to connect to if a proxy endpoint is used. + --context string Context to be used in command + -e, --endpoints strings override default endpoints in Talos configuration + -n, --nodes strings target the specified nodes + --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. +``` + +### SEE ALSO + +* [talosctl](#talosctl) - A CLI for out-of-band management of Kubernetes nodes created by Talos +* [talosctl wipe disk](#talosctl-wipe-disk) - Wipe a block device (disk or partition) which is not used as a volume + ## talosctl A CLI for out-of-band management of Kubernetes nodes created by Talos @@ -3189,7 +3220,6 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos * [talosctl containers](#talosctl-containers) - List containers * [talosctl copy](#talosctl-copy) - Copy data out from the node * [talosctl dashboard](#talosctl-dashboard) - Cluster dashboard with node overview, logs and real-time metrics -* [talosctl disks](#talosctl-disks) - Get the list of disks from /sys/block on the machine * [talosctl dmesg](#talosctl-dmesg) - Retrieve kernel logs * [talosctl edit](#talosctl-edit) - Edit a resource from the default editor. * [talosctl etcd](#talosctl-etcd) - Manage etcd @@ -3227,4 +3257,5 @@ A CLI for out-of-band management of Kubernetes nodes created by Talos * [talosctl usage](#talosctl-usage) - Retrieve a disk usage * [talosctl validate](#talosctl-validate) - Validate config * [talosctl version](#talosctl-version) - Prints the version +* [talosctl wipe](#talosctl-wipe) - Wipe block device or volumes