Skip to content

Commit 4e9c271

Browse files
committed
feat: Use WMI to implement Volume API to reduce PowerShell overhead
chore: update fix remove New-SmbGlobalMapping fix header fix fix vendor
1 parent b3c724c commit 4e9c271

File tree

12 files changed

+230
-162
lines changed

12 files changed

+230
-162
lines changed

pkg/azurefile/azurefile.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ type Driver struct {
253253
appendActimeoOption bool
254254
printVolumeStatsCallLogs bool
255255
enableKataCCMount bool
256+
useWinCIMAPI bool
256257
mounter *mount.SafeFormatAndMount
257258
server *grpc.Server
258259
// lock per volume attach (only for vhd disk feature)
@@ -342,6 +343,7 @@ func NewDriver(options *DriverOptions) *Driver {
342343
driver.resolver = new(NetResolver)
343344
driver.directVolume = new(directVolume)
344345
driver.isKataNode = false
346+
driver.useWinCIMAPI = options.UseWinCIMAPI
345347

346348
var err error
347349
getter := func(_ context.Context, _ string) (interface{}, error) { return nil, nil }
@@ -416,7 +418,7 @@ func (d *Driver) Run(ctx context.Context) error {
416418
}
417419
klog.V(2).Infof("cloud: %s, location: %s, rg: %s, VnetName: %s, VnetResourceGroup: %s, SubnetName: %s", d.cloud.Cloud, d.cloud.Location, d.cloud.ResourceGroup, d.cloud.VnetName, d.cloud.VnetResourceGroup, d.cloud.SubnetName)
418420

419-
d.mounter, err = mounter.NewSafeMounter(d.enableWindowsHostProcess)
421+
d.mounter, err = mounter.NewSafeMounter(d.enableWindowsHostProcess, d.useWinCIMAPI)
420422
if err != nil {
421423
klog.Fatalf("Failed to get safe mounter. Error: %v", err)
422424
}

pkg/azurefile/azurefile_options.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type DriverOptions struct {
4545
AppendNoShareSockOption bool
4646
AppendNoResvPortOption bool
4747
AppendActimeoOption bool
48+
UseWinCIMAPI bool
4849
SkipMatchingTagCacheExpireInMinutes int
4950
VolStatsCacheExpireInMinutes int
5051
PrintVolumeStatsCallLogs bool
@@ -85,6 +86,7 @@ func (o *DriverOptions) AddFlags() *flag.FlagSet {
8586
fs.BoolVar(&o.AppendNoShareSockOption, "append-nosharesock-option", true, "Whether appending nosharesock option to smb mount command")
8687
fs.BoolVar(&o.AppendNoResvPortOption, "append-noresvport-option", true, "Whether appending noresvport option to nfs mount command")
8788
fs.BoolVar(&o.AppendActimeoOption, "append-actimeo-option", true, "Whether appending actimeo=0 option to nfs mount command")
89+
fs.BoolVar(&o.UseWinCIMAPI, "use-win-cim-api", true, "Whether performing azure file operations using CIM API or Powershell command on Windows node")
8890
fs.IntVar(&o.SkipMatchingTagCacheExpireInMinutes, "skip-matching-tag-cache-expire-in-minutes", 30, "The cache expire time in minutes for skipMatchingTagCache")
8991
fs.IntVar(&o.VolStatsCacheExpireInMinutes, "vol-stats-cache-expire-in-minutes", 10, "The cache expire time in minutes for volume stats cache")
9092
fs.BoolVar(&o.PrintVolumeStatsCallLogs, "print-volume-stats-call-logs", false, "Whether to print volume statfs call logs with log level 2")

pkg/azurefile/fake_mounter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (f *fakeMounter) IsMountPoint(file string) (bool, error) {
7474
// NewFakeMounter fake mounter
7575
func NewFakeMounter() (*mount.SafeFormatAndMount, error) {
7676
if runtime.GOOS == "windows" {
77-
return mounter.NewSafeMounter(true)
77+
return mounter.NewSafeMounter(true, true)
7878
}
7979
return &mount.SafeFormatAndMount{
8080
Interface: &fakeMounter{},

pkg/mounter/safe_mounter_host_process_windows.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,20 @@ var driverGlobalMountPath = "C:\\var\\lib\\kubelet\\plugins\\kubernetes.io\\csi\
3737

3838
var _ CSIProxyMounter = &winMounter{}
3939

40-
type winMounter struct{}
40+
type winMounter struct {
41+
smbAPI smb.SMBAPI
42+
}
4143

42-
func NewWinMounter() *winMounter {
43-
return &winMounter{}
44+
func NewWinMounter(useWinCIMAPI bool) *winMounter {
45+
var smbAPI smb.SMBAPI
46+
if useWinCIMAPI {
47+
smbAPI = smb.NewCimSMBAPI()
48+
} else {
49+
smbAPI = smb.NewPowerShellSMBAPI()
50+
}
51+
return &winMounter{
52+
smbAPI: smbAPI,
53+
}
4454
}
4555

4656
func (mounter *winMounter) SMBMount(source, target, fsType string, mountOptions, sensitiveMountOptions []string) error {
@@ -75,7 +85,7 @@ func (mounter *winMounter) SMBMount(source, target, fsType string, mountOptions,
7585
return fmt.Errorf("remote path is empty")
7686
}
7787

78-
isMapped, err := smb.IsSmbMapped(remotePath)
88+
isMapped, err := mounter.smbAPI.IsSmbMapped(remotePath)
7989
if err != nil {
8090
isMapped = false
8191
}
@@ -88,7 +98,7 @@ func (mounter *winMounter) SMBMount(source, target, fsType string, mountOptions,
8898

8999
if !valid {
90100
klog.Warningf("RemotePath %s is not valid, removing now", remotePath)
91-
if err := smb.RemoveSmbGlobalMapping(remotePath); err != nil {
101+
if err := mounter.smbAPI.RemoveSmbGlobalMapping(remotePath); err != nil {
92102
klog.Errorf("RemoveSmbGlobalMapping(%s) failed with %v", remotePath, err)
93103
return err
94104
}
@@ -100,7 +110,7 @@ func (mounter *winMounter) SMBMount(source, target, fsType string, mountOptions,
100110
klog.V(2).Infof("Remote %s not mapped. Mapping now!", remotePath)
101111
username := mountOptions[0]
102112
password := sensitiveMountOptions[0]
103-
if err := smb.NewSmbGlobalMapping(remotePath, username, password); err != nil {
113+
if err := mounter.smbAPI.NewSmbGlobalMapping(remotePath, username, password); err != nil {
104114
klog.Errorf("NewSmbGlobalMapping(%s) failed with %v", remotePath, err)
105115
return err
106116
}
@@ -136,7 +146,7 @@ func (mounter *winMounter) Unmount(target string) error {
136146
klog.V(2).Infof("remote server path: %s, local path: %s", remoteServer, target)
137147
if hasDupSMBMount, err := smb.CheckForDuplicateSMBMounts(driverGlobalMountPath, target, remoteServer); err == nil {
138148
if !hasDupSMBMount {
139-
if err := smb.RemoveSmbGlobalMapping(remoteServer); err != nil {
149+
if err := mounter.smbAPI.RemoveSmbGlobalMapping(remoteServer); err != nil {
140150
klog.Errorf("RemoveSmbGlobalMapping(%s) failed with %v", target, err)
141151
}
142152
} else {

pkg/mounter/safe_mounter_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
utilexec "k8s.io/utils/exec"
2525
)
2626

27-
func NewSafeMounter(_ bool) (*mount.SafeFormatAndMount, error) {
27+
func NewSafeMounter(_, _ bool) (*mount.SafeFormatAndMount, error) {
2828
return &mount.SafeFormatAndMount{
2929
Interface: mount.New(""),
3030
Exec: utilexec.New(),

pkg/mounter/safe_mounter_unix_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
)
2424

2525
func TestNewSafeMounter(t *testing.T) {
26-
resp, err := NewSafeMounter(true)
26+
resp, err := NewSafeMounter(true, true)
2727
assert.NotNil(t, resp)
2828
assert.Nil(t, err)
2929
}

pkg/mounter/safe_mounter_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,11 @@ func NewCSIProxyMounter() (*csiProxyMounter, error) {
291291
}, nil
292292
}
293293

294-
func NewSafeMounter(enableWindowsHostProcess bool) (*mount.SafeFormatAndMount, error) {
294+
func NewSafeMounter(enableWindowsHostProcess, useWinCIMAPI bool) (*mount.SafeFormatAndMount, error) {
295295
if enableWindowsHostProcess {
296296
klog.V(2).Infof("using windows host process mounter")
297297
return &mount.SafeFormatAndMount{
298-
Interface: NewWinMounter(),
298+
Interface: NewWinMounter(useWinCIMAPI),
299299
Exec: utilexec.New(),
300300
}, nil
301301
}

pkg/os/cim/smb.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//go:build windows
2+
// +build windows
3+
4+
/*
5+
Copyright 2025 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package cim
21+
22+
import (
23+
"github.com/microsoft/wmi/pkg/base/query"
24+
cim "github.com/microsoft/wmi/pkg/wmiinstance"
25+
)
26+
27+
// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/smb/msft-smbmapping
28+
const (
29+
SmbMappingStatusOK int32 = iota
30+
SmbMappingStatusPaused
31+
SmbMappingStatusDisconnected
32+
SmbMappingStatusNetworkError
33+
SmbMappingStatusConnecting
34+
SmbMappingStatusReconnecting
35+
SmbMappingStatusUnavailable
36+
)
37+
38+
// QuerySmbGlobalMappingByRemotePath retrieves the SMB global mapping from its remote path.
39+
//
40+
// The equivalent WMI query is:
41+
//
42+
// SELECT [selectors] FROM MSFT_SmbGlobalMapping
43+
//
44+
// Refer to https://pkg.go.dev/github.com/microsoft/wmi/server2019/root/microsoft/windows/smb#MSFT_SmbGlobalMapping
45+
// for the WMI class definition.
46+
func QuerySmbGlobalMappingByRemotePath(remotePath string) (*cim.WmiInstance, error) {
47+
smbQuery := query.NewWmiQuery("MSFT_SmbGlobalMapping", "RemotePath", remotePath)
48+
instances, err := QueryInstances(WMINamespaceSmb, smbQuery)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
return instances[0], err
54+
}
55+
56+
// RemoveSmbGlobalMappingByRemotePath removes a SMB global mapping matching to the remote path.
57+
//
58+
// Refer to https://pkg.go.dev/github.com/microsoft/wmi/server2019/root/microsoft/windows/smb#MSFT_SmbGlobalMapping
59+
// for the WMI class definition.
60+
func RemoveSmbGlobalMappingByRemotePath(remotePath string) error {
61+
smbQuery := query.NewWmiQuery("MSFT_SmbGlobalMapping", "RemotePath", remotePath)
62+
instances, err := QueryInstances(WMINamespaceSmb, smbQuery)
63+
if err != nil {
64+
return err
65+
}
66+
67+
_, err = instances[0].InvokeMethod("Remove", true)
68+
return err
69+
}

pkg/os/cim/wmi.go

Lines changed: 5 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@ package cim
2121

2222
import (
2323
"fmt"
24-
"log"
25-
"strings"
2624

2725
"github.com/go-ole/go-ole"
2826
"github.com/go-ole/go-ole/oleutil"
2927
"github.com/microsoft/wmi/pkg/base/query"
3028
"github.com/microsoft/wmi/pkg/errors"
3129
cim "github.com/microsoft/wmi/pkg/wmiinstance"
30+
"k8s.io/klog/v2"
3231
)
3332

3433
const (
3534
WMINamespaceRoot = "Root\\CimV2"
3635
WMINamespaceStorage = "Root\\Microsoft\\Windows\\Storage"
36+
WMINamespaceSmb = "Root\\Microsoft\\Windows\\Smb"
3737
)
3838

3939
type InstanceHandler func(instance *cim.WmiInstance) (bool, error)
@@ -108,8 +108,9 @@ func QueryInstances(namespace string, query *query.WmiQuery) ([]*cim.WmiInstance
108108
}
109109

110110
// TODO: fix the panic in microsoft/wmi library and remove this workaround
111+
// Refer to https://github.com/microsoft/wmi/issues/167
111112
func executeClassMethodParam(classInst *cim.WmiInstance, method *cim.WmiMethod, inParam, outParam cim.WmiMethodParamCollection) (result *cim.WmiMethodResult, err error) {
112-
log.Printf("[WMI] - Executing Method [%s]\n", method.Name)
113+
klog.V(6).Infof("[WMI] - Executing Method [%s]\n", method.Name)
113114

114115
iDispatchInstance := classInst.GetIDispatch()
115116
if iDispatchInstance == nil {
@@ -162,7 +163,6 @@ func executeClassMethodParam(classInst *cim.WmiInstance, method *cim.WmiMethod,
162163
defer inparams.Clear()
163164

164165
for _, inp := range inParam {
165-
// log.Printf("InParam [%s]=>[%+v]\n", inp.Name, inp.Value)
166166
addInParam(inparams, inp.Name, inp.Value)
167167
}
168168

@@ -184,7 +184,7 @@ func executeClassMethodParam(classInst *cim.WmiInstance, method *cim.WmiMethod,
184184
defer returnRaw.Clear()
185185
if returnRaw.Value() != nil {
186186
result.ReturnValue = returnRaw.Value().(int32)
187-
log.Printf("[WMI] - Return [%d] ", result.ReturnValue)
187+
klog.V(6).Infof("[WMI] - Return [%d] ", result.ReturnValue)
188188
}
189189

190190
for _, outp := range outParam {
@@ -200,7 +200,6 @@ func executeClassMethodParam(classInst *cim.WmiInstance, method *cim.WmiMethod,
200200
err = err1
201201
return
202202
}
203-
// log.Printf("OutParam [%s]=> [%+v]\n", outp.Name, value)
204203

205204
result.OutMethodParams[outp.Name] = cim.NewWmiMethodParam(outp.Name, value)
206205
}
@@ -263,122 +262,3 @@ func IgnoreNotFound(err error) error {
263262
}
264263
return err
265264
}
266-
267-
// parseObjectRef extracts the object ID from a WMI object reference string.
268-
// The result string is in this format
269-
// {1}\\WIN-8E2EVAQ9QSB\ROOT/Microsoft/Windows/Storage/Providers_v2\WSP_Partition.ObjectId="{b65bb3cd-da86-11ee-854b-806e6f6e6963}:PR:{00000000-0000-0000-0000-100000000000}\\?\scsi#disk&ven_vmware&prod_virtual_disk#4&2c28f6c4&0&000000#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}"
270-
// from an escape string
271-
func parseObjectRef(input, objectClass, refName string) (string, error) {
272-
tokens := strings.Split(input, fmt.Sprintf("%s.%s=", objectClass, refName))
273-
if len(tokens) < 2 {
274-
return "", fmt.Errorf("invalid object ID value: %s", input)
275-
}
276-
277-
objectID := tokens[1]
278-
objectID = strings.ReplaceAll(objectID, "\\\"", "\"")
279-
objectID = strings.ReplaceAll(objectID, "\\\\", "\\")
280-
objectID = objectID[1 : len(objectID)-1]
281-
return objectID, nil
282-
}
283-
284-
// ListWMIInstanceMappings queries WMI instances and creates a map using custom indexing functions
285-
// to extract keys and values from each instance.
286-
func ListWMIInstanceMappings(namespace, mappingClassName string, selectorList []string, keyIndexer InstanceIndexer, valueIndexer InstanceIndexer) (map[string]string, error) {
287-
q := query.NewWmiQueryWithSelectList(mappingClassName, selectorList)
288-
mappingInstances, err := QueryInstances(namespace, q)
289-
if err != nil {
290-
return nil, err
291-
}
292-
293-
result := make(map[string]string)
294-
for _, mapping := range mappingInstances {
295-
key, err := keyIndexer(mapping)
296-
if err != nil {
297-
return nil, err
298-
}
299-
300-
value, err := valueIndexer(mapping)
301-
if err != nil {
302-
return nil, err
303-
}
304-
305-
result[key] = value
306-
}
307-
308-
return result, nil
309-
}
310-
311-
// FindInstancesByMapping filters instances based on a mapping relationship,
312-
// matching instances through custom indexing and mapping functions.
313-
func FindInstancesByMapping(instanceToFind []*cim.WmiInstance, instanceToFindIndex InstanceIndexer, associatedInstances []*cim.WmiInstance, associatedInstanceIndexer InstanceIndexer, instanceMappings map[string]string) ([]*cim.WmiInstance, error) {
314-
associatedInstanceObjectIDMapping := map[string]*cim.WmiInstance{}
315-
for _, inst := range associatedInstances {
316-
key, err := associatedInstanceIndexer(inst)
317-
if err != nil {
318-
return nil, err
319-
}
320-
321-
associatedInstanceObjectIDMapping[key] = inst
322-
}
323-
324-
var filtered []*cim.WmiInstance
325-
for _, inst := range instanceToFind {
326-
key, err := instanceToFindIndex(inst)
327-
if err != nil {
328-
return nil, err
329-
}
330-
331-
valueObjectID, ok := instanceMappings[key]
332-
if !ok {
333-
continue
334-
}
335-
336-
_, ok = associatedInstanceObjectIDMapping[strings.ToUpper(valueObjectID)]
337-
if !ok {
338-
continue
339-
}
340-
filtered = append(filtered, inst)
341-
}
342-
343-
if len(filtered) == 0 {
344-
return nil, errors.NotFound
345-
}
346-
347-
return filtered, nil
348-
}
349-
350-
// mappingObjectRefIndexer indexes an WMI object by the Object ID reference from a specified property.
351-
func mappingObjectRefIndexer(propertyName, className, refName string) InstanceIndexer {
352-
return func(instance *cim.WmiInstance) (string, error) {
353-
valueVal, err := instance.GetProperty(propertyName)
354-
if err != nil {
355-
return "", err
356-
}
357-
358-
refValue, err := parseObjectRef(valueVal.(string), className, refName)
359-
return strings.ToUpper(refValue), err
360-
}
361-
}
362-
363-
// stringPropertyIndexer indexes a WMI object from a string property.
364-
func stringPropertyIndexer(propertyName string) InstanceIndexer {
365-
return func(instance *cim.WmiInstance) (string, error) {
366-
valueVal, err := instance.GetProperty(propertyName)
367-
if err != nil {
368-
return "", err
369-
}
370-
371-
return strings.ToUpper(valueVal.(string)), err
372-
}
373-
}
374-
375-
var (
376-
// objectIDPropertyIndexer indexes a WMI object from its ObjectId property.
377-
objectIDPropertyIndexer = stringPropertyIndexer("ObjectId")
378-
)
379-
380-
// FindInstancesByObjectIDMapping filters instances based on ObjectId mapping
381-
// between two sets of WMI instances.
382-
func FindInstancesByObjectIDMapping(instanceToFind []*cim.WmiInstance, associatedInstances []*cim.WmiInstance, instanceMappings map[string]string) ([]*cim.WmiInstance, error) {
383-
return FindInstancesByMapping(instanceToFind, objectIDPropertyIndexer, associatedInstances, objectIDPropertyIndexer, instanceMappings)
384-
}

0 commit comments

Comments
 (0)