Skip to content

Commit 441b4a3

Browse files
ivanLeontk8s-infra-cherrypick-robot
authored andcommitted
Added support for NVME Hyperdisks on Windows Nodes
1 parent 72d375e commit 441b4a3

File tree

3 files changed

+424
-0
lines changed

3 files changed

+424
-0
lines changed

pkg/mount-manager/safe-mounter-v1_windows.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ package mountmanager
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"errors"
2223
"fmt"
24+
"io"
25+
"net/http"
2326
"os"
2427
"path/filepath"
2528
"strconv"
2629
"strings"
30+
"time"
2731

2832
diskapi "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1"
2933
diskclient "github.com/kubernetes-csi/csi-proxy/client/groups/disk/v1"
@@ -38,6 +42,16 @@ import (
3842
mount "k8s.io/mount-utils"
3943
)
4044

45+
// GoogleCloudDisk represents a disk from Google Cloud metadata
46+
type GoogleCloudDisk struct {
47+
DeviceName string `json:"deviceName"`
48+
Index int `json:"index"`
49+
Interface string `json:"interface"`
50+
Mode string `json:"mode"`
51+
NvmeNamespaceIdentifier uint64 `json:"nvmeNamespaceIdentifier"`
52+
Type string `json:"type"`
53+
}
54+
4155
// CSIProxyMounterV1 is the mounter implementation that uses the v1 API
4256
type CSIProxyMounterV1 struct {
4357
FsClient *fsclient.Client
@@ -182,6 +196,197 @@ func (mounter *CSIProxyMounterV1) Unmount(target string) error {
182196
}
183197

184198
func (mounter *CSIProxyMounterV1) GetDiskNumber(deviceName string, partition string, volumeKey string) (string, error) {
199+
// First, get Google Cloud metadata to find the nvmeNamespaceIdentifier for this device
200+
googleDisks, err := mounter.getGoogleCloudDisks()
201+
if err != nil {
202+
klog.V(4).Infof("Failed to get Google Cloud metadata, falling back to legacy method: %v", err)
203+
return mounter.getDiskNumberLegacy(deviceName)
204+
}
205+
206+
// Find the nvmeNamespaceIdentifier for the given deviceName
207+
var targetNamespaceId uint64
208+
var diskInterface string
209+
found := false
210+
for _, disk := range googleDisks {
211+
if disk.DeviceName == deviceName {
212+
targetNamespaceId = disk.NvmeNamespaceIdentifier
213+
diskInterface = disk.Interface
214+
found = true
215+
klog.V(4).Infof("Found target namespace ID %d for device %s with interface %s", targetNamespaceId, deviceName, diskInterface)
216+
break
217+
}
218+
}
219+
220+
if !found {
221+
klog.V(4).Infof("Device %s not found in Google Cloud metadata, falling back to legacy method", deviceName)
222+
return mounter.getDiskNumberLegacy(deviceName)
223+
}
224+
225+
// Check if device is NVME - if not, use legacy method
226+
if diskInterface != "NVME" {
227+
klog.V(4).Infof("Device %s is %s interface (not NVME), falling back to legacy method", deviceName, diskInterface)
228+
return mounter.getDiskNumberLegacy(deviceName)
229+
}
230+
231+
// Get Windows disk information
232+
listRequest := &diskapi.ListDiskIDsRequest{}
233+
diskIDsResponse, err := mounter.DiskClient.ListDiskIDs(context.Background(), listRequest)
234+
if err != nil {
235+
return "", err
236+
}
237+
diskIDsMap := diskIDsResponse.GetDiskIDs()
238+
239+
// Iterate through Windows disks and convert EUI to decimal for matching
240+
for diskNum, diskInfo := range diskIDsMap {
241+
klog.V(4).Infof("found disk number %d, disk info %v", diskNum, diskInfo)
242+
243+
// Check if this disk has an EUI identifier
244+
euiValue := mounter.extractEUIFromDiskInfo(diskInfo)
245+
if euiValue == "" {
246+
continue
247+
}
248+
249+
// Convert EUI hex to decimal
250+
decimalValue, err := mounter.convertEUIToDecimal(euiValue)
251+
if err != nil {
252+
klog.V(4).Infof("Failed to convert EUI %s to decimal: %v", euiValue, err)
253+
continue
254+
}
255+
256+
klog.V(4).Infof("Disk %d: EUI %s converts to decimal %d", diskNum, euiValue, decimalValue)
257+
258+
// Check if this matches our target namespace identifier
259+
if decimalValue == targetNamespaceId {
260+
klog.V(4).Infof("Found matching disk: Windows disk %d matches Google namespace ID %d", diskNum, targetNamespaceId)
261+
return strconv.FormatUint(uint64(diskNum), 10), nil
262+
}
263+
}
264+
265+
// Final fallback: if NVME matching failed, try legacy method
266+
klog.V(4).Infof("Could not find NVME match for device %s with namespace ID %d, falling back to legacy method", deviceName, targetNamespaceId)
267+
return mounter.getDiskNumberLegacy(deviceName)
268+
}
269+
270+
// Helper function to extract EUI from disk info (v1 API format)
271+
func (mounter *CSIProxyMounterV1) extractEUIFromDiskInfo(diskInfo *diskapi.DiskIDs) string {
272+
klog.V(4).Infof("extractEUIFromDiskInfo called for disk with Page83=%s, SerialNumber=%s", diskInfo.Page83, diskInfo.SerialNumber)
273+
274+
// Check if Page83 contains an EUI format
275+
if diskInfo.Page83 != "" && strings.HasPrefix(diskInfo.Page83, "eui.") {
276+
klog.V(4).Infof("Found EUI in Page83: %s", diskInfo.Page83)
277+
return diskInfo.Page83
278+
}
279+
280+
// For NVMe disks, check SerialNumber field and convert to EUI format
281+
if diskInfo.SerialNumber != "" {
282+
klog.V(4).Infof("Attempting to convert serial number %s to EUI", diskInfo.SerialNumber)
283+
// Convert serial number format like "10CC_9636_B6E3_CE3B_0000_0000_0000_0000." to EUI format
284+
eui := mounter.convertSerialToEUI(diskInfo.SerialNumber)
285+
if eui != "" {
286+
klog.V(4).Infof("Successfully converted serial number %s to EUI %s", diskInfo.SerialNumber, eui)
287+
return eui
288+
} else {
289+
klog.V(4).Infof("Failed to convert serial number %s to EUI", diskInfo.SerialNumber)
290+
}
291+
} else {
292+
klog.V(4).Infof("No serial number found for disk")
293+
}
294+
295+
klog.V(4).Infof("No EUI found for disk")
296+
return ""
297+
}
298+
299+
// Helper function to convert serial number to EUI format
300+
func (mounter *CSIProxyMounterV1) convertSerialToEUI(serialNumber string) string {
301+
klog.V(4).Infof("convertSerialToEUI: input=%s", serialNumber)
302+
303+
// Remove trailing period and underscores from serial number
304+
// Format: "10CC_9636_B6E3_CE3B_0000_0000_0000_0000." -> "10CC9636B6E3CE3B0000000000000000"
305+
cleaned := strings.TrimSuffix(serialNumber, ".")
306+
cleaned = strings.ReplaceAll(cleaned, "_", "")
307+
klog.V(4).Infof("convertSerialToEUI: cleaned=%s, length=%d", cleaned, len(cleaned))
308+
309+
// Validate that it's a valid hex string of expected length
310+
if len(cleaned) != 32 {
311+
klog.V(4).Infof("convertSerialToEUI: invalid length %d, expected 32", len(cleaned))
312+
return ""
313+
}
314+
315+
// Check if it's valid hex (just test parsing the first 16 chars to avoid overflow)
316+
if _, err := strconv.ParseUint(cleaned[:16], 16, 64); err != nil {
317+
klog.V(4).Infof("convertSerialToEUI: invalid hex in first 16 chars: %v", err)
318+
return ""
319+
}
320+
321+
// Return in EUI format
322+
result := "eui." + cleaned
323+
klog.V(4).Infof("convertSerialToEUI: result=%s", result)
324+
return result
325+
}
326+
327+
// Helper function to convert EUI hex to decimal
328+
func (mounter *CSIProxyMounterV1) convertEUIToDecimal(euiValue string) (uint64, error) {
329+
// Extract hex part from EUI (first 16 characters after "eui.")
330+
if !strings.HasPrefix(euiValue, "eui.") {
331+
return 0, fmt.Errorf("invalid EUI format: %s", euiValue)
332+
}
333+
334+
hexPart := strings.TrimPrefix(euiValue, "eui.")
335+
if len(hexPart) < 16 {
336+
return 0, fmt.Errorf("EUI hex part too short: %s", hexPart)
337+
}
338+
339+
// Take first 16 hex characters
340+
hexPart = hexPart[:16]
341+
342+
// Convert to decimal
343+
decimalValue, err := strconv.ParseUint(hexPart, 16, 64)
344+
if err != nil {
345+
return 0, fmt.Errorf("failed to parse hex %s: %v", hexPart, err)
346+
}
347+
348+
return decimalValue, nil
349+
}
350+
351+
// Helper function to get Google Cloud metadata
352+
func (mounter *CSIProxyMounterV1) getGoogleCloudDisks() ([]GoogleCloudDisk, error) {
353+
client := &http.Client{
354+
Timeout: 10 * time.Second,
355+
}
356+
357+
req, err := http.NewRequest("GET", "http://metadata.google.internal/computeMetadata/v1/instance/disks/?recursive=true", nil)
358+
if err != nil {
359+
return nil, fmt.Errorf("failed to create request: %v", err)
360+
}
361+
362+
req.Header.Set("Metadata-Flavor", "Google")
363+
364+
resp, err := client.Do(req)
365+
if err != nil {
366+
return nil, fmt.Errorf("failed to call metadata service: %v", err)
367+
}
368+
defer resp.Body.Close()
369+
370+
if resp.StatusCode != http.StatusOK {
371+
return nil, fmt.Errorf("metadata service returned status %d", resp.StatusCode)
372+
}
373+
374+
body, err := io.ReadAll(resp.Body)
375+
if err != nil {
376+
return nil, fmt.Errorf("failed to read response body: %v", err)
377+
}
378+
379+
var disks []GoogleCloudDisk
380+
if err := json.Unmarshal(body, &disks); err != nil {
381+
return nil, fmt.Errorf("failed to parse JSON response: %v", err)
382+
}
383+
384+
klog.V(4).Infof("Retrieved %d disks from Google Cloud metadata", len(disks))
385+
return disks, nil
386+
}
387+
388+
// Legacy method for backward compatibility
389+
func (mounter *CSIProxyMounterV1) getDiskNumberLegacy(deviceName string) (string, error) {
185390
listRequest := &diskapi.ListDiskIDsRequest{}
186391
diskIDsResponse, err := mounter.DiskClient.ListDiskIDs(context.Background(), listRequest)
187392
if err != nil {

0 commit comments

Comments
 (0)