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

Conversation

anastasiamac
Copy link
Contributor

Description of change

This is major part of the work that allows models to react when cloud credential becomes invalid.
This PR introduces a worker and its manifold, configured for models.

@anastasiamac
Copy link
Contributor Author

Copy link
Member

@wallyworld wallyworld left a comment

Choose a reason for hiding this comment

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

A few things to look at. Nice to see this stuff progressing.

@@ -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

@@ -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.


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/testing"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
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.

and here etc

}

// modelUUID is the model tag we're using in the tests.
var modelUUID = "01234567-89ab-cdef-0123-456789abcdef"
Copy link
Member

Choose a reason for hiding this comment

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

we have a predefined model tag for tests we could use


// ErrModelCredentialChanged indicates that a Worker has bounced
// because model credential was replaced.
var ErrModelCredentialChanged = errors.New("unexpected model credential")
Copy link
Member

Choose a reason for hiding this comment

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

"model credential changed"
?


// checkManifoldNotValid checks that the supplied ManifoldConfig creates
// a manifold that cannot be started.
func checkManifoldNotValid(c *gc.C, config credentialvalidator.ManifoldConfig, expect string) {
Copy link
Member

Choose a reason for hiding this comment

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

Interesting approach. In other workers, we use an exported Validate() method on the config and just call that directly. There's a bit more boilerplate with this approach.

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 followed whatever was in migration flag. I am not big fun of exporting functions that are not otherwise exported just for testing...

Copy link
Contributor

Choose a reason for hiding this comment

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

what function would you be exporting that isn't otherwise exported?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, ok. Can go either way with this one. We sometimes export methods to make testing easier. But this way is fine, was just thinking out loud. I wish we didn't have so many ways things that are the same have been implemented over time.

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 don't think it's worth putting the foot down. I'll export Validate() and will change the test to call it directly rather than going through Start().

I do not see a significant difference in implementation - nothing gained through exporting it from behavioral or performance consideration.
However, I agree that the test is simpler and leaner and this can be a metrics of quality by itself :D

return errors.Trace(err)
}
if !exists {
// Really should not happen in practice since the uninstall
Copy link
Member

Choose a reason for hiding this comment

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

given the comment should we at least log that this has occurred as a Warning

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

if err := config.Validate(); err != nil {
return nil, errors.Trace(err)
}
modelCredential, exists, err := config.Facade.ModelCredential()
Copy link
Member

Choose a reason for hiding this comment

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

These steps should all be at the top of the loop method (before the for{})
Besides following current practice, it means that modelCredential and watcher don't need to be worker attributes. The watcher would be added to the catacomb when created in the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Getting model credential is the biggest and not a simple operation. There is no need to get it all the time only if a valid and interesting change is reported.
The same approach is used in migration flag which to me was a better and very similar example to what I was doing than any other worker we have.

Copy link
Member

Choose a reason for hiding this comment

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

it won't be got ll the time - it's just moving the code from here to the top of the worker loop (not inside the for). 99% of workers have all their logic inside the loop(). Migration worker would be the outlier here. It's better to keep all the worker logic together in the loop function.

Copy link
Contributor

Choose a reason for hiding this comment

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

While it has been idiomatic in other places to have the New method just initialise the structure and start the loop, I don't see anything wrong with the initialisation including getting the credentials from the DB.

Copy link
Member

Choose a reason for hiding this comment

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

Consistency is good. If there's no real reason to stray, why introduce the cognitive overhead. Code is write once, read many.

Copy link
Member

Choose a reason for hiding this comment

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

Plus the suggested approach removes the need for unnecessary worker attributes. Less LOC = less bugs by definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think consistency is good. Ideally new people coming into the code will be able to see the patterns of how things are written. I'd agree that moving the code into the loop function for consistency is worth it.

"github.com/juju/juju/worker/workertest"
)

type WorkerSuite struct {
Copy link
Member

Choose a reason for hiding this comment

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

There need to be tests that the worker and watcher are properly stopped.
See other worker packages for examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, all these are similar to model migration flag. All the tests here that get a valid watcher/worker have workertest.CheckKilled etc... Did you look at:
TestModelCredentialErrorWhileRunning l. 64;
TestCredentialChangeToInvalid l.91;
TestCredentialChangeFromInvalid l.110;
TestModelCredentialReplaced l. 129;
TestNoRelevantCredentialChange l.147.

I have also ran all the tests in other packages that test workers start and stop (like cmd/jujud/agent).

In addition, I have live tested.

Copy link
Member

Choose a reason for hiding this comment

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

We often have a simple test like this:

func (s *WorkerSuite) TestStartStop(c *gc.C) {
w, err := caasoperator.NewWorker(s.config)
c.Assert(err, jc.ErrorIsNil)
workertest.CheckAlive(c, w)
workertest.CleanKill(c, w)
}

Also, there's no check that I can see that the watcher is killed when the worker dies.

Copy link
Member

Choose a reason for hiding this comment

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

Don't necessarily assume that what's in migration flag or elsewhere is current best practice. Sadly this stuff tends to evolve and there's a bit of legacy code all around.

Copy link
Contributor

Choose a reason for hiding this comment

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

I do agree with Ian on this one, traditionally workers have a simple test that just has the start and a clean kill.

@@ -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.

}

// 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.


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.

@@ -0,0 +1,177 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
Copy link
Contributor

Choose a reason for hiding this comment

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

The file you copied from had the wrong licence - AGPL

c.Check(err, gc.ErrorMatches, "expected out to be a \\*Flag; got a .*")
}

func (*ManifoldSuite) TestOutputBadInput(c *gc.C) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this bad imput?

err := config.Validate()
check(err)

worker, err := credentialvalidator.New(config)
Copy link
Contributor

Choose a reason for hiding this comment

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

You only need to check that the config is validated in the new worker function once. This violates the "test it once" idea.


// checkManifoldNotValid checks that the supplied ManifoldConfig creates
// a manifold that cannot be started.
func checkManifoldNotValid(c *gc.C, config credentialvalidator.ManifoldConfig, expect string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what function would you be exporting that isn't otherwise exported?

if err := config.Validate(); err != nil {
return nil, errors.Trace(err)
}
modelCredential, exists, err := config.Facade.ModelCredential()
Copy link
Contributor

Choose a reason for hiding this comment

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

While it has been idiomatic in other places to have the New method just initialise the structure and start the loop, I don't see anything wrong with the initialisation including getting the credentials from the DB.


workertest.CheckAlive(c, worker)
workertest.CleanKill(c, worker)
s.facade.CheckCallNames(c, "ModelCredential", "WatchCredential", "ModelCredential", "ModelCredential", "ModelCredential")
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels racey to me. How do we know that the right number of calls have been made befor we kill the worker?

"github.com/juju/juju/worker/workertest"
)

type WorkerSuite struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

I do agree with Ian on this one, traditionally workers have a simple test that just has the start and a clean kill.

Copy link
Member

@wallyworld wallyworld left a comment

Choose a reason for hiding this comment

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

Overall this is really good. There's just the few nit picky things. With many new starters, we're trying to be a little stricter on code consistency to make it easier for folks to understand the code when the read it. Once Tim is happy, the LGTM.

@howbazaar
Copy link
Contributor

I'm happy in general. Overall I think the quality of the work is very high, and the actual issues are very minor. Almost all of the changes are to be more consistent with what has emerged as best practice for workers and their config.

Good job @anastasiamac , it really is good.

@anastasiamac
Copy link
Contributor Author

Thank you for your reviews. Once all the checks pass, I'll land this with some changes and will follow up with a separate PR that has tighter tests both of manifold within worker pkg and outside, specifically in cmd/juju/agent.

@anastasiamac
Copy link
Contributor Author

$$merge$$

@jujubot jujubot merged commit 8bb3a88 into juju:develop Apr 13, 2018
@anastasiamac anastasiamac deleted the valid-cred-manifold-etc branch April 13, 2018 06:51
jujubot added a commit that referenced this pull request Apr 16, 2018
…worker

#8594

## Description of change

Based on review comments in PR #8586, this PR improves credential validator worker tests:
 prdesc in worker pkg itself - watcher is constructed deterministically and sends definitive changes; added compliant StartStop test;
* in cmd/jujud/agent pkg - only models with cloud credential are expected to have a credential validator worker. 

## QA steps

Internal change so all unit tests are expected to pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants