// RestoreError makes a best effort at converting the given error // back into an error originally converted by ServerError(). If the // error could not be converted then false is returned. func RestoreError(err error) (error, bool) { err = errors.Cause(err) if apiErr, ok := err.(*params.Error); !ok { return err, false } else if apiErr == nil { return nil, true } if params.ErrCode(err) == "" { return err, false } msg := err.Error() if singleton, ok := singletonError(err); ok { return singleton, true } // TODO(ericsnow) Support the other error types handled by ServerError(). switch { case params.IsCodeUnauthorized(err): return errors.NewUnauthorized(nil, msg), true case params.IsCodeNotFound(err): // TODO(ericsnow) UnknownModelError should be handled here too. // ...by parsing msg? return errors.NewNotFound(nil, msg), true case params.IsCodeAlreadyExists(err): return errors.NewAlreadyExists(nil, msg), true case params.IsCodeNotAssigned(err): return errors.NewNotAssigned(nil, msg), true case params.IsCodeHasAssignedUnits(err): // TODO(ericsnow) Handle state.HasAssignedUnitsError here. // ...by parsing msg? return err, false case params.IsCodeNoAddressSet(err): // TODO(ericsnow) Handle isNoAddressSetError here. // ...by parsing msg? return err, false case params.IsCodeNotProvisioned(err): return errors.NewNotProvisioned(nil, msg), true case params.IsCodeUpgradeInProgress(err): // TODO(ericsnow) Handle state.UpgradeInProgressError here. // ...by parsing msg? return err, false case params.IsCodeMachineHasAttachedStorage(err): // TODO(ericsnow) Handle state.HasAttachmentsError here. // ...by parsing msg? return err, false case params.IsCodeNotSupported(err): return errors.NewNotSupported(nil, msg), true case params.IsBadRequest(err): return errors.NewBadRequest(nil, msg), true case params.IsMethodNotAllowed(err): return errors.NewMethodNotAllowed(nil, msg), true case params.ErrCode(err) == params.CodeDischargeRequired: // TODO(ericsnow) Handle DischargeRequiredError here. return err, false default: return err, false } }
func (s *deployerSuite) TestUnitRemove(c *gc.C) { unit, err := s.st.Unit(s.principal.Tag().(names.UnitTag)) c.Assert(err, jc.ErrorIsNil) // It fails because the entity is still alive. // And EnsureDead will fail because there is a subordinate. err = unit.Remove() c.Assert(err, gc.ErrorMatches, `cannot remove entity "unit-mysql-0": still alive`) c.Assert(params.ErrCode(err), gc.Equals, "") // With the subordinate it also fails due to it being alive. unit, err = s.st.Unit(s.subordinate.Tag().(names.UnitTag)) c.Assert(err, jc.ErrorIsNil) err = unit.Remove() c.Assert(err, gc.ErrorMatches, `cannot remove entity "unit-logging-0": still alive`) c.Assert(params.ErrCode(err), gc.Equals, "") // Make it dead first and try again. err = s.subordinate.EnsureDead() c.Assert(err, jc.ErrorIsNil) err = unit.Remove() c.Assert(err, jc.ErrorIsNil) // Verify it's gone. err = unit.Refresh() s.assertUnauthorized(c, err) unit, err = s.st.Unit(s.subordinate.Tag().(names.UnitTag)) s.assertUnauthorized(c, err) c.Assert(unit, gc.IsNil) }
func (*errorSuite) TestErrCode(c *gc.C) { var err error err = ¶ms.Error{Code: params.CodeDead, Message: "brain dead test"} c.Check(params.ErrCode(err), gc.Equals, params.CodeDead) err = errors.Trace(err) c.Check(params.ErrCode(err), gc.Equals, params.CodeDead) }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } code, ok := singletonCode(err) switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case state.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case IsNoAddressSetError(err): code = params.CodeNoAddressSet case state.IsNotProvisionedError(err): code = params.CodeNotProvisioned case IsUnknownEnviromentError(err): code = params.CodeNotFound default: code = params.ErrCode(err) } return ¶ms.Error{ Message: err.Error(), Code: code, } }
func (s *httpSuite) TestHTTPClient(c *gc.C) { var handler http.HandlerFunc srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { handler(w, req) })) defer srv.Close() s.client.BaseURL = srv.URL for i, test := range httpClientTests { c.Logf("test %d: %s", i, test.about) handler = test.handler var resp interface{} if test.expectResponse != nil { resp = reflect.New(reflect.TypeOf(test.expectResponse).Elem()).Interface() } err := s.client.Get("/", resp) if test.expectError != "" { c.Check(err, gc.ErrorMatches, test.expectError) c.Check(params.ErrCode(err), gc.Equals, test.expectErrorCode) if err, ok := errors.Cause(err).(*params.Error); ok { c.Check(err.Info, jc.DeepEquals, test.expectErrorInfo) } else if test.expectErrorInfo != nil { c.Fatalf("no error info found in error") } continue } c.Check(err, gc.IsNil) c.Check(resp, jc.DeepEquals, test.expectResponse) } }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } logger.Tracef("server RPC error %v", errors.Details(err)) msg := err.Error() // Skip past annotations when looking for the code. err = errors.Cause(err) code, ok := singletonCode(err) var info *params.ErrorInfo switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsUserNotFound(err): code = params.CodeUserNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case errors.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case state.IsHasHostedModelsError(err): code = params.CodeHasHostedModels case isNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): code = params.CodeNotProvisioned case IsUpgradeInProgressError(err): code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage case isUnknownModelError(err): code = params.CodeModelNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported case errors.IsBadRequest(err): code = params.CodeBadRequest case errors.IsMethodNotAllowed(err): code = params.CodeMethodNotAllowed default: if err, ok := err.(*DischargeRequiredError); ok { code = params.CodeDischargeRequired info = ¶ms.ErrorInfo{ Macaroon: err.Macaroon, // One macaroon fits all. MacaroonPath: "/", } break } code = params.ErrCode(err) } return ¶ms.Error{ Message: msg, Code: code, Info: info, } }
// ConnectStream implements StreamConnector.ConnectStream. func (st *state) ConnectStream(path string, attrs url.Values) (base.Stream, error) { if !st.isLoggedIn() { return nil, errors.New("cannot use ConnectStream without logging in") } // We use the standard "macaraq" macaroon authentication dance here. // That is, we attach any macaroons we have to the initial request, // and if that succeeds, all's good. If it fails with a DischargeRequired // error, the response will contain a macaroon that, when discharged, // may allow access, so we discharge it (using bakery.Client.HandleError) // and try the request again. conn, err := st.connectStream(path, attrs) if err == nil { return conn, err } if params.ErrCode(err) != params.CodeDischargeRequired { return nil, errors.Trace(err) } if err := st.bakeryClient.HandleError(st.cookieURL, bakeryError(err)); err != nil { return nil, errors.Trace(err) } // Try again with the discharged macaroon. conn, err = st.connectStream(path, attrs) if err != nil { return nil, errors.Trace(err) } return conn, nil }
// NewAPIRoot returns a new connection to the API server for the given // model or controller. func (c *JujuCommandBase) NewAPIRoot( store jujuclient.ClientStore, controllerName, modelName string, ) (api.Connection, error) { accountDetails, err := store.AccountDetails(controllerName) if err != nil && !errors.IsNotFound(err) { return nil, errors.Trace(err) } // If there are no account details or there's no logged-in // user or the user is external, then trigger macaroon authentication // by using an empty AccountDetails. if accountDetails == nil || accountDetails.User == "" { accountDetails = &jujuclient.AccountDetails{} } else { u := names.NewUserTag(accountDetails.User) if !u.IsLocal() { accountDetails = &jujuclient.AccountDetails{} } } param, err := c.NewAPIConnectionParams( store, controllerName, modelName, accountDetails, ) if err != nil { return nil, errors.Trace(err) } conn, err := juju.NewAPIConnection(param) if modelName != "" && params.ErrCode(err) == params.CodeModelNotFound { return nil, c.missingModelError(store, controllerName, modelName) } return conn, err }
func (s *apiclientSuite) TestOpenHonorsEnvironTag(c *gc.C) { info := s.APIInfo(c) // TODO(jam): 2014-06-05 http://pad.lv/1326802 // we want to test this eventually, but for now s.APIInfo uses // conn.StateInfo() which doesn't know about EnvironTag. // c.Check(info.EnvironTag, gc.Equals, env.Tag()) // c.Assert(info.EnvironTag, gc.Not(gc.Equals), "") // We start by ensuring we have an invalid tag, and Open should fail. info.EnvironTag = names.NewEnvironTag("bad-tag") _, err := api.Open(info, api.DialOpts{}) c.Check(err, gc.ErrorMatches, `unknown environment: "bad-tag"`) c.Check(params.ErrCode(err), gc.Equals, params.CodeNotFound) // Now set it to the right tag, and we should succeed. info.EnvironTag = s.State.EnvironTag() st, err := api.Open(info, api.DialOpts{}) c.Assert(err, jc.ErrorIsNil) st.Close() // Backwards compatibility, we should succeed if we do not set an // environ tag info.EnvironTag = names.NewEnvironTag("") st, err = api.Open(info, api.DialOpts{}) c.Assert(err, jc.ErrorIsNil) st.Close() }
// UploadTools uploads tools at the specified location to the API server over HTTPS. func (c *Client) UploadTools(r io.ReadSeeker, vers version.Binary, additionalSeries ...string) (*tools.Tools, error) { endpoint := fmt.Sprintf("/tools?binaryVersion=%s&series=%s", vers, strings.Join(additionalSeries, ",")) req, err := http.NewRequest("POST", endpoint, nil) if err != nil { return nil, errors.Annotate(err, "cannot create upload request") } req.Header.Set("Content-Type", "application/x-tar-gz") httpClient, err := c.st.HTTPClient() if err != nil { return nil, errors.Trace(err) } var resp params.ToolsResult err = httpClient.Do(req, r, &resp) if err != nil { msg := err.Error() if params.ErrCode(err) == "" && strings.Contains(msg, params.CodeOperationBlocked) { // We're probably talking to an old version of the API server // that doesn't provide error codes. // See https://bugs.launchpad.net/juju-core/+bug/1499277 err = ¶ms.Error{ Code: params.CodeOperationBlocked, Message: msg, } } return nil, errors.Trace(err) } return resp.Tools, nil }
func singletonError(err error) (error, bool) { errCode := params.ErrCode(err) for singleton, code := range singletonErrorCodes { if errCode == code && singleton.Error() == err.Error() { return singleton, true } } return nil, false }
func (s *loginSuite) TestBadLogin(c *gc.C) { // Start our own server so we can control when the first login // happens. Otherwise in JujuConnSuite.SetUpTest api.Open is // called with user-admin permissions automatically. info, cleanup := s.setupServerWithValidator(c, nil) defer cleanup() adminUser := s.AdminUserTag(c) for i, t := range []struct { tag names.Tag password string err error code string }{{ tag: adminUser, password: "******", err: &rpc.RequestError{ Message: "invalid entity name or password", Code: "unauthorized access", }, code: params.CodeUnauthorized, }, { tag: names.NewUserTag("unknown"), password: "******", err: &rpc.RequestError{ Message: "invalid entity name or password", Code: "unauthorized access", }, code: params.CodeUnauthorized, }} { c.Logf("test %d; entity %q; password %q", i, t.tag, t.password) func() { // Open the API without logging in, so we can perform // operations on the connection before calling Login. st := s.openAPIWithoutLogin(c, info) defer st.Close() _, err := apimachiner.NewState(st).Machine(names.NewMachineTag("0")) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown object type "Machiner"`, Code: "not implemented", }) // Since these are user login tests, the nonce is empty. err = st.Login(t.tag, t.password, "", nil) c.Assert(errors.Cause(err), gc.DeepEquals, t.err) c.Assert(params.ErrCode(err), gc.Equals, t.code) _, err = apimachiner.NewState(st).Machine(names.NewMachineTag("0")) c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ Message: `unknown object type "Machiner"`, Code: "not implemented", }) }() } }
// WaitForAgentInitialisation polls the bootstrapped controller with a read-only // command which will fail until the controller is fully initialised. // TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose. func WaitForAgentInitialisation(ctx *cmd.Context, c *modelcmd.ModelCommandBase, controllerName, hostedModelName string) error { // TODO(katco): 2016-08-09: lp:1611427 attempts := utils.AttemptStrategy{ Min: bootstrapReadyPollCount, Delay: bootstrapReadyPollDelay, } var ( apiAttempts int err error ) // Make a best effort to find the new controller address so we can print it. addressInfo := "" controller, err := c.ClientStore().ControllerByName(controllerName) if err == nil && len(controller.APIEndpoints) > 0 { addr, err := network.ParseHostPort(controller.APIEndpoints[0]) if err == nil { addressInfo = fmt.Sprintf(" at %s", addr.Address.Value) } } ctx.Infof("Contacting Juju controller%s to verify accessibility...", addressInfo) apiAttempts = 1 for attempt := attempts.Start(); attempt.Next(); apiAttempts++ { err = tryAPI(c) if err == nil { ctx.Infof("Bootstrap complete, %q controller now available.", controllerName) ctx.Infof("Controller machines are in the %q model.", bootstrap.ControllerModelName) ctx.Infof("Initial model %q added.", hostedModelName) break } // As the API server is coming up, it goes through a number of steps. // Initially the upgrade steps run, but the api server allows some // calls to be processed during the upgrade, but not the list blocks. // Logins are also blocked during space discovery. // It is also possible that the underlying database causes connections // to be dropped as it is initialising, or reconfiguring. These can // lead to EOF or "connection is shut down" error messages. We skip // these too, hoping that things come back up before the end of the // retry poll count. errorMessage := errors.Cause(err).Error() switch { case errors.Cause(err) == io.EOF, strings.HasSuffix(errorMessage, "connection is shut down"), strings.HasSuffix(errorMessage, "no api connection available"), strings.Contains(errorMessage, "spaces are still being discovered"): ctx.Verbosef("Still waiting for API to become available") continue case params.ErrCode(err) == params.CodeUpgradeInProgress: ctx.Verbosef("Still waiting for API to become available: %v", err) continue } break } return errors.Annotatef(err, "unable to contact api server after %d attempts", apiAttempts) }
func (s *loginSuite) TestBadLogin(c *gc.C) { // Start our own server so we can control when the first login // happens. Otherwise in JujuConnSuite.SetUpTest api.Open is // called with user-admin permissions automatically. info, cleanup := s.setupServer(c) defer cleanup() adminUser := s.AdminUserTag(c) for i, t := range []struct { tag string password string err string code string }{{ tag: adminUser.String(), password: "******", err: "invalid entity name or password", code: params.CodeUnauthorized, }, { tag: "user-unknown", password: "******", err: "invalid entity name or password", code: params.CodeUnauthorized, }, { tag: "bar", password: "******", err: `"bar" is not a valid tag`, }} { c.Logf("test %d; entity %q; password %q", i, t.tag, t.password) // Note that Open does not log in if the tag and password // are empty. This allows us to test operations on the connection // before calling Login, which we could not do if Open // always logged in. info.Tag = nil info.Password = "" func() { st, err := api.Open(info, fastDialOpts) c.Assert(err, gc.IsNil) defer st.Close() _, err = st.Machiner().Machine(names.NewMachineTag("0")) c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) // Since these are user login tests, the nonce is empty. err = st.Login(t.tag, t.password, "") c.Assert(err, gc.ErrorMatches, t.err) c.Assert(params.ErrCode(err), gc.Equals, t.code) _, err = st.Machiner().Machine(names.NewMachineTag("0")) c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) }() } }
func (c *showControllerCommand) agentVersion(client ControllerAccessAPI, ctx *cmd.Context) string { var ver string mc, err := client.ModelConfig() if err != nil { code := params.ErrCode(err) if code != "" { ver = fmt.Sprintf("(%s)", code) } else { fmt.Fprintln(ctx.Stderr, err) ver = "(error)" } return ver } return mc["agent-version"].(string) }
func (c *showControllerCommand) userAccess(client ControllerAccessAPI, ctx *cmd.Context, user string) string { var access string userAccess, err := client.GetControllerAccess(user) if err == nil { access = string(userAccess) } else { code := params.ErrCode(err) if code != "" { access = fmt.Sprintf("(%s)", code) } else { fmt.Fprintln(ctx.Stderr, err) access = "(error)" } } return access }
// bakeryError translates any discharge-required error into // an error value that the httpbakery package will recognize. // Other errors are returned unchanged. func bakeryError(err error) error { if params.ErrCode(err) != params.CodeDischargeRequired { return err } errResp := errors.Cause(err).(*params.Error) if errResp.Info == nil { return errors.Annotatef(err, "no error info found in discharge-required response error") } // It's a discharge-required error, so make an appropriate httpbakery // error from it. return &httpbakery.Error{ Message: err.Error(), Code: httpbakery.ErrDischargeRequired, Info: &httpbakery.ErrorInfo{ Macaroon: errResp.Info.Macaroon, MacaroonPath: errResp.Info.MacaroonPath, }, } }
func (s *keyManagerSuite) TestAddJujuSystemKeyNotMachine(c *gc.C) { anAuthoriser := s.authoriser anAuthoriser.EnvironManager = true anAuthoriser.Tag = names.NewUnitTag("wordpress/0") var err error s.keymanager, err = keymanager.NewKeyManagerAPI(s.State, s.resources, anAuthoriser) c.Assert(err, jc.ErrorIsNil) key1 := sshtesting.ValidKeyOne.Key s.setAuthorisedKeys(c, key1) newKey := sshtesting.ValidKeyThree.Key + " juju-system-key" args := params.ModifyUserSSHKeys{ User: "******", Keys: []string{newKey}, } _, err = s.keymanager.AddKeys(args) c.Assert(err, gc.ErrorMatches, "permission denied") c.Assert(params.ErrCode(err), gc.Equals, params.CodeUnauthorized) s.assertEnvironKeys(c, []string{key1}) }
// newTimedModelStatus returns a function which waits a given period of time // before querying the API server for the status of a model. func newTimedModelStatus(ctx *cmd.Context, api DestroyModelAPI, tag names.ModelTag, sleepFunc func(time.Duration)) func(time.Duration) *modelData { return func(wait time.Duration) *modelData { sleepFunc(wait) status, err := api.ModelStatus(tag) if err != nil { if params.ErrCode(err) != params.CodeNotFound { ctx.Infof("Unable to get the model status from the API: %v.", err) } return nil } if l := len(status); l != 1 { ctx.Infof("error finding model status: expected one result, got %d", l) return nil } return &modelData{ machineCount: status[0].HostedMachineCount, applicationCount: status[0].ServiceCount, } } }
// waitForAgentInitialisation polls the bootstrapped controller with a read-only // command which will fail until the controller is fully initialised. // TODO(wallyworld) - add a bespoke command to maybe the admin facade for this purpose. func (c *bootstrapCommand) waitForAgentInitialisation(ctx *cmd.Context) error { attempts := utils.AttemptStrategy{ Min: bootstrapReadyPollCount, Delay: bootstrapReadyPollDelay, } var ( apiAttempts int err error ) apiAttempts = 1 for attempt := attempts.Start(); attempt.Next(); apiAttempts++ { err = c.tryAPI() if err == nil { ctx.Infof("Bootstrap complete, %s now available.", c.controllerName) break } // As the API server is coming up, it goes through a number of steps. // Initially the upgrade steps run, but the api server allows some // calls to be processed during the upgrade, but not the list blocks. // Logins are also blocked during space discovery. // It is also possible that the underlying database causes connections // to be dropped as it is initialising, or reconfiguring. These can // lead to EOF or "connection is shut down" error messages. We skip // these too, hoping that things come back up before the end of the // retry poll count. errorMessage := errors.Cause(err).Error() switch { case errors.Cause(err) == io.EOF, strings.HasSuffix(errorMessage, "connection is shut down"), strings.Contains(errorMessage, "spaces are still being discovered"): ctx.Infof("Waiting for API to become available") continue case params.ErrCode(err) == params.CodeUpgradeInProgress: ctx.Infof("Waiting for API to become available: %v", err) continue } break } return errors.Annotatef(err, "unable to contact api server after %d attempts", apiAttempts) }
// ServerError returns an error suitable for returning to an API // client, with an error code suitable for various kinds of errors // generated in packages outside the API. func ServerError(err error) *params.Error { if err == nil { return nil } msg := err.Error() // Skip past annotations when looking for the code. err = errors.Cause(err) code, ok := singletonCode(err) switch { case ok: case errors.IsUnauthorized(err): code = params.CodeUnauthorized case errors.IsNotFound(err): code = params.CodeNotFound case errors.IsAlreadyExists(err): code = params.CodeAlreadyExists case errors.IsNotAssigned(err): code = params.CodeNotAssigned case state.IsHasAssignedUnitsError(err): code = params.CodeHasAssignedUnits case IsNoAddressSetError(err): code = params.CodeNoAddressSet case errors.IsNotProvisioned(err): code = params.CodeNotProvisioned case state.IsUpgradeInProgressError(err): code = params.CodeUpgradeInProgress case state.IsHasAttachmentsError(err): code = params.CodeMachineHasAttachedStorage case IsUnknownEnviromentError(err): code = params.CodeNotFound case errors.IsNotSupported(err): code = params.CodeNotSupported default: code = params.ErrCode(err) } return ¶ms.Error{ Message: msg, Code: code, } }