Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Watch model's cloud credential for validity. #8586

Merged
merged 6 commits into from
Apr 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/credentialvalidator/credentialvalidator.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (c *Facade) ModelCredential() (base.StoredCredential, bool, error) {
func (c *Facade) WatchCredential(credentialID string) (watcher.NotifyWatcher, error) {
in := names.NewCloudCredentialTag(credentialID).String()
var result params.NotifyWatchResult
err := c.facade.FacadeCall("WatchCredential", in, &result)
err := c.facade.FacadeCall("WatchCredential", params.Entity{in}, &result)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package credentialvalidator

import (
Expand All @@ -17,7 +18,7 @@ var logger = loggo.GetLogger("juju.api.credentialvalidator")
// CredentialValidator defines the methods on credentialvalidator API endpoint.
type CredentialValidator interface {
ModelCredential() (params.ModelCredential, error)
WatchCredential(string) (params.NotifyWatchResult, error)
WatchCredential(params.Entity) (params.NotifyWatchResult, error)
}

type CredentialValidatorAPI struct {
Expand All @@ -44,20 +45,17 @@ func internalNewCredentialValidatorAPI(backend Backend, resources facade.Resourc
}, nil
}

// WatchCredential returns a collection of NotifyWatchers that observe
// changes to the given cloud credentials.
// The order of returned watchers is important and corresponds directly to the
// order of supplied cloud credentials collection.
func (api *CredentialValidatorAPI) WatchCredential(tag string) (params.NotifyWatchResult, error) {
// WatchCredential returns a NotifyWatcher that observes
// changes to a given cloud credential.
func (api *CredentialValidatorAPI) WatchCredential(tag params.Entity) (params.NotifyWatchResult, error) {
fail := func(failure error) (params.NotifyWatchResult, error) {
return params.NotifyWatchResult{}, common.ServerError(failure)
}

credentialTag, err := names.ParseCloudCredentialTag(tag)
credentialTag, err := names.ParseCloudCredentialTag(tag.Tag)
if err != nil {
return fail(err)
}

// Is credential used by the model that has created this backend?
isUsed, err := api.backend.ModelUsesCredential(credentialTag)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ func (s *CredentialValidatorSuite) TestModelCredentialNotNeeded(c *gc.C) {
}

func (s *CredentialValidatorSuite) TestWatchCredential(c *gc.C) {
result, err := s.api.WatchCredential(credentialTag.String())
result, err := s.api.WatchCredential(params.Entity{credentialTag.String()})
c.Assert(err, jc.ErrorIsNil)
c.Assert(result, gc.DeepEquals, params.NotifyWatchResult{"1", nil})
c.Assert(s.resources.Count(), gc.Equals, 1)
}

func (s *CredentialValidatorSuite) TestWatchCredentialNotUsedInThisModel(c *gc.C) {
s.backend.isUsed = false
_, err := s.api.WatchCredential(credentialTag.String())
_, err := s.api.WatchCredential(params.Entity{credentialTag.String()})
c.Assert(err, gc.ErrorMatches, common.ErrPerm.Error())
c.Assert(s.resources.Count(), gc.Equals, 0)
}

func (s *CredentialValidatorSuite) TestWatchCredentialInvalidTag(c *gc.C) {
_, err := s.api.WatchCredential("my-tag")
_, err := s.api.WatchCredential(params.Entity{"my-tag"})
c.Assert(err, gc.ErrorMatches, `"my-tag" is not a valid tag`)
c.Assert(s.resources.Count(), gc.Equals, 0)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/jujud/agent/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ var (
"action-pruner",
"charm-revision-updater",
"compute-provisioner",
"credential-validator-flag",
"environ-tracker",
"firewaller",
"instance-poller",
Expand Down
16 changes: 14 additions & 2 deletions cmd/jujud/agent/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1041,8 +1041,20 @@ func (s *MachineSuite) TestHostedModelWorkers(c *gc.C) {
return &minModelWorkersEnviron{}, nil
})

st, closer := s.setUpNewModel(c)
defer closer()
st := s.Factory.MakeModel(c, &factory.ModelParams{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we care about the ConfigAttrs? Are the defaults not good enough?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NO, here we care about cloud credential being on the model, otherwise cred-validator will not start and it has too since it's in the list of valid model workers that this suite maintains.

I might need to write cleaner narrow test cases (like 'model (+live/not/dying/not dead/not migrating/etc variations) on a cloud that requires cred must have a worker', 'model on a loud that does not require cred, does not have a worker') but I'd prefer to do these in a follow-up PR. That PR will clean up this mess a bit.

ConfigAttrs: coretesting.Attrs{
"max-status-history-age": "2h",
"max-status-history-size": "4M",
"max-action-results-age": "2h",
"max-action-results-size": "4M",
},
CloudCredential: names.NewCloudCredentialTag("dummy/admin/cred"),
})
defer func() {
err := st.Close()
c.Check(err, jc.ErrorIsNil)
}()

uuid := st.ModelUUID()

tracker := agenttest.NewEngineTracker()
Expand Down
20 changes: 19 additions & 1 deletion cmd/jujud/agent/model/manifolds.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/juju/juju/worker/charmrevision"
"github.com/juju/juju/worker/charmrevision/charmrevisionmanifold"
"github.com/juju/juju/worker/cleaner"
"github.com/juju/juju/worker/credentialvalidator"
"github.com/juju/juju/worker/dependency"
"github.com/juju/juju/worker/environ"
"github.com/juju/juju/worker/firewaller"
Expand Down Expand Up @@ -80,7 +81,7 @@ type ManifoldsConfig struct {
Clock clock.Clock

// InstPollerAggregationDelay is the delay before sending a batch of
// requests in the instancpoller.Worker's aggregate loop.
// requests in the instancepoller.Worker's aggregate loop.
InstPollerAggregationDelay time.Duration

// RunFlagDuration defines for how long this controller will ask
Expand Down Expand Up @@ -170,6 +171,13 @@ func commonManifolds(config ManifoldsConfig) dependency.Manifolds {
NewFacade: singular.NewFacade,
NewWorker: singular.NewWorker,
}),
// Cloud credential validator runs on all models, and
// determines if model's cloud credential is valid.
credentialValidatorFlagName: ifNotUpgrading(ifNotDead(credentialvalidator.Manifold(credentialvalidator.ManifoldConfig{
APICallerName: apiCallerName,
NewFacade: credentialvalidator.NewFacade,
NewWorker: credentialvalidator.NewWorker,
}))),

// The migration workers collaborate to run migrations;
// and to create a mechanism for running other workers
Expand Down Expand Up @@ -519,6 +527,14 @@ var (
modelUpgradedFlagName,
},
}.Decorate

// ifCredentialValid wraps a manifold such that it only runs if
// the model has a valid credential.
ifCredentialValid = engine.Housing{
Flags: []string{
credentialValidatorFlagName,
},
}.Decorate
)

const (
Expand Down Expand Up @@ -560,4 +576,6 @@ const (
caasOperatorProvisionerName = "caas-operator-provisioner"
caasUnitProvisionerName = "caas-unit-provisioner"
caasBrokerTrackerName = "caas-broker-tracker"

credentialValidatorFlagName = "credential-validator-flag"
)
3 changes: 3 additions & 0 deletions cmd/jujud/agent/model/manifolds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (s *ManifoldsSuite) TestIAASNames(c *gc.C) {
"charm-revision-updater",
"clock",
"compute-provisioner",
"credential-validator-flag",
"environ-tracker",
"firewaller",
"instance-poller",
Expand Down Expand Up @@ -84,6 +85,7 @@ func (s *ManifoldsSuite) TestCAASNames(c *gc.C) {
"caas-unit-provisioner",
"charm-revision-updater",
"clock",
"credential-validator-flag",
"is-responsible-flag",
"log-forwarder",
"migration-fortress",
Expand All @@ -107,6 +109,7 @@ func (s *ManifoldsSuite) TestFlagDependencies(c *gc.C) {
"api-caller",
"api-config-watcher",
"clock",
"credential-validator-flag",
"is-responsible-flag",
"not-alive-flag",
"not-dead-flag",
Expand Down
78 changes: 78 additions & 0 deletions worker/credentialvalidator/manifold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package credentialvalidator

import (
"github.com/juju/errors"
"gopkg.in/juju/worker.v1"

"github.com/juju/juju/api/base"
"github.com/juju/juju/cmd/jujud/agent/engine"
"github.com/juju/juju/worker/dependency"
)

// ManifoldConfig holds the dependencies and configuration for a
// Worker manifold.
type ManifoldConfig struct {
APICallerName string

NewFacade func(base.APICaller) (Facade, error)
NewWorker func(Config) (worker.Worker, error)
}

// Validate is called by start to check for bad configuration.
func (config ManifoldConfig) Validate() error {
if config.APICallerName == "" {
return errors.NotValidf("empty APICallerName")
}
if config.NewFacade == nil {
return errors.NotValidf("nil NewFacade")
}
if config.NewWorker == nil {
return errors.NotValidf("nil NewWorker")
}
return nil
}

// start is a StartFunc for a Worker manifold.
func (config ManifoldConfig) start(context dependency.Context) (worker.Worker, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quite like the idea of breaking start out as a config method. seems cleaner.

if err := config.Validate(); err != nil {
return nil, errors.Trace(err)
}
var apiCaller base.APICaller
if err := context.Get(config.APICallerName, &apiCaller); err != nil {
return nil, errors.Trace(err)
}
facade, err := config.NewFacade(apiCaller)
if err != nil {
return nil, errors.Trace(err)
}
worker, err := config.NewWorker(Config{
Facade: facade,
})
if err != nil {
return nil, errors.Trace(err)
}
return worker, nil
}

// Manifold packages a Worker for use in a dependency.Engine.
func Manifold(config ManifoldConfig) dependency.Manifold {
return dependency.Manifold{
Inputs: []string{config.APICallerName},
Start: config.start,
Output: engine.FlagOutput,
Filter: filterErrors,
}
}

func filterErrors(err error) error {
cause := errors.Cause(err)
if cause == ErrValidityChanged || cause == ErrModelCredentialChanged {
return dependency.ErrBounce
} else if cause == ErrModelDoesNotNeedCredential {
return dependency.ErrUninstall
}
return err
}
Loading