From ddeaa66797e11c712c7e1da4c0778bf3d29e1757 Mon Sep 17 00:00:00 2001 From: Harshal Patel Date: Tue, 30 Jun 2026 18:39:19 +0530 Subject: [PATCH] internal/hns: preserve HRESULT in typed HNSError Previously, hnsCall swallowed the specific ErrorCode/HRESULT returned by the Host Networking Service into a flat string. This prevented downstream callers (like containerd's CRI plugin) from using errors.As() to detect retryable or transient states (e.g., vSwitch restarting). This change introduces a strongly-typed HNSError that preserves the ErrorCode, allowing callers to correctly handle specific HNS failure modes. Signed-off-by: Harshal Patel --- internal/hns/hnsfuncs.go | 18 +++++++++++++-- internal/hns/hnsfuncs_test.go | 42 +++++++++++++++++++++++++++++++++++ internal/hns/hnsnetwork.go | 7 +++--- 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 internal/hns/hnsfuncs_test.go diff --git a/internal/hns/hnsfuncs.go b/internal/hns/hnsfuncs.go index e61dc8de62..2240013cb6 100644 --- a/internal/hns/hnsfuncs.go +++ b/internal/hns/hnsfuncs.go @@ -28,13 +28,27 @@ func hnsCallRawResponse(method, path, request string) (*hnsResponse, error) { return hnsresponse, nil } +type HNSError struct { + ErrorString string + ErrorCode uint32 +} + +func (e *HNSError) Error() string { + return fmt.Sprintf("hns failed with error : %s", e.ErrorString) +} + +var hnsCallRawResponseMock = hnsCallRawResponse + func hnsCall(method, path, request string, returnResponse interface{}) error { - hnsresponse, err := hnsCallRawResponse(method, path, request) + hnsresponse, err := hnsCallRawResponseMock(method, path, request) if err != nil { return fmt.Errorf("failed during hnsCallRawResponse: %w", err) } if !hnsresponse.Success { - return fmt.Errorf("hns failed with error : %s", hnsresponse.Error) + return &HNSError{ + ErrorString: hnsresponse.Error, + ErrorCode: hnsresponse.ErrorCode, + } } if len(hnsresponse.Output) == 0 { diff --git a/internal/hns/hnsfuncs_test.go b/internal/hns/hnsfuncs_test.go new file mode 100644 index 0000000000..b4b2478dc1 --- /dev/null +++ b/internal/hns/hnsfuncs_test.go @@ -0,0 +1,42 @@ +//go:build windows + +package hns + +import ( + "errors" + "testing" +) + +func TestHNSErrorPreservation(t *testing.T) { + // Mock the raw response + originalMock := hnsCallRawResponseMock + defer func() { hnsCallRawResponseMock = originalMock }() + + hnsCallRawResponseMock = func(method, path, request string) (*hnsResponse, error) { + return &hnsResponse{ + Success: false, + Error: "vSwitch in transient state", + ErrorCode: 0x803b0013, // HCS_E_VMSWITCH_IN_TRANSIENT_STATE + }, nil + } + + // Execute hnsCall + err := hnsCall("POST", "/endpoints", "{}", nil) + if err == nil { + t.Fatalf("expected error, got nil") + } + + // Assert errors.As + var hnsErr *HNSError + if !errors.As(err, &hnsErr) { + t.Fatalf("expected error to be of type *HNSError, got %T: %v", err, err) + } + + if hnsErr.ErrorCode != 0x803b0013 { + t.Errorf("expected ErrorCode 0x803b0013, got %#x", hnsErr.ErrorCode) + } + + if hnsErr.ErrorString != "vSwitch in transient state" { + t.Errorf("expected ErrorString 'vSwitch in transient state', got %s", hnsErr.ErrorString) + } +} diff --git a/internal/hns/hnsnetwork.go b/internal/hns/hnsnetwork.go index 8861faee7a..133bfe6910 100644 --- a/internal/hns/hnsnetwork.go +++ b/internal/hns/hnsnetwork.go @@ -43,9 +43,10 @@ type HNSNetwork struct { } type hnsResponse struct { - Success bool - Error string - Output json.RawMessage + Success bool + Error string + ErrorCode uint32 `json:"ErrorCode,omitempty"` + Output json.RawMessage } // HNSNetworkRequest makes a call into HNS to update/query a single network