forked from cloudfoundry/bosh-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
/
iscsi_device_path_resolver.go
243 lines (203 loc) · 7.24 KB
/
iscsi_device_path_resolver.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
237
238
239
240
241
242
243
package devicepathresolver
import (
"fmt"
"path"
"path/filepath"
"regexp"
"strings"
"time"
boshopeniscsi "github.com/cloudfoundry/bosh-agent/platform/openiscsi"
boshsettings "github.com/cloudfoundry/bosh-agent/settings"
boshdirs "github.com/cloudfoundry/bosh-agent/settings/directories"
bosherr "github.com/cloudfoundry/bosh-utils/errors"
boshlog "github.com/cloudfoundry/bosh-utils/logger"
boshsys "github.com/cloudfoundry/bosh-utils/system"
)
// iscsiDevicePathResolver resolves device path by performing Open-iscsi discovery
type iscsiDevicePathResolver struct {
diskWaitTimeout time.Duration
runner boshsys.CmdRunner
openiscsi boshopeniscsi.OpenIscsi
fs boshsys.FileSystem
dirProvider boshdirs.Provider
logTag string
logger boshlog.Logger
}
func NewIscsiDevicePathResolver(
diskWaitTimeout time.Duration,
runner boshsys.CmdRunner,
openiscsi boshopeniscsi.OpenIscsi,
fs boshsys.FileSystem,
dirProvider boshdirs.Provider,
logger boshlog.Logger,
) DevicePathResolver {
return iscsiDevicePathResolver{
diskWaitTimeout: diskWaitTimeout,
runner: runner,
openiscsi: openiscsi,
fs: fs,
dirProvider: dirProvider,
logTag: "iscsiResolver",
logger: logger,
}
}
func (ispr iscsiDevicePathResolver) GetRealDevicePath(diskSettings boshsettings.DiskSettings) (string, bool, error) {
err := ispr.checkISCSISettings(diskSettings.ISCSISettings)
if err != nil {
return "", false, bosherr.WrapError(err, "Checking disk settings")
}
// fetch existing path if disk is last mounted
lastDiskID, err := ispr.lastMountedCid()
if err != nil {
return "", false, bosherr.WrapError(err, "Fetching last mounted disk CID")
}
ispr.logger.Debug(ispr.logTag, "Last mounted disk CID: '%s'", lastDiskID)
mappedDevices, err := ispr.getMappedDevices()
if err != nil {
return "", false, bosherr.WrapError(err, "Getting mapped devices")
}
var existingPaths []string
existingPaths, err = ispr.getDevicePaths(mappedDevices, true)
if err != nil {
return "", false, bosherr.WrapError(err, "Getting existing paths")
}
ispr.logger.Debug(ispr.logTag, "Existing real paths '%+v'", existingPaths)
if len(existingPaths) > 2 {
return "", false, bosherr.WrapError(err, "More than 2 persistent disks attached")
}
if lastDiskID == diskSettings.ID && len(existingPaths) > 0 {
ispr.logger.Info(ispr.logTag, "Found existing path '%s'", existingPaths[0])
return existingPaths[0], false, nil
}
err = ispr.connectTarget(diskSettings.ISCSISettings)
if err != nil {
return "", false, bosherr.WrapError(err, "connecting iSCSI target")
}
// Combine paths to whole string to filter target path
exstingPathString := strings.Join(existingPaths, ",")
realPath, err := ispr.getDevicePathAfterConnectTarget(exstingPathString)
if err != nil {
if strings.Contains(err.Error(), "Timed out to get real iSCSI device path") {
return "", true, bosherr.WrapError(err, "get device path after connect iSCSI target")
}
return "", false, bosherr.WrapError(err, "get device path after connect iSCSI target")
}
return realPath, false, nil
}
func (ispr iscsiDevicePathResolver) checkISCSISettings(iSCSISettings boshsettings.ISCSISettings) error {
if iSCSISettings.InitiatorName == "" {
return bosherr.Errorf("iSCSI InitiatorName is not set")
}
if iSCSISettings.Username == "" {
return bosherr.Errorf("iSCSI Username is not set")
}
if iSCSISettings.Password == "" {
return bosherr.Errorf("iSCSI Password is not set")
}
if iSCSISettings.Target == "" {
return bosherr.Errorf("iSCSI Target is not set")
}
return nil
}
func (ispr iscsiDevicePathResolver) getMappedDevices() ([]string, error) {
var devices []string
result, _, _, err := ispr.runner.RunCommand("dmsetup", "ls")
if err != nil {
return devices, bosherr.WrapError(err, "listing mapped devices")
}
if strings.Contains(result, "No devices found") {
return devices, nil
}
devices = strings.Split(strings.Trim(result, "\n"), "\n")
ispr.logger.Debug(ispr.logTag, "devices: '%+v'", devices)
return devices, nil
}
// getDevicePaths: to find iSCSI device paths
// a "–part1" suffix device based on origin multipath device
// last mounted disk already have this device, new disk doesn't have this device yet
func (ispr iscsiDevicePathResolver) getDevicePaths(devices []string, shouldExist bool) ([]string, error) {
var paths []string
for _, device := range devices {
exist, err := regexp.MatchString("-part1", device)
if err != nil {
return paths, bosherr.WrapError(err, "There is a problem with your regexp: '-part1'. That is used to find existing device")
}
if exist == shouldExist {
matchedPath := path.Join("/dev/mapper", strings.Split(strings.Fields(device)[0], "-")[0])
ispr.logger.Debug(ispr.logTag, "path in device list: '%+v'", matchedPath)
paths = append(paths, matchedPath)
}
}
return paths, nil
}
func (ispr iscsiDevicePathResolver) connectTarget(iSCSISettings boshsettings.ISCSISettings) error {
err := ispr.openiscsi.Setup(iSCSISettings.InitiatorName, iSCSISettings.Username, iSCSISettings.Password)
if err != nil {
return bosherr.WrapError(err, "Could not setup Open-iSCSI")
}
err = ispr.openiscsi.Discovery(iSCSISettings.Target)
if err != nil {
return bosherr.WrapError(err, fmt.Sprintf("Could not discovery lun against portal %s", iSCSISettings.Target))
}
hasBeenLoggedin, err := ispr.openiscsi.IsLoggedin()
if err != nil {
return bosherr.WrapError(err, "Could not check all sessions")
}
if hasBeenLoggedin {
err = ispr.openiscsi.Logout()
if err != nil {
return bosherr.WrapError(err, "Could not logout all sessions")
}
}
err = ispr.openiscsi.Login()
if err != nil {
return bosherr.WrapError(err, "Could not login all sessions")
}
return nil
}
func (ispr iscsiDevicePathResolver) getDevicePathAfterConnectTarget(existingPath string) (string, error) {
ispr.logger.Debug(ispr.logTag, "Waiting for iSCSI device to appear")
timer := time.NewTimer(ispr.diskWaitTimeout)
for {
select {
case <-timer.C:
return "", bosherr.Errorf("Timed out to get real iSCSI device path")
default:
mappedDevices, err := ispr.getMappedDevices()
if err != nil {
return "", bosherr.WrapError(err, "Getting mapped devices")
}
var realPaths []string
realPaths, err = ispr.getDevicePaths(mappedDevices, false)
if err != nil {
return "", bosherr.WrapError(err, "Getting real paths")
}
if existingPath == "" && len(realPaths) == 1 {
ispr.logger.Info(ispr.logTag, "Found real path '%s'", realPaths[0])
return realPaths[0], nil
}
for _, realPath := range realPaths {
if strings.Contains(existingPath, realPath) {
continue
} else {
ispr.logger.Info(ispr.logTag, "Found real path '%s'", realPath)
return realPath, nil
}
}
}
time.Sleep(5 * time.Second)
}
}
func (ispr iscsiDevicePathResolver) lastMountedCid() (string, error) {
managedDiskSettingsPath := filepath.Join(ispr.dirProvider.BoshDir(), "managed_disk_settings.json")
var lastMountedCid string
if ispr.fs.FileExists(managedDiskSettingsPath) {
contents, err := ispr.fs.ReadFile(managedDiskSettingsPath)
if err != nil {
return "", bosherr.WrapError(err, "Reading managed_disk_settings.json")
}
lastMountedCid = string(contents)
return lastMountedCid, nil
}
return "", nil
}