-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathiscsi.go
236 lines (208 loc) · 6.45 KB
/
iscsi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package iscsi
import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
// Connection is a data structure holding all of the info related to
// an iSCSI connection
type Connection struct {
Device string
IQN string
MPDevice string
Host string
Channel string
FileSystem string
ChapEnabled bool
Portal string
Port string
TgtIQN string
Lun string
ChapLogin string
ChapPasswd string
}
// Device is a dummy
type Device struct {
IQN string
Path string
MPDevice string
Portal string
Port string
IFace string
UseChap bool
ChapLogin string
ChapPasswd string
Lun int
}
var (
Trace *log.Logger
Info *log.Logger
Warning *log.Logger
Error *log.Logger
)
func init() {
Trace = log.New(os.Stdout,
"TRACE: ",
log.Ldate|log.Ltime|log.Lshortfile)
Info = log.New(os.Stdout,
"INFO: ",
log.Ldate|log.Ltime|log.Lshortfile)
Warning = log.New(os.Stdout,
"WARNING: ",
log.Ldate|log.Ltime|log.Lshortfile)
Error = log.New(os.Stdout,
"ERROR: ",
log.Ldate|log.Ltime|log.Lshortfile)
}
func isMultipath(dev string) bool {
args := []string{"-c", dev}
out, err := exec.Command("multipath", args...).CombinedOutput()
if err != nil {
Error.Println("multipath check failed, multipath not running?")
return false
}
Trace.Printf("response from multipath cmd: %s", out)
if strings.Contains(string(out), "is a valid multipath device") {
Trace.Printf("returning isMultipath == true\n")
return true
}
return false
}
func stat(f string) (string, error) {
out, err := exec.Command("sh", "-c", (fmt.Sprintf("stat %s", f))).CombinedOutput()
return string(out), err
}
func waitForPathToExist(d string, maxRetries int) bool {
for i := 0; i < maxRetries; i++ {
if _, err := stat(d); err == nil {
return true
}
time.Sleep(time.Second * time.Duration(2*1))
}
return false
}
// Attach performs an attachment of the specified resource
func Attach(d *Device) (Device, error) {
// Verify iscsi initiator tools are available
out, err := exec.Command("iscsiadm", []string{"-m", "iface", "-I", d.IFace, "-o", "show"}...).CombinedOutput()
if err != nil {
Error.Printf("iscsi unable to read from interface %s, error: %s", d.IFace, string(out))
return Device{}, err
}
// Make sure we're not already attached
path := "/dev/disk/by-path/ip-" + d.Portal + "-iscsi-" + d.IQN + "lun-" + strconv.Itoa(d.Lun)
if waitForPathToExist(path, 0) == true {
d.Path = path
return *d, nil
}
if d.UseChap == true {
err := LoginWithChap(d.IQN, d.Portal, d.ChapLogin, d.ChapPasswd, d.IFace)
if err != nil {
Error.Printf("error: %+v", err)
}
} else {
err := Login(d.IQN, d.Portal, d.IFace)
if err != nil {
Error.Printf("error: %+v", err)
}
}
if waitForPathToExist(path, 10) {
d.Path = path
}
return *d, nil
}
// GetInitiatorIqns queries the system to obtain the list of configured initiator names
// and returns them to the caller
func GetInitiatorIqns() ([]string, error) {
var iqns []string
out, err := exec.Command("cat", "/etc/iscsi/initiatorname.iscsi").CombinedOutput()
if err != nil {
Error.Printf("unable to gather initiator names: %v\n", err)
return nil, err
}
lines := strings.Split(string(out), "\n")
for _, l := range lines {
if strings.Contains(l, "InitiatorName=") {
iqns = append(iqns, strings.Split(l, "=")[1])
}
}
return iqns, nil
}
// LoginWithChap performs the necessary iscsiadm commands to log in to the
// specified iSCSI target. This wrapper will create a new node record, setup
// CHAP credentials and issue the login command
func LoginWithChap(tiqn, portal, username, password, iface string) error {
args := []string{"-m", "node", "-T", tiqn, "-p", portal}
createArgs := append(args, []string{"--interface", iface, "--op", "new"}...)
if _, err := exec.Command("iscsiadm", createArgs...).CombinedOutput(); err != nil {
return err
}
authMethodArgs := append(args, []string{"--op=update", "--name", "node.session.auth.authmethod", "--value=CHAP"}...)
if out, err := exec.Command("iscsiadm", authMethodArgs...).CombinedOutput(); err != nil {
Error.Printf("output of failed iscsiadm cmd: %+v\n", out)
return err
}
authUserArgs := append(args, []string{"--op=update", "--name", "node.session.auth.username", "--value=" + username}...)
if _, err := exec.Command("iscsiadm", authUserArgs...).CombinedOutput(); err != nil {
return err
}
authPasswordArgs := append(args, []string{"--op=update", "--name", "node.session.auth.password", "--value=" + password}...)
if _, err := exec.Command("iscsiadm", authPasswordArgs...).CombinedOutput(); err != nil {
return err
}
// Finally do the login
loginArgs := append(args, []string{"--login"}...)
_, err := exec.Command("iscsiadm", loginArgs...).CombinedOutput()
return err
}
// Login performs a simple iSCSI login (devices that do not use CHAP)
func Login(tiqn, portal, iface string) error {
args := []string{"-m", "node", "-T", tiqn, "-p", portal}
loginArgs := append(args, []string{"--login"}...)
Trace.Printf("attempt login with args: %s", loginArgs)
_, err := exec.Command("iscsiadm", loginArgs...).CombinedOutput()
return err
}
// GetDevice Attempts to gather device info for the specified target.
// If the device file is not found, we assuem the target is not connected
// and return an empty Device struct
func GetDevice(tgtIQN string) (Device, error) {
args := []string{"-t"}
out, err := exec.Command("lsscsi", args...).CombinedOutput()
if err != nil {
Error.Printf("unable to perform lsscsi -t, error: %+v", err)
return Device{}, err
}
devices := strings.Split(strings.TrimSpace(string(out)), "\n")
dev := Device{}
for _, entry := range devices {
if strings.Contains(entry, tgtIQN) {
fields := strings.Fields(entry)
dev.Path = fields[len(fields)-1]
}
}
Info.Printf("found lsscsi device: %s\n", dev)
if isMultipath(dev.Path) == true {
Info.Println("multipath detected...")
args = []string{dev.Path, "-n", "-o", "name", "-r"}
out, err = exec.Command("lsblk", args...).CombinedOutput()
if err != nil {
Error.Printf("unable to find mpath device due to lsblk error: %+v\n", err)
return dev, err
}
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
if len(lines) > 1 {
mpdev := lines[1]
dev.MPDevice = "/dev/mapper/" + mpdev
Info.Printf("parsed %s to extract mp device: %s\n", lines, dev.MPDevice)
} else {
Error.Printf("unable to parse lsblk output (%v)\n", lines)
// FIXME(jdg): Create an error here and return it
}
}
return dev, nil
}