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
Conversation
There was a problem hiding this 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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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
worker/credentialvalidator/worker.go
Outdated
|
||
// ErrModelCredentialChanged indicates that a Worker has bounced | ||
// because model credential was replaced. | ||
var ErrModelCredentialChanged = errors.New("unexpected model credential") |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
worker/credentialvalidator/worker.go
Outdated
return errors.Trace(err) | ||
} | ||
if !exists { | ||
// Really should not happen in practice since the uninstall |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
worker/credentialvalidator/worker.go
Outdated
if err := config.Validate(); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
modelCredential, exists, err := config.Facade.ModelCredential() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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{ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
worker/credentialvalidator/worker.go
Outdated
if err := config.Validate(); err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
modelCredential, exists, err := config.Facade.ModelCredential() |
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
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. |
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. |
|
…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.
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.