master
go 360 lines 13.6 KB
Raw
1 package migrations
2
3 // NOTE: These migration tests require the local Kubo binary (built with 'make build') to be in PATH.
4 //
5 // To run these tests successfully:
6 // export PATH="$(pwd)/cmd/ipfs:$PATH"
7 // go test ./test/cli/migrations/
8
9 import (
10 "context"
11 "encoding/json"
12 "fmt"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "strings"
17 "testing"
18 "time"
19
20 ipfs "github.com/ipfs/kubo"
21 "github.com/ipfs/kubo/test/cli/harness"
22 "github.com/stretchr/testify/require"
23 )
24
25 // TestMigration17ToLatest tests migration from repo version 17 to the latest version.
26 //
27 // Since we don't have a v17 repo fixture, we start with v16 and migrate it to v17 first,
28 // then test the 17-to-18 migration specifically.
29 //
30 // This test focuses on the Provider/Reprovider to Provide consolidation that happens in 17-to-18.
31 func TestMigration17ToLatest(t *testing.T) {
32 t.Parallel()
33
34 // Tests for Provider/Reprovider to Provide migration (17-to-18)
35 t.Run("daemon migrate: Provider/Reprovider to Provide consolidation", testProviderReproviderMigration)
36 t.Run("daemon migrate: flat strategy conversion", testFlatStrategyConversion)
37 t.Run("daemon migrate: empty Provider/Reprovider sections", testEmptyProviderReproviderMigration)
38 t.Run("daemon migrate: partial configuration (Provider only)", testProviderOnlyMigration)
39 t.Run("daemon migrate: partial configuration (Reprovider only)", testReproviderOnlyMigration)
40 t.Run("repo migrate: invalid strategy values preserved", testInvalidStrategyMigration)
41 t.Run("repo migrate: Provider/Reprovider to Provide consolidation", testRepoProviderReproviderMigration)
42 }
43
44 // =============================================================================
45 // MIGRATION 17-to-18 SPECIFIC TESTS: Provider/Reprovider to Provide consolidation
46 // =============================================================================
47
48 func testProviderReproviderMigration(t *testing.T) {
49 // TEST: 17-to-18 migration with explicit Provider/Reprovider configuration
50 node := setupV17RepoWithProviderConfig(t)
51
52 configPath := filepath.Join(node.Dir, "config")
53 versionPath := filepath.Join(node.Dir, "version")
54
55 // Run migration using daemon --migrate command
56 stdoutOutput, migrationSuccess := runDaemonMigrationFromV17(t, node)
57
58 // Debug: Print the actual output
59 t.Logf("Daemon output:\n%s", stdoutOutput)
60
61 // Verify migration was successful
62 require.True(t, migrationSuccess, "Migration should have been successful")
63 require.Contains(t, stdoutOutput, "applying 17-to-18 repo migration", "Migration 17-to-18 should have been triggered")
64 require.Contains(t, stdoutOutput, "Migration 17-to-18 succeeded", "Migration 17-to-18 should have completed successfully")
65
66 // Verify version was updated to latest
67 versionData, err := os.ReadFile(versionPath)
68 require.NoError(t, err)
69 expectedVersion := fmt.Sprint(ipfs.RepoVersion)
70 require.Equal(t, expectedVersion, strings.TrimSpace(string(versionData)), "Version should be updated to %s (latest)", expectedVersion)
71
72 // =============================================================================
73 // MIGRATION 17-to-18 ASSERTIONS: Provider/Reprovider to Provide consolidation
74 // =============================================================================
75 helper := NewMigrationTestHelper(t, configPath)
76
77 // Verify Provider/Reprovider migration to Provide
78 helper.RequireProviderMigration().
79 RequireFieldEquals("Provide.Enabled", true). // Migrated from Provider.Enabled
80 RequireFieldEquals("Provide.DHT.MaxWorkers", float64(8)). // Migrated from Provider.WorkerCount
81 RequireFieldEquals("Provide.Strategy", "roots"). // Migrated from Reprovider.Strategy
82 RequireFieldEquals("Provide.DHT.Interval", "24h") // Migrated from Reprovider.Interval
83
84 // Verify old sections are removed
85 helper.RequireFieldAbsent("Provider").
86 RequireFieldAbsent("Reprovider")
87 }
88
89 func testFlatStrategyConversion(t *testing.T) {
90 // TEST: 17-to-18 migration with "flat" strategy that should convert to "all"
91 node := setupV17RepoWithFlatStrategy(t)
92
93 configPath := filepath.Join(node.Dir, "config")
94
95 // Run migration using daemon --migrate command
96 stdoutOutput, migrationSuccess := runDaemonMigrationFromV17(t, node)
97
98 // Verify migration was successful
99 require.True(t, migrationSuccess, "Migration should have been successful")
100 require.Contains(t, stdoutOutput, "applying 17-to-18 repo migration", "Migration 17-to-18 should have been triggered")
101 require.Contains(t, stdoutOutput, "Migration 17-to-18 succeeded", "Migration 17-to-18 should have completed successfully")
102
103 // =============================================================================
104 // MIGRATION 17-to-18 ASSERTIONS: "flat" to "all" strategy conversion
105 // =============================================================================
106 helper := NewMigrationTestHelper(t, configPath)
107
108 // Verify "flat" was converted to "all"
109 helper.RequireProviderMigration().
110 RequireFieldEquals("Provide.Strategy", "all"). // "flat" converted to "all"
111 RequireFieldEquals("Provide.DHT.Interval", "12h")
112 }
113
114 func testEmptyProviderReproviderMigration(t *testing.T) {
115 // TEST: 17-to-18 migration with empty Provider and Reprovider sections
116 node := setupV17RepoWithEmptySections(t)
117
118 configPath := filepath.Join(node.Dir, "config")
119
120 // Run migration
121 stdoutOutput, migrationSuccess := runDaemonMigrationFromV17(t, node)
122
123 // Verify migration was successful
124 require.True(t, migrationSuccess, "Migration should have been successful")
125 require.Contains(t, stdoutOutput, "Migration 17-to-18 succeeded")
126
127 // Verify empty sections are removed and no Provide section is created
128 helper := NewMigrationTestHelper(t, configPath)
129 helper.RequireFieldAbsent("Provider").
130 RequireFieldAbsent("Reprovider").
131 RequireFieldAbsent("Provide") // No Provide section should be created for empty configs
132 }
133
134 func testProviderOnlyMigration(t *testing.T) {
135 // TEST: 17-to-18 migration with only Provider configuration
136 node := setupV17RepoWithProviderOnly(t)
137
138 configPath := filepath.Join(node.Dir, "config")
139
140 // Run migration
141 stdoutOutput, migrationSuccess := runDaemonMigrationFromV17(t, node)
142
143 // Verify migration was successful
144 require.True(t, migrationSuccess, "Migration should have been successful")
145 require.Contains(t, stdoutOutput, "Migration 17-to-18 succeeded")
146
147 // Verify only Provider fields are migrated
148 helper := NewMigrationTestHelper(t, configPath)
149 helper.RequireProviderMigration().
150 RequireFieldEquals("Provide.Enabled", false).
151 RequireFieldEquals("Provide.DHT.MaxWorkers", float64(32)).
152 RequireFieldAbsent("Provide.Strategy"). // No Reprovider.Strategy to migrate
153 RequireFieldAbsent("Provide.DHT.Interval") // No Reprovider.Interval to migrate
154 }
155
156 func testReproviderOnlyMigration(t *testing.T) {
157 // TEST: 17-to-18 migration with only Reprovider configuration
158 node := setupV17RepoWithReproviderOnly(t)
159
160 configPath := filepath.Join(node.Dir, "config")
161
162 // Run migration
163 stdoutOutput, migrationSuccess := runDaemonMigrationFromV17(t, node)
164
165 // Verify migration was successful
166 require.True(t, migrationSuccess, "Migration should have been successful")
167 require.Contains(t, stdoutOutput, "Migration 17-to-18 succeeded")
168
169 // Verify only Reprovider fields are migrated
170 helper := NewMigrationTestHelper(t, configPath)
171 helper.RequireProviderMigration().
172 RequireFieldEquals("Provide.Strategy", "pinned").
173 RequireFieldEquals("Provide.DHT.Interval", "48h").
174 RequireFieldAbsent("Provide.Enabled"). // No Provider.Enabled to migrate
175 RequireFieldAbsent("Provide.DHT.MaxWorkers") // No Provider.WorkerCount to migrate
176 }
177
178 func testInvalidStrategyMigration(t *testing.T) {
179 // TEST: 17-to-18 migration with invalid strategy values (should be preserved as-is)
180 // The migration itself should succeed, but daemon start will fail due to invalid strategy
181 node := setupV17RepoWithInvalidStrategy(t)
182
183 configPath := filepath.Join(node.Dir, "config")
184
185 // Run the migration using 'ipfs repo migrate' (not daemon --migrate)
186 // because daemon would fail to start with invalid strategy after migration
187 result := node.RunIPFS("repo", "migrate")
188 require.Empty(t, result.Stderr.String(), "Migration should succeed without errors")
189
190 // Verify invalid strategy is preserved as-is (not validated during migration)
191 helper := NewMigrationTestHelper(t, configPath)
192 helper.RequireProviderMigration().
193 RequireFieldEquals("Provide.Strategy", "invalid-strategy") // Should be preserved
194
195 // Now verify that daemon fails to start with invalid strategy
196 // Note: We cannot use --offline as it skips provider validation
197 // Use a context with timeout to avoid hanging
198 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
199 defer cancel()
200
201 cmd := exec.CommandContext(ctx, node.IPFSBin, "daemon")
202 cmd.Dir = node.Dir
203 for k, v := range node.Runner.Env {
204 cmd.Env = append(cmd.Env, k+"="+v)
205 }
206
207 output, err := cmd.CombinedOutput()
208
209 // The daemon should fail (either with error or timeout if it's hanging)
210 require.Error(t, err, "Daemon should fail to start with invalid strategy")
211
212 // Check if we got the expected error message
213 outputStr := string(output)
214 t.Logf("Daemon output with invalid strategy: %s", outputStr)
215
216 // The error should mention unknown strategy token
217 require.Contains(t, outputStr, "unknown provide strategy token", "Should report unknown strategy error")
218 }
219
220 func testRepoProviderReproviderMigration(t *testing.T) {
221 // TEST: 17-to-18 migration using 'ipfs repo migrate' command
222 node := setupV17RepoWithProviderConfig(t)
223
224 configPath := filepath.Join(node.Dir, "config")
225
226 // Run migration using 'ipfs repo migrate' command
227 result := node.RunIPFS("repo", "migrate")
228 require.Empty(t, result.Stderr.String(), "Migration should succeed without errors")
229
230 // Verify same results as daemon migrate
231 helper := NewMigrationTestHelper(t, configPath)
232 helper.RequireProviderMigration().
233 RequireFieldEquals("Provide.Enabled", true).
234 RequireFieldEquals("Provide.DHT.MaxWorkers", float64(8)).
235 RequireFieldEquals("Provide.Strategy", "roots").
236 RequireFieldEquals("Provide.DHT.Interval", "24h")
237 }
238
239 // =============================================================================
240 // HELPER FUNCTIONS
241 // =============================================================================
242
243 // setupV17RepoWithProviderConfig creates a v17 repo with Provider/Reprovider configuration
244 func setupV17RepoWithProviderConfig(t *testing.T) *harness.Node {
245 return setupV17RepoWithConfig(t,
246 map[string]any{
247 "Enabled": true,
248 "WorkerCount": 8,
249 },
250 map[string]any{
251 "Strategy": "roots",
252 "Interval": "24h",
253 })
254 }
255
256 // setupV17RepoWithFlatStrategy creates a v17 repo with "flat" strategy for testing conversion
257 func setupV17RepoWithFlatStrategy(t *testing.T) *harness.Node {
258 return setupV17RepoWithConfig(t,
259 map[string]any{
260 "Enabled": false,
261 },
262 map[string]any{
263 "Strategy": "flat", // This should be converted to "all"
264 "Interval": "12h",
265 })
266 }
267
268 // setupV17RepoWithConfig is a helper that creates a v17 repo with specified Provider/Reprovider config
269 func setupV17RepoWithConfig(t *testing.T, providerConfig, reproviderConfig map[string]any) *harness.Node {
270 node := setupStaticV16Repo(t)
271
272 // First migrate to v17
273 result := node.RunIPFS("repo", "migrate", "--to=17")
274 require.Empty(t, result.Stderr.String(), "Migration to v17 should succeed")
275
276 // Update config with specified Provider and Reprovider settings
277 configPath := filepath.Join(node.Dir, "config")
278 var config map[string]any
279 configData, err := os.ReadFile(configPath)
280 require.NoError(t, err)
281 require.NoError(t, json.Unmarshal(configData, &config))
282
283 if providerConfig != nil {
284 config["Provider"] = providerConfig
285 } else {
286 config["Provider"] = map[string]any{}
287 }
288
289 if reproviderConfig != nil {
290 config["Reprovider"] = reproviderConfig
291 } else {
292 config["Reprovider"] = map[string]any{}
293 }
294
295 modifiedConfigData, err := json.MarshalIndent(config, "", " ")
296 require.NoError(t, err)
297 require.NoError(t, os.WriteFile(configPath, modifiedConfigData, 0644))
298
299 return node
300 }
301
302 // setupV17RepoWithEmptySections creates a v17 repo with empty Provider/Reprovider sections
303 func setupV17RepoWithEmptySections(t *testing.T) *harness.Node {
304 return setupV17RepoWithConfig(t,
305 map[string]any{},
306 map[string]any{})
307 }
308
309 // setupV17RepoWithProviderOnly creates a v17 repo with only Provider configuration
310 func setupV17RepoWithProviderOnly(t *testing.T) *harness.Node {
311 return setupV17RepoWithConfig(t,
312 map[string]any{
313 "Enabled": false,
314 "WorkerCount": 32,
315 },
316 map[string]any{})
317 }
318
319 // setupV17RepoWithReproviderOnly creates a v17 repo with only Reprovider configuration
320 func setupV17RepoWithReproviderOnly(t *testing.T) *harness.Node {
321 return setupV17RepoWithConfig(t,
322 map[string]any{},
323 map[string]any{
324 "Strategy": "pinned",
325 "Interval": "48h",
326 })
327 }
328
329 // setupV17RepoWithInvalidStrategy creates a v17 repo with an invalid strategy value
330 func setupV17RepoWithInvalidStrategy(t *testing.T) *harness.Node {
331 return setupV17RepoWithConfig(t,
332 map[string]any{},
333 map[string]any{
334 "Strategy": "invalid-strategy", // This is not a valid strategy
335 "Interval": "24h",
336 })
337 }
338
339 // runDaemonMigrationFromV17 monitors daemon startup for 17-to-18 migration only
340 func runDaemonMigrationFromV17(t *testing.T, node *harness.Node) (string, bool) {
341 // Monitor only the 17-to-18 migration
342 expectedMigrations := []struct {
343 pattern string
344 success string
345 }{
346 {
347 pattern: "applying 17-to-18 repo migration",
348 success: "Migration 17-to-18 succeeded",
349 },
350 }
351
352 return runDaemonWithMultipleMigrationMonitoring(t, node, expectedMigrations)
353 }
354
355 // RequireProviderMigration verifies that Provider/Reprovider have been migrated to Provide section
356 func (h *MigrationTestHelper) RequireProviderMigration() *MigrationTestHelper {
357 return h.RequireFieldExists("Provide").
358 RequireFieldAbsent("Provider").
359 RequireFieldAbsent("Reprovider")
360 }