Skip to content

Commit ac7d6cd

Browse files
authored
Merge pull request #397 from laozc/wmi-library-iscsi-api
feat: Use WMI to implement iSCSI API to reduce PowerShell overhead (library version)
2 parents f3aa922 + c2824b2 commit ac7d6cd

File tree

2 files changed

+549
-132
lines changed

2 files changed

+549
-132
lines changed

pkg/cim/iscsi.go

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package cim
5+
6+
import (
7+
"fmt"
8+
"strconv"
9+
10+
"github.com/microsoft/wmi/pkg/base/query"
11+
"github.com/microsoft/wmi/server2019/root/microsoft/windows/storage"
12+
)
13+
14+
var (
15+
ISCSITargetPortalDefaultSelectorList = []string{"TargetPortalAddress", "TargetPortalPortNumber"}
16+
)
17+
18+
// ListISCSITargetPortals retrieves a list of iSCSI target portals.
19+
//
20+
// The equivalent WMI query is:
21+
//
22+
// SELECT [selectors] FROM MSFT_IscsiTargetPortal
23+
//
24+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal
25+
// for the WMI class definition.
26+
func ListISCSITargetPortals(selectorList []string) ([]*storage.MSFT_iSCSITargetPortal, error) {
27+
q := query.NewWmiQueryWithSelectList("MSFT_IscsiTargetPortal", selectorList)
28+
instances, err := QueryInstances(WMINamespaceStorage, q)
29+
if IgnoreNotFound(err) != nil {
30+
return nil, err
31+
}
32+
33+
var targetPortals []*storage.MSFT_iSCSITargetPortal
34+
for _, instance := range instances {
35+
portal, err := storage.NewMSFT_iSCSITargetPortalEx1(instance)
36+
if err != nil {
37+
return nil, fmt.Errorf("failed to query iSCSI target portal %v. error: %v", instance, err)
38+
}
39+
40+
targetPortals = append(targetPortals, portal)
41+
}
42+
43+
return targetPortals, nil
44+
}
45+
46+
// QueryISCSITargetPortal retrieves information about a specific iSCSI target portal
47+
// identified by its network address and port number.
48+
//
49+
// The equivalent WMI query is:
50+
//
51+
// SELECT [selectors] FROM MSFT_IscsiTargetPortal
52+
// WHERE TargetPortalAddress = '<address>'
53+
// AND TargetPortalPortNumber = '<port>'
54+
//
55+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal
56+
// for the WMI class definition.
57+
func QueryISCSITargetPortal(address string, port uint32, selectorList []string) (*storage.MSFT_iSCSITargetPortal, error) {
58+
portalQuery := query.NewWmiQueryWithSelectList(
59+
"MSFT_iSCSITargetPortal", selectorList,
60+
"TargetPortalAddress", address,
61+
"TargetPortalPortNumber", strconv.Itoa(int(port)))
62+
instances, err := QueryInstances(WMINamespaceStorage, portalQuery)
63+
if err != nil {
64+
return nil, err
65+
}
66+
67+
targetPortal, err := storage.NewMSFT_iSCSITargetPortalEx1(instances[0])
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to query iSCSI target portal at (%s:%d). error: %v", address, port, err)
70+
}
71+
72+
return targetPortal, nil
73+
}
74+
75+
// ListISCSITargetsByTargetPortalAddressAndPort retrieves ISCSI targets by address and port of an iSCSI target portal.
76+
func ListISCSITargetsByTargetPortalAddressAndPort(address string, port uint32, selectorList []string) ([]*storage.MSFT_iSCSITarget, error) {
77+
instance, err := QueryISCSITargetPortal(address, port, selectorList)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
targets, err := ListISCSITargetsByTargetPortal([]*storage.MSFT_iSCSITargetPortal{instance})
83+
return targets, err
84+
}
85+
86+
// NewISCSITargetPortal creates a new iSCSI target portal.
87+
//
88+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal-new
89+
// for the WMI method definition.
90+
func NewISCSITargetPortal(targetPortalAddress string,
91+
targetPortalPortNumber uint32,
92+
initiatorInstanceName *string,
93+
initiatorPortalAddress *string,
94+
isHeaderDigest *bool,
95+
isDataDigest *bool) (*storage.MSFT_iSCSITargetPortal, error) {
96+
params := map[string]interface{}{
97+
"TargetPortalAddress": targetPortalAddress,
98+
"TargetPortalPortNumber": targetPortalPortNumber,
99+
}
100+
if initiatorInstanceName != nil {
101+
params["InitiatorInstanceName"] = *initiatorInstanceName
102+
}
103+
if initiatorPortalAddress != nil {
104+
params["InitiatorPortalAddress"] = *initiatorPortalAddress
105+
}
106+
if isHeaderDigest != nil {
107+
params["IsHeaderDigest"] = *isHeaderDigest
108+
}
109+
if isDataDigest != nil {
110+
params["IsDataDigest"] = *isDataDigest
111+
}
112+
result, _, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_iSCSITargetPortal", "New", params)
113+
if err != nil {
114+
return nil, fmt.Errorf("failed to create iSCSI target portal with %v. result: %d, error: %v", params, result, err)
115+
}
116+
117+
return QueryISCSITargetPortal(targetPortalAddress, targetPortalPortNumber, nil)
118+
}
119+
120+
// ParseISCSITargetPortal retrieves the portal address and port number of an iSCSI target portal.
121+
func ParseISCSITargetPortal(instance *storage.MSFT_iSCSITargetPortal) (string, uint32, error) {
122+
portalAddress, err := instance.GetPropertyTargetPortalAddress()
123+
if err != nil {
124+
return "", 0, fmt.Errorf("failed parsing target portal address %v. err: %w", instance, err)
125+
}
126+
127+
portalPort, err := instance.GetProperty("TargetPortalPortNumber")
128+
if err != nil {
129+
return "", 0, fmt.Errorf("failed parsing target portal port number %v. err: %w", instance, err)
130+
}
131+
132+
return portalAddress, uint32(portalPort.(int32)), nil
133+
}
134+
135+
// RemoveISCSITargetPortal removes an iSCSI target portal.
136+
//
137+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal-remove
138+
// for the WMI method definition.
139+
func RemoveISCSITargetPortal(instance *storage.MSFT_iSCSITargetPortal) (int, error) {
140+
address, port, err := ParseISCSITargetPortal(instance)
141+
if err != nil {
142+
return 0, fmt.Errorf("failed to parse target portal %v. error: %v", instance, err)
143+
}
144+
145+
result, err := instance.InvokeMethodWithReturn("Remove",
146+
nil,
147+
nil,
148+
int(port),
149+
address,
150+
)
151+
return int(result), err
152+
}
153+
154+
// ListISCSITargetsByTargetPortal retrieves all iSCSI targets from the specified iSCSI target portal
155+
// using MSFT_iSCSITargetToiSCSITargetPortal association.
156+
//
157+
// WMI association MSFT_iSCSITargetToiSCSITargetPortal:
158+
//
159+
// iSCSITarget | iSCSITargetPortal
160+
// ----------- | -----------------
161+
// MSFT_iSCSITarget (NodeAddress = "iqn.1991-05.com.microsoft:win-8e2evaq9q...) | MSFT_iSCSITargetPortal (TargetPortalAdd...
162+
//
163+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitarget
164+
// for the WMI class definition.
165+
func ListISCSITargetsByTargetPortal(portals []*storage.MSFT_iSCSITargetPortal) ([]*storage.MSFT_iSCSITarget, error) {
166+
var targets []*storage.MSFT_iSCSITarget
167+
for _, portal := range portals {
168+
collection, err := portal.GetAssociated("MSFT_iSCSITargetToiSCSITargetPortal", "MSFT_iSCSITarget", "iSCSITarget", "iSCSITargetPortal")
169+
if err != nil {
170+
return nil, fmt.Errorf("failed to query associated iSCSITarget for %v. error: %v", portal, err)
171+
}
172+
173+
for _, instance := range collection {
174+
target, err := storage.NewMSFT_iSCSITargetEx1(instance)
175+
if err != nil {
176+
return nil, fmt.Errorf("failed to query iSCSI target %v. error: %v", instance, err)
177+
}
178+
179+
targets = append(targets, target)
180+
}
181+
}
182+
183+
return targets, nil
184+
}
185+
186+
// QueryISCSITarget retrieves the iSCSI target from the specified portal address, portal and node address.
187+
func QueryISCSITarget(address string, port uint32, nodeAddress string) (*storage.MSFT_iSCSITarget, error) {
188+
portal, err := QueryISCSITargetPortal(address, port, nil)
189+
if err != nil {
190+
return nil, err
191+
}
192+
193+
targets, err := ListISCSITargetsByTargetPortal([]*storage.MSFT_iSCSITargetPortal{portal})
194+
if err != nil {
195+
return nil, err
196+
}
197+
198+
for _, target := range targets {
199+
targetNodeAddress, err := GetISCSITargetNodeAddress(target)
200+
if err != nil {
201+
return nil, fmt.Errorf("failed to query iSCSI target %v. error: %v", target, err)
202+
}
203+
204+
if targetNodeAddress == nodeAddress {
205+
return target, nil
206+
}
207+
}
208+
209+
return nil, nil
210+
}
211+
212+
// GetISCSITargetNodeAddress returns the node address of an iSCSI target.
213+
func GetISCSITargetNodeAddress(target *storage.MSFT_iSCSITarget) (string, error) {
214+
nodeAddress, err := target.GetProperty("NodeAddress")
215+
if err != nil {
216+
return "", err
217+
}
218+
219+
return nodeAddress.(string), err
220+
}
221+
222+
// IsISCSITargetConnected returns whether the iSCSI target is connected.
223+
func IsISCSITargetConnected(target *storage.MSFT_iSCSITarget) (bool, error) {
224+
return target.GetPropertyIsConnected()
225+
}
226+
227+
// QueryISCSISessionByTarget retrieves the iSCSI session from the specified iSCSI target
228+
// using MSFT_iSCSITargetToiSCSISession association.
229+
//
230+
// WMI association MSFT_iSCSITargetToiSCSISession:
231+
//
232+
// iSCSISession | iSCSITarget
233+
// ------------ | -----------
234+
// MSFT_iSCSISession (SessionIdentifier = "ffffac0cacbff010-4000013700000016") | MSFT_iSCSITarget (NodeAddress = "iqn.199...
235+
//
236+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsisession
237+
// for the WMI class definition.
238+
func QueryISCSISessionByTarget(target *storage.MSFT_iSCSITarget) (*storage.MSFT_iSCSISession, error) {
239+
collection, err := target.GetAssociated("MSFT_iSCSITargetToiSCSISession", "MSFT_iSCSISession", "iSCSISession", "iSCSITarget")
240+
if err != nil {
241+
return nil, fmt.Errorf("failed to query associated iSCSISession for %v. error: %v", target, err)
242+
}
243+
244+
if len(collection) == 0 {
245+
return nil, nil
246+
}
247+
248+
session, err := storage.NewMSFT_iSCSISessionEx1(collection[0])
249+
return session, err
250+
}
251+
252+
// UnregisterISCSISession unregisters the iSCSI session so that it is no longer persistent.
253+
//
254+
// Refer https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsisession-unregister
255+
// for the WMI method definition.
256+
func UnregisterISCSISession(session *storage.MSFT_iSCSISession) (int, error) {
257+
result, err := session.InvokeMethodWithReturn("Unregister")
258+
return int(result), err
259+
}
260+
261+
// SetISCSISessionChapSecret sets a CHAP secret key for use with iSCSI initiator connections.
262+
//
263+
// Refer https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitarget-disconnect
264+
// for the WMI method definition.
265+
func SetISCSISessionChapSecret(mutualChapSecret string) (int, error) {
266+
result, _, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_iSCSISession", "SetCHAPSecret", map[string]interface{}{"ChapSecret": mutualChapSecret})
267+
return result, err
268+
}
269+
270+
// GetISCSISessionIdentifier returns the identifier of an iSCSI session.
271+
func GetISCSISessionIdentifier(session *storage.MSFT_iSCSISession) (string, error) {
272+
return session.GetPropertySessionIdentifier()
273+
}
274+
275+
// IsISCSISessionPersistent returns whether an iSCSI session is persistent.
276+
func IsISCSISessionPersistent(session *storage.MSFT_iSCSISession) (bool, error) {
277+
return session.GetPropertyIsPersistent()
278+
}
279+
280+
// ListDisksByTarget find all disks associated with an iSCSITarget.
281+
// It finds out the iSCSIConnections from MSFT_iSCSITargetToiSCSIConnection association,
282+
// then locate MSFT_Disk objects from MSFT_iSCSIConnectionToDisk association.
283+
//
284+
// WMI association MSFT_iSCSITargetToiSCSIConnection:
285+
//
286+
// iSCSIConnection | iSCSITarget
287+
// --------------- | -----------
288+
// MSFT_iSCSIConnection (ConnectionIdentifier = "ffffac0cacbff010-15") | MSFT_iSCSITarget (NodeAddress = "iqn.1991-05.com...
289+
//
290+
// WMI association MSFT_iSCSIConnectionToDisk:
291+
//
292+
// Disk | iSCSIConnection
293+
// ---- | ---------------
294+
// MSFT_Disk (ObjectId = "{1}\\WIN-8E2EVAQ9QSB\root/Microsoft/Win...) | MSFT_iSCSIConnection (ConnectionIdentifier = "fff...
295+
//
296+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsiconnection
297+
// for the WMI class definition.
298+
func ListDisksByTarget(target *storage.MSFT_iSCSITarget) ([]*storage.MSFT_Disk, error) {
299+
// list connections to the given iSCSI target
300+
collection, err := target.GetAssociated("MSFT_iSCSITargetToiSCSIConnection", "MSFT_iSCSIConnection", "iSCSIConnection", "iSCSITarget")
301+
if err != nil {
302+
return nil, fmt.Errorf("failed to query associated iSCSISession for %v. error: %v", target, err)
303+
}
304+
305+
if len(collection) == 0 {
306+
return nil, nil
307+
}
308+
309+
var result []*storage.MSFT_Disk
310+
for _, conn := range collection {
311+
instances, err := conn.GetAssociated("MSFT_iSCSIConnectionToDisk", "MSFT_Disk", "Disk", "iSCSIConnection")
312+
if err != nil {
313+
return nil, fmt.Errorf("failed to query associated disk for %v. error: %v", target, err)
314+
}
315+
316+
for _, instance := range instances {
317+
disk, err := storage.NewMSFT_DiskEx1(instance)
318+
if err != nil {
319+
return nil, fmt.Errorf("failed to query associated disk %v. error: %v", instance, err)
320+
}
321+
322+
result = append(result, disk)
323+
}
324+
}
325+
326+
return result, err
327+
}
328+
329+
// ConnectISCSITarget establishes a connection to an iSCSI target with optional CHAP authentication credential.
330+
//
331+
// Refer https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitarget-connect
332+
// for the WMI method definition.
333+
func ConnectISCSITarget(portalAddress string, portalPortNumber uint32, nodeAddress string, authType string, chapUsername *string, chapSecret *string) (int, error) {
334+
inParams := map[string]interface{}{
335+
"NodeAddress": nodeAddress,
336+
"TargetPortalAddress": portalAddress,
337+
"TargetPortalPortNumber": int(portalPortNumber),
338+
"AuthenticationType": authType,
339+
}
340+
// InitiatorPortalAddress
341+
// IsDataDigest
342+
// IsHeaderDigest
343+
// ReportToPnP
344+
if chapUsername != nil {
345+
inParams["ChapUsername"] = *chapUsername
346+
}
347+
if chapSecret != nil {
348+
inParams["ChapSecret"] = *chapSecret
349+
}
350+
351+
result, _, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_iSCSITarget", "Connect", inParams)
352+
return result, err
353+
}
354+
355+
// DisconnectISCSITarget disconnects the specified session between an iSCSI initiator and an iSCSI target.
356+
//
357+
// Refer https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitarget-disconnect
358+
// for the WMI method definition.
359+
func DisconnectISCSITarget(target *storage.MSFT_iSCSITarget, sessionIdentifier string) (int, error) {
360+
result, err := target.InvokeMethodWithReturn("Disconnect", sessionIdentifier)
361+
return int(result), err
362+
}

0 commit comments

Comments
 (0)