Skip to content

Commit b80e1f9

Browse files
authored
br: add a new CRD for compact backup (#5822)
1 parent bcf2c89 commit b80e1f9

File tree

42 files changed

+15927
-8243
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+15927
-8243
lines changed

cmd/backup-manager/app/backup/manager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (bm *Manager) performBackup(ctx context.Context, backup *v1alpha1.Backup, d
198198

199199
var errs []error
200200

201-
backupFullPath, err := util.GetStoragePath(backup)
201+
backupFullPath, err := util.GetStoragePath(&backup.Spec.StorageProvider)
202202
if err != nil {
203203
errs = append(errs, err)
204204
uerr := bm.StatusUpdater.Update(backup, &v1alpha1.BackupCondition{
@@ -506,7 +506,7 @@ func (bm *Manager) performLogBackup(ctx context.Context, backup *v1alpha1.Backup
506506
// startLogBackup starts log backup.
507507
func (bm *Manager) startLogBackup(ctx context.Context, backup *v1alpha1.Backup) (*controller.BackupUpdateStatus, string, error) {
508508
started := time.Now()
509-
backupFullPath, err := util.GetStoragePath(backup)
509+
backupFullPath, err := util.GetStoragePath(&backup.Spec.StorageProvider)
510510
if err != nil {
511511
klog.Errorf("Get backup full path of cluster %s failed, err: %s", bm, err)
512512
return nil, "GetBackupRemotePathFailed", err

cmd/backup-manager/app/clean/clean_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ import (
2323
"github.com/agiledragon/gomonkey/v2"
2424
"github.com/aws/aws-sdk-go/service/s3/s3iface"
2525
. "github.com/onsi/gomega"
26-
"gocloud.dev/blob"
27-
"gocloud.dev/blob/driver"
28-
2926
"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
3027
"github.com/pingcap/tidb-operator/pkg/backup/util"
28+
"gocloud.dev/blob"
29+
"gocloud.dev/blob/driver"
3130
)
3231

3332
func TestCleanBRRemoteBackupDataOnce(t *testing.T) {

cmd/backup-manager/app/cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func NewBackupMgrCommand() *cobra.Command {
3434
cmds.AddCommand(NewRestoreCommand())
3535
cmds.AddCommand(NewImportCommand())
3636
cmds.AddCommand(NewCleanCommand())
37+
cmds.AddCommand(NewCompactCommand())
3738
return cmds
3839
}
3940

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright 2024 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package cmd
15+
16+
import (
17+
"context"
18+
19+
"github.com/pingcap/tidb-operator/cmd/backup-manager/app/compact"
20+
"github.com/pingcap/tidb-operator/cmd/backup-manager/app/compact/options"
21+
"github.com/pingcap/tidb-operator/cmd/backup-manager/app/constants"
22+
"github.com/pingcap/tidb-operator/cmd/backup-manager/app/util"
23+
informers "github.com/pingcap/tidb-operator/pkg/client/informers/externalversions"
24+
"github.com/pingcap/tidb-operator/pkg/controller"
25+
"github.com/spf13/cobra"
26+
"k8s.io/client-go/tools/cache"
27+
cmdutil "k8s.io/kubectl/pkg/cmd/util"
28+
)
29+
30+
func NewCompactCommand() *cobra.Command {
31+
opts := options.CompactOpts{}
32+
33+
cmd := &cobra.Command{
34+
Use: "compact",
35+
Short: "Compact log backup.",
36+
Run: func(cmd *cobra.Command, args []string) {
37+
util.ValidCmdFlags(cmd.CommandPath(), cmd.LocalFlags())
38+
cmdutil.CheckErr(runCompact(opts, kubecfg))
39+
},
40+
}
41+
42+
cmd.Flags().StringVar(&opts.Namespace, "namespace", "", "Backup CR's namespace")
43+
cmd.Flags().StringVar(&opts.ResourceName, "resourceName", "", "Backup CRD object name")
44+
return cmd
45+
}
46+
47+
func runCompact(compactOpts options.CompactOpts, kubecfg string) error {
48+
kubeCli, cli, err := util.NewKubeAndCRCli(kubecfg)
49+
if err != nil {
50+
return err
51+
}
52+
options := []informers.SharedInformerOption{
53+
informers.WithNamespace(compactOpts.Namespace),
54+
}
55+
informerFactory := informers.NewSharedInformerFactoryWithOptions(cli, constants.ResyncDuration, options...)
56+
recorder := util.NewEventRecorder(kubeCli, "compact-manager")
57+
compactInformer := informerFactory.Pingcap().V1alpha1().CompactBackups()
58+
statusUpdater := controller.NewCompactStatusUpdater(recorder, compactInformer.Lister(), cli)
59+
60+
ctx, cancel := context.WithCancel(context.Background())
61+
defer cancel()
62+
go informerFactory.Start(ctx.Done())
63+
64+
// waiting for the shared informer's store has synced.
65+
cache.WaitForCacheSync(ctx.Done(), compactInformer.Informer().HasSynced)
66+
67+
// klog.Infof("start to process backup %s", compactOpts.String())
68+
cm := compact.NewManager(compactInformer.Lister(), statusUpdater, compactOpts)
69+
return cm.ProcessCompact()
70+
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// Copyright 2019 PingCAP, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package compact
15+
16+
import (
17+
"bytes"
18+
"context"
19+
"encoding/json"
20+
"io"
21+
"os"
22+
"os/exec"
23+
"path/filepath"
24+
"strconv"
25+
26+
"github.com/pingcap/errors"
27+
"github.com/pingcap/tidb-operator/cmd/backup-manager/app/compact/options"
28+
backuputil "github.com/pingcap/tidb-operator/cmd/backup-manager/app/util"
29+
"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
30+
pkgutil "github.com/pingcap/tidb-operator/pkg/backup/util"
31+
listers "github.com/pingcap/tidb-operator/pkg/client/listers/pingcap/v1alpha1"
32+
"github.com/pingcap/tidb-operator/pkg/controller"
33+
"github.com/pingcap/tidb-operator/pkg/util"
34+
"k8s.io/klog/v2"
35+
)
36+
37+
const (
38+
messageCompactionDone = "Finishing compaction."
39+
messageCompactAborted = "Compaction aborted."
40+
)
41+
42+
// logLine is line of JSON log.
43+
// It just extracted the message from the JSON and keeps the origin json bytes.
44+
// So you may extract fields from it by `json.Unmarshal(l.Raw, ...)`.
45+
type logLine struct {
46+
Message string `json:"Message"`
47+
Raw json.RawMessage `json:"-"`
48+
}
49+
50+
// Manager mainly used to manage backup related work
51+
type Manager struct {
52+
compact *v1alpha1.CompactBackup
53+
resourceLister listers.CompactBackupLister
54+
statusUpdater controller.CompactStatusUpdaterInterface
55+
options options.CompactOpts
56+
}
57+
58+
// NewManager return a Manager
59+
func NewManager(
60+
lister listers.CompactBackupLister,
61+
statusUpdater controller.CompactStatusUpdaterInterface,
62+
compactOpts options.CompactOpts) *Manager {
63+
compact, err := lister.CompactBackups(compactOpts.Namespace).Get(compactOpts.ResourceName)
64+
if err != nil {
65+
klog.Errorf("can't find compact %s:%s CRD object, err: %v", compactOpts.Namespace, compactOpts.ResourceName, err)
66+
return nil
67+
}
68+
return &Manager{
69+
compact,
70+
lister,
71+
statusUpdater,
72+
compactOpts,
73+
}
74+
}
75+
76+
func (cm *Manager) brBin() string {
77+
return filepath.Join(util.BRBinPath, "br")
78+
}
79+
80+
func (cm *Manager) kvCtlBin() string {
81+
return filepath.Join(util.KVCTLBinPath, "tikv-ctl")
82+
}
83+
84+
// ProcessBackup used to process the backup logic
85+
func (cm *Manager) ProcessCompact() error {
86+
var err error
87+
ctx, cancel := backuputil.GetContextForTerminationSignals(cm.options.ResourceName)
88+
defer cancel()
89+
90+
compact, err := cm.resourceLister.CompactBackups(cm.options.Namespace).Get(cm.options.ResourceName)
91+
defer func() {
92+
cm.statusUpdater.OnFinish(ctx, cm.compact, err)
93+
}()
94+
if err != nil {
95+
return errors.New("backup not found")
96+
}
97+
if err = options.ParseCompactOptions(compact, &cm.options); err != nil {
98+
return errors.Annotate(err, "failed to parse compact options")
99+
}
100+
101+
b64, err := cm.base64ifyStorage(ctx)
102+
if err != nil {
103+
return errors.Annotate(err, "failed to base64ify storage")
104+
}
105+
return cm.runCompaction(ctx, b64)
106+
}
107+
108+
func (cm *Manager) base64ifyStorage(ctx context.Context) (string, error) {
109+
brCmd, err := cm.base64ifyCmd(ctx)
110+
if err != nil {
111+
return "", err
112+
}
113+
out, err := brCmd.Output()
114+
if err != nil {
115+
eerr, ok := err.(*exec.ExitError)
116+
if !ok {
117+
return "", errors.Annotatef(err, "failed to execute BR with args %v", brCmd.Args)
118+
}
119+
klog.Warningf("Failed to execute base64ify; stderr = %s", string(eerr.Stderr))
120+
return "", errors.Annotatef(err, "failed to execute BR with args %v", brCmd.Args)
121+
}
122+
out = bytes.Trim(out, "\r\n \t")
123+
return string(out), nil
124+
}
125+
126+
func (cm *Manager) base64ifyCmd(ctx context.Context) (*exec.Cmd, error) {
127+
br := cm.brBin()
128+
args := []string{
129+
"operator",
130+
"base64ify",
131+
}
132+
StorageOpts, err := pkgutil.GenStorageArgsForFlag(cm.compact.Spec.StorageProvider, "storage")
133+
if err != nil {
134+
return nil, err
135+
}
136+
args = append(args, StorageOpts...)
137+
return exec.CommandContext(ctx, br, args...), nil
138+
}
139+
140+
func (cm *Manager) runCompaction(ctx context.Context, base64Storage string) (err error) {
141+
cmd := cm.compactCmd(ctx, base64Storage)
142+
143+
// tikvLog is used to capture the log from tikv-ctl, which is sent to stderr by default
144+
tikvLog, err := cmd.StderrPipe()
145+
if err != nil {
146+
return errors.Annotate(err, "failed to create stderr pipe for compact")
147+
}
148+
if err := cmd.Start(); err != nil {
149+
return errors.Annotate(err, "failed to start compact")
150+
}
151+
152+
cm.statusUpdater.OnStart(ctx, cm.compact)
153+
err = cm.processCompactionLogs(ctx, io.TeeReader(tikvLog, os.Stdout))
154+
if err != nil {
155+
return err
156+
}
157+
158+
return cmd.Wait()
159+
}
160+
161+
func (cm *Manager) compactCmd(ctx context.Context, base64Storage string) *exec.Cmd {
162+
ctl := cm.kvCtlBin()
163+
// You should not change the log configuration here, it should sync with the upstream setting
164+
args := []string{
165+
"--log-level",
166+
"INFO",
167+
"--log-format",
168+
"json",
169+
"compact-log-backup",
170+
"--storage-base64",
171+
base64Storage,
172+
"--from",
173+
strconv.FormatUint(cm.options.FromTS, 10),
174+
"--until",
175+
strconv.FormatUint(cm.options.UntilTS, 10),
176+
"-N",
177+
strconv.FormatUint(cm.options.Concurrency, 10),
178+
}
179+
return exec.CommandContext(ctx, ctl, args...)
180+
}
181+
182+
func (cm *Manager) processCompactionLogs(ctx context.Context, logStream io.Reader) error {
183+
dec := json.NewDecoder(logStream)
184+
185+
for dec.More() {
186+
if ctx.Err() != nil {
187+
return ctx.Err()
188+
}
189+
190+
var raw json.RawMessage
191+
if err := dec.Decode(&raw); err != nil {
192+
return errors.Annotate(err, "failed to decode raw log line")
193+
}
194+
195+
var line logLine
196+
if err := json.Unmarshal(raw, &line); err != nil {
197+
return errors.Annotate(err, "failed to decode the line of log")
198+
}
199+
line.Raw = raw
200+
201+
if err := cm.processLogLine(ctx, line); err != nil {
202+
return err
203+
}
204+
}
205+
206+
return nil
207+
}
208+
209+
func (cm *Manager) processLogLine(ctx context.Context, l logLine) error {
210+
switch l.Message {
211+
case messageCompactionDone:
212+
var prog controller.Progress
213+
if err := json.Unmarshal(l.Raw, &prog); err != nil {
214+
return errors.Annotatef(err, "failed to decode progress message: %s", string(l.Raw))
215+
}
216+
cm.statusUpdater.OnProgress(ctx, cm.compact, prog)
217+
return nil
218+
case messageCompactAborted:
219+
errContainer := struct {
220+
Err string `json:"err"`
221+
}{}
222+
if err := json.Unmarshal(l.Raw, &errContainer); err != nil {
223+
return errors.Annotatef(err, "failed to decode error message: %s", string(l.Raw))
224+
}
225+
return errors.New(errContainer.Err)
226+
default:
227+
return nil
228+
}
229+
}

0 commit comments

Comments
 (0)