Discovery sessions have nil Target and nil ITNexus. The cleanup path crashed with nil pointer dereference when trying to remove ITNexus or access Target.Sessions for discovery sessions. Guard against nil Target (just release TSIH) and nil ITNexus. Also add nil safety to clearHostIP helper. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
548 lines
15 KiB
Go
548 lines
15 KiB
Go
/*
|
|
Copyright 2016 The GoStor Authors All rights reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package iscsit
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gostor/gotgt/pkg/api"
|
|
"github.com/gostor/gotgt/pkg/scsi"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
SESSION_NORMAL int = 0
|
|
SESSION_DISCOVERY int = 1
|
|
)
|
|
|
|
var DIGEST_CRC32C uint = 1 << 1
|
|
var DIGEST_NONE uint = 1 << 0
|
|
var DIGEST_ALL uint = DIGEST_NONE | DIGEST_CRC32C
|
|
var BHS_SIZE = 48
|
|
|
|
const (
|
|
MAX_QUEUE_CMD_MIN = 1
|
|
MAX_QUEUE_CMD_DEF = 128
|
|
MAX_QUEUE_CMD_MAX = 512
|
|
)
|
|
|
|
const (
|
|
ISCSI_PARAM_MAX_RECV_DLENGTH = iota
|
|
ISCSI_PARAM_HDRDGST_EN
|
|
ISCSI_PARAM_DATADGST_EN
|
|
ISCSI_PARAM_INITIAL_R2T_EN
|
|
ISCSI_PARAM_MAX_R2T
|
|
ISCSI_PARAM_IMM_DATA_EN
|
|
ISCSI_PARAM_FIRST_BURST
|
|
ISCSI_PARAM_MAX_BURST
|
|
ISCSI_PARAM_PDU_INORDER_EN
|
|
ISCSI_PARAM_DATASEQ_INORDER_EN
|
|
ISCSI_PARAM_ERL
|
|
ISCSI_PARAM_IFMARKER_EN
|
|
ISCSI_PARAM_OFMARKER_EN
|
|
ISCSI_PARAM_DEFAULTTIME2WAIT
|
|
ISCSI_PARAM_DEFAULTTIME2RETAIN
|
|
ISCSI_PARAM_OFMARKINT
|
|
ISCSI_PARAM_IFMARKINT
|
|
ISCSI_PARAM_MAXCONNECTIONS
|
|
/* iSCSI Extensions for RDMA (RFC5046) */
|
|
ISCSI_PARAM_RDMA_EXTENSIONS
|
|
ISCSI_PARAM_TARGET_RDSL
|
|
ISCSI_PARAM_INITIATOR_RDSL
|
|
ISCSI_PARAM_MAX_OUTST_PDU
|
|
/* "local" parmas, never sent to the initiator */
|
|
ISCSI_PARAM_FIRST_LOCAL
|
|
ISCSI_PARAM_MAX_XMIT_DLENGTH = ISCSI_PARAM_FIRST_LOCAL
|
|
ISCSI_PARAM_MAX_QUEUE_CMD
|
|
/* must always be last */
|
|
ISCSI_PARAM_MAX
|
|
)
|
|
|
|
type ISCSISessionParam struct {
|
|
idx uint
|
|
State int
|
|
Value uint
|
|
}
|
|
type ISCSISessionParamList []ISCSISessionParam
|
|
|
|
func (list ISCSISessionParamList) Len() int {
|
|
return len(list)
|
|
}
|
|
|
|
func (list ISCSISessionParamList) Less(i, j int) bool {
|
|
if list[i].idx <= list[j].idx {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (list ISCSISessionParamList) Swap(i, j int) {
|
|
list[i], list[j] = list[j], list[i]
|
|
}
|
|
|
|
/*
|
|
* The defaults here are according to the spec and must not be changed,
|
|
* otherwise the initiator may make the wrong assumption. If you want
|
|
* to change a value, edit the value in iscsi_target_create.
|
|
*
|
|
* The param MaxXmitDataSegmentLength doesn't really exist. It's a way
|
|
* to remember the RDSL of the initiator, which defaults to 8k if he has
|
|
* not told us otherwise.
|
|
*/
|
|
type KeyConvFunc func(value string) (uint, bool)
|
|
type KeyInConvFunc func(value uint) string
|
|
|
|
type iscsiSessionKeys struct {
|
|
idx uint
|
|
constValue bool
|
|
def uint
|
|
min uint
|
|
max uint
|
|
conv KeyConvFunc
|
|
inConv KeyInConvFunc
|
|
}
|
|
|
|
func digestKeyConv(value string) (uint, bool) {
|
|
var crc uint
|
|
valueArray := strings.Split(value, ",")
|
|
if len(valueArray) == 0 {
|
|
return crc, false
|
|
}
|
|
for _, tmpV := range valueArray {
|
|
if strings.EqualFold(tmpV, "crc32c") {
|
|
crc |= DIGEST_CRC32C
|
|
} else if strings.EqualFold(tmpV, "none") {
|
|
crc |= DIGEST_NONE
|
|
} else {
|
|
return crc, false
|
|
}
|
|
}
|
|
|
|
return crc, true
|
|
}
|
|
|
|
func digestKeyInConv(value uint) string {
|
|
str := ""
|
|
switch value {
|
|
case DIGEST_NONE:
|
|
str = "None"
|
|
case DIGEST_CRC32C:
|
|
str = "CRC32C"
|
|
case DIGEST_ALL:
|
|
str = "None,CRC32C"
|
|
}
|
|
return str
|
|
}
|
|
|
|
func numberKeyConv(value string) (uint, bool) {
|
|
v, err := strconv.Atoi(value)
|
|
if err == nil {
|
|
return uint(v), true
|
|
}
|
|
return uint(v), false
|
|
}
|
|
|
|
func numberKeyInConv(value uint) string {
|
|
s := strconv.Itoa(int(value))
|
|
return s
|
|
}
|
|
|
|
func boolKeyConv(value string) (uint, bool) {
|
|
if strings.EqualFold(value, "yes") {
|
|
return 1, true
|
|
} else if strings.EqualFold(value, "no") {
|
|
return 0, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func boolKeyInConv(value uint) string {
|
|
if value == 0 {
|
|
return "No"
|
|
}
|
|
return "Yes"
|
|
}
|
|
|
|
var sessionKeys map[string]*iscsiSessionKeys = map[string]*iscsiSessionKeys{
|
|
// ISCSI_PARAM_MAX_RECV_DLENGTH
|
|
"MaxRecvDataSegmentLength": {ISCSI_PARAM_MAX_RECV_DLENGTH, true, 65536, 512, 16777215, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_HDRDGST_EN
|
|
"HeaderDigest": {ISCSI_PARAM_HDRDGST_EN, false, DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, digestKeyConv, digestKeyInConv},
|
|
// ISCSI_PARAM_DATADGST_EN
|
|
"DataDigest": {ISCSI_PARAM_DATADGST_EN, false, DIGEST_NONE, DIGEST_NONE, DIGEST_ALL, digestKeyConv, digestKeyInConv},
|
|
// ISCSI_PARAM_INITIAL_R2T_EN
|
|
"InitialR2T": {ISCSI_PARAM_INITIAL_R2T_EN, true, 1, 0, 1, boolKeyConv, boolKeyInConv},
|
|
// ISCSI_PARAM_MAX_R2T
|
|
"MaxOutstandingR2T": {ISCSI_PARAM_MAX_R2T, true, 1, 1, 65535, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_IMM_DATA_EN
|
|
"ImmediateData": {ISCSI_PARAM_IMM_DATA_EN, true, 1, 0, 1, boolKeyConv, boolKeyInConv},
|
|
// ISCSI_PARAM_FIRST_BURST
|
|
"FirstBurstLength": {ISCSI_PARAM_FIRST_BURST, true, 65536, 512, 16777215, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_MAX_BURST
|
|
"MaxBurstLength": {ISCSI_PARAM_MAX_BURST, true, 262144, 512, 16777215, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_PDU_INORDER_EN
|
|
"DataPDUInOrder": {ISCSI_PARAM_PDU_INORDER_EN, true, 1, 0, 1, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_DATASEQ_INORDER_EN
|
|
"DataSequenceInOrder": {ISCSI_PARAM_DATASEQ_INORDER_EN, true, 1, 0, 1, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_ERL
|
|
"ErrorRecoveryLevel": {ISCSI_PARAM_ERL, true, 0, 0, 2, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_IFMARKER_EN
|
|
"IFMarker": {ISCSI_PARAM_IFMARKER_EN, true, 0, 0, 1, boolKeyConv, boolKeyInConv},
|
|
// ISCSI_PARAM_OFMARKER_EN
|
|
"OFMarker": {ISCSI_PARAM_OFMARKER_EN, true, 0, 0, 1, boolKeyConv, boolKeyInConv},
|
|
// ISCSI_PARAM_DEFAULTTIME2WAIT
|
|
"DefaultTime2Wait": {ISCSI_PARAM_DEFAULTTIME2WAIT, true, 2, 0, 3600, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_DEFAULTTIME2RETAIN
|
|
"DefaultTime2Retain": {ISCSI_PARAM_DEFAULTTIME2RETAIN, false, 20, 0, 3600, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_OFMARKINT
|
|
"OFMarkInt": {ISCSI_PARAM_OFMARKINT, true, 2048, 1, 65535, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_IFMARKINT
|
|
"IFMarkInt": {ISCSI_PARAM_IFMARKINT, true, 2048, 1, 65535, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_MAXCONNECTIONS
|
|
"MaxConnections": {ISCSI_PARAM_MAXCONNECTIONS, true, 1, 1, 65535, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_RDMA_EXTENSIONS
|
|
"RDMAExtensions": {ISCSI_PARAM_RDMA_EXTENSIONS, true, 0, 0, 1, boolKeyConv, boolKeyInConv},
|
|
// ISCSI_PARAM_TARGET_RDSL
|
|
"TargetRecvDataSegmentLength": {ISCSI_PARAM_TARGET_RDSL, true, 8192, 512, 16777215, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_INITIATOR_RDSL
|
|
"InitiatorRecvDataSegmentLength": {ISCSI_PARAM_INITIATOR_RDSL, true, 8192, 512, 16777215, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_MAX_OUTST_PDU
|
|
"MaxOutstandingUnexpectedPDUs": {ISCSI_PARAM_MAX_OUTST_PDU, true, 0, 2, 4294967295, numberKeyConv, numberKeyInConv},
|
|
// "local" parmas, never sent to the initiator
|
|
// ISCSI_PARAM_MAX_XMIT_DLENGTH
|
|
"MaxXmitDataSegmentLength": {ISCSI_PARAM_MAX_XMIT_DLENGTH, true, 8192, 512, 16777215, numberKeyConv, numberKeyInConv},
|
|
// ISCSI_PARAM_MAX_QUEUE_CMD
|
|
"MaxQueueCmd": {ISCSI_PARAM_MAX_QUEUE_CMD, true, MAX_QUEUE_CMD_DEF, MAX_QUEUE_CMD_MIN, MAX_QUEUE_CMD_MAX, numberKeyConv, numberKeyInConv},
|
|
}
|
|
|
|
// Session is an iSCSI session.
|
|
type ISCSISession struct {
|
|
Refcount int
|
|
Initiator string
|
|
InitiatorAlias string
|
|
Target *ISCSITarget
|
|
ISID uint64
|
|
TSIH uint16
|
|
TPGT uint16
|
|
SessionType int
|
|
ITNexus *api.ITNexus
|
|
|
|
ExpCmdSN uint32
|
|
MaxCmdSN uint32
|
|
// currently, this is only one connection per session
|
|
Connections map[uint16]*iscsiConnection
|
|
ConnectionsRWMutex sync.RWMutex
|
|
Commands []*ISCSICommand
|
|
PendingTasks taskQueue
|
|
PendingTasksMutex sync.RWMutex
|
|
MaxQueueCommand uint32
|
|
SessionParam ISCSISessionParamList
|
|
Info string
|
|
Rdma int
|
|
}
|
|
|
|
type taskQueue []*iscsiTask
|
|
|
|
func (tq taskQueue) Len() int { return len(tq) }
|
|
|
|
func (tq taskQueue) Less(i, j int) bool {
|
|
// We want Pop to give us the highest, not lowest, priority so we use greater than here.
|
|
return tq[i].cmd.CmdSN > tq[j].cmd.CmdSN
|
|
}
|
|
|
|
func (tq taskQueue) Swap(i, j int) {
|
|
tq[i], tq[j] = tq[j], tq[i]
|
|
}
|
|
|
|
func (tq *taskQueue) Push(x *iscsiTask) {
|
|
item := x
|
|
*tq = append(*tq, item)
|
|
}
|
|
|
|
func (tq *taskQueue) Pop() *iscsiTask {
|
|
old := *tq
|
|
n := len(old)
|
|
item := old[n-1]
|
|
*tq = old[0 : n-1]
|
|
return item
|
|
}
|
|
|
|
func (tq taskQueue) GetByTag(tag uint32) *iscsiTask {
|
|
for _, t := range tq {
|
|
if t.tag == tag {
|
|
return t
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tq *taskQueue) RemoveByTag(tag uint32) *iscsiTask {
|
|
old := *tq
|
|
for i, t := range old {
|
|
if t.tag == tag {
|
|
*tq = append(old[:i], old[i+1:]...)
|
|
return t
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetDriver) LookupISCSISession(tgtName string, iniName string, isid uint64, tsih uint16, tpgt uint16) *ISCSISession {
|
|
var (
|
|
tgt *ISCSITarget
|
|
sess *ISCSISession
|
|
ok bool
|
|
)
|
|
tgt, ok = s.iSCSITargets[tgtName]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
tgt.SessionsRWMutex.RLock()
|
|
defer tgt.SessionsRWMutex.RUnlock()
|
|
sess, ok = tgt.Sessions[tsih]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if (sess.ISID == isid) && (sess.TPGT == tpgt) {
|
|
return sess
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ISCSITargetDriver) UnBindISCSISession(sess *ISCSISession) {
|
|
target := sess.Target
|
|
if target == nil {
|
|
// Discovery sessions have no target; just release the TSIH.
|
|
s.ReleaseTSIH(sess.TSIH)
|
|
return
|
|
}
|
|
target.SessionsRWMutex.Lock()
|
|
defer target.SessionsRWMutex.Unlock()
|
|
delete(target.Sessions, sess.TSIH)
|
|
if sess.ITNexus != nil {
|
|
scsi.RemoveITNexus(target.SCSITarget, sess.ITNexus)
|
|
}
|
|
s.ReleaseTSIH(sess.TSIH)
|
|
log.Infof("session %x unbound from target %s", sess.TSIH, target.SCSITarget.Name)
|
|
}
|
|
|
|
// removeConnectionFromSession removes a connection from its session.
|
|
// If the session has no remaining connections, the session is unbound.
|
|
// This is safe to call concurrently; cleanup runs at most once per connection.
|
|
func (s *ISCSITargetDriver) removeConnectionFromSession(conn *iscsiConnection) {
|
|
conn.cleanupOnce.Do(func() {
|
|
sess := conn.session
|
|
if sess == nil {
|
|
return
|
|
}
|
|
|
|
sess.ConnectionsRWMutex.Lock()
|
|
delete(sess.Connections, conn.cid)
|
|
remaining := len(sess.Connections)
|
|
sess.ConnectionsRWMutex.Unlock()
|
|
|
|
if remaining == 0 {
|
|
s.UnBindISCSISession(sess)
|
|
}
|
|
conn.session = nil
|
|
})
|
|
}
|
|
|
|
func (s *ISCSITargetDriver) BindISCSISession(conn *iscsiConnection) error {
|
|
var (
|
|
target *ISCSITarget
|
|
existSess *ISCSISession
|
|
existConn *iscsiConnection
|
|
newSess *ISCSISession
|
|
tpgt uint16
|
|
err error
|
|
)
|
|
|
|
//Find TPGT and Target ID
|
|
if conn.loginParam.sessionType == SESSION_DISCOVERY {
|
|
conn.tid = 0xffff
|
|
} else {
|
|
for _, t := range s.iSCSITargets {
|
|
if t.SCSITarget.Name == conn.loginParam.target {
|
|
target = t
|
|
break
|
|
}
|
|
}
|
|
if target == nil {
|
|
return fmt.Errorf("No target found with name(%s)", conn.loginParam.target)
|
|
}
|
|
|
|
tpgt, err = target.FindTPG(conn.conn.LocalAddr().String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conn.loginParam.tpgt = tpgt
|
|
conn.tid = target.TID
|
|
}
|
|
|
|
existSess = s.LookupISCSISession(conn.loginParam.target, conn.loginParam.initiator,
|
|
conn.loginParam.isid, conn.loginParam.tsih, conn.loginParam.tpgt)
|
|
if existSess != nil {
|
|
existConn = existSess.LookupConnection(conn.cid)
|
|
}
|
|
|
|
if conn.loginParam.sessionType == SESSION_DISCOVERY &&
|
|
conn.loginParam.tsih != ISCSI_UNSPEC_TSIH &&
|
|
existSess != nil {
|
|
return fmt.Errorf("initiator err, invalid request")
|
|
}
|
|
|
|
if existSess != nil && conn.loginParam.tsih != 0 &&
|
|
existSess.TSIH != conn.loginParam.tsih {
|
|
return fmt.Errorf("initiator err, no session")
|
|
}
|
|
|
|
if existSess == nil {
|
|
newSess, err = s.NewISCSISession(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if newSess.SessionType == SESSION_NORMAL {
|
|
log.Infof("Login request received from initiator: %v, Session type: %s, Target name:%v, ISID: 0x%x",
|
|
conn.loginParam.initiator, "Normal", conn.loginParam.target, conn.loginParam.isid)
|
|
//register normal session
|
|
itnexus := &api.ITNexus{ID: uuid.New(), Tag: GeniSCSIITNexusID(newSess)}
|
|
scsi.AddITNexus(newSess.Target.SCSITarget, itnexus)
|
|
newSess.ITNexus = itnexus
|
|
conn.session = newSess
|
|
|
|
newSess.Target.SessionsRWMutex.Lock()
|
|
newSess.Target.Sessions[newSess.TSIH] = newSess
|
|
newSess.Target.SessionsRWMutex.Unlock()
|
|
} else {
|
|
log.Infof("Discovery request received from initiator: %v, Session type: %s, ISID: 0x%x",
|
|
conn.loginParam.initiator, "Discovery", conn.loginParam.isid)
|
|
conn.session = newSess
|
|
}
|
|
} else {
|
|
if conn.loginParam.tsih == ISCSI_UNSPEC_TSIH {
|
|
log.Infof("Session Reinstatement initiator name:%v,target name:%v,ISID:0x%x",
|
|
conn.loginParam.initiator, conn.loginParam.target, conn.loginParam.isid)
|
|
if existConn != nil {
|
|
newSess, err = s.ReInstatement(existConn.session, conn)
|
|
} else {
|
|
// Old connection already closed; unbind the stale session and create new
|
|
s.UnBindISCSISession(existSess)
|
|
newSess, err = s.NewISCSISession(conn)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
itnexus := &api.ITNexus{ID: uuid.New(), Tag: GeniSCSIITNexusID(newSess)}
|
|
scsi.AddITNexus(newSess.Target.SCSITarget, itnexus)
|
|
newSess.ITNexus = itnexus
|
|
conn.session = newSess
|
|
|
|
newSess.Target.SessionsRWMutex.Lock()
|
|
newSess.Target.Sessions[newSess.TSIH] = newSess
|
|
newSess.Target.SessionsRWMutex.Unlock()
|
|
} else {
|
|
if existConn != nil {
|
|
log.Infof("Connection Reinstatement initiator name:%v,target name:%v,ISID:0x%x",
|
|
conn.loginParam.initiator, conn.loginParam.target, conn.loginParam.isid)
|
|
existConn.ReInstatement(conn)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// New creates a new session.
|
|
func (s *ISCSITargetDriver) NewISCSISession(conn *iscsiConnection) (*ISCSISession, error) {
|
|
var (
|
|
target *ISCSITarget
|
|
tsih uint16
|
|
)
|
|
|
|
for _, t := range s.iSCSITargets {
|
|
if t.TID == conn.tid {
|
|
target = t
|
|
break
|
|
}
|
|
}
|
|
if target == nil && conn.tid != 0xffff {
|
|
return nil, fmt.Errorf("No target found with tid(%d)", conn.tid)
|
|
}
|
|
|
|
tsih = s.AllocTSIH()
|
|
if tsih == ISCSI_UNSPEC_TSIH {
|
|
return nil, fmt.Errorf("TSIH Pool exhausted tid(%d)", conn.tid)
|
|
}
|
|
|
|
sess := &ISCSISession{
|
|
TSIH: tsih,
|
|
ISID: conn.loginParam.isid,
|
|
TPGT: conn.loginParam.tpgt,
|
|
Initiator: conn.loginParam.initiator,
|
|
InitiatorAlias: conn.loginParam.initiatorAlias,
|
|
SessionType: conn.loginParam.sessionType,
|
|
Target: target,
|
|
Connections: map[uint16]*iscsiConnection{conn.cid: conn},
|
|
SessionParam: conn.loginParam.sessionParam,
|
|
MaxQueueCommand: uint32(conn.loginParam.sessionParam[ISCSI_PARAM_MAX_QUEUE_CMD].Value),
|
|
Rdma: 0,
|
|
ExpCmdSN: conn.expCmdSN,
|
|
}
|
|
return sess, nil
|
|
}
|
|
|
|
func (sess *ISCSISession) LookupConnection(cid uint16) *iscsiConnection {
|
|
sess.ConnectionsRWMutex.RLock()
|
|
defer sess.ConnectionsRWMutex.RUnlock()
|
|
conn := sess.Connections[cid]
|
|
return conn
|
|
}
|
|
|
|
func (s *ISCSITargetDriver) ReInstatement(existSess *ISCSISession, conn *iscsiConnection) (*ISCSISession, error) {
|
|
newSess, err := s.NewISCSISession(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newSess.ExpCmdSN = existSess.ExpCmdSN
|
|
newSess.MaxCmdSN = existSess.MaxCmdSN + 1
|
|
s.UnBindISCSISession(existSess)
|
|
for _, tmpConn := range existSess.Connections {
|
|
tmpConn.close()
|
|
}
|
|
existSess.Connections = map[uint16]*iscsiConnection{}
|
|
return newSess, nil
|
|
}
|
|
|
|
/*
|
|
* iSCSI I_T nexus identifer = (iSCSI Initiator Name + 'i' + ISID, iSCSI Target Name + 't' + Portal Group Tag)
|
|
*/
|
|
func GeniSCSIITNexusID(sess *ISCSISession) string {
|
|
strID := fmt.Sprintf("%si0x%12x,%st%d",
|
|
sess.Initiator, sess.ISID,
|
|
sess.Target.SCSITarget.Name,
|
|
sess.TPGT)
|
|
return strID
|
|
}
|