feat: implement blockdevice watch controller
This controller combines kobject events, and scan of `/sys/block` to build a consistent list of available block devices, updating resources as the blockdevice changes. Based on these resources the next step can run probe on the blockdevices as they change to present a consistent view of filesystems/partitions. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
06e3bc0cbd
commit
15beb14780
@ -100,9 +100,8 @@ linters-settings:
|
||||
replace-local: true
|
||||
replace-allow-list:
|
||||
- gopkg.in/yaml.v3
|
||||
- github.com/vmware-tanzu/sonobuoy
|
||||
- golang.org/x/sys
|
||||
- github.com/coredns/coredns
|
||||
- github.com/mdlayher/kobject
|
||||
retract-allow-no-explanation: false
|
||||
exclude-forbidden: true
|
||||
|
||||
|
38
api/resource/definitions/block/block.proto
Executable file
38
api/resource/definitions/block/block.proto
Executable file
@ -0,0 +1,38 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package talos.resource.definitions.block;
|
||||
|
||||
option go_package = "github.com/siderolabs/talos/pkg/machinery/api/resource/definitions/block";
|
||||
|
||||
// DeviceSpec is the spec for devices status.
|
||||
message DeviceSpec {
|
||||
string type = 1;
|
||||
int64 major = 2;
|
||||
int64 minor = 3;
|
||||
string partition_name = 4;
|
||||
int64 partition_number = 5;
|
||||
int64 generation = 6;
|
||||
string device_path = 7;
|
||||
string parent = 8;
|
||||
}
|
||||
|
||||
// DiscoveredVolumeSpec is the spec for DiscoveredVolumes status.
|
||||
message DiscoveredVolumeSpec {
|
||||
uint64 size = 1;
|
||||
uint64 sector_size = 2;
|
||||
uint64 io_size = 3;
|
||||
string name = 4;
|
||||
string uuid = 5;
|
||||
string label = 6;
|
||||
uint32 block_size = 7;
|
||||
uint32 filesystem_block_size = 8;
|
||||
uint64 probed_size = 9;
|
||||
string partition_uuid = 10;
|
||||
string partition_type = 11;
|
||||
string partition_label = 12;
|
||||
uint64 partition_index = 13;
|
||||
string type = 14;
|
||||
string device_path = 15;
|
||||
string parent = 16;
|
||||
}
|
||||
|
6
go.mod
6
go.mod
@ -5,6 +5,10 @@ go 1.22.1
|
||||
replace (
|
||||
// forked coredns so we don't carry caddy and other stuff into the Talos
|
||||
github.com/coredns/coredns => github.com/siderolabs/coredns v1.11.52
|
||||
|
||||
// see https://github.com/mdlayher/kobject/pull/5
|
||||
github.com/mdlayher/kobject => github.com/smira/kobject v0.0.0-20240304111826-49c8d4613389
|
||||
|
||||
// Use nested module.
|
||||
github.com/siderolabs/talos/pkg/machinery => ./pkg/machinery
|
||||
|
||||
@ -87,6 +91,7 @@ require (
|
||||
github.com/mdlayher/arp v0.0.0-20220512170110-6706a2966875
|
||||
github.com/mdlayher/ethtool v0.1.0
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/kobject v0.0.0-20200520190114-19ca17470d7d
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/mdlayher/netx v0.0.0-20230430222610-7e21880baee8
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
@ -112,6 +117,7 @@ require (
|
||||
github.com/siderolabs/gen v0.4.8
|
||||
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-20240301135834-a5481f5272f2
|
||||
github.com/siderolabs/go-circular v0.1.0
|
||||
github.com/siderolabs/go-cmd v0.1.1
|
||||
github.com/siderolabs/go-copy v0.1.0
|
||||
|
14
go.sum
14
go.sum
@ -446,6 +446,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v1.4.1 h1:JfD4jthWBqZMEffc5RjgmlzpYttAVw1sdnmiNaPO3hE=
|
||||
github.com/jsimonetti/rtnetlink v1.4.1/go.mod h1:xJjT7t59UIZ62GLZbv6PLLo8VFrostJMPBAheR6OM8w=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
@ -501,6 +503,9 @@ github.com/mdlayher/ethtool v0.1.0 h1:XAWHsmKhyPOo42qq/yTPb0eFBGUKKTR1rE0dVrWVQ0
|
||||
github.com/mdlayher/ethtool v0.1.0/go.mod h1:fBMLn2UhfRGtcH5ZFjr+6GUiHEjZsItFD7fSn7jbZVQ=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/netx v0.0.0-20230430222610-7e21880baee8 h1:HMgSn3c16SXca3M+n6fLK2hXJLd4mhKAsZZh7lQfYmQ=
|
||||
@ -659,6 +664,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-20240301135834-a5481f5272f2 h1:+/vwLuWQuBL85p0XO9Ak22rqvBPreC2R+pta4Bw1HFI=
|
||||
github.com/siderolabs/go-blockdevice/v2 v2.0.0-20240301135834-a5481f5272f2/go.mod h1:UBbbc+L7hU0UggOQeKCA+Qp3ImGkSeaLfVOiCbxRxEI=
|
||||
github.com/siderolabs/go-circular v0.1.0 h1:zpBJNUbCZSh0odZxA4Dcj0d3ShLLR2WxKW6hTdAtoiE=
|
||||
github.com/siderolabs/go-circular v0.1.0/go.mod h1:14XnLf/I3J0VjzTgmwWNGjp58/bdIi4zXppAEx8plfw=
|
||||
github.com/siderolabs/go-cmd v0.1.1 h1:nTouZUSxLeiiEe7hFexSVvaTsY/3O8k1s08BxPRrsps=
|
||||
@ -702,6 +709,8 @@ github.com/siderolabs/tcpproxy v0.1.0/go.mod h1:onn6CPPj/w1UNqQ0U97oRPF0CqbrgEAp
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/smira/kobject v0.0.0-20240304111826-49c8d4613389 h1:f/5NRv5IGZxbjBhc5MnlbNmyuXBPxvekhBAUzyKWyLY=
|
||||
github.com/smira/kobject v0.0.0-20240304111826-49c8d4613389/go.mod h1:+SexPO1ZvdbbWUdUnyXEWv3+4NwHZjKhxOmQqHY4Pqc=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
@ -886,6 +895,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@ -946,15 +957,18 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
6
internal/app/machined/pkg/controllers/block/block.go
Normal file
6
internal/app/machined/pkg/controllers/block/block.go
Normal file
@ -0,0 +1,6 @@
|
||||
// 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 provides the controllers related to blockdevices, mounts, etc.
|
||||
package block
|
246
internal/app/machined/pkg/controllers/block/devices.go
Normal file
246
internal/app/machined/pkg/controllers/block/devices.go
Normal file
@ -0,0 +1,246 @@
|
||||
// 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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"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/internal/app/machined/pkg/controllers/block/internal/inotify"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/kobject"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/sysblock"
|
||||
machineruntime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
)
|
||||
|
||||
// DevicesController provides a view of available block devices with information about pending updates.
|
||||
type DevicesController struct {
|
||||
V1Alpha1Mode machineruntime.Mode
|
||||
}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
func (ctrl *DevicesController) Name() string {
|
||||
return "block.DevicesController"
|
||||
}
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *DevicesController) Inputs() []controller.Input {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *DevicesController) Outputs() []controller.Output {
|
||||
return []controller.Output{
|
||||
{
|
||||
Type: block.DeviceType,
|
||||
Kind: controller.OutputExclusive,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *DevicesController) Run(ctx context.Context, r controller.Runtime, logger *zap.Logger) error {
|
||||
// in container mode, no devices
|
||||
if ctrl.V1Alpha1Mode == machineruntime.ModeContainer {
|
||||
return nil
|
||||
}
|
||||
|
||||
// start the watcher first
|
||||
watcher, err := kobject.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create kobject watcher: %w", err)
|
||||
}
|
||||
|
||||
defer watcher.Close() //nolint:errcheck
|
||||
|
||||
watchCh := watcher.Run(logger)
|
||||
|
||||
// start the inotify watcher
|
||||
inotifyWatcher, err := inotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create inotify watcher: %w", err)
|
||||
}
|
||||
|
||||
defer inotifyWatcher.Close() //nolint:errcheck
|
||||
|
||||
inotifyCh, inotifyErrCh := inotifyWatcher.Run()
|
||||
|
||||
// reconcile the initial list of devices while the watcher is running
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-r.EventCh():
|
||||
}
|
||||
|
||||
if err = ctrl.resync(ctx, r, logger, inotifyWatcher); err != nil {
|
||||
return fmt.Errorf("failed to resync: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-watchCh:
|
||||
if ev.Subsystem != "block" {
|
||||
continue
|
||||
}
|
||||
|
||||
ev.DevicePath = filepath.Join("/sys", ev.DevicePath)
|
||||
|
||||
if err = ctrl.processEvent(ctx, r, logger, inotifyWatcher, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
case err = <-inotifyErrCh:
|
||||
return fmt.Errorf("inotify watcher failed: %w", err)
|
||||
case updatedPath := <-inotifyCh:
|
||||
id := filepath.Base(updatedPath)
|
||||
|
||||
if err = ctrl.bumpGeneration(ctx, r, logger, id); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *DevicesController) bumpGeneration(ctx context.Context, r controller.Runtime, logger *zap.Logger, id string) error {
|
||||
_, err := safe.ReaderGetByID[*block.Device](ctx, r, id)
|
||||
if err != nil {
|
||||
if state.IsNotFoundError(err) {
|
||||
// skip it
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Debug("bumping generation for device, inotify update", zap.String("id", id))
|
||||
|
||||
return safe.WriterModify(ctx, r, block.NewDevice(block.NamespaceName, id), func(dev *block.Device) error {
|
||||
dev.TypedSpec().Generation++
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ctrl *DevicesController) resync(ctx context.Context, r controller.Runtime, logger *zap.Logger, inotifyWatcher *inotify.Watcher) error {
|
||||
events, err := sysblock.Walk("/sys/block")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk /sys/block: %w", err)
|
||||
}
|
||||
|
||||
touchedIDs := make(map[string]struct{}, len(events))
|
||||
|
||||
for _, ev := range events {
|
||||
if err = ctrl.processEvent(ctx, r, logger, inotifyWatcher, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
touchedIDs[ev.Values["DEVNAME"]] = struct{}{}
|
||||
}
|
||||
|
||||
// remove devices that were not touched
|
||||
devices, err := safe.ReaderListAll[*block.Device](ctx, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list devices: %w", err)
|
||||
}
|
||||
|
||||
for iterator := devices.Iterator(); iterator.Next(); {
|
||||
dev := iterator.Value()
|
||||
|
||||
if _, ok := touchedIDs[dev.Metadata().ID()]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = r.Destroy(ctx, dev.Metadata()); err != nil && !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("failed to remove device: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (ctrl *DevicesController) processEvent(ctx context.Context, r controller.Runtime, logger *zap.Logger, inotifyWatcher *inotify.Watcher, ev *kobject.Event) error {
|
||||
logger = logger.With(
|
||||
zap.String("action", string(ev.Action)),
|
||||
zap.String("path", ev.DevicePath),
|
||||
zap.String("id", ev.Values["DEVNAME"]),
|
||||
)
|
||||
|
||||
logger.Debug("processing event")
|
||||
|
||||
id := ev.Values["DEVNAME"]
|
||||
devPath := filepath.Join("/dev", id)
|
||||
|
||||
// re-stat the sysfs entry to make sure we are not out of sync with events
|
||||
_, reStatErr := os.Stat(ev.DevicePath)
|
||||
|
||||
switch ev.Action {
|
||||
case kobject.ActionAdd, kobject.ActionBind, kobject.ActionOnline, kobject.ActionChange, kobject.ActionMove, kobject.ActionUnbind, kobject.ActionOffline:
|
||||
if reStatErr != nil {
|
||||
logger.Debug("skipped, as device path doesn't exist")
|
||||
|
||||
return nil //nolint:nilerr // entry doesn't exist now, so skip the event
|
||||
}
|
||||
|
||||
if err := safe.WriterModify(ctx, r, block.NewDevice(block.NamespaceName, id), func(dev *block.Device) error {
|
||||
dev.TypedSpec().Type = ev.Values["DEVTYPE"]
|
||||
dev.TypedSpec().Major = atoiOrZero(ev.Values["MAJOR"])
|
||||
dev.TypedSpec().Minor = atoiOrZero(ev.Values["MINOR"])
|
||||
dev.TypedSpec().PartitionName = ev.Values["PARTNAME"]
|
||||
dev.TypedSpec().PartitionNumber = atoiOrZero(ev.Values["PARTN"])
|
||||
|
||||
dev.TypedSpec().DevicePath = ev.DevicePath
|
||||
|
||||
if dev.TypedSpec().Type == "partition" {
|
||||
dev.TypedSpec().Parent = filepath.Base(filepath.Dir(dev.TypedSpec().DevicePath))
|
||||
}
|
||||
|
||||
dev.TypedSpec().Generation++
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to modify device %q: %w", id, err)
|
||||
}
|
||||
|
||||
if err := inotifyWatcher.Add(devPath); err != nil {
|
||||
return fmt.Errorf("failed to add inotify watch for %q: %w", devPath, err)
|
||||
}
|
||||
case kobject.ActionRemove:
|
||||
if reStatErr == nil { // entry still exists, skip removing
|
||||
logger.Debug("skipped, as device path still exists")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.Destroy(ctx, block.NewDevice(block.NamespaceName, id).Metadata()); err != nil && !state.IsNotFoundError(err) {
|
||||
return fmt.Errorf("failed to remove device %q: %w", id, err)
|
||||
}
|
||||
|
||||
if err := inotifyWatcher.Remove(devPath); err != nil {
|
||||
return fmt.Errorf("failed to remove inotify watch for %q: %w", devPath, err)
|
||||
}
|
||||
default:
|
||||
logger.Debug("skipped, as action is not supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func atoiOrZero(s string) int {
|
||||
i, _ := strconv.Atoi(s) //nolint:errcheck
|
||||
|
||||
return i
|
||||
}
|
34
internal/app/machined/pkg/controllers/block/devices_test.go
Normal file
34
internal/app/machined/pkg/controllers/block/devices_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
blockctrls "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
)
|
||||
|
||||
type DevicesSuite struct {
|
||||
ctest.DefaultSuite
|
||||
}
|
||||
|
||||
func TestDevicesSuite(t *testing.T) {
|
||||
suite.Run(t, new(DevicesSuite))
|
||||
}
|
||||
|
||||
func (suite *DevicesSuite) TestDiscover() {
|
||||
suite.Require().NoError(suite.Runtime().RegisterController(&blockctrls.DevicesController{}))
|
||||
|
||||
// these devices should always exist on Linux
|
||||
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []string{"loop0", "loop1"}, func(r *block.Device, assertions *assert.Assertions) {
|
||||
assertions.Equal("disk", r.TypedSpec().Type)
|
||||
})
|
||||
}
|
277
internal/app/machined/pkg/controllers/block/discovery.go
Normal file
277
internal/app/machined/pkg/controllers/block/discovery.go
Normal file
@ -0,0 +1,277 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/controller"
|
||||
"github.com/cosi-project/runtime/pkg/safe"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/siderolabs/gen/maps"
|
||||
"github.com/siderolabs/go-blockdevice/v2/blkid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
)
|
||||
|
||||
// DiscoveryController provides a filesystem/partition discovery for blockdevices.
|
||||
type DiscoveryController struct{}
|
||||
|
||||
// Name implements controller.Controller interface.
|
||||
func (ctrl *DiscoveryController) Name() string {
|
||||
return "block.DiscoveryController"
|
||||
}
|
||||
|
||||
// Inputs implements controller.Controller interface.
|
||||
func (ctrl *DiscoveryController) Inputs() []controller.Input {
|
||||
return []controller.Input{
|
||||
{
|
||||
Namespace: block.NamespaceName,
|
||||
Type: block.DeviceType,
|
||||
Kind: controller.InputWeak,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Outputs implements controller.Controller interface.
|
||||
func (ctrl *DiscoveryController) Outputs() []controller.Output {
|
||||
return []controller.Output{
|
||||
{
|
||||
Type: block.DiscoveredVolumeType,
|
||||
Kind: controller.OutputExclusive,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements controller.Controller interface.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (ctrl *DiscoveryController) 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{}
|
||||
|
||||
// nextRescan holds the pool of devices to be rescanned in the next batch.
|
||||
nextRescan := map[string]int{}
|
||||
|
||||
rescanTicker := time.NewTicker(100 * time.Millisecond)
|
||||
defer rescanTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-rescanTicker.C:
|
||||
if len(nextRescan) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := ctrl.rescan(ctx, r, logger, maps.Keys(nextRescan)); err != nil {
|
||||
return fmt.Errorf("failed to rescan devices: %w", err)
|
||||
}
|
||||
|
||||
nextRescan = map[string]int{}
|
||||
case <-r.EventCh():
|
||||
devices, err := safe.ReaderListAll[*block.Device](ctx, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list devices: %w", err)
|
||||
}
|
||||
|
||||
parents := map[string]string{}
|
||||
allDevices := map[string]struct{}{}
|
||||
|
||||
for iterator := devices.Iterator(); iterator.Next(); {
|
||||
device := iterator.Value()
|
||||
|
||||
allDevices[device.Metadata().ID()] = struct{}{}
|
||||
|
||||
if device.TypedSpec().Parent != "" {
|
||||
parents[device.Metadata().ID()] = device.TypedSpec().Parent
|
||||
}
|
||||
|
||||
if device.TypedSpec().Generation == lastObservedGenerations[device.Metadata().ID()] {
|
||||
continue
|
||||
}
|
||||
|
||||
nextRescan[device.Metadata().ID()] = device.TypedSpec().Generation
|
||||
lastObservedGenerations[device.Metadata().ID()] = device.TypedSpec().Generation
|
||||
}
|
||||
|
||||
// remove child devices if the parent is marked for rescan
|
||||
for id := range nextRescan {
|
||||
if parent, ok := parents[id]; ok {
|
||||
if _, ok := nextRescan[parent]; ok {
|
||||
delete(nextRescan, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the device is removed, add it to the nextRescan, and remove from lastObservedGenerations
|
||||
for id := range lastObservedGenerations {
|
||||
if _, ok := allDevices[id]; !ok {
|
||||
nextRescan[id] = lastObservedGenerations[id]
|
||||
delete(lastObservedGenerations, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (ctrl *DiscoveryController) rescan(ctx context.Context, r controller.Runtime, logger *zap.Logger, ids []string) error {
|
||||
failedIDs := map[string]struct{}{}
|
||||
touchedIDs := map[string]struct{}{}
|
||||
|
||||
for _, id := range ids {
|
||||
device, err := safe.ReaderGetByID[*block.Device](ctx, r, id)
|
||||
if err != nil {
|
||||
if state.IsNotFoundError(err) {
|
||||
failedIDs[id] = struct{}{}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to get device: %w", err)
|
||||
}
|
||||
|
||||
info, err := blkid.ProbePath(filepath.Join("/dev", id))
|
||||
if err != nil {
|
||||
logger.Debug("failed to probe device", zap.String("id", id), zap.Error(err))
|
||||
|
||||
failedIDs[id] = struct{}{}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err = safe.WriterModify(ctx, r, block.NewDiscoveredVolume(block.NamespaceName, id), func(dv *block.DiscoveredVolume) error {
|
||||
dv.TypedSpec().Type = device.TypedSpec().Type
|
||||
dv.TypedSpec().DevicePath = device.TypedSpec().DevicePath
|
||||
dv.TypedSpec().Parent = device.TypedSpec().Parent
|
||||
|
||||
dv.TypedSpec().Size = info.Size
|
||||
dv.TypedSpec().SectorSize = info.SectorSize
|
||||
dv.TypedSpec().IOSize = info.IOSize
|
||||
|
||||
ctrl.fillDiscoveredVolumeFromInfo(dv, info.ProbeResult)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to write discovered volume: %w", err)
|
||||
}
|
||||
|
||||
touchedIDs[id] = struct{}{}
|
||||
|
||||
for _, nested := range info.Parts {
|
||||
partID := partitionID(id, nested.PartitionIndex)
|
||||
|
||||
if err = safe.WriterModify(ctx, r, block.NewDiscoveredVolume(block.NamespaceName, partID), func(dv *block.DiscoveredVolume) error {
|
||||
dv.TypedSpec().Type = "partition"
|
||||
dv.TypedSpec().DevicePath = filepath.Join(device.TypedSpec().DevicePath, partID)
|
||||
dv.TypedSpec().Parent = id
|
||||
|
||||
dv.TypedSpec().Size = nested.ProbedSize
|
||||
dv.TypedSpec().SectorSize = info.SectorSize
|
||||
dv.TypedSpec().IOSize = info.IOSize
|
||||
|
||||
ctrl.fillDiscoveredVolumeFromInfo(dv, nested.ProbeResult)
|
||||
|
||||
if nested.PartitionUUID != nil {
|
||||
dv.TypedSpec().PartitionUUID = nested.PartitionUUID.String()
|
||||
} else {
|
||||
dv.TypedSpec().PartitionUUID = ""
|
||||
}
|
||||
|
||||
if nested.PartitionType != nil {
|
||||
dv.TypedSpec().PartitionType = nested.PartitionType.String()
|
||||
} else {
|
||||
dv.TypedSpec().PartitionType = ""
|
||||
}
|
||||
|
||||
if nested.PartitionLabel != nil {
|
||||
dv.TypedSpec().PartitionLabel = *nested.PartitionLabel
|
||||
} else {
|
||||
dv.TypedSpec().PartitionLabel = ""
|
||||
}
|
||||
|
||||
dv.TypedSpec().PartitionIndex = nested.PartitionIndex
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to write discovered volume: %w", err)
|
||||
}
|
||||
|
||||
touchedIDs[partID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// clean up discovered volumes
|
||||
discoveredVolumes, err := safe.ReaderListAll[*block.DiscoveredVolume](ctx, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list discovered volumes: %w", err)
|
||||
}
|
||||
|
||||
for iterator := discoveredVolumes.Iterator(); iterator.Next(); {
|
||||
dv := iterator.Value()
|
||||
|
||||
if _, ok := touchedIDs[dv.Metadata().ID()]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
_, isFailed := failedIDs[dv.Metadata().ID()]
|
||||
|
||||
parentTouched := false
|
||||
|
||||
if dv.TypedSpec().Parent != "" {
|
||||
if _, ok := touchedIDs[dv.TypedSpec().Parent]; ok {
|
||||
parentTouched = true
|
||||
}
|
||||
}
|
||||
|
||||
if isFailed || parentTouched {
|
||||
// if the probe failed, or if the parent was touched, while this device was not, remove it
|
||||
if err = r.Destroy(ctx, dv.Metadata()); err != nil {
|
||||
return fmt.Errorf("failed to destroy discovered volume: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctrl *DiscoveryController) fillDiscoveredVolumeFromInfo(dv *block.DiscoveredVolume, info blkid.ProbeResult) {
|
||||
dv.TypedSpec().Name = info.Name
|
||||
|
||||
if info.UUID != nil {
|
||||
dv.TypedSpec().UUID = info.UUID.String()
|
||||
} else {
|
||||
dv.TypedSpec().UUID = ""
|
||||
}
|
||||
|
||||
if info.Label != nil {
|
||||
dv.TypedSpec().Label = *info.Label
|
||||
} else {
|
||||
dv.TypedSpec().Label = ""
|
||||
}
|
||||
|
||||
dv.TypedSpec().BlockSize = info.BlockSize
|
||||
dv.TypedSpec().FilesystemBlockSize = info.FilesystemBlockSize
|
||||
dv.TypedSpec().ProbedSize = info.ProbedSize
|
||||
}
|
||||
|
||||
func partitionID(devname string, part uint) string {
|
||||
result := devname
|
||||
|
||||
if len(result) > 0 && result[len(result)-1] >= '0' && result[len(result)-1] <= '9' {
|
||||
result += "p"
|
||||
}
|
||||
|
||||
return result + strconv.FormatUint(uint64(part), 10)
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
// 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 inotify implements a specialized inotify watcher for block devices.
|
||||
package inotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type (
|
||||
watches struct {
|
||||
mu sync.RWMutex
|
||||
wd map[uint32]*watch // wd → watch
|
||||
path map[string]uint32 // pathname → wd
|
||||
}
|
||||
watch struct {
|
||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||
path string // Watch path.
|
||||
}
|
||||
)
|
||||
|
||||
func newWatches() *watches {
|
||||
return &watches{
|
||||
wd: make(map[uint32]*watch),
|
||||
path: make(map[string]uint32),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watches) remove(wd uint32) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
delete(w.path, w.wd[wd].path)
|
||||
delete(w.wd, wd)
|
||||
}
|
||||
|
||||
func (w *watches) removePath(path string) (uint32, bool) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
wd, ok := w.path[path]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
delete(w.path, path)
|
||||
delete(w.wd, wd)
|
||||
|
||||
return wd, true
|
||||
}
|
||||
|
||||
func (w *watches) byWd(wd uint32) *watch {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
|
||||
return w.wd[wd]
|
||||
}
|
||||
|
||||
func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
var existing *watch
|
||||
|
||||
wd, ok := w.path[path]
|
||||
if ok {
|
||||
existing = w.wd[wd]
|
||||
}
|
||||
|
||||
upd, err := f(existing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if upd != nil {
|
||||
w.wd[upd.wd] = upd
|
||||
w.path[upd.path] = upd.wd
|
||||
|
||||
if upd.wd != wd {
|
||||
delete(w.wd, wd)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watcher implements inotify-based file watching.
|
||||
type Watcher struct {
|
||||
wg sync.WaitGroup
|
||||
fd int
|
||||
inotifyFile *os.File
|
||||
watches *watches
|
||||
}
|
||||
|
||||
// NewWatcher creates a new inotify Watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
|
||||
// I/O operations won't terminate on close.
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
||||
if fd == -1 {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return &Watcher{
|
||||
fd: fd,
|
||||
inotifyFile: os.NewFile(uintptr(fd), ""),
|
||||
watches: newWatches(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close the inotify watcher.
|
||||
func (w *Watcher) Close() error {
|
||||
// Causes any blocking reads to return with an error, provided the file
|
||||
// still supports deadline operations.
|
||||
err := w.inotifyFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for goroutine to close
|
||||
w.wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run the watcher, returns two channels for errors and events (paths changed).
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (w *Watcher) Run() (<-chan string, <-chan error) {
|
||||
errCh := make(chan error, 1)
|
||||
eventCh := make(chan string, 128)
|
||||
|
||||
w.wg.Add(1)
|
||||
|
||||
var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
|
||||
go func() {
|
||||
defer w.wg.Done()
|
||||
|
||||
for {
|
||||
n, err := w.inotifyFile.Read(buf[:])
|
||||
|
||||
switch {
|
||||
case errors.Is(err, os.ErrClosed):
|
||||
return
|
||||
case err != nil:
|
||||
errCh <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if n < unix.SizeofInotifyEvent {
|
||||
errCh <- errors.New("short read from inotify")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var offset uint32
|
||||
|
||||
// We don't know how many events we just read into the buffer
|
||||
// While the offset points to at least one whole event...
|
||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||
var (
|
||||
// Point "raw" to the event in the buffer
|
||||
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||
mask = raw.Mask
|
||||
nameLen = raw.Len
|
||||
)
|
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||
errCh <- errors.New("inotify queue overflow")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If the event happened to the watched directory or the watched file, the kernel
|
||||
// doesn't append the filename to the event, but we would like to always fill the
|
||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||
// the "paths" map.
|
||||
watch := w.watches.byWd(uint32(raw.Wd))
|
||||
|
||||
// inotify will automatically remove the watch on deletes; just need
|
||||
// to clean our state here.
|
||||
if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||
w.watches.remove(watch.wd)
|
||||
}
|
||||
|
||||
var name string
|
||||
if watch != nil {
|
||||
name = watch.path
|
||||
}
|
||||
|
||||
if nameLen > 0 {
|
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||
}
|
||||
|
||||
// Send the events that are not ignored on the events channel
|
||||
if mask&unix.IN_IGNORED == 0 {
|
||||
eventCh <- name
|
||||
}
|
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.SizeofInotifyEvent + nameLen
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return eventCh, errCh
|
||||
}
|
||||
|
||||
// Add a watch to the inotify watcher.
|
||||
func (w *Watcher) Add(name string) error {
|
||||
var flags uint32 = unix.IN_CLOSE_WRITE | unix.IN_DELETE_SELF
|
||||
|
||||
return w.watches.updatePath(name, func(existing *watch) (*watch, error) {
|
||||
if existing != nil {
|
||||
flags |= existing.flags | unix.IN_MASK_ADD
|
||||
}
|
||||
|
||||
wd, err := unix.InotifyAddWatch(w.fd, name, flags)
|
||||
if wd == -1 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existing == nil {
|
||||
return &watch{
|
||||
wd: uint32(wd),
|
||||
path: name,
|
||||
flags: flags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
existing.wd = uint32(wd)
|
||||
existing.flags = flags
|
||||
|
||||
return existing, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Remove a watch from the inotify watcher.
|
||||
func (w *Watcher) Remove(name string) error {
|
||||
wd, ok := w.watches.removePath(name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
success, errno := unix.InotifyRmWatch(w.fd, wd)
|
||||
if success == -1 {
|
||||
// TODO: Perhaps it's not helpful to return an error here in every case;
|
||||
// The only two possible errors are:
|
||||
//
|
||||
// - EBADF, which happens when w.fd is not a valid file descriptor
|
||||
// of any kind.
|
||||
// - EINVAL, which is when fd is not an inotify descriptor or wd
|
||||
// is not a valid watch descriptor. Watch descriptors are
|
||||
// invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they
|
||||
// are watching is deleted.
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// 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 inotify_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/inotify"
|
||||
)
|
||||
|
||||
func TestWatcher(t *testing.T) {
|
||||
watcher, err := inotify.NewWatcher()
|
||||
require.NoError(t, err)
|
||||
|
||||
d := t.TempDir()
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(d, "file1"), []byte("test1"), 0o644))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(d, "file2"), []byte("test2"), 0o644))
|
||||
|
||||
require.NoError(t, watcher.Add(filepath.Join(d, "file1")))
|
||||
|
||||
watchCh, errCh := watcher.Run()
|
||||
|
||||
require.NoError(t, watcher.Add(filepath.Join(d, "file2")))
|
||||
|
||||
select {
|
||||
case path := <-watchCh:
|
||||
require.FailNow(t, "unexpected path", "%s", path)
|
||||
case err = <-errCh:
|
||||
require.FailNow(t, "unexpected error", "%s", err)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
// open file1 for writing, should get inotify event
|
||||
f1, err := os.OpenFile(filepath.Join(d, "file1"), os.O_WRONLY, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, f1.Close())
|
||||
|
||||
select {
|
||||
case path := <-watchCh:
|
||||
require.Equal(t, filepath.Join(d, "file1"), path)
|
||||
case err = <-errCh:
|
||||
require.FailNow(t, "unexpected error", "%s", err)
|
||||
case <-time.After(time.Second):
|
||||
require.FailNow(t, "timeout")
|
||||
}
|
||||
|
||||
// open file2 for reading, should not get inotify event
|
||||
f2, err := os.OpenFile(filepath.Join(d, "file2"), os.O_RDONLY, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, f2.Close())
|
||||
|
||||
select {
|
||||
case path := <-watchCh:
|
||||
require.FailNow(t, "unexpected path", "%s", path)
|
||||
case err = <-errCh:
|
||||
require.FailNow(t, "unexpected error", "%s", err)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
require.NoError(t, watcher.Remove(filepath.Join(d, "file2")))
|
||||
|
||||
require.NoError(t, watcher.Close())
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
// 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 kobject implements Linux kernel kobject uvent watcher.
|
||||
package kobject
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/mdlayher/kobject"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const readBufferSize = 64 * 1024 * 1024
|
||||
|
||||
// Event is exported.
|
||||
type Event = kobject.Event
|
||||
|
||||
// Re-export action constants.
|
||||
const (
|
||||
ActionAdd = kobject.Add
|
||||
ActionRemove = kobject.Remove
|
||||
ActionChange = kobject.Change
|
||||
ActionMove = kobject.Move
|
||||
ActionOnline = kobject.Online
|
||||
ActionOffline = kobject.Offline
|
||||
ActionBind = kobject.Bind
|
||||
ActionUnbind = kobject.Unbind
|
||||
)
|
||||
|
||||
// Watcher is a kobject uvent watcher.
|
||||
type Watcher struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
cli *kobject.Client
|
||||
}
|
||||
|
||||
// NewWatcher creates a new kobject watcher.
|
||||
func NewWatcher() (*Watcher, error) {
|
||||
cli, err := kobject.New()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create kobject client: %w", err)
|
||||
}
|
||||
|
||||
if err = cli.SetReadBuffer(readBufferSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Watcher{
|
||||
cli: cli,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close the watcher.
|
||||
func (w *Watcher) Close() error {
|
||||
if err := w.cli.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run the watcher, returns the channel of events.
|
||||
func (w *Watcher) Run(logger *zap.Logger) <-chan *Event {
|
||||
ch := make(chan *kobject.Event, 128)
|
||||
|
||||
w.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer w.wg.Done()
|
||||
defer close(ch)
|
||||
|
||||
for {
|
||||
ev, err := w.cli.Receive()
|
||||
if err != nil {
|
||||
logger.Error("failed to receive kobject event", zap.Error(err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ch <- ev
|
||||
}
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// 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 kobject_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/kobject"
|
||||
)
|
||||
|
||||
func TestWatcher(t *testing.T) {
|
||||
watcher, err := kobject.NewWatcher()
|
||||
require.NoError(t, err)
|
||||
|
||||
evCh := watcher.Run(zaptest.NewLogger(t))
|
||||
|
||||
require.NoError(t, watcher.Close())
|
||||
|
||||
// the evCh should be closed
|
||||
for range evCh { //nolint:revive
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
// 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 sysblock implements gathering block device information from /sys/block filesystem.
|
||||
package sysblock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mdlayher/kobject"
|
||||
)
|
||||
|
||||
// Walk the /sys/block filesystem and gather block device information.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func Walk(root string) ([]*kobject.Event, error) {
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %w", root, err)
|
||||
}
|
||||
|
||||
result := make([]*kobject.Event, 0, len(entries))
|
||||
|
||||
for _, entry := range entries {
|
||||
fi, err := entry.Info()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to stat %s: %w", entry.Name(), err)
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := filepath.EvalSymlinks(filepath.Join(root, entry.Name()))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to resolve symlink %s: %w", entry.Name(), err)
|
||||
}
|
||||
|
||||
uevent, err := readUevent(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, &kobject.Event{
|
||||
Action: kobject.Add,
|
||||
DevicePath: path,
|
||||
Subsystem: "block",
|
||||
Values: uevent,
|
||||
})
|
||||
|
||||
partitions, err := readPartitions(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, partitions...)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// readUevent reads the /sys/block/<device>/uevent file and returns the content.
|
||||
func readUevent(path string) (map[string]string, error) {
|
||||
path = filepath.Join(path, "uevent")
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %w", path, err)
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
|
||||
for _, kv := range bytes.Split(content, []byte("\n")) {
|
||||
key, value, ok := bytes.Cut(kv, []byte("="))
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
result[string(key)] = string(value)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// readPartitions reads partitions for a given device and returns the list of events.
|
||||
func readPartitions(path string) ([]*kobject.Event, error) {
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to read %s: %w", path, err)
|
||||
}
|
||||
|
||||
var result []*kobject.Event //nolint:prealloc
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
partitionPath := filepath.Join(path, entry.Name())
|
||||
|
||||
_, err = os.Stat(filepath.Join(partitionPath, "partition"))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
uevent, err := readUevent(partitionPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, &kobject.Event{
|
||||
Action: kobject.Add,
|
||||
DevicePath: partitionPath,
|
||||
Subsystem: "block",
|
||||
Values: uevent,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
// 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 sysblock_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mdlayher/kobject"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block/internal/sysblock"
|
||||
)
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
events, err := sysblock.Walk("/sys/block")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, events)
|
||||
|
||||
// there should be at least a single blockdevice and a partition
|
||||
partitions, disks := 0, 0
|
||||
|
||||
for _, event := range events {
|
||||
require.Equal(t, "block", event.Subsystem)
|
||||
require.EqualValues(t, kobject.Add, event.Action)
|
||||
|
||||
require.NotEmpty(t, event.DevicePath)
|
||||
require.NotEmpty(t, event.Action)
|
||||
|
||||
switch event.Values["DEVTYPE"] {
|
||||
case "partition":
|
||||
partitions++
|
||||
case "disk":
|
||||
disks++
|
||||
}
|
||||
}
|
||||
|
||||
require.Greater(t, partitions, 0)
|
||||
require.Greater(t, disks, 0)
|
||||
}
|
@ -20,6 +20,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/block"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/cluster"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/config"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/cri"
|
||||
@ -87,6 +88,10 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error
|
||||
}
|
||||
|
||||
for _, c := range []controller.Controller{
|
||||
&block.DevicesController{
|
||||
V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(),
|
||||
},
|
||||
&block.DiscoveryController{},
|
||||
&cluster.AffiliateMergeController{},
|
||||
cluster.NewConfigController(),
|
||||
&cluster.DiscoveryServiceController{},
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/cosi-project/runtime/pkg/state/registry"
|
||||
|
||||
talosconfig "github.com/siderolabs/talos/pkg/machinery/config"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/cluster"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/config"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/cri"
|
||||
@ -94,6 +95,8 @@ func NewState() (*State, error) {
|
||||
|
||||
// register Talos resources
|
||||
for _, r := range []meta.ResourceWithRD{
|
||||
&block.Device{},
|
||||
&block.DiscoveredVolume{},
|
||||
&cluster.Affiliate{},
|
||||
&cluster.Config{},
|
||||
&cluster.Identity{},
|
||||
|
104
internal/integration/api/volumes.go
Normal file
104
internal/integration/api/volumes.go
Normal file
@ -0,0 +1,104 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
//go:build integration_api
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/safe"
|
||||
|
||||
"github.com/siderolabs/talos/internal/integration/base"
|
||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
)
|
||||
|
||||
// VolumesSuite ...
|
||||
type VolumesSuite struct {
|
||||
base.APISuite
|
||||
|
||||
ctx context.Context //nolint:containedctx
|
||||
ctxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
// SuiteName ...
|
||||
func (suite *VolumesSuite) SuiteName() string {
|
||||
return "api.VolumesSuite"
|
||||
}
|
||||
|
||||
// SetupTest ...
|
||||
func (suite *VolumesSuite) SetupTest() {
|
||||
suite.ctx, suite.ctxCancel = context.WithTimeout(context.Background(), time.Minute)
|
||||
}
|
||||
|
||||
// TearDownTest ...
|
||||
func (suite *VolumesSuite) TearDownTest() {
|
||||
if suite.ctxCancel != nil {
|
||||
suite.ctxCancel()
|
||||
}
|
||||
}
|
||||
|
||||
// TestDiscoveredVolumes verifies that standard Talos partitions are discovered.
|
||||
func (suite *VolumesSuite) TestDiscoveredVolumes() {
|
||||
if !suite.Capabilities().SupportsVolumes {
|
||||
suite.T().Skip("cluster doesn't support volumes")
|
||||
}
|
||||
|
||||
node := suite.RandomDiscoveredNodeInternalIP()
|
||||
ctx := client.WithNode(suite.ctx, node)
|
||||
|
||||
volumes, err := safe.StateListAll[*block.DiscoveredVolume](ctx, suite.Client.COSI)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
expectedVolumes := map[string]struct {
|
||||
Name string
|
||||
}{
|
||||
"META": {},
|
||||
"STATE": {
|
||||
Name: "xfs",
|
||||
},
|
||||
"EPHEMERAL": {
|
||||
Name: "xfs",
|
||||
},
|
||||
}
|
||||
|
||||
for iterator := volumes.Iterator(); iterator.Next(); {
|
||||
dv := iterator.Value()
|
||||
|
||||
partitionLabel := dv.TypedSpec().PartitionLabel
|
||||
filesystemLabel := dv.TypedSpec().Label
|
||||
|
||||
// this is encrypted partition, skip it, we should see another device with actual filesystem
|
||||
if dv.TypedSpec().Name == "luks" {
|
||||
continue
|
||||
}
|
||||
|
||||
// match either by partition or filesystem label
|
||||
id := partitionLabel
|
||||
|
||||
expected, ok := expectedVolumes[id]
|
||||
if !ok {
|
||||
id = filesystemLabel
|
||||
|
||||
expected, ok = expectedVolumes[id]
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
suite.Assert().Equal(expected.Name, dv.TypedSpec().Name)
|
||||
|
||||
delete(expectedVolumes, id)
|
||||
}
|
||||
|
||||
suite.Assert().Empty(expectedVolumes)
|
||||
}
|
||||
|
||||
func init() {
|
||||
allSuites = append(allSuites, new(VolumesSuite))
|
||||
}
|
@ -152,6 +152,7 @@ type Capabilities struct {
|
||||
RunsTalosKernel bool
|
||||
SupportsReboot bool
|
||||
SupportsRecover bool
|
||||
SupportsVolumes bool
|
||||
SecureBooted bool
|
||||
}
|
||||
|
||||
@ -169,6 +170,7 @@ func (apiSuite *APISuite) Capabilities() Capabilities {
|
||||
caps.RunsTalosKernel = true
|
||||
caps.SupportsReboot = true
|
||||
caps.SupportsRecover = true
|
||||
caps.SupportsVolumes = true
|
||||
}
|
||||
}
|
||||
|
||||
|
433
pkg/machinery/api/resource/definitions/block/block.pb.go
Normal file
433
pkg/machinery/api/resource/definitions/block/block.pb.go
Normal file
@ -0,0 +1,433 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.33.0
|
||||
// protoc v4.25.3
|
||||
// source: resource/definitions/block/block.proto
|
||||
|
||||
package block
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// DeviceSpec is the spec for devices status.
|
||||
type DeviceSpec struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
|
||||
Major int64 `protobuf:"varint,2,opt,name=major,proto3" json:"major,omitempty"`
|
||||
Minor int64 `protobuf:"varint,3,opt,name=minor,proto3" json:"minor,omitempty"`
|
||||
PartitionName string `protobuf:"bytes,4,opt,name=partition_name,json=partitionName,proto3" json:"partition_name,omitempty"`
|
||||
PartitionNumber int64 `protobuf:"varint,5,opt,name=partition_number,json=partitionNumber,proto3" json:"partition_number,omitempty"`
|
||||
Generation int64 `protobuf:"varint,6,opt,name=generation,proto3" json:"generation,omitempty"`
|
||||
DevicePath string `protobuf:"bytes,7,opt,name=device_path,json=devicePath,proto3" json:"device_path,omitempty"`
|
||||
Parent string `protobuf:"bytes,8,opt,name=parent,proto3" json:"parent,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) Reset() {
|
||||
*x = DeviceSpec{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_resource_definitions_block_block_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DeviceSpec) ProtoMessage() {}
|
||||
|
||||
func (x *DeviceSpec) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_resource_definitions_block_block_proto_msgTypes[0]
|
||||
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 DeviceSpec.ProtoReflect.Descriptor instead.
|
||||
func (*DeviceSpec) Descriptor() ([]byte, []int) {
|
||||
return file_resource_definitions_block_block_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetMajor() int64 {
|
||||
if x != nil {
|
||||
return x.Major
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetMinor() int64 {
|
||||
if x != nil {
|
||||
return x.Minor
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetPartitionName() string {
|
||||
if x != nil {
|
||||
return x.PartitionName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetPartitionNumber() int64 {
|
||||
if x != nil {
|
||||
return x.PartitionNumber
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetGeneration() int64 {
|
||||
if x != nil {
|
||||
return x.Generation
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetDevicePath() string {
|
||||
if x != nil {
|
||||
return x.DevicePath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeviceSpec) GetParent() string {
|
||||
if x != nil {
|
||||
return x.Parent
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// DiscoveredVolumeSpec is the spec for DiscoveredVolumes status.
|
||||
type DiscoveredVolumeSpec struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Size uint64 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"`
|
||||
SectorSize uint64 `protobuf:"varint,2,opt,name=sector_size,json=sectorSize,proto3" json:"sector_size,omitempty"`
|
||||
IoSize uint64 `protobuf:"varint,3,opt,name=io_size,json=ioSize,proto3" json:"io_size,omitempty"`
|
||||
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Uuid string `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"`
|
||||
Label string `protobuf:"bytes,6,opt,name=label,proto3" json:"label,omitempty"`
|
||||
BlockSize uint32 `protobuf:"varint,7,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"`
|
||||
FilesystemBlockSize uint32 `protobuf:"varint,8,opt,name=filesystem_block_size,json=filesystemBlockSize,proto3" json:"filesystem_block_size,omitempty"`
|
||||
ProbedSize uint64 `protobuf:"varint,9,opt,name=probed_size,json=probedSize,proto3" json:"probed_size,omitempty"`
|
||||
PartitionUuid string `protobuf:"bytes,10,opt,name=partition_uuid,json=partitionUuid,proto3" json:"partition_uuid,omitempty"`
|
||||
PartitionType string `protobuf:"bytes,11,opt,name=partition_type,json=partitionType,proto3" json:"partition_type,omitempty"`
|
||||
PartitionLabel string `protobuf:"bytes,12,opt,name=partition_label,json=partitionLabel,proto3" json:"partition_label,omitempty"`
|
||||
PartitionIndex uint64 `protobuf:"varint,13,opt,name=partition_index,json=partitionIndex,proto3" json:"partition_index,omitempty"`
|
||||
Type string `protobuf:"bytes,14,opt,name=type,proto3" json:"type,omitempty"`
|
||||
DevicePath string `protobuf:"bytes,15,opt,name=device_path,json=devicePath,proto3" json:"device_path,omitempty"`
|
||||
Parent string `protobuf:"bytes,16,opt,name=parent,proto3" json:"parent,omitempty"`
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) Reset() {
|
||||
*x = DiscoveredVolumeSpec{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_resource_definitions_block_block_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DiscoveredVolumeSpec) ProtoMessage() {}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_resource_definitions_block_block_proto_msgTypes[1]
|
||||
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 DiscoveredVolumeSpec.ProtoReflect.Descriptor instead.
|
||||
func (*DiscoveredVolumeSpec) Descriptor() ([]byte, []int) {
|
||||
return file_resource_definitions_block_block_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetSize() uint64 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetSectorSize() uint64 {
|
||||
if x != nil {
|
||||
return x.SectorSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetIoSize() uint64 {
|
||||
if x != nil {
|
||||
return x.IoSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetUuid() string {
|
||||
if x != nil {
|
||||
return x.Uuid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetLabel() string {
|
||||
if x != nil {
|
||||
return x.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetBlockSize() uint32 {
|
||||
if x != nil {
|
||||
return x.BlockSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetFilesystemBlockSize() uint32 {
|
||||
if x != nil {
|
||||
return x.FilesystemBlockSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetProbedSize() uint64 {
|
||||
if x != nil {
|
||||
return x.ProbedSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetPartitionUuid() string {
|
||||
if x != nil {
|
||||
return x.PartitionUuid
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetPartitionType() string {
|
||||
if x != nil {
|
||||
return x.PartitionType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetPartitionLabel() string {
|
||||
if x != nil {
|
||||
return x.PartitionLabel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetPartitionIndex() uint64 {
|
||||
if x != nil {
|
||||
return x.PartitionIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetDevicePath() string {
|
||||
if x != nil {
|
||||
return x.DevicePath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DiscoveredVolumeSpec) GetParent() string {
|
||||
if x != nil {
|
||||
return x.Parent
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_resource_definitions_block_block_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_resource_definitions_block_block_proto_rawDesc = []byte{
|
||||
0x0a, 0x26, 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, 0x2f, 0x62, 0x6c, 0x6f,
|
||||
0x63, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e,
|
||||
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0xf7, 0x01, 0x0a, 0x0a, 0x44,
|
||||
0x65, 0x76, 0x69, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6d, 0x61,
|
||||
0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x03, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x72,
|
||||
0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x75,
|
||||
0x6d, 0x62, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x61, 0x72, 0x74,
|
||||
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x67,
|
||||
0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||
0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x64,
|
||||
0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x07, 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, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61,
|
||||
0x72, 0x65, 0x6e, 0x74, 0x22, 0x83, 0x04, 0x0a, 0x14, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65,
|
||||
0x72, 0x65, 0x64, 0x56, 0x6f, 0x6c, 0x75, 0x6d, 0x65, 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, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x69,
|
||||
0x7a, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x6f, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x04, 0x52, 0x06, 0x69, 0x6f, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
|
||||
0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75,
|
||||
0x75, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x06, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x62, 0x6c, 0x6f,
|
||||
0x63, 0x6b, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x62,
|
||||
0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x66, 0x69, 0x6c, 0x65,
|
||||
0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x73, 0x69, 0x7a,
|
||||
0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x13, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73,
|
||||
0x74, 0x65, 0x6d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
|
||||
0x70, 0x72, 0x6f, 0x62, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28,
|
||||
0x04, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x62, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x25, 0x0a,
|
||||
0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x55, 0x75, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x61,
|
||||
0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x70,
|
||||
0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x0c,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x4c,
|
||||
0x61, 0x62, 0x65, 0x6c, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x70,
|
||||
0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a,
|
||||
0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70,
|
||||
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,
|
||||
}
|
||||
|
||||
var (
|
||||
file_resource_definitions_block_block_proto_rawDescOnce sync.Once
|
||||
file_resource_definitions_block_block_proto_rawDescData = file_resource_definitions_block_block_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_resource_definitions_block_block_proto_rawDescGZIP() []byte {
|
||||
file_resource_definitions_block_block_proto_rawDescOnce.Do(func() {
|
||||
file_resource_definitions_block_block_proto_rawDescData = protoimpl.X.CompressGZIP(file_resource_definitions_block_block_proto_rawDescData)
|
||||
})
|
||||
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_goTypes = []interface{}{
|
||||
(*DeviceSpec)(nil), // 0: talos.resource.definitions.block.DeviceSpec
|
||||
(*DiscoveredVolumeSpec)(nil), // 1: talos.resource.definitions.block.DiscoveredVolumeSpec
|
||||
}
|
||||
var file_resource_definitions_block_block_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_resource_definitions_block_block_proto_init() }
|
||||
func file_resource_definitions_block_block_proto_init() {
|
||||
if File_resource_definitions_block_block_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_resource_definitions_block_block_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeviceSpec); 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[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DiscoveredVolumeSpec); 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{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_resource_definitions_block_block_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_resource_definitions_block_block_proto_goTypes,
|
||||
DependencyIndexes: file_resource_definitions_block_block_proto_depIdxs,
|
||||
MessageInfos: file_resource_definitions_block_block_proto_msgTypes,
|
||||
}.Build()
|
||||
File_resource_definitions_block_block_proto = out.File
|
||||
file_resource_definitions_block_block_proto_rawDesc = nil
|
||||
file_resource_definitions_block_block_proto_goTypes = nil
|
||||
file_resource_definitions_block_block_proto_depIdxs = nil
|
||||
}
|
1067
pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go
Normal file
1067
pkg/machinery/api/resource/definitions/block/block_vtproto.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
17
pkg/machinery/resources/block/block.go
Normal file
17
pkg/machinery/resources/block/block.go
Normal file
@ -0,0 +1,17 @@
|
||||
// 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 provides resources related to blockdevices, mounts, etc.
|
||||
package block
|
||||
|
||||
import (
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
|
||||
"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 .
|
||||
|
||||
// NamespaceName contains configuration resources.
|
||||
const NamespaceName resource.Namespace = v1alpha1.NamespaceName
|
33
pkg/machinery/resources/block/block_test.go
Normal file
33
pkg/machinery/resources/block/block_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// 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_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/resource/meta"
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
|
||||
"github.com/cosi-project/runtime/pkg/state/registry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
)
|
||||
|
||||
func TestRegisterResource(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
|
||||
resources := state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
resourceRegistry := registry.NewResourceRegistry(resources)
|
||||
|
||||
for _, resource := range []meta.ResourceWithRD{
|
||||
&block.Device{},
|
||||
&block.DiscoveredVolume{},
|
||||
} {
|
||||
assert.NoError(t, resourceRegistry.Register(ctx, resource))
|
||||
}
|
||||
}
|
19
pkg/machinery/resources/block/deep_copy.generated.go
Normal file
19
pkg/machinery/resources/block/deep_copy.generated.go
Normal file
@ -0,0 +1,19 @@
|
||||
// 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/.
|
||||
|
||||
// Code generated by "deep-copy -type DeviceSpec -type DiscoveredVolumeSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go ."; DO NOT EDIT.
|
||||
|
||||
package block
|
||||
|
||||
// DeepCopy generates a deep copy of DeviceSpec.
|
||||
func (o DeviceSpec) DeepCopy() DeviceSpec {
|
||||
var cp DeviceSpec = o
|
||||
return cp
|
||||
}
|
||||
|
||||
// DeepCopy generates a deep copy of DiscoveredVolumeSpec.
|
||||
func (o DiscoveredVolumeSpec) DeepCopy() DiscoveredVolumeSpec {
|
||||
var cp DiscoveredVolumeSpec = o
|
||||
return cp
|
||||
}
|
81
pkg/machinery/resources/block/device.go
Normal file
81
pkg/machinery/resources/block/device.go
Normal file
@ -0,0 +1,81 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// DeviceType is type of BlockDevice resource.
|
||||
const DeviceType = resource.Type("BlockDevices.block.talos.dev")
|
||||
|
||||
// Device resource holds status of hardware devices (overall).
|
||||
type Device = typed.Resource[DeviceSpec, DeviceExtension]
|
||||
|
||||
// DeviceSpec is the spec for devices status.
|
||||
//
|
||||
//gotagsrewrite:gen
|
||||
type DeviceSpec struct {
|
||||
Type string `yaml:"type" protobuf:"1"`
|
||||
Major int `yaml:"major" protobuf:"2"`
|
||||
Minor int `yaml:"minor" protobuf:"3"`
|
||||
PartitionName string `yaml:"partitionName,omitempty" protobuf:"4"`
|
||||
PartitionNumber int `yaml:"partitionNumber,omitempty" protobuf:"5"`
|
||||
DevicePath string `yaml:"devicePath" protobuf:"7"`
|
||||
|
||||
// Generation is bumped every time the device might have changed and might need to be re-probed.
|
||||
Generation int `yaml:"generation" protobuf:"6"`
|
||||
|
||||
// Parent (if set) specifies the parent device ID.
|
||||
Parent string `yaml:"parent,omitempty" protobuf:"8"`
|
||||
}
|
||||
|
||||
// NewDevice initializes a BlockDevice resource.
|
||||
func NewDevice(namespace resource.Namespace, id resource.ID) *Device {
|
||||
return typed.NewResource[DeviceSpec, DeviceExtension](
|
||||
resource.NewMetadata(namespace, DeviceType, id, resource.VersionUndefined),
|
||||
DeviceSpec{},
|
||||
)
|
||||
}
|
||||
|
||||
// DeviceExtension is auxiliary resource data for BlockDevice.
|
||||
type DeviceExtension struct{}
|
||||
|
||||
// ResourceDefinition implements meta.ResourceDefinitionProvider interface.
|
||||
func (DeviceExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
|
||||
return meta.ResourceDefinitionSpec{
|
||||
Type: DeviceType,
|
||||
Aliases: []resource.Type{},
|
||||
DefaultNamespace: NamespaceName,
|
||||
PrintColumns: []meta.PrintColumn{
|
||||
{
|
||||
Name: "Type",
|
||||
JSONPath: `{.type}`,
|
||||
},
|
||||
{
|
||||
Name: "PartitionName",
|
||||
JSONPath: `{.partitionName}`,
|
||||
},
|
||||
{
|
||||
Name: "Generation",
|
||||
JSONPath: `{.generation}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterDefaultTypes()
|
||||
|
||||
err := protobuf.RegisterDynamic[DeviceSpec](DeviceType, &Device{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
102
pkg/machinery/resources/block/discovered_volume.go
Normal file
102
pkg/machinery/resources/block/discovered_volume.go
Normal file
@ -0,0 +1,102 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// DiscoveredVolumeType is type of BlockDiscoveredVolume resource.
|
||||
const DiscoveredVolumeType = resource.Type("DiscoveredVolumes.block.talos.dev")
|
||||
|
||||
// DiscoveredVolume resource holds status of hardware DiscoveredVolumes (overall).
|
||||
type DiscoveredVolume = typed.Resource[DiscoveredVolumeSpec, DiscoveredVolumeExtension]
|
||||
|
||||
// DiscoveredVolumeSpec is the spec for DiscoveredVolumes status.
|
||||
//
|
||||
//gotagsrewrite:gen
|
||||
type DiscoveredVolumeSpec struct {
|
||||
Type string `yaml:"type" protobuf:"14"`
|
||||
DevicePath string `yaml:"devicePath" protobuf:"15"`
|
||||
Parent string `yaml:"parent,omitempty" protobuf:"16"`
|
||||
|
||||
// Overall size of the probed device (in bytes).
|
||||
Size uint64 `yaml:"size" protobuf:"1"`
|
||||
|
||||
// Sector size of the device (in bytes).
|
||||
SectorSize uint `yaml:"sectorSize,omitempty" protobuf:"2"`
|
||||
|
||||
// Optimal I/O size for the device (in bytes).
|
||||
IOSize uint `yaml:"ioSize,omitempty" protobuf:"3"`
|
||||
|
||||
Name string `yaml:"name" protobuf:"4"`
|
||||
UUID string `yaml:"uuid,omitempty" protobuf:"5"`
|
||||
Label string `yaml:"label,omitempty" protobuf:"6"`
|
||||
|
||||
BlockSize uint32 `yaml:"blockSize,omitempty" protobuf:"7"`
|
||||
FilesystemBlockSize uint32 `yaml:"filesystemBlockSize,omitempty" protobuf:"8"`
|
||||
ProbedSize uint64 `yaml:"probedSize,omitempty" protobuf:"9"`
|
||||
|
||||
PartitionUUID string `yaml:"partitionUUID,omitempty" protobuf:"10"`
|
||||
PartitionType string `yaml:"partitionType,omitempty" protobuf:"11"`
|
||||
PartitionLabel string `yaml:"partitionLabel,omitempty" protobuf:"12"`
|
||||
PartitionIndex uint `yaml:"partitionIndex,omitempty" protobuf:"13"`
|
||||
}
|
||||
|
||||
// NewDiscoveredVolume initializes a BlockDiscoveredVolume resource.
|
||||
func NewDiscoveredVolume(namespace resource.Namespace, id resource.ID) *DiscoveredVolume {
|
||||
return typed.NewResource[DiscoveredVolumeSpec, DiscoveredVolumeExtension](
|
||||
resource.NewMetadata(namespace, DiscoveredVolumeType, id, resource.VersionUndefined),
|
||||
DiscoveredVolumeSpec{},
|
||||
)
|
||||
}
|
||||
|
||||
// DiscoveredVolumeExtension is auxiliary resource data for BlockDiscoveredVolume.
|
||||
type DiscoveredVolumeExtension struct{}
|
||||
|
||||
// ResourceDefinition implements meta.ResourceDefinitionProvider interface.
|
||||
func (DiscoveredVolumeExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
|
||||
return meta.ResourceDefinitionSpec{
|
||||
Type: DiscoveredVolumeType,
|
||||
Aliases: []resource.Type{},
|
||||
DefaultNamespace: NamespaceName,
|
||||
PrintColumns: []meta.PrintColumn{
|
||||
{
|
||||
Name: "Type",
|
||||
JSONPath: `{.type}`,
|
||||
},
|
||||
{
|
||||
Name: "Size",
|
||||
JSONPath: `{.size}`,
|
||||
},
|
||||
{
|
||||
Name: "Discovered",
|
||||
JSONPath: `{.name}`,
|
||||
},
|
||||
{
|
||||
Name: "Label",
|
||||
JSONPath: `{.label}`,
|
||||
},
|
||||
{
|
||||
Name: "PartitionLabel",
|
||||
JSONPath: `{.partitionLabel}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterDefaultTypes()
|
||||
|
||||
err := protobuf.RegisterDynamic[DiscoveredVolumeSpec](DiscoveredVolumeType, &DiscoveredVolume{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -11,12 +11,8 @@ import (
|
||||
"github.com/cosi-project/runtime/pkg/resource/typed"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/proto"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1"
|
||||
)
|
||||
|
||||
// NamespaceName contains configuration resources.
|
||||
const NamespaceName resource.Namespace = v1alpha1.NamespaceName
|
||||
|
||||
// KernelParamSpecType is type of KernelParam resource.
|
||||
const KernelParamSpecType = resource.Type("KernelParamSpecs.runtime.talos.dev")
|
||||
|
||||
|
@ -4,4 +4,13 @@
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"github.com/cosi-project/runtime/pkg/resource"
|
||||
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1"
|
||||
)
|
||||
|
||||
//go:generate deep-copy -type DevicesStatusSpec -type EventSinkConfigSpec -type ExtensionServiceConfigSpec -type ExtensionServiceConfigStatusSpec -type KernelModuleSpecSpec -type KernelParamSpecSpec -type KernelParamStatusSpec -type KmsgLogConfigSpec -type MaintenanceServiceConfigSpec -type MaintenanceServiceRequestSpec -type MachineResetSignalSpec -type MachineStatusSpec -type MetaKeySpec -type MountStatusSpec -type PlatformMetadataSpec -type SecurityStateSpec -type MetaLoadedSpec -type UniqueMachineTokenSpec -header-file ../../../../hack/boilerplate.txt -o deep_copy.generated.go .
|
||||
|
||||
// NamespaceName contains configuration resources.
|
||||
const NamespaceName resource.Namespace = v1alpha1.NamespaceName
|
||||
|
@ -25,6 +25,10 @@ description: Talos gRPC API reference.
|
||||
|
||||
- [File-level Extensions](#common/common.proto-extensions)
|
||||
|
||||
- [resource/definitions/block/block.proto](#resource/definitions/block/block.proto)
|
||||
- [DeviceSpec](#talos.resource.definitions.block.DeviceSpec)
|
||||
- [DiscoveredVolumeSpec](#talos.resource.definitions.block.DiscoveredVolumeSpec)
|
||||
|
||||
- [resource/definitions/cluster/cluster.proto](#resource/definitions/cluster/cluster.proto)
|
||||
- [AffiliateSpec](#talos.resource.definitions.cluster.AffiliateSpec)
|
||||
- [ConfigSpec](#talos.resource.definitions.cluster.ConfigSpec)
|
||||
@ -730,6 +734,74 @@ Common metadata message nested in all reply message types
|
||||
|
||||
|
||||
|
||||
<a name="resource/definitions/block/block.proto"></a>
|
||||
<p align="right"><a href="#top">Top</a></p>
|
||||
|
||||
## resource/definitions/block/block.proto
|
||||
|
||||
|
||||
|
||||
<a name="talos.resource.definitions.block.DeviceSpec"></a>
|
||||
|
||||
### DeviceSpec
|
||||
DeviceSpec is the spec for devices status.
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| type | [string](#string) | | |
|
||||
| major | [int64](#int64) | | |
|
||||
| minor | [int64](#int64) | | |
|
||||
| partition_name | [string](#string) | | |
|
||||
| partition_number | [int64](#int64) | | |
|
||||
| generation | [int64](#int64) | | |
|
||||
| device_path | [string](#string) | | |
|
||||
| parent | [string](#string) | | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="talos.resource.definitions.block.DiscoveredVolumeSpec"></a>
|
||||
|
||||
### DiscoveredVolumeSpec
|
||||
DiscoveredVolumeSpec is the spec for DiscoveredVolumes status.
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| size | [uint64](#uint64) | | |
|
||||
| sector_size | [uint64](#uint64) | | |
|
||||
| io_size | [uint64](#uint64) | | |
|
||||
| name | [string](#string) | | |
|
||||
| uuid | [string](#string) | | |
|
||||
| label | [string](#string) | | |
|
||||
| block_size | [uint32](#uint32) | | |
|
||||
| filesystem_block_size | [uint32](#uint32) | | |
|
||||
| probed_size | [uint64](#uint64) | | |
|
||||
| partition_uuid | [string](#string) | | |
|
||||
| partition_type | [string](#string) | | |
|
||||
| partition_label | [string](#string) | | |
|
||||
| partition_index | [uint64](#uint64) | | |
|
||||
| type | [string](#string) | | |
|
||||
| device_path | [string](#string) | | |
|
||||
| parent | [string](#string) | | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- end messages -->
|
||||
|
||||
<!-- end enums -->
|
||||
|
||||
<!-- end HasExtensions -->
|
||||
|
||||
<!-- end services -->
|
||||
|
||||
|
||||
|
||||
<a name="resource/definitions/cluster/cluster.proto"></a>
|
||||
<p align="right"><a href="#top">Top</a></p>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user