-
Notifications
You must be signed in to change notification settings - Fork 564
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: provide disk detection based on new blockdevices
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
Showing
23 changed files
with
1,621 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
91
internal/app/machined/pkg/controllers/block/system_disk.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.