polardbxoperator/pkg/hpfs/hpfs_grpc.go

938 lines
30 KiB
Go

/*
Copyright 2021 Alibaba Group Holding Limited.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package hpfs
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/alibaba/polardbx-operator/pkg/hpfs/backupbinlog"
"github.com/alibaba/polardbx-operator/pkg/hpfs/common"
"github.com/alibaba/polardbx-operator/pkg/hpfs/config"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/go-logr/logr"
protobuf "github.com/golang/protobuf/proto"
"github.com/google/uuid"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/alibaba/polardbx-operator/pkg/hpfs/discovery"
"github.com/alibaba/polardbx-operator/pkg/hpfs/local"
"github.com/alibaba/polardbx-operator/pkg/hpfs/proto"
"github.com/alibaba/polardbx-operator/pkg/hpfs/remote"
"github.com/alibaba/polardbx-operator/pkg/hpfs/task"
)
const (
OpTransfer = "Transfer"
OpDownload = "Download"
OpUpload = "Upload"
)
type rpcService struct {
logr.Logger
hostDiscovery discovery.HostDiscovery
localFileService local.LocalFileService
taskEngine task.Engine
// Systemd style. Refer to https://github.com/kubernetes/kubernetes/blob/ea07644522/pkg/kubelet/cm/cgroup_manager_linux.go#L53.
systemdStyleCgroup bool
}
func (r *rpcService) init() error {
ok, err := r.localFileService.IsExists("/sys/fs/cgroup/blkio/kubepods.slice")
if err != nil {
return err
}
if ok {
r.systemdStyleCgroup = true
}
return nil
}
func (r *rpcService) ok(msg string) *proto.Status {
return &proto.Status{
Code: proto.Status_OK,
Message: msg,
}
}
func (r *rpcService) fail(err error) *proto.Status {
code := proto.Status_UNKNOWN
if os.IsExist(err) {
code = proto.Status_EXISTS
} else if os.IsNotExist(err) {
code = proto.Status_NOT_EXIST
} else if os.IsPermission(err) {
code = proto.Status_PERMISSION_DENIED
} else if err == task.ErrTaskNotFound {
code = proto.Status_NOT_EXIST
} else if err == task.ErrTaskCanceled {
code = proto.Status_INVALID
} else if err == task.ErrTaskAlreadyRun {
code = proto.Status_INVALID
} else if err == task.ErrTaskNotSubmitted {
code = proto.Status_INVALID
}
return &proto.Status{
Code: code,
Message: err.Error(),
}
}
func (r *rpcService) invalid(msg string) *proto.Status {
return &proto.Status{
Code: proto.Status_INVALID,
Message: msg,
}
}
func (r *rpcService) handleOwnerAndGroup(fileRequest *proto.FileRequest) error {
uid, gid := -1, -1
// Change gid and uid if specified
if fileRequest.GetGroup() != nil {
if _, ok := fileRequest.Group.(*proto.FileRequest_GroupName); ok {
groupName := fileRequest.GetGroupName()
group, err := user.LookupGroup(groupName)
if err != nil {
return err
}
gid, _ = strconv.Atoi(group.Gid)
} else if _, ok := fileRequest.Group.(*proto.FileRequest_Gid); ok {
gid = int(fileRequest.GetGid())
}
}
if fileRequest.GetOwner() != nil {
if _, ok := fileRequest.Owner.(*proto.FileRequest_User); ok {
username := fileRequest.GetUser()
u, err := user.Lookup(username)
if err != nil {
return err
}
uid, _ = strconv.Atoi(u.Uid)
} else if _, ok := fileRequest.Owner.(*proto.FileRequest_Uid); ok {
uid = int(fileRequest.GetUid())
}
}
if uid >= 0 || gid >= 0 {
return r.localFileService.Lchown(fileRequest.Path, uid, gid)
}
return nil
}
func (r *rpcService) CreateDirectory(ctx context.Context, request *proto.CreateDirectoryRequest) (*proto.CreateDirectoryResponse, error) {
dir := request.Directory
if dir == nil {
return &proto.CreateDirectoryResponse{Status: r.invalid("dir is nil")}, nil
}
// Create directory with options
var err error
opts := request.Options
if opts != nil && opts.CreateParentDirectories {
err = r.localFileService.CreateDirectories(dir.Path, os.FileMode(dir.Mode))
} else {
err = r.localFileService.CreateDirectory(dir.Path, os.FileMode(dir.Mode))
}
if err != nil {
if opts != nil && opts.IgnoreIfExists && os.IsExist(err) {
return &proto.CreateDirectoryResponse{Status: r.ok("ignore exists")}, nil
} else {
return &proto.CreateDirectoryResponse{Status: r.fail(err)}, nil
}
}
// Set owner and group if specified
if err := r.handleOwnerAndGroup(dir); err != nil {
return &proto.CreateDirectoryResponse{Status: r.fail(err)}, nil
}
return &proto.CreateDirectoryResponse{Status: r.ok("")}, nil
}
func (r *rpcService) CreateFile(ctx context.Context, request *proto.CreateFileRequest) (*proto.CreateFileResponse, error) {
file := request.File
if file == nil {
return &proto.CreateFileResponse{Status: r.invalid("file is nil")}, nil
}
opts := request.Options
exists, err := r.localFileService.IsExists(file.Path)
if err != nil {
return &proto.CreateFileResponse{Status: r.fail(err)}, nil
}
if exists {
if opts != nil && opts.OverwriteIfExists {
// Delete and recreate
err := r.localFileService.DeleteFile(file.Path)
if err != nil {
return &proto.CreateFileResponse{Status: r.fail(err)}, nil
}
err = r.localFileService.CreateFile(file.Path, os.FileMode(file.Mode))
if err != nil {
return &proto.CreateFileResponse{Status: r.fail(err)}, nil
}
} else if opts != nil && opts.IgnoreIfExists {
return &proto.CreateFileResponse{Status: r.ok("ignore exists")}, nil
}
} else {
// Create file and handle errors
err := r.localFileService.CreateFile(file.Path, os.FileMode(file.Mode))
if err != nil {
return &proto.CreateFileResponse{Status: r.fail(err)}, nil
}
}
// Set the content of the file if specified
if len(request.Content) > 0 {
err = r.localFileService.WriteFile(file.Path, []byte(request.Content))
if err != nil {
return &proto.CreateFileResponse{Status: r.fail(err)}, nil
}
}
// Set owner and group if specified
if err := r.handleOwnerAndGroup(file); err != nil {
return &proto.CreateFileResponse{Status: r.fail(err)}, nil
}
return &proto.CreateFileResponse{Status: r.ok("")}, nil
}
func (r *rpcService) CreateSymbolicLink(ctx context.Context, request *proto.CreateSymbolicLinkRequest) (*proto.CreateSymbolicLinkResponse, error) {
linkPath := request.LinkPath
if linkPath == nil {
return &proto.CreateSymbolicLinkResponse{Status: r.invalid("file is nil")}, nil
}
opts := request.Options
// Create file and handle errors
err := r.localFileService.CreateSymlink(request.DestPath, linkPath.Path)
if err != nil {
if opts == nil {
return &proto.CreateSymbolicLinkResponse{Status: r.fail(err)}, nil
}
if os.IsExist(err) {
if opts.IgnoreIfExists {
return &proto.CreateSymbolicLinkResponse{Status: r.ok("ignore exists")}, nil
} else {
return &proto.CreateSymbolicLinkResponse{Status: r.fail(err)}, nil
}
}
}
// Set owner and group if specified
if err := r.handleOwnerAndGroup(linkPath); err != nil {
return &proto.CreateSymbolicLinkResponse{Status: r.fail(err)}, nil
}
return &proto.CreateSymbolicLinkResponse{Status: r.ok("")}, nil
}
func (r *rpcService) RemoveDirectory(ctx context.Context, request *proto.RemoveDirectoryRequest) (*proto.RemoveDirectoryResponse, error) {
opts := request.Options
// Remove directory
err := r.localFileService.DeleteDirectory(request.Path, opts != nil && opts.Recursive)
if err != nil {
if os.IsNotExist(err) && opts != nil && opts.IgnoreIfNotExists {
return &proto.RemoveDirectoryResponse{Status: r.ok("ignore not exists")}, nil
} else {
return &proto.RemoveDirectoryResponse{Status: r.fail(err)}, nil
}
}
return &proto.RemoveDirectoryResponse{Status: r.ok("")}, nil
}
func (r *rpcService) RemoveFile(ctx context.Context, request *proto.RemoveFileRequest) (*proto.RemoveFileResponse, error) {
opts := request.Options
// Remove file
err := r.localFileService.DeleteFile(request.Path)
if err != nil {
if os.IsNotExist(err) && opts != nil && opts.IgnoreIfNotExists {
return &proto.RemoveFileResponse{Status: r.ok("ignore not exists")}, nil
} else {
return &proto.RemoveFileResponse{Status: r.fail(err)}, nil
}
}
return &proto.RemoveFileResponse{Status: r.ok("")}, nil
}
func (r *rpcService) TruncateFile(ctx context.Context, request *proto.TruncateFileRequest) (*proto.TruncateFileResponse, error) {
opts := request.Options
// Truncate file
err := r.localFileService.TruncateFile(request.Path, 0)
if err != nil {
if os.IsNotExist(err) && opts != nil && opts.IgnoreIfNotExists {
return &proto.TruncateFileResponse{Status: r.ok("ignore not exists")}, nil
} else {
return &proto.TruncateFileResponse{Status: r.fail(err)}, nil
}
}
return &proto.TruncateFileResponse{Status: r.ok("")}, nil
}
func (r *rpcService) DownloadFiles(ctx context.Context, request *proto.DownloadRequest) (*proto.DownloadResponse, error) {
// Check request
if request.Tasks == nil || len(request.Tasks) == 0 {
return &proto.DownloadResponse{Status: r.invalid("empty tasks")}, nil
}
for _, dt := range request.Tasks {
if len(dt.Path) == 0 || dt.Source == nil {
return &proto.DownloadResponse{Status: r.invalid("invalid task")}, nil
}
}
if request.AsyncTask == nil || len(request.AsyncTask.TraceId) == 0 {
// New trace id.
request.AsyncTask = &proto.AsyncTaskRequest{TraceId: uuid.New().String()}
}
// Find task by trace id.
t, err := r.taskEngine.Get(request.AsyncTask.TraceId)
if err != nil {
return &proto.DownloadResponse{
Status: r.fail(err),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
if t == nil {
t = &task.Task{
TraceId: request.AsyncTask.TraceId,
Operation: OpDownload,
Details: request.String(),
}
}
// Submit the task to engine.
if t, err = r.taskEngine.Submit(t); err != nil {
return &proto.DownloadResponse{
Status: r.fail(err),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
// Submitted.
return &proto.DownloadResponse{Status: r.ok(""), Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId}}, nil
}
func (r *rpcService) UploadFiles(ctx context.Context, request *proto.UploadRequest) (*proto.UploadResponse, error) {
// Check request
if len(request.Path) == 0 {
return &proto.UploadResponse{Status: r.invalid("invalid path")}, nil
}
if request.Target == nil || request.Target.Endpoint == nil {
return &proto.UploadResponse{Status: r.invalid("invalid target")}, nil
}
if request.AsyncTask == nil || len(request.AsyncTask.TraceId) == 0 {
// New trace id.
request.AsyncTask = &proto.AsyncTaskRequest{TraceId: uuid.New().String()}
}
// Find task by trace id.
t, err := r.taskEngine.Get(request.AsyncTask.TraceId)
if err != nil {
return &proto.UploadResponse{
Status: r.fail(err),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
if t == nil {
t = &task.Task{
TraceId: request.AsyncTask.TraceId,
Operation: OpUpload,
Details: request.String(),
}
}
// Submit
if t, err = r.taskEngine.Submit(t); err != nil {
return &proto.UploadResponse{
Status: r.fail(err),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
// Submitted.
return &proto.UploadResponse{Status: r.ok(""), Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId}}, nil
}
func (r *rpcService) DeleteRemoteFile(ctx context.Context, request *proto.DeleteRemoteFileRequest) (*proto.DeleteRemoteFileResponse, error) {
fs, err := remote.GetFileService(request.Target.Protocol)
if err != nil {
return &proto.DeleteRemoteFileResponse{Status: r.fail(err)}, nil
}
err = fs.DeleteFile(ctx, request.Target.Path, request.Target.Auth, request.Target.Other)
if err != nil {
return &proto.DeleteRemoteFileResponse{Status: r.fail(err)}, nil
}
return &proto.DeleteRemoteFileResponse{Status: r.ok("")}, nil
}
func (r *rpcService) TransferFiles(ctx context.Context, request *proto.TransferRequest) (*proto.TransferResponse, error) {
// Check request
if request.SrcHost == nil || request.DestHost == nil {
return &proto.TransferResponse{Status: r.invalid("invalid src/dest host")}, nil
}
if len(request.SrcPath) == 0 || len(request.DestPath) == 0 {
return &proto.TransferResponse{Status: r.invalid("invalid src/dest path")}, nil
}
if request.AsyncTask == nil || len(request.AsyncTask.TraceId) == 0 {
// New trace id.
request.AsyncTask = &proto.AsyncTaskRequest{TraceId: uuid.New().String()}
}
// Find task by trace id.
t, err := r.taskEngine.Get(request.AsyncTask.TraceId)
if err != nil {
return &proto.TransferResponse{
Status: r.fail(err),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
if t == nil {
t = &task.Task{
TraceId: request.AsyncTask.TraceId,
Operation: OpTransfer,
Details: request.String(),
}
}
// Submit
if t, err = r.taskEngine.Submit(t); err != nil {
if err == task.ErrTaskAlreadyRun || err == task.ErrTaskCanceled {
return &proto.TransferResponse{
Status: r.ok(err.Error()),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
return &proto.TransferResponse{
Status: r.fail(err),
Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId},
}, nil
}
// Submitted.
return &proto.TransferResponse{Status: r.ok(""), Task: &proto.AsyncTask{TraceId: request.AsyncTask.TraceId}}, nil
}
var taskStatusMap = map[task.TaskStatus]proto.TaskStatus{
task.Pending: proto.TaskStatus_PENDING,
task.Running: proto.TaskStatus_RUNNING,
task.Complete: proto.TaskStatus_SUCCESS,
task.Error: proto.TaskStatus_FAILED,
task.Canceling: proto.TaskStatus_CANCELING,
task.Cancel: proto.TaskStatus_CANCELED,
}
func (r *rpcService) ShowAsyncTaskStatus(ctx context.Context, request *proto.ShowAsyncTaskStatusRequest) (*proto.ShowAsyncTaskStatusResponse, error) {
t, err := r.taskEngine.Get(request.Task.TraceId)
if err != nil {
return &proto.ShowAsyncTaskStatusResponse{Status: r.fail(err)}, nil
}
if t != nil {
resp := &proto.ShowAsyncTaskStatusResponse{Status: r.ok("")}
var ok bool
resp.TaskStatus, ok = taskStatusMap[t.Status]
if !ok {
resp.TaskStatus = proto.TaskStatus_UNKNOWN
}
resp.Progress = float64(t.Progress)
return resp, nil
} else {
return &proto.ShowAsyncTaskStatusResponse{
Status: r.invalid("task not found"),
}, nil
}
}
func (r *rpcService) CancelAsyncTask(ctx context.Context, request *proto.CancelAsyncTaskRequest) (*proto.CancelAsyncTaskResponse, error) {
t, err := r.taskEngine.Get(request.Task.TraceId)
if err != nil {
return &proto.CancelAsyncTaskResponse{Status: r.fail(err)}, nil
}
if t != nil {
if err := r.taskEngine.Cancel(t); err != nil {
return &proto.CancelAsyncTaskResponse{Status: r.fail(err)}, nil
}
return &proto.CancelAsyncTaskResponse{Status: r.ok("")}, nil
} else {
return &proto.CancelAsyncTaskResponse{Status: r.ok("task not found")}, nil
}
}
func (r *rpcService) ShowDiskUsage(ctx context.Context, request *proto.ShowDiskUsageRequest) (*proto.ShowDiskUsageResponse, error) {
usedBytes, err := r.localFileService.GetDiskUsage(request.Path)
if err != nil {
return &proto.ShowDiskUsageResponse{Status: r.fail(err)}, nil
}
return &proto.ShowDiskUsageResponse{Status: r.ok(""), Size: usedBytes}, nil
}
func (r *rpcService) ShowDiskInfo(ctx context.Context, request *proto.ShowDiskInfoRequest) (*proto.ShowDiskInfoResponse, error) {
di, err := r.localFileService.GetDiskInfo(request.GetPath())
if err != nil {
return &proto.ShowDiskInfoResponse{Status: r.fail(err)}, err
}
return &proto.ShowDiskInfoResponse{
Status: r.ok(""),
Info: &proto.DiskInfo{
Total: di.Total,
Free: di.Free,
Used: di.Used,
Files: di.Files,
FFree: di.Ffree,
FsType: di.FSType,
},
}, nil
}
func (r *rpcService) ListDirectory(ctx context.Context, request *proto.ListDirectoryRequest) (*proto.ListDirectoryResponse, error) {
files, err := r.localFileService.ListDirectory(request.Path)
if err != nil {
return &proto.ListDirectoryResponse{Status: r.fail(err)}, nil
}
pFiles := make([]*proto.FileInfo, 0, len(files))
for _, f := range files {
pFiles = append(pFiles, &proto.FileInfo{
Name: f.Name(),
Size: uint64(f.Size()),
Mode: uint32(f.Mode()),
ModTime: timestamppb.New(f.ModTime()),
IsDir: f.IsDir(),
})
}
return &proto.ListDirectoryResponse{Status: r.ok(""), Files: pFiles}, nil
}
func (r *rpcService) executeAsyncTask(ctx context.Context, t *task.Task) error {
logger := r.Logger.WithValues("operation", t.Operation)
switch t.Operation {
case OpTransfer:
request := proto.TransferRequest{}
err := protobuf.UnmarshalText(t.Details, &request)
if err != nil {
return err
}
return r.transferFiles(ctx, logger, &request)
case OpDownload:
panic("unsupported")
case OpUpload:
panic("unsupported")
default:
panic("unrecognized operation")
}
}
func (r *rpcService) transferFiles(ctx context.Context, logger logr.Logger, request *proto.TransferRequest) error {
// For debug purpose
if request.SrcHost.NodeName == "sleep" {
sleepSeconds, err := strconv.Atoi(request.SrcPath)
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, "sleep", strconv.Itoa(sleepSeconds))
return cmd.Run()
}
srcHost, err := r.hostDiscovery.GetHost(request.SrcHost.NodeName)
if err != nil {
return fmt.Errorf("failed to get src host info: %w", err)
}
srcPath, destPath := request.SrcPath, request.DestPath
rsyncOpts := []string{
"-a",
}
if !r.hostDiscovery.IsLocal(request.SrcHost.NodeName) {
srcPath = fmt.Sprintf("root@%s:%s", srcHost.HpfsHost, request.SrcPath)
rsyncOpts = append(rsyncOpts, "--rsh", fmt.Sprintf("ssh -o StrictHostKeyChecking=no -p %d", srcHost.SshPort))
}
rsyncOpts = append(rsyncOpts, srcPath, destPath)
// Do execute rsync with context
cmd := exec.CommandContext(ctx, "rsync", rsyncOpts...)
return cmd.Run()
}
var blkioKeyFileMap = map[proto.BlkioKey]string{
proto.BlkioKey_BPS_READ: "blkio.throttle.read_bps_device",
proto.BlkioKey_BPS_WRITE: "blkio.throttle.write_bps_device",
proto.BlkioKey_IOPS_READ: "blkio.throttle.read_iops_device",
proto.BlkioKey_IOPS_WRITE: "blkio.throttle.write_iops_device",
}
func (r *rpcService) getCgroupBlkioPath(podUid string) (string, error) {
// Find the pod's blkio cgroups path.
const cgroupRoot = "/sys/fs/cgroup"
if r.systemdStyleCgroup {
// Systemd style needs the replace.
podUid = strings.ReplaceAll(podUid, "-", "_")
kubePodBlkioCgroupPath := path.Join(cgroupRoot, "blkio", "kubepods.slice")
kubePodsQosLevelPath := []string{"besteffort", "burstable", ""}
podBlkioCgroupPath := ""
for _, levelPath := range kubePodsQosLevelPath {
subPath := "kubepods-" + levelPath + ".slice"
endDir := "kubepods-" + levelPath + "-pod" + podUid + ".slice"
if len(levelPath) == 0 {
subPath = ""
endDir = "kubepods-pod" + podUid + ".slice"
}
p := path.Join(kubePodBlkioCgroupPath, subPath, endDir)
if ok, err := r.localFileService.IsExists(p); err != nil {
} else if ok {
podBlkioCgroupPath = p
break
}
}
return podBlkioCgroupPath, nil
} else {
kubePodBlkioCgroupPath := path.Join(cgroupRoot, "blkio", "kubepods")
kubePodsQosLevelPath := []string{"besteffort", "burstable", ""}
podBlkioCgroupPath := ""
for _, levelPath := range kubePodsQosLevelPath {
p := path.Join(kubePodBlkioCgroupPath, levelPath, "pod"+podUid)
if ok, err := r.localFileService.IsExists(p); err != nil {
} else if ok {
podBlkioCgroupPath = p
break
}
}
return podBlkioCgroupPath, nil
}
}
func (r *rpcService) ControlCgroupsBlkio(ctx context.Context, request *proto.ControlCgroupsBlkioRequest) (*proto.ControlCgroupsBlkioResponse, error) {
// Check request
if len(request.PodUid) == 0 {
return &proto.ControlCgroupsBlkioResponse{Status: r.invalid("empty pod uid")}, nil
}
if request.Controls == nil || len(request.Controls) == 0 {
return &proto.ControlCgroupsBlkioResponse{Status: r.ok("")}, nil
}
validateCtrl := func(ctrl *proto.BlkioCtrl) error {
if ctrl.Device == nil {
return errors.New("invalid device")
}
return nil
}
for _, ctrl := range request.Controls {
if err := validateCtrl(ctrl); err != nil {
return &proto.ControlCgroupsBlkioResponse{Status: r.fail(err)}, nil
}
}
podBlkioCgroupPath, err := r.getCgroupBlkioPath(request.PodUid)
if err != nil {
return &proto.ControlCgroupsBlkioResponse{Status: r.fail(err)}, nil
}
if len(podBlkioCgroupPath) == 0 {
return &proto.ControlCgroupsBlkioResponse{Status: r.invalid("pod's cgroup path not found")}, nil
}
r.Info("found pod's blkio cgroups path", "pod", request.PodUid, "blkio-path", podBlkioCgroupPath)
// Get all devices' major:minor number
getDeviceMajorMinorNumber := func(ctrl *proto.BlkioCtrl) (string, error) {
s, ok := ctrl.GetDevice().(*proto.BlkioCtrl_MajorMinor)
if ok {
return s.MajorMinor, nil
}
deviceName, ok := ctrl.GetDevice().(*proto.BlkioCtrl_DeviceName)
if ok {
major, minor, err := r.localFileService.GetMajorMinorNumber(path.Join("/dev", deviceName.DeviceName), true)
if err != nil {
return "", err
}
return fmt.Sprintf("%d:%d", major, minor), nil
}
p, ok := ctrl.GetDevice().(*proto.BlkioCtrl_Path)
if ok {
major, minor, err := r.localFileService.GetMajorMinorNumber(p.Path, true)
if err != nil {
return "", err
}
return fmt.Sprintf("%d:%d", major, minor), nil
}
return "", errors.New("invalid device")
}
majorMinors := make([]string, 0, len(request.Controls))
for _, ctrl := range request.Controls {
majorMinor, err := getDeviceMajorMinorNumber(ctrl)
if err != nil {
return &proto.ControlCgroupsBlkioResponse{Status: r.ok("")}, nil
}
majorMinors = append(majorMinors, majorMinor)
}
// update cgroups blkio
for i, ctrl := range request.Controls {
if ctrl.Value < 0 {
continue
}
cgroupFile := path.Join(podBlkioCgroupPath, blkioKeyFileMap[ctrl.Key])
mm := majorMinors[i]
// <major>:<minor> <limit>
value := fmt.Sprintf("%s %d", mm, ctrl.Value)
r.Info("writing blkio cgroups value", "pod", request.PodUid, "blkio-path", cgroupFile, "value", value)
err := os.WriteFile(cgroupFile, []byte(fmt.Sprintf("%s %d", mm, ctrl.Value)), 000)
if err != nil {
r.Error(err, "failed to write blkio cgroups value", "pod", request.PodUid, "blkio-path", cgroupFile, "value", value)
return &proto.ControlCgroupsBlkioResponse{Status: r.fail(err)}, nil
}
}
return &proto.ControlCgroupsBlkioResponse{Status: r.ok("")}, nil
}
func (r *rpcService) OpenBackupBinlog(ctx context.Context, request *proto.OpenBackupBinlogRequest) (*proto.OpenBackupBinlogResponse, error) {
infoVersionFilepath := filepath.Join(request.GetLogDir(), backupbinlog.InfoVersionFilename)
versionFileExists, err := r.localFileService.IsExists(infoVersionFilepath)
if err != nil {
return &proto.OpenBackupBinlogResponse{Status: r.fail(err)}, nil
}
version := backupbinlog.InfoVersion + "=" + strconv.FormatInt(time.Now().Unix(), 10)
if !versionFileExists {
r.Info("the version file does not exists, create one", "filepath", infoVersionFilepath)
os.WriteFile(infoVersionFilepath, []byte(version), 0644)
} else {
data, err := os.ReadFile(infoVersionFilepath)
if err != nil {
return &proto.OpenBackupBinlogResponse{Status: r.fail(err)}, nil
}
version = string(data)
}
infoFilepath := filepath.Join(request.GetLogDir(), backupbinlog.InfoFilename)
exists, err := r.localFileService.IsExists(infoFilepath)
if err != nil {
return &proto.OpenBackupBinlogResponse{Status: r.fail(err)}, nil
}
if exists {
r.Info("the file exists, do update", "filepath", infoFilepath)
}
buf := bytes.Buffer{}
buf.Write([]byte(request.GetContent()))
buf.Write([]byte(version))
buf.Write([]byte("\n"))
err = os.WriteFile(infoFilepath, buf.Bytes(), 0644)
if err != nil {
r.Error(err, "failed to write file", "filepath", infoFilepath)
return &proto.OpenBackupBinlogResponse{Status: r.fail(err)}, nil
}
return &proto.OpenBackupBinlogResponse{Status: r.ok("")}, nil
}
func (r *rpcService) CloseBackupBinlog(ctx context.Context, request *proto.CloseBackupBinlogRequest) (*proto.CloseBackupBinlogResponse, error) {
infoFilepath := filepath.Join(request.GetLogDir(), backupbinlog.InfoFilename)
exists, err := r.localFileService.IsExists(infoFilepath)
if err != nil {
return &proto.CloseBackupBinlogResponse{Status: r.fail(err)}, nil
}
if exists {
err := os.Remove(infoFilepath)
if err != nil {
r.Error(err, "failed to remove bakcup binlog infoFile", "filepath", infoFilepath)
return &proto.CloseBackupBinlogResponse{Status: r.fail(err)}, nil
}
}
return &proto.CloseBackupBinlogResponse{Status: r.ok("")}, nil
}
func (r *rpcService) UploadLatestBinlogFile(ctx context.Context, request *proto.UploadLatestBinlogFileRequest) (*proto.UploadLatestBinlogFileResponse, error) {
done := backupbinlog.UploadLatestBinlogFile(request.GetLogDir())
r.Info(fmt.Sprintf("latest binlog file upload, done=%v", done))
return &proto.UploadLatestBinlogFileResponse{Status: r.ok(""), Done: done}, nil
}
func (r *rpcService) GetWatcherInfoHash(ctx context.Context, request *proto.GetWatcherInfoHashRequest) (*proto.GetWatcherInfoHashResponse, error) {
hash := backupbinlog.GetWatcherInfoHash(request.GetLogDir())
return &proto.GetWatcherInfoHashResponse{Status: r.ok(""), Hash: hash}, nil
}
func GetFileServiceParam(sinkName string, sinkType string) (err error, params map[string]string, auth map[string]string, fileServiceName string, returnSink config.Sink) {
var sinkPtr *config.Sink
for _, sink := range config.GetConfig().Sinks {
if sink.Name == sinkName && sink.Type == sinkType {
sinkPtr = &sink
returnSink = sink
break
}
}
if sinkPtr == nil {
err = fmt.Errorf("sink not found. type=%s name=%s", sinkType, sinkName)
return
}
auth = map[string]string{}
params = map[string]string{}
if sinkPtr.Type == config.SinkTypeOss {
auth["endpoint"] = sinkPtr.Endpoint
auth["access_key"] = sinkPtr.AccessKey
auth["access_secret"] = sinkPtr.AccessSecret
params["bucket"] = sinkPtr.Bucket
fileServiceName = "aliyun-oss"
} else if sinkPtr.Type == config.SinkTypeSftp {
auth["port"] = strconv.FormatInt(int64(sinkPtr.Port), 10)
auth["host"] = sinkPtr.Host
auth["username"] = sinkPtr.User
auth["password"] = sinkPtr.Password
fileServiceName = "sftp"
}
return
}
func (r *rpcService) DeleteBinlogFilesBefore(ctx context.Context, request *proto.DeleteBinlogFilesBeforeRequest) (*proto.DeleteBinlogFilesBeforeResponse, error) {
err, params, auth, fileServiceName, sink := GetFileServiceParam(request.GetSinkName(), request.GetSinkType())
if err != nil {
return &proto.DeleteBinlogFilesBeforeResponse{Status: r.fail(err)}, nil
}
params["deadline"] = strconv.FormatInt(request.GetUnixTime(), 10)
pxcBinlogDir := config.GetPxcBinlogStorageDirectory(request.GetNamespace(), request.GetPxcName(), request.GetPxcUid())
if sink.RootPath != "" && !strings.HasPrefix(pxcBinlogDir, "/") {
pxcBinlogDir = filepath.Join(sink.RootPath, pxcBinlogDir)
}
expiredFiles := make([]string, 0)
expiredFilesPtr := &expiredFiles
fileService, err := remote.GetFileService(fileServiceName)
if err != nil {
r.Error(err, "Failed to get file service")
return &proto.DeleteBinlogFilesBeforeResponse{Status: r.fail(err)}, nil
}
ctx = context.WithValue(ctx, common.AffectedFiles, expiredFilesPtr)
ft, err := fileService.DeleteExpiredFile(ctx, pxcBinlogDir, auth, params)
if err == nil {
err = ft.Wait()
}
if err != nil {
return &proto.DeleteBinlogFilesBeforeResponse{Status: r.fail(err)}, nil
}
return &proto.DeleteBinlogFilesBeforeResponse{Status: r.ok(""), DeletedFiles: *expiredFilesPtr}, nil
}
func (r *rpcService) ListLocalBinlogList(ctx context.Context, request *proto.ListLocalBinlogListRequest) (*proto.ListLocalBinlogListResponse, error) {
logDir := request.GetLogDir()
exists, err := r.localFileService.IsExists(logDir)
if err != nil {
return &proto.ListLocalBinlogListResponse{Status: r.fail(err)}, nil
}
if !exists {
return &proto.ListLocalBinlogListResponse{Status: r.fail(errors.New(fmt.Sprintf("not found filepath = %s", logDir)))}, nil
}
versionFilepath := filepath.Join(logDir, backupbinlog.InfoVersionFilename)
versionBytes, err := os.ReadFile(versionFilepath)
if err != nil {
return &proto.ListLocalBinlogListResponse{Status: r.fail(err)}, nil
}
index := bytes.IndexByte(versionBytes, '=')
var version string
if index >= 0 {
version = string(versionBytes[index+1:])
}
indexFilepath := filepath.Join(logDir, backupbinlog.IndexFilename)
indexBytes, err := os.ReadFile(indexFilepath)
if err != nil {
return &proto.ListLocalBinlogListResponse{Status: r.fail(err)}, nil
}
indexFileContent := string(indexBytes)
binlogFiles := make([]string, 0, 20)
for _, file := range strings.Split(indexFileContent, "\n") {
if len(file) > 0 {
binlogFiles = append(binlogFiles, file)
}
}
return &proto.ListLocalBinlogListResponse{
Version: version,
BinlogFiles: binlogFiles,
Status: r.ok(""),
}, nil
}
func (r *rpcService) ListRemoteBinlogList(ctx context.Context, request *proto.ListRemoteBinlogListRequest) (*proto.ListRemoteBinlogListResponse, error) {
err, params, auth, fileServiceName, sink := GetFileServiceParam(request.GetSinkName(), request.GetSinkType())
if err != nil {
return &proto.ListRemoteBinlogListResponse{Status: r.fail(err)}, nil
}
fileService, err := remote.GetFileService(fileServiceName)
if err != nil {
r.Error(err, "Failed to get file service")
return &proto.ListRemoteBinlogListResponse{Status: r.fail(err)}, nil
}
resultFiles := make([]string, 0)
resultFilesPtr := &resultFiles
ctx = context.WithValue(ctx, common.AffectedFiles, resultFilesPtr)
xstoreBinlogDir := config.GetXStorePodBinlogStorageDirectory(request.GetNamespace(), request.GetPxcName(), request.GetPxcUid(), request.GetXStoreName(), request.GetXStoreUid(), request.GetPodName())
if sink.RootPath != "" && !strings.HasPrefix(xstoreBinlogDir, "/") {
xstoreBinlogDir = filepath.Join(sink.RootPath, xstoreBinlogDir)
}
params["deadline"] = strconv.FormatInt(time.Now().Unix()+3600, 10)
ft, err := fileService.ListAllFiles(ctx, xstoreBinlogDir, auth, params)
if err == nil {
err = ft.Wait()
}
if err != nil {
return &proto.ListRemoteBinlogListResponse{Status: r.fail(err)}, nil
}
return &proto.ListRemoteBinlogListResponse{Status: r.ok(""), Files: *resultFilesPtr}, nil
return nil, nil
}