github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/errors.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/txn"
    13  	"gopkg.in/juju/names.v2"
    14  	"gopkg.in/macaroon.v2-unstable"
    15  
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/core/leadership"
    18  	"github.com/juju/juju/core/lease"
    19  	"github.com/juju/juju/state"
    20  )
    21  
    22  func NotSupportedError(tag names.Tag, operation string) error {
    23  	return errors.Errorf("entity %q does not support %s", tag, operation)
    24  }
    25  
    26  type noAddressSetError struct {
    27  	unitTag     names.UnitTag
    28  	addressName string
    29  }
    30  
    31  func (e *noAddressSetError) Error() string {
    32  	return fmt.Sprintf("%q has no %s address set", e.unitTag, e.addressName)
    33  }
    34  
    35  func NoAddressSetError(unitTag names.UnitTag, addressName string) error {
    36  	return &noAddressSetError{unitTag: unitTag, addressName: addressName}
    37  }
    38  
    39  func isNoAddressSetError(err error) bool {
    40  	_, ok := err.(*noAddressSetError)
    41  	return ok
    42  }
    43  
    44  type unknownModelError struct {
    45  	uuid string
    46  }
    47  
    48  func (e *unknownModelError) Error() string {
    49  	return fmt.Sprintf("unknown model: %q", e.uuid)
    50  }
    51  
    52  func UnknownModelError(uuid string) error {
    53  	return &unknownModelError{uuid: uuid}
    54  }
    55  
    56  func isUnknownModelError(err error) bool {
    57  	_, ok := err.(*unknownModelError)
    58  	return ok
    59  }
    60  
    61  // DischargeRequiredError is the error returned when a macaroon requires discharging
    62  // to complete authentication.
    63  type DischargeRequiredError struct {
    64  	Cause    error
    65  	Macaroon *macaroon.Macaroon
    66  }
    67  
    68  // Error implements the error interface.
    69  func (e *DischargeRequiredError) Error() string {
    70  	return e.Cause.Error()
    71  }
    72  
    73  // IsDischargeRequiredError reports whether the cause
    74  // of the error is a *DischargeRequiredError.
    75  func IsDischargeRequiredError(err error) bool {
    76  	_, ok := errors.Cause(err).(*DischargeRequiredError)
    77  	return ok
    78  }
    79  
    80  // IsUpgradeInProgress returns true if this error is caused
    81  // by an upgrade in progress.
    82  func IsUpgradeInProgressError(err error) bool {
    83  	if state.IsUpgradeInProgressError(err) {
    84  		return true
    85  	}
    86  	return errors.Cause(err) == params.UpgradeInProgressError
    87  }
    88  
    89  var (
    90  	ErrBadId              = errors.New("id not found")
    91  	ErrBadCreds           = errors.New("invalid entity name or password")
    92  	ErrNoCreds            = errors.New("no credentials provided")
    93  	ErrLoginExpired       = errors.New("login expired")
    94  	ErrPerm               = errors.New("permission denied")
    95  	ErrNotLoggedIn        = errors.New("not logged in")
    96  	ErrUnknownWatcher     = errors.New("unknown watcher id")
    97  	ErrStoppedWatcher     = errors.New("watcher has been stopped")
    98  	ErrBadRequest         = errors.New("invalid request")
    99  	ErrTryAgain           = errors.New("try again")
   100  	ErrActionNotAvailable = errors.New("action no longer available")
   101  )
   102  
   103  // OperationBlockedError returns an error which signifies that
   104  // an operation has been blocked; the message should describe
   105  // what has been blocked.
   106  func OperationBlockedError(msg string) error {
   107  	if msg == "" {
   108  		msg = "the operation has been blocked"
   109  	}
   110  	return &params.Error{
   111  		Message: msg,
   112  		Code:    params.CodeOperationBlocked,
   113  	}
   114  }
   115  
   116  var singletonErrorCodes = map[error]string{
   117  	state.ErrCannotEnterScopeYet: params.CodeCannotEnterScopeYet,
   118  	state.ErrCannotEnterScope:    params.CodeCannotEnterScope,
   119  	state.ErrUnitHasSubordinates: params.CodeUnitHasSubordinates,
   120  	state.ErrDead:                params.CodeDead,
   121  	txn.ErrExcessiveContention:   params.CodeExcessiveContention,
   122  	leadership.ErrClaimDenied:    params.CodeLeadershipClaimDenied,
   123  	lease.ErrClaimDenied:         params.CodeLeaseClaimDenied,
   124  	ErrBadId:                     params.CodeNotFound,
   125  	ErrBadCreds:                  params.CodeUnauthorized,
   126  	ErrNoCreds:                   params.CodeNoCreds,
   127  	ErrLoginExpired:              params.CodeLoginExpired,
   128  	ErrPerm:                      params.CodeUnauthorized,
   129  	ErrNotLoggedIn:               params.CodeUnauthorized,
   130  	ErrUnknownWatcher:            params.CodeNotFound,
   131  	ErrStoppedWatcher:            params.CodeStopped,
   132  	ErrTryAgain:                  params.CodeTryAgain,
   133  	ErrActionNotAvailable:        params.CodeActionNotAvailable,
   134  }
   135  
   136  func singletonCode(err error) (string, bool) {
   137  	// All error types may not be hashable; deal with
   138  	// that by catching the panic if we try to look up
   139  	// a non-hashable type.
   140  	defer func() {
   141  		recover()
   142  	}()
   143  	code, ok := singletonErrorCodes[err]
   144  	return code, ok
   145  }
   146  
   147  func singletonError(err error) (error, bool) {
   148  	errCode := params.ErrCode(err)
   149  	for singleton, code := range singletonErrorCodes {
   150  		if errCode == code && singleton.Error() == err.Error() {
   151  			return singleton, true
   152  		}
   153  	}
   154  	return nil, false
   155  }
   156  
   157  // ServerErrorAndStatus is like ServerError but also
   158  // returns an HTTP status code appropriate for using
   159  // in a response holding the given error.
   160  func ServerErrorAndStatus(err error) (*params.Error, int) {
   161  	err1 := ServerError(err)
   162  	if err1 == nil {
   163  		return nil, http.StatusOK
   164  	}
   165  	status := http.StatusInternalServerError
   166  	switch err1.Code {
   167  	case params.CodeUnauthorized:
   168  		status = http.StatusUnauthorized
   169  	case params.CodeNotFound,
   170  		params.CodeUserNotFound,
   171  		params.CodeModelNotFound:
   172  		status = http.StatusNotFound
   173  	case params.CodeBadRequest:
   174  		status = http.StatusBadRequest
   175  	case params.CodeMethodNotAllowed:
   176  		status = http.StatusMethodNotAllowed
   177  	case params.CodeOperationBlocked:
   178  		// This should really be http.StatusForbidden but earlier versions
   179  		// of juju clients rely on the 400 status, so we leave it like that.
   180  		status = http.StatusBadRequest
   181  	case params.CodeForbidden:
   182  		status = http.StatusForbidden
   183  	case params.CodeDischargeRequired:
   184  		status = http.StatusUnauthorized
   185  	case params.CodeRetry:
   186  		status = http.StatusServiceUnavailable
   187  	}
   188  	return err1, status
   189  }
   190  
   191  // ServerError returns an error suitable for returning to an API
   192  // client, with an error code suitable for various kinds of errors
   193  // generated in packages outside the API.
   194  func ServerError(err error) *params.Error {
   195  	if err == nil {
   196  		return nil
   197  	}
   198  	logger.Tracef("server RPC error %v", errors.Details(err))
   199  	msg := err.Error()
   200  	// Skip past annotations when looking for the code.
   201  	err = errors.Cause(err)
   202  	code, ok := singletonCode(err)
   203  	var info *params.ErrorInfo
   204  	switch {
   205  	case ok:
   206  	case errors.IsUnauthorized(err):
   207  		code = params.CodeUnauthorized
   208  	case errors.IsNotFound(err):
   209  		code = params.CodeNotFound
   210  	case errors.IsUserNotFound(err):
   211  		code = params.CodeUserNotFound
   212  	case errors.IsAlreadyExists(err):
   213  		code = params.CodeAlreadyExists
   214  	case errors.IsNotAssigned(err):
   215  		code = params.CodeNotAssigned
   216  	case state.IsHasAssignedUnitsError(err):
   217  		code = params.CodeHasAssignedUnits
   218  	case state.IsHasHostedModelsError(err):
   219  		code = params.CodeHasHostedModels
   220  	case state.IsHasPersistentStorageError(err):
   221  		code = params.CodeHasPersistentStorage
   222  	case state.IsModelNotEmptyError(err):
   223  		code = params.CodeModelNotEmpty
   224  	case isNoAddressSetError(err):
   225  		code = params.CodeNoAddressSet
   226  	case errors.IsNotProvisioned(err):
   227  		code = params.CodeNotProvisioned
   228  	case IsUpgradeInProgressError(err):
   229  		code = params.CodeUpgradeInProgress
   230  	case state.IsHasAttachmentsError(err):
   231  		code = params.CodeMachineHasAttachedStorage
   232  	case state.IsStorageAttachedError(err):
   233  		code = params.CodeStorageAttached
   234  	case isUnknownModelError(err):
   235  		code = params.CodeModelNotFound
   236  	case errors.IsNotSupported(err):
   237  		code = params.CodeNotSupported
   238  	case errors.IsBadRequest(err):
   239  		code = params.CodeBadRequest
   240  	case errors.IsMethodNotAllowed(err):
   241  		code = params.CodeMethodNotAllowed
   242  	case errors.IsNotImplemented(err):
   243  		code = params.CodeNotImplemented
   244  	case state.IsIncompatibleSeriesError(err):
   245  		code = params.CodeIncompatibleSeries
   246  	default:
   247  		if err, ok := err.(*DischargeRequiredError); ok {
   248  			code = params.CodeDischargeRequired
   249  			info = &params.ErrorInfo{
   250  				Macaroon: err.Macaroon,
   251  				// One macaroon fits all.
   252  				MacaroonPath: "/",
   253  			}
   254  			break
   255  		}
   256  		code = params.ErrCode(err)
   257  	}
   258  	return &params.Error{
   259  		Message: msg,
   260  		Code:    code,
   261  		Info:    info,
   262  	}
   263  }
   264  
   265  func DestroyErr(desc string, ids []string, errs []error) error {
   266  	// TODO(waigani) refactor DestroyErr to take a map of ids to errors.
   267  	if len(errs) == 0 {
   268  		return nil
   269  	}
   270  	msg := "some %s were not destroyed"
   271  	if len(errs) == len(ids) {
   272  		msg = "no %s were destroyed"
   273  	}
   274  	msg = fmt.Sprintf(msg, desc)
   275  	errStrings := make([]string, len(errs))
   276  	for i, err := range errs {
   277  		errStrings[i] = err.Error()
   278  	}
   279  	return errors.Errorf("%s: %s", msg, strings.Join(errStrings, "; "))
   280  }
   281  
   282  // RestoreError makes a best effort at converting the given error
   283  // back into an error originally converted by ServerError().
   284  func RestoreError(err error) error {
   285  	err = errors.Cause(err)
   286  
   287  	if apiErr, ok := err.(*params.Error); !ok {
   288  		return err
   289  	} else if apiErr == nil {
   290  		return nil
   291  	}
   292  	if params.ErrCode(err) == "" {
   293  		return err
   294  	}
   295  	msg := err.Error()
   296  
   297  	if singleton, ok := singletonError(err); ok {
   298  		return singleton
   299  	}
   300  
   301  	// TODO(ericsnow) Support the other error types handled by ServerError().
   302  	switch {
   303  	case params.IsCodeUnauthorized(err):
   304  		return errors.NewUnauthorized(nil, msg)
   305  	case params.IsCodeNotFound(err):
   306  		// TODO(ericsnow) UnknownModelError should be handled here too.
   307  		// ...by parsing msg?
   308  		return errors.NewNotFound(nil, msg)
   309  	case params.IsCodeUserNotFound(err):
   310  		return errors.NewUserNotFound(nil, msg)
   311  	case params.IsCodeAlreadyExists(err):
   312  		return errors.NewAlreadyExists(nil, msg)
   313  	case params.IsCodeNotAssigned(err):
   314  		return errors.NewNotAssigned(nil, msg)
   315  	case params.IsCodeHasAssignedUnits(err):
   316  		// TODO(ericsnow) Handle state.HasAssignedUnitsError here.
   317  		// ...by parsing msg?
   318  		return err
   319  	case params.IsCodeHasHostedModels(err):
   320  		return err
   321  	case params.IsCodeHasPersistentStorage(err):
   322  		return err
   323  	case params.IsCodeModelNotEmpty(err):
   324  		return err
   325  	case params.IsCodeNoAddressSet(err):
   326  		// TODO(ericsnow) Handle isNoAddressSetError here.
   327  		// ...by parsing msg?
   328  		return err
   329  	case params.IsCodeNotProvisioned(err):
   330  		return errors.NewNotProvisioned(nil, msg)
   331  	case params.IsCodeUpgradeInProgress(err):
   332  		// TODO(ericsnow) Handle state.UpgradeInProgressError here.
   333  		// ...by parsing msg?
   334  		return err
   335  	case params.IsCodeMachineHasAttachedStorage(err):
   336  		// TODO(ericsnow) Handle state.HasAttachmentsError here.
   337  		// ...by parsing msg?
   338  		return err
   339  	case params.IsCodeStorageAttached(err):
   340  		return err
   341  	case params.IsCodeNotSupported(err):
   342  		return errors.NewNotSupported(nil, msg)
   343  	case params.IsBadRequest(err):
   344  		return errors.NewBadRequest(nil, msg)
   345  	case params.IsMethodNotAllowed(err):
   346  		return errors.NewMethodNotAllowed(nil, msg)
   347  	case params.ErrCode(err) == params.CodeDischargeRequired:
   348  		// TODO(ericsnow) Handle DischargeRequiredError here.
   349  		return err
   350  	default:
   351  		return err
   352  	}
   353  }