From f07b79f4a8c647d358b8cd41b3704eccf0341d33 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 7 Jun 2024 19:48:41 +0400 Subject: [PATCH] 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 --- api/resource/definitions/block/block.proto | 21 + cmd/talosctl/cmd/mgmt/cluster/create.go | 13 +- go.mod | 2 +- go.sum | 4 +- .../machined/pkg/controllers/block/disks.go | 167 +++++ .../pkg/controllers/block/system_disk.go | 91 +++ .../runtime/v1alpha2/v1alpha2_controller.go | 2 + .../pkg/runtime/v1alpha2/v1alpha2_state.go | 2 + internal/app/storaged/server.go | 61 +- internal/integration/api/volumes.go | 62 +- internal/integration/cli/disk.go | 4 + .../resource/definitions/block/block.pb.go | 250 ++++++- .../definitions/block/block_vtproto.pb.go | 679 ++++++++++++++++++ pkg/machinery/resources/block/block.go | 2 +- pkg/machinery/resources/block/block_test.go | 2 + .../resources/block/deep_copy.generated.go | 14 +- pkg/machinery/resources/block/disk.go | 99 +++ pkg/machinery/resources/block/system_disk.go | 65 ++ pkg/provision/providers/qemu/launch.go | 66 +- pkg/provision/providers/qemu/node.go | 21 +- pkg/provision/request.go | 4 + website/content/v1.8/reference/api.md | 43 ++ website/content/v1.8/reference/cli.md | 1 + 23 files changed, 1621 insertions(+), 54 deletions(-) create mode 100644 internal/app/machined/pkg/controllers/block/disks.go create mode 100644 internal/app/machined/pkg/controllers/block/system_disk.go create mode 100644 pkg/machinery/resources/block/disk.go create mode 100644 pkg/machinery/resources/block/system_disk.go diff --git a/api/resource/definitions/block/block.proto b/api/resource/definitions/block/block.proto index 78b90d5ea8..02b48f24f1 100755 --- a/api/resource/definitions/block/block.proto +++ b/api/resource/definitions/block/block.proto @@ -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; +} + diff --git a/cmd/talosctl/cmd/mgmt/cluster/create.go b/cmd/talosctl/cmd/mgmt/cluster/create.go index cbff4bc47f..73bff7b42f 100644 --- a/cmd/talosctl/cmd/mgmt/cluster/create.go +++ b/cmd/talosctl/cmd/mgmt/cluster/create.go @@ -137,6 +137,7 @@ var ( clusterDisks []string extraDisks int extraDiskSize int + extraDisksDrivers []string targetArch string clusterWait bool clusterWaitTimeout time.Duration @@ -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, }) } @@ -1045,6 +1053,7 @@ func getDisks() ([]*provision.Disk, error) { { Size: uint64(clusterDiskSize) * 1024 * 1024, SkipPreallocate: !clusterDiskPreallocate, + Driver: "virtio", }, } @@ -1092,6 +1101,7 @@ func getDisks() ([]*provision.Disk, error) { Size: diskSize + 2*1024*1024, Partitions: diskPartitions, SkipPreallocate: !clusterDiskPreallocate, + Driver: "ide", }) } @@ -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: :::") 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") diff --git a/go.mod b/go.mod index 7fa214c4af..c3733205d6 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index d4174c156a..ef8090418a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/app/machined/pkg/controllers/block/disks.go b/internal/app/machined/pkg/controllers/block/disks.go new file mode 100644 index 0000000000..01b36f7a15 --- /dev/null +++ b/internal/app/machined/pkg/controllers/block/disks.go @@ -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 + }) +} diff --git a/internal/app/machined/pkg/controllers/block/system_disk.go b/internal/app/machined/pkg/controllers/block/system_disk.go new file mode 100644 index 0000000000..af8747dd52 --- /dev/null +++ b/internal/app/machined/pkg/controllers/block/system_disk.go @@ -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) + } + } + } +} diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go index 50cf52303e..ca25c9d25c 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go @@ -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{}, diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go index 1c33808d52..39c47c616e 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_state.go @@ -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{}, diff --git a/internal/app/storaged/server.go b/internal/app/storaged/server.go index 75fa4e2607..70a58409ca 100644 --- a/internal/app/storaged/server.go +++ b/internal/app/storaged/server.go @@ -7,17 +7,20 @@ package internal import ( "context" + "path/filepath" - "github.com/siderolabs/gen/xslices" - bddisk "github.com/siderolabs/go-blockdevice/blockdevice/util/disk" + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" "google.golang.org/protobuf/types/known/emptypb" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" "github.com/siderolabs/talos/pkg/machinery/api/storage" + "github.com/siderolabs/talos/pkg/machinery/resources/block" ) // Server implements storage.StorageService. -// TODO: this is not a full blown service yet, it's used as the common base in the machine and the maintenance services. +// +// It is only kept here for compatibility purposes, proper API is to query `block.Disk` resources. type Server struct { storage.UnimplementedStorageServiceServer Controller runtime.Controller @@ -25,32 +28,48 @@ type Server struct { // Disks implements storage.StorageService. func (s *Server) Disks(ctx context.Context, in *emptypb.Empty) (reply *storage.DisksResponse, err error) { - disks, err := bddisk.List() + st := s.Controller.Runtime().State().V1Alpha2().Resources() + + systemDisk, err := safe.StateGetByID[*block.SystemDisk](ctx, st, block.SystemDiskID) + if err != nil && !state.IsNotFoundError(err) { + return nil, err + } + + disks, err := safe.StateListAll[*block.Disk](ctx, st) if err != nil { return nil, err } - systemDisk := s.Controller.Runtime().State().Machine().Disk() + diskConv := func(d *block.Disk) (*storage.Disk, error) { + var diskType storage.Disk_DiskType - diskConv := func(d *bddisk.Disk) *storage.Disk { - return &storage.Disk{ - DeviceName: d.DeviceName, - Model: d.Model, - Size: d.Size, - Name: d.Name, - Serial: d.Serial, - Modalias: d.Modalias, - Uuid: d.UUID, - Wwid: d.WWID, - Type: storage.Disk_DiskType(d.Type), - BusPath: d.BusPath, - SystemDisk: systemDisk != nil && d.DeviceName == systemDisk.Device().Name(), - Subsystem: d.SubSystem, - Readonly: d.ReadOnly, + switch { + case d.TypedSpec().Transport == "nvme": + diskType = storage.Disk_NVME + case d.TypedSpec().Transport == "mmc": + diskType = storage.Disk_SD + case d.TypedSpec().Rotational: + diskType = storage.Disk_HDD + case d.TypedSpec().Transport != "": + diskType = storage.Disk_SSD } + + return &storage.Disk{ + DeviceName: filepath.Join("/dev", d.Metadata().ID()), + Model: d.TypedSpec().Model, + Size: d.TypedSpec().Size, + Serial: d.TypedSpec().Serial, + Modalias: d.TypedSpec().Modalias, + Wwid: d.TypedSpec().WWID, + Type: diskType, + BusPath: d.TypedSpec().BusPath, + SystemDisk: systemDisk != nil && d.Metadata().ID() == systemDisk.TypedSpec().DiskID, + Subsystem: d.TypedSpec().SubSystem, + Readonly: d.TypedSpec().Readonly, + }, nil } - diskList := xslices.Map(disks, diskConv) + diskList, _ := safe.Map(disks, diskConv) //nolint:errcheck reply = &storage.DisksResponse{ Messages: []*storage.Disks{ diff --git a/internal/integration/api/volumes.go b/internal/integration/api/volumes.go index f7bde26b1b..af25471f42 100644 --- a/internal/integration/api/volumes.go +++ b/internal/integration/api/volumes.go @@ -32,6 +32,10 @@ func (suite *VolumesSuite) SuiteName() string { // SetupTest ... func (suite *VolumesSuite) SetupTest() { + if !suite.Capabilities().SupportsVolumes { + suite.T().Skip("cluster doesn't support volumes") + } + suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), time.Minute) } @@ -44,10 +48,6 @@ func (suite *VolumesSuite) TearDownTest() { // TestDiscoveredVolumes verifies that standard Talos partitions are discovered. func (suite *VolumesSuite) TestDiscoveredVolumes() { - if !suite.Capabilities().SupportsVolumes { - suite.T().Skip("cluster doesn't support volumes") - } - for _, node := range suite.DiscoverNodeInternalIPs(suite.ctx) { suite.Run(node, func() { suite.testDiscoveredVolumes(node) @@ -114,6 +114,60 @@ func (suite *VolumesSuite) testDiscoveredVolumes(node string) { } } +// TestSystemDisk verifies that Talos system disk is discovered. +func (suite *VolumesSuite) TestSystemDisk() { + for _, node := range suite.DiscoverNodeInternalIPs(suite.ctx) { + suite.Run(node, func() { + ctx := client.WithNode(suite.ctx, node) + + systemDisk, err := safe.StateGetByID[*block.SystemDisk](ctx, suite.Client.COSI, block.SystemDiskID) + suite.Require().NoError(err) + + suite.Assert().NotEmpty(systemDisk.TypedSpec().DiskID) + + suite.T().Logf("system disk: %s", systemDisk.TypedSpec().DiskID) + }) + } +} + +// TestDisks verifies that Talos discovers disks. +func (suite *VolumesSuite) TestDisks() { + for _, node := range suite.DiscoverNodeInternalIPs(suite.ctx) { + suite.Run(node, func() { + ctx := client.WithNode(suite.ctx, node) + + disks, err := safe.StateListAll[*block.Disk](ctx, suite.Client.COSI) + suite.Require().NoError(err) + + // there should be at least two disks - loop0 for Talos squashfs and a system disk + suite.Assert().Greater(disks.Len(), 1) + + var diskNames []string + + for iter := disks.Iterator(); iter.Next(); { + disk := iter.Value() + + if disk.TypedSpec().Readonly { + continue + } + + suite.Assert().NotEmpty(disk.TypedSpec().Size, "disk: %s", disk.Metadata().ID()) + suite.Assert().NotEmpty(disk.TypedSpec().IOSize, "disk: %s", disk.Metadata().ID()) + suite.Assert().NotEmpty(disk.TypedSpec().SectorSize, "disk: %s", disk.Metadata().ID()) + + if suite.Cluster != nil { + // running on our own provider, transport should be always detected + suite.Assert().NotEmpty(disk.TypedSpec().Transport, "disk: %s", disk.Metadata().ID()) + } + + diskNames = append(diskNames, disk.Metadata().ID()) + } + + suite.T().Logf("disks: %v", diskNames) + }) + } +} + func init() { allSuites = append(allSuites, new(VolumesSuite)) } diff --git a/internal/integration/cli/disk.go b/internal/integration/cli/disk.go index 72b3f27788..41d5005cca 100644 --- a/internal/integration/cli/disk.go +++ b/internal/integration/cli/disk.go @@ -22,6 +22,10 @@ func (suite *DisksSuite) SuiteName() string { // 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()}) } diff --git a/pkg/machinery/api/resource/definitions/block/block.pb.go b/pkg/machinery/api/resource/definitions/block/block.pb.go index d41e573a31..a77cb54903 100644 --- a/pkg/machinery/api/resource/definitions/block/block.pb.go +++ b/pkg/machinery/api/resource/definitions/block/block.pb.go @@ -293,6 +293,190 @@ func (x *DiscoveredVolumeSpec) GetParent() string { return "" } +// DiskSpec is the spec for Disks status. +type DiskSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Size uint64 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` + IoSize uint64 `protobuf:"varint,2,opt,name=io_size,json=ioSize,proto3" json:"io_size,omitempty"` + SectorSize uint64 `protobuf:"varint,3,opt,name=sector_size,json=sectorSize,proto3" json:"sector_size,omitempty"` + Readonly bool `protobuf:"varint,4,opt,name=readonly,proto3" json:"readonly,omitempty"` + Model string `protobuf:"bytes,5,opt,name=model,proto3" json:"model,omitempty"` + Serial string `protobuf:"bytes,6,opt,name=serial,proto3" json:"serial,omitempty"` + Modalias string `protobuf:"bytes,7,opt,name=modalias,proto3" json:"modalias,omitempty"` + Wwid string `protobuf:"bytes,8,opt,name=wwid,proto3" json:"wwid,omitempty"` + BusPath string `protobuf:"bytes,9,opt,name=bus_path,json=busPath,proto3" json:"bus_path,omitempty"` + SubSystem string `protobuf:"bytes,10,opt,name=sub_system,json=subSystem,proto3" json:"sub_system,omitempty"` + Transport string `protobuf:"bytes,11,opt,name=transport,proto3" json:"transport,omitempty"` + Rotational bool `protobuf:"varint,12,opt,name=rotational,proto3" json:"rotational,omitempty"` +} + +func (x *DiskSpec) Reset() { + *x = DiskSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_definitions_block_block_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DiskSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiskSpec) ProtoMessage() {} + +func (x *DiskSpec) ProtoReflect() protoreflect.Message { + mi := &file_resource_definitions_block_block_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiskSpec.ProtoReflect.Descriptor instead. +func (*DiskSpec) Descriptor() ([]byte, []int) { + return file_resource_definitions_block_block_proto_rawDescGZIP(), []int{2} +} + +func (x *DiskSpec) GetSize() uint64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *DiskSpec) GetIoSize() uint64 { + if x != nil { + return x.IoSize + } + return 0 +} + +func (x *DiskSpec) GetSectorSize() uint64 { + if x != nil { + return x.SectorSize + } + return 0 +} + +func (x *DiskSpec) GetReadonly() bool { + if x != nil { + return x.Readonly + } + return false +} + +func (x *DiskSpec) GetModel() string { + if x != nil { + return x.Model + } + return "" +} + +func (x *DiskSpec) GetSerial() string { + if x != nil { + return x.Serial + } + return "" +} + +func (x *DiskSpec) GetModalias() string { + if x != nil { + return x.Modalias + } + return "" +} + +func (x *DiskSpec) GetWwid() string { + if x != nil { + return x.Wwid + } + return "" +} + +func (x *DiskSpec) GetBusPath() string { + if x != nil { + return x.BusPath + } + return "" +} + +func (x *DiskSpec) GetSubSystem() string { + if x != nil { + return x.SubSystem + } + return "" +} + +func (x *DiskSpec) GetTransport() string { + if x != nil { + return x.Transport + } + return "" +} + +func (x *DiskSpec) GetRotational() bool { + if x != nil { + return x.Rotational + } + return false +} + +// SystemDiskSpec is the spec for SystemDisks status. +type SystemDiskSpec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DiskId string `protobuf:"bytes,1,opt,name=disk_id,json=diskId,proto3" json:"disk_id,omitempty"` +} + +func (x *SystemDiskSpec) Reset() { + *x = SystemDiskSpec{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_definitions_block_block_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SystemDiskSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SystemDiskSpec) ProtoMessage() {} + +func (x *SystemDiskSpec) ProtoReflect() protoreflect.Message { + mi := &file_resource_definitions_block_block_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SystemDiskSpec.ProtoReflect.Descriptor instead. +func (*SystemDiskSpec) Descriptor() ([]byte, []int) { + return file_resource_definitions_block_block_proto_rawDescGZIP(), []int{3} +} + +func (x *SystemDiskSpec) GetDiskId() string { + if x != nil { + return x.DiskId + } + return "" +} + var File_resource_definitions_block_block_proto protoreflect.FileDescriptor var file_resource_definitions_block_block_proto_rawDesc = []byte{ @@ -348,12 +532,36 @@ var file_resource_definitions_block_block_proto_rawDesc = []byte{ 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x4a, 0x5a, 0x48, 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, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0xca, 0x02, 0x0a, 0x08, 0x44, + 0x69, 0x73, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, + 0x6f, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x69, 0x6f, + 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, + 0x1a, 0x0a, 0x08, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x64, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x77, + 0x77, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x77, 0x77, 0x69, 0x64, 0x12, + 0x19, 0x0a, 0x08, 0x62, 0x75, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x62, 0x75, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x75, + 0x62, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x75, 0x62, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x6f, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0x29, 0x0a, 0x0e, 0x53, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x44, 0x69, 0x73, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x69, 0x73, + 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x73, 0x6b, + 0x49, 0x64, 0x42, 0x4a, 0x5a, 0x48, 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, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x64, 0x65, 0x66, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -368,10 +576,12 @@ func file_resource_definitions_block_block_proto_rawDescGZIP() []byte { return file_resource_definitions_block_block_proto_rawDescData } -var file_resource_definitions_block_block_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_resource_definitions_block_block_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_resource_definitions_block_block_proto_goTypes = []interface{}{ (*DeviceSpec)(nil), // 0: talos.resource.definitions.block.DeviceSpec (*DiscoveredVolumeSpec)(nil), // 1: talos.resource.definitions.block.DiscoveredVolumeSpec + (*DiskSpec)(nil), // 2: talos.resource.definitions.block.DiskSpec + (*SystemDiskSpec)(nil), // 3: talos.resource.definitions.block.SystemDiskSpec } var file_resource_definitions_block_block_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -411,6 +621,30 @@ func file_resource_definitions_block_block_proto_init() { return nil } } + file_resource_definitions_block_block_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiskSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_definitions_block_block_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SystemDiskSpec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -418,7 +652,7 @@ func file_resource_definitions_block_block_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_resource_definitions_block_block_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go b/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go index 7a22b9b0c0..b22c197327 100644 --- a/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go +++ b/pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go @@ -233,6 +233,163 @@ func (m *DiscoveredVolumeSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *DiskSpec) 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 *DiskSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *DiskSpec) 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.Rotational { + i-- + if m.Rotational { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x60 + } + if len(m.Transport) > 0 { + i -= len(m.Transport) + copy(dAtA[i:], m.Transport) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Transport))) + i-- + dAtA[i] = 0x5a + } + if len(m.SubSystem) > 0 { + i -= len(m.SubSystem) + copy(dAtA[i:], m.SubSystem) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.SubSystem))) + i-- + dAtA[i] = 0x52 + } + if len(m.BusPath) > 0 { + i -= len(m.BusPath) + copy(dAtA[i:], m.BusPath) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.BusPath))) + i-- + dAtA[i] = 0x4a + } + if len(m.Wwid) > 0 { + i -= len(m.Wwid) + copy(dAtA[i:], m.Wwid) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Wwid))) + i-- + dAtA[i] = 0x42 + } + if len(m.Modalias) > 0 { + i -= len(m.Modalias) + copy(dAtA[i:], m.Modalias) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Modalias))) + i-- + dAtA[i] = 0x3a + } + if len(m.Serial) > 0 { + i -= len(m.Serial) + copy(dAtA[i:], m.Serial) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Serial))) + i-- + dAtA[i] = 0x32 + } + if len(m.Model) > 0 { + i -= len(m.Model) + copy(dAtA[i:], m.Model) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Model))) + i-- + dAtA[i] = 0x2a + } + if m.Readonly { + i-- + if m.Readonly { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if m.SectorSize != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.SectorSize)) + i-- + dAtA[i] = 0x18 + } + if m.IoSize != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.IoSize)) + i-- + dAtA[i] = 0x10 + } + if m.Size != 0 { + i = protohelpers.EncodeVarint(dAtA, i, uint64(m.Size)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *SystemDiskSpec) 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 *SystemDiskSpec) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *SystemDiskSpec) 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.DiskId) > 0 { + i -= len(m.DiskId) + copy(dAtA[i:], m.DiskId) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.DiskId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *DeviceSpec) SizeVT() (n int) { if m == nil { return 0 @@ -338,6 +495,73 @@ func (m *DiscoveredVolumeSpec) SizeVT() (n int) { return n } +func (m *DiskSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Size != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.Size)) + } + if m.IoSize != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.IoSize)) + } + if m.SectorSize != 0 { + n += 1 + protohelpers.SizeOfVarint(uint64(m.SectorSize)) + } + if m.Readonly { + n += 2 + } + l = len(m.Model) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Serial) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Modalias) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Wwid) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.BusPath) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.SubSystem) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + l = len(m.Transport) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + if m.Rotational { + n += 2 + } + n += len(m.unknownFields) + return n +} + +func (m *SystemDiskSpec) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.DiskId) + if l > 0 { + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + n += len(m.unknownFields) + return n +} + func (m *DeviceSpec) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1065,3 +1289,458 @@ func (m *DiscoveredVolumeSpec) UnmarshalVT(dAtA []byte) error { } return nil } +func (m *DiskSpec) 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: DiskSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DiskSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Size", wireType) + } + m.Size = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Size |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IoSize", wireType) + } + m.IoSize = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IoSize |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SectorSize", wireType) + } + m.SectorSize = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SectorSize |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Readonly", 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.Readonly = bool(v != 0) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Model", 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.Model = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Serial", 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.Serial = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Modalias", 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.Modalias = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Wwid", 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.Wwid = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BusPath", 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.BusPath = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubSystem", 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.SubSystem = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Transport", 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.Transport = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rotational", 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.Rotational = 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 *SystemDiskSpec) 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: SystemDiskSpec: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SystemDiskSpec: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DiskId", 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.DiskId = string(dAtA[iNdEx:postIndex]) + 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/resources/block/block.go b/pkg/machinery/resources/block/block.go index 48d181360b..3ca9b814f6 100644 --- a/pkg/machinery/resources/block/block.go +++ b/pkg/machinery/resources/block/block.go @@ -11,7 +11,7 @@ import ( "github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1" ) -//go:generate deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go . +//go:generate deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiskSpec -type SystemDiskSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go . // NamespaceName contains configuration resources. const NamespaceName resource.Namespace = v1alpha1.NamespaceName diff --git a/pkg/machinery/resources/block/block_test.go b/pkg/machinery/resources/block/block_test.go index d672416e40..f5314a6c68 100644 --- a/pkg/machinery/resources/block/block_test.go +++ b/pkg/machinery/resources/block/block_test.go @@ -27,6 +27,8 @@ func TestRegisterResource(t *testing.T) { for _, resource := range []meta.ResourceWithRD{ &block.Device{}, &block.DiscoveredVolume{}, + &block.Disk{}, + &block.SystemDisk{}, } { assert.NoError(t, resourceRegistry.Register(ctx, resource)) } diff --git a/pkg/machinery/resources/block/deep_copy.generated.go b/pkg/machinery/resources/block/deep_copy.generated.go index 7bc4c3280b..720526e3e2 100644 --- a/pkg/machinery/resources/block/deep_copy.generated.go +++ b/pkg/machinery/resources/block/deep_copy.generated.go @@ -2,7 +2,7 @@ // 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/. -// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT. +// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -type DiskSpec -type SystemDiskSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT. package block @@ -17,3 +17,15 @@ func (o DiscoveredVolumeSpec) DeepCopy() DiscoveredVolumeSpec { var cp DiscoveredVolumeSpec = o return cp } + +// DeepCopy generates a deep copy of DiskSpec. +func (o DiskSpec) DeepCopy() DiskSpec { + var cp DiskSpec = o + return cp +} + +// DeepCopy generates a deep copy of SystemDiskSpec. +func (o SystemDiskSpec) DeepCopy() SystemDiskSpec { + var cp SystemDiskSpec = o + return cp +} diff --git a/pkg/machinery/resources/block/disk.go b/pkg/machinery/resources/block/disk.go new file mode 100644 index 0000000000..c1f4a12bdb --- /dev/null +++ b/pkg/machinery/resources/block/disk.go @@ -0,0 +1,99 @@ +// 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 ( + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "github.com/cosi-project/runtime/pkg/resource/typed" + + "github.com/siderolabs/talos/pkg/machinery/proto" +) + +// DiskType is type of Disk resource. +const DiskType = resource.Type("Disks.block.talos.dev") + +// Disk resource holds status of hardware disks. +type Disk = typed.Resource[DiskSpec, DiskExtension] + +// DiskSpec is the spec for Disks status. +// +//gotagsrewrite:gen +type DiskSpec struct { + Size uint64 `yaml:"size" protobuf:"1"` + IOSize uint `yaml:"ioSize" protobuf:"2"` + SectorSize uint `yaml:"sectorSize" protobuf:"3"` + + Readonly bool `yaml:"readonly" protobuf:"4"` + + Model string `yaml:"model,omitempty" protobuf:"5"` + Serial string `yaml:"serial,omitempty" protobuf:"6"` + Modalias string `yaml:"modalias,omitempty" protobuf:"7"` + WWID string `yaml:"wwid,omitempty" protobuf:"8"` + BusPath string `yaml:"busPath,omitempty" protobuf:"9"` + SubSystem string `yaml:"subSystem,omitempty" protobuf:"10"` + Transport string `yaml:"transport,omitempty" protobuf:"11"` + Rotational bool `yaml:"rotational,omitempty" protobuf:"12"` +} + +// NewDisk initializes a BlockDisk resource. +func NewDisk(namespace resource.Namespace, id resource.ID) *Disk { + return typed.NewResource[DiskSpec, DiskExtension]( + resource.NewMetadata(namespace, DiskType, id, resource.VersionUndefined), + DiskSpec{}, + ) +} + +// DiskExtension is auxiliary resource data for BlockDisk. +type DiskExtension struct{} + +// ResourceDefinition implements meta.ResourceDefinitionProvider interface. +func (DiskExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: DiskType, + Aliases: []resource.Type{}, + DefaultNamespace: NamespaceName, + PrintColumns: []meta.PrintColumn{ + { + Name: "Size", + JSONPath: `{.size}`, + }, + { + Name: "Read Only", + JSONPath: `{.readonly}`, + }, + { + Name: "Transport", + JSONPath: `{.transport}`, + }, + { + Name: "Rotational", + JSONPath: `{.rotational}`, + }, + { + Name: "WWID", + JSONPath: `{.wwid}`, + }, + { + Name: "Model", + JSONPath: `{.model}`, + }, + { + Name: "Serial", + JSONPath: `{.serial}`, + }, + }, + } +} + +func init() { + proto.RegisterDefaultTypes() + + err := protobuf.RegisterDynamic[DiskSpec](DiskType, &Disk{}) + if err != nil { + panic(err) + } +} diff --git a/pkg/machinery/resources/block/system_disk.go b/pkg/machinery/resources/block/system_disk.go new file mode 100644 index 0000000000..cc620ce9e0 --- /dev/null +++ b/pkg/machinery/resources/block/system_disk.go @@ -0,0 +1,65 @@ +// 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 ( + "github.com/cosi-project/runtime/pkg/resource" + "github.com/cosi-project/runtime/pkg/resource/meta" + "github.com/cosi-project/runtime/pkg/resource/protobuf" + "github.com/cosi-project/runtime/pkg/resource/typed" + + "github.com/siderolabs/talos/pkg/machinery/proto" +) + +// SystemDiskType is type of SystemDisk resource. +const SystemDiskType = resource.Type("SystemDisks.block.talos.dev") + +// SystemDisk resource holds ID of the disk which is the Talos system disk. +type SystemDisk = typed.Resource[SystemDiskSpec, SystemDiskExtension] + +// SystemDiskID is the singleton resource ID. +const SystemDiskID resource.ID = "system-disk" + +// SystemDiskSpec is the spec for SystemDisks status. +// +//gotagsrewrite:gen +type SystemDiskSpec struct { + DiskID string `yaml:"diskID" protobuf:"1"` +} + +// NewSystemDisk initializes a BlockSystemDisk resource. +func NewSystemDisk(namespace resource.Namespace, id resource.ID) *SystemDisk { + return typed.NewResource[SystemDiskSpec, SystemDiskExtension]( + resource.NewMetadata(namespace, SystemDiskType, id, resource.VersionUndefined), + SystemDiskSpec{}, + ) +} + +// SystemDiskExtension is auxiliary resource data for BlockSystemDisk. +type SystemDiskExtension struct{} + +// ResourceDefinition implements meta.ResourceDefinitionProvider interface. +func (SystemDiskExtension) ResourceDefinition() meta.ResourceDefinitionSpec { + return meta.ResourceDefinitionSpec{ + Type: SystemDiskType, + Aliases: []resource.Type{}, + DefaultNamespace: NamespaceName, + PrintColumns: []meta.PrintColumn{ + { + Name: "Disk", + JSONPath: `{.diskID}`, + }, + }, + } +} + +func init() { + proto.RegisterDefaultTypes() + + err := protobuf.RegisterDynamic[SystemDiskSpec](SystemDiskType, &SystemDisk{}) + if err != nil { + panic(err) + } +} diff --git a/pkg/provision/providers/qemu/launch.go b/pkg/provision/providers/qemu/launch.go index 4b40ce2d3a..63eeb4d796 100644 --- a/pkg/provision/providers/qemu/launch.go +++ b/pkg/provision/providers/qemu/launch.go @@ -40,6 +40,7 @@ type LaunchConfig struct { // VM options DiskPaths []string + DiskDrivers []string VCPUCount int64 MemSize int64 QemuExecutable string @@ -327,26 +328,65 @@ func launchVM(config *LaunchConfig) error { "-no-reboot", "-boot", fmt.Sprintf("order=%s,reboot-timeout=5000", bootOrder), "-smbios", fmt.Sprintf("type=1,uuid=%s", config.NodeUUID), - "-chardev", - fmt.Sprintf("socket,path=%s/%s.sock,server=on,wait=off,id=qga0", config.StatePath, config.Hostname), - "-device", - "virtio-serial", - "-device", - "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", - "-device", - "i6300esb,id=watchdog0", + "-chardev", fmt.Sprintf("socket,path=%s/%s.sock,server=on,wait=off,id=qga0", config.StatePath, config.Hostname), + "-device", "virtio-serial", + "-device", "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", + "-device", "i6300esb,id=watchdog0", "-watchdog-action", "pause", } + var ( + scsiAttached, ahciAttached, nvmeAttached bool + ahciBus int + ) + for i, disk := range config.DiskPaths { - driver := "virtio" + driver := config.DiskDrivers[i] + + switch driver { + case "virtio": + args = append(args, "-drive", fmt.Sprintf("format=raw,if=virtio,file=%s,cache=none,", disk)) + case "ide": + args = append(args, "-drive", fmt.Sprintf("format=raw,if=ide,file=%s,cache=none,", disk)) + case "ahci": + if !ahciAttached { + args = append(args, "-device", "ahci,id=ahci0") + ahciAttached = true + } - if i > 0 { - driver = "ide" - } + args = append(args, + "-drive", fmt.Sprintf("id=ide%d,format=raw,if=none,file=%s", i, disk), + "-device", fmt.Sprintf("ide-hd,drive=ide%d,bus=ahci0.%d", i, ahciBus), + ) + + ahciBus++ + case "scsi": + if !scsiAttached { + args = append(args, "-device", "virtio-scsi-pci,id=scsi0") + scsiAttached = true + } - args = append(args, "-drive", fmt.Sprintf("format=raw,if=%s,file=%s,cache=unsafe", driver, disk)) + args = append(args, + "-drive", fmt.Sprintf("id=scsi%d,format=raw,if=none,file=%s,discard=unmap,aio=native,cache=none", i, disk), + "-device", fmt.Sprintf("scsi-hd,drive=scsi%d,bus=scsi0.0", i), + ) + case "nvme": + if !nvmeAttached { + // [TODO]: once Talos is fixed, use multipath NVME: https://qemu-project.gitlab.io/qemu/system/devices/nvme.html + args = append(args, + "-device", "nvme,id=nvme-ctrl-0,serial=deadbeef", + ) + nvmeAttached = true + } + + args = append(args, + "-drive", fmt.Sprintf("id=nvme%d,format=raw,if=none,file=%s,discard=unmap,aio=native,cache=none", i, disk), + "-device", fmt.Sprintf("nvme-ns,drive=nvme%d", i), + ) + default: + return fmt.Errorf("unsupported disk driver %q", driver) + } } machineArg := config.MachineType diff --git a/pkg/provision/providers/qemu/node.go b/pkg/provision/providers/qemu/node.go index fdb2a9d462..450f7985ca 100644 --- a/pkg/provision/providers/qemu/node.go +++ b/pkg/provision/providers/qemu/node.go @@ -19,6 +19,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/go-multierror" + "github.com/siderolabs/gen/xslices" "github.com/siderolabs/go-procfs/procfs" "github.com/siderolabs/talos/pkg/machinery/constants" @@ -114,9 +115,25 @@ func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRe defaultBootOrder = nodeReq.DefaultBootOrder } + // backwards compatibility, set Driver if not set + for i := range nodeReq.Disks { + if nodeReq.Disks[i].Driver != "" { + continue + } + + if i == 0 { + nodeReq.Disks[i].Driver = "virtio" + } else { + nodeReq.Disks[i].Driver = "ide" + } + } + launchConfig := LaunchConfig{ - QemuExecutable: arch.QemuExecutable(), - DiskPaths: diskPaths, + QemuExecutable: arch.QemuExecutable(), + DiskPaths: diskPaths, + DiskDrivers: xslices.Map(nodeReq.Disks, func(disk *provision.Disk) string { + return disk.Driver + }), VCPUCount: vcpuCount, MemSize: memSize, KernelArgs: cmdline.String(), diff --git a/pkg/provision/request.go b/pkg/provision/request.go index a2a8dbf1f3..5231275ede 100644 --- a/pkg/provision/request.go +++ b/pkg/provision/request.go @@ -158,6 +158,10 @@ type Disk struct { SkipPreallocate bool // Partitions represents the list of partitions. Partitions []*v1alpha1.DiskPartition + // Driver for the disk. + // + // Supported types: "virtio", "ide", "ahci", "scsi", "nvme". + Driver string } // NodeRequest describes a request for a node. diff --git a/website/content/v1.8/reference/api.md b/website/content/v1.8/reference/api.md index 1047fecd4e..e72448dce9 100644 --- a/website/content/v1.8/reference/api.md +++ b/website/content/v1.8/reference/api.md @@ -29,6 +29,8 @@ description: Talos gRPC API reference. - [resource/definitions/block/block.proto](#resource/definitions/block/block.proto) - [DeviceSpec](#talos.resource.definitions.block.DeviceSpec) - [DiscoveredVolumeSpec](#talos.resource.definitions.block.DiscoveredVolumeSpec) + - [DiskSpec](#talos.resource.definitions.block.DiskSpec) + - [SystemDiskSpec](#talos.resource.definitions.block.SystemDiskSpec) - [resource/definitions/cluster/cluster.proto](#resource/definitions/cluster/cluster.proto) - [AffiliateSpec](#talos.resource.definitions.cluster.AffiliateSpec) @@ -813,6 +815,47 @@ DiscoveredVolumeSpec is the spec for DiscoveredVolumes status. + + + +### DiskSpec +DiskSpec is the spec for Disks status. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| size | [uint64](#uint64) | | | +| io_size | [uint64](#uint64) | | | +| sector_size | [uint64](#uint64) | | | +| readonly | [bool](#bool) | | | +| model | [string](#string) | | | +| serial | [string](#string) | | | +| modalias | [string](#string) | | | +| wwid | [string](#string) | | | +| bus_path | [string](#string) | | | +| sub_system | [string](#string) | | | +| transport | [string](#string) | | | +| rotational | [bool](#bool) | | | + + + + + + + + +### SystemDiskSpec +SystemDiskSpec is the spec for SystemDisks status. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| disk_id | [string](#string) | | | + + + + + diff --git a/website/content/v1.8/reference/cli.md b/website/content/v1.8/reference/cli.md index 3531c66dca..5a4b657fbd 100644 --- a/website/content/v1.8/reference/cli.md +++ b/website/content/v1.8/reference/cli.md @@ -122,6 +122,7 @@ talosctl cluster create [flags] -p, --exposed-ports string Comma-separated list of ports/protocols to expose on init node. Ex -p :/ (Docker provisioner only) --extra-boot-kernel-args string add extra kernel args to the initial boot from vmlinuz and initramfs (QEMU only) --extra-disks int number of extra disks to create for each worker VM + --extra-disks-drivers strings driver for each extra disk (virtio, ide, ahci, scsi, nvme) --extra-disks-size int default limit on disk size in MB (each VM) (default 5120) --extra-uefi-search-paths strings additional search paths for UEFI firmware (only applies when UEFI is enabled) -h, --help help for create