Files
Olares/cli/pkg/storage/redis.go

348 lines
9.4 KiB
Go

package storage
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/beclab/Olares/cli/pkg/core/logger"
"github.com/beclab/Olares/cli/pkg/common"
cc "github.com/beclab/Olares/cli/pkg/core/common"
"github.com/beclab/Olares/cli/pkg/core/connector"
"github.com/beclab/Olares/cli/pkg/core/task"
"github.com/beclab/Olares/cli/pkg/core/util"
"github.com/beclab/Olares/cli/pkg/manifest"
"github.com/beclab/Olares/cli/pkg/utils"
redisTemplates "github.com/beclab/Olares/cli/pkg/storage/templates"
"github.com/pkg/errors"
)
type CheckRedisServiceState struct {
common.KubeAction
}
func (t *CheckRedisServiceState) Execute(runtime connector.Runtime) error {
var systemInfo = runtime.GetSystemInfo()
var localIp = systemInfo.GetLocalIp()
var rpwd, _ = t.PipelineCache.GetMustString(common.CacheHostRedisPassword)
var cmd = fmt.Sprintf("%s -h %s -a %s ping", RedisCliFile, localIp, rpwd)
if pong, _ := runtime.GetRunner().SudoCmd(cmd, false, false); !strings.Contains(pong, "PONG") {
return fmt.Errorf("failed to connect redis server: %s:6379", localIp)
}
return nil
}
type EnableRedisService struct {
common.KubeAction
}
func (t *EnableRedisService) Execute(runtime connector.Runtime) error {
if _, err := runtime.GetRunner().SudoCmd("sysctl -w vm.overcommit_memory=1 net.core.somaxconn=10240", false, false); err != nil {
return err
}
if _, err := runtime.GetRunner().SudoCmd("systemctl daemon-reload", false, false); err != nil {
return err
}
if _, err := runtime.GetRunner().SudoCmd("systemctl restart redis-server", false, false); err != nil {
return err
}
if _, err := runtime.GetRunner().SudoCmd("systemctl enable redis-server", false, false); err != nil {
return err
}
var cmd = "( sleep 10 && systemctl --no-pager status redis-server ) || ( systemctl restart redis-server && sleep 3 && systemctl --no-pager status redis-server ) || ( systemctl restart redis-server && sleep 3 && systemctl --no-pager status redis-server )"
if _, err := runtime.GetRunner().SudoCmd(cmd, false, false); err != nil {
return err
}
return nil
}
type GetOrSetRedisConfig struct {
common.KubeAction
}
func (t *GetOrSetRedisConfig) Execute(runtime connector.Runtime) (err error) {
var redisPassword string
redisAddress := runtime.RemoteHost().GetInternalAddress()
defer func() {
if err == nil {
t.PipelineCache.Set(common.CacheHostRedisPassword, redisPassword)
t.PipelineCache.Set(common.CacheHostRedisAddress, redisAddress)
}
}()
exist, err := runtime.GetRunner().FileExist(RedisConfigFile)
if err != nil {
return err
}
if !exist {
redisPassword, _ = utils.GeneratePassword(16)
return
}
redisPassword, err = getRedisPwdFromConfigFile(runtime)
if err != nil {
return
}
if redisPassword != "" {
logger.Debugf("using existing Redis password: %s found in %s", redisPassword, RedisConfigFile)
return
}
logger.Warnf("found Redis config file %s but password is not set, generating a new one", RedisConfigFile)
redisPassword, _ = utils.GeneratePassword(16)
return
}
func getRedisPwdFromConfigFile(runtime connector.Runtime) (string, error) {
var cmd = fmt.Sprintf("cat %s 2>&1 |grep requirepass |cut -d' ' -f2 |tr -d '\n'", RedisConfigFile)
if res, err := runtime.GetRunner().SudoCmd(cmd, false, false); err != nil {
return "", errors.Wrap(err, "failed to get redis password")
} else {
return res, nil
}
}
type GenerateRedisService struct {
common.KubeAction
}
func (t *GenerateRedisService) Execute(runtime connector.Runtime) error {
redisPassword, ok := t.PipelineCache.GetMustString(common.CacheHostRedisPassword)
if !ok || redisPassword == "" {
return errors.New("redis password not set")
}
var systemInfo = runtime.GetSystemInfo()
var localIp = systemInfo.GetLocalIp()
if !utils.IsExist(RedisRootDir) {
utils.Mkdir(RedisConfigDir)
utils.Mkdir(RedisDataDir)
utils.Mkdir(RedisLogDir)
utils.Mkdir(RedisRunDir)
}
var data = util.Data{
"LocalIP": localIp,
"RootPath": RedisRootDir,
"Password": redisPassword,
}
redisConfStr, err := util.Render(redisTemplates.RedisConf, data)
if err != nil {
return errors.Wrap(errors.WithStack(err), "render redis conf template failed")
}
if err := util.WriteFile(RedisConfigFile, []byte(redisConfStr), cc.FileMode0640); err != nil {
return errors.Wrap(errors.WithStack(err), fmt.Sprintf("write redis conf %s failed", RedisConfigFile))
}
data = util.Data{
"RedisBinPath": RedisServerFile,
"RootPath": RedisRootDir,
"RedisConfPath": RedisConfigFile,
}
redisServiceStr, err := util.Render(redisTemplates.RedisService, data)
if err != nil {
return errors.Wrap(errors.WithStack(err), "render redis service template failed")
}
if err := util.WriteFile(RedisServiceFile, []byte(redisServiceStr), cc.FileMode0644); err != nil {
return errors.Wrap(errors.WithStack(err), fmt.Sprintf("write redis service %s failed", RedisServiceFile))
}
return nil
}
type CheckRedisExists struct {
common.KubePrepare
}
func (p *CheckRedisExists) PreCheck(runtime connector.Runtime) (bool, error) {
if !utils.IsExist(RedisServerInstalledFile) {
return true, nil
}
if !utils.IsExist(RedisServerFile) {
return true, nil
}
return false, nil
}
type InstallRedis struct {
common.KubeAction
manifest.ManifestAction
}
func (t *InstallRedis) Execute(runtime connector.Runtime) error {
compName := "redis"
version, err := t.getGlibcVersion(runtime)
if err != nil {
logger.Warnf("get glibc version error, %v", err)
} else {
if version.lessThan("2.32") {
compName = "redis-231"
}
}
redis, err := t.Manifest.Get(compName)
if err != nil {
return err
}
path := redis.FilePath(t.BaseDir)
if _, err := runtime.GetRunner().SudoCmd(fmt.Sprintf("rm -rf /tmp/redis-* && cp -f %s /tmp/%s && cd /tmp && tar xf ./%s", path, redis.Filename, redis.Filename), false, false); err != nil {
return errors.Wrapf(errors.WithStack(err), "untar redis failed")
}
unpackPath := strings.TrimSuffix(redis.Filename, ".tar.gz")
var cmd = fmt.Sprintf("cd /tmp/%s && cp ./* /usr/local/bin/ && rm -rf ./%s",
unpackPath, unpackPath)
if _, err := runtime.GetRunner().SudoCmd(cmd, false, false); err != nil {
return err
}
// if _, err := runtime.GetRunner().SudoCmd("[[ ! -f /usr/local/bin/redis-sentinel ]] && /usr/local/bin/redis-server /usr/local/bin/redis-sentinel || true", false, true); err != nil {
// return err
// }
if exist, err := runtime.GetRunner().FileExist(RedisServerFile); err != nil {
return err
} else if !exist {
if _, err := runtime.GetRunner().SudoCmd(fmt.Sprintf("ln -s %s %s", RedisServerInstalledFile, RedisServerFile), false, true); err != nil {
return err
}
}
if exist, err := runtime.GetRunner().FileExist(RedisCliFile); err != nil {
return err
} else if !exist {
if _, err := runtime.GetRunner().SudoCmd(fmt.Sprintf("ln -s %s %s", RedisCliInstalledFile, RedisCliFile), false, true); err != nil {
return err
}
}
return nil
}
func (t *InstallRedis) getGlibcVersion(runtime connector.Runtime) (GlibcVersion, error) {
output, err := runtime.GetRunner().SudoCmd("ldd --version", false, false)
if err != nil {
return "", err
}
// Convert the output to string
outputStr := string(output)
// Find the line containing the glibc version
lines := strings.Split(outputStr, "\n")
for _, line := range lines {
if strings.Contains(line, "GLIBC") {
lineToken := strings.Split(strings.TrimSpace(line), " ")
version := lineToken[len(lineToken)-1]
return GlibcVersion(version), nil
}
}
return "", errors.New("glibc version not found")
}
type GlibcVersion string
func (v GlibcVersion) lessThan(v2 GlibcVersion) bool {
return compareGLibC(string(v), string(v2)) < 0
}
func compareGLibC(versionA, versionB string) int {
partsA := strings.Split(versionA, ".")
partsB := strings.Split(versionB, ".")
// Convert major and minor versions to integers
majorA, _ := strconv.Atoi(partsA[0])
minorA, _ := strconv.Atoi(partsA[1])
majorB, _ := strconv.Atoi(partsB[0])
minorB, _ := strconv.Atoi(partsB[1])
if majorA < majorB {
return -1
} else if majorA > majorB {
return 1
} else {
if minorA < minorB {
return -1
} else if minorA > minorB {
return 1
}
}
return 0 // versions are equal
}
type InstallRedisModule struct {
common.KubeModule
manifest.ManifestModule
Skip bool
}
func (m *InstallRedisModule) IsSkip() bool {
return m.Skip
}
func (m *InstallRedisModule) Init() {
m.Name = "InstallRedis"
installRedis := &task.RemoteTask{
Name: "Install",
Hosts: m.Runtime.GetAllHosts(),
Prepare: &CheckRedisExists{},
Action: &InstallRedis{
ManifestAction: manifest.ManifestAction{
BaseDir: m.BaseDir,
Manifest: m.Manifest,
},
},
Parallel: false,
Retry: 0,
}
getOrSetRedisPassword := &task.LocalTask{
Name: "GetOrSetRedisConfig",
Action: new(GetOrSetRedisConfig),
Retry: 1,
}
configRedis := &task.RemoteTask{
Name: "Config",
Hosts: m.Runtime.GetAllHosts(),
Action: new(GenerateRedisService),
Parallel: false,
Retry: 0,
}
enableRedisService := &task.RemoteTask{
Name: "EnableRedisService",
Hosts: m.Runtime.GetAllHosts(),
Action: new(EnableRedisService),
Parallel: false,
Retry: 0,
}
checkRedisServiceState := &task.RemoteTask{
Name: "CheckState",
Hosts: m.Runtime.GetAllHosts(),
Action: new(CheckRedisServiceState),
Parallel: false,
Retry: 3,
Delay: 3 * time.Second,
}
m.Tasks = []task.Interface{
installRedis,
getOrSetRedisPassword,
configRedis,
enableRedisService,
checkRedisServiceState,
}
}