func ParseNormalizedSnapshotID(ctx context.Context, snapID string) (string, string, error) {
	log := GetRunIDLogger(ctx)
	tokens := strings.Split(snapID, SnapshotIDSeparator)
	if len(tokens) < 1 {
		return "", "", fmt.Errorf("snapshot ID '%s' cannot be split into tokens", snapID)
	}
	var clusterName string
	if len(tokens) > 1 {
		clusterName = tokens[1]
	}
	log.Debugf("normalized snapshot ID '%s' parsed into snapshot ID '%s' and cluster name '%s'",
		snapID, tokens[0], clusterName)
	return tokens[0], clusterName, nil
}
ParseNormalizedSnapshotID parses the normalized snapshot ID(using SnapshotIDSeparator) to extract the snapshot ID and cluster name(optional) that make up the normalized snapshot ID
e.g. 12345 => 12345, ""
e.g. 12345=_=_=cluster1 => 12345, cluster1
package utils
import (
	"context"
	"errors"
	"fmt"
	"gopkg.in/yaml.v3"
	"net"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"github.com/Showmax/go-fqdn"
	"github.com/container-storage-interface/spec/lib/go/csi"
	"github.com/dell/csi-isilon/common/constants"
	csictx "github.com/dell/gocsi/context"
	isi "github.com/dell/goisilon"
	"github.com/google/uuid"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)
func ParseBooleanFromContext(ctx context.Context, key string) bool {
	log := GetRunIDLogger(ctx)
	if val, ok := csictx.LookupEnv(ctx, key); ok {
		b, err := strconv.ParseBool(val)
		if err != nil {
			log.WithField(key, val).Debugf(
				"invalid boolean value for '%s', defaulting to false", key)
			return false
		}
		return b
	}
	return false
}
func ParseArrayFromContext(ctx context.Context, key string) ([]string, error) {
	var values []string
	if val, ok := csictx.LookupEnv(ctx, key); ok {
		err := yaml.Unmarshal([]byte(val), &values)
		if err != nil {
			return values, fmt.Errorf("invalid array value for '%s'", key)
		}
	}
	return values, nil
}
func ParseUintFromContext(ctx context.Context, key string) uint {
	log := GetRunIDLogger(ctx)
	if val, ok := csictx.LookupEnv(ctx, key); ok {
		i, err := strconv.ParseUint(val, 10, 0)
		if err != nil {
			log.WithField(key, val).Debugf(
				"invalid int value for '%s', defaulting to 0", key)
			return 0
		}
		return uint(i)
	}
	return 0
}
func ParseInt64FromContext(ctx context.Context, key string) (int64, error) {
	if val, ok := csictx.LookupEnv(ctx, key); ok {
		i, err := strconv.ParseInt(val, 10, 64)
		if err != nil {
			return 0, fmt.Errorf("invalid int64 value '%v' specified for '%s'", val, key)
		}
		return i, nil
	}
	return 0, nil
}
func RemoveExistingCSISockFile() error {
	log := GetLogger()
	protoAddr := os.Getenv(constants.EnvCSIEndpoint)
	log.Debugf("check if sock file '%s' has already been created", protoAddr)
	if protoAddr == "" {
		return nil
	}
	if _, err := os.Stat(protoAddr); !os.IsNotExist(err) {
		log.Debugf("sock file '%s' already exists, remove it", protoAddr)
		if err := os.RemoveAll(protoAddr); err != nil {
			log.WithError(err).Debugf("error removing sock file '%s'", protoAddr)
			return fmt.Errorf(
				"failed to remove sock file: '%s', error '%v'", protoAddr, err)
		}
		log.Debugf("sock file '%s' removed", protoAddr)
	} else {
		log.Debugf("sock file '%s' does not exist yet, move along", protoAddr)
	}
	return nil
}
func GetNewUUID() (string, error) {
	log := GetLogger()
	id, err := uuid.NewUUID()
	if err != nil {
		log.Errorf("error generating UUID : '%s'", err)
		return "", err
	}
	return id.String(), nil
}
func LogMap(ctx context.Context, mapName string, m map[string]string) {
	log := GetRunIDLogger(ctx)
	log.Debugf("map '%s':", mapName)
	for key, value := range m {
		log.Debugf("    [%s]='%s'", key, value)
	}
}
func RemoveSurroundingQuotes(s string) string {
	if len(s) > 0 && s[0] == '"' {
		s = s[1:]
	}
	if len(s) > 0 && s[len(s)-1] == '"' {
		s = s[:len(s)-1]
	}
	return s
}
func CombineTwoStrings(s1 string, s2 string, sign string) string {
	s := s1 + sign + s2
	return s
}
var CSIQuotaIDPrefix = "CSI_QUOTA_ID:"
var QuotaIDPattern = regexp.MustCompile(fmt.Sprintf("^%s(.*)", CSIQuotaIDPrefix))
var VolumeIDSeparator = "=_=_="
var SnapshotIDSeparator = "=_=_="
var VolumeIDPattern = regexp.MustCompile(fmt.Sprintf("^(.+)%s(\\d+)%s(.+)$", VolumeIDSeparator, VolumeIDSeparator))
var NodeIDSeparator = "=#=#="
var NodeIDPattern = regexp.MustCompile(fmt.Sprintf("^(.+)%s(.+)%s(.+)$", NodeIDSeparator, NodeIDSeparator))
var ExportConflictMessagePattern = regexp.MustCompile(fmt.Sprintf("^Export rules (\\d+) and (\\d+) conflict on '(.+)'$"))
var DummyHostNodeID = "localhost=#=#=localhost=#=#=127.0.0.1"
func GetQuotaIDWithCSITag(quotaID string) string {
	if quotaID == "" {
		return ""
	}
	return fmt.Sprintf("%s%s", CSIQuotaIDPrefix, quotaID)
}
func GetQuotaIDFromDescription(ctx context.Context, export isi.Export) (string, error) {
	log := GetRunIDLogger(ctx)
	log.Debugf("try to extract quota id from the description field of export (id:'%d', path: '%s', description : '%s')", export.ID, export.Paths, export.Description)
	if export.Description == "" {
		log.Debugf("description field is empty, this could be normal, the backing directory might not have a quota set on it, return normally")
		return "", nil
	}
	matches := QuotaIDPattern.FindStringSubmatch(export.Description)
	if len(matches) < 2 {
		log.Debugf("description field does not match the expected CSI_QUOTA_ID:(.*) pattern, this could be normal, the backing directory might not have a quota set on it and the description is a user-set text irrelevant to the export id, return normally")
		return "", nil
	}
	quotaID := matches[1]
	log.Debugf("quotaID extracted : '%s'", quotaID)
	return quotaID, nil
}
func GetFQDNByIP(ctx context.Context, ip string) (string, error) {
	log := GetRunIDLogger(ctx)
	names, err := net.LookupAddr(ip)
	if err != nil {
		log.Debugf("error getting FQDN: '%s'", err)
		return "", err
	}
	FQDN := strings.TrimSuffix(names[0], ".")
	return FQDN, nil
}
func GetOwnFQDN() (string, error) {
	nodeFQDN := fqdn.Get()
	if nodeFQDN == "unknown" {
		return "", errors.New("cannot get FQDN")
	}
	return nodeFQDN, nil
}
func IsStringInSlice(str string, list []string) bool {
	for _, b := range list {
		if b == str {
			return true
		}
	}
	return false
}
func IsStringInSlices(str string, list ...[]string) bool {
	for _, strs := range list {
		if IsStringInSlice(str, strs) {
			return true
		}
	}
	return false
}
func RemoveStringFromSlice(str string, list []string) []string {
	result := make([]string, 0)
	for _, v := range list {
		if str != v {
			result = append(result, v)
		}
	}
	return result
}
func RemoveStringsFromSlice(filters []string, list []string) []string {
	result := make([]string, 0)
	for _, str := range list {
		if !IsStringInSlice(str, filters) {
			result = append(result, str)
		}
	}
	return result
}
func GetAccessMode(req *csi.ControllerPublishVolumeRequest) (*csi.VolumeCapability_AccessMode_Mode, error) {
	vc := req.GetVolumeCapability()
	if vc == nil {
		return nil, status.Error(codes.InvalidArgument,
			"volume capability is required")
	}
	am := vc.GetAccessMode()
	if am == nil {
		return nil, status.Error(codes.InvalidArgument,
			"access mode is required")
	}
	if am.Mode == csi.VolumeCapability_AccessMode_UNKNOWN {
		return nil, status.Error(codes.InvalidArgument,
			"unknown access mode")
	}
	return &(am.Mode), nil
}
func GetNormalizedVolumeID(ctx context.Context, volName string, exportID int, accessZone, clusterName string) string {
	log := GetRunIDLogger(ctx)
	volID := fmt.Sprintf("%s%s%s%s%s%s%s", volName, VolumeIDSeparator, strconv.Itoa(exportID), VolumeIDSeparator, accessZone, VolumeIDSeparator, clusterName)
	log.Debugf("combined volume name '%s' with export ID '%d', access zone '%s' and cluster name '%s' to form volume ID '%s'",
		volName, exportID, accessZone, clusterName, volID)
	return volID
}
func ParseNormalizedVolumeID(ctx context.Context, volID string) (string, int, string, string, error) {
	log := GetRunIDLogger(ctx)
	tokens := strings.Split(volID, VolumeIDSeparator)
	if len(tokens) < 3 {
		return "", 0, "", "", fmt.Errorf("volume ID '%s' cannot be split into tokens", volID)
	}
	var clusterName string
	exportID, err := strconv.Atoi(tokens[1])
	if err != nil {
		return "", 0, "", "", err
	}
	if len(tokens) > 3 {
		clusterName = tokens[3]
	}
	log.Debugf("volume ID '%s' parsed into volume name '%s', export ID '%d', access zone '%s' and cluster name '%s'",
		volID, tokens[0], exportID, tokens[2], clusterName)
	return tokens[0], exportID, tokens[2], clusterName, nil
}
func GetNormalizedSnapshotID(ctx context.Context, snapshotID, clusterName string) string {
	log := GetRunIDLogger(ctx)
	snapID := fmt.Sprintf("%s%s%s", snapshotID, SnapshotIDSeparator, clusterName)
	log.Debugf("combined snapshot id '%s' and cluster name '%s' to form normalized snapshot ID '%s'",
		snapshotID, clusterName, snapID)
	return snapID
}