mirror of
https://github.com/SynologyOpenSource/synology-csi.git
synced 2026-08-25 01:27:15 +00:00
168 lines
4.1 KiB
Go
168 lines
4.1 KiB
Go
/*
|
|
* Copyright 2025 Synology Inc.
|
|
*/
|
|
|
|
package driver
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
utilexec "k8s.io/utils/exec"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Namespace represents a single NVMe namespace device visible to the node.
|
|
type Namespace struct {
|
|
DevPath string // e.g. /dev/nvme1n1
|
|
Subsystem string // e.g. nqn.2014-08.org.nvmexpress:uuid:...
|
|
Uuid string
|
|
}
|
|
|
|
var (
|
|
nvmeNamespaceRegex = regexp.MustCompile(`^nvme[0-9]+n[0-9]+$`)
|
|
nvmeRegex = regexp.MustCompile(`^nvme[0-9]+$`)
|
|
)
|
|
|
|
const (
|
|
NVME_SUBSYSTEM_PATH = "/sys/class/nvme-subsystem"
|
|
NVME_FABRICS_PATH = "/sys/class/nvme-fabrics/ctl"
|
|
NVMePort = 4420
|
|
)
|
|
|
|
func (t *tools) nvme(cmdArgs ...string) utilexec.Cmd {
|
|
return t.executor.Command("nvme", cmdArgs...)
|
|
}
|
|
|
|
func (t *tools) nvmeDiscover(ip string, port int, transport string, hostNqn string) error {
|
|
args := []string{"discover", "-t", transport, "-a", ip, "-s", fmt.Sprint(port)}
|
|
if hostNqn != "" {
|
|
args = append(args, "--hostnqn", hostNqn)
|
|
}
|
|
|
|
cmd := t.nvme(args...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("%s (%v)", strings.TrimSpace(string(out)), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tools) nvmeConnect(ip string, port int, transport string, nqn string, hostNqn string) error {
|
|
args := []string{"connect", "-t", transport, "-a", ip, "-s", fmt.Sprint(port), "-n", nqn}
|
|
if hostNqn != "" {
|
|
args = append(args, "--hostnqn", hostNqn)
|
|
}
|
|
|
|
cmd := t.nvme(args...)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("%s (%v)", strings.TrimSpace(string(out)), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *tools) nvmeDisconnect(nqn string) error {
|
|
cmd := t.nvme("disconnect", "-n", nqn)
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("%s (%v)", strings.TrimSpace(string(out)), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readTrimFile(path string) (string, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
log.Warnf("Failed to read %s: %v", path, err)
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(string(data)), nil
|
|
}
|
|
|
|
// listNamespacesFromSysfs lists all namespaces for a given subsystem NQN
|
|
func listNamespacesFromSysfs(subsysNqn string) ([]Namespace, error) {
|
|
subsysDirs, err := os.ReadDir(NVME_SUBSYSTEM_PATH)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to read %s: %v", NVME_SUBSYSTEM_PATH, err)
|
|
}
|
|
|
|
var res []Namespace
|
|
for _, sd := range subsysDirs {
|
|
subsysPath := filepath.Join(NVME_SUBSYSTEM_PATH, sd.Name())
|
|
|
|
nqn, err := readTrimFile(filepath.Join(subsysPath, "subsysnqn"))
|
|
if err != nil || nqn != subsysNqn {
|
|
continue
|
|
}
|
|
|
|
nsDirs, err := os.ReadDir(subsysPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, nsDir := range nsDirs {
|
|
name := nsDir.Name()
|
|
if nvmeNamespaceRegex.MatchString(name) {
|
|
nsPath := filepath.Join(subsysPath, name)
|
|
|
|
uuid, err := readTrimFile(filepath.Join(nsPath, "uuid"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
devPath := filepath.Join("/dev", name)
|
|
res = append(res, Namespace{
|
|
DevPath: devPath,
|
|
Subsystem: nqn,
|
|
Uuid: uuid,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(res) == 0 {
|
|
return nil, fmt.Errorf("No namespaces found for subsystem %s", subsysNqn)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func hasNVMeSession(ip string, port int, transport string, nqn string) bool {
|
|
dirs, err := os.ReadDir(NVME_FABRICS_PATH)
|
|
if err != nil {
|
|
log.Errorf("Failed to read %s: %v", NVME_FABRICS_PATH, err)
|
|
return false
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if !nvmeRegex.MatchString(dir.Name()) {
|
|
continue
|
|
}
|
|
sessionPath := filepath.Join(NVME_FABRICS_PATH, dir.Name())
|
|
|
|
nqnStr, err := readTrimFile(filepath.Join(sessionPath, "subsysnqn"))
|
|
if err != nil || nqnStr != nqn {
|
|
continue
|
|
}
|
|
|
|
transportStr, err := readTrimFile(filepath.Join(sessionPath, "transport"))
|
|
if err != nil || transportStr != transport {
|
|
continue
|
|
}
|
|
|
|
addrStr, err := readTrimFile(filepath.Join(sessionPath, "address"))
|
|
// e.g. traddr=192.0.2.10,trsvcid=4420,src_addr=198.51.100.20
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if strings.Contains(addrStr, fmt.Sprintf("traddr=%s", ip)) &&
|
|
strings.Contains(addrStr, fmt.Sprintf("trsvcid=%d", port)) {
|
|
log.Infof("NVMe session[%s -> %s:%d] already exists.", nqn, ip, port)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} |