From 8ebe6d315dc9551b435f2a187cba3c10822dae72 Mon Sep 17 00:00:00 2001 From: Alastair Flynn Date: Fri, 6 Dec 2024 09:57:31 +0000 Subject: [PATCH 1/2] feat: wire up the uniter with the domain application watcher Before this change, the uniter API embedded the AgentEntityWatcher. This provided the Watch method on the api and would look up the entity in state and return a notify watcher. The attach-resource command increments the CharmModifiedVersion in state and therefor triggers the watcher. With resources being moved to DQLite, and the charm_modified_version integer now being in the application table in domain, this watcher needs to be watching this table instead. Not all application functionality has yet been moved over to DQLite. This could pose an issue, as the uniter checks the CharmModifiedVersion and the CharmURL when the watcher is triggered. The charm URL should work as expected as this is currently written in DQLite. The CharmModifiedVersion is incremented in state in two places. I found this via the incCharmModifiedVersionOps method and couldn't find any other places it was set. They are: When a resource is set - this will be take care of in the upcoming resources work. When a charm is changed with SetCharm (there are two callsites in this method to incCharmModifiedVersionOps) - The only call remaining for this method is doulbe written to DQLite. Watching the application table in DQLite instead of these should be safe for the reasons above. The change made is to remove the AgentEntityWatcher from the UniterAPI and implement a Watch and WatchApplication on the UniterAPI directly. The Watch method is kept only so that the uniter API can work with 3.x clients. The ApplicationWatcher uses the watcher from the application service, and the Watch method checks if the tag is an application, if it is, the Watcher used is from the application service. If it is a unit watcher, it still watches mongo state. The uniter call to CharmModifiedVersion is also updated to return the charm modifed version from the DQLite application table rather than state. --- api/agent/uniter/application.go | 3 +- api/agent/uniter/application_test.go | 12 +- api/agent/uniter/uniter.go | 16 ++ api/facadeversions.go | 2 +- apiserver/facades/agent-schema.json | 22 +-- apiserver/facades/agent/uniter/export_test.go | 1 + .../facades/agent/uniter/package_test.go | 10 ++ apiserver/facades/agent/uniter/register.go | 13 +- apiserver/facades/agent/uniter/service.go | 18 +++ apiserver/facades/agent/uniter/uniter.go | 142 ++++++++++++++++-- apiserver/facades/agent/uniter/uniter_test.go | 52 ++++++- domain/application/service/application.go | 2 +- 12 files changed, 247 insertions(+), 46 deletions(-) diff --git a/api/agent/uniter/application.go b/api/agent/uniter/application.go index c49b5e02a26..a0826fa4274 100644 --- a/api/agent/uniter/application.go +++ b/api/agent/uniter/application.go @@ -10,7 +10,6 @@ import ( "github.com/juju/errors" "github.com/juju/names/v5" - "github.com/juju/juju/api/common" "github.com/juju/juju/core/life" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" @@ -44,7 +43,7 @@ func (s *Application) String() string { // Watch returns a watcher for observing changes to an application. func (s *Application) Watch(ctx context.Context) (watcher.NotifyWatcher, error) { - return common.Watch(ctx, s.client.facade, "Watch", s.tag) + return s.client.watchApplication(ctx, s.tag) } // Life returns the application's current life state. diff --git a/api/agent/uniter/application_test.go b/api/agent/uniter/application_test.go index 3e24ebd55ad..1582e2410a2 100644 --- a/api/agent/uniter/application_test.go +++ b/api/agent/uniter/application_test.go @@ -53,13 +53,11 @@ func (s *applicationSuite) apiCallerFunc(c *gc.C) basetesting.APICallerFunc { Life: s.life, }}, } - case "Watch": - c.Assert(arg, jc.DeepEquals, params.Entities{Entities: []params.Entity{{Tag: "application-mysql"}}}) - c.Assert(result, gc.FitsTypeOf, ¶ms.NotifyWatchResults{}) - *(result.(*params.NotifyWatchResults)) = params.NotifyWatchResults{ - Results: []params.NotifyWatchResult{{ - NotifyWatcherId: "1", - }}, + case "WatchApplication": + c.Assert(arg, jc.DeepEquals, params.Entity{Tag: "application-mysql"}) + c.Assert(result, gc.FitsTypeOf, ¶ms.NotifyWatchResult{}) + *(result.(*params.NotifyWatchResult)) = params.NotifyWatchResult{ + NotifyWatcherId: "1", } case "CharmURL": c.Assert(arg, jc.DeepEquals, params.Entities{Entities: []params.Entity{{Tag: "application-mysql"}}}) diff --git a/api/agent/uniter/uniter.go b/api/agent/uniter/uniter.go index a389e595312..3ddeae684d2 100644 --- a/api/agent/uniter/uniter.go +++ b/api/agent/uniter/uniter.go @@ -165,6 +165,22 @@ func (client *Client) getOneAction(ctx context.Context, tag *names.ActionTag) (p return result, nil } +// watchApplication starts a NotifyWatcher for the specified application +func (client *Client) watchApplication(ctx context.Context, tag names.ApplicationTag) (watcher.NotifyWatcher, error) { + args := params.Entity{Tag: tag.String()} + var result params.NotifyWatchResult + + err := client.facade.FacadeCall(ctx, "WatchApplication", args, &result) + if err != nil { + return nil, errors.Trace(apiservererrors.RestoreError(err)) + } + + if result.Error != nil { + return nil, result.Error + } + return apiwatcher.NewNotifyWatcher(client.facade.RawAPICaller(), result), nil +} + // LeadershipSettingsAccessor is an interface that allows us not to have // to use the concrete `api/uniter/LeadershipSettings` type, thus // simplifying testing. diff --git a/api/facadeversions.go b/api/facadeversions.go index 2e54e47aeb4..56d4e209274 100644 --- a/api/facadeversions.go +++ b/api/facadeversions.go @@ -118,7 +118,7 @@ var facadeVersions = facades.FacadeVersions{ "Subnets": {5}, "Undertaker": {1}, "UnitAssigner": {1}, - "Uniter": {19}, + "Uniter": {19, 20}, "Upgrader": {1}, "UpgradeSteps": {3}, "UserManager": {3}, diff --git a/apiserver/facades/agent-schema.json b/apiserver/facades/agent-schema.json index 9ea903c1318..daff76a5a7e 100644 --- a/apiserver/facades/agent-schema.json +++ b/apiserver/facades/agent-schema.json @@ -10747,7 +10747,7 @@ { "Name": "Uniter", "Description": "", - "Version": 19, + "Version": 20, "AvailableTo": [ "controller-machine-agent", "machine-agent", @@ -11448,33 +11448,33 @@ } } }, - "Watch": { + "WatchAPIHostPorts": { "type": "object", "properties": { - "Params": { - "$ref": "#/definitions/Entities" - }, "Result": { - "$ref": "#/definitions/NotifyWatchResults" + "$ref": "#/definitions/NotifyWatchResult" } } }, - "WatchAPIHostPorts": { + "WatchActionNotifications": { "type": "object", "properties": { + "Params": { + "$ref": "#/definitions/Entities" + }, "Result": { - "$ref": "#/definitions/NotifyWatchResult" + "$ref": "#/definitions/StringsWatchResults" } } }, - "WatchActionNotifications": { + "WatchApplication": { "type": "object", "properties": { "Params": { - "$ref": "#/definitions/Entities" + "$ref": "#/definitions/Entity" }, "Result": { - "$ref": "#/definitions/StringsWatchResults" + "$ref": "#/definitions/NotifyWatchResult" } } }, diff --git a/apiserver/facades/agent/uniter/export_test.go b/apiserver/facades/agent/uniter/export_test.go index 69a3d0b1bbf..6bf20a559ef 100644 --- a/apiserver/facades/agent/uniter/export_test.go +++ b/apiserver/facades/agent/uniter/export_test.go @@ -21,6 +21,7 @@ var ( WatchStorageAttachment = watchStorageAttachment NewUniterAPI = newUniterAPI + NewUniterAPIv19 = newUniterAPIv19 NewUniterAPIWithServices = newUniterAPIWithServices ) diff --git a/apiserver/facades/agent/uniter/package_test.go b/apiserver/facades/agent/uniter/package_test.go index 05d0fefce1d..92d98147464 100644 --- a/apiserver/facades/agent/uniter/package_test.go +++ b/apiserver/facades/agent/uniter/package_test.go @@ -174,6 +174,16 @@ func (s *uniterSuiteBase) newUniterAPI(c *gc.C, st *state.State, auth facade.Aut return uniterAPI } +func (s *uniterSuiteBase) newUniterAPIv19(c *gc.C, st *state.State, auth facade.Authorizer) *uniter.UniterAPIv19 { + facadeContext := s.facadeContext(c) + facadeContext.State_ = st + facadeContext.Auth_ = auth + facadeContext.LeadershipRevoker_ = s.leadershipRevoker + uniterAPI, err := uniter.NewUniterAPIv19(context.Background(), facadeContext) + c.Assert(err, jc.ErrorIsNil) + return uniterAPI +} + func (s *uniterSuiteBase) addRelation(c *gc.C, first, second string) *state.Relation { st := s.ControllerModel(c).State() eps, err := st.InferEndpoints(first, second) diff --git a/apiserver/facades/agent/uniter/register.go b/apiserver/facades/agent/uniter/register.go index ee671b555c4..5c582d03437 100644 --- a/apiserver/facades/agent/uniter/register.go +++ b/apiserver/facades/agent/uniter/register.go @@ -22,10 +22,21 @@ import ( // Register is called to expose a package of facades onto a given registry. func Register(registry facade.FacadeRegistry) { registry.MustRegister("Uniter", 19, func(stdCtx context.Context, ctx facade.ModelContext) (facade.Facade, error) { + return newUniterAPIv19(stdCtx, ctx) + }, reflect.TypeOf((*UniterAPIv19)(nil))) + registry.MustRegister("Uniter", 20, func(stdCtx context.Context, ctx facade.ModelContext) (facade.Facade, error) { return newUniterAPI(stdCtx, ctx) }, reflect.TypeOf((*UniterAPI)(nil))) } +func newUniterAPIv19(stdCtx context.Context, ctx facade.ModelContext) (*UniterAPIv19, error) { + api, err := newUniterAPI(stdCtx, ctx) + if err != nil { + return nil, err + } + return &UniterAPIv19{UniterAPI: api}, nil +} + // newUniterAPI creates a new instance of the core Uniter API. func newUniterAPI(stdCtx context.Context, ctx facade.ModelContext) (*UniterAPI, error) { domainServices := ctx.DomainServices() @@ -131,7 +142,6 @@ func newUniterAPIWithServices( } logger := context.Logger().Child("uniter") return &UniterAPI{ - AgentEntityWatcher: common.NewAgentEntityWatcher(st, watcherRegistry, accessUnitOrApplication), APIAddresser: common.NewAPIAddresser(systemState, resources), ModelConfigWatcher: common.NewModelConfigWatcher(modelConfigService, context.WatcherRegistry()), RebootRequester: common.NewRebootRequester(machineService, accessMachine), @@ -169,5 +179,6 @@ func newUniterAPIWithServices( StorageAPI: storageAPI, logger: logger, store: context.ObjectStore(), + watcherRegistry: watcherRegistry, }, nil } diff --git a/apiserver/facades/agent/uniter/service.go b/apiserver/facades/agent/uniter/service.go index c06f4e94209..1083e253aa3 100644 --- a/apiserver/facades/agent/uniter/service.go +++ b/apiserver/facades/agent/uniter/service.go @@ -8,6 +8,7 @@ import ( "github.com/juju/juju/cloud" "github.com/juju/juju/controller" + coreapplication "github.com/juju/juju/core/application" "github.com/juju/juju/core/credential" "github.com/juju/juju/core/leadership" "github.com/juju/juju/core/life" @@ -73,6 +74,23 @@ type ApplicationService interface { // DestroyUnit prepares a unit for removal from the model. DestroyUnit(ctx context.Context, unitName coreunit.Name) error + + // WatchApplication returns a NotifyWatcher for changes to the application. + WatchApplication(ctx context.Context, name string) (watcher.NotifyWatcher, error) + + // GetApplicationIDByUnitName returns the application ID for the named unit. + // + // Returns [applicationerrors.UnitNotFound] if the unit is not found + GetApplicationIDByUnitName(ctx context.Context, unitName coreunit.Name) (coreapplication.ID, error) + + // GetApplicationIDByName returns an application ID by application name. + // + // Returns [applicationerrors.ApplicationNotFound] if the application is not found. + GetApplicationIDByName(ctx context.Context, name string) (coreapplication.ID, error) + + // GetCharmModifiedVersion looks up the charm modified version of the given + // application. + GetCharmModifiedVersion(ctx context.Context, id coreapplication.ID) (int, error) } // UnitStateService describes the ability to retrieve and persist diff --git a/apiserver/facades/agent/uniter/uniter.go b/apiserver/facades/agent/uniter/uniter.go index 854e63e1976..b00384824cf 100644 --- a/apiserver/facades/agent/uniter/uniter.go +++ b/apiserver/facades/agent/uniter/uniter.go @@ -21,7 +21,9 @@ import ( apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/apiserver/facade" leadershipapiserver "github.com/juju/juju/apiserver/facades/agent/leadership" + "github.com/juju/juju/apiserver/internal" "github.com/juju/juju/caas" + "github.com/juju/juju/core/application" "github.com/juju/juju/core/leadership" "github.com/juju/juju/core/life" corelogger "github.com/juju/juju/core/logger" @@ -30,6 +32,7 @@ import ( "github.com/juju/juju/core/objectstore" "github.com/juju/juju/core/status" coreunit "github.com/juju/juju/core/unit" + corewatcher "github.com/juju/juju/core/watcher" applicationerrors "github.com/juju/juju/domain/application/errors" machineerrors "github.com/juju/juju/domain/machine/errors" "github.com/juju/juju/domain/unitstate" @@ -46,7 +49,6 @@ type UniterAPI struct { *StatusAPI *StorageAPI - *common.AgentEntityWatcher *common.APIAddresser *common.ModelConfigWatcher *common.RebootRequester @@ -66,6 +68,7 @@ type UniterAPI struct { accessUnitOrApplication common.GetAuthFunc accessMachine common.GetAuthFunc containerBrokerFunc caas.NewContainerBrokerFunc + watcherRegistry facade.WatcherRegistry cloudService CloudService credentialService CredentialService @@ -89,6 +92,10 @@ type UniterAPI struct { logger corelogger.Logger } +type UniterAPIv19 struct { + *UniterAPI +} + // EnsureDead calls EnsureDead on each given unit from state. // If it's Alive, nothing will happen. func (u *UniterAPI) EnsureDead(ctx context.Context, args params.Entities) (params.ErrorResults, error) { @@ -617,7 +624,7 @@ func (u *UniterAPI) CharmModifiedVersion(ctx context.Context, args params.Entiti return results, err } for i, entity := range args.Entities { - ver, err := u.charmModifiedVersion(entity.Tag, canAccess) + ver, err := u.charmModifiedVersion(ctx, entity.Tag, canAccess) if err != nil { results.Results[i].Error = apiservererrors.ServerError(err) continue @@ -627,7 +634,11 @@ func (u *UniterAPI) CharmModifiedVersion(ctx context.Context, args params.Entiti return results, nil } -func (u *UniterAPI) charmModifiedVersion(tagStr string, canAccess func(names.Tag) bool) (int, error) { +func (u *UniterAPI) charmModifiedVersion( + ctx context.Context, + tagStr string, + canAccess func(names.Tag) bool, +) (int, error) { tag, err := names.ParseTag(tagStr) if err != nil { return -1, apiservererrors.ErrPerm @@ -635,23 +646,37 @@ func (u *UniterAPI) charmModifiedVersion(tagStr string, canAccess func(names.Tag if !canAccess(tag) { return -1, apiservererrors.ErrPerm } - unitOrApplication, err := u.st.FindEntity(tag) - if err != nil { - return -1, err - } - var application *state.Application - switch entity := unitOrApplication.(type) { - case *state.Application: - application = entity - case *state.Unit: - application, err = entity.Application() + + var id application.ID + switch tag.(type) { + case names.ApplicationTag: + id, err = u.applicationService.GetApplicationIDByName(ctx, tag.Id()) + if errors.Is(err, applicationerrors.ApplicationNotFound) { + // Return an error that also matches a generic not found error. + return -1, internalerrors.Join(err, errors.Hide(errors.NotFound)) + } else if err != nil { + return -1, err + } + case names.UnitTag: + name, err := coreunit.NewName(tag.Id()) if err != nil { return -1, err } + id, err = u.applicationService.GetApplicationIDByUnitName(ctx, name) + if errors.Is(err, applicationerrors.UnitNotFound) { + // Return an error that also matches a generic not found error. + return -1, internalerrors.Join(err, errors.Hide(errors.NotFound)) + } else if err != nil { + return -1, err + } default: - return -1, errors.BadRequestf("type %T does not have a CharmModifiedVersion", entity) + return -1, errors.BadRequestf("type %s does not have a CharmModifiedVersion", tag.Kind()) + } + charmModifiedVersion, err := u.applicationService.GetCharmModifiedVersion(ctx, id) + if err != nil { + return -1, err } - return application.CharmModifiedVersion(), nil + return charmModifiedVersion, nil } // CharmURL returns the charm URL for all given units or applications. @@ -2835,3 +2860,90 @@ func (u *UniterAPI) APIAddresses(ctx context.Context) (result params.StringsResu return u.APIAddresser.APIAddresses(ctx, controllerConfig) } + +// WatchApplication starts an NotifyWatcher for an application. +func (u *UniterAPI) WatchApplication(ctx context.Context, entity params.Entity) (params.NotifyWatchResult, error) { + canWatch, err := u.accessApplication() + if err != nil { + return params.NotifyWatchResult{}, errors.Trace(err) + } + + tag, err := names.ParseApplicationTag(entity.Tag) + if err != nil { + return params.NotifyWatchResult{Error: apiservererrors.ServerError(apiservererrors.ErrPerm)}, nil + } + + if !canWatch(tag) { + return params.NotifyWatchResult{Error: apiservererrors.ServerError(apiservererrors.ErrPerm)}, nil + } + + watcher, err := u.applicationService.WatchApplication(ctx, tag.Id()) + if err != nil { + return params.NotifyWatchResult{Error: apiservererrors.ServerError(err)}, nil + } + + id, _, err := internal.EnsureRegisterWatcher[struct{}](ctx, u.watcherRegistry, watcher) + return params.NotifyWatchResult{ + NotifyWatcherId: id, + Error: apiservererrors.ServerError(err), + }, nil +} + +// Watch starts an NotifyWatcher for a unit or application. +// This is being deprecated in favour of separate WatchUnit and WatchApplication +// methods. +func (u *UniterAPIv19) Watch(ctx context.Context, args params.Entities) (params.NotifyWatchResults, error) { + result := params.NotifyWatchResults{ + Results: make([]params.NotifyWatchResult, len(args.Entities)), + } + if len(args.Entities) == 0 { + return result, nil + } + canWatch, err := u.accessUnitOrApplication() + if err != nil { + return params.NotifyWatchResults{}, errors.Trace(err) + } + for i, entity := range args.Entities { + tag, err := names.ParseTag(entity.Tag) + if err != nil { + result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) + continue + } + + if !canWatch(tag) { + result.Results[i].Error = apiservererrors.ServerError(apiservererrors.ErrPerm) + continue + } + + var watcher corewatcher.NotifyWatcher + switch tag.(type) { + case names.ApplicationTag: + watcher, err = u.applicationService.WatchApplication(ctx, tag.Id()) + default: + watcher, err = u.watchUnit(tag) + } + if err != nil { + result.Results[i].Error = apiservererrors.ServerError(err) + continue + } + + id, _, err := internal.EnsureRegisterWatcher[struct{}](ctx, u.watcherRegistry, watcher) + result.Results[i].NotifyWatcherId = id + result.Results[i].Error = apiservererrors.ServerError(err) + } + return result, nil +} + +// watchUnit returns a state notify watcher for the given unit. +func (u *UniterAPI) watchUnit(tag names.Tag) (corewatcher.NotifyWatcher, error) { + entity0, err := u.st.FindEntity(tag) + if err != nil { + return nil, err + } + entity, ok := entity0.(state.NotifyWatcherFactory) + if !ok { + return nil, apiservererrors.NotSupportedError(tag, "watching") + } + watcher := entity.Watch() + return watcher, err +} diff --git a/apiserver/facades/agent/uniter/uniter_test.go b/apiserver/facades/agent/uniter/uniter_test.go index 237505b470f..b9e07e6a4d3 100644 --- a/apiserver/facades/agent/uniter/uniter_test.go +++ b/apiserver/facades/agent/uniter/uniter_test.go @@ -352,7 +352,7 @@ func (s *uniterSuite) TestEnsureDead(c *gc.C) { func (s *uniterSuite) TestWatch(c *gc.C) { defer s.setUpMocks(c).Finish() // Recreate the uniter API with the mocks initialized. - s.uniter = s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + uniterAPI := s.newUniterAPIv19(c, s.ControllerModel(c).State(), s.authorizer) args := params.Entities{Entities: []params.Entity{ {Tag: "unit-mysql-0"}, {Tag: "unit-wordpress-0"}, @@ -360,14 +360,10 @@ func (s *uniterSuite) TestWatch(c *gc.C) { {Tag: "application-mysql"}, {Tag: "application-wordpress"}, {Tag: "application-foo"}, - // TODO(dfc) these aren't valid tags any more - // but I hope to restore this test when params.Entity takes - // tags, not strings, which is coming soon. - // {Tag: "just-foo"}, }} s.watcherRegistry.EXPECT().Register(gomock.Any()).Return("1", nil) s.watcherRegistry.EXPECT().Register(gomock.Any()).Return("2", nil) - result, err := s.uniter.Watch(context.Background(), args) + result, err := uniterAPI.Watch(context.Background(), args) c.Assert(err, jc.ErrorIsNil) c.Assert(result, gc.DeepEquals, params.NotifyWatchResults{ Results: []params.NotifyWatchResult{ @@ -377,12 +373,52 @@ func (s *uniterSuite) TestWatch(c *gc.C) { {Error: apiservertesting.ErrUnauthorized}, {NotifyWatcherId: "2"}, {Error: apiservertesting.ErrUnauthorized}, - // see above - // {Error: apiservertesting.ErrUnauthorized}, }, }) } +func (s *uniterSuite) TestWatchNoArgsNoError(c *gc.C) { + uniterAPI := s.newUniterAPIv19(c, s.ControllerModel(c).State(), s.authorizer) + result, err := uniterAPI.Watch(context.Background(), params.Entities{}) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result.Results, gc.HasLen, 0) +} + +func (s *uniterSuite) TestApplicationWatch(c *gc.C) { + defer s.setUpMocks(c).Finish() + // Recreate the uniter API with the mocks initialized. + uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + args := params.Entity{Tag: "application-wordpress"} + s.watcherRegistry.EXPECT().Register(gomock.Any()).Return("1", nil) + result, err := uniterAPI.WatchApplication(context.Background(), args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, gc.DeepEquals, params.NotifyWatchResult{ + NotifyWatcherId: "1", + }) +} + +func (s *uniterSuite) TestWatchApplicationBadTag(c *gc.C) { + uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + result, err := uniterAPI.WatchApplication(context.Background(), params.Entity{Tag: "bad-tag"}) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{Error: ¶ms.Error{ + Code: params.CodeUnauthorized, + Message: "permission denied", + }}) +} + +func (s *uniterSuite) TestWatchApplicationNotPermission(c *gc.C) { + uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + // Permissions for mysql will be denied by the accessApplication function + // defined in test set up. + result, err := uniterAPI.WatchApplication(context.Background(), params.Entity{Tag: "application-mysql"}) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{Error: ¶ms.Error{ + Code: params.CodeUnauthorized, + Message: "permission denied", + }}) +} + func (s *uniterSuite) TestPublicAddress(c *gc.C) { // Try first without setting an address. args := params.Entities{Entities: []params.Entity{ diff --git a/domain/application/service/application.go b/domain/application/service/application.go index 637f723c8a5..ac6f0632bc9 100644 --- a/domain/application/service/application.go +++ b/domain/application/service/application.go @@ -943,7 +943,7 @@ func (s *Service) UpdateApplicationCharm(ctx context.Context, name string, param return nil } -// GetApplicationIDByName returns a application ID by application name. It +// GetApplicationIDByName returns an application ID by application name. It // returns an error if the application can not be found by the name. // // Returns [applicationerrors.ApplicationNameNotValid] if the name is not valid, From 1736de36d7e932da9327b56fb5e6ac26fbe1a98c Mon Sep 17 00:00:00 2001 From: Alastair Flynn Date: Fri, 6 Dec 2024 12:08:38 +0000 Subject: [PATCH 2/2] feat: add WatchUnit facade to v20 of the uniter api The Watch facade is used by the unit. This facade is being depreicated in favour of seperate WatchUnit and WatchApplication facades. Implement the WatchUnit facade in v20 of the uniter API and leave Watch only on v19. --- api/agent/uniter/application.go | 15 +++- api/agent/uniter/unit.go | 17 +++- api/agent/uniter/unit_test.go | 12 ++- api/agent/uniter/uniter.go | 16 ---- apiserver/facades/agent-schema.json | 11 +++ apiserver/facades/agent/uniter/service.go | 6 +- apiserver/facades/agent/uniter/uniter.go | 30 +++++++ apiserver/facades/agent/uniter/uniter_test.go | 37 ++++++++- .../worker/uniter/relation/resolver_test.go | 81 ++++++++++--------- 9 files changed, 157 insertions(+), 68 deletions(-) diff --git a/api/agent/uniter/application.go b/api/agent/uniter/application.go index a0826fa4274..8110b23429e 100644 --- a/api/agent/uniter/application.go +++ b/api/agent/uniter/application.go @@ -10,6 +10,8 @@ import ( "github.com/juju/errors" "github.com/juju/names/v5" + apiwatcher "github.com/juju/juju/api/watcher" + apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/core/life" "github.com/juju/juju/core/status" "github.com/juju/juju/core/watcher" @@ -43,7 +45,18 @@ func (s *Application) String() string { // Watch returns a watcher for observing changes to an application. func (s *Application) Watch(ctx context.Context) (watcher.NotifyWatcher, error) { - return s.client.watchApplication(ctx, s.tag) + arg := params.Entity{Tag: s.tag.String()} + var result params.NotifyWatchResult + + err := s.client.facade.FacadeCall(ctx, "WatchApplication", arg, &result) + if err != nil { + return nil, errors.Trace(apiservererrors.RestoreError(err)) + } + + if result.Error != nil { + return nil, result.Error + } + return apiwatcher.NewNotifyWatcher(s.client.facade.RawAPICaller(), result), nil } // Life returns the application's current life state. diff --git a/api/agent/uniter/unit.go b/api/agent/uniter/unit.go index b081d6188c1..dd451a4b848 100644 --- a/api/agent/uniter/unit.go +++ b/api/agent/uniter/unit.go @@ -158,9 +158,20 @@ func (u *Unit) EnsureDead(ctx context.Context) error { return result.OneError() } -// Watch returns a watcher for observing changes to the unit. -func (u *Unit) Watch(ctx context.Context) (watcher.NotifyWatcher, error) { - return common.Watch(ctx, u.client.facade, "Watch", u.tag) +// Watch returns a watcher for observing changes to an application. +func (s *Unit) Watch(ctx context.Context) (watcher.NotifyWatcher, error) { + arg := params.Entity{Tag: s.tag.String()} + var result params.NotifyWatchResult + + err := s.client.facade.FacadeCall(ctx, "WatchUnit", arg, &result) + if err != nil { + return nil, errors.Trace(apiservererrors.RestoreError(err)) + } + + if result.Error != nil { + return nil, result.Error + } + return apiwatcher.NewNotifyWatcher(s.client.facade.RawAPICaller(), result), nil } // WatchRelations returns a StringsWatcher that notifies of changes to diff --git a/api/agent/uniter/unit_test.go b/api/agent/uniter/unit_test.go index ce794307003..5dc067948fa 100644 --- a/api/agent/uniter/unit_test.go +++ b/api/agent/uniter/unit_test.go @@ -344,13 +344,11 @@ func (s *unitSuite) TestWatch(c *gc.C) { return nil } c.Assert(objType, gc.Equals, "Uniter") - c.Assert(request, gc.Equals, "Watch") - c.Assert(arg, gc.DeepEquals, params.Entities{Entities: []params.Entity{{Tag: "unit-mysql-0"}}}) - c.Assert(result, gc.FitsTypeOf, ¶ms.NotifyWatchResults{}) - *(result.(*params.NotifyWatchResults)) = params.NotifyWatchResults{ - Results: []params.NotifyWatchResult{{ - NotifyWatcherId: "1", - }}, + c.Assert(request, gc.Equals, "WatchUnit") + c.Assert(arg, gc.DeepEquals, params.Entity{Tag: "unit-mysql-0"}) + c.Assert(result, gc.FitsTypeOf, ¶ms.NotifyWatchResult{}) + *(result.(*params.NotifyWatchResult)) = params.NotifyWatchResult{ + NotifyWatcherId: "1", } return nil }) diff --git a/api/agent/uniter/uniter.go b/api/agent/uniter/uniter.go index 3ddeae684d2..a389e595312 100644 --- a/api/agent/uniter/uniter.go +++ b/api/agent/uniter/uniter.go @@ -165,22 +165,6 @@ func (client *Client) getOneAction(ctx context.Context, tag *names.ActionTag) (p return result, nil } -// watchApplication starts a NotifyWatcher for the specified application -func (client *Client) watchApplication(ctx context.Context, tag names.ApplicationTag) (watcher.NotifyWatcher, error) { - args := params.Entity{Tag: tag.String()} - var result params.NotifyWatchResult - - err := client.facade.FacadeCall(ctx, "WatchApplication", args, &result) - if err != nil { - return nil, errors.Trace(apiservererrors.RestoreError(err)) - } - - if result.Error != nil { - return nil, result.Error - } - return apiwatcher.NewNotifyWatcher(client.facade.RawAPICaller(), result), nil -} - // LeadershipSettingsAccessor is an interface that allows us not to have // to use the concrete `api/uniter/LeadershipSettings` type, thus // simplifying testing. diff --git a/apiserver/facades/agent-schema.json b/apiserver/facades/agent-schema.json index daff76a5a7e..8ea7123d7d8 100644 --- a/apiserver/facades/agent-schema.json +++ b/apiserver/facades/agent-schema.json @@ -11552,6 +11552,17 @@ } } }, + "WatchUnit": { + "type": "object", + "properties": { + "Params": { + "$ref": "#/definitions/Entity" + }, + "Result": { + "$ref": "#/definitions/NotifyWatchResult" + } + } + }, "WatchUnitAddressesHash": { "type": "object", "properties": { diff --git a/apiserver/facades/agent/uniter/service.go b/apiserver/facades/agent/uniter/service.go index 1083e253aa3..03857e0f4df 100644 --- a/apiserver/facades/agent/uniter/service.go +++ b/apiserver/facades/agent/uniter/service.go @@ -80,12 +80,14 @@ type ApplicationService interface { // GetApplicationIDByUnitName returns the application ID for the named unit. // - // Returns [applicationerrors.UnitNotFound] if the unit is not found + // Returns [github.com/juju/juju/domain/application.UnitNotFound] if the + // unit is not found. GetApplicationIDByUnitName(ctx context.Context, unitName coreunit.Name) (coreapplication.ID, error) // GetApplicationIDByName returns an application ID by application name. // - // Returns [applicationerrors.ApplicationNotFound] if the application is not found. + // Returns [github.com/juju/juju/domain/application.ApplicationNotFound] if + // the application is not found. GetApplicationIDByName(ctx context.Context, name string) (coreapplication.ID, error) // GetCharmModifiedVersion looks up the charm modified version of the given diff --git a/apiserver/facades/agent/uniter/uniter.go b/apiserver/facades/agent/uniter/uniter.go index b00384824cf..39aef201b7a 100644 --- a/apiserver/facades/agent/uniter/uniter.go +++ b/apiserver/facades/agent/uniter/uniter.go @@ -2862,6 +2862,7 @@ func (u *UniterAPI) APIAddresses(ctx context.Context) (result params.StringsResu } // WatchApplication starts an NotifyWatcher for an application. +// WatchApplication is not implemented in the UniterAPIv19 facade. func (u *UniterAPI) WatchApplication(ctx context.Context, entity params.Entity) (params.NotifyWatchResult, error) { canWatch, err := u.accessApplication() if err != nil { @@ -2889,6 +2890,35 @@ func (u *UniterAPI) WatchApplication(ctx context.Context, entity params.Entity) }, nil } +// WatchUnit starts an NotifyWatcher for a unit. +// WatchUnit is not implemented in the UniterAPIv19 facade. +func (u *UniterAPI) WatchUnit(ctx context.Context, entity params.Entity) (params.NotifyWatchResult, error) { + canWatch, err := u.accessUnit() + if err != nil { + return params.NotifyWatchResult{}, errors.Trace(err) + } + + tag, err := names.ParseUnitTag(entity.Tag) + if err != nil { + return params.NotifyWatchResult{Error: apiservererrors.ServerError(apiservererrors.ErrPerm)}, nil + } + + if !canWatch(tag) { + return params.NotifyWatchResult{Error: apiservererrors.ServerError(apiservererrors.ErrPerm)}, nil + } + + watcher, err := u.watchUnit(tag) + if err != nil { + return params.NotifyWatchResult{Error: apiservererrors.ServerError(err)}, nil + } + + id, _, err := internal.EnsureRegisterWatcher[struct{}](ctx, u.watcherRegistry, watcher) + return params.NotifyWatchResult{ + NotifyWatcherId: id, + Error: apiservererrors.ServerError(err), + }, nil +} + // Watch starts an NotifyWatcher for a unit or application. // This is being deprecated in favour of separate WatchUnit and WatchApplication // methods. diff --git a/apiserver/facades/agent/uniter/uniter_test.go b/apiserver/facades/agent/uniter/uniter_test.go index b9e07e6a4d3..71335eb990d 100644 --- a/apiserver/facades/agent/uniter/uniter_test.go +++ b/apiserver/facades/agent/uniter/uniter_test.go @@ -407,7 +407,7 @@ func (s *uniterSuite) TestWatchApplicationBadTag(c *gc.C) { }}) } -func (s *uniterSuite) TestWatchApplicationNotPermission(c *gc.C) { +func (s *uniterSuite) TestWatchApplicationNoPermission(c *gc.C) { uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) // Permissions for mysql will be denied by the accessApplication function // defined in test set up. @@ -419,6 +419,41 @@ func (s *uniterSuite) TestWatchApplicationNotPermission(c *gc.C) { }}) } +func (s *uniterSuite) TestUnitWatch(c *gc.C) { + defer s.setUpMocks(c).Finish() + // Recreate the uniter API with the mocks initialized. + uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + args := params.Entity{Tag: "unit-wordpress-0"} + s.watcherRegistry.EXPECT().Register(gomock.Any()).Return("1", nil) + result, err := uniterAPI.WatchUnit(context.Background(), args) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, gc.DeepEquals, params.NotifyWatchResult{ + NotifyWatcherId: "1", + }) +} + +func (s *uniterSuite) TestWatchUnitBadTag(c *gc.C) { + uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + result, err := uniterAPI.WatchUnit(context.Background(), params.Entity{Tag: "bad-tag"}) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{Error: ¶ms.Error{ + Code: params.CodeUnauthorized, + Message: "permission denied", + }}) +} + +func (s *uniterSuite) TestWatchUnitNoPermission(c *gc.C) { + uniterAPI := s.newUniterAPI(c, s.ControllerModel(c).State(), s.authorizer) + // Permissions for mysql will be denied by the accessUnit function + // defined in test set up. + result, err := uniterAPI.WatchUnit(context.Background(), params.Entity{Tag: "unit-mysql-0"}) + c.Assert(err, jc.ErrorIsNil) + c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{Error: ¶ms.Error{ + Code: params.CodeUnauthorized, + Message: "permission denied", + }}) +} + func (s *uniterSuite) TestPublicAddress(c *gc.C) { // Try first without setting an address. args := params.Entities{Entities: []params.Entity{ diff --git a/internal/worker/uniter/relation/resolver_test.go b/internal/worker/uniter/relation/resolver_test.go index 1a37e8de0cd..bccd8aae5fd 100644 --- a/internal/worker/uniter/relation/resolver_test.go +++ b/internal/worker/uniter/relation/resolver_test.go @@ -168,7 +168,8 @@ func (s *relationResolverSuite) assertNewRelationsWithExistingRelations(c *gc.C, s.leadershipContext = &stubLeadershipContext{isLeader: isLeader} var numCalls int32 - unitEntity := params.Entities{Entities: []params.Entity{{Tag: "unit-wordpress-0"}}} + unitEntitySingleton := params.Entities{Entities: []params.Entity{{Tag: "unit-wordpress-0"}}} + unitEntity := params.Entity{Tag: "unit-wordpress-0"} relationUnits := params.RelationUnits{RelationUnits: []params.RelationUnit{ {Relation: "relation-wordpress.db#mysql.db", Unit: "unit-wordpress-0"}, }} @@ -198,14 +199,14 @@ func (s *relationResolverSuite) assertNewRelationsWithExistingRelations(c *gc.C, unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{}}} apiCalls := []apiCall{ - uniterAPICall("Refresh", unitEntity, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), - uniterAPICall("GetPrincipal", unitEntity, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "", Ok: false}}}, nil), - uniterAPICall("State", unitEntity, unitStateResults, nil), - uniterAPICall("RelationsStatus", unitEntity, params.RelationUnitStatusResults{Results: []params.RelationUnitStatusResult{ + uniterAPICall("Refresh", unitEntitySingleton, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), + uniterAPICall("GetPrincipal", unitEntitySingleton, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "", Ok: false}}}, nil), + uniterAPICall("State", unitEntitySingleton, unitStateResults, nil), + uniterAPICall("RelationsStatus", unitEntitySingleton, params.RelationUnitStatusResults{Results: []params.RelationUnitStatusResult{ {RelationResults: []params.RelationUnitStatus{{RelationTag: "relation-wordpress:db mysql:db", InScope: true}}}}}, nil), uniterAPICall("Relation", relationUnits, relationResults, nil), uniterAPICall("Relation", relationUnits, relationResults, nil), - uniterAPICall("Watch", unitEntity, params.NotifyWatchResults{Results: []params.NotifyWatchResult{{NotifyWatcherId: "1"}}}, nil), + uniterAPICall("WatchUnit", unitEntity, params.NotifyWatchResult{NotifyWatcherId: "1"}, nil), uniterAPICall("SetState", unitSetStateArgs, noErrorResult, nil), uniterAPICall("EnterScope", relationUnits, params.ErrorResults{Results: []params.ErrorResult{{}}}, nil), } @@ -278,7 +279,8 @@ func relationJoinedAPICalls() []apiCall { } func relationJoinedAPICalls2SetState() []apiCall { - unitEntity := params.Entities{Entities: []params.Entity{{Tag: "unit-wordpress-0"}}} + unitEntitySingleton := params.Entities{Entities: []params.Entity{{Tag: "unit-wordpress-0"}}} + unitEntity := params.Entity{Tag: "unit-wordpress-0"} relationResults := params.RelationResults{ Results: []params.RelationResult{ { @@ -314,15 +316,15 @@ func relationJoinedAPICalls2SetState() []apiCall { unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{}}} apiCalls := []apiCall{ - uniterAPICall("Refresh", unitEntity, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), - uniterAPICall("GetPrincipal", unitEntity, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "", Ok: false}}}, nil), - uniterAPICall("State", unitEntity, unitStateResults, nil), - uniterAPICall("RelationsStatus", unitEntity, params.RelationUnitStatusResults{Results: []params.RelationUnitStatusResult{{RelationResults: []params.RelationUnitStatus{}}}}, nil), + uniterAPICall("Refresh", unitEntitySingleton, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), + uniterAPICall("GetPrincipal", unitEntitySingleton, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "", Ok: false}}}, nil), + uniterAPICall("State", unitEntitySingleton, unitStateResults, nil), + uniterAPICall("RelationsStatus", unitEntitySingleton, params.RelationUnitStatusResults{Results: []params.RelationUnitStatusResult{{RelationResults: []params.RelationUnitStatus{}}}}, nil), uniterAPICall("RelationById", params.RelationIds{RelationIds: []int{1}}, relationResults, nil), uniterAPICall("Relation", relationUnits, relationResults, nil), - //uniterAPICall("State", unitEntity, unitStateResults, nil), + //uniterAPICall("State", unitEntitySingleton, unitStateResults, nil), uniterAPICall("Relation", relationUnits, relationResults, nil), - uniterAPICall("Watch", unitEntity, params.NotifyWatchResults{Results: []params.NotifyWatchResult{{NotifyWatcherId: "1"}}}, nil), + uniterAPICall("WatchUnit", unitEntity, params.NotifyWatchResult{NotifyWatcherId: "1"}, nil), uniterAPICall("SetState", unitSetStateArgs, noErrorResult, nil), uniterAPICall("EnterScope", relationUnits, params.ErrorResults{Results: []params.ErrorResult{{}}}, nil), uniterAPICall("SetRelationStatus", relationStatus, noErrorResult, nil), @@ -777,7 +779,9 @@ func (s *relationResolverSuite) TestCommitHook(c *gc.C) { func (s *relationResolverSuite) TestImplicitRelationNoHooks(c *gc.C) { unitTag := names.NewUnitTag("wordpress/0") - unitEntity := params.Entities{Entities: []params.Entity{{Tag: "unit-wordpress-0"}}} + unitEntitySingleton := params.Entities{Entities: []params.Entity{{Tag: "unit-wordpress-0"}}} + unitEntity := params.Entity{Tag: "unit-wordpress-0"} + relationResults := params.RelationResults{ Results: []params.RelationResult{ { @@ -808,14 +812,14 @@ func (s *relationResolverSuite) TestImplicitRelationNoHooks(c *gc.C) { unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{}}} apiCalls := []apiCall{ - uniterAPICall("Refresh", unitEntity, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), - uniterAPICall("GetPrincipal", unitEntity, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "", Ok: false}}}, nil), - uniterAPICall("State", unitEntity, unitStateResults, nil), - uniterAPICall("RelationsStatus", unitEntity, params.RelationUnitStatusResults{Results: []params.RelationUnitStatusResult{{RelationResults: []params.RelationUnitStatus{}}}}, nil), + uniterAPICall("Refresh", unitEntitySingleton, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), + uniterAPICall("GetPrincipal", unitEntitySingleton, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "", Ok: false}}}, nil), + uniterAPICall("State", unitEntitySingleton, unitStateResults, nil), + uniterAPICall("RelationsStatus", unitEntitySingleton, params.RelationUnitStatusResults{Results: []params.RelationUnitStatusResult{{RelationResults: []params.RelationUnitStatus{}}}}, nil), uniterAPICall("RelationById", params.RelationIds{RelationIds: []int{1}}, relationResults, nil), uniterAPICall("Relation", relationUnits, relationResults, nil), uniterAPICall("Relation", relationUnits, relationResults, nil), - uniterAPICall("Watch", unitEntity, params.NotifyWatchResults{Results: []params.NotifyWatchResult{{NotifyWatcherId: "1"}}}, nil), + uniterAPICall("WatchUnit", unitEntity, params.NotifyWatchResult{NotifyWatcherId: "1"}, nil), uniterAPICall("SetState", unitSetStateArgs, noErrorResult, nil), uniterAPICall("EnterScope", relationUnits, params.ErrorResults{Results: []params.ErrorResult{{}}}, nil), uniterAPICall("SetRelationStatus", relationStatus, noErrorResult, nil), @@ -846,9 +850,10 @@ func (s *relationResolverSuite) TestImplicitRelationNoHooks(c *gc.C) { } var ( - noErrorResult = params.ErrorResults{Results: []params.ErrorResult{{}}} - nrpeUnitTag = names.NewUnitTag("nrpe/0") - nrpeUnitEntity = params.Entities{Entities: []params.Entity{{Tag: nrpeUnitTag.String()}}} + noErrorResult = params.ErrorResults{Results: []params.ErrorResult{{}}} + nrpeUnitTag = names.NewUnitTag("nrpe/0") + nrpeUnitEntitySingleton = params.Entities{Entities: []params.Entity{{Tag: nrpeUnitTag.String()}}} + nrpeUnitEntity = params.Entity{Tag: nrpeUnitTag.String()} ) func subSubRelationAPICalls() []apiCall { @@ -927,19 +932,19 @@ func subSubRelationAPICalls() []apiCall { unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{}}} return []apiCall{ - uniterAPICall("Refresh", nrpeUnitEntity, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), - uniterAPICall("GetPrincipal", nrpeUnitEntity, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "unit-wordpress-0", Ok: true}}}, nil), - uniterAPICall("State", nrpeUnitEntity, unitStateResults, nil), - uniterAPICall("RelationsStatus", nrpeUnitEntity, relationStatusResults, nil), + uniterAPICall("Refresh", nrpeUnitEntitySingleton, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), + uniterAPICall("GetPrincipal", nrpeUnitEntitySingleton, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "unit-wordpress-0", Ok: true}}}, nil), + uniterAPICall("State", nrpeUnitEntitySingleton, unitStateResults, nil), + uniterAPICall("RelationsStatus", nrpeUnitEntitySingleton, relationStatusResults, nil), uniterAPICall("Relation", relationUnits1, relationResults1, nil), uniterAPICall("Relation", relationUnits2, relationResults2, nil), uniterAPICall("Relation", relationUnits1, relationResults1, nil), - uniterAPICall("Watch", nrpeUnitEntity, params.NotifyWatchResults{Results: []params.NotifyWatchResult{{NotifyWatcherId: "1"}}}, nil), + uniterAPICall("WatchUnit", nrpeUnitEntity, params.NotifyWatchResult{NotifyWatcherId: "1"}, nil), uniterAPICall("SetState", unitSetStateArgs1, noErrorResult, nil), uniterAPICall("EnterScope", relationUnits1, noErrorResult, nil), uniterAPICall("SetRelationStatus", relationStatus1, noErrorResult, nil), uniterAPICall("Relation", relationUnits2, relationResults2, nil), - uniterAPICall("Watch", nrpeUnitEntity, params.NotifyWatchResults{Results: []params.NotifyWatchResult{{NotifyWatcherId: "2"}}}, nil), + uniterAPICall("WatchUnit", nrpeUnitEntity, params.NotifyWatchResult{NotifyWatcherId: "2"}, nil), uniterAPICall("SetState", unitSetStateArgs2, noErrorResult, nil), uniterAPICall("EnterScope", relationUnits2, noErrorResult, nil), uniterAPICall("SetRelationStatus", relationStatus2, noErrorResult, nil), @@ -956,11 +961,11 @@ func (s *relationResolverSuite) TestSubSubPrincipalRelationDyingDestroysUnit(c * // This should only be called once the relation to the // principal app is destroyed. - apiCalls = append(apiCalls, uniterAPICall("Destroy", nrpeUnitEntity, noErrorResult, nil)) + apiCalls = append(apiCalls, uniterAPICall("Destroy", nrpeUnitEntitySingleton, noErrorResult, nil)) //unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{ // RelationState: map[int]string{2: "id: 2\n"}, //}}} - //apiCalls = append(apiCalls, uniterAPICall("State", nrpeUnitEntity, unitStateResults, nil)) + //apiCalls = append(apiCalls, uniterAPICall("State", nrpeUnitEntitySingleton, unitStateResults, nil)) apiCaller := mockAPICaller(c, &numCalls, apiCalls...) r := s.newRelationStateTracker(c, apiCaller, nrpeUnitTag) @@ -1015,7 +1020,7 @@ func (s *relationResolverSuite) TestSubSubOtherRelationDyingNotDestroyed(c *gc.C //unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{ // RelationState: map[int]string{2: "id: 2\n"}, //}}} - //apiCalls = append(apiCalls, uniterAPICall("State", nrpeUnitEntity, unitStateResults, nil)) + //apiCalls = append(apiCalls, uniterAPICall("State", nrpeUnitEntitySingleton, unitStateResults, nil)) apiCaller := mockAPICaller(c, &numCalls, apiCalls...) @@ -1109,13 +1114,13 @@ func principalWithSubordinateAPICalls() []apiCall { unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{}}} return []apiCall{ - uniterAPICall("Refresh", nrpeUnitEntity, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), - uniterAPICall("GetPrincipal", nrpeUnitEntity, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "unit-wordpress-0", Ok: true}}}, nil), - uniterAPICall("State", nrpeUnitEntity, unitStateResults, nil), - uniterAPICall("RelationsStatus", nrpeUnitEntity, relationStatusResults, nil), + uniterAPICall("Refresh", nrpeUnitEntitySingleton, params.UnitRefreshResults{Results: []params.UnitRefreshResult{{Life: life.Alive, Resolved: params.ResolvedNone}}}, nil), + uniterAPICall("GetPrincipal", nrpeUnitEntitySingleton, params.StringBoolResults{Results: []params.StringBoolResult{{Result: "unit-wordpress-0", Ok: true}}}, nil), + uniterAPICall("State", nrpeUnitEntitySingleton, unitStateResults, nil), + uniterAPICall("RelationsStatus", nrpeUnitEntitySingleton, relationStatusResults, nil), uniterAPICall("Relation", relationUnits1, relationResults1, nil), uniterAPICall("Relation", relationUnits1, relationResults1, nil), - uniterAPICall("Watch", nrpeUnitEntity, params.NotifyWatchResults{Results: []params.NotifyWatchResult{{NotifyWatcherId: "1"}}}, nil), + uniterAPICall("WatchUnit", nrpeUnitEntity, params.NotifyWatchResult{NotifyWatcherId: "1"}, nil), uniterAPICall("SetState", unitSetStateArgs, noErrorResult, nil), uniterAPICall("EnterScope", relationUnits1, noErrorResult, nil), uniterAPICall("SetRelationStatus", relationStatus1, noErrorResult, nil), @@ -1131,11 +1136,11 @@ func (s *relationResolverSuite) TestPrincipalDyingDestroysSubordinates(c *gc.C) callsBeforeDestroy := int32(len(apiCalls)) callsAfterDestroy := callsBeforeDestroy + 1 // This should only be called after we queue the subordinate for destruction - apiCalls = append(apiCalls, uniterAPICall("Destroy", nrpeUnitEntity, noErrorResult, nil)) + apiCalls = append(apiCalls, uniterAPICall("Destroy", nrpeUnitEntitySingleton, noErrorResult, nil)) //unitStateResults := params.UnitStateResults{Results: []params.UnitStateResult{{ // RelationState: map[int]string{1: "id: 1\n", 73: ""}, //}}} - //apiCalls = append(apiCalls, uniterAPICall("State", nrpeUnitEntity, unitStateResults, nil)) + //apiCalls = append(apiCalls, uniterAPICall("State", nrpeUnitEntitySingleton, unitStateResults, nil)) apiCaller := mockAPICaller(c, &numCalls, apiCalls...) r := s.newRelationStateTracker(c, apiCaller, nrpeUnitTag)