Skip to content

Commit cd2f235

Browse files
authored
Merge pull request #372 from laozc/wmi-library
feat: Use WMI to implement Volume API to reduce PowerShell overhead (library version)
2 parents 78e9c5f + 9c5813a commit cd2f235

File tree

392 files changed

+83882
-2285
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

392 files changed

+83882
-2285
lines changed

.github/workflows/windows.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ jobs:
55
integration_tests:
66
strategy:
77
matrix:
8-
go: ['1.16']
8+
go: ['1.22']
99
platform: [windows-latest]
1010
runs-on: ${{ matrix.platform }}
1111
steps:
@@ -22,11 +22,11 @@ jobs:
2222
Install-WindowsFeature -name Hyper-V-PowerShell
2323
2424
$env:CSI_PROXY_GH_ACTIONS="TRUE"
25-
go test --timeout 20m -v -race ./integrationtests/...
25+
go test --timeout 20m -v ./integrationtests/...
2626
unit_tests:
2727
strategy:
2828
matrix:
29-
go: ['1.16']
29+
go: ['1.22']
3030
platform: [windows-latest]
3131
runs-on: ${{ matrix.platform }}
3232
steps:

docs/IMPLEMENTATION.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# CSI Proxy API Implementation with WMI
2+
<a name="top"></a>
3+
4+
## Table of Contents
5+
6+
- [Windows Management Instrumentation](#wmi)
7+
- [microsoft/wmi library](#microsoft-wmi-library)
8+
- [How to make WMI queries and debug with PowerShell](#debug-powershell)
9+
10+
11+
<a name="wmi"></a>
12+
## Windows Management Instrumentation
13+
14+
Windows Management Instrumentation (WMI) is the infrastructure for management data and operations on Windows-based operating systems.
15+
Refer to [WMI start page](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page) for more details.
16+
17+
The purpose of WMI is to define a proprietary set of environment-independent specifications that enable sharing management information between management apps.
18+
19+
CSI-proxy makes WMI queries using `microsoft/wmi` library. Refer to for the call graph below.
20+
21+
<a name="microsoft-wmi-library"></a>
22+
## microsoft/wmi library
23+
24+
![WMI based implementation](./WMI.png)
25+
26+
`microsoft/wmi` library leverages the traditional COM interfaces (`IDispatch`) to call the WMI.
27+
28+
COM interfaces wrap the parameters and return value in a `VARIANT` struct.
29+
`microsoft/wmi` library converts the `VARIANT` to native Go types and struct.
30+
31+
A typical WMI query may need to obtain a WMI session of the target machine first, which
32+
can be done by the helper methods `NewWMISession` and `QueryFromWMI` in `pkg/cim`.
33+
34+
A query like `SELECT * FROM MSFT_Volume` may return all the volumes on the current node.
35+
36+
### Queries
37+
38+
The query may return a list of WMI objects of the generic type `cim.WmiInstance`. You may further cast
39+
the object down to a specific WMI class (e.g. `MSFT_Disk`). You may find the WMI class definition
40+
from the API doc.
41+
42+
For example, the property `PartitionStyle` on [MSFT_Disk](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk#properties) is defined as
43+
44+
| API constraints | Settings |
45+
|-----------------|---------------------------------------|
46+
| Property | PartitionStyle |
47+
| Data type | UInt16 |
48+
| Access type | Read-only |
49+
| Qualifiers | Required |
50+
| Description | The partition style used by the disk. |
51+
52+
You may use `GetProperty` to get the value of `PartitionStyle` to get the value from the `VARIANT` and
53+
converts it back to Go types.
54+
55+
```go
56+
retValue, err := disk.GetProperty("PartitionStyle")
57+
if err != nil {
58+
return false, fmt.Errorf("failed to query partition style of disk %d: %w", diskNumber, err)
59+
}
60+
61+
partitionStyle = retValue.(int32)
62+
```
63+
64+
Note that some auto-generated wrapper methods in `microsoft/wmi` may have wrong data types mapping to Go native types.
65+
It's always recommended to use `GetProperty` instead of these pre-defined wrapper methods.
66+
67+
### Class Method
68+
69+
A WMI class may have some Class Method to call for a specific operation (e.g., creating a new partition).
70+
71+
You may use the method `InvokeMethodWithReturn`.
72+
73+
```go
74+
result, err := disk.InvokeMethodWithReturn(
75+
"CreatePartition",
76+
nil, // Size
77+
true, // UseMaximumSize
78+
nil, // Offset
79+
nil, // Alignment
80+
nil, // DriveLetter
81+
false, // AssignDriveLetter
82+
nil, // MbrType,
83+
cim.GPTPartitionTypeBasicData, // GPT Type
84+
false, // IsHidden
85+
false, // IsActive,
86+
)
87+
// 42002 is returned by driver letter failed to assign after partition
88+
if (result != 0 && result != 42002) || err != nil {
89+
return fmt.Errorf("error creating partition on disk %d. result: %d, err: %v", diskNumber, result, err)
90+
}
91+
```
92+
93+
Both input and output parameters can be found in the [CreatePartition API doc](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk).
94+
95+
```c
96+
UInt32 CreatePartition(
97+
[in] UInt64 Size,
98+
[in] Boolean UseMaximumSize,
99+
[in] UInt64 Offset,
100+
[in] UInt32 Alignment,
101+
[in] Char16 DriveLetter,
102+
[in] Boolean AssignDriveLetter,
103+
[in] UInt16 MbrType,
104+
[in] String GptType,
105+
[in] Boolean IsHidden,
106+
[in] Boolean IsActive,
107+
[out] String CreatedPartition,
108+
[out] String ExtendedStatus
109+
);
110+
```
111+
112+
There parameters will be wrapped in `VARIANT` with the corresponding types.
113+
114+
Eventually the method `CreatePartition` on the WMI object will be called via `IDispatch` interface in native COM/OLE calls.
115+
116+
Refer to [CallMethod](https://github.com/go-ole/go-ole/blob/master/oleutil/oleutil.go#L49-L52) if you need to know the details of a COM/OLE call.
117+
118+
```go
119+
func CallMethod(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT, err error) {
120+
return disp.InvokeWithOptionalArgs(name, ole.DISPATCH_METHOD, params)
121+
}
122+
```
123+
124+
### Association
125+
126+
Association can be used to retrieve all instances that are associated with
127+
a particular source instance.
128+
129+
There are a few Association classes in WMI.
130+
131+
For example, association class [MSFT_PartitionToVolume](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-partitiontovolume)
132+
can be used to retrieve a volume (`MSFT_Volume`) from a partition (`MSFT_Partition`), and vice versa.
133+
134+
```go
135+
collection, err := part.GetAssociated("MSFT_PartitionToVolume", "MSFT_Volume", "Volume", "Partition")
136+
```
137+
138+
<a name="debug-powershell"></a>
139+
## Debug with PowerShell
140+
141+
### How to make WMI call with PowerShell
142+
143+
You will find the `Query` for each method in `pkg/cim` package.
144+
For example, this is the comment of `ListVolume`
145+
146+
```go
147+
// ListVolumes retrieves all available volumes on the system.
148+
//
149+
// The equivalent WMI query is:
150+
//
151+
// SELECT [selectors] FROM MSFT_Volume
152+
//
153+
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-volume
154+
// for the WMI class definition.
155+
```
156+
157+
You may use the same query with PowerShell cmdlet `Get-CimInstance` targeting
158+
the corresponding namespace `Root\Microsoft\Windows\Storage`.
159+
160+
The return result will be an object of `MSFT_Volume` WMI class.
161+
162+
e.g. if we're going to list the details of the first volume on Windows system:
163+
164+
```powershell
165+
PS C:\Users\Administrator> $vol = (Get-CimInstance -Namespace "Root\Microsoft\Windows\Storage" -Query "SELECT * FROM MSFT_Volume")[0]
166+
PS C:\Users\Administrator> $vol
167+
168+
ObjectId : {1}\\WIN-8E2EVAQ9QSB\root/Microsoft/Windows/Storage/Providers_v2\WSP_Volume.ObjectId=
169+
"{b65bb3cd-da86-11ee-854b-806e6f6e6963}:VO:\\?\Volume{1781d1eb-2c0a-47ed-987f-c229b9c
170+
02527}\"
171+
PassThroughClass :
172+
PassThroughIds :
173+
PassThroughNamespace :
174+
PassThroughServer :
175+
UniqueId : \\?\Volume{1781d1eb-2c0a-47ed-987f-c229b9c02527}\
176+
AllocationUnitSize : 4096
177+
DedupMode : 4
178+
DriveLetter : C
179+
DriveType : 3
180+
FileSystem : NTFS
181+
FileSystemLabel :
182+
FileSystemType : 14
183+
HealthStatus : 1
184+
OperationalStatus : {53261}
185+
Path : \\?\Volume{1781d1eb-2c0a-47ed-987f-c229b9c02527}\
186+
Size : 536198770688
187+
SizeRemaining : 407553982464
188+
PSComputerName :
189+
```
190+
191+
Then you may use `obj.FileSystem` to get the file system of the volume.
192+
193+
```powershell
194+
PS C:\Users\Administrator> $vol.FileSystem
195+
NTFS
196+
```
197+
198+
### Association
199+
200+
```powershell
201+
PS C:\Users\Administrator> $partition = (Get-CimInstance -Namespace root\Microsoft\Windows\Storage -ClassName MSFT_Partition -Filter "DiskNumber = 0")[0]
202+
PS C:\Users\Administrator> Get-CimAssociatedInstance -InputObject $partition -Association MSFT_PartitionToVolume
203+
```
204+
205+
### Call Class Method
206+
207+
You may get Class Methods for a single CIM class using `$class.CimClassMethods`.
208+
209+
```powershell
210+
211+
PS C:\Users\Administrator> $class = Get-CimClass -ClassName MSFT_StorageSetting -Namespace "Root\Microsoft\Windows\Storage"
212+
PS C:\Users\Administrator> $class.CimClassMethods
213+
214+
Name ReturnType Parameters Qualifiers
215+
---- ---------- ---------- ----------
216+
Get UInt32 {StorageSetting} {implemented, static}
217+
Set UInt32 {NewDiskPolicy, ScrubPolicy} {implemented, static}
218+
UpdateHostStorageCache UInt32 {} {implemented, static}
219+
```
220+
221+
You may use `Invoke-CimMethod` to invoke those static methods on the `CimClass` object.
222+
223+
```powershell
224+
PS C:\Users\Administrator> Invoke-CimMethod -CimClass $class -MethodName UpdateHostStorageCache @{}
225+
226+
ReturnValue PSComputerName
227+
----------- --------------
228+
0
229+
230+
```

docs/WMI.png

1.75 MB
Loading

go.mod

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
module github.com/kubernetes-csi/csi-proxy/v2
22

3-
go 1.16
3+
go 1.22
44

55
require (
6-
github.com/davecgh/go-spew v1.1.1 // indirect
7-
github.com/stretchr/testify v1.5.1
6+
github.com/go-ole/go-ole v1.3.0
7+
github.com/microsoft/wmi v0.25.1
8+
github.com/pkg/errors v0.9.1
9+
github.com/stretchr/testify v1.7.0
10+
golang.org/x/sys v0.25.0
811
k8s.io/klog/v2 v2.9.0
912
)
13+
14+
require (
15+
github.com/davecgh/go-spew v1.1.1 // indirect
16+
github.com/go-logr/logr v0.4.0 // indirect
17+
github.com/pmezard/go-difflib v1.0.0 // indirect
18+
gopkg.in/yaml.v3 v3.0.0 // indirect
19+
)

go.sum

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
33
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
55
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
6+
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
7+
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
8+
github.com/microsoft/wmi v0.25.1 h1:sQv9hCEHtW5K6yEVL78T6XGRMGxk4aTpcJwCiB5rLN0=
9+
github.com/microsoft/wmi v0.25.1/go.mod h1:1zbdSF0A+5OwTUII5p3hN7/K6KF2m3o27pSG6Y51VU8=
10+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
11+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
612
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
713
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
814
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9-
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
10-
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
15+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
16+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
17+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18+
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
19+
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
1120
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1221
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13-
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
14-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
22+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23+
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
24+
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1525
k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=
1626
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=

pkg/cim/disk.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
const (
15+
// PartitionStyleUnknown indicates an unknown partition table format
16+
PartitionStyleUnknown = 0
17+
// PartitionStyleGPT indicates the disk uses GUID Partition Table (GPT) format
18+
PartitionStyleGPT = 2
19+
20+
// GPTPartitionTypeBasicData is the GUID for basic data partitions in GPT
21+
// Used for general purpose storage partitions
22+
GPTPartitionTypeBasicData = "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}"
23+
// GPTPartitionTypeMicrosoftReserved is the GUID for Microsoft Reserved Partition (MSR)
24+
// Reserved by Windows for system use
25+
GPTPartitionTypeMicrosoftReserved = "{e3c9e316-0b5c-4db8-817d-f92df00215ae}"
26+
27+
// ErrorCodeCreatePartitionAccessPathAlreadyInUse is the error code (42002) returned when the driver letter failed to assign after partition created
28+
ErrorCodeCreatePartitionAccessPathAlreadyInUse = 42002
29+
)
30+
31+
// QueryDiskByNumber retrieves disk information for a specific disk identified by its number.
32+
//
33+
// The equivalent WMI query is:
34+
//
35+
// SELECT [selectors] FROM MSFT_Disk
36+
// WHERE DiskNumber = '<diskNumber>'
37+
//
38+
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk
39+
// for the WMI class definition.
40+
func QueryDiskByNumber(diskNumber uint32, selectorList []string) (*storage.MSFT_Disk, error) {
41+
diskQuery := query.NewWmiQueryWithSelectList("MSFT_Disk", selectorList, "Number", strconv.Itoa(int(diskNumber)))
42+
instances, err := QueryInstances(WMINamespaceStorage, diskQuery)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
disk, err := storage.NewMSFT_DiskEx1(instances[0])
48+
if err != nil {
49+
return nil, fmt.Errorf("failed to query disk %d. error: %v", diskNumber, err)
50+
}
51+
52+
return disk, nil
53+
}
54+
55+
// RefreshDisk Refreshes the cached disk layout information.
56+
//
57+
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-refresh
58+
// for the WMI method definition.
59+
func RefreshDisk(disk *storage.MSFT_Disk) (int, string, error) {
60+
var status string
61+
result, err := disk.InvokeMethodWithReturn("Refresh", &status)
62+
return int(result), status, err
63+
}

0 commit comments

Comments
 (0)