Skip to content

Commit

Permalink
feat: provide disk detection based on new blockdevices
Browse files Browse the repository at this point in the history
Uses go-siderolabs/go-blockdevice/v2 for all the hard parts,
provides new resource `Disk` which describes all disks in the system.

Additional resource `SystemDisk` always point to the system disk (based
on the location of `META` partition).

The `Disks` API (and `talosctl disks`) provides a view now into the
`talosctl get disks` to keep backwards compatibility.

QEMU provisioner can now create extra disks of various types: IDE, AHCI,
SCSI, NVME, this allows to test detection properly.

The new resource will be the foundation for volume provisioning (to pick
up the disk to provision the volume on).

Example:

```
talosctl -n 172.20.0.5 get disks
NODE         NAMESPACE   TYPE   ID        VERSION   SIZE          READ ONLY   TRANSPORT   ROTATIONAL   WWID                                                               MODEL            SERIAL
172.20.0.5   runtime     Disk   loop0     1         65568768      true
172.20.0.5   runtime     Disk   nvme0n1   1         10485760000   false       nvme                     nvme.1b36-6465616462656566-51454d55204e564d65204374726c-00000001   QEMU NVMe Ctrl   deadbeef
172.20.0.5   runtime     Disk   sda       1         10485760000   false       virtio      true                                                                            QEMU HARDDISK
172.20.0.5   runtime     Disk   sdb       1         10485760000   false       sata        true         t10.ATA     QEMU HARDDISK                           QM00013        QEMU HARDDISK
172.20.0.5   runtime     Disk   sdc       1         10485760000   false       sata        true         t10.ATA     QEMU HARDDISK                           QM00001        QEMU HARDDISK
172.20.0.5   runtime     Disk   vda       1         12884901888   false       virtio      true
```

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
smira committed Jun 7, 2024
1 parent 8ee0872 commit f07b79f
Show file tree
Hide file tree
Showing 23 changed files with 1,621 additions and 54 deletions.
21 changes: 21 additions & 0 deletions api/resource/definitions/block/block.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,24 @@ message DiscoveredVolumeSpec {
string parent = 16;
}

// DiskSpec is the spec for Disks status.
message DiskSpec {
uint64 size = 1;
uint64 io_size = 2;
uint64 sector_size = 3;
bool readonly = 4;
string model = 5;
string serial = 6;
string modalias = 7;
string wwid = 8;
string bus_path = 9;
string sub_system = 10;
string transport = 11;
bool rotational = 12;
}

// SystemDiskSpec is the spec for SystemDisks status.
message SystemDiskSpec {
string disk_id = 1;
}

13 changes: 12 additions & 1 deletion cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ var (
clusterDisks []string
extraDisks int
extraDiskSize int
extraDisksDrivers []string
targetArch string
clusterWait bool
clusterWaitTimeout time.Duration
Expand Down Expand Up @@ -832,10 +833,17 @@ func create(ctx context.Context) error {
}

// append extra disks
for range extraDisks {
for i := range extraDisks {
driver := "ide"

if i < len(extraDisksDrivers) {
driver = extraDisksDrivers[i]
}

disks = append(disks, &provision.Disk{
Size: uint64(extraDiskSize) * 1024 * 1024,
SkipPreallocate: !clusterDiskPreallocate,
Driver: driver,
})
}

Expand Down Expand Up @@ -1045,6 +1053,7 @@ func getDisks() ([]*provision.Disk, error) {
{
Size: uint64(clusterDiskSize) * 1024 * 1024,
SkipPreallocate: !clusterDiskPreallocate,
Driver: "virtio",
},
}

Expand Down Expand Up @@ -1092,6 +1101,7 @@ func getDisks() ([]*provision.Disk, error) {
Size: diskSize + 2*1024*1024,
Partitions: diskPartitions,
SkipPreallocate: !clusterDiskPreallocate,
Driver: "ide",
})
}

Expand Down Expand Up @@ -1143,6 +1153,7 @@ func init() {
createCmd.Flags().BoolVar(&clusterDiskPreallocate, clusterDiskPreallocateFlag, true, "whether disk space should be preallocated")
createCmd.Flags().StringSliceVar(&clusterDisks, clusterDisksFlag, []string{}, "list of disks to create for each VM in format: <mount_point1>:<size1>:<mount_point2>:<size2>")
createCmd.Flags().IntVar(&extraDisks, "extra-disks", 0, "number of extra disks to create for each worker VM")
createCmd.Flags().StringSliceVar(&extraDisksDrivers, "extra-disks-drivers", nil, "driver for each extra disk (virtio, ide, ahci, scsi, nvme)")
createCmd.Flags().IntVar(&extraDiskSize, "extra-disks-size", 5*1024, "default limit on disk size in MB (each VM)")
createCmd.Flags().StringVar(&targetArch, "arch", stdruntime.GOARCH, "cluster architecture")
createCmd.Flags().BoolVar(&clusterWait, "wait", true, "wait for the cluster to be ready before returning")
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ require (
github.com/siderolabs/gen v0.5.0
github.com/siderolabs/go-api-signature v0.3.2
github.com/siderolabs/go-blockdevice v0.4.7
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240604163945-81b69bf28eaa
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240607145058-1a51f162a09e
github.com/siderolabs/go-circular v0.2.0
github.com/siderolabs/go-cmd v0.1.1
github.com/siderolabs/go-copy v0.1.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -661,8 +661,8 @@ github.com/siderolabs/go-api-signature v0.3.2 h1:blqrZF1GM7TWgq7mY7CsR+yQ93u6az0
github.com/siderolabs/go-api-signature v0.3.2/go.mod h1:punhUOaXa7LELYBRCUhfgUGH6ieVz68GrP98apCKXj8=
github.com/siderolabs/go-blockdevice v0.4.7 h1:2bk4WpEEflGxjrNwp57ye24Pr+cYgAiAeNMWiQOuWbQ=
github.com/siderolabs/go-blockdevice v0.4.7/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240604163945-81b69bf28eaa h1:OjQLrcis/GuqaqxnIw2dxp4ZzT/zk5p1GI3NxcMxkrA=
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240604163945-81b69bf28eaa/go.mod h1:5GnL7VLNp5/vgiwYP74fi6KuTUfqGcRxQxtto2tzD+I=
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240607145058-1a51f162a09e h1:PQhtHJj3zwaqehthq0fs2TyW8bW/mlOYoHfZIeSYQ3M=
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240607145058-1a51f162a09e/go.mod h1:5GnL7VLNp5/vgiwYP74fi6KuTUfqGcRxQxtto2tzD+I=
github.com/siderolabs/go-circular v0.2.0 h1:Xca8zrjF/YsujLbwDSojkKzJe7ngetnpuIJn8N78DJI=
github.com/siderolabs/go-circular v0.2.0/go.mod h1:rrYCwHLYWmxqrmZP+LjYtwB2a55lxzQi0Ztu1VpWZSc=
github.com/siderolabs/go-cmd v0.1.1 h1:nTouZUSxLeiiEe7hFexSVvaTsY/3O8k1s08BxPRrsps=
Expand Down
167 changes: 167 additions & 0 deletions internal/app/machined/pkg/controllers/block/disks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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 block

import (
"context"
"fmt"
"path/filepath"

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

"github.com/siderolabs/talos/pkg/machinery/resources/block"
)

// DisksController provides a detailed view of blockdevices of type 'disk'.
type DisksController struct{}

// Name implements controller.Controller interface.
func (ctrl *DisksController) Name() string {
return "block.DisksController"
}

// Inputs implements controller.Controller interface.
func (ctrl *DisksController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: block.NamespaceName,
Type: block.DeviceType,
Kind: controller.InputWeak,
},
}
}

// Outputs implements controller.Controller interface.
func (ctrl *DisksController) Outputs() []controller.Output {
return []controller.Output{
{
Type: block.DiskType,
Kind: controller.OutputExclusive,
},
}
}

// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *DisksController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
// lastObservedGenerations holds the last observed generation of each device.
//
// when the generation of a device changes, the device might have changed and might need to be re-probed.
lastObservedGenerations := map[string]int{}

for {
select {
case <-r.EventCh():
case <-ctx.Done():
return nil
}

blockdevices, err := safe.ReaderListAll[*block.Device](ctx, r)
if err != nil {
return fmt.Errorf("failed to list block devices: %w", err)
}

touchedDisks := map[string]struct{}{}

for iter := blockdevices.Iterator(); iter.Next(); {
device := iter.Value()

if device.TypedSpec().Type != "disk" {
continue
}

if lastObserved, ok := lastObservedGenerations[device.Metadata().ID()]; ok && device.TypedSpec().Generation == lastObserved {
// ignore disks which have some generation as before (don't query them once again)
touchedDisks[device.Metadata().ID()] = struct{}{}

continue
}

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 {
return fmt.Errorf("failed to analyze block device: %w", err)
}
}

disks, err := safe.ReaderListAll[*block.Disk](ctx, r)
if err != nil {
return fmt.Errorf("failed to list disks: %w", err)
}

for iter := disks.Iterator(); iter.Next(); {
disk := iter.Value()

if _, ok := touchedDisks[disk.Metadata().ID()]; ok {
continue
}

if err = r.Destroy(ctx, disk.Metadata()); err != nil {
return fmt.Errorf("failed to remove disk: %w", err)
}

delete(lastObservedGenerations, disk.Metadata().ID())
}
}
}

func (ctrl *DisksController) analyzeBlockDevice(ctx context.Context, r controller.Runtime, logger *zap.Logger, device *block.Device, touchedDisks map[string]struct{}) error {
bd, err := blkdev.NewFromPath(filepath.Join("/dev", device.Metadata().ID()))
if err != nil {
logger.Debug("failed to open blockdevice", zap.Error(err))

return nil
}

size, err := bd.GetSize()
if err != nil || size == 0 {
return nil //nolint:nilerr
}

if privateDM, _ := bd.IsPrivateDeviceMapper(); privateDM { //nolint:errcheck
return nil
}

ioSize, err := bd.GetIOSize()
if err != nil {
logger.Debug("failed to get io size", zap.Error(err))
}

sectorSize := bd.GetSectorSize()

readOnly, err := bd.IsReadOnly()
if err != nil {
logger.Debug("failed to get read only", zap.Error(err))
}

props, err := bd.GetProperties()
if err != nil {
logger.Debug("failed to get properties", zap.Error(err))
}

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

return safe.WriterModify(ctx, r, block.NewDisk(block.NamespaceName, device.Metadata().ID()), func(d *block.Disk) error {
d.TypedSpec().Size = size
d.TypedSpec().IOSize = ioSize
d.TypedSpec().SectorSize = sectorSize
d.TypedSpec().Readonly = readOnly

d.TypedSpec().Model = props.Model
d.TypedSpec().Serial = props.Serial
d.TypedSpec().Modalias = props.Modalias
d.TypedSpec().WWID = props.WWID
d.TypedSpec().BusPath = props.BusPath
d.TypedSpec().SubSystem = props.SubSystem
d.TypedSpec().Transport = props.Transport
d.TypedSpec().Rotational = props.Rotational

return nil
})
}
91 changes: 91 additions & 0 deletions internal/app/machined/pkg/controllers/block/system_disk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 block

import (
"context"
"fmt"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"go.uber.org/zap"

"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/resources/block"
)

// SystemDiskController provides a detailed view of blockdevices of type 'disk'.
type SystemDiskController struct{}

// Name implements controller.Controller interface.
func (ctrl *SystemDiskController) Name() string {
return "block.SystemDiskController"
}

// Inputs implements controller.Controller interface.
func (ctrl *SystemDiskController) Inputs() []controller.Input {
return []controller.Input{
{
Namespace: block.NamespaceName,
Type: block.DiscoveredVolumeType,
Kind: controller.InputWeak,
},
}
}

// Outputs implements controller.Controller interface.
func (ctrl *SystemDiskController) Outputs() []controller.Output {
return []controller.Output{
{
Type: block.SystemDiskType,
Kind: controller.OutputExclusive,
},
}
}

// Run implements controller.Controller interface.
//
//nolint:gocyclo
func (ctrl *SystemDiskController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
for {
select {
case <-r.EventCh():
case <-ctx.Done():
return nil
}

discoveredVolumes, err := safe.ReaderListAll[*block.DiscoveredVolume](ctx, r)
if err != nil {
return fmt.Errorf("failed to list discovered volumes: %w", err)
}

var systemDiskID string

for iter := discoveredVolumes.Iterator(); iter.Next(); {
volume := iter.Value()

if volume.TypedSpec().PartitionLabel == constants.MetaPartitionLabel {
systemDiskID = volume.TypedSpec().Parent

break
}
}

if systemDiskID != "" {
if err = safe.WriterModify(ctx, r, block.NewSystemDisk(block.NamespaceName, block.SystemDiskID), func(d *block.SystemDisk) error {
d.TypedSpec().DiskID = systemDiskID

return nil
}); err != nil {
return fmt.Errorf("failed to write system disk: %w", err)
}
} else {
if err = r.Destroy(ctx, block.NewSystemDisk(block.NamespaceName, block.SystemDiskID).Metadata()); err != nil && !state.IsNotFoundError(err) {
return fmt.Errorf("failed to destroy system disk: %w", err)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
},
&block.DiscoveryController{},
&block.DisksController{},
&block.SystemDiskController{},
&cluster.AffiliateMergeController{},
cluster.NewConfigController(),
&cluster.DiscoveryServiceController{},
Expand Down
2 changes: 2 additions & 0 deletions internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ func NewState() (*State, error) {
for _, r := range []meta.ResourceWithRD{
&block.Device{},
&block.DiscoveredVolume{},
&block.Disk{},
&block.SystemDisk{},
&cluster.Affiliate{},
&cluster.Config{},
&cluster.Identity{},
Expand Down
Loading

0 comments on commit f07b79f

Please sign in to comment.