Skip to content

Commit

Permalink
feat: implement tracking of blockdevice secondaries
Browse files Browse the repository at this point in the history
This is going to be used to detect disks that are safe to wipe.

For blockdevices, track secondaries as direct references, e.g. encrypted
`STATE` partition might have secondary `vda5`.

For disks, re-map secondaries to be whole devices names, e.g. `vda`.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Nov 13, 2024
1 parent 8a7476c commit 43fe380
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 262 deletions.
2 changes: 2 additions & 0 deletions api/resource/definitions/block/block.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ message DeviceSpec {
int64 generation = 6;
string device_path = 7;
string parent = 8;
repeated string secondaries = 9;
}

// DiscoveredVolumeSpec is the spec for DiscoveredVolumes resource.
Expand Down Expand Up @@ -75,6 +76,7 @@ message DiskSpec {
bool cdrom = 13;
string dev_path = 14;
string pretty_size = 15;
repeated string secondary_disks = 16;
}

// EncryptionKey is the spec for volume encryption key.
Expand Down
4 changes: 4 additions & 0 deletions internal/app/machined/pkg/controllers/block/devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ func (ctrl *DevicesController) processEvent(ctx context.Context, r controller.Ru

if dev.TypedSpec().Type == "partition" {
dev.TypedSpec().Parent = filepath.Base(filepath.Dir(dev.TypedSpec().DevicePath))
dev.TypedSpec().Secondaries = nil
} else {
dev.TypedSpec().Parent = ""
dev.TypedSpec().Secondaries = sysblock.ReadSecondaries(ev.DevicePath)
}

dev.TypedSpec().Generation++
Expand Down
22 changes: 20 additions & 2 deletions internal/app/machined/pkg/controllers/block/disks.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/siderolabs/gen/xslices"
blkdev "github.com/siderolabs/go-blockdevice/v2/block"
"go.uber.org/zap"

Expand Down Expand Up @@ -90,7 +91,7 @@ func (ctrl *DisksController) Run(ctx context.Context, r controller.Runtime, logg

lastObservedGenerations[device.Metadata().ID()] = device.TypedSpec().Generation

if err = ctrl.analyzeBlockDevice(ctx, r, logger.With(zap.String("device", device.Metadata().ID())), device, touchedDisks); err != nil {
if err = ctrl.analyzeBlockDevice(ctx, r, logger.With(zap.String("device", device.Metadata().ID())), device, touchedDisks, blockdevices); err != nil {
return fmt.Errorf("failed to analyze block device: %w", err)
}
}
Expand All @@ -114,7 +115,10 @@ func (ctrl *DisksController) Run(ctx context.Context, r controller.Runtime, logg
}
}

func (ctrl *DisksController) analyzeBlockDevice(ctx context.Context, r controller.Runtime, logger *zap.Logger, device *block.Device, touchedDisks map[string]struct{}) error {
//nolint:gocyclo
func (ctrl *DisksController) analyzeBlockDevice(
ctx context.Context, r controller.Runtime, logger *zap.Logger, device *block.Device, touchedDisks map[string]struct{}, allBlockdevices safe.List[*block.Device],
) error {
bd, err := blkdev.NewFromPath(filepath.Join("/dev", device.Metadata().ID()))
if err != nil {
logger.Debug("failed to open blockdevice", zap.Error(err))
Expand Down Expand Up @@ -156,6 +160,18 @@ func (ctrl *DisksController) analyzeBlockDevice(ctx context.Context, r controlle
logger.Debug("failed to get properties", zap.Error(err))
}

secondaryDisks := xslices.Map(device.TypedSpec().Secondaries, func(devID string) string {
if secondary, ok := allBlockdevices.Find(func(dev *block.Device) bool {
return dev.Metadata().ID() == devID
}); ok {
if secondary.TypedSpec().Parent != "" {
return secondary.TypedSpec().Parent
}
}

return devID
})

touchedDisks[device.Metadata().ID()] = struct{}{}

return safe.WriterModify(ctx, r, block.NewDisk(block.NamespaceName, device.Metadata().ID()), func(d *block.Disk) error {
Expand All @@ -176,6 +192,8 @@ func (ctrl *DisksController) analyzeBlockDevice(ctx context.Context, r controlle
d.TypedSpec().Transport = props.Transport
d.TypedSpec().Rotational = props.Rotational

d.TypedSpec().SecondaryDisks = secondaryDisks

return nil
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"path/filepath"

"github.com/mdlayher/kobject"
"github.com/siderolabs/gen/xslices"
)

// Walk the /sys/block filesystem and gather block device information.
Expand Down Expand Up @@ -142,3 +143,15 @@ func readPartitions(path string) ([]*kobject.Event, error) {

return result, nil
}

// ReadSecondaries reads secondary devices for a given device and returns the list.
func ReadSecondaries(devPath string) []string {
entries, err := os.ReadDir(filepath.Join(devPath, "slaves"))
if err != nil {
return nil
}

return xslices.Map(entries, func(entry os.DirEntry) string {
return entry.Name()
})
}
7 changes: 7 additions & 0 deletions internal/integration/api/volumes.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ func (suite *VolumesSuite) TestDisks() {
}
}

if strings.HasPrefix(disk.Metadata().ID(), "dm-") {
// devicemapper disks should have secondaries
suite.Assert().NotEmpty(disk.TypedSpec().SecondaryDisks, "disk: %s", disk.Metadata().ID())

suite.T().Logf("disk: %s secondaries: %v", disk.Metadata().ID(), disk.TypedSpec().SecondaryDisks)
}

diskNames = append(diskNames, disk.Metadata().ID())
}

Expand Down
540 changes: 280 additions & 260 deletions pkg/machinery/api/resource/definitions/block/block.pb.go

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions pkg/machinery/resources/block/deep_copy.generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions pkg/machinery/resources/block/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ type DeviceSpec struct {

// Parent (if set) specifies the parent device ID.
Parent string `yaml:"parent,omitempty" protobuf:"8"`

// Secondaries (if set) specifies the secondary device IDs.
//
// E.g. for a LVM volume secondary is a list of blockdevices that the volume consists of.
Secondaries []string `yaml:"secondaries,omitempty" protobuf:"9"`
}

// NewDevice initializes a BlockDevice resource.
Expand Down
6 changes: 6 additions & 0 deletions pkg/machinery/resources/block/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ type DiskSpec struct {
SubSystem string `yaml:"sub_system,omitempty" protobuf:"10"`
Transport string `yaml:"transport,omitempty" protobuf:"11"`
Rotational bool `yaml:"rotational,omitempty" protobuf:"12"`

// SecondaryDisks (if set) specifies the secondary disk IDs.
//
// E.g. if the blockdevice secondary is vda5, the secondary disk will be set as vda.
// This allows to map secondaries between disks ignoring the partitions.
SecondaryDisks []string `yaml:"secondary_disks,omitempty" protobuf:"16"`
}

// SetSize sets the size of the disk, including the pretty size.
Expand Down
2 changes: 2 additions & 0 deletions website/content/v1.9/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ DeviceSpec is the spec for devices status.
| generation | [int64](#int64) | | |
| device_path | [string](#string) | | |
| parent | [string](#string) | | |
| secondaries | [string](#string) | repeated | |



Expand Down Expand Up @@ -912,6 +913,7 @@ DiskSpec is the spec for Disks status.
| cdrom | [bool](#bool) | | |
| dev_path | [string](#string) | | |
| pretty_size | [string](#string) | | |
| secondary_disks | [string](#string) | repeated | |



Expand Down

0 comments on commit 43fe380

Please sign in to comment.