Skip to content

Commit

Permalink
remote relations worker polls offer model
Browse files Browse the repository at this point in the history
  • Loading branch information
wallyworld committed Jun 21, 2017
1 parent 3c332bc commit 836dfb5
Show file tree
Hide file tree
Showing 26 changed files with 1,331 additions and 830 deletions.
47 changes: 42 additions & 5 deletions api/crossmodelrelations/crossmodelrelations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"github.com/juju/errors"

"github.com/juju/juju/api/base"
apiwatcher "github.com/juju/juju/api/watcher"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/watcher"
)

// Client provides access to the crossmodelrelations api facade.
Expand All @@ -21,14 +23,14 @@ func NewClient(caller base.APICaller) *Client {
return &Client{facadeCaller}
}

// PublishLocalRelationChange publishes local relations changes to the
// remote side offering those relations.
func (c *Client) PublishLocalRelationChange(change params.RemoteRelationChangeEvent) error {
// PublishRelationChange publishes relation changes to the
// model hosting the remote application involved in the relation.
func (c *Client) PublishRelationChange(change params.RemoteRelationChangeEvent) error {
args := params.RemoteRelationsChanges{
Changes: []params.RemoteRelationChangeEvent{change},
}
var results params.ErrorResults
err := c.facade.FacadeCall("PublishLocalRelationChange", args, &results)
err := c.facade.FacadeCall("PublishRelationChange", args, &results)
if err != nil {
return errors.Trace(err)
}
Expand All @@ -42,7 +44,8 @@ func (c *Client) PublishLocalRelationChange(change params.RemoteRelationChangeEv
return nil
}

// RegisterRemoteRelations sets up the local model to participate in the specified relations.
// RegisterRemoteRelation sets up the remote model to participate
// in the specified relations.
func (c *Client) RegisterRemoteRelations(relations ...params.RegisterRemoteRelation) ([]params.RemoteEntityIdResult, error) {
args := params.RegisterRemoteRelations{Relations: relations}
var results params.RemoteEntityIdResults
Expand All @@ -55,3 +58,37 @@ func (c *Client) RegisterRemoteRelations(relations ...params.RegisterRemoteRelat
}
return results.Results, nil
}

// WatchRelationUnits returns a watcher that notifies of changes to the
// units in the remote model for the relation with the given remote id.
func (c *Client) WatchRelationUnits(remoteRelationId params.RemoteEntityId) (watcher.RelationUnitsWatcher, error) {
args := params.RemoteEntities{Entities: []params.RemoteEntityId{remoteRelationId}}
var results params.RelationUnitsWatchResults
err := c.facade.FacadeCall("WatchRelationUnits", args, &results)
if err != nil {
return nil, errors.Trace(err)
}
if len(results.Results) != 1 {
return nil, errors.Errorf("expected 1 result, got %d", len(results.Results))
}
result := results.Results[0]
if result.Error != nil {
return nil, result.Error
}
w := apiwatcher.NewRelationUnitsWatcher(c.facade.RawAPICaller(), result)
return w, nil
}

// RelationUnitSettings returns the relation unit settings for the given relation units in the remote model.
func (c *Client) RelationUnitSettings(relationUnits []params.RemoteRelationUnit) ([]params.SettingsResult, error) {
args := params.RemoteRelationUnits{relationUnits}
var results params.SettingsResults
err := c.facade.FacadeCall("RelationUnitSettings", args, &results)
if err != nil {
return nil, errors.Trace(err)
}
if len(results.Results) != len(relationUnits) {
return nil, errors.Errorf("expected %d result(s), got %d", len(relationUnits), len(results.Results))
}
return results.Results, nil
}
57 changes: 54 additions & 3 deletions api/crossmodelrelations/crossmodelrelations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ func (s *CrossModelRelationsSuite) TestNewClient(c *gc.C) {
c.Assert(client, gc.NotNil)
}

func (s *CrossModelRelationsSuite) TestPublishLocalRelationChange(c *gc.C) {
func (s *CrossModelRelationsSuite) TestPublishRelationChange(c *gc.C) {
var callCount int
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
c.Check(objType, gc.Equals, "CrossModelRelations")
c.Check(version, gc.Equals, 0)
c.Check(id, gc.Equals, "")
c.Check(request, gc.Equals, "PublishLocalRelationChange")
c.Check(request, gc.Equals, "PublishRelationChange")
c.Check(arg, gc.DeepEquals, params.RemoteRelationsChanges{
Changes: []params.RemoteRelationChangeEvent{{
DepartedUnits: []int{1}}},
Expand All @@ -48,7 +48,7 @@ func (s *CrossModelRelationsSuite) TestPublishLocalRelationChange(c *gc.C) {
return nil
})
client := crossmodelrelations.NewClient(apiCaller)
err := client.PublishLocalRelationChange(params.RemoteRelationChangeEvent{DepartedUnits: []int{1}})
err := client.PublishRelationChange(params.RemoteRelationChangeEvent{DepartedUnits: []int{1}})
c.Check(err, gc.ErrorMatches, "FAIL")
c.Check(callCount, gc.Equals, 1)
}
Expand Down Expand Up @@ -93,3 +93,54 @@ func (s *CrossModelRelationsSuite) TestRegisterRemoteRelationCount(c *gc.C) {
_, err := client.RegisterRemoteRelations(params.RegisterRemoteRelation{})
c.Check(err, gc.ErrorMatches, `expected 1 result\(s\), got 2`)
}

func (s *CrossModelRelationsSuite) TestWatchRelationUnits(c *gc.C) {
remoteRelationId := params.RemoteEntityId{}
var callCount int
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
c.Check(objType, gc.Equals, "CrossModelRelations")
c.Check(version, gc.Equals, 0)
c.Check(id, gc.Equals, "")
c.Check(arg, jc.DeepEquals, params.RemoteEntities{Entities: []params.RemoteEntityId{remoteRelationId}})
c.Check(request, gc.Equals, "WatchRelationUnits")
c.Assert(result, gc.FitsTypeOf, &params.RelationUnitsWatchResults{})
*(result.(*params.RelationUnitsWatchResults)) = params.RelationUnitsWatchResults{
Results: []params.RelationUnitsWatchResult{{
Error: &params.Error{Message: "FAIL"},
}},
}
callCount++
return nil
})
client := crossmodelrelations.NewClient(apiCaller)
_, err := client.WatchRelationUnits(remoteRelationId)
c.Check(err, gc.ErrorMatches, "FAIL")
c.Check(callCount, gc.Equals, 1)
}

func (s *CrossModelRelationsSuite) TestRelationUnitSettings(c *gc.C) {
remoteRelationId := params.RemoteEntityId{}
var callCount int
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
c.Check(objType, gc.Equals, "CrossModelRelations")
c.Check(version, gc.Equals, 0)
c.Check(id, gc.Equals, "")
c.Check(request, gc.Equals, "RelationUnitSettings")
c.Check(arg, gc.DeepEquals, params.RemoteRelationUnits{
RelationUnits: []params.RemoteRelationUnit{{RelationId: remoteRelationId, Unit: "u"}}})
c.Assert(result, gc.FitsTypeOf, &params.SettingsResults{})
*(result.(*params.SettingsResults)) = params.SettingsResults{
Results: []params.SettingsResult{{
Error: &params.Error{Message: "FAIL"},
}},
}
callCount++
return nil
})
client := crossmodelrelations.NewClient(apiCaller)
result, err := client.RelationUnitSettings([]params.RemoteRelationUnit{{RelationId: remoteRelationId, Unit: "u"}})
c.Check(err, jc.ErrorIsNil)
c.Assert(result, gc.HasLen, 1)
c.Check(result[0].Error, gc.ErrorMatches, "FAIL")
c.Check(callCount, gc.Equals, 1)
}
14 changes: 14 additions & 0 deletions api/remoterelations/remoterelations.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,17 @@ func (c *Client) WatchRemoteRelations() (watcher.StringsWatcher, error) {
w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result)
return w, nil
}

// ConsumeRemoteRelationChange consumes a change to settings originating
// from the remote/offering side of a relation.
func (c *Client) ConsumeRemoteRelationChange(change params.RemoteRelationChangeEvent) error {
args := params.RemoteRelationsChanges{
Changes: []params.RemoteRelationChangeEvent{change},
}
var results params.ErrorResults
err := c.facade.FacadeCall("ConsumeRemoteRelationChange", args, &results)
if err != nil {
return errors.Trace(err)
}
return results.OneError()
}
26 changes: 26 additions & 0 deletions api/remoterelations/remoterelations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,29 @@ func (s *remoteRelationsSuite) TestWatchRemoteRelations(c *gc.C) {
c.Check(err, gc.ErrorMatches, "FAIL")
c.Check(callCount, gc.Equals, 1)
}

func (s *remoteRelationsSuite) TestConsumeRemoteRelationChange(c *gc.C) {
var callCount int
change := params.RemoteRelationChangeEvent{}
changes := params.RemoteRelationsChanges{
Changes: []params.RemoteRelationChangeEvent{change},
}
apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error {
c.Check(objType, gc.Equals, "RemoteRelations")
c.Check(version, gc.Equals, 0)
c.Check(id, gc.Equals, "")
c.Check(request, gc.Equals, "ConsumeRemoteRelationChange")
c.Check(arg, jc.DeepEquals, changes)
c.Assert(result, gc.FitsTypeOf, &params.ErrorResults{})
*(result.(*params.ErrorResults)) = params.ErrorResults{
Results: []params.ErrorResult{{
Error: &params.Error{Message: "FAIL"},
}}}
callCount++
return nil
})
client := remoterelations.NewClient(apiCaller)
err := client.ConsumeRemoteRelationChange(change)
c.Check(err, gc.ErrorMatches, "FAIL")
c.Check(callCount, gc.Equals, 1)
}
1 change: 1 addition & 0 deletions apiserver/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ func (s *loginSuite) TestAnonymousModelLogin(c *gc.C) {
c.Assert(result.ModelTag, gc.Equals, s.State.ModelTag().String())
c.Assert(result.Facades, jc.DeepEquals, []params.FacadeVersions{
{Name: "CrossModelRelations", Versions: []int{1}},
{Name: "RelationUnitsWatcher", Versions: []int{1}},
})
}

Expand Down
184 changes: 184 additions & 0 deletions apiserver/common/crossmodel/crossmodel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2017 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package crossmodel

import (
"fmt"

"github.com/juju/errors"
"github.com/juju/loggo"
"gopkg.in/juju/names.v2"

"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/state"
)

var logger = loggo.GetLogger("juju.apiserver.common.crossmodel")

// PublishRelationChange applies the relation change event to the specified backend.
func PublishRelationChange(backend Backend, change params.RemoteRelationChangeEvent) error {
logger.Debugf("publish into model %v change: %+v", backend.ModelUUID(), change)

relationTag, err := getRemoteEntityTag(backend, change.RelationId)
if err != nil {
if errors.IsNotFound(err) {
logger.Debugf("not found relation tag %+v in model %v, exit early", change.RelationId, backend.ModelUUID())
return nil
}
return errors.Trace(err)
}
logger.Debugf("relation tag for remote id %+v is %v", change.RelationId, relationTag)

// Ensure the relation exists.
rel, err := backend.KeyRelation(relationTag.Id())
if errors.IsNotFound(err) {
if change.Life != params.Alive {
return nil
}
}
if err != nil {
return errors.Trace(err)
}

// Look up the application on the remote side of this relation
// ie from the model which published this change.
applicationTag, err := getRemoteEntityTag(backend, change.ApplicationId)
if err != nil {
return errors.Trace(err)
}
logger.Debugf("application tag for remote id %+v is %v", change.ApplicationId, applicationTag)

// If the remote model has destroyed the relation, do it here also.
if change.Life != params.Alive {
logger.Debugf("remote side of %v died", relationTag)
if err := rel.Destroy(); err != nil {
return errors.Trace(err)
}
// See if we need to remove the remote application proxy - we do this
// on the offering side as there is 1:1 between proxy and consuming app.
if applicationTag != nil {
remoteApp, err := backend.RemoteApplication(applicationTag.Id())
if err != nil && !errors.IsNotFound(err) {
return errors.Trace(err)
}
if err == nil && remoteApp.IsConsumerProxy() {
logger.Debugf("destroy consuming app proxy for %v", applicationTag.Id())
if err := remoteApp.Destroy(); err != nil {
return errors.Trace(err)
}
}
}
}

// TODO(wallyworld) - deal with remote application being removed
if applicationTag == nil {
logger.Infof("no remote application found for %v", relationTag.Id())
return nil
}
logger.Debugf("remote applocation for changed relation %v is %v", relationTag.Id(), applicationTag.Id())

for _, id := range change.DepartedUnits {
unitTag := names.NewUnitTag(fmt.Sprintf("%s/%v", applicationTag.Id(), id))
logger.Debugf("unit %v has departed relation %v", unitTag.Id(), relationTag.Id())
ru, err := rel.RemoteUnit(unitTag.Id())
if err != nil {
return errors.Trace(err)
}
logger.Debugf("%s leaving scope", unitTag.Id())
if err := ru.LeaveScope(); err != nil {
return errors.Trace(err)
}
}

for _, change := range change.ChangedUnits {
unitTag := names.NewUnitTag(fmt.Sprintf("%s/%v", applicationTag.Id(), change.UnitId))
logger.Debugf("changed unit tag for remote id %v is %v", change.UnitId, unitTag)
ru, err := rel.RemoteUnit(unitTag.Id())
if err != nil {
return errors.Trace(err)
}
inScope, err := ru.InScope()
if err != nil {
return errors.Trace(err)
}
settings := make(map[string]interface{})
for k, v := range change.Settings {
settings[k] = v
}
if !inScope {
logger.Debugf("%s entering scope (%v)", unitTag.Id(), settings)
err = ru.EnterScope(settings)
} else {
logger.Debugf("%s updated settings (%v)", unitTag.Id(), settings)
err = ru.ReplaceSettings(settings)
}
if err != nil {
return errors.Trace(err)
}
}
return nil
}

func getRemoteEntityTag(backend Backend, id params.RemoteEntityId) (names.Tag, error) {
modelTag := names.NewModelTag(id.ModelUUID)
return backend.GetRemoteEntity(modelTag, id.Token)
}

// WatchRelationUnits returns a watcher for changes to the units on the specified relation.
func WatchRelationUnits(backend Backend, tag names.RelationTag) (state.RelationUnitsWatcher, error) {
relation, err := backend.KeyRelation(tag.Id())
if err != nil {
return nil, errors.Trace(err)
}
for _, ep := range relation.Endpoints() {
_, err := backend.Application(ep.ApplicationName)
if errors.IsNotFound(err) {
// Not found, so it's the remote application. Try the next endpoint.
continue
} else if err != nil {
return nil, errors.Trace(err)
}
w, err := relation.WatchUnits(ep.ApplicationName)
if err != nil {
return nil, errors.Trace(err)
}
return w, nil
}
return nil, errors.NotFoundf("local application for %s", names.ReadableString(tag))
}

// RelationUnitSettings returns the unit settings for the specified relation unit.
func RelationUnitSettings(backend Backend, ru params.RelationUnit) (params.Settings, error) {
relationTag, err := names.ParseRelationTag(ru.Relation)
if err != nil {
return nil, errors.Trace(err)
}
rel, err := backend.KeyRelation(relationTag.Id())
if err != nil {
return nil, errors.Trace(err)
}
unitTag, err := names.ParseUnitTag(ru.Unit)
if err != nil {
return nil, errors.Trace(err)
}
unit, err := rel.Unit(unitTag.Id())
if err != nil {
return nil, errors.Trace(err)
}
settings, err := unit.Settings()
if err != nil {
return nil, errors.Trace(err)
}
paramsSettings := make(params.Settings)
for k, v := range settings {
vString, ok := v.(string)
if !ok {
return nil, errors.Errorf(
"invalid relation setting %q: expected string, got %T", k, v,
)
}
paramsSettings[k] = vString
}
return paramsSettings, nil
}
Loading

0 comments on commit 836dfb5

Please sign in to comment.