github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/names"
    13  	"github.com/juju/txn"
    14  	"gopkg.in/macaroon.v1"
    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, 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  	ErrPerm               = errors.New("permission denied")
    93  	ErrNotLoggedIn        = errors.New("not logged in")
    94  	ErrUnknownWatcher     = errors.New("unknown watcher id")
    95  	ErrUnknownPinger      = errors.New("unknown pinger id")
    96  	ErrStoppedWatcher     = errors.New("watcher has been stopped")
    97  	ErrBadRequest         = errors.New("invalid request")
    98  	ErrTryAgain           = errors.New("try again")
    99  	ErrActionNotAvailable = errors.New("action no longer available")
   100  )
   101  
   102  // OperationBlockedError returns an error which signifies that
   103  // an operation has been blocked; the message should describe
   104  // what has been blocked.
   105  func OperationBlockedError(msg string) error {
   106  	if msg == "" {
   107  		msg = "the operation has been blocked"
   108  	}
   109  	return &params.Error{
   110  		Message: msg,
   111  		Code:    params.CodeOperationBlocked,
   112  	}
   113  }
   114  
   115  var singletonErrorCodes = map[error]string{
   116  	state.ErrCannotEnterScopeYet: params.CodeCannotEnterScopeYet,
   117  	state.ErrCannotEnterScope:    params.CodeCannotEnterScope,
   118  	state.ErrUnitHasSubordinates: params.CodeUnitHasSubordinates,
   119  	state.ErrDead:                params.CodeDead,
   120  	txn.ErrExcessiveContention:   params.CodeExcessiveContention,
   121  	leadership.ErrClaimDenied:    params.CodeLeadershipClaimDenied,
   122  	lease.ErrClaimDenied:         params.CodeLeaseClaimDenied,
   123  	ErrBadId:                     params.CodeNotFound,
   124  	ErrBadCreds:                  params.CodeUnauthorized,
   125  	ErrPerm:                      params.CodeUnauthorized,
   126  	ErrNotLoggedIn:               params.CodeUnauthorized,
   127  	ErrUnknownWatcher:            params.CodeNotFound,
   128  	ErrStoppedWatcher:            params.CodeStopped,
   129  	ErrTryAgain:                  params.CodeTryAgain,
   130  	ErrActionNotAvailable:        params.CodeActionNotAvailable,
   131  }
   132  
   133  func singletonCode(err error) (string, bool) {
   134  	// All error types may not be hashable; deal with
   135  	// that by catching the panic if we try to look up
   136  	// a non-hashable type.
   137  	defer func() {
   138  		recover()
   139  	}()
   140  	code, ok := singletonErrorCodes[err]
   141  	return code, ok
   142  }
   143  
   144  func singletonError(err error) (error, bool) {
   145  	errCode := params.ErrCode(err)
   146  	for singleton, code := range singletonErrorCodes {
   147  		if errCode == code && singleton.Error() == err.Error() {
   148  			return singleton, true
   149  		}
   150  	}
   151  	return nil, false
   152  }
   153  
   154  // ServerErrorAndStatus is like ServerError but also
   155  // returns an HTTP status code appropriate for using
   156  // in a response holding the given error.
   157  func ServerErrorAndStatus(err error) (*params.Error, int) {
   158  	err1 := ServerError(err)
   159  	if err1 == nil {
   160  		return nil, http.StatusOK
   161  	}
   162  	status := http.StatusInternalServerError
   163  	switch err1.Code {
   164  	case params.CodeUnauthorized:
   165  		status = http.StatusUnauthorized
   166  	case params.CodeNotFound:
   167  		status = http.StatusNotFound
   168  	case params.CodeBadRequest:
   169  		status = http.StatusBadRequest
   170  	case params.CodeMethodNotAllowed:
   171  		status = http.StatusMethodNotAllowed
   172  	case params.CodeOperationBlocked:
   173  		// This should really be http.StatusForbidden but earlier versions
   174  		// of juju clients rely on the 400 status, so we leave it like that.
   175  		status = http.StatusBadRequest
   176  	case params.CodeForbidden:
   177  		status = http.StatusForbidden
   178  	case params.CodeDischargeRequired:
   179  		status = http.StatusUnauthorized
   180  	}
   181  	return err1, status
   182  }
   183  
   184  // ServerError returns an error suitable for returning to an API
   185  // client, with an error code suitable for various kinds of errors
   186  // generated in packages outside the API.
   187  func ServerError(err error) *params.Error {
   188  	if err == nil {
   189  		return nil
   190  	}
   191  	msg := err.Error()
   192  	// Skip past annotations when looking for the code.
   193  	err = errors.Cause(err)
   194  	code, ok := singletonCode(err)
   195  	var info *params.ErrorInfo
   196  	switch {
   197  	case ok:
   198  	case errors.IsUnauthorized(err):
   199  		code = params.CodeUnauthorized
   200  	case errors.IsNotFound(err):
   201  		code = params.CodeNotFound
   202  	case errors.IsAlreadyExists(err):
   203  		code = params.CodeAlreadyExists
   204  	case errors.IsNotAssigned(err):
   205  		code = params.CodeNotAssigned
   206  	case state.IsHasAssignedUnitsError(err):
   207  		code = params.CodeHasAssignedUnits
   208  	case state.IsHasHostedModelsError(err):
   209  		code = params.CodeHasHostedModels
   210  	case isNoAddressSetError(err):
   211  		code = params.CodeNoAddressSet
   212  	case errors.IsNotProvisioned(err):
   213  		code = params.CodeNotProvisioned
   214  	case IsUpgradeInProgressError(err):
   215  		code = params.CodeUpgradeInProgress
   216  	case state.IsHasAttachmentsError(err):
   217  		code = params.CodeMachineHasAttachedStorage
   218  	case isUnknownModelError(err):
   219  		code = params.CodeNotFound
   220  	case errors.IsNotSupported(err):
   221  		code = params.CodeNotSupported
   222  	case errors.IsBadRequest(err):
   223  		code = params.CodeBadRequest
   224  	case errors.IsMethodNotAllowed(err):
   225  		code = params.CodeMethodNotAllowed
   226  	default:
   227  		if err, ok := err.(*DischargeRequiredError); ok {
   228  			code = params.CodeDischargeRequired
   229  			info = &params.ErrorInfo{
   230  				Macaroon: err.Macaroon,
   231  				// One macaroon fits all.
   232  				MacaroonPath: "/",
   233  			}
   234  			break
   235  		}
   236  		code = params.ErrCode(err)
   237  	}
   238  	return &params.Error{
   239  		Message: msg,
   240  		Code:    code,
   241  		Info:    info,
   242  	}
   243  }
   244  
   245  func DestroyErr(desc string, ids, errs []string) error {
   246  	// TODO(waigani) refactor DestroyErr to take a map of ids to errors.
   247  	if len(errs) == 0 {
   248  		return nil
   249  	}
   250  	msg := "some %s were not destroyed"
   251  	if len(errs) == len(ids) {
   252  		msg = "no %s were destroyed"
   253  	}
   254  	msg = fmt.Sprintf(msg, desc)
   255  	return errors.Errorf("%s: %s", msg, strings.Join(errs, "; "))
   256  }
   257  
   258  // RestoreError makes a best effort at converting the given error
   259  // back into an error originally converted by ServerError().
   260  func RestoreError(err error) error {
   261  	err = errors.Cause(err)
   262  
   263  	if apiErr, ok := err.(*params.Error); !ok {
   264  		return err
   265  	} else if apiErr == nil {
   266  		return nil
   267  	}
   268  	if params.ErrCode(err) == "" {
   269  		return err
   270  	}
   271  	msg := err.Error()
   272  
   273  	if singleton, ok := singletonError(err); ok {
   274  		return singleton
   275  	}
   276  
   277  	// TODO(ericsnow) Support the other error types handled by ServerError().
   278  	switch {
   279  	case params.IsCodeUnauthorized(err):
   280  		return errors.NewUnauthorized(nil, msg)
   281  	case params.IsCodeNotFound(err):
   282  		// TODO(ericsnow) UnknownModelError should be handled here too.
   283  		// ...by parsing msg?
   284  		return errors.NewNotFound(nil, msg)
   285  	case params.IsCodeAlreadyExists(err):
   286  		return errors.NewAlreadyExists(nil, msg)
   287  	case params.IsCodeNotAssigned(err):
   288  		return errors.NewNotAssigned(nil, msg)
   289  	case params.IsCodeHasAssignedUnits(err):
   290  		// TODO(ericsnow) Handle state.HasAssignedUnitsError here.
   291  		// ...by parsing msg?
   292  		return err
   293  	case params.IsCodeHasHostedModels(err):
   294  		return err
   295  	case params.IsCodeNoAddressSet(err):
   296  		// TODO(ericsnow) Handle isNoAddressSetError here.
   297  		// ...by parsing msg?
   298  		return err
   299  	case params.IsCodeNotProvisioned(err):
   300  		return errors.NewNotProvisioned(nil, msg)
   301  	case params.IsCodeUpgradeInProgress(err):
   302  		// TODO(ericsnow) Handle state.UpgradeInProgressError here.
   303  		// ...by parsing msg?
   304  		return err
   305  	case params.IsCodeMachineHasAttachedStorage(err):
   306  		// TODO(ericsnow) Handle state.HasAttachmentsError here.
   307  		// ...by parsing msg?
   308  		return err
   309  	case params.IsCodeNotSupported(err):
   310  		return errors.NewNotSupported(nil, msg)
   311  	case params.IsBadRequest(err):
   312  		return errors.NewBadRequest(nil, msg)
   313  	case params.IsMethodNotAllowed(err):
   314  		return errors.NewMethodNotAllowed(nil, msg)
   315  	case params.ErrCode(err) == params.CodeDischargeRequired:
   316  		// TODO(ericsnow) Handle DischargeRequiredError here.
   317  		return err
   318  	default:
   319  		return err
   320  	}
   321  }