From 7c6e8ee3e0e91c91f8bba60d7f0093d26b7c99c4 Mon Sep 17 00:00:00 2001 From: Masaki Kimura Date: Mon, 17 Jun 2019 19:47:18 +0000 Subject: [PATCH 1/2] Use csi-lib-iscsi --- pkg/iscsi/iscsi.go | 129 ++++++++++++-- pkg/iscsi/iscsi_util.go | 384 ++-------------------------------------- 2 files changed, 125 insertions(+), 388 deletions(-) diff --git a/pkg/iscsi/iscsi.go b/pkg/iscsi/iscsi.go index 96647786..6174df82 100644 --- a/pkg/iscsi/iscsi.go +++ b/pkg/iscsi/iscsi.go @@ -19,9 +19,11 @@ package iscsi import ( "encoding/json" "fmt" + "strconv" "strings" "github.com/container-storage-interface/spec/lib/go/csi" + iscsi_lib "github.com/kubernetes-csi/csi-lib-iscsi/iscsi" "k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/volume/util" ) @@ -38,6 +40,14 @@ func getISCSIInfo(req *csi.NodePublishVolumeRequest) (*iscsiDisk, error) { portalList := req.GetVolumeContext()["portals"] secretParams := req.GetVolumeContext()["secret"] secret := parseSecret(secretParams) + sessionSecret, err := parseSessionSecret(secret) + if err != nil { + return nil, err + } + discoverySecret, err := parseDiscoverySecret(secret) + if err != nil { + return nil, err + } portal := portalMounter(tp) var bkportal []string @@ -64,16 +74,45 @@ func getISCSIInfo(req *csi.NodePublishVolumeRequest) (*iscsiDisk, error) { chapSession = true } + var lunVal int32 + if lun != "" { + l, err := strconv.Atoi(lun) + if err != nil { + return nil, err + } + lunVal = int32(l) + } + return &iscsiDisk{ - VolName: volName, - Portals: bkportal, - Iqn: iqn, - lun: lun, - Iface: iface, - chapDiscovery: chapDiscovery, - chapSession: chapSession, - secret: secret, - InitiatorName: initiatorName}, nil + VolName: volName, + Portals: bkportal, + Iqn: iqn, + lun: lunVal, + Iface: iface, + chapDiscovery: chapDiscovery, + chapSession: chapSession, + secret: secret, + sessionSecret: sessionSecret, + discoverySecret: discoverySecret, + InitiatorName: initiatorName}, nil +} + +func buildISCSIConnector(iscsiInfo *iscsiDisk) *iscsi_lib.Connector { + c := iscsi_lib.Connector{ + VolumeName: iscsiInfo.VolName, + TargetIqn: iscsiInfo.Iqn, + TargetPortals: iscsiInfo.Portals, + Multipath: len(iscsiInfo.Portals) > 1, + } + + if iscsiInfo.sessionSecret != (iscsi_lib.Secrets{}) { + c.SessionSecrets = iscsiInfo.sessionSecret + if iscsiInfo.discoverySecret != (iscsi_lib.Secrets{}) { + c.DiscoverySecrets = iscsiInfo.discoverySecret + } + } + + return &c } func getISCSIDiskMounter(iscsiInfo *iscsiDisk, req *csi.NodePublishVolumeRequest) *iscsiDiskMounter { @@ -90,6 +129,7 @@ func getISCSIDiskMounter(iscsiInfo *iscsiDisk, req *csi.NodePublishVolumeRequest exec: mount.NewOsExec(), targetPath: req.GetTargetPath(), deviceUtil: util.NewDeviceHandler(util.NewIOHandler()), + connector: buildISCSIConnector(iscsiInfo), } } @@ -118,16 +158,68 @@ func parseSecret(secretParams string) map[string]string { return secret } +func parseSessionSecret(secretParams map[string]string) (iscsi_lib.Secrets, error) { + var ok bool + secret := iscsi_lib.Secrets{} + + if len(secretParams) == 0 { + return secret, nil + } + + if secret.UserName, ok = secretParams["node.session.auth.username"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.session.auth.username not found in secret") + } + if secret.Password, ok = secretParams["node.session.auth.password"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.session.auth.password not found in secret") + } + if secret.UserNameIn, ok = secretParams["node.session.auth.username_in"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.session.auth.username_in not found in secret") + } + if secret.PasswordIn, ok = secretParams["node.session.auth.password_in"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.session.auth.password_in not found in secret") + } + + secret.SecretsType = "chap" + return secret, nil +} + +func parseDiscoverySecret(secretParams map[string]string) (iscsi_lib.Secrets, error) { + var ok bool + secret := iscsi_lib.Secrets{} + + if len(secretParams) == 0 { + return secret, nil + } + + if secret.UserName, ok = secretParams["node.sendtargets.auth.username"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.sendtargets.auth.username not found in secret") + } + if secret.Password, ok = secretParams["node.sendtargets.auth.password"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.sendtargets.auth.password not found in secret") + } + if secret.UserNameIn, ok = secretParams["node.sendtargets.auth.username_in"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.sendtargets.auth.username_in not found in secret") + } + if secret.PasswordIn, ok = secretParams["node.sendtargets.auth.password_in"]; !ok { + return iscsi_lib.Secrets{}, fmt.Errorf("node.sendtargets.auth.password_in not found in secret") + } + + secret.SecretsType = "chap" + return secret, nil +} + type iscsiDisk struct { - Portals []string - Iqn string - lun string - Iface string - chapDiscovery bool - chapSession bool - secret map[string]string - InitiatorName string - VolName string + Portals []string + Iqn string + lun int32 + Iface string + chapDiscovery bool + chapSession bool + secret map[string]string + sessionSecret iscsi_lib.Secrets + discoverySecret iscsi_lib.Secrets + InitiatorName string + VolName string } type iscsiDiskMounter struct { @@ -139,6 +231,7 @@ type iscsiDiskMounter struct { exec mount.Exec deviceUtil util.DeviceUtil targetPath string + connector *iscsi_lib.Connector } type iscsiDiskUnmounter struct { diff --git a/pkg/iscsi/iscsi_util.go b/pkg/iscsi/iscsi_util.go index 54b8bfab..70b95e0e 100644 --- a/pkg/iscsi/iscsi_util.go +++ b/pkg/iscsi/iscsi_util.go @@ -17,255 +17,26 @@ limitations under the License. package iscsi import ( - "encoding/json" "fmt" "os" "path" - "path/filepath" - "regexp" - "strings" - "time" "github.com/golang/glog" + iscsi_lib "github.com/kubernetes-csi/csi-lib-iscsi/iscsi" "k8s.io/kubernetes/pkg/util/mount" ) -var ( - chapSt = []string{ - "discovery.sendtargets.auth.username", - "discovery.sendtargets.auth.password", - "discovery.sendtargets.auth.username_in", - "discovery.sendtargets.auth.password_in"} - chapSess = []string{ - "node.session.auth.username", - "node.session.auth.password", - "node.session.auth.username_in", - "node.session.auth.password_in"} - ifaceTransportNameRe = regexp.MustCompile(`iface.transport_name = (.*)\n`) -) - -func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error { - if !b.chapDiscovery { - return nil - } - out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP") - if err != nil { - return fmt.Errorf("iscsi: failed to update discoverydb with CHAP, output: %v", string(out)) - } - - for _, k := range chapSt { - v := b.secret[k] - if len(v) > 0 { - out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", k, "-v", v) - if err != nil { - return fmt.Errorf("iscsi: failed to update discoverydb key %q with value %q error: %v", k, v, string(out)) - } - } - } - return nil -} - -func updateISCSINode(b iscsiDiskMounter, tp string) error { - if !b.chapSession { - return nil - } - - out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", "node.session.auth.authmethod", "-v", "CHAP") - if err != nil { - return fmt.Errorf("iscsi: failed to update node with CHAP, output: %v", string(out)) - } - - for _, k := range chapSess { - v := b.secret[k] - if len(v) > 0 { - out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", k, "-v", v) - if err != nil { - return fmt.Errorf("iscsi: failed to update node session key %q with value %q error: %v", k, v, string(out)) - } - } - } - return nil -} - -// stat a path, if not exists, retry maxRetries times -// when iscsi transports other than default are used, use glob instead as pci id of device is unknown -type StatFunc func(string) (os.FileInfo, error) -type GlobFunc func(string) ([]string, error) - -func waitForPathToExist(devicePath *string, maxRetries int, deviceTransport string) bool { - // This makes unit testing a lot easier - return waitForPathToExistInternal(devicePath, maxRetries, deviceTransport, os.Stat, filepath.Glob) -} - -func waitForPathToExistInternal(devicePath *string, maxRetries int, deviceTransport string, osStat StatFunc, filepathGlob GlobFunc) bool { - if devicePath == nil { - return false - } - - for i := 0; i < maxRetries; i++ { - var err error - if deviceTransport == "tcp" { - _, err = osStat(*devicePath) - } else { - fpath, _ := filepathGlob(*devicePath) - if fpath == nil { - err = os.ErrNotExist - } else { - // There might be a case that fpath contains multiple device paths if - // multiple PCI devices connect to same iscsi target. We handle this - // case at subsequent logic. Pick up only first path here. - *devicePath = fpath[0] - } - } - if err == nil { - return true - } - if !os.IsNotExist(err) { - return false - } - if i == maxRetries-1 { - break - } - time.Sleep(time.Second) - } - return false -} - type ISCSIUtil struct{} -func (util *ISCSIUtil) persistISCSI(conf iscsiDisk, mnt string) error { - file := path.Join(mnt, conf.VolName+".json") - fp, err := os.Create(file) - if err != nil { - return fmt.Errorf("iscsi: create %s err %s", file, err) - } - defer fp.Close() - encoder := json.NewEncoder(fp) - if err = encoder.Encode(conf); err != nil { - return fmt.Errorf("iscsi: encode err: %v", err) - } - return nil -} - -func (util *ISCSIUtil) loadISCSI(conf *iscsiDisk, mnt string) error { - // NOTE: The iscsi config json is not deleted after logging out from target portals. - file := path.Join(mnt, conf.VolName+".json") - fp, err := os.Open(file) - if err != nil { - return fmt.Errorf("iscsi: open %s err %s", file, err) - } - defer fp.Close() - decoder := json.NewDecoder(fp) - if err = decoder.Decode(conf); err != nil { - return fmt.Errorf("iscsi: decode err: %v", err) - } - return nil -} - func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) { - var devicePath string - var devicePaths []string - var iscsiTransport string - var lastErr error - - out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show") + devicePath, err := iscsi_lib.Connect(*b.connector) if err != nil { - glog.Errorf("iscsi: could not read iface %s error: %s", b.Iface, string(out)) return "", err } - - iscsiTransport = extractTransportname(string(out)) - - bkpPortal := b.Portals - - // create new iface and copy parameters from pre-configured iface to the created iface - if b.InitiatorName != "" { - // new iface name is : - newIface := bkpPortal[0] + ":" + b.VolName - err = cloneIface(b, newIface) - if err != nil { - glog.Errorf("iscsi: failed to clone iface: %s error: %v", b.Iface, err) - return "", err - } - // update iface name - b.Iface = newIface + if devicePath == "" { + return "", fmt.Errorf("connect reported success, but no path returned") } - for _, tp := range bkpPortal { - // Rescan sessions to discover newly mapped LUNs. Do not specify the interface when rescanning - // to avoid establishing additional sessions to the same target. - out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-R") - if err != nil { - glog.Errorf("iscsi: failed to rescan session with error: %s (%v)", string(out), err) - } - - if iscsiTransport == "" { - glog.Errorf("iscsi: could not find transport name in iface %s", b.Iface) - return "", fmt.Errorf("Could not parse iface file for %s", b.Iface) - } - if iscsiTransport == "tcp" { - devicePath = strings.Join([]string{"/dev/disk/by-path/ip", tp, "iscsi", b.Iqn, "lun", b.lun}, "-") - } else { - devicePath = strings.Join([]string{"/dev/disk/by-path/pci", "*", "ip", tp, "iscsi", b.Iqn, "lun", b.lun}, "-") - } - - if exist := waitForPathToExist(&devicePath, 1, iscsiTransport); exist { - glog.V(4).Infof("iscsi: devicepath (%s) exists", devicePath) - devicePaths = append(devicePaths, devicePath) - continue - } - // build discoverydb and discover iscsi target - b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "new") - // update discoverydb with CHAP secret - err = updateISCSIDiscoverydb(b, tp) - if err != nil { - lastErr = fmt.Errorf("iscsi: failed to update discoverydb to portal %s error: %v", tp, err) - continue - } - out, err = b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "--discover") - if err != nil { - // delete discoverydb record - b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "delete") - lastErr = fmt.Errorf("iscsi: failed to sendtargets to portal %s output: %s, err %v", tp, string(out), err) - continue - } - err = updateISCSINode(b, tp) - if err != nil { - // failure to update node db is rare. But deleting record will likely impact those who already start using it. - lastErr = fmt.Errorf("iscsi: failed to update iscsi node to portal %s error: %v", tp, err) - continue - } - // login to iscsi target - out, err = b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "--login") - if err != nil { - // delete the node record from database - b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-I", b.Iface, "-T", b.Iqn, "-o", "delete") - lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", string(out), err) - continue - } - if exist := waitForPathToExist(&devicePath, 10, iscsiTransport); !exist { - glog.Errorf("Could not attach disk: Timeout after 10s") - // update last error - lastErr = fmt.Errorf("Could not attach disk: Timeout after 10s") - continue - } else { - devicePaths = append(devicePaths, devicePath) - } - } - - if len(devicePaths) == 0 { - // delete cloned iface - b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "delete") - glog.Errorf("iscsi: failed to get any path for iscsi disk, last err seen:\n%v", lastErr) - return "", fmt.Errorf("failed to get any path for iscsi disk, last err seen:\n%v", lastErr) - } - if lastErr != nil { - glog.Errorf("iscsi: last error occurred during iscsi init:\n%v", lastErr) - } - - // Make sure we use a valid devicepath to find mpio device. - devicePath = devicePaths[0] - // Mount device mntPath := b.targetPath notMnt, err := b.mounter.IsLikelyNotMountPoint(mntPath) @@ -283,22 +54,12 @@ func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) { } // Persist iscsi disk config to json file for DetachDisk path - if err := util.persistISCSI(*(b.iscsiDisk), b.targetPath); err != nil { - glog.Errorf("iscsi: failed to save iscsi config with error: %v", err) - return "", err - } - - for _, path := range devicePaths { - // There shouldnt be any empty device paths. However adding this check - // for safer side to avoid the possibility of an empty entry. - if path == "" { - continue - } - // check if the dev is using mpio and if so mount it via the dm-XX device - if mappedDevicePath := b.deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" { - devicePath = mappedDevicePath - break - } + file := path.Join(mntPath, b.VolName+".json") + err = iscsi_lib.PersistConnector(b.connector, file) + if err != nil { + glog.Errorf("failed to persist connection info: %v", err) + glog.Errorf("disconnecting volume and failing the publish request because persistence files are required for reliable Unpublish") + return "", fmt.Errorf("unable to create persistence file for connection") } var options []string @@ -340,51 +101,15 @@ func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, targetPath string) error return nil } - var bkpPortal []string - var volName, iqn, iface, initiatorName string - found := true - // load iscsi disk config from json file - if err := util.loadISCSI(c.iscsiDisk, targetPath); err == nil { - bkpPortal, iqn, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Iface, c.iscsiDisk.VolName - initiatorName = c.iscsiDisk.InitiatorName - } else { + file := path.Join(targetPath, c.iscsiDisk.VolName+".json") + connector, err := iscsi_lib.GetConnectorFromFile(file) + if err != nil { glog.Errorf("iscsi detach disk: failed to get iscsi config from path %s Error: %v", targetPath, err) return err } - portals := removeDuplicate(bkpPortal) - if len(portals) == 0 { - return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations.") - } - for _, portal := range portals { - logoutArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "--logout"} - deleteArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "-o", "delete"} - if found { - logoutArgs = append(logoutArgs, []string{"-I", iface}...) - deleteArgs = append(deleteArgs, []string{"-I", iface}...) - } - glog.Infof("iscsi: log out target %s iqn %s iface %s", portal, iqn, iface) - out, err := c.exec.Run("iscsiadm", logoutArgs...) - if err != nil { - glog.Errorf("iscsi: failed to detach disk Error: %s", string(out)) - } - // Delete the node record - glog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn) - out, err = c.exec.Run("iscsiadm", deleteArgs...) - if err != nil { - glog.Errorf("iscsi: failed to delete node record Error: %s", string(out)) - } - } - // Delete the iface after all sessions have logged out - // If the iface is not created via iscsi plugin, skip to delete - if initiatorName != "" && found && iface == (portals[0]+":"+volName) { - deleteArgs := []string{"-m", "iface", "-I", iface, "-o", "delete"} - out, err := c.exec.Run("iscsiadm", deleteArgs...) - if err != nil { - glog.Errorf("iscsi: failed to delete iface Error: %s", string(out)) - } - } + iscsi_lib.Disconnect(connector.TargetIqn, connector.TargetPortals) if err := os.RemoveAll(targetPath); err != nil { glog.Errorf("iscsi: failed to remove mount path Error: %v", err) @@ -393,84 +118,3 @@ func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, targetPath string) error return nil } - -func extractTransportname(ifaceOutput string) (iscsiTransport string) { - rexOutput := ifaceTransportNameRe.FindStringSubmatch(ifaceOutput) - if rexOutput == nil { - return "" - } - iscsiTransport = rexOutput[1] - - // While iface.transport_name is a required parameter, handle it being unspecified anyways - if iscsiTransport == "" { - iscsiTransport = "tcp" - } - return iscsiTransport -} - -// Remove duplicates or string -func removeDuplicate(s []string) []string { - m := map[string]bool{} - for _, v := range s { - if v != "" && !m[v] { - s[len(m)] = v - m[v] = true - } - } - s = s[:len(m)] - return s -} - -func parseIscsiadmShow(output string) (map[string]string, error) { - params := make(map[string]string) - slice := strings.Split(output, "\n") - for _, line := range slice { - if !strings.HasPrefix(line, "iface.") || strings.Contains(line, "") { - continue - } - iface := strings.Fields(line) - if len(iface) != 3 || iface[1] != "=" { - return nil, fmt.Errorf("Error: invalid iface setting: %v", iface) - } - // iscsi_ifacename is immutable once the iface is created - if iface[0] == "iface.iscsi_ifacename" { - continue - } - params[iface[0]] = iface[2] - } - return params, nil -} - -func cloneIface(b iscsiDiskMounter, newIface string) error { - var lastErr error - // get pre-configured iface records - out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show") - if err != nil { - lastErr = fmt.Errorf("iscsi: failed to show iface records: %s (%v)", string(out), err) - return lastErr - } - // parse obtained records - params, err := parseIscsiadmShow(string(out)) - if err != nil { - lastErr = fmt.Errorf("iscsi: failed to parse iface records: %s (%v)", string(out), err) - return lastErr - } - // update initiatorname - params["iface.initiatorname"] = b.InitiatorName - // create new iface - out, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "new") - if err != nil { - lastErr = fmt.Errorf("iscsi: failed to create new iface: %s (%v)", string(out), err) - return lastErr - } - // update new iface records - for key, val := range params { - _, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "update", "-n", key, "-v", val) - if err != nil { - b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "delete") - lastErr = fmt.Errorf("iscsi: failed to update iface records: %s (%v). iface(%s) will be used", string(out), err, b.Iface) - break - } - } - return lastErr -} From d2ff8aee50a826f04c91d484f79bb8b9c40792d5 Mon Sep 17 00:00:00 2001 From: Masaki Kimura Date: Mon, 17 Jun 2019 22:21:15 +0000 Subject: [PATCH 2/2] Update vendor files to include csi-lib-iscsi --- Gopkg.lock | 8 +- .../kubernetes-csi/csi-lib-iscsi/LICENSE | 201 ++++++++++ .../csi-lib-iscsi/iscsi/iscsi.go | 359 ++++++++++++++++++ .../csi-lib-iscsi/iscsi/iscsiadm.go | 177 +++++++++ 4 files changed, 744 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/kubernetes-csi/csi-lib-iscsi/LICENSE create mode 100644 vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsi.go create mode 100644 vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsiadm.go diff --git a/Gopkg.lock b/Gopkg.lock index 4b8db1b0..6407e633 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -92,6 +92,12 @@ revision = "1624edc4454b8682399def8740d46db5e4362ba4" version = "v1.1.5" +[[projects]] + branch = "master" + name = "github.com/kubernetes-csi/csi-lib-iscsi" + packages = ["iscsi"] + revision = "c545557492f4c010327f964dd98a7ae72fb9bb30" + [[projects]] name = "github.com/kubernetes-csi/csi-lib-utils" packages = ["protosanitizer"] @@ -618,6 +624,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "6150221cd71f06d608b019cfb84207b6b370b414ceb86ddd7a8417771e267555" + inputs-digest = "90ccf1b778ece0e26e1b1d2660970d28f7d8ad7c00351f88a0236a97f8612829" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/kubernetes-csi/csi-lib-iscsi/LICENSE b/vendor/github.com/kubernetes-csi/csi-lib-iscsi/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/kubernetes-csi/csi-lib-iscsi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsi.go b/vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsi.go new file mode 100644 index 00000000..14b07412 --- /dev/null +++ b/vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsi.go @@ -0,0 +1,359 @@ +package iscsi + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + "syscall" + "time" +) + +var ( + debug *log.Logger + execCommand = exec.Command +) + +type statFunc func(string) (os.FileInfo, error) +type globFunc func(string) ([]string, error) + +type iscsiSession struct { + Protocol string + ID int32 + Portal string + IQN string + Name string +} + +//Connector provides a struct to hold all of the needed parameters to make our iscsi connection +type Connector struct { + VolumeName string `json:"volume_name"` + TargetIqn string `json:"target_iqn"` + TargetPortals []string `json:"target_portals"` + Port string `json:"port"` + Lun int32 `json:"lun"` + AuthType string `json:"auth_type"` + DiscoverySecrets Secrets `json:"discovery_secrets"` + SessionSecrets Secrets `json:"session_secrets"` + Interface string `json:"interface"` + Multipath bool `json:"multipath"` + RetryCount int32 `json:"retry_count"` + CheckInterval int32 `json:"check_interval"` +} + +func init() { + // by default we don't log anything, EnableDebugLogging() can turn on some tracing + debug = log.New(ioutil.Discard, "", 0) + +} + +// EnableDebugLogging provides a mechanism to turn on debug logging for this package +// output is written to the provided io.Writer +func EnableDebugLogging(writer io.Writer) { + debug = log.New(writer, "DEBUG: ", log.Ldate|log.Ltime|log.Lshortfile) +} + +// parseSession takes the raw stdout from the iscsiadm -m session command and encodes it into an iscsi session type +func parseSessions(lines string) []iscsiSession { + entries := strings.Split(strings.TrimSpace(string(lines)), "\n") + r := strings.NewReplacer("[", "", + "]", "") + + var sessions []iscsiSession + for _, entry := range entries { + e := strings.Fields(entry) + if len(e) < 4 { + continue + } + protocol := strings.Split(e[0], ":")[0] + id := r.Replace(e[1]) + id64, _ := strconv.ParseInt(id, 10, 32) + portal := strings.Split(e[2], ",")[0] + + s := iscsiSession{ + Protocol: protocol, + ID: int32(id64), + Portal: portal, + IQN: e[3], + Name: strings.Split(e[3], ":")[1], + } + sessions = append(sessions, s) + } + return sessions +} + +func sessionExists(tgtPortal, tgtIQN string) (bool, error) { + sessions, err := getCurrentSessions() + if err != nil { + return false, err + } + var existingSessions []iscsiSession + for _, s := range sessions { + if tgtIQN == s.IQN && tgtPortal == s.Portal { + existingSessions = append(existingSessions, s) + } + } + exists := false + if len(existingSessions) > 0 { + exists = true + } + return exists, nil +} + +func extractTransportName(output string) string { + res := regexp.MustCompile(`iface.transport_name = (.*)\n`).FindStringSubmatch(output) + if res == nil { + return "" + } + if res[1] == "" { + return "tcp" + } + return res[1] +} + +func getCurrentSessions() ([]iscsiSession, error) { + + out, err := GetSessions() + if err != nil { + exitErr, ok := err.(*exec.ExitError) + if ok && exitErr.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() == 21 { + return []iscsiSession{}, nil + } + return nil, err + } + session := parseSessions(out) + return session, err +} + +func waitForPathToExist(devicePath *string, maxRetries, intervalSeconds int, deviceTransport string) (bool, error) { + return waitForPathToExistImpl(devicePath, maxRetries, intervalSeconds, deviceTransport, os.Stat, filepath.Glob) +} + +func waitForPathToExistImpl(devicePath *string, maxRetries, intervalSeconds int, deviceTransport string, osStat statFunc, filepathGlob globFunc) (bool, error) { + if devicePath == nil { + return false, fmt.Errorf("Unable to check unspecified devicePath") + } + + var err error + for i := 0; i < maxRetries; i++ { + err = nil + if deviceTransport == "tcp" { + _, err = osStat(*devicePath) + if err != nil && !strings.Contains(err.Error(), "no such file or directory") { + debug.Printf("Error attempting to stat device: %s", err.Error()) + return false, err + } else if err != nil { + debug.Printf("Device not found for: %s", *devicePath) + } + + } else { + fpath, _ := filepathGlob(*devicePath) + if fpath == nil { + err = os.ErrNotExist + } else { + // There might be a case that fpath contains multiple device paths if + // multiple PCI devices connect to same iscsi target. We handle this + // case at subsequent logic. Pick up only first path here. + *devicePath = fpath[0] + } + } + if err == nil { + return true, nil + } + if i == maxRetries-1 { + break + } + time.Sleep(time.Second * time.Duration(intervalSeconds)) + } + return false, err +} + +func getMultipathDisk(path string) (string, error) { + // Follow link to destination directory + debug.Printf("Checking for multipath device for path: %s", path) + devicePath, err := os.Readlink(path) + if err != nil { + debug.Printf("Failed reading link for multipath disk: %s -- error: %s\n", path, err.Error()) + return "", err + } + sdevice := filepath.Base(devicePath) + // If destination directory is already identified as a multipath device, + // just return its path + if strings.HasPrefix(sdevice, "dm-") { + debug.Printf("Already found multipath device: %s", sdevice) + return path, nil + } + // Fallback to iterating through all the entries under /sys/block/dm-* and + // check to see if any have an entry under /sys/block/dm-*/slaves matching + // the device the symlink was pointing at + dmPaths, err := filepath.Glob("/sys/block/dm-*") + if err != nil { + debug.Printf("Glob error: %s", err) + return "", err + } + for _, dmPath := range dmPaths { + sdevices, err := filepath.Glob(filepath.Join(dmPath, "slaves", "*")) + if err != nil { + debug.Printf("Glob error: %s", err) + } + for _, spath := range sdevices { + s := filepath.Base(spath) + debug.Printf("Basepath: %s", s) + if sdevice == s { + // We've found a matching entry, return the path for the + // dm-* device it was found under + p := filepath.Join("/dev", filepath.Base(dmPath)) + debug.Printf("Found matching multipath device: %s under dm-* device path %s", sdevice, dmPath) + return p, nil + } + } + } + debug.Printf("Couldn't find dm-* path for path: %s, found non dm-* path: %s", path, devicePath) + return "", fmt.Errorf("Couldn't find dm-* path for path: %s, found non dm-* path: %s", path, devicePath) +} + +// Connect attempts to connect a volume to this node using the provided Connector info +func Connect(c Connector) (string, error) { + + if c.RetryCount == 0 { + c.RetryCount = 10 + } + if c.CheckInterval == 0 { + c.CheckInterval = 1 + } + + if c.RetryCount < 0 || c.CheckInterval < 0 { + return "", fmt.Errorf("Invalid RetryCount and CheckInterval combination, both must be positive integers. "+ + "RetryCount: %d, CheckInterval: %d", c.RetryCount, c.CheckInterval) + } + var devicePaths []string + iFace := "default" + if c.Interface != "" { + iFace = c.Interface + } + + // make sure our iface exists and extract the transport type + out, err := ShowInterface(iFace) + if err != nil { + return "", err + } + iscsiTransport := extractTransportName(out) + + for _, p := range c.TargetPortals { + debug.Printf("process portal: %s\n", p) + baseArgs := []string{"-m", "node", "-T", c.TargetIqn, "-p", p} + + // create our devicePath that we'll be looking for based on the transport being used + if c.Port != "" { + p = strings.Join([]string{p, c.Port}, ":") + } + devicePath := strings.Join([]string{"/dev/disk/by-path/ip", p, "iscsi", c.TargetIqn, "lun", fmt.Sprint(c.Lun)}, "-") + if iscsiTransport != "tcp" { + devicePath = strings.Join([]string{"/dev/disk/by-path/pci", "*", "ip", p, "iscsi", c.TargetIqn, "lun", fmt.Sprint(c.Lun)}, "-") + } + + exists, _ := sessionExists(p, c.TargetIqn) + if exists { + if exists, err := waitForPathToExist(&devicePath, 1, 1, iscsiTransport); exists { + debug.Printf("Appending device path: %s", devicePath) + devicePaths = append(devicePaths, devicePath) + continue + } else if err != nil { + return "", err + } + } + + // create db entry + args := append(baseArgs, []string{"-I", iFace, "-o", "new"}...) + debug.Printf("create the new record: %s\n", args) + // Make sure we don't log the secrets + err := CreateDBEntry(c.TargetIqn, p, iFace, c.DiscoverySecrets, c.SessionSecrets) + if err != nil { + debug.Printf("Error creating db entry: %s\n", err.Error()) + continue + } + // perform the login + err = Login(c.TargetIqn, p) + if err != nil { + return "", err + } + retries := int(c.RetryCount / c.CheckInterval) + if exists, err := waitForPathToExist(&devicePath, retries, int(c.CheckInterval), iscsiTransport); exists { + devicePaths = append(devicePaths, devicePath) + continue + } else if err != nil { + return "", err + } + if len(devicePaths) < 1 { + return "", fmt.Errorf("failed to find device path: %s", devicePath) + } + + } + + for i, path := range devicePaths { + if path != "" { + if mappedDevicePath, err := getMultipathDisk(path); mappedDevicePath != "" { + devicePaths[i] = mappedDevicePath + if err != nil { + return "", err + } + } + } + } + debug.Printf("After connect we're returning devicePaths: %s", devicePaths) + if len(devicePaths) > 0 { + return devicePaths[0], err + + } + return "", err +} + +//Disconnect performs a disconnect operation on a volume +func Disconnect(tgtIqn string, portals []string) error { + err := Logout(tgtIqn, portals) + if err != nil { + return err + } + err = DeleteDBEntry(tgtIqn) + return err +} + +// PersistConnector persists the provided Connector to the specified file (ie /var/lib/pfile/myConnector.json) +func PersistConnector(c *Connector, filePath string) error { + //file := path.Join("mnt", c.VolumeName+".json") + f, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("error creating iscsi persistence file %s: %s", filePath, err) + } + defer f.Close() + encoder := json.NewEncoder(f) + if err = encoder.Encode(c); err != nil { + return fmt.Errorf("error encoding connector: %v", err) + } + return nil + +} + +// GetConnectorFromFile attempts to create a Connector using the specified json file (ie /var/lib/pfile/myConnector.json) +func GetConnectorFromFile(filePath string) (*Connector, error) { + f, err := ioutil.ReadFile(filePath) + if err != nil { + return &Connector{}, err + + } + data := Connector{} + err = json.Unmarshal([]byte(f), &data) + if err != nil { + return &Connector{}, err + } + + return &data, nil + +} diff --git a/vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsiadm.go b/vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsiadm.go new file mode 100644 index 00000000..d9cbcf4c --- /dev/null +++ b/vendor/github.com/kubernetes-csi/csi-lib-iscsi/iscsi/iscsiadm.go @@ -0,0 +1,177 @@ +package iscsi + +import ( + "bytes" + "fmt" + "strings" +) + +// Secrets provides optional iscsi security credentials (CHAP settings) +type Secrets struct { + // SecretsType is the type of Secrets being utilized (currently we only impleemnent "chap" + SecretsType string `json:"secretsType,omitempty"` + // UserName is the configured iscsi user login + UserName string `json:"userName"` + // Password is the configured iscsi password + Password string `json:"password"` + // UserNameIn provides a specific input login for directional CHAP configurations + UserNameIn string `json:"userNameIn,omitempty"` + // PasswordIn provides a specific input password for directional CHAP configurations + PasswordIn string `json:"passwordIn,omitempty"` +} + +// CmdError is a custom error to provide details including the command, stderr output and exit code. +// iscsiadm in some cases requires all of this info to determine success or failure +type CmdError struct { + CMD string + StdErr string + ExitCode int +} + +func (e *CmdError) Error() string { + // we don't output the command in the error string to avoid leaking any security info + // the command is still available in the error structure if the caller wants it though + return fmt.Sprintf("iscsiadm returned an error: %s, exit-code: %d", e.StdErr, e.ExitCode) +} + +func iscsiCmd(args ...string) (string, error) { + cmd := execCommand("iscsiadm", args...) + var stdout bytes.Buffer + var iscsiadmError error + cmd.Stdout = &stdout + cmd.Stderr = &stdout + defer stdout.Reset() + + // we're using Start and Wait because we want to grab exit codes + err := cmd.Start() + if err != nil { + // This is usually a cmd not found so we'll set our own error here + formattedOutput := strings.Replace(string(stdout.Bytes()), "\n", "", -1) + iscsiadmError = fmt.Errorf("iscsiadm error: %s (%s)", formattedOutput, err.Error()) + + } else { + err = cmd.Wait() + if err != nil { + formattedOutput := strings.Replace(string(stdout.Bytes()), "\n", "", -1) + iscsiadmError = fmt.Errorf("iscsiadm error: %s (%s)", formattedOutput, err.Error()) + + } + } + + iscsiadmDebug(string(stdout.Bytes()), iscsiadmError) + return string(stdout.Bytes()), iscsiadmError +} + +func iscsiadmDebug(output string, cmdError error) { + debugOutput := strings.Replace(output, "\n", "\\n", -1) + debug.Printf("Output of iscsiadm command: {output: %s}", debugOutput) + if cmdError != nil { + debug.Printf("Error message returned from iscsiadm command: %s", cmdError.Error()) + } +} + +// ListInterfaces returns a list of all iscsi interfaces configured on the node +/// along with the raw output in Response.StdOut we add the convenience of +// returning a list of entries found +func ListInterfaces() ([]string, error) { + debug.Println("Begin ListInterface...") + out, err := iscsiCmd("-m", "iface", "-o", "show") + return strings.Split(out, "\n"), err +} + +// ShowInterface retrieves the details for the specified iscsi interface +// caller should inspect r.Err and use r.StdOut for interface details +func ShowInterface(iface string) (string, error) { + debug.Println("Begin ShowInterface...") + out, err := iscsiCmd("-m", "iface", "-o", "show", "-I", iface) + return out, err +} + +// CreateDBEntry sets up a node entry for the specified tgt in the nodes iscsi nodes db +func CreateDBEntry(tgtIQN, portal, iFace string, discoverySecrets, sessionSecrets Secrets) error { + debug.Println("Begin CreateDBEntry...") + baseArgs := []string{"-m", "node", "-T", tgtIQN, "-p", portal} + _, err := iscsiCmd(append(baseArgs, []string{"-I", iFace, "-o", "new"}...)...) + if err != nil { + return err + } + if discoverySecrets.SecretsType == "chap" { + debug.Printf("Setting CHAP Discovery...") + createCHAPEntries(baseArgs, discoverySecrets, true) + } + + if sessionSecrets.SecretsType == "chap" { + debug.Printf("Setting CHAP Session...") + createCHAPEntries(baseArgs, sessionSecrets, false) + + } + return err + +} + +func createCHAPEntries(baseArgs []string, secrets Secrets, discovery bool) error { + args := []string{} + debug.Printf("Begin createCHAPEntries (discovery=%t)...", discovery) + if discovery { + args = append(baseArgs, []string{"-o", "update", + "-n", "node.discovery.auth.authmethod", "-v", "CHAP", + "-n", "node.discovery.auth.username", "-v", secrets.UserName, + "-n", "node.discovery.auth.password", "-v", secrets.Password}...) + if secrets.UserNameIn != "" { + args = append(args, []string{"-n", "node.discovery.auth.username_in", "-v", secrets.UserNameIn}...) + } + if secrets.UserNameIn != "" { + args = append(args, []string{"-n", "node.discovery.auth.password_in", "-v", secrets.PasswordIn}...) + } + + } else { + + args = append(baseArgs, []string{"-o", "update", + "-n", "node.session.auth.authmethod", "-v", "CHAP", + "-n", "node.session.auth.username", "-v", secrets.UserName, + "-n", "node.session.auth.password", "-v", secrets.Password}...) + if secrets.UserNameIn != "" { + args = append(args, []string{"-n", "node.session.auth.username_in", "-v", secrets.UserNameIn}...) + } + if secrets.UserNameIn != "" { + args = append(args, []string{"-n", "node.session.auth.password_in", "-v", secrets.PasswordIn}...) + } + } + _, err := iscsiCmd(args...) + return err + +} + +// GetSessions retrieves a list of current iscsi sessions on the node +func GetSessions() (string, error) { + debug.Println("Begin GetSessions...") + out, err := iscsiCmd("-m", "session") + return out, err +} + +// Login performs an iscsi login for the specified target +func Login(tgtIQN, portal string) error { + _, err := iscsiCmd([]string{"-m", "node", "-T", tgtIQN, "-p", portal, "-l"}...) + return err +} + +// Logout logs out the specified target, if the target is not logged in it's not considered an error +func Logout(tgtIQN string, portals []string) error { + debug.Println("Begin Logout...") + baseArgs := []string{"-m", "node", "-T", tgtIQN} + for _, p := range portals { + debug.Printf("attempting logout for portal: %s", p) + args := append(baseArgs, []string{"-p", p, "-u"}...) + iscsiCmd(args...) + } + return nil +} + +// DeleteDBEntry deletes the iscsi db entry fo rthe specified target +func DeleteDBEntry(tgtIQN string) error { + debug.Println("Begin DeleteDBEntry...") + args := []string{"-m", "node", "-T", tgtIQN, "-o", "delete"} + iscsiCmd(args...) + return nil + +}