* Copyright (c) KylinSoft Co., Ltd. 2024.All rights reserved.
* ha-api licensed under the Mulan Permissive Software License, Version 2.
* See LICENSE file for more details.
* Author: bixiaoyan <bixiaoyan@kylinos.cn>
* Date: Thu Mar 27 09:32:28 2025 +0800
*/
package utils
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"gitee.com/openeuler/ha-api/settings"
"github.com/spf13/viper"
)
var port, _ = ReadPortFromConfig()
var IsInSlice = func(str string, sli []string) bool {
for _, item := range sli {
if item == str {
return true
}
}
return false
}
func RemoveDupl(strs []string) []string {
strSet := map[string]bool{}
for _, v := range strs {
strSet[v] = true
}
strsDupl := []string{}
for k := range strSet {
strsDupl = append(strsDupl, k)
}
return strsDupl
}
func isDevEnvironment() bool {
env := os.Getenv("HA_API")
return env != "production"
}
func GetNumAndUnitFromStr(s string) (string, string) {
r := regexp.MustCompile("[0-9](.*)[0-9]")
index := r.FindStringIndex(s)
if len(index) == 0 {
return s[:1], s[1:]
}
return s[:index[1]], s[index[1]:]
}
var globalHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: isDevEnvironment(),
},
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 90 * time.Second,
}
var SendRequest = func(url string, method string, data interface{}) (resp *http.Response, err error) {
var httpResp *http.Response
var req *http.Request
client := globalHTTPClient
switch method {
case "POST":
req, err = http.NewRequest("POST", url, bytes.NewReader(data.([]byte)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
case "GET":
req, err = http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
case "DELETE":
req, err = http.NewRequest("DELETE", url, bytes.NewReader(data.([]byte)))
if err != nil {
return nil, err
}
case "PUT":
req, err = http.NewRequest("PUT", url, bytes.NewReader(data.([]byte)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
default:
return nil, errors.New("unsupported method")
}
if err != nil {
return nil, err
}
httpResp, err = client.Do(req)
if err != nil {
return nil, err
}
return httpResp, nil
}
func ReadPortFromConfig() (string, error) {
defaultPort := "8088"
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
if err := viper.ReadInConfig(); err != nil {
slog.Warn(fmt.Sprintf("Error reading config file, %s, using default port %s", err, defaultPort))
return defaultPort, fmt.Errorf("Error reading config file, %s, using default port %s", err, defaultPort)
} else {
portStr := viper.GetString("port.ha-api")
_, err := strconv.Atoi(portStr)
if err != nil {
slog.Warn("Port in config is not a number, using default port 8088")
return defaultPort, fmt.Errorf("port in config is not a number, using default port %s", defaultPort)
}
return portStr, nil
}
}
func IsLocalCluster(clusterName string) bool {
localClusterName, err := getLocalClusterName()
if err != nil {
return false
}
if localClusterName != clusterName {
return false
}
return true
}
func getLocalClusterName() (string, error) {
filename := settings.CorosyncConfFile
file, err := os.Open(filename)
if err != nil {
return "", fmt.Errorf("open corosync conf failed")
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) >= 2 {
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if key == "cluster_name" {
return value, nil
}
}
}
return "", fmt.Errorf("not found cluster name info in corosync conf")
}
func Contains(slice []string, str string) bool {
for _, item := range slice {
if item == str {
return true
}
}
return false
}
func Values[K comparable, V any](m map[K]V) []V {
values := make([]V, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func RemoveByValue[T comparable](slice []T, value T) []T {
var newSlice []T
for _, v := range slice {
if v != value {
newSlice = append(newSlice, v)
}
}
return newSlice
}
func Pop[T any](slice []T) (T, []T) {
if len(slice) == 0 {
var zero T
return zero, slice
}
last := slice[len(slice)-1]
return last, slice[:len(slice)-1]
}
func PopFirst[T any](slice []T) (T, []T) {
if len(slice) == 0 {
var zero T
return zero, slice
}
first := slice[0]
return first, slice[1:]
}
func DifferenceSlice[T comparable](mainSlice, subtractSlice []T) []T {
subtractSet := make(map[T]struct{})
for _, item := range subtractSlice {
subtractSet[item] = struct{}{}
}
var diffSlice []T
for _, item := range mainSlice {
if _, found := subtractSet[item]; !found {
diffSlice = append(diffSlice, item)
}
}
return diffSlice
}
func SplitLinesScanner(s string) []string {
var lines []string
scanner := bufio.NewScanner(strings.NewReader(s))
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
return lines
}
func HostExists(hostName string) bool {
_, err := RunCommand(fmt.Sprintf(CmdHostExists, hostName))
return err == nil
}
func GrepHostsFile(host string) (int, string, error) {
cmd := fmt.Sprintf(CmdHostSearch, ShellEscape(host))
out, err := RunCommand(cmd)
if err != nil {
return 0, "", err
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
count := len(lines)
if lines[0] == "" {
count = 0
}
return count, string(out), nil
}
func HasSpace(s string) bool {
matched, _ := regexp.MatchString(`\s`, s)
return matched
}
*检查集群pcsd offfline的节点
*/
func CheckPcsdOfflineNodes() ([]string, error) {
out, err := RunCommand(CmdPcsdStatus)
if err != nil {
return nil, err
}
var offlineNodes []string
lines := strings.Split(string(out), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasSuffix(line, "Offline") {
parts := strings.Split(line, ":")
if len(parts) > 1 {
node := strings.TrimSpace(parts[0])
offlineNodes = append(offlineNodes, node)
}
}
}
return offlineNodes, nil
}
func IsClusterStarted() bool {
_, err := RunCommand(CmdClusterStatus)
return err == nil
}
func FindWithStart(s, substr string, start int) int {
if start < 0 || start > len(s) {
return -1
}
slice := s[start:]
index := strings.Index(slice, substr)
if index == -1 {
return -1
}
return start + index
}
func FindSubstring(s string, substr string, start int, end int) int {
if start < 0 || end > len(s) || start > end {
return -1
}
slice := s[start:end]
index := strings.Index(slice, substr)
if index != -1 {
return start + index
}
return -1
}
func GenerateRemoteRequestURL(node string, uri string) string {
if strings.HasPrefix(uri, "/remote") {
return "https://" + node + ":" + port + uri
}
return "https://" + node + ":" + port + "/remote" + uri
}
func CopyFile(src string, dest string) error {
cmd := exec.Command("/usr/bin/cp", src, dest)
return cmd.Run()
}
func CopyKeyFiles(privateKeySrc, publicKeySrc, destDir string) error {
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %v", destDir, err)
}
privateKeyDest := filepath.Join(destDir, filepath.Base(settings.RSA_PRIVATE_KEY))
if err := CopyFile(privateKeySrc, privateKeyDest); err != nil {
return fmt.Errorf("failed to copy private key: %v", err)
}
publicKeyDest := filepath.Join(destDir, filepath.Base(settings.RSA_PUBLIC_KEY))
if err := CopyFile(publicKeySrc, publicKeyDest); err != nil {
return fmt.Errorf("failed to copy public key: %v", err)
}
return nil
}