Skip to content

Commit

Permalink
Anexia Provider: Return TerminalError on 401 and 403 engine responses
Browse files Browse the repository at this point in the history
  • Loading branch information
anx-mschaefer committed Jan 9, 2023
1 parent d20b3d2 commit 489692b
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 3 deletions.
28 changes: 25 additions & 3 deletions pkg/cloudprovider/provider/anexia/provider.go
Expand Up @@ -27,6 +27,8 @@ import (
"sync"
"time"

"go.anx.io/go-anxcloud/pkg/api"
"go.anx.io/go-anxcloud/pkg/client"
anxclient "go.anx.io/go-anxcloud/pkg/client"
anxaddr "go.anx.io/go-anxcloud/pkg/ipam/address"
"go.anx.io/go-anxcloud/pkg/vsphere"
Expand Down Expand Up @@ -119,7 +121,7 @@ func (p *provider) Create(ctx context.Context, machine *clusterv1alpha1.Machine,
// provision machine
err = provisionVM(ctx, client)
if err != nil {
return nil, err
return nil, anexiaErrorToTerminalError(err, "failed waiting for vm provisioning")
}
return p.Get(ctx, machine, data)
}
Expand Down Expand Up @@ -431,7 +433,7 @@ func (p *provider) Get(ctx context.Context, machine *clusterv1alpha1.Machine, pd
if status.InstanceID == "" {
progress, err := vsphereAPI.Provisioning().Progress().Get(ctx, status.ProvisioningID)
if err != nil {
return nil, fmt.Errorf("failed to get provisioning progress: %w", err)
return nil, anexiaErrorToTerminalError(err, "failed to get provisioning progress")
}
if len(progress.Errors) > 0 {
return nil, fmt.Errorf("vm provisioning had errors: %s", strings.Join(progress.Errors, ","))
Expand All @@ -458,7 +460,7 @@ func (p *provider) Get(ctx context.Context, machine *clusterv1alpha1.Machine, pd

info, err := vsphereAPI.Info().Get(timeoutCtx, status.InstanceID)
if err != nil {
return nil, fmt.Errorf("failed get machine info: %w", err)
return nil, anexiaErrorToTerminalError(err, "failed getting machine info")
}
instance.info = &info

Expand Down Expand Up @@ -587,3 +589,23 @@ func updateMachineStatus(machine *clusterv1alpha1.Machine, status anxtypes.Provi

return nil
}

func anexiaErrorToTerminalError(err error, msg string) error {
httpError := api.HTTPError{}
if errors.As(err, &httpError) && (httpError.StatusCode() == http.StatusForbidden || httpError.StatusCode() == http.StatusUnauthorized) {
return cloudprovidererrors.TerminalError{
Reason: common.InvalidConfigurationMachineError,
Message: "Request was rejected due to invalid credentials",
}
}

var responseError *client.ResponseError
if errors.As(err, &responseError) && (responseError.ErrorData.Code == http.StatusForbidden || responseError.ErrorData.Code == http.StatusUnauthorized) {
return cloudprovidererrors.TerminalError{
Reason: common.InvalidConfigurationMachineError,
Message: "Request was rejected due to invalid credentials",
}
}

return fmt.Errorf("%s: %w", msg, err)
}
90 changes: 90 additions & 0 deletions pkg/cloudprovider/provider/anexia/provider_test.go
Expand Up @@ -21,17 +21,24 @@ import (
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

"github.com/gophercloud/gophercloud/testhelper"
"go.anx.io/go-anxcloud/pkg/api"
corev1 "go.anx.io/go-anxcloud/pkg/apis/core/v1"
"go.anx.io/go-anxcloud/pkg/client"
anxclient "go.anx.io/go-anxcloud/pkg/client"
"go.anx.io/go-anxcloud/pkg/core"
"go.anx.io/go-anxcloud/pkg/ipam/address"
"go.anx.io/go-anxcloud/pkg/vsphere/provisioning/progress"
"go.anx.io/go-anxcloud/pkg/vsphere/provisioning/vm"

"github.com/kubermatic/machine-controller/pkg/apis/cluster/v1alpha1"
clusterv1alpha1 "github.com/kubermatic/machine-controller/pkg/apis/cluster/v1alpha1"
cloudprovidererrors "github.com/kubermatic/machine-controller/pkg/cloudprovider/errors"
anxtypes "github.com/kubermatic/machine-controller/pkg/cloudprovider/provider/anexia/types"
cloudprovidertypes "github.com/kubermatic/machine-controller/pkg/cloudprovider/types"

Expand Down Expand Up @@ -346,3 +353,86 @@ func TestUpdateStatus(t *testing.T) {
testhelper.AssertEquals(t, true, called)
testhelper.AssertNoErr(t, err)
}

func Test_anexiaErrorToTerminalError(t *testing.T) {
forbiddenMockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error": {"code": 403}}`))
})

unauthorizedMockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error": {"code": 401}}`))
})

legacyClientRun := func(url string) error {
client, err := client.New(client.BaseURL(url), client.IgnoreMissingToken(), client.ParseEngineErrors(true))
testhelper.AssertNoErr(t, err)
_, err = core.NewAPI(client).Location().List(context.TODO(), 1, 1, "", "")
return err
}

apiClientRun := func(url string) error {
client, err := api.NewAPI(api.WithClientOptions(
client.BaseURL(url),
client.IgnoreMissingToken(),
))
testhelper.AssertNoErr(t, err)
return client.Get(context.TODO(), &corev1.Location{Identifier: "foo"})
}

testCases := []struct {
name string
mockHandler http.HandlerFunc
run func(url string) error
}{
{
name: "api client returns forbidden",
mockHandler: forbiddenMockHandler,
run: apiClientRun,
},
{
name: "api client returns unauthorized",
mockHandler: unauthorizedMockHandler,
run: apiClientRun,
},
{
name: "legacy client returns forbidden",
mockHandler: forbiddenMockHandler,
run: legacyClientRun,
},
{
name: "legacy client returns unauthorized",
mockHandler: unauthorizedMockHandler,
run: legacyClientRun,
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
srv := httptest.NewServer(testCase.mockHandler)
defer srv.Close()

err := anexiaErrorToTerminalError(testCase.run(srv.URL), "foo")
if ok, _, _ := cloudprovidererrors.IsTerminalError(err); !ok {
t.Errorf("unexpected error %#v, expected TerminalError", err)
}
})
}

t.Run("api client 404 HTTPError shouldn't convert to TerminalError", func(t *testing.T) {
err := api.NewHTTPError(http.StatusNotFound, "GET", &url.URL{}, errors.New("foo"))
err = anexiaErrorToTerminalError(err, "foo")
if ok, _, _ := cloudprovidererrors.IsTerminalError(err); ok {
t.Errorf("unexpected error %#v, expected no TerminalError", err)
}
})

t.Run("legacy api client unspecific ResponseError shouldn't convert to TerminalError", func(t *testing.T) {
var err error = &client.ResponseError{}
err = anexiaErrorToTerminalError(err, "foo")
if ok, _, _ := cloudprovidererrors.IsTerminalError(err); ok {
t.Errorf("unexpected error %#v, expected no TerminalError", err)
}
})
}

0 comments on commit 489692b

Please sign in to comment.