Skip to content

Commit babfebf

Browse files
authored
Merge pull request containerd#10472 from fuweid/migrate-sandboxes-bucket
core/metadata: migrate sandboxes bucket into v1
2 parents fd2a767 + 4cfeb7b commit babfebf

File tree

8 files changed

+321
-11
lines changed

8 files changed

+321
-11
lines changed

core/metadata/buckets.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ func getIngestBucket(tx *bolt.Tx, namespace, ref string) *bolt.Bucket {
301301
func createSandboxBucket(tx *bolt.Tx, namespace string) (*bolt.Bucket, error) {
302302
return createBucketIfNotExists(
303303
tx,
304+
bucketKeyVersion,
304305
[]byte(namespace),
305306
bucketKeyObjectSandboxes,
306307
)
@@ -309,6 +310,7 @@ func createSandboxBucket(tx *bolt.Tx, namespace string) (*bolt.Bucket, error) {
309310
func getSandboxBucket(tx *bolt.Tx, namespace string) *bolt.Bucket {
310311
return getBucket(
311312
tx,
313+
bucketKeyVersion,
312314
[]byte(namespace),
313315
bucketKeyObjectSandboxes,
314316
)

core/metadata/db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const (
5050
// dbVersion represents updates to the schema
5151
// version which are additions and compatible with
5252
// prior version of the same schema.
53-
dbVersion = 3
53+
dbVersion = 4
5454
)
5555

5656
// DBOpt configures how we set up the DB

core/metadata/db_test.go

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,42 @@ func TestMigrations(t *testing.T) {
140140
bref: "",
141141
},
142142
}
143+
144+
testSandboxes := []struct {
145+
id string
146+
keyValues [][3]string // {bucket, key, value}
147+
}{
148+
{
149+
id: "sb1",
150+
keyValues: [][3]string{
151+
{
152+
"", // is not sub bucket
153+
"created", "2dayago",
154+
},
155+
{
156+
"", // is not sub bucket
157+
"updated", "1dayago",
158+
},
159+
{
160+
"extension",
161+
"labels", strings.Repeat("whoknows", 10),
162+
},
163+
},
164+
},
165+
{
166+
id: "sb2",
167+
keyValues: [][3]string{
168+
{
169+
"", // is not sub bucket
170+
"sandboxer", "default",
171+
},
172+
{
173+
"labels", "hello", "panic",
174+
},
175+
},
176+
},
177+
}
178+
143179
migrationTests := []struct {
144180
name string
145181
init func(*bolt.Tx) error
@@ -282,7 +318,6 @@ func TestMigrations(t *testing.T) {
282318
return nil
283319
},
284320
},
285-
286321
{
287322
name: "NoOp",
288323
init: func(tx *bolt.Tx) error {
@@ -292,6 +327,65 @@ func TestMigrations(t *testing.T) {
292327
return nil
293328
},
294329
},
330+
{
331+
name: "MigrateSandboxes",
332+
init: func(tx *bolt.Tx) error {
333+
allsbbkt, err := createBucketIfNotExists(tx, []byte("kubernetes"), bucketKeyObjectSandboxes)
334+
if err != nil {
335+
return err
336+
}
337+
338+
for _, sbDef := range testSandboxes {
339+
sbbkt, err := allsbbkt.CreateBucket([]byte(sbDef.id))
340+
if err != nil {
341+
return err
342+
}
343+
344+
for _, keyValues := range sbDef.keyValues {
345+
bkt := sbbkt
346+
if keyValues[0] != "" {
347+
bkt, err = sbbkt.CreateBucketIfNotExists([]byte(keyValues[0]))
348+
if err != nil {
349+
return err
350+
}
351+
}
352+
353+
if err = bkt.Put([]byte(keyValues[1]), []byte(keyValues[2])); err != nil {
354+
return err
355+
}
356+
}
357+
}
358+
return nil
359+
},
360+
check: func(tx *bolt.Tx) error {
361+
allsbbkt := getSandboxBucket(tx, "kubernetes")
362+
363+
for _, sbDef := range testSandboxes {
364+
sbbkt := allsbbkt.Bucket([]byte(sbDef.id))
365+
366+
for _, keyValues := range sbDef.keyValues {
367+
bkt := sbbkt
368+
if keyValues[0] != "" {
369+
bkt = sbbkt.Bucket([]byte(keyValues[0]))
370+
}
371+
372+
key := []byte(keyValues[1])
373+
expected := keyValues[2]
374+
375+
value := string(bkt.Get(key))
376+
if value != expected {
377+
return fmt.Errorf("expected %s, but got %s in sandbox %s", expected, value, sbDef.id)
378+
}
379+
}
380+
}
381+
382+
allsbbkt = getBucket(tx, []byte("kubernetes"), bucketKeyObjectSandboxes)
383+
if allsbbkt != nil {
384+
return errors.New("old sandboxes bucket still exists")
385+
}
386+
return nil
387+
},
388+
},
295389
}
296390

297391
if len(migrationTests) != len(migrations) {

core/metadata/migrations.go

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616

1717
package metadata
1818

19-
import bolt "go.etcd.io/bbolt"
19+
import (
20+
"bytes"
21+
"fmt"
22+
23+
bolt "go.etcd.io/bbolt"
24+
)
2025

2126
type migration struct {
2227
schema string
@@ -50,6 +55,11 @@ var migrations = []migration{
5055
version: 3,
5156
migrate: noOpMigration,
5257
},
58+
{
59+
schema: "v1",
60+
version: 4,
61+
migrate: migrateSandboxes,
62+
},
5363
}
5464

5565
// addChildLinks Adds children key to the snapshotters to enforce snapshot
@@ -160,6 +170,87 @@ func migrateIngests(tx *bolt.Tx) error {
160170
return nil
161171
}
162172

173+
// migrateSandboxes moves sandboxes from root bucket into v1 bucket.
174+
func migrateSandboxes(tx *bolt.Tx) error {
175+
v1bkt, err := tx.CreateBucketIfNotExists(bucketKeyVersion)
176+
if err != nil {
177+
return err
178+
}
179+
180+
deletingBuckets := [][]byte{}
181+
182+
if merr := tx.ForEach(func(ns []byte, nsbkt *bolt.Bucket) error {
183+
// Skip v1 bucket, even if users created sandboxes in v1 namespace.
184+
if bytes.Equal(bucketKeyVersion, ns) {
185+
return nil
186+
}
187+
188+
deletingBuckets = append(deletingBuckets, ns)
189+
190+
allsbbkt := nsbkt.Bucket(bucketKeyObjectSandboxes)
191+
if allsbbkt == nil {
192+
return nil
193+
}
194+
195+
tnsbkt, err := v1bkt.CreateBucketIfNotExists(ns)
196+
if err != nil {
197+
return fmt.Errorf("failed to create namespace %s in bucket %s: %w",
198+
ns, bucketKeyVersion, err)
199+
}
200+
201+
tallsbbkt, err := tnsbkt.CreateBucketIfNotExists(bucketKeyObjectSandboxes)
202+
if err != nil {
203+
return fmt.Errorf("failed to create bucket sandboxes in namespace %s: %w", ns, err)
204+
}
205+
206+
return allsbbkt.ForEachBucket(func(sb []byte) error {
207+
sbbkt := allsbbkt.Bucket(sb) // single sandbox bucket
208+
209+
tsbbkt, err := tallsbbkt.CreateBucketIfNotExists(sb)
210+
if err != nil {
211+
return fmt.Errorf("failed to create sandbox object %s in namespace %s: %w",
212+
sb, ns, err)
213+
}
214+
215+
// copy single
216+
if cerr := sbbkt.ForEach(func(key, value []byte) error {
217+
if value == nil {
218+
return nil
219+
}
220+
221+
return tsbbkt.Put(key, value)
222+
}); cerr != nil {
223+
return cerr
224+
}
225+
226+
return sbbkt.ForEachBucket(func(subbkt []byte) error {
227+
tsubbkt, err := tsbbkt.CreateBucketIfNotExists(subbkt)
228+
if err != nil {
229+
return fmt.Errorf("failed to create subbucket %s in sandbox %s (namespace %s): %w",
230+
subbkt, sb, ns, err)
231+
}
232+
233+
return sbbkt.Bucket(subbkt).ForEach(func(key, value []byte) error {
234+
if value == nil {
235+
return fmt.Errorf("unexpected bucket %s", key)
236+
}
237+
return tsubbkt.Put(key, value)
238+
})
239+
})
240+
})
241+
}); merr != nil {
242+
return fmt.Errorf("failed to copy sandboxes into v1 bucket: %w", err)
243+
}
244+
245+
for _, ns := range deletingBuckets {
246+
derr := tx.DeleteBucket(ns)
247+
if derr != nil {
248+
return fmt.Errorf("failed to cleanup bucket %s in root: %w", ns, err)
249+
}
250+
}
251+
return nil
252+
}
253+
163254
// noOpMigration was for a database change from boltdb/bolt which is no
164255
// longer being supported, to go.etcd.io/bbolt which is the currently
165256
// maintained repo for boltdb.

integration/container_volume_linux_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ version = 3
5656
require.NoError(t, err)
5757

5858
t.Logf("Starting containerd")
59-
currentProc := newCtrdProc(t, "containerd", workDir)
59+
currentProc := newCtrdProc(t, "containerd", workDir, nil)
6060
require.NoError(t, currentProc.isReady())
6161
t.Cleanup(func() {
6262
t.Log("Cleanup all the pods")

integration/issue10467_linux_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package integration
18+
19+
import (
20+
"fmt"
21+
"path/filepath"
22+
"syscall"
23+
"testing"
24+
"time"
25+
26+
"github.com/containerd/continuity/fs"
27+
"github.com/stretchr/testify/require"
28+
"go.etcd.io/bbolt"
29+
)
30+
31+
func TestIssue10467(t *testing.T) {
32+
latestVersion := "v1.7.20"
33+
34+
releaseBinDir := t.TempDir()
35+
36+
downloadReleaseBinary(t, releaseBinDir, latestVersion)
37+
38+
t.Logf("Install config for release %s", latestVersion)
39+
workDir := t.TempDir()
40+
previousReleaseCtrdConfig(t, releaseBinDir, workDir)
41+
42+
t.Log("Starting the previous release's containerd")
43+
previousCtrdBinPath := filepath.Join(releaseBinDir, "bin", "containerd")
44+
previousProc := newCtrdProc(t, previousCtrdBinPath, workDir, []string{"ENABLE_CRI_SANDBOXES=yes"})
45+
46+
boltdbPath := filepath.Join(workDir, "root", "io.containerd.metadata.v1.bolt", "meta.db")
47+
48+
ctrdLogPath := previousProc.logPath()
49+
t.Cleanup(func() {
50+
if t.Failed() {
51+
dumpFileContent(t, ctrdLogPath)
52+
}
53+
})
54+
55+
require.NoError(t, previousProc.isReady())
56+
57+
needToCleanup := true
58+
t.Cleanup(func() {
59+
if t.Failed() && needToCleanup {
60+
t.Logf("Try to cleanup leaky pods")
61+
cleanupPods(t, previousProc.criRuntimeService(t))
62+
}
63+
})
64+
65+
t.Log("Prepare pods for current release")
66+
upgradeCaseFunc, hookFunc := shouldManipulateContainersInPodAfterUpgrade(t, previousProc.criRuntimeService(t), previousProc.criImageService(t))
67+
needToCleanup = false
68+
require.Nil(t, hookFunc)
69+
70+
t.Log("Gracefully stop previous release's containerd process")
71+
require.NoError(t, previousProc.kill(syscall.SIGTERM))
72+
require.NoError(t, previousProc.wait(5*time.Minute))
73+
74+
t.Logf("%s should have bucket k8s.io in root", boltdbPath)
75+
db, err := bbolt.Open(boltdbPath, 0600, &bbolt.Options{ReadOnly: true})
76+
require.NoError(t, err)
77+
require.NoError(t, db.View(func(tx *bbolt.Tx) error {
78+
if tx.Bucket([]byte("k8s.io")) == nil {
79+
return fmt.Errorf("expected k8s.io bucket")
80+
}
81+
return nil
82+
}))
83+
require.NoError(t, db.Close())
84+
85+
t.Log("Install default config for current release")
86+
currentReleaseCtrdDefaultConfig(t, workDir)
87+
88+
t.Log("Starting the current release's containerd")
89+
currentProc := newCtrdProc(t, "containerd", workDir, nil)
90+
require.NoError(t, currentProc.isReady())
91+
92+
t.Cleanup(func() {
93+
t.Log("Cleanup all the pods")
94+
cleanupPods(t, currentProc.criRuntimeService(t))
95+
96+
t.Log("Stopping current release's containerd process")
97+
require.NoError(t, currentProc.kill(syscall.SIGTERM))
98+
require.NoError(t, currentProc.wait(5*time.Minute))
99+
})
100+
101+
t.Logf("%s should not have bucket k8s.io in root after restart", boltdbPath)
102+
copiedBoltdbPath := filepath.Join(t.TempDir(), "meta.db.new")
103+
require.NoError(t, fs.CopyFile(copiedBoltdbPath, boltdbPath))
104+
105+
db, err = bbolt.Open(copiedBoltdbPath, 0600, &bbolt.Options{ReadOnly: true})
106+
require.NoError(t, err)
107+
require.NoError(t, db.View(func(tx *bbolt.Tx) error {
108+
if tx.Bucket([]byte("k8s.io")) != nil {
109+
return fmt.Errorf("unexpected k8s.io bucket")
110+
}
111+
return nil
112+
}))
113+
require.NoError(t, db.Close())
114+
115+
t.Log("Verifing")
116+
upgradeCaseFunc(t, currentProc.criRuntimeService(t), currentProc.criImageService(t))
117+
}

0 commit comments

Comments
 (0)