-
Notifications
You must be signed in to change notification settings - Fork 211
Improve dapr init experience on Windows machines running WSL
#1641
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
Open
WhitWaldo
wants to merge
14
commits into
dapr:master
Choose a base branch
from
WhitWaldo:windows-wsl-fallback
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
ebfddf9
Implementing additional functionality for `dapr init` when running in…
WhitWaldo 3600df1
Fixing lint error
WhitWaldo a524ef6
Potential fix for pull request finding
WhitWaldo e9321b5
Addressing more accurate error message after failing to start schedul…
WhitWaldo 35a7030
Add conditional flag to track and restart WinNAT only if it was stopped
WhitWaldo d0edaf0
More accurately represents the test per review
WhitWaldo 84a8e70
Addressing concerns in review about validating info.dockerNetwork val…
WhitWaldo 9867baa
Addressed comment scope
WhitWaldo 1bcd6e4
Addressing comment raised in review
WhitWaldo 99eb570
Removed call-out about WSL2 per review comment
WhitWaldo 32ce657
Addressing latest Copilot comment regarding race condition in tests
WhitWaldo c0ef62f
Potential fix for pull request finding
WhitWaldo 99de306
Addressing how to emit errors properly per Copilot feedback
WhitWaldo b3e0860
Merge remote-tracking branch 'origin/windows-wsl-fallback' into windo…
WhitWaldo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| //go:build !windows | ||
|
|
||
| /* | ||
| Copyright 2021 The Dapr Authors | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package standalone | ||
|
|
||
| // isWindowsElevated always returns false on non-Windows platforms. | ||
| func isWindowsElevated() bool { return false } | ||
|
|
||
| // isWSLAvailable always returns false on non-Windows platforms. | ||
| func isWSLAvailable() bool { return false } | ||
|
|
||
| // shutdownWSL is a no-op on non-Windows platforms. | ||
| func shutdownWSL() error { return nil } | ||
|
|
||
| // stopWinNAT is a no-op on non-Windows platforms. | ||
| func stopWinNAT() error { return nil } | ||
|
|
||
| // startWinNAT is a no-op on non-Windows platforms. | ||
| func startWinNAT() error { return nil } | ||
|
|
||
| // startWSLBackground is a no-op on non-Windows platforms. | ||
| func startWSLBackground() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| //go:build !windows | ||
|
|
||
| /* | ||
| Copyright 2021 The Dapr Authors | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package standalone | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| // TestNoopStubs verifies that every non-Windows stub returns the correct | ||
| // zero/no-op value and does not panic. This guards against accidental breakage | ||
| // of the cross-platform build contract. | ||
| func TestNoopStubs(t *testing.T) { | ||
| assert.False(t, isWindowsElevated(), "isWindowsElevated must always be false on non-Windows") | ||
| assert.False(t, isWSLAvailable(), "isWSLAvailable must always be false on non-Windows") | ||
| assert.NoError(t, shutdownWSL()) | ||
| assert.NoError(t, stopWinNAT()) | ||
| assert.NoError(t, startWinNAT()) | ||
| startWSLBackground() // must not panic | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| /* | ||
| Copyright 2021 The Dapr Authors | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package standalone | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // TestCheckPorts exercises the core port-availability helper used by the | ||
| // Windows WSL2 port-conflict detection path. | ||
| func TestCheckPorts(t *testing.T) { | ||
| t.Run("returns nil when given no ports", func(t *testing.T) { | ||
| assert.NoError(t, checkPorts()) | ||
| }) | ||
|
|
||
| t.Run("returns nil when all ports are free", func(t *testing.T) { | ||
| // Port 0 always passes CheckIfPortAvailable (the OS selects a free | ||
| // ephemeral port), so there is no bind/close race here. | ||
| assert.NoError(t, checkPorts(0, 0)) | ||
| }) | ||
|
|
||
| t.Run("returns error containing port number when port is in use", func(t *testing.T) { | ||
| ln := holdPort(t) | ||
| defer ln.Close() | ||
| port := ln.Addr().(*net.TCPAddr).Port | ||
|
|
||
| err := checkPorts(port) | ||
| require.Error(t, err) | ||
| assert.Contains(t, err.Error(), fmt.Sprintf("port %d", port)) | ||
| }) | ||
|
|
||
| t.Run("returns error for first occupied port in the list", func(t *testing.T) { | ||
| ln := holdPort(t) | ||
| defer ln.Close() | ||
| busy := ln.Addr().(*net.TCPAddr).Port | ||
|
|
||
| // Port 0 is always free (OS picks an ephemeral port); busy comes second. | ||
| // We still expect failure once the busy port is reached. | ||
| err := checkPorts(0, busy) | ||
| require.Error(t, err) | ||
|
WhitWaldo marked this conversation as resolved.
|
||
| assert.Contains(t, err.Error(), fmt.Sprintf("port %d", busy)) | ||
| }) | ||
|
|
||
| t.Run("errors when the first port is occupied, ignoring a free port that follows", func(t *testing.T) { | ||
| ln := holdPort(t) | ||
| defer ln.Close() | ||
| busy := ln.Addr().(*net.TCPAddr).Port | ||
|
|
||
| // busy comes first, port 0 (always free) follows — error must name busy only. | ||
| err := checkPorts(busy, 0) | ||
| require.Error(t, err) | ||
| assert.Contains(t, err.Error(), fmt.Sprintf("port %d", busy)) | ||
| assert.NotContains(t, err.Error(), "port 0") | ||
| }) | ||
| } | ||
|
|
||
| // TestCheckSchedulerPorts_PortInUse verifies that checkSchedulerPorts surfaces | ||
| // an error (with the port number) when the gRPC port it is given is already | ||
| // bound. This is the scenario triggered by WSL2 holding scheduler ports. | ||
| func TestCheckSchedulerPorts_PortInUse(t *testing.T) { | ||
| ln := holdPort(t) | ||
| defer ln.Close() | ||
| busyPort := ln.Addr().(*net.TCPAddr).Port | ||
|
|
||
| err := checkSchedulerPorts(busyPort) | ||
| require.Error(t, err) | ||
| assert.Contains(t, err.Error(), fmt.Sprintf("port %d", busyPort)) | ||
| } | ||
|
|
||
| // holdPort binds an OS-assigned port and returns the listener. The caller is | ||
| // responsible for closing it. Using ":0" matches the binding style of | ||
| // utils.CheckIfPortAvailable so the conflict is detected reliably. | ||
| func holdPort(t *testing.T) net.Listener { | ||
| t.Helper() | ||
| ln, err := net.Listen("tcp", ":0") | ||
| require.NoError(t, err) | ||
| return ln | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| //go:build windows | ||
|
|
||
| /* | ||
| Copyright 2021 The Dapr Authors | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package standalone | ||
|
|
||
| import ( | ||
| "os/exec" | ||
|
|
||
| "golang.org/x/sys/windows" | ||
|
|
||
| "github.com/dapr/cli/utils" | ||
| ) | ||
|
|
||
| // isWindowsElevated returns true if the current process is running with | ||
| // elevated (Administrator) privileges. | ||
| func isWindowsElevated() bool { | ||
| return windows.GetCurrentProcessToken().IsElevated() | ||
| } | ||
|
|
||
| // isWSLAvailable returns true if the wsl executable is available in PATH. | ||
| func isWSLAvailable() bool { | ||
| _, err := exec.LookPath("wsl") | ||
| return err == nil | ||
| } | ||
|
|
||
| // shutdownWSL runs `wsl --shutdown` to terminate the WSL2 instance and free any | ||
| // ports it holds. | ||
| func shutdownWSL() error { | ||
| _, err := utils.RunCmdAndWait("wsl", "--shutdown") | ||
| return err | ||
| } | ||
|
|
||
| // stopWinNAT stops the Windows NAT driver service (WinNat) so that Docker | ||
| // can re-acquire port bindings that WinNAT was caching. | ||
| func stopWinNAT() error { | ||
| _, err := utils.RunCmdAndWait("net", "stop", "winnat") | ||
| return err | ||
| } | ||
|
|
||
| // startWinNAT starts the Windows NAT driver service after the scheduler | ||
| // container has been created. | ||
| func startWinNAT() error { | ||
| _, err := utils.RunCmdAndWait("net", "start", "winnat") | ||
| return err | ||
| } | ||
|
|
||
| // startWSLBackground starts WSL in the background to re-initialize WSL | ||
| // networking after a wsl --shutdown. We run a no-op command so the session | ||
| // exits immediately once WSL services are up, then wait in a goroutine to | ||
| // clean up the process handle. | ||
| func startWSLBackground() { | ||
| cmd := exec.Command("wsl", "--exec", "echo") | ||
| if err := cmd.Start(); err != nil { | ||
| return | ||
| } | ||
| go func() { _ = cmd.Wait() }() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| //go:build windows | ||
|
|
||
| /* | ||
| Copyright 2021 The Dapr Authors | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| */ | ||
|
|
||
| package standalone | ||
|
|
||
| import ( | ||
| "os/exec" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| // TestIsWindowsElevated_Callable verifies the function completes without | ||
| // panicking. The actual return value depends on whether the test process is | ||
| // running as Administrator, so we only log it rather than assert a fixed value. | ||
| func TestIsWindowsElevated_Callable(t *testing.T) { | ||
| elevated := isWindowsElevated() | ||
| t.Logf("isWindowsElevated() = %v (test process running as Administrator: %v)", elevated, elevated) | ||
| } | ||
|
|
||
| // TestIsWSLAvailable_MatchesLookPath verifies that isWSLAvailable reports the | ||
| // same result as exec.LookPath("wsl"), confirming it accurately reflects | ||
| // whether wsl.exe is on the PATH. | ||
| func TestIsWSLAvailable_MatchesLookPath(t *testing.T) { | ||
| _, err := exec.LookPath("wsl") | ||
| expected := err == nil | ||
| assert.Equal(t, expected, isWSLAvailable(), | ||
| "isWSLAvailable() should return true iff wsl.exe is on PATH") | ||
| } | ||
|
|
||
| // TestStartWSLBackground_DoesNotBlock verifies that startWSLBackground returns | ||
| // promptly regardless of whether WSL is installed. When WSL is absent the | ||
| // internal cmd.Start() fails silently; when present, wsl --exec echo exits | ||
| // immediately and the cleanup goroutine reaps it. | ||
| func TestStartWSLBackground_DoesNotBlock(t *testing.T) { | ||
| // This must complete without hanging; no assertion on side-effects. | ||
| startWSLBackground() | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.