Skip to content

Commit ec3bd87

Browse files
committed
refactor: remove the go-blockdevice v1 completely
This is driven by a bug in `nocloud`, but it was long overdue to nuke the old version of the library completely and rely on new code. This refactors all four platforms which do load something from a mounted disk into one implementation. Fixes #11948 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent 33544bd commit ec3bd87

File tree

9 files changed

+296
-233
lines changed

9 files changed

+296
-233
lines changed

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ require (
144144
github.com/siderolabs/discovery-client v0.1.13
145145
github.com/siderolabs/gen v0.8.5
146146
github.com/siderolabs/go-api-signature v0.3.8
147-
github.com/siderolabs/go-blockdevice v0.4.8
148147
github.com/siderolabs/go-blockdevice/v2 v2.0.19
149148
github.com/siderolabs/go-circular v0.2.3
150149
github.com/siderolabs/go-cmd v0.1.3

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,8 +617,6 @@ github.com/siderolabs/gen v0.8.5 h1:xlWXTynnGD/epaj7uplvKvmAkBH+Fp51bLnw1JC0xME=
617617
github.com/siderolabs/gen v0.8.5/go.mod h1:CRrktDXQf3yDJI7xKv+cDYhBbKdfd/YE16OpgcHoT9E=
618618
github.com/siderolabs/go-api-signature v0.3.8 h1:0iTcOWIxOAc7M8aB2L+WScUd4BoqdXshvQ4h9tSSeF8=
619619
github.com/siderolabs/go-api-signature v0.3.8/go.mod h1:MQy+DcXCQIFFXZr+E4tbMmnQSQs7WpubSpJFRN694mI=
620-
github.com/siderolabs/go-blockdevice v0.4.8 h1:KfdWvIx0Jft5YVuCsFIJFwjWEF1oqtzkgX9PeU9cX4c=
621-
github.com/siderolabs/go-blockdevice v0.4.8/go.mod h1:4PeOuk71pReJj1JQEXDE7kIIQJPVe8a+HZQa+qjxSEA=
622620
github.com/siderolabs/go-blockdevice/v2 v2.0.19 h1:V2a7HHzXlgMcZWUxpYp6BdapnwJGDAs/oGy1zkPicNA=
623621
github.com/siderolabs/go-blockdevice/v2 v2.0.19/go.mod h1:nqUmQxOSex/Sg70x+QyhdE/VtXZ6Q53Z4FoR2RFBzZU=
624622
github.com/siderolabs/go-circular v0.2.3 h1:GKkA1Tw79kEFGtWdl7WTxEUTbwtklITeiRT0V1McHrA=

internal/app/machined/pkg/controllers/block/internal/volumes/format.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ func Format(ctx context.Context, logger *zap.Logger, volumeContext ManagerContex
5454

5555
switch {
5656
case volumeContext.Cfg.TypedSpec().Provisioning.FilesystemSpec.Type == block.FilesystemTypeNone:
57+
volumeContext.Status.Filesystem, _ = block.FilesystemTypeString(info.Name) //nolint:errcheck
58+
5759
// this is mountable
5860
if volumeContext.Cfg.TypedSpec().Mount.TargetPath != "" {
5961
switch info.Name {
@@ -65,10 +67,6 @@ func Format(ctx context.Context, logger *zap.Logger, volumeContext ManagerContex
6567

6668
return fmt.Errorf("volume is encrypted, but no encryption config provided")
6769
}
68-
69-
volumeContext.Status.Filesystem, _ = block.FilesystemTypeString(info.Name) //nolint:errcheck
70-
} else {
71-
volumeContext.Status.Filesystem = block.FilesystemTypeNone
7270
}
7371

7472
volumeContext.Status.Phase = block.VolumePhaseReady
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
// Package blockutils provides volume-related helpers for platform implementation.
6+
package blockutils
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"io/fs"
12+
"log"
13+
"time"
14+
15+
"github.com/cosi-project/runtime/pkg/resource"
16+
"github.com/cosi-project/runtime/pkg/safe"
17+
"github.com/cosi-project/runtime/pkg/state"
18+
"github.com/google/cel-go/common/ast"
19+
"github.com/google/cel-go/common/operators"
20+
"github.com/google/cel-go/common/types"
21+
"github.com/siderolabs/gen/xslices"
22+
23+
"github.com/siderolabs/talos/internal/pkg/mount/v3"
24+
"github.com/siderolabs/talos/pkg/machinery/cel"
25+
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
26+
"github.com/siderolabs/talos/pkg/machinery/resources/block"
27+
"github.com/siderolabs/talos/pkg/xfs"
28+
"github.com/siderolabs/talos/pkg/xfs/fsopen"
29+
)
30+
31+
// VolumeMatch returns a CEL expression that matches a volume by filesystem or partition label.
32+
func VolumeMatch(labels []string) (*cel.Expression, error) {
33+
builder := cel.NewBuilder(celenv.VolumeLocator())
34+
35+
// "(volume.label in ['%s', ...] || volume.partition_label in ['%s', ...]) && volume.name != ''"
36+
expr := builder.NewCall(
37+
builder.NextID(),
38+
operators.LogicalAnd,
39+
builder.NewCall(
40+
builder.NextID(),
41+
operators.LogicalOr,
42+
builder.NewCall(
43+
builder.NextID(),
44+
operators.In,
45+
builder.NewSelect(
46+
builder.NextID(),
47+
builder.NewIdent(builder.NextID(), "volume"),
48+
"label",
49+
),
50+
builder.NewList(
51+
builder.NextID(),
52+
xslices.Map(labels, func(label string) ast.Expr {
53+
return builder.NewLiteral(builder.NextID(), types.String(label))
54+
}),
55+
nil,
56+
),
57+
),
58+
builder.NewCall(
59+
builder.NextID(),
60+
operators.In,
61+
builder.NewSelect(
62+
builder.NextID(),
63+
builder.NewIdent(builder.NextID(), "volume"),
64+
"partition_label",
65+
),
66+
builder.NewList(
67+
builder.NextID(),
68+
xslices.Map(labels, func(label string) ast.Expr {
69+
return builder.NewLiteral(builder.NextID(), types.String(label))
70+
}),
71+
nil,
72+
),
73+
),
74+
),
75+
builder.NewCall(
76+
builder.NextID(),
77+
operators.NotEquals,
78+
builder.NewSelect(
79+
builder.NextID(),
80+
builder.NewIdent(builder.NextID(), "volume"),
81+
"name",
82+
),
83+
builder.NewLiteral(builder.NextID(), types.String("")),
84+
),
85+
)
86+
87+
boolExpr, err := builder.ToBooleanExpression(expr)
88+
if err != nil {
89+
return nil, fmt.Errorf("error creating boolean expression: %w", err)
90+
}
91+
92+
return boolExpr, nil
93+
}
94+
95+
// ReadFromVolume tries to find a volume with the given label, mounts it
96+
// as read-only, calls the provided function with xfs.Root and unmounts it.
97+
//
98+
// If the volume wasn't found, fs.ErrNotExist is returned.
99+
func ReadFromVolume(ctx context.Context, r state.State, labels []string, cb func(xfs.Root, *block.VolumeStatus) error) error {
100+
if len(labels) == 0 {
101+
panic("at least one label must be provided")
102+
}
103+
104+
volumeID := "platform/" + labels[0] + "/config"
105+
106+
matchExr, err := VolumeMatch(labels)
107+
if err != nil {
108+
return fmt.Errorf("error creating volume match expression: %w", err)
109+
}
110+
111+
// create a volume which matches the expected filesystem label
112+
vc := block.NewVolumeConfig(block.NamespaceName, volumeID)
113+
vc.Metadata().Labels().Set(block.PlatformLabel, "")
114+
vc.TypedSpec().Type = block.VolumeTypePartition
115+
vc.TypedSpec().Locator = block.LocatorSpec{
116+
Match: *matchExr,
117+
}
118+
119+
if err := r.Create(ctx, vc); err != nil && !state.IsConflictError(err) {
120+
return fmt.Errorf("error creating user disk volume configuration: %w", err)
121+
}
122+
123+
defer func() {
124+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
125+
defer cancel()
126+
127+
if err := r.TeardownAndDestroy(ctx, vc.Metadata()); err != nil {
128+
log.Printf("error destroying volume config %s/%s: %v", vc.Metadata().Namespace(), vc.Metadata().ID(), err)
129+
}
130+
}()
131+
132+
// wait for the volume to be either ready or missing (includes waiting for devices to be ready)
133+
volumeStatus, err := safe.StateWatchFor[*block.VolumeStatus](ctx,
134+
r,
135+
block.NewVolumeStatus(vc.Metadata().Namespace(), vc.Metadata().ID()).Metadata(),
136+
state.WithEventTypes(state.Created, state.Updated),
137+
state.WithCondition(func(r resource.Resource) (bool, error) {
138+
phase := r.(*block.VolumeStatus).TypedSpec().Phase
139+
140+
return phase == block.VolumePhaseReady || phase == block.VolumePhaseMissing, nil
141+
}),
142+
)
143+
if err != nil {
144+
return fmt.Errorf("failed to watch for volume status: %w", err)
145+
}
146+
147+
if volumeStatus.TypedSpec().Phase == block.VolumePhaseMissing {
148+
return fmt.Errorf("failed to find volume with machine configuration %s: %w", vc.TypedSpec().Locator.Match, fs.ErrNotExist)
149+
}
150+
151+
manager := mount.NewManager(
152+
mount.WithReadOnly(),
153+
mount.WithPrinter(log.Printf),
154+
mount.WithFsopen(
155+
volumeStatus.TypedSpec().Filesystem.String(),
156+
fsopen.WithSource(volumeStatus.TypedSpec().MountLocation),
157+
),
158+
mount.WithDetached(),
159+
)
160+
161+
// mount the volume, unmount when done
162+
p, err := manager.Mount()
163+
if err != nil {
164+
return fmt.Errorf("failed to mount volume: %w", err)
165+
}
166+
167+
defer manager.Unmount() //nolint:errcheck
168+
169+
return cb(p.Root(), volumeStatus)
170+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package blockutils_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/blockutils"
14+
"github.com/siderolabs/talos/pkg/machinery/constants"
15+
)
16+
17+
func TestVolumeMatch(t *testing.T) {
18+
t.Parallel()
19+
20+
expr, err := blockutils.VolumeMatch([]string{constants.MetalConfigISOLabel})
21+
require.NoError(t, err)
22+
23+
assert.Equal(t, `(volume.label in ["metal-iso"] || volume.partition_label in ["metal-iso"]) && volume.name != ""`, expr.String())
24+
}

internal/app/machined/pkg/runtime/v1alpha1/platform/metal/metal.go

Lines changed: 18 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,8 @@ import (
1111
"fmt"
1212
"io/fs"
1313
"log"
14-
"os"
15-
"path/filepath"
1614
"time"
1715

18-
"github.com/cosi-project/runtime/pkg/resource"
19-
"github.com/cosi-project/runtime/pkg/safe"
2016
"github.com/cosi-project/runtime/pkg/state"
2117
"github.com/siderolabs/gen/channel"
2218
"github.com/siderolabs/go-pointer"
@@ -26,24 +22,18 @@ import (
2622

2723
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
2824
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
25+
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/blockutils"
2926
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/netutils"
3027
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/oauth2"
3128
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal/url"
32-
"github.com/siderolabs/talos/internal/pkg/mount/v3"
3329
"github.com/siderolabs/talos/pkg/download"
34-
"github.com/siderolabs/talos/pkg/machinery/cel"
35-
"github.com/siderolabs/talos/pkg/machinery/cel/celenv"
3630
"github.com/siderolabs/talos/pkg/machinery/constants"
3731
"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
3832
"github.com/siderolabs/talos/pkg/machinery/meta"
3933
"github.com/siderolabs/talos/pkg/machinery/resources/block"
4034
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
4135
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
42-
"github.com/siderolabs/talos/pkg/xfs/fsopen"
43-
)
44-
45-
const (
46-
mnt = "/mnt"
36+
"github.com/siderolabs/talos/pkg/xfs"
4737
)
4838

4939
// Metal is a discoverer for non-cloud environments.
@@ -131,81 +121,28 @@ func (m *Metal) Mode() runtime.Mode {
131121
return runtime.ModeMetal
132122
}
133123

134-
func metalISOMatch() cel.Expression {
135-
return cel.MustExpression(cel.ParseBooleanExpression(
136-
fmt.Sprintf("volume.label == '%s' || volume.partition_label == '%s'", constants.MetalConfigISOLabel, constants.MetalConfigISOLabel),
137-
celenv.VolumeLocator(),
138-
))
139-
}
140-
141124
func readConfigFromISO(ctx context.Context, r state.State) ([]byte, error) {
142-
volumeID := "platform/metal/config"
143-
144-
// create a volume which matches the expected filesystem label
145-
vc := block.NewVolumeConfig(block.NamespaceName, volumeID)
146-
vc.Metadata().Labels().Set(block.PlatformLabel, "")
147-
vc.TypedSpec().Type = block.VolumeTypePartition
148-
vc.TypedSpec().Locator = block.LocatorSpec{
149-
Match: metalISOMatch(),
150-
}
151-
vc.TypedSpec().Mount = block.MountSpec{
152-
TargetPath: mnt,
153-
}
154-
155-
if err := r.Create(ctx, vc); err != nil && !state.IsConflictError(err) {
156-
return nil, fmt.Errorf("error creating user disk volume configuration: %w", err)
157-
}
158-
159-
// wait for the volume to be either ready or missing (includes waiting for devices to be ready)
160-
volumeStatus, err := safe.StateWatchFor[*block.VolumeStatus](ctx,
161-
r,
162-
block.NewVolumeStatus(vc.Metadata().Namespace(), vc.Metadata().ID()).Metadata(),
163-
state.WithEventTypes(state.Created, state.Updated),
164-
state.WithCondition(func(r resource.Resource) (bool, error) {
165-
phase := r.(*block.VolumeStatus).TypedSpec().Phase
166-
167-
return phase == block.VolumePhaseReady || phase == block.VolumePhaseMissing, nil
168-
}),
169-
)
170-
if err != nil {
171-
return nil, fmt.Errorf("failed to watch for volume status: %w", err)
172-
}
125+
var b []byte
173126

174-
if volumeStatus.TypedSpec().Phase == block.VolumePhaseMissing {
175-
return nil, fmt.Errorf("failed to find volume with machine configuration %s", vc.TypedSpec().Locator.Match)
176-
}
127+
err := blockutils.ReadFromVolume(ctx, r, []string{constants.MetalConfigISOLabel}, func(root xfs.Root, volumeStatus *block.VolumeStatus) error {
128+
var err error
177129

178-
manager := mount.NewManager(
179-
mount.WithTarget(volumeStatus.TypedSpec().MountSpec.TargetPath),
180-
mount.WithReadOnly(),
181-
mount.WithPrinter(log.Printf),
182-
mount.WithFsopen(
183-
volumeStatus.TypedSpec().Filesystem.String(),
184-
fsopen.WithBoolParameter("ro"),
185-
fsopen.WithSource(volumeStatus.TypedSpec().MountLocation),
186-
),
187-
)
188-
189-
// mount the volume, unmount when done
190-
if _, err := manager.Mount(); err != nil {
191-
return nil, fmt.Errorf("failed to mount volume: %w", err)
192-
}
193-
194-
defer manager.Unmount() //nolint:errcheck
130+
b, err = xfs.ReadFile(root, constants.ConfigFilename)
131+
if err != nil {
132+
return fmt.Errorf("read config: %w", err)
133+
}
195134

196-
b, err := os.ReadFile(filepath.Join(mnt, constants.ConfigFilename))
197-
if err != nil {
198-
return nil, fmt.Errorf("read config: %w", err)
199-
}
135+
log.Printf("read machine config from volume: %s (filesystem %q, UUID %q, size %s)",
136+
volumeStatus.TypedSpec().Location,
137+
volumeStatus.TypedSpec().Filesystem,
138+
volumeStatus.TypedSpec().UUID,
139+
volumeStatus.TypedSpec().PrettySize,
140+
)
200141

201-
log.Printf("read machine config from volume: %s (filesystem %q, UUID %q, size %s)",
202-
volumeStatus.TypedSpec().Location,
203-
volumeStatus.TypedSpec().Filesystem,
204-
volumeStatus.TypedSpec().UUID,
205-
volumeStatus.TypedSpec().PrettySize,
206-
)
142+
return nil
143+
})
207144

208-
return b, nil
145+
return b, err
209146
}
210147

211148
// KernelArgs implements the runtime.Platform interface.

0 commit comments

Comments
 (0)