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 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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
Expand Up @@ -17,7 +17,8 @@ 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)
//WatchCredential(string) (params.NotifyWatchResult, error)
Copy link
Member

Choose a reason for hiding this comment

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

delete me

}

type CredentialValidatorAPI struct {
Expand Down Expand Up @@ -48,16 +49,15 @@ func internalNewCredentialValidatorAPI(backend Backend, resources facade.Resourc
// changes to the given cloud credentials.
Copy link
Member

Choose a reason for hiding this comment

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

Comment is plural but api call is single.
We should stick to convention and implement as a bulk call, even though we'll just be passing in one credential at the moment.

// 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) {
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
21 changes: 20 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,14 @@ 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,
Check: credentialvalidator.IsValid,
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 +528,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 +577,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
83 changes: 83 additions & 0 deletions worker/credentialvalidator/manifold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package credentialvalidator

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

Choose a reason for hiding this comment

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

delete alias. someone introduced this a while back, not sure why


"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
Check Predicate

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.Check == nil {
return errors.NotValidf("nil Check")
}
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,
Check: config.Check,
})
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 == ErrChanged || cause == ErrModelCredentialChanged {
Copy link
Contributor

Choose a reason for hiding this comment

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

What about the situation where the credentails become invalid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ErrChanged will be thrown specifically when credential is invalid. This is defined in the worker :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll rename the error since when we throw ErrChanged, we are specifically reacting to 'invalid credential' only.

return dependency.ErrBounce
} else if cause == ErrModelDoesNotNeedCredential {
return dependency.ErrUninstall
}
return err
}