Skip to content

Commit 89492ee

Browse files
committed
Backup/Restore RBAC related objects from Zookeeper via direct connection to zookeeper/keeper, fix #604
1 parent 99c8361 commit 89492ee

File tree

11 files changed

+391
-34
lines changed

11 files changed

+391
-34
lines changed

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ IMPROVEMENTS
33
- first implementation for properly backup S3/GCS/Azure disks, support server-side copy to back up bucket during `clickhouse-backup` create and during `clickhouse-backup restore`, requires add `object_disk_path` to `s3`,`gcs`,`azblob` section, fix [447](https://github.com/Altinity/clickhouse-backup/issues/447)
44
- Implementation blacklist for table engines during backup / download / upload / restore [537](https://github.com/Altinity/clickhouse-backup/issues/537)
55
- restore RBAC / configs, refactoring restart clickhouse-server via `sql:SYSTEM SHUTDOWN` or `exec:systemctl restart clickhouse-server`, add `--rbac-only` and `--configs-only` options to `create`, `upload`, `download`, `restore` command. fix [706]https://github.com/Altinity/clickhouse-backup/issues/706
6+
- Backup/Restore RBAC related objects from Zookeeper via direct connection to zookeeper/keeper, fix [604](https://github.com/Altinity/clickhouse-backup/issues/604)
67

78
BUG FIXES
89
- fix possible create backup failures during UNFREEZE not exists tables, affected 2.2.7+ version, fix [704](https://github.com/Altinity/clickhouse-backup/issues/704)

Dockerfile

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,29 @@ RUN go env
2727
WORKDIR /src/
2828
# cache modules when go.mod go.sum changed
2929
COPY go.mod go.sum ./
30-
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go go mod download -x
30+
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/ go mod download -x
3131

3232
FROM builder-base AS builder-race
3333
ARG TARGETPLATFORM
3434
COPY ./ /src/
3535
RUN mkdir -p ./clickhouse-backup/
36-
# RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go GOOS=$( echo ${TARGETPLATFORM} | cut -d "/" -f 1) GOARCH=$( echo ${TARGETPLATFORM} | cut -d "/" -f 2) CC=musl-gcc CGO_ENABLED=1 go build -a -cover -buildvcs=false -ldflags "-X 'main.version=race' -linkmode=external -extldflags '-static'" -gcflags "all=-N -l" -race -o ./clickhouse-backup/clickhouse-backup-race ./cmd/clickhouse-backup
37-
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go GOOS=$( echo ${TARGETPLATFORM} | cut -d "/" -f 1) GOARCH=$( echo ${TARGETPLATFORM} | cut -d "/" -f 2) CC=musl-gcc CGO_ENABLED=1 go build -a -cover -buildvcs=false -ldflags "-X 'main.version=race' -linkmode=external -extldflags '-static'" -race -o ./clickhouse-backup/clickhouse-backup-race ./cmd/clickhouse-backup
36+
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/ GOOS=$( echo ${TARGETPLATFORM} | cut -d "/" -f 1) GOARCH=$( echo ${TARGETPLATFORM} | cut -d "/" -f 2) CC=musl-gcc CGO_ENABLED=1 go build -a -cover -buildvcs=false -ldflags "-X 'main.version=race' -linkmode=external -extldflags '-static'" -race -o ./clickhouse-backup/clickhouse-backup-race ./cmd/clickhouse-backup
3837
RUN cp -l ./clickhouse-backup/clickhouse-backup-race /bin/clickhouse-backup && echo "$(ldd ./clickhouse-backup/clickhouse-backup-race 2>&1 || true)" | grep -c "not a dynamic executable"
39-
# RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go GOOS=$( echo ${TARGETPLATFORM} | cut -d "/" -f 1) GOARCH=$( echo ${TARGETPLATFORM} | cut -d "/" -f 2) GOEXPERIMENT=boringcrypto CC=musl-gcc CGO_ENABLED=1 go build -cover -buildvcs=false -ldflags "-X 'main.version=race-fips' -linkmode=external -extldflags '-static'" -gcflags "all=-N -l" -race -o ./clickhouse-backup/clickhouse-backup-race-fips ./cmd/clickhouse-backup
40-
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go GOOS=$( echo ${TARGETPLATFORM} | cut -d "/" -f 1) GOARCH=$( echo ${TARGETPLATFORM} | cut -d "/" -f 2) GOEXPERIMENT=boringcrypto CC=musl-gcc CGO_ENABLED=1 go build -cover -buildvcs=false -ldflags "-X 'main.version=race-fips' -linkmode=external -extldflags '-static'" -race -o ./clickhouse-backup/clickhouse-backup-race-fips ./cmd/clickhouse-backup
38+
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/ GOOS=$( echo ${TARGETPLATFORM} | cut -d "/" -f 1) GOARCH=$( echo ${TARGETPLATFORM} | cut -d "/" -f 2) GOEXPERIMENT=boringcrypto CC=musl-gcc CGO_ENABLED=1 go build -cover -buildvcs=false -ldflags "-X 'main.version=race-fips' -linkmode=external -extldflags '-static'" -race -o ./clickhouse-backup/clickhouse-backup-race-fips ./cmd/clickhouse-backup
4139
RUN cp -l ./clickhouse-backup/clickhouse-backup-race-fips /bin/clickhouse-backup-fips && echo "$(ldd ./clickhouse-backup/clickhouse-backup-race-fips 2>&1 || true)" | grep -c "not a dynamic executable"
4240
COPY entrypoint.sh /entrypoint.sh
4341

4442

4543
FROM builder-base AS builder-docker
4644
COPY ./ /src/
4745
RUN mkdir -p ./build/
48-
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go make build
46+
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/ make build
4947

5048

5149
FROM builder-base AS builder-fips
5250
COPY ./ /src/
5351
RUN mkdir -p ./build/
54-
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/.cache/go make build-fips
52+
RUN --mount=type=cache,id=clickhouse-backup-gobuild,target=/root/ make build-fips
5553

5654

5755
FROM scratch AS make-build-race

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/djherbis/nio/v3 v3.0.1
2121
github.com/eapache/go-resiliency v1.3.0
2222
github.com/go-logfmt/logfmt v0.6.0
23+
github.com/go-zookeeper/zk v1.0.3
2324
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
2425
github.com/google/uuid v1.3.0
2526
github.com/gorilla/mux v1.8.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
167167
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
168168
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
169169
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
170+
github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
171+
github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
170172
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
171173
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
172174
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=

pkg/backup/create.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"github.com/Altinity/clickhouse-backup/pkg/config"
9+
"github.com/Altinity/clickhouse-backup/pkg/keeper"
910
"github.com/Altinity/clickhouse-backup/pkg/partition"
1011
"github.com/Altinity/clickhouse-backup/pkg/status"
1112
"github.com/Altinity/clickhouse-backup/pkg/storage"
@@ -229,14 +230,14 @@ func (b *Backuper) createBackupLocal(ctx context.Context, backupName string, par
229230

230231
if createRBAC || rbacOnly {
231232
if backupRBACSize, err = b.createBackupRBAC(ctx, backupPath, disks); err != nil {
232-
log.Errorf("error during do RBAC backup: %v", err)
233+
log.Fatalf("error during do RBAC backup: %v", err)
233234
} else {
234235
log.WithField("size", utils.FormatBytes(backupRBACSize)).Info("done createBackupRBAC")
235236
}
236237
}
237238
if createConfigs || configsOnly {
238239
if backupConfigSize, err = b.createBackupConfigs(ctx, backupPath); err != nil {
239-
log.Errorf("error during do CONFIG backup: %v", err)
240+
log.Fatalf("error during do CONFIG backup: %v", err)
240241
} else {
241242
log.WithField("size", utils.FormatBytes(backupConfigSize)).Info("done createBackupConfigs")
242243
}
@@ -451,8 +452,43 @@ func (b *Backuper) createBackupRBAC(ctx context.Context, backupPath string, disk
451452
return false, nil
452453
},
453454
})
454-
return rbacDataSize, copyErr
455+
if copyErr != nil {
456+
return 0, copyErr
457+
}
458+
replicatedRBACDataSize, err := b.createBackupRBACReplicated(ctx, rbacBackup)
459+
if err != nil {
460+
return 0, err
461+
}
462+
return rbacDataSize + replicatedRBACDataSize, nil
463+
}
464+
}
465+
466+
func (b *Backuper) createBackupRBACReplicated(ctx context.Context, rbacBackup string) (replicatedRBACDataSize uint64, err error) {
467+
replicatedRBAC := make([]struct {
468+
Name string `ch:"name"`
469+
}, 0)
470+
rbacDataSize := uint64(0)
471+
if err = b.ch.SelectContext(ctx, &replicatedRBAC, "SELECT name FROM system.user_directories WHERE type='replicated'"); err == nil && len(replicatedRBAC) > 0 {
472+
k := keeper.Keeper{Log: b.log.WithField("logger", "keeper")}
473+
if err = k.Connect(ctx, b.ch, b.cfg); err != nil {
474+
return 0, err
475+
}
476+
defer k.Close()
477+
for _, userDirectory := range replicatedRBAC {
478+
replicatedAccessPath, err := k.GetReplicatedAccessPath(userDirectory.Name)
479+
if err != nil {
480+
return 0, err
481+
}
482+
dumpFile := path.Join(rbacBackup, userDirectory.Name+".jsonl")
483+
b.log.WithField("logger", "createBackupRBACReplicated").Infof("keeper.Dump %s -> %s", replicatedAccessPath, dumpFile)
484+
dumpRBACSize, dumpErr := k.Dump(replicatedAccessPath, dumpFile)
485+
if dumpErr != nil {
486+
return 0, dumpErr
487+
}
488+
rbacDataSize += uint64(dumpRBACSize)
489+
}
455490
}
491+
return rbacDataSize, nil
456492
}
457493

458494
func (b *Backuper) AddTableToBackup(ctx context.Context, backupName, shadowBackupUUID string, diskList []clickhouse.Disk, table *clickhouse.Table, partitionsIdsMap common.EmptyMap) (map[string][]metadata.Part, map[string]int64, error) {

pkg/backup/restore.go

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"github.com/Altinity/clickhouse-backup/pkg/config"
8+
"github.com/Altinity/clickhouse-backup/pkg/keeper"
89
"github.com/Altinity/clickhouse-backup/pkg/status"
910
"github.com/Altinity/clickhouse-backup/pkg/storage"
1011
"github.com/Altinity/clickhouse-backup/pkg/storage/object_disk"
@@ -277,7 +278,7 @@ func (b *Backuper) restoreRBAC(ctx context.Context, backupName string, disks []c
277278
if err != nil {
278279
return err
279280
}
280-
if err = b.restoreBackupRelatedDir(backupName, "access", accessPath, disks); err == nil {
281+
if err = b.restoreBackupRelatedDir(backupName, "access", accessPath, disks, []string{"*.jsonl"}); err == nil {
281282
markFile := path.Join(accessPath, "need_rebuild_lists.mark")
282283
log.Infof("create %s for properly rebuild RBAC after restart clickhouse-server", markFile)
283284
file, err := os.Create(markFile)
@@ -298,22 +299,81 @@ func (b *Backuper) restoreRBAC(ctx context.Context, backupName string, disks []c
298299
}
299300
}
300301
}
301-
if !os.IsNotExist(err) {
302+
if err != nil && !os.IsNotExist(err) {
303+
return err
304+
}
305+
if err = b.restoreRBACReplicated(ctx, backupName, "access", disks); err != nil {
302306
return err
303307
}
304308
return nil
305309
}
306310

311+
func (b *Backuper) restoreRBACReplicated(ctx context.Context, backupName string, backupPrefixDir string, disks []clickhouse.Disk) error {
312+
log := b.log.WithField("logger", "restoreRBACReplicated")
313+
defaultDataPath, err := b.ch.GetDefaultPath(disks)
314+
if err != nil {
315+
return ErrUnknownClickhouseDataPath
316+
}
317+
srcBackupDir := path.Join(defaultDataPath, "backup", backupName, backupPrefixDir)
318+
info, err := os.Stat(srcBackupDir)
319+
if err != nil {
320+
return err
321+
}
322+
323+
if !info.IsDir() {
324+
return fmt.Errorf("%s is not a dir", srcBackupDir)
325+
}
326+
replicatedRBAC := make([]struct {
327+
Name string `ch:"name"`
328+
}, 0)
329+
if err = b.ch.SelectContext(ctx, &replicatedRBAC, "SELECT name FROM system.user_directories WHERE type='replicated'"); err == nil && len(replicatedRBAC) > 0 {
330+
jsonLFiles, err := filepathx.Glob(path.Join(srcBackupDir, "*.jsonl"))
331+
if err != nil {
332+
return err
333+
}
334+
if len(jsonLFiles) == 0 {
335+
return nil
336+
}
337+
k := keeper.Keeper{Log: b.log.WithField("logger", "keeper")}
338+
if err = k.Connect(ctx, b.ch, b.cfg); err != nil {
339+
return err
340+
}
341+
defer k.Close()
342+
restoreReplicatedRBACMap := make(map[string]string, len(jsonLFiles))
343+
for _, jsonLFile := range jsonLFiles {
344+
for _, userDirectory := range replicatedRBAC {
345+
if strings.HasSuffix(jsonLFile, userDirectory.Name+".jsonl") {
346+
restoreReplicatedRBACMap[jsonLFile] = userDirectory.Name
347+
}
348+
}
349+
if _, exists := restoreReplicatedRBACMap[jsonLFile]; !exists {
350+
restoreReplicatedRBACMap[jsonLFile] = replicatedRBAC[0].Name
351+
}
352+
}
353+
for jsonLFile, userDirectoryName := range restoreReplicatedRBACMap {
354+
replicatedAccessPath, err := k.GetReplicatedAccessPath(userDirectoryName)
355+
if err != nil {
356+
return err
357+
}
358+
log.Infof("keeper.Restore(%s) -> %s", jsonLFile, replicatedAccessPath)
359+
if err := k.Restore(jsonLFile, replicatedAccessPath); err != nil {
360+
return err
361+
}
362+
}
363+
}
364+
return nil
365+
}
366+
307367
// restoreConfigs - copy backup_name/configs folder to /etc/clickhouse-server/
308368
func (b *Backuper) restoreConfigs(backupName string, disks []clickhouse.Disk) error {
309-
if err := b.restoreBackupRelatedDir(backupName, "configs", b.ch.Config.ConfigDir, disks); err != nil && os.IsNotExist(err) {
369+
if err := b.restoreBackupRelatedDir(backupName, "configs", b.ch.Config.ConfigDir, disks, nil); err != nil && os.IsNotExist(err) {
310370
return nil
311371
} else {
312372
return err
313373
}
314374
}
315375

316-
func (b *Backuper) restoreBackupRelatedDir(backupName, backupPrefixDir, destinationDir string, disks []clickhouse.Disk) error {
376+
func (b *Backuper) restoreBackupRelatedDir(backupName, backupPrefixDir, destinationDir string, disks []clickhouse.Disk, skipPatterns []string) error {
317377
log := b.log.WithField("logger", "restoreBackupRelatedDir")
318378
defaultDataPath, err := b.ch.GetDefaultPath(disks)
319379
if err != nil {
@@ -329,9 +389,19 @@ func (b *Backuper) restoreBackupRelatedDir(backupName, backupPrefixDir, destinat
329389
return fmt.Errorf("%s is not a dir", srcBackupDir)
330390
}
331391
log.Debugf("copy %s -> %s", srcBackupDir, destinationDir)
332-
copyOptions := recursiveCopy.Options{OnDirExists: func(src, dest string) recursiveCopy.DirExistsAction {
333-
return recursiveCopy.Merge
334-
}}
392+
copyOptions := recursiveCopy.Options{
393+
OnDirExists: func(src, dest string) recursiveCopy.DirExistsAction {
394+
return recursiveCopy.Merge
395+
},
396+
Skip: func(srcinfo os.FileInfo, src, dest string) (bool, error) {
397+
for _, pattern := range skipPatterns {
398+
if matched, matchErr := filepath.Match(pattern, filepath.Base(src)); matchErr != nil || matched {
399+
return true, matchErr
400+
}
401+
}
402+
return false, nil
403+
},
404+
}
335405
if err := recursiveCopy.Copy(srcBackupDir, destinationDir, copyOptions); err != nil {
336406
return err
337407
}

pkg/clickhouse/clickhouse.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ func (ch *ClickHouse) SelectSingleRow(ctx context.Context, dest interface{}, que
975975

976976
func (ch *ClickHouse) SelectSingleRowNoCtx(dest interface{}, query string, args ...interface{}) error {
977977
err := ch.conn.QueryRow(context.Background(), ch.LogQuery(query, args...), args...).Scan(dest)
978-
if err != nil && err == sql.ErrNoRows {
978+
if err != nil && errors.Is(err, sql.ErrNoRows) {
979979
return nil
980980
}
981981
return err
@@ -1004,21 +1004,32 @@ func (ch *ClickHouse) IsAtomic(database string) (bool, error) {
10041004
return isDatabaseAtomic == "Atomic", nil
10051005
}
10061006

1007-
// GetAccessManagementPath @todo think about how to properly extract access_management_path from /etc/clickhouse-server/
1007+
// GetAccessManagementPath extract path from following sources system.user_directories, access_control_path from /var/lib/clickhouse/preprocessed_configs/config.xml, system.disks
10081008
func (ch *ClickHouse) GetAccessManagementPath(ctx context.Context, disks []Disk) (string, error) {
10091009
accessPath := "/var/lib/clickhouse/access"
10101010
rows := make([]struct {
10111011
AccessPath string `ch:"access_path"`
10121012
}, 0)
10131013
if err := ch.SelectContext(ctx, &rows, "SELECT JSONExtractString(params,'path') AS access_path FROM system.user_directories WHERE type='local directory'"); err != nil || len(rows) == 0 {
1014+
configFile, doc, err := ch.ParseXML(ctx, "config.xml")
1015+
if err != nil {
1016+
ch.Log.Warnf("can't parse config.xml from %s, error: %v", configFile, err)
1017+
}
1018+
if err == nil {
1019+
accessControlPathNode := doc.SelectElement("access_control_path")
1020+
if accessControlPathNode != nil {
1021+
return accessControlPathNode.InnerText(), nil
1022+
}
1023+
}
1024+
10141025
if disks == nil {
10151026
disks, err = ch.GetDisks(ctx, false)
10161027
if err != nil {
10171028
return "", err
10181029
}
10191030
}
10201031
for _, disk := range disks {
1021-
if _, err := os.Stat(path.Join(disk.Path, "access")); !os.IsNotExist(err) {
1032+
if fInfo, err := os.Stat(path.Join(disk.Path, "access")); err == nil && fInfo.IsDir() {
10221033
accessPath = path.Join(disk.Path, "access")
10231034
break
10241035
}
@@ -1201,6 +1212,23 @@ func (ch *ClickHouse) GetPreprocessedConfigPath(ctx context.Context) (string, er
12011212
return path.Join("/", path.Join(paths[:len(paths)-1]...), "preprocessed_configs"), nil
12021213
}
12031214

1215+
func (ch *ClickHouse) ParseXML(ctx context.Context, configName string) (configFile string, doc *xmlquery.Node, err error) {
1216+
preprocessedConfigPath, err := ch.GetPreprocessedConfigPath(ctx)
1217+
if err != nil {
1218+
return "", nil, err
1219+
}
1220+
configFile = path.Join(preprocessedConfigPath, configName)
1221+
f, err := os.Open(configFile)
1222+
if err != nil {
1223+
return "", nil, err
1224+
}
1225+
doc, err = xmlquery.Parse(f)
1226+
if err != nil {
1227+
return "", nil, err
1228+
}
1229+
return configFile, doc, nil
1230+
}
1231+
12041232
var preprocessedXMLSettings map[string]map[string]string
12051233

12061234
// GetPreprocessedXMLSettings - @todo think about from_end and from_zookeeper corner cases
@@ -1211,7 +1239,7 @@ func (ch *ClickHouse) GetPreprocessedXMLSettings(ctx context.Context, settingsXP
12111239
}
12121240
resultSettings := make(map[string]string, len(settingsXPath))
12131241
if _, exists := preprocessedXMLSettings[fileName]; !exists {
1214-
preprocessedXMLSettings[fileName] = make(map[string]string, 0)
1242+
preprocessedXMLSettings[fileName] = make(map[string]string)
12151243
}
12161244
var doc *xmlquery.Node
12171245
for settingName, xpathExpr := range settingsXPath {

0 commit comments

Comments
 (0)