diff --git a/errors.go b/errors.go index b441b0cd3a..14747b16fe 100644 --- a/errors.go +++ b/errors.go @@ -12,6 +12,10 @@ import ( "github.com/Microsoft/hcsshim/internal/hcserror" ) +// HNSError is a typed error for HNS failures that preserves the HRESULT (ErrorCode) +// It is re-exported from internal/hns to allow downstream consumers to use errors.As() +type HNSError = hns.HNSError + var ( // ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists = hcs.exist ErrComputeSystemDoesNotExist = hcs.ErrComputeSystemDoesNotExist diff --git a/internal/hns/hnsfuncs.go b/internal/hns/hnsfuncs.go index e61dc8de62..956b8a0bee 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: uint32(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..8db56f7a84 --- /dev/null +++ b/internal/hns/hnsfuncs_test.go @@ -0,0 +1,43 @@ +//go:build windows + +package hns + +import ( + "encoding/json" + "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) { + var resp hnsResponse + if err := json.Unmarshal([]byte(`{"Success":false,"Error":"vSwitch in transient state","ErrorCode":-2143617005}`), &resp); err != nil { + return nil, err + } + return &resp, 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..c08e3aea7a 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 int32 `json:"ErrorCode,omitempty"` + Output json.RawMessage } // HNSNetworkRequest makes a call into HNS to update/query a single network