diff --git a/gonvme_tcp.go b/gonvme_tcp.go index 64495b0..74f5589 100644 --- a/gonvme_tcp.go +++ b/gonvme_tcp.go @@ -20,11 +20,16 @@ const ( // NVMePort - port number NVMePort = "4420" + + // NVMeNoObjsFoundExitCode exit code indicates that no records/targets/sessions/portals + // found to execute operation on + NVMeNoObjsFoundExitCode = 21 ) // NVMeTCP provides many nvme-specific functions type NVMeTCP struct { NVMeType + sessionParser NVMeSessionParser } // NewNVMeTCP - returns a new NVMeTCP client @@ -35,7 +40,7 @@ func NewNVMeTCP(opts map[string]string) *NVMeTCP { options: opts, }, } - + nvme.sessionParser = &sessionParser{} return &nvme } @@ -301,3 +306,26 @@ func (nvme *NVMeTCP) nvmeDisonnect(target NVMeTarget) error { return err } + +// GetSessions queries information about NVMe sessions +func (nvme *NVMeTCP) GetSessions() ([]NVMESession, error) { + exe := nvme.buildNVMeCommand([]string{"nvme", "list-subsys", "-o", "json"}) + cmd := exec.Command(exe[0], exe[1:]...) + output, err := cmd.Output() + if err != nil { + if isNoObjsExitCode(err) { + return []NVMESession{}, nil + } + return []NVMESession{}, err + } + return nvme.sessionParser.Parse(output), nil +} + +func isNoObjsExitCode(err error) bool { + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + return exitError.ExitCode() == NVMeNoObjsFoundExitCode + } + } + return false +} diff --git a/gonvme_types.go b/gonvme_types.go index 5579f99..0550688 100644 --- a/gonvme_types.go +++ b/gonvme_types.go @@ -14,10 +14,44 @@ type NVMeTarget struct { TargetType string //trtype } +//NVMESessionState defines the NVMe connection state +type NVMESessionState string + +//NVMETransportName defines the NMVe protocol +type NVMETransportName string + const ( // NVMeTransportTypeTCP - Placeholder for NVMe Transport type TCP NVMeTransportTypeTCP = "tcp" // NVMeTransportTypeFC - Placeholder for NVMe Transport type FC NVMeTransportTypeFC = "fc" + + //NVMESessionStateLive indicates the NVMe connection state as live + NVMESessionStateLive NVMESessionState = "live" + //NVMESessionStateDeleting indicates the NVMe connection state as deleting + NVMESessionStateDeleting NVMESessionState = "deleting" + //NVMESessionStateConnecting indicates the NVMe connection state as connecting + NVMESessionStateConnecting NVMESessionState = "connecting" + + //NVMETransportNameTCP indicates the NVMe protocol as tcp + NVMETransportNameTCP NVMETransportName = "tcp" + //NVMETransportNameFC indicates the NVMe protocol as fc + NVMETransportNameFC NVMETransportName = "fc" + //NVMETransportNameRDMA indicates the NVMe protocol as rdma + NVMETransportNameRDMA NVMETransportName = "rdma" ) + +// NVMESession defines an iSCSI session info +type NVMESession struct { + Target string + Portal string + Name string + NVMESessionState NVMESessionState + NVMETransportName NVMETransportName +} + +// NVMeSessionParser defines an NVMe session parser +type NVMeSessionParser interface { + Parse([]byte) []NVMESession +} diff --git a/gonvme_utils.go b/gonvme_utils.go new file mode 100644 index 0000000..d4cebb7 --- /dev/null +++ b/gonvme_utils.go @@ -0,0 +1,52 @@ +package gonvme + +import ( + "regexp" + "strings" +) + +type sessionParser struct{} + +func (sp *sessionParser) Parse(data []byte) []NVMESession { + str := string(data) + lines := strings.Split(str, "\n") + + var result []NVMESession + var curSession *NVMESession + var Target string + var Name string + + for _, line := range lines { + line = strings.ReplaceAll(strings.TrimSpace(line), ",", "") + + switch { + case regexp.MustCompile("^\"NQN\"").Match([]byte(line)): + Target = strings.ReplaceAll(strings.Fields(line)[2], "\"", "") + + case regexp.MustCompile("^\"Name\"").Match([]byte(line)): + Name = strings.ReplaceAll(strings.Fields(line)[2], "\"", "") + + case regexp.MustCompile("^\"Transport\"").Match([]byte(line)): + session := NVMESession{} + session.Target = Target + session.Name = Name + session.NVMETransportName = NVMETransportName(strings.ReplaceAll(strings.Fields(line)[2], "\"", "")) + if curSession != nil { + result = append(result, *curSession) + } + curSession = &session + + case regexp.MustCompile("^\"Address\"").Match([]byte(line)): + targetIP := strings.Split(strings.Fields(line)[2], "=")[1] + targetPortal := strings.ReplaceAll(strings.Split(strings.Fields(line)[3], "=")[1], "\"", "") + curSession.Portal = targetIP + ":" + targetPortal + + case regexp.MustCompile("^\"State\"").Match([]byte(line)): + curSession.NVMESessionState = NVMESessionState(strings.ReplaceAll(strings.Fields(line)[2], "\"", "")) + } + } + if curSession != nil { + result = append(result, *curSession) + } + return result +}