diff --git a/pkg/api/types.go b/pkg/api/types.go index eb056ea74b..2d680472e4 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -149,6 +149,8 @@ const ( Migrating ProvisioningState = "Migrating" // Upgrading means an existing ContainerService resource is being upgraded Upgrading ProvisioningState = "Upgrading" + // Canceled means a deployment has been canceled + Canceled ProvisioningState = "Canceled" ) // OrchestratorProfile contains Orchestrator properties diff --git a/pkg/apierror/apierror.go b/pkg/apierror/apierror.go new file mode 100644 index 0000000000..d32b751180 --- /dev/null +++ b/pkg/apierror/apierror.go @@ -0,0 +1,16 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +package apierror + +// New creates an ErrorResponse +func New(errorCategory ErrorCategory, errorCode ErrorCode, message string) *ErrorResponse { + return &ErrorResponse{ + Body: Error{ + Code: errorCode, + Message: message, + Category: errorCategory, + }, + } +} diff --git a/pkg/apierror/apierror_test.go b/pkg/apierror/apierror_test.go new file mode 100644 index 0000000000..563911de2f --- /dev/null +++ b/pkg/apierror/apierror_test.go @@ -0,0 +1,33 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +package apierror + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestNewAPIError(t *testing.T) { + RegisterTestingT(t) + + apiError := New( + ClientError, + InvalidParameter, + "error test") + + Expect(apiError.Body.Code).Should(Equal(ErrorCode("InvalidParameter"))) +} + +func TestAcsNewAPIError(t *testing.T) { + RegisterTestingT(t) + + apiError := New( + ClientError, + InvalidSubscriptionStateTransition, + "error test") + + Expect(apiError.Body.Code).Should(Equal(ErrorCode("InvalidSubscriptionStateTransition"))) +} diff --git a/pkg/apierror/const.go b/pkg/apierror/const.go new file mode 100644 index 0000000000..b5fcb7eade --- /dev/null +++ b/pkg/apierror/const.go @@ -0,0 +1,71 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +package apierror + +// ErrorCategory indicates the kind of error +type ErrorCategory string + +const ( + // ClientError is expected error + ClientError ErrorCategory = "ClientError" + + // InternalError is system or internal error + InternalError ErrorCategory = "InternalError" +) + +// ErrorCode is Common Azure Resource Provider API error code +type ErrorCode string + +const ( + // From Microsoft.Azure.ResourceProvider.API.ErrorCode + + // InvalidParameter error + InvalidParameter ErrorCode = "InvalidParameter" + // BadRequest error + BadRequest ErrorCode = "BadRequest" + // NotFound error + NotFound ErrorCode = "NotFound" + // Conflict error + Conflict ErrorCode = "Conflict" + // PreconditionFailed error + PreconditionFailed ErrorCode = "PreconditionFailed" + // OperationNotAllowed error + OperationNotAllowed ErrorCode = "OperationNotAllowed" + // OperationPreempted error + OperationPreempted ErrorCode = "OperationPreempted" + // PropertyChangeNotAllowed error + PropertyChangeNotAllowed ErrorCode = "PropertyChangeNotAllowed" + // InternalOperationError error + InternalOperationError ErrorCode = "InternalOperationError" + // InvalidSubscriptionStateTransition error + InvalidSubscriptionStateTransition ErrorCode = "InvalidSubscriptionStateTransition" + // UnregisterWithResourcesNotAllowed error + UnregisterWithResourcesNotAllowed ErrorCode = "UnregisterWithResourcesNotAllowed" + // InvalidParameterConflictingProperties error + InvalidParameterConflictingProperties ErrorCode = "InvalidParameterConflictingProperties" + // SubscriptionNotRegistered error + SubscriptionNotRegistered ErrorCode = "SubscriptionNotRegistered" + // ConflictingUserInput error + ConflictingUserInput ErrorCode = "ConflictingUserInput" + // ProvisioningInternalError error + ProvisioningInternalError ErrorCode = "ProvisioningInternalError" + // ProvisioningFailed error + ProvisioningFailed ErrorCode = "ProvisioningFailed" + // NetworkingInternalOperationError error + NetworkingInternalOperationError ErrorCode = "NetworkingInternalOperationError" + // QuotaExceeded error + QuotaExceeded ErrorCode = "QuotaExceeded" + // Unauthorized error + Unauthorized ErrorCode = "Unauthorized" + // ResourcesOverConstrained error + ResourcesOverConstrained ErrorCode = "ResourcesOverConstrained" + + // ResourceDeploymentFailure error + ResourceDeploymentFailure ErrorCode = "ResourceDeploymentFailure" + // InvalidTemplateDeployment error + InvalidTemplateDeployment ErrorCode = "InvalidTemplateDeployment" + // DeploymentFailed error + DeploymentFailed ErrorCode = "DeploymentFailed" +) diff --git a/pkg/apierror/helper.go b/pkg/apierror/helper.go new file mode 100644 index 0000000000..4b96a479de --- /dev/null +++ b/pkg/apierror/helper.go @@ -0,0 +1,47 @@ +package apierror + +import ( + "encoding/json" + "net/http" + + "github.com/Azure/azure-sdk-for-go/arm/resources/resources" +) + +// ExtractCodeFromARMHttpResponse returns the ARM error's Code field +// If not found return defaultCode +func ExtractCodeFromARMHttpResponse(resp *http.Response, defaultCode ErrorCode) ErrorCode { + if resp == nil { + return defaultCode + } + decoder := json.NewDecoder(resp.Body) + errorJSON := ErrorResponse{} + if err := decoder.Decode(&errorJSON); err != nil { + return defaultCode + } + + if errorJSON.Body.Code == "" { + return defaultCode + } + return ErrorCode(errorJSON.Body.Code) +} + +//ConvertToAPIError turns a ManagementErrorWithDetails into a apierror.Error +func ConvertToAPIError(mError *resources.ManagementErrorWithDetails) *Error { + retVal := &Error{} + if mError.Code != nil { + retVal.Code = ErrorCode(*mError.Code) + } + if mError.Message != nil { + retVal.Message = *mError.Message + } + if mError.Target != nil { + retVal.Target = *mError.Target + } + if mError.Details != nil { + retVal.Details = []Error{} + for _, me := range *mError.Details { + retVal.Details = append(retVal.Details, *ConvertToAPIError(&me)) + } + } + return retVal +} diff --git a/pkg/apierror/helper_test.go b/pkg/apierror/helper_test.go new file mode 100644 index 0000000000..0c45711974 --- /dev/null +++ b/pkg/apierror/helper_test.go @@ -0,0 +1,21 @@ +package apierror + +import ( + "bytes" + "io/ioutil" + "net/http" + "testing" + + . "github.com/onsi/gomega" +) + +func TestExtractCodeFromARMHttpResponse(t *testing.T) { + RegisterTestingT(t) + + resp := &http.Response{ + Body: ioutil.NopCloser(bytes.NewBufferString(`{"error":{"code":"ResourceGroupNotFound","message":"Resource group 'jiren-fakegroup' could not be found."}}`)), + } + + code := ExtractCodeFromARMHttpResponse(resp, "") + Expect(code).To(Equal(ErrorCode("ResourceGroupNotFound"))) +} diff --git a/pkg/apierror/types.go b/pkg/apierror/types.go new file mode 100644 index 0000000000..307044a1a3 --- /dev/null +++ b/pkg/apierror/types.go @@ -0,0 +1,39 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +package apierror + +import "encoding/json" + +// Error is the OData v4 format, used by the RPC and +// will go into the v2.2 Azure REST API guidelines +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message"` + Target string `json:"target,omitempty"` + Details []Error `json:"details,omitempty"` + + Category ErrorCategory `json:"-"` + ExceptionType string `json:"-"` + InternalDetail string `json:"-"` +} + +// ErrorResponse defines Resource Provider API 2.0 Error Response Content structure +type ErrorResponse struct { + Body Error `json:"error"` +} + +// Error implements error interface to return error in json +func (e *ErrorResponse) Error() string { + return e.Body.Error() +} + +// Error implements error interface to return error in json +func (e *Error) Error() string { + output, err := json.MarshalIndent(e, " ", " ") + if err != nil { + return err.Error() + } + return string(output) +} diff --git a/pkg/armhelpers/deploymentError.go b/pkg/armhelpers/deploymentError.go new file mode 100644 index 0000000000..3e63710c32 --- /dev/null +++ b/pkg/armhelpers/deploymentError.go @@ -0,0 +1,226 @@ +package armhelpers + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + "github.com/Azure/acs-engine/pkg/api" + "github.com/Azure/acs-engine/pkg/apierror" + "github.com/Azure/azure-sdk-for-go/arm/resources/resources" + "github.com/sirupsen/logrus" +) + +func parseDeploymentOperation(logger *logrus.Entry, operation resources.DeploymentOperation) (*apierror.Error, error) { + if operation.Properties == nil || operation.Properties.StatusMessage == nil { + return nil, fmt.Errorf("DeploymentOperation.Properties is not set") + } + b, err := json.MarshalIndent(operation.Properties.StatusMessage, "", " ") + if err != nil { + logger.Errorf("Error occurred marshalling JSON: '%v'", err) + return nil, err + } + return toError(logger, b) +} + +func toError(logger *logrus.Entry, b []byte) (*apierror.Error, error) { + errresp := &apierror.ErrorResponse{} + + if err := json.Unmarshal(b, errresp); err != nil { + logger.Errorf("Error occurred unmarshalling JSON: '%v' JSON: '%s'", err, string(b)) + return nil, err + } + + armError := &errresp.Body + // If error code is ResourceDeploymentFailure then RP error is defined in the child object field: "details + switch armError.Code { + case apierror.ResourceDeploymentFailure, + apierror.InvalidTemplateDeployment, + apierror.DeploymentFailed: + // StatusMessage.error.details array supports multiple errors but in this particular case + // DeploymentOperationProperties contains error from one specific resource type so the + // chances of multiple deployment errors being returned for a single resource type is slim + // (but possible) based on current error/QoS analysis. In those cases where multiple errors + // are returned ACS will pick the first error code for determining whether this is an internal + // or a client error. This can be reevaluated later based on practical experience. + // However, note that customer will be returned the entire contents of "StatusMessage" object + // (like before) so they have access to all the errors returned by ARM. + logger.Infof("Found %s error code - error response = '%v'", armError.Code, armError) + if len(armError.Details) > 0 { + armError = &armError.Details[0] + } + } + armError.Category = getErrorCategory(armError.Code) + return armError, nil +} + +func getErrorCategory(code apierror.ErrorCode) apierror.ErrorCategory { + switch code { + case apierror.InvalidParameter, + apierror.BadRequest, + apierror.OperationNotAllowed, + apierror.PropertyChangeNotAllowed, + apierror.UnregisterWithResourcesNotAllowed, + apierror.InvalidParameterConflictingProperties, + apierror.SubscriptionNotRegistered, + apierror.ConflictingUserInput, + apierror.QuotaExceeded, + apierror.Unauthorized, + apierror.ResourcesOverConstrained: + return apierror.ClientError + default: + return apierror.InternalError + } +} + +// DeployTemplateSync deploys the template and returns apierror +func DeployTemplateSync(az ACSEngineClient, logger *logrus.Entry, resourceGroupName, deploymentName string, template map[string]interface{}, parameters map[string]interface{}) error { + depExt, depErr := az.DeployTemplate(resourceGroupName, deploymentName, template, parameters, nil) + if depErr == nil { + return nil + } + + logger.Infof("Getting detailed deployment errors for %s", deploymentName) + + if depExt == nil { + logger.Warn("DeploymentExtended is nil") + return &apierror.Error{ + Code: apierror.InternalOperationError, + Message: depErr.Error(), + Category: apierror.InternalError} + } + + // try to extract error from ARM Response + var armErr *apierror.Error + if depExt.Response.Response != nil && depExt.Body != nil { + buf := new(bytes.Buffer) + buf.ReadFrom(depExt.Body) + logger.Infof("StatusCode: %d, Error: %s", depExt.Response.StatusCode, buf.String()) + if resp, err := toError(logger, buf.Bytes()); err == nil { + switch { + case depExt.Response.StatusCode < 500 && depExt.Response.StatusCode >= 400: + resp.Category = apierror.ClientError + case depExt.Response.StatusCode >= 500: + resp.Category = apierror.InternalError + } + armErr = resp + } else { + logger.Errorf("unable to unmarshal response into apierror: %v", err) + } + } else { + logger.Errorf("Got error from Azure SDK without response from ARM") + // This is the failed sdk validation before calling ARM path + return &apierror.Error{ + Code: apierror.InternalOperationError, + Message: depErr.Error(), + Category: apierror.InternalError} + } + + // Check that ARM returned ErrorResponse + if armErr == nil || len(armErr.Message) == 0 || len(armErr.Code) == 0 { + logger.Warn("Not an ARM Response") + return &apierror.Error{ + Code: apierror.InternalOperationError, + Message: depErr.Error(), + Category: apierror.InternalError} + } + + if depExt.Properties == nil || depExt.Properties.ProvisioningState == nil { + logger.Warn("No resources.DeploymentExtended.Properties") + return armErr + } + properties := depExt.Properties + + switch *properties.ProvisioningState { + case string(api.Canceled): + logger.Warning("template deployment has been canceled") + return &apierror.Error{ + Code: apierror.ProvisioningFailed, + Message: "template deployment has been canceled", + Category: apierror.ClientError} + + case string(api.Failed): + var top int32 = 1 + results := make([]resources.DeploymentOperationsListResult, top) + res, err := az.ListDeploymentOperations(resourceGroupName, deploymentName, &top) + if err != nil { + logger.Errorf("unable to list deployment operations %s. error: %v", deploymentName, err) + return armErr + } + results[0] = res + + for res.NextLink != nil { + res, err = az.ListDeploymentOperationsNextResults(res) + if err != nil { + logger.Warningf("unable to list next deployment operations %s. error: %v", deploymentName, err) + break + } + + results = append(results, res) + } + apierr, err := analyzeDeploymentResultAndSaveError(resourceGroupName, deploymentName, results, logger) + if err != nil || apierr == nil { + return armErr + } + return apierr + + default: + logger.Warningf("Unexpected ProvisioningState %s", *properties.ProvisioningState) + return armErr + } +} + +func analyzeDeploymentResultAndSaveError(resourceGroupName, deploymentName string, + operationLists []resources.DeploymentOperationsListResult, logger *logrus.Entry) (*apierror.Error, error) { + var apierr *apierror.Error + var err error + errs := []string{} + isInternalErr := false + + for _, operationsList := range operationLists { + if operationsList.Value == nil { + continue + } + + for _, operation := range *operationsList.Value { + if operation.Properties == nil || *operation.Properties.ProvisioningState != string(api.Failed) { + continue + } + + // log the full deployment operation error response + if operation.ID != nil && operation.OperationID != nil { + b, _ := json.Marshal(operation.Properties) + logger.Infof("deployment operation ID %s, operationID %s, prooperties: %s", *operation.ID, *operation.OperationID, b) + } else { + logger.Error("either deployment ID or operationID is nil") + } + + apierr, err = parseDeploymentOperation(logger, operation) + if err != nil { + logger.Errorf("unable to convert deployment operation to error response in deployment %s from ARM. error: %v", deploymentName, err) + return nil, err + } + if apierr.Category == apierror.InternalError { + isInternalErr = true + } + errs = append(errs, apierr.Error()) + } + } + provisionErr := &apierror.Error{} + if len(errs) > 0 { + if isInternalErr { + provisionErr.Category = apierror.InternalError + } else { + provisionErr.Category = apierror.ClientError + } + if len(errs) == 1 { + provisionErr = apierr + } else { + provisionErr.Code = apierror.ProvisioningFailed + provisionErr.Message = strings.Join(errs, "\n") + } + return provisionErr, nil + } + return nil, nil +} diff --git a/pkg/armhelpers/deploymentError_test.go b/pkg/armhelpers/deploymentError_test.go new file mode 100644 index 0000000000..0872561d6a --- /dev/null +++ b/pkg/armhelpers/deploymentError_test.go @@ -0,0 +1,55 @@ +package armhelpers + +import ( + "testing" + + "github.com/Azure/acs-engine/pkg/apierror" + . "github.com/Azure/acs-engine/pkg/test" + . "github.com/onsi/gomega" + + . "github.com/onsi/ginkgo" + log "github.com/sirupsen/logrus" +) + +func TestUpgradeCluster(t *testing.T) { + RunSpecsWithReporters(t, "templatedeployment", "Server Suite") +} + +var _ = Describe("Template deployment tests", func() { + + It("Should return InternalOperationError error code", func() { + mockClient := &MockACSEngineClient{} + mockClient.FailDeployTemplate = true + logger := log.NewEntry(log.New()) + + err := DeployTemplateSync(mockClient, logger, "rg1", "agentvm", map[string]interface{}{}, map[string]interface{}{}) + Expect(err).NotTo(BeNil()) + apierr, ok := err.(*apierror.Error) + Expect(ok).To(BeTrue()) + Expect(apierr.Code).To(Equal(apierror.InternalOperationError)) + }) + + It("Should return QuotaExceeded error code, specified in details", func() { + mockClient := &MockACSEngineClient{} + mockClient.FailDeployTemplateQuota = true + logger := log.NewEntry(log.New()) + + err := DeployTemplateSync(mockClient, logger, "rg1", "agentvm", map[string]interface{}{}, map[string]interface{}{}) + Expect(err).NotTo(BeNil()) + apierr, ok := err.(*apierror.Error) + Expect(ok).To(BeTrue()) + Expect(apierr.Code).To(Equal(apierror.QuotaExceeded)) + }) + + It("Should return Conflict error code, specified in details", func() { + mockClient := &MockACSEngineClient{} + mockClient.FailDeployTemplateConflict = true + logger := log.NewEntry(log.New()) + + err := DeployTemplateSync(mockClient, logger, "rg1", "agentvm", map[string]interface{}{}, map[string]interface{}{}) + Expect(err).NotTo(BeNil()) + apierr, ok := err.(*apierror.Error) + Expect(ok).To(BeTrue()) + Expect(apierr.Code).To(Equal(apierror.Conflict)) + }) +}) diff --git a/pkg/armhelpers/deployments.go b/pkg/armhelpers/deployments.go index c354e7fce9..9c926a4d97 100644 --- a/pkg/armhelpers/deployments.go +++ b/pkg/armhelpers/deployments.go @@ -31,7 +31,7 @@ func (az *AzureClient) DeployTemplate(resourceGroupName, deploymentName string, return nil, err } - log.Infof("Finished ARM Deployment (%s).", deploymentName) + log.Infof("Finished ARM Deployment (%s). Error: %v", deploymentName, err) return &res, err } diff --git a/pkg/armhelpers/interfaces.go b/pkg/armhelpers/interfaces.go index ae1e6cb177..7e927aefd2 100644 --- a/pkg/armhelpers/interfaces.go +++ b/pkg/armhelpers/interfaces.go @@ -78,6 +78,14 @@ type ACSEngineClient interface { GetKubernetesClient(masterURL, kubeConfig string, interval, timeout time.Duration) (KubernetesClient, error) ListProviders() (resources.ProviderListResult, error) + + // DEPLOYMENTS + + // ListDeploymentOperations gets all deployments operations for a deployment. + ListDeploymentOperations(resourceGroupName string, deploymentName string, top *int32) (result resources.DeploymentOperationsListResult, err error) + + // ListDeploymentOperationsNextResults retrieves the next set of results, if any. + ListDeploymentOperationsNextResults(lastResults resources.DeploymentOperationsListResult) (result resources.DeploymentOperationsListResult, err error) } // ACSStorageClient interface models the azure storage client diff --git a/pkg/armhelpers/mockclients.go b/pkg/armhelpers/mockclients.go index 3f163e4412..ae57d8e053 100644 --- a/pkg/armhelpers/mockclients.go +++ b/pkg/armhelpers/mockclients.go @@ -1,7 +1,11 @@ package armhelpers import ( + "bytes" + "errors" "fmt" + "io/ioutil" + "net/http" "time" "github.com/Azure/azure-sdk-for-go/arm/authorization" @@ -17,6 +21,8 @@ import ( //MockACSEngineClient is an implementation of ACSEngineClient where all requests error out type MockACSEngineClient struct { FailDeployTemplate bool + FailDeployTemplateQuota bool + FailDeployTemplateConflict bool FailEnsureResourceGroup bool FailListVirtualMachines bool FailListVirtualMachineScaleSets bool @@ -125,11 +131,53 @@ func (mc *MockACSEngineClient) AddAcceptLanguages(languages []string) { //DeployTemplate mock func (mc *MockACSEngineClient) DeployTemplate(resourceGroup, name string, template, parameters map[string]interface{}, cancel <-chan struct{}) (*resources.DeploymentExtended, error) { - if mc.FailDeployTemplate { - return nil, fmt.Errorf("DeployTemplate failed") + switch { + case mc.FailDeployTemplate: + return nil, errors.New("DeployTemplate failed") + + case mc.FailDeployTemplateQuota: + errmsg := `resources.DeploymentsClient#CreateOrUpdate: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error.` + resp := `{ +"error":{ + "code":"InvalidTemplateDeployment", + "message":"The template deployment is not valid according to the validation procedure. The tracking id is 'b5bd7d6b-fddf-4ec3-a3b0-ce285a48bd31'. See inner errors for details. Please see https://aka.ms/arm-deploy for usage details.", + "details":[{ + "code":"QuotaExceeded", + "message":"Operation results in exceeding quota limits of Core. Maximum allowed: 10, Current in use: 10, Additional requested: 2. Please read more about quota increase at http://aka.ms/corequotaincrease." +}]}}` + + return &resources.DeploymentExtended{ + Response: autorest.Response{ + Response: &http.Response{ + Status: "400 Bad Request", + StatusCode: 400, + Body: ioutil.NopCloser(bytes.NewReader([]byte(resp))), + }}}, + errors.New(errmsg) + + case mc.FailDeployTemplateConflict: + errmsg := `resources.DeploymentsClient#CreateOrUpdate: Failure sending request: StatusCode=200 -- Original Error: Long running operation terminated with status 'Failed': Code="DeploymentFailed" Message="At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.` + resp := `{ +"status":"Failed", +"error":{ + "code":"DeploymentFailed", + "message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.", + "details":[{ + "code":"Conflict", + "message":"{\r\n \"error\": {\r\n \"code\": \"PropertyChangeNotAllowed\",\r\n \"target\": \"dataDisk.createOption\",\r\n \"message\": \"Changing property 'dataDisk.createOption' is not allowed.\"\r\n }\r\n}" +}]}}` + return &resources.DeploymentExtended{ + Response: autorest.Response{ + Response: &http.Response{ + Status: "200 OK", + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewReader([]byte(resp))), + }}}, + errors.New(errmsg) + + default: + return nil, nil } - - return nil, nil } //EnsureResourceGroup mock @@ -397,3 +445,13 @@ func (mc *MockACSEngineClient) ListProviders() (resources.ProviderListResult, er return resources.ProviderListResult{}, nil } + +// ListDeploymentOperations gets all deployments operations for a deployment. +func (mc *MockACSEngineClient) ListDeploymentOperations(resourceGroupName string, deploymentName string, top *int32) (result resources.DeploymentOperationsListResult, err error) { + return resources.DeploymentOperationsListResult{}, nil +} + +// ListDeploymentOperationsNextResults retrieves the next set of results, if any. +func (mc *MockACSEngineClient) ListDeploymentOperationsNextResults(lastResults resources.DeploymentOperationsListResult) (result resources.DeploymentOperationsListResult, err error) { + return resources.DeploymentOperationsListResult{}, nil +} diff --git a/pkg/operations/kubernetesupgrade/upgradeagentnode.go b/pkg/operations/kubernetesupgrade/upgradeagentnode.go index 831ed219dd..207b61e654 100644 --- a/pkg/operations/kubernetesupgrade/upgradeagentnode.go +++ b/pkg/operations/kubernetesupgrade/upgradeagentnode.go @@ -80,18 +80,7 @@ func (kan *UpgradeAgentNode) CreateNode(poolName string, agentNo int) error { deploymentSuffix := random.Int31() deploymentName := fmt.Sprintf("agent-%s-%d", time.Now().Format("06-01-02T15.04.05"), deploymentSuffix) - _, err := kan.Client.DeployTemplate( - kan.ResourceGroup, - deploymentName, - kan.TemplateMap, - kan.ParametersMap, - nil) - - if err != nil { - return err - } - - return nil + return armhelpers.DeployTemplateSync(kan.Client, kan.logger, kan.ResourceGroup, deploymentName, kan.TemplateMap, kan.ParametersMap) } // Validate will verify that agent node has been upgraded as expected. @@ -100,7 +89,7 @@ func (kan *UpgradeAgentNode) Validate(vmName *string) error { kan.logger.Warningf("VM name was empty. Skipping node condition check") return nil } - + kan.logger.Infof("Validating %s", *vmName) var masterURL string if kan.UpgradeContainerService.Properties.HostedMasterProfile != nil { masterURL = kan.UpgradeContainerService.Properties.HostedMasterProfile.FQDN