Skip to content

Commit e2e5f08

Browse files
committed
chore: migrate uncompressed resources data
Abusing generics to compress uncompressed data in `*omni.ConfigPatch`, `*omni.ClusterMachineConfig`, `*omni.RedactedClusterMachineConfig` and `*omni.ClusterMachineConfigPatches`. Fixes #853 Signed-off-by: Dmitriy Matrenichev <[email protected]>
1 parent fd888ab commit e2e5f08

File tree

3 files changed

+292
-44
lines changed

3 files changed

+292
-44
lines changed

internal/backend/runtime/omni/migration/manager.go

+4
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ func NewManager(state state.State, logger *zap.Logger) *Manager {
181181
callback: removeMaintenanceConfigPatchFinalizers,
182182
name: "removeMaintenanceConfigPatchFinalizers",
183183
},
184+
{
185+
callback: compressMachineConfigsAndPatches,
186+
name: "compressMachineConfigsAndPatches",
187+
},
184188
},
185189
}
186190
}

internal/backend/runtime/omni/migration/migration_test.go

+195-44
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ package migration_test
77

88
import (
99
"context"
10+
"encoding/hex"
1011
"errors"
1112
"fmt"
1213
"testing"
1314
"time"
1415

16+
"github.com/cosi-project/runtime/pkg/controller/generic"
1517
"github.com/cosi-project/runtime/pkg/controller/runtime"
1618
"github.com/cosi-project/runtime/pkg/resource"
19+
"github.com/cosi-project/runtime/pkg/resource/protobuf"
1720
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
1821
"github.com/cosi-project/runtime/pkg/resource/typed"
1922
"github.com/cosi-project/runtime/pkg/safe"
@@ -23,9 +26,11 @@ import (
2326
"github.com/google/uuid"
2427
"github.com/siderolabs/gen/pair"
2528
"github.com/siderolabs/gen/xslices"
29+
"github.com/siderolabs/gen/xtesting/must"
2630
"github.com/siderolabs/go-pointer"
2731
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
2832
"github.com/stretchr/testify/assert"
33+
"github.com/stretchr/testify/require"
2934
"github.com/stretchr/testify/suite"
3035
"go.uber.org/zap"
3136
"go.uber.org/zap/zaptest"
@@ -86,11 +91,7 @@ func (suite *MigrationSuite) TestClusterInfo() {
8691

8792
cluster, machine := suite.createCluster(ctx, "c1")
8893

89-
suite.Require().NoError(suite.manager.Run(ctx), migration.WithFilter(
90-
func(name string) bool {
91-
return name == "clusterInfo"
92-
},
93-
))
94+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("clusterInfo", "clusterInstallImageToTalosVersion"))))
9495

9596
var err error
9697

@@ -116,11 +117,7 @@ func (suite *MigrationSuite) TestClusterInfo() {
116117

117118
// This shouldn't happen: create old version of the cluster again and see it not being updated
118119
// as the DB version is already up-to-date.
119-
suite.Require().NoError(suite.manager.Run(ctx), migration.WithFilter(
120-
func(name string) bool {
121-
return name == "clusterInfo"
122-
},
123-
))
120+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("clusterInfo", "clusterInstallImageToTalosVersion"))))
124121

125122
cluster, err = safe.StateGet[*omni.Cluster](ctx, suite.state, cluster.Metadata())
126123

@@ -212,9 +209,7 @@ func (suite *MigrationSuite) Test_changePublicKeyOwner() {
212209
}
213210

214211
// test migration in isolation
215-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
216-
return name == "changePublicKeyOwner"
217-
})))
212+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("changePublicKeyOwner"))))
218213

219214
keyVerifier := func(key *authres.PublicKey, expectedVersion int) {
220215
result, err := safe.StateGet[*authres.PublicKey](ctx, suite.state, key.Metadata())
@@ -439,9 +434,7 @@ func (suite *MigrationSuite) TestUserDefaultScopes() {
439434
suite.Require().NoError(suite.state.Create(ctx, user3))
440435

441436
// test migration in isolation
442-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
443-
return name == "addDefaultScopesToUsers"
444-
})))
437+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("addDefaultScopesToUsers"))))
445438

446439
user1, err = safe.StateGet[*authres.User](ctx, suite.state, user1.Metadata())
447440
suite.Require().NoError(err)
@@ -743,9 +736,7 @@ func (suite *MigrationSuite) TestAddServiceAccountScopesToUsers() {
743736
suite.Require().NoError(suite.state.Create(ctx, publicKey2))
744737

745738
// test migration in isolation
746-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
747-
return name == "addServiceAccountScopesToUsers"
748-
})))
739+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("addServiceAccountScopesToUsers"))))
749740

750741
user1, err := safe.StateGet[*authres.User](ctx, suite.state, user1.Metadata())
751742
suite.Require().NoError(err)
@@ -788,9 +779,7 @@ func (suite *MigrationSuite) TestMigrateLabels() {
788779
machineLabels.Metadata().Labels().Set("user-label", "value")
789780
suite.Require().NoError(suite.state.Create(ctx, machineLabels))
790781

791-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
792-
return name == "migrateLabels" || name == "dropOldLabels"
793-
})))
782+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("migrateLabels", "dropOldLabels"))))
794783

795784
updatedMachineStatus, err := suite.state.Get(ctx, machineStatus.Metadata())
796785
suite.Require().NoError(err)
@@ -860,9 +849,7 @@ func (suite *MigrationSuite) TestConvertScopesToRoles() {
860849
suite.Require().NoError(suite.state.Create(ctx, pubKeyWithUserMgmtScopes))
861850
suite.Require().NoError(suite.state.Create(ctx, pubKeyWithServiceAccountScopes))
862851

863-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
864-
return name == "convertScopesToRoles"
865-
})))
852+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("convertScopesToRoles"))))
866853

867854
updatedUserWithNoScopes, err := safe.StateGet[*authres.User](ctx, suite.state, userWithNoScopes.Metadata())
868855
suite.Require().NoError(err)
@@ -1274,9 +1261,7 @@ func (suite *MigrationSuite) TestClearEmptyConfigPatches() {
12741261
suite.Require().NoError(suite.state.Create(ctx, cp1, state.WithCreateOwner("MachineSetStatusController")))
12751262
suite.Require().NoError(suite.state.Create(ctx, cp2, state.WithCreateOwner("MachineSetStatusController")))
12761263

1277-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
1278-
return name == "clearEmptyConfigPatches"
1279-
})))
1264+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("clearEmptyConfigPatches"))))
12801265

12811266
cp1After, err := safe.StateGetByID[*omni.ClusterMachineConfigPatches](ctx, suite.state, "1")
12821267
suite.Require().NoError(err)
@@ -1398,9 +1383,7 @@ func (suite *MigrationSuite) TestSetMachineStatusSnapshotOwner() {
13981383
}
13991384

14001385
// test migration in isolation
1401-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
1402-
return name == "setMachineStatusSnapshotOwner"
1403-
})))
1386+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("setMachineStatusSnapshotOwner"))))
14041387

14051388
check := func(item *omni.MachineStatusSnapshot, expectedVersion int) {
14061389
result, err := safe.StateGet[*omni.MachineStatusSnapshot](ctx, suite.state, item.Metadata())
@@ -1469,9 +1452,7 @@ func (suite *MigrationSuite) TestMigrateInstallImageConfigIntoGenOptions() {
14691452
suite.Require().NoError(suite.state.Create(ctx, res, state.WithCreateOwner(res.Metadata().Owner())))
14701453
}
14711454

1472-
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(func(name string) bool {
1473-
return name == "migrateInstallImageConfigIntoGenOptions"
1474-
})))
1455+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("migrateInstallImageConfigIntoGenOptions"))))
14751456

14761457
genOptions, err := safe.StateGet[*omni.MachineConfigGenOptions](ctx, suite.state, omni.NewMachineConfigGenOptions(resources.DefaultNamespace, "test").Metadata())
14771458
suite.Require().NoError(err)
@@ -1588,11 +1569,7 @@ func (suite *MigrationSuite) testDeleteDeprecatedResources(createRes func(id str
15881569

15891570
suite.Require().NoError(err)
15901571

1591-
suite.Require().NoError(suite.manager.Run(ctx), migration.WithFilter(
1592-
func(name string) bool {
1593-
return name == migrationFilter
1594-
},
1595-
))
1572+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith(migrationFilter))))
15961573

15971574
list, err := suite.state.List(ctx, resource.NewMetadata(res.Metadata().Namespace(), res.Metadata().Type(), "", resource.VersionUndefined))
15981575

@@ -1639,11 +1616,7 @@ func (suite *MigrationSuite) TestRemoveMaintenanceConfigPatchFinalizers() {
16391616
suite.Require().NoError(suite.state.Create(ctx, m2Status, state.WithCreateOwner("MachineStatusController")))
16401617
suite.Require().NoError(suite.state.Create(ctx, m3Status, state.WithCreateOwner("MachineStatusController")))
16411618

1642-
suite.Require().NoError(suite.manager.Run(ctx), migration.WithFilter(
1643-
func(name string) bool {
1644-
return name == "removeMaintenanceConfigPatchFinalizers"
1645-
},
1646-
))
1619+
suite.Require().NoError(suite.manager.Run(ctx, migration.WithFilter(filterWith("removeMaintenanceConfigPatchFinalizers"))))
16471620

16481621
rtestutils.AssertAll(ctx, suite.T(), suite.state, func(
16491622
res *omni.MachineStatus, assert *assert.Assertions,
@@ -1652,8 +1625,186 @@ func (suite *MigrationSuite) TestRemoveMaintenanceConfigPatchFinalizers() {
16521625
})
16531626
}
16541627

1628+
func (suite *MigrationSuite) TestCompressUncompressMigrations() {
1629+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1630+
defer cancel()
1631+
1632+
const (
1633+
data1 = "Hello world!"
1634+
data2 = "Hello Sidero!"
1635+
)
1636+
1637+
encoded1 := must.Value(hex.DecodeString("28b52ffd040061000048656c6c6f20776f726c6421b27dfd7f"))(suite.T())
1638+
encoded2 := must.Value(hex.DecodeString("28b52ffd040069000048656c6c6f2053696465726f211ce853bc"))(suite.T())
1639+
disabled := specs.WithConfigCompressionOption(specs.CompressionConfig{Enabled: false})
1640+
1641+
const ns = resources.DefaultNamespace
1642+
1643+
checkMigrations := []func(t *testing.T){
1644+
startMigration(
1645+
ctx,
1646+
suite.T(),
1647+
suite.state,
1648+
omni.NewConfigPatch(ns, "config-patch-1"),
1649+
fillData[*specs.ConfigPatchSpec](data1, disabled),
1650+
checkCompressed[string, *specs.ConfigPatchSpec](encoded1),
1651+
),
1652+
startMigration(
1653+
ctx,
1654+
suite.T(),
1655+
suite.state,
1656+
omni.NewClusterMachineConfig(ns, "machine-config-1"),
1657+
fillData[*specs.ClusterMachineConfigSpec](data1, disabled),
1658+
checkCompressed[[]byte, *specs.ClusterMachineConfigSpec](encoded1),
1659+
),
1660+
startMigration(
1661+
ctx,
1662+
suite.T(),
1663+
suite.state,
1664+
omni.NewRedactedClusterMachineConfig(ns, "redacted-machine-config-1"),
1665+
fillData[*specs.RedactedClusterMachineConfigSpec](data1, disabled),
1666+
checkCompressed[string, *specs.RedactedClusterMachineConfigSpec](encoded1),
1667+
),
1668+
startMigration(
1669+
ctx,
1670+
suite.T(),
1671+
suite.state,
1672+
omni.NewConfigPatch(ns, "config-patch-2"),
1673+
fillData[*specs.ConfigPatchSpec](data2, disabled),
1674+
checkCompressed[string, *specs.ConfigPatchSpec](encoded2),
1675+
),
1676+
startMigration(
1677+
ctx,
1678+
suite.T(),
1679+
suite.state,
1680+
omni.NewClusterMachineConfig(ns, "machine-config-2"),
1681+
fillData[*specs.ClusterMachineConfigSpec](data2, disabled),
1682+
checkCompressed[[]byte, *specs.ClusterMachineConfigSpec](encoded2),
1683+
),
1684+
startMigration(
1685+
ctx,
1686+
suite.T(),
1687+
suite.state,
1688+
omni.NewClusterMachineConfigPatches(ns, "cluster-machine-config-patches-1"),
1689+
func(t *testing.T, spec *omni.ClusterMachineConfigPatchesSpec) {
1690+
require.NoError(t, spec.Value.SetUncompressedPatches([]string{data1, data2}, disabled))
1691+
},
1692+
func(t *assert.Assertions, spec *omni.ClusterMachineConfigPatchesSpec) {
1693+
uncompressed := spec.Value.GetPatches()
1694+
t.Empty(uncompressed)
1695+
1696+
patches := spec.Value.GetCompressedPatches()
1697+
t.NotEmpty(patches)
1698+
1699+
for i, data := range [][]byte{encoded1, encoded2} {
1700+
t.Equalf(data, patches[i], "%x != %x", data, patches[i])
1701+
}
1702+
},
1703+
),
1704+
startMigration(
1705+
ctx,
1706+
suite.T(),
1707+
suite.state,
1708+
omni.NewClusterMachineConfigPatches(ns, "cluster-machine-config-patches-2"),
1709+
func(t *testing.T, spec *omni.ClusterMachineConfigPatchesSpec) {
1710+
require.NoError(t, spec.Value.SetUncompressedPatches([]string{data2, data1}, disabled))
1711+
},
1712+
func(t *assert.Assertions, spec *omni.ClusterMachineConfigPatchesSpec) {
1713+
uncompressed := spec.Value.GetPatches()
1714+
t.Empty(uncompressed)
1715+
1716+
patches := spec.Value.GetCompressedPatches()
1717+
t.NotEmpty(patches)
1718+
1719+
for i, data := range [][]byte{encoded2, encoded1} {
1720+
t.Equalf(data, patches[i], "%x != %x", data, patches[i])
1721+
}
1722+
},
1723+
),
1724+
}
1725+
1726+
require.NoError(suite.T(), suite.manager.Run(ctx, migration.WithFilter(filterWith("compressMachineConfigsAndPatches"))))
1727+
1728+
for _, check := range checkMigrations {
1729+
check(suite.T())
1730+
}
1731+
}
1732+
1733+
func startMigration[
1734+
R interface {
1735+
generic.ResourceWithRD
1736+
TypedSpec() *protobuf.ResourceSpec[T, S]
1737+
},
1738+
T any,
1739+
S protobuf.Spec[T],
1740+
](
1741+
ctx context.Context,
1742+
t *testing.T,
1743+
st state.State,
1744+
res R,
1745+
fill func(t *testing.T, spec *protobuf.ResourceSpec[T, S]),
1746+
check func(t *assert.Assertions, spec *protobuf.ResourceSpec[T, S]),
1747+
) func(t *testing.T) {
1748+
fill(t, res.TypedSpec())
1749+
1750+
require.NoError(t, st.Create(ctx, res))
1751+
1752+
return func(t *testing.T) {
1753+
rtestutils.AssertResource(
1754+
ctx,
1755+
t,
1756+
st,
1757+
res.Metadata().ID(),
1758+
func(found R, assertion *assert.Assertions) { check(assertion, found.TypedSpec()) },
1759+
)
1760+
}
1761+
}
1762+
1763+
func fillData[
1764+
S interface {
1765+
SetUncompressedData(data []byte, opts ...specs.CompressionOption) error
1766+
protobuf.Spec[T]
1767+
},
1768+
T any,
1769+
](data string, opts ...specs.CompressionOption) func(t *testing.T, spec *protobuf.ResourceSpec[T, S]) {
1770+
return func(t *testing.T, spec *protobuf.ResourceSpec[T, S]) {
1771+
require.NoError(t, spec.Value.SetUncompressedData([]byte(data), opts...))
1772+
}
1773+
}
1774+
1775+
func checkCompressed[
1776+
D string | []byte,
1777+
S interface {
1778+
GetData() D
1779+
GetCompressedData() []byte
1780+
protobuf.Spec[T]
1781+
},
1782+
T any,
1783+
](data []byte) func(t *assert.Assertions, spec *protobuf.ResourceSpec[T, S]) {
1784+
return func(t *assert.Assertions, spec *protobuf.ResourceSpec[T, S]) {
1785+
uncompressed := spec.Value.GetData()
1786+
t.Empty(uncompressed)
1787+
1788+
result := spec.Value.GetCompressedData()
1789+
t.NotEmpty(result)
1790+
t.Equalf(data, result, "%x != %x", data, result)
1791+
}
1792+
}
1793+
16551794
func TestMigrationSuite(t *testing.T) {
16561795
t.Parallel()
16571796

16581797
suite.Run(t, new(MigrationSuite))
16591798
}
1799+
1800+
func filterWith(vals ...string) func(string) bool {
1801+
return func(cur string) bool {
1802+
for _, val := range vals {
1803+
if cur == val {
1804+
return true
1805+
}
1806+
}
1807+
1808+
return false
1809+
}
1810+
}

0 commit comments

Comments
 (0)