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

Transfer control of dev loop from file watcher to dev listener #2354

Merged
merged 6 commits into from
Jul 3, 2019

Conversation

nkubala
Copy link
Contributor

@nkubala nkubala commented Jun 26, 2019

This PR changes the way the dev loop control flow is handled in the codebase.

Previously, the file watcher was responsible for triggering the actions that skaffold dev would perform: when a file change was detected (whether through a polling, fsnotify, or manual mechanism), the file watcher itself was responsible for performing the builds, syncs, and deploys, and the actual "dev loop" served only as a wrapper around the file watcher.

In this new design, the file watcher (renamed to File Monitor and referred to as such from now on) now only serves as a component that the dev loop uses to compute filesystem changes when necessary. The dev loop now contains a Listener, which is responsible for listening to the same trigger that was previously triggering the old file watcher. When a trigger is received, it first asks the file monitor to compute the changes on the local filesystem, and then conditionally performs the actions in the dev loop based on the changes it finds. This trigger is also moved out to a component stored on the runner, rather than being embedded in the file watcher (or in the listener).

The changeSet struct is moved from a dev-scoped variable to a struct on the runner, and is closed over in the callbacks that are registered to the file monitor before the listener is started. This struct still serves the same purpose: the track the "actions" that the dev loop needs to perform on each cycle. These actions are semantically computed by the file monitor when it determines the changes to the local filesystem since the last cycle of the dev loop. (This is not a new concept to this PR, just a clarification for better understanding).

This is the first step toward introducing component-specific triggers in the dev loop (e.g. manual build, manual deploy, manual sync), as a way to give users more control over what parts of skaffold dev happen when.

Summary of changes:

  • Watcher -> Monitor (package watch moved to package filemon, and trigger-related code moved to package trigger)
  • decouple the "file monitoring" component (previously Watcher, now Monitor) from the "trigger" component that actually kicks off the dev cycle (Trigger)
  • move changeSet from "dev"-scoped variable to struct on Runner: this struct is closed over in the callbacks that are registered on the file watcher
  • Run() method on the file monitor is now called by the dev listener whenever a trigger is received
  • move onChange() callback from file watcher to dev listener: this is now called when a trigger is received by the listener, after it calls r.Monitor.Run(...)

Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

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

Started looking at it. Some initial feedback for some nits and questions.
I have to digest the debounce logic again - but otherwise it actually looks good to me!

@@ -26,20 +26,7 @@ import (
"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestWatchWithPollTrigger(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

TestWatchWithPollTrigger and TestWatchWithNotifyTrigger should find their places in the new design too somehow. Maybe under the new trigger package?

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 added these as integration tests.

@nkubala nkubala force-pushed the dev-control-flow branch from 748ec32 to eb1733d Compare June 27, 2019 22:43
@codecov
Copy link

codecov bot commented Jun 27, 2019

Codecov Report

❗ No coverage uploaded for pull request base (master@7fe4c6f). Click here to learn what that means.
The diff coverage is 65.03%.

Impacted Files Coverage Δ
pkg/skaffold/filemon/changes.go 79.62% <ø> (ø)
pkg/skaffold/runner/diagnose.go 13.04% <0%> (ø)
pkg/skaffold/runner/listen.go 0% <0%> (ø)
pkg/skaffold/sync/sync.go 82.71% <100%> (ø)
pkg/skaffold/runner/changeset.go 100% <100%> (ø)
pkg/skaffold/runner/runner.go 71.31% <100%> (ø)
pkg/skaffold/runner/logger.go 100% <100%> (ø)
pkg/skaffold/trigger/triggers.go 29.12% <21.42%> (ø)
pkg/skaffold/runner/dev.go 64.1% <79.24%> (ø)
pkg/skaffold/filemon/monitor.go 81.25% <81.25%> (ø)

@nkubala nkubala force-pushed the dev-control-flow branch from eb1733d to de22b28 Compare June 27, 2019 22:55
@dgageot dgageot self-assigned this Jun 28, 2019
Copy link
Contributor

@dgageot dgageot left a comment

Choose a reason for hiding this comment

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

This is an initial feedback. I like where it's going.

return r.changeSet.needsReload || len(r.changeSet.needsRebuild) > 0 || r.changeSet.needsRedeploy
}

func (r *SkaffoldRunner) processArtifacts() 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'm not sure extracting this piece of code makes it easier to read. It seems disconnected to the rest of the code now. If you still prefer to extract it, at least maybe change the name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the main reason I did this is because it's also being used in the tests, and it felt better than having duplicated code. I'll leave it moved out for now, but I'll put a TODO to try and move it into the callback registered with the monitor. I changed the name to separateBuildAndSync(), suggestions welcome though (I don't like this one either)

@@ -150,5 +166,5 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la
return errors.Wrap(err, "starting forwarder manager")
}

return r.Watcher.Run(ctx, out, onChange)
return r.Listen(ctx, out, onChange)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the function be called WaitForChanges or WaitForTrigger or something that better tels what it's doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep good idea. I changed this to WatchForChanges and the previous method with that name to LogWatchToUser, WDYT?

@@ -42,43 +61,40 @@ func (r *SkaffoldRunner) Dev(ctx context.Context, out io.Writer, artifacts []*la
defer forwarderManager.Stop()

// Create watcher and register artifacts to build current state of files.
changed := changes{}
Copy link
Contributor

Choose a reason for hiding this comment

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

I liked when this state was local to this function. This way, it was clear that nobody else could change its state.

Copy link
Contributor

Choose a reason for hiding this comment

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

changeSet is a better name though.

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 see your point, it's much easier to handle the tests when the changeSet is embedded on the runner though. plus, it's not really true that "nobody can change its state": the file monitor does exactly this, albeit through the callbacks that we register before we start the dev loop.

if you feel strongly about it, I can change it back.

@@ -159,6 +213,38 @@ func createRunner(t *testutil.T, testBench *TestBench) *SkaffoldRunner {
runner.Syncer = testBench
runner.Tester = testBench
runner.Deployer = testBench
runner.Listener = testBench

testBench.devLoop = func() error {
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels super strange that we have to reimplement that part of the runner. It doesn't feel like we're testing the actual code. (Maybe it wasn't the case before either)

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, this was the case before as well, TestBench is a big mock. it is very hard to see what is the actual part of production code that is tested.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've just checked and it looks like TestBench is implementing Builder, Deployer, Tester... but didn't implement anything from the SkaffoldRunner. It's basically a list of mocks that record the interactions of a SkaffoldRunner with the other components.
That doesn't seem to be the case anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah it's weird. I can't call runner.Dev() or runner.WatchForChanges() in the tests because those will run forever; so this was my way of "simulating" the dev loop by "mapping" out the general flow of it.

I'm not sure I totally follow what you're saying though....it sounds like you're proposing that we rewrite the TestBench to mock out the methods defined on the SkaffoldRunner interface? I don't totally disagree with you (I don't really like the way this test is structure to begin with) but it would be a pretty big change. what I did here was the most minimal way I found to retrofit the old tests to the new structure.

Copy link
Contributor

Choose a reason for hiding this comment

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

What I'm saying is that runner_test.go tries to test the code in runner.go, so, the SkaffoldRunner. If feels weird to reimplement the devloop part in tests because then, what are we testing?
The idea of this test was to mock the Builder, the Deployer, the Tester..., group then under the name TestBench and see how SkaffoldRunner interacts with them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok I see what you mean. I took a stab at moving the dev loop out to a method on the SkaffoldRunner, and then calling that from the tests, with a tiny wrapper that calls the file monitor. this is the same thing the actual Listener does whenever it receives a trigger, so it should be actually testing the correct thing now. have a look at the latest commit and tell me what you think

@nkubala nkubala force-pushed the dev-control-flow branch 5 times, most recently from 7c9d24c to bc75908 Compare July 1, 2019 16:58
Copy link
Contributor

@dgageot dgageot left a comment

Choose a reason for hiding this comment

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

LGTM with just one nit

@dgageot
Copy link
Contributor

dgageot commented Jul 2, 2019

I think it would make sense to merge just after the next release to be able to safely test the impact of those changes.

@nkubala nkubala force-pushed the dev-control-flow branch from 913efef to 47f1ba3 Compare July 2, 2019 17:29
@nkubala nkubala force-pushed the dev-control-flow branch from 47f1ba3 to e28672d Compare July 2, 2019 17:30
@nkubala nkubala merged commit 1d98ece into GoogleContainerTools:master Jul 3, 2019
@nkubala nkubala deleted the dev-control-flow branch October 17, 2019 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants