forked from hpe-storage/common-host-libs
-
Notifications
You must be signed in to change notification settings - Fork 1
/
chapiclient.go
620 lines (551 loc) · 19.7 KB
/
chapiclient.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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
package chapi
import (
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/hpe-storage/common-host-libs/connectivity"
log "github.com/hpe-storage/common-host-libs/logger"
"github.com/hpe-storage/common-host-libs/model"
"github.com/hpe-storage/common-host-libs/util"
)
var (
// Hosts represents collection of hosts
Hosts model.Hosts
// DeviceURIfmt represents particular device endpoint format for GET and POST requests
DeviceURIfmt = "%sdevices/%s"
// HostURIfmt represents hosts endpoint format for GET requests
HostURIfmt = "/hosts/%s/"
// MountURIfmt represents mounts endpoint format for GET and POST requests
MountURIfmt = "%smounts/%s"
// DevicesURIfmt represents devices endpoint format for GET requests
DevicesURIfmt = "%sdevices"
// UnmountURIfmt represents particular mount endpoint for POST requests
UnmountURIfmt = "%smounts/%s"
// CreateFSURIfmt represents devices endpoint format for POST requests
CreateFSURIfmt = "%sdevices/%s/%s"
// NetworksURIfmt represents networks endpoint format for GET requests
NetworksURIfmt = "%snetworks"
// InitiatorsURIfmt represents initiators endpoint for GET requests
InitiatorsURIfmt = "%sinitiators"
// HostnameURIfmt represents hostname endpoint for GET requests
HostnameURIfmt = "%shostname"
// ChapInfoURIfmt represents endpoint to obtain initiator CHAP credentials
ChapInfoURIfmt = "%schapinfo"
// LogLevel of the chapi client
LogLevel = "info"
)
// Client defines client for chapid and corresponding socket file or http:port info
type Client struct {
// client is the http client for chapid server
client *connectivity.Client
// socket is the socket name on which chapid is listening
socket string
// hostname where server is running
hostname string
// port number where the server is listening on.
port uint64
// host ID where server is running
hostID string
// HTTP headers
header map[string]string
}
// AccessKeyPath struct
type AccessKeyPath struct {
Path string `json:"path"`
}
// Print to dump chapi client struct
func (chapiClient *Client) Print() {
log.Traceln("HostID : ", chapiClient.hostID)
log.Traceln("Socket : ", chapiClient.socket)
log.Traceln("Hostname : ", chapiClient.hostname)
log.Traceln("Port : ", chapiClient.port)
log.Traceln("Header : ", chapiClient.header)
}
// GetHostURL to consruct HTTP URL of the form "hostname:port"
func GetHostURL(hostName string, port uint64) string {
return fmt.Sprintf("%v:%v", hostName, port)
}
// NewChapiHTTPClient to create chapi http client
func NewChapiHTTPClient(hostName string, port uint64) (*Client, error) {
var chapiClient *Client
hostURL := GetHostURL(hostName, port)
log.Debugln("setting up chapi client with http", hostURL)
// setup chapi client with timeout
httpClient := connectivity.NewHTTPClient(hostURL)
chapiClient = &Client{client: httpClient, hostname: hostName, port: port}
return chapiClient, nil
}
// NewChapiHTTPClientWithTimeout to create chapi http client with timeout
func NewChapiHTTPClientWithTimeout(hostName string, port uint64, timeout time.Duration) (*Client, error) {
var chapiClient *Client
hostURL := GetHostURL(hostName, port)
log.Debugln("setting up chapi client with http", hostURL, "and timeout", timeout)
// setup chapi client with timeout
httpClient := connectivity.NewHTTPClientWithTimeout(hostURL, timeout)
chapiClient = &Client{client: httpClient, hostname: hostName, port: port}
return chapiClient, nil
}
// NewChapiHTTPClientWithTimeoutAndHeader to create chapi http client with timeout and headers
func NewChapiHTTPClientWithTimeoutAndHeader(hostName string, port uint64, timeout time.Duration, header map[string]string) (*Client, error) {
chapiClient, err := NewChapiHTTPClientWithTimeout(hostName, port, timeout)
if err != nil {
return nil, err
}
// Insert http headers
chapiClient.AddHeader(header)
return chapiClient, nil
}
// AddHeader to insert HTTP headers to chapi client
func (chapiClient *Client) AddHeader(header map[string]string) error {
log.Traceln("Inserting http headers", header, "to chapi client")
chapiClient.header = header
return nil
}
// get host ID and cache it with chapi client
func (chapiClient *Client) cacheHostID() (err error) {
log.Tracef("cacheHostID called")
if chapiClient.hostID == "" {
chapiClient.hostID, err = chapiClient.GetHostID()
if err != nil {
log.Errorln("Failed to get host ID")
return err
}
}
return nil
}
// GetHostID will retrieve the host uuid from host
func (chapiClient *Client) GetHostID() (string, error) {
log.Trace("GetHostID called")
var hosts []*model.Host
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &hosts
chapiResp.Err = &errResp
_, err := chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: "/hosts", Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Errorf(errResp.Info)
return "", fmt.Errorf(errResp.Info)
}
log.Errorf("GetHostID Err :%s", err.Error())
return "", err
}
if len(hosts) != 1 {
err := fmt.Errorf("no valid host found")
log.Errorf(err.Error())
return "", err
}
log.Tracef("hostid obtained is :%s", hosts[0].UUID)
return hosts[0].UUID, nil
}
// GetInitiators will return host initiator list (WWPNs in case of FC)
func (chapiClient *Client) GetInitiators() (initiators []*model.Initiator, err error) {
log.Trace("GetInitiators called")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return nil, err
}
var chapiResp Response
chapiResp.Data = &initiators
var errResp *ErrorResponse
chapiResp.Err = &errResp
initiatorsURI := fmt.Sprintf(InitiatorsURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID))
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: initiatorsURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return nil, errors.New(errResp.Info)
}
log.Errorf("GetInitiators Err :%s", err.Error())
return nil, err
}
return initiators, nil
}
// GetChapInfo will return host initiator CHAP details
func (chapiClient *Client) GetChapInfo() (chapInfo *model.ChapInfo, err error) {
log.Trace("GetChapInfo called")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return nil, err
}
chapURI := fmt.Sprintf(ChapInfoURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID))
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Err = &errResp
chapiResp.Data = &chapInfo
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: chapURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return nil, errors.New(errResp.Info)
}
log.Errorf("GetChapInfo Err :%s", err.Error())
return nil, err
}
return chapInfo, nil
}
// GetNetworks return list of host network interface details
func (chapiClient *Client) GetNetworks() (networks []*model.NetworkInterface, err error) {
log.Trace("GetNetworks called")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return nil, err
}
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &networks
chapiResp.Err = &errResp
networksURI := fmt.Sprintf(NetworksURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID))
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: networksURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return nil, errors.New(errResp.Info)
}
log.Errorf("GetNetworks Err :", err.Error())
return nil, err
}
return networks, nil
}
// AttachDevice will attach the given os device for given volume on the host
func (chapiClient *Client) AttachDevice(volumes []*model.Volume) (devices []*model.Device, err error) {
log.Tracef(">>>>> AttachDevice called with %#v", volumes)
defer log.Trace("<<<<< AttachDevice")
// check if volumes is not nil
if len(volumes) == 0 {
return nil, fmt.Errorf("no volume available to attach")
}
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return nil, err
}
devicesURI := fmt.Sprintf(DevicesURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID))
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &devices
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "POST", Path: devicesURI, Header: chapiClient.header, Payload: &volumes, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Errorf("AttachDevice: %s for volume(%s)", errResp.Info, volumes[0].Name)
return nil, errors.New(errResp.Info)
}
log.Errorf("AttachDevice: Err:%s for volume(%s)", err.Error(), volumes[0].Name)
return nil, err
}
// Win CHAPI doesn't return Device[0].State, added OR condition to handele it.
if len(devices) != 0 && ((devices[0].State == model.ActiveState.String()) || (devices[0].State == "")) {
log.Debugf("Device found with active paths %+v", devices[0])
return devices, nil
}
return nil, nil
}
// AttachAndMountDevice will attach the given os device for given volume and mounts the filesystem on the host
func (chapiClient *Client) AttachAndMountDevice(volume *model.Volume, mountPath string) (err error) {
log.Tracef(">>>>> AttachAndMountDevice on volume %#v to mount path %s", volume, mountPath)
defer log.Trace("<<<<< AttachAndMountDevice")
var vols []*model.Volume
vols = append(vols, volume)
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return err
}
// first create and attach the device
devices, err := chapiClient.AttachDevice(vols)
if err != nil || len(devices) == 0 {
err = fmt.Errorf("unable to attach device %s", err.Error())
log.Tracef(err.Error())
return err
}
// wait for a second to mount the device
time.Sleep(time.Second)
// perform the mount of the device created above
err = chapiClient.retryMountFileSystem(volume, mountPath)
if err != nil {
return err
}
return nil
}
func (chapiClient *Client) retryMountFileSystem(volume *model.Volume, mountPath string) (err error) {
log.Tracef("retryMountFileSystem called with %#v and mountPoint %s", volume, mountPath)
maxTries := 3
try := 0
for {
err = chapiClient.MountFilesystem(volume, mountPath)
if err == nil {
return nil
}
log.Tracef("error trying to mount filesystem %v", err.Error())
if strings.Contains(err.Error(), "no such file") || strings.Contains(err.Error(), "doesn't exist") {
if try < maxTries {
try++
log.Debugf("try=%d for mountFileSystem for %s", try, volume.Name)
time.Sleep(time.Duration(try) * time.Second)
continue
}
}
return err
}
}
// CreateFilesystem calls chapi server to create filesystem
func (chapiClient *Client) CreateFilesystem(device *model.Device, vol *model.Volume, filesystem string) (err error) {
log.Tracef("CreateFilesystem called for %s and filesystem %s", device.MpathName, filesystem)
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return err
}
createFSURI := fmt.Sprintf(CreateFSURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID), device.SerialNumber, filesystem)
var errResp *ErrorResponse
var dev *model.Device
var chapiResp Response
chapiResp.Data = &dev
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "PUT", Path: createFSURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return errors.New(errResp.Info)
}
log.Error("CreateFilesystem Err :", err.Error())
return err
}
return nil
}
// Mount makes a POST request to chapi to mount filesystem on the host
func (chapiClient *Client) Mount(reqMount *model.Mount, respMount *model.Mount) (err error) {
log.Tracef("Mount called on for device %s serialNumber %s", reqMount.Device.MpathName, reqMount.Device.SerialNumber)
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return err
}
mountURI := fmt.Sprintf(MountURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID), reqMount.Device.SerialNumber)
log.Tracef("mountURI=%s", mountURI)
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &respMount
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "POST", Path: mountURI, Header: chapiClient.header, Payload: &reqMount, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return errors.New(errResp.Info)
}
log.Error("Mount Err :", err.Error())
return err
}
if respMount == nil {
return errors.New("no valid Mount Point created")
}
return nil
}
// GetDevices will return all the OS devices on the host
func (chapiClient *Client) GetDevices() (devices []*model.Device, err error) {
log.Trace(">>>>> GetDevices")
defer log.Trace("<<<<< GetDevices")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return nil, err
}
var errResp *ErrorResponse
var chapiResp Response
devicesURI := fmt.Sprintf(DevicesURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID))
chapiResp.Data = &devices
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: devicesURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error("GetDevices: error response, ", errResp.Info)
return nil, errors.New(errResp.Info)
}
log.Debug("GetDevices: error getting device list, ", err.Error())
return nil, err
}
return devices, nil
}
// GetDeviceFromVolume will return os device for given storage volume
func (chapiClient *Client) GetDeviceFromVolume(volume *model.Volume) (device *model.Device, err error) {
log.Tracef(">>>>>GetDeviceFromVolume called for %s", volume.Name)
defer log.Tracef("<<<<< GetDeviceFromVolume")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return nil, err
}
serialNumber := volume.SerialNumber
devicesURI := fmt.Sprintf(DeviceURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID), serialNumber)
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &device
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: devicesURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return nil, errors.New(errResp.Info)
}
log.Error("GetDeviceFromVolume Err :", err.Error())
return nil, err
}
if device == nil {
return nil, fmt.Errorf("no matching device found for volume %s", volume.Name)
}
device.TargetScope = volume.TargetScope
return device, nil
}
// GetMounts will return all mountpoints of a volume with given serial
func (chapiClient *Client) GetMounts(respMount *[]*model.Mount, serialNumber string) (err error) {
log.Trace("GetMounts called")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return err
}
log.Tracef("getting mounts for serial Number %s", serialNumber)
mountsURI := fmt.Sprintf(MountURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID), serialNumber)
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &respMount
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "GET", Path: mountsURI, Header: chapiClient.header, Payload: nil, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Error(errResp.Info)
return errors.New(errResp.Info)
}
log.Error("GetMounts Err :", err.Error())
return err
}
return err
}
//UnmountDevice performs the host side workflow to unmount a volume
func (chapiClient *Client) UnmountDevice(volume *model.Volume) error {
log.Trace(">>>>> UnmountDevice for volume ", volume.Name)
defer log.Trace("<<<<< UnmountDevice")
var respMount []*model.Mount
err := chapiClient.GetMounts(&respMount, volume.SerialNumber)
if err != nil && !(strings.Contains(err.Error(), "object was not found")) {
return err
}
if respMount != nil {
for _, mount := range respMount {
log.Tracef("perform an unmount on the host with %#v", mount)
if mount.Device.SerialNumber == volume.SerialNumber {
log.Tracef("Device to ummount found :%s", mount.Mountpoint)
var respMount model.Mount
err = chapiClient.Unmount(mount, &respMount)
if err != nil {
return err
}
// TODO: This can be dangerous. Move this to docker plugin to be docker specific
//3. delete the Mounpoint
_, isDir, _ := util.FileExists(mount.Mountpoint)
if isDir {
os.RemoveAll(mount.Mountpoint)
if err != nil {
return err
}
}
break
}
}
}
return nil
}
// Unmount make a DELETE request to chapi to unmount filesystem on the host
func (chapiClient *Client) Unmount(reqMount *model.Mount, respMount *model.Mount) (err error) {
log.Trace(">>>>> Unmount called on %s", reqMount.Mountpoint)
defer log.Trace("<<<<< Unmount")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return err
}
unMountURI := fmt.Sprintf(UnmountURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID), reqMount.ID)
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &respMount
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(
&connectivity.Request{
Action: "DELETE",
Path: unMountURI,
Header: chapiClient.header,
Payload: &reqMount,
Response: &chapiResp,
ResponseError: &chapiResp,
},
)
if err != nil {
if errResp != nil {
log.Errorf("Unmount: err info %s", errResp.Info)
return errors.New(errResp.Info)
}
log.Error("Unmount Err :", err.Error())
return err
}
return err
}
//OfflineDevice : offline the device in preparation of removing ACL to avoid race-condition to be discovered again
func (chapiClient *Client) OfflineDevice(device *model.Device) (err error) {
log.Tracef(">>>>> OfflineDevice with %#v", device)
defer log.Trace("<<<<< OfflineDevice")
err = chapiClient.cacheHostID()
if err != nil {
return err
}
serialNumber := device.SerialNumber
deviceOfflineURI := fmt.Sprintf("/hosts/%s/devices/%s/actions/offline", chapiClient.hostID, serialNumber)
var deviceResp model.Device
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &deviceResp
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "PUT", Path: deviceOfflineURI, Header: chapiClient.header, Payload: device, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Errorf("OfflineDevice Err info :%s", errResp.Info)
return fmt.Errorf(errResp.Info)
}
log.Errorf("OfflineDevice Err :%s", err.Error())
return err
}
return nil
}
//DeleteDevice : delete the os device on the host
// nolint : Remove this once 'DetachDevice' from chapiclient_windows.go is removed
func (chapiClient *Client) DeleteDevice(device *model.Device) (err error) {
log.Tracef(">>>>> DeleteDevice with %#v", device)
defer log.Trace("<<<<< DeleteDevice")
// fetch host ID
err = chapiClient.cacheHostID()
if err != nil {
return err
}
serialNumber := device.SerialNumber
deviceURI := fmt.Sprintf(DeviceURIfmt, fmt.Sprintf(HostURIfmt, chapiClient.hostID), serialNumber)
var deviceResp model.Device
var errResp *ErrorResponse
var chapiResp Response
chapiResp.Data = &deviceResp
chapiResp.Err = &errResp
_, err = chapiClient.client.DoJSON(&connectivity.Request{Action: "DELETE", Path: deviceURI, Header: chapiClient.header, Payload: device, Response: &chapiResp, ResponseError: &chapiResp})
if err != nil {
if errResp != nil {
log.Errorf("DeleteDevice Err info %s", errResp.Info)
return errors.New(errResp.Info)
}
log.Errorf("DeleteDevice Err :%s", err.Error())
return err
}
return err
}