Skip to content

Conversation

@douglascamata
Copy link
Contributor

@douglascamata douglascamata commented Nov 30, 2022

With proper detection of WSL 2 we can give it the special treatment it needs.

Changes

On detection of WSL 2, the following happens:

  • Fail fast if cAdvisor is also enabled: it's not supported in WSL 2.
  • The check for networking from container to host will listen only on tcp4 (IPv4). I tested with tcp (should be IPv4 and v6) but it ended up only opening a tcp6 port and the check happens via IPv4, which fails.
  • The hostAddr is set to host.docker.local, as Windows/WSL 2 also use it.
  • The OpenInBrowser will open the URLs in the Windows browser.

I tested this out in my Windows box and it works flawlessly.

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Comment on lines 43 to 52
func WSL2() (bool, error) {
if runtime.GOOS != "linux" {
return false, nil
}
out, err := exec.Command("cat", "/proc/version").CombinedOutput()
if err != nil {
return false, errors.Wrapf(err, "detecting WSL2: %s", string(out))
}
return bytes.Contains(out, []byte("WSL2")), nil
}
Copy link
Contributor Author

@douglascamata douglascamata Nov 30, 2022

Choose a reason for hiding this comment

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

I would love to move this WSL2 function to https://github.com/efficientgo/core. What do you think? cc @matej-g @bwplotka

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also considering adding context support there to enable quick work cancellation. Not sure whether it would be valuable.

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
if runtime.GOOS != "linux" {
return false, nil
}
out, err := exec.Command("cat", "/proc/version").CombinedOutput()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can just do grep here instead of reading the entire contents and doing bytes.Contains?

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 am afraid grep might not be thoroughly available in Linux distros by default, especially the more slim ones. /proc/version contains very little text, so I don't think the performance is a problem. It has this "shape": Linux version 2.6.31.12-0.2-default (geeko@buildhost) (gcc version 4.4.1 [gcc-4_4-branch revision 150839] (SUSE Linux) ) #1 SMP 2010-03-16 21:25:39 +0100.

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 even thought about not using cat and directly reading /proc/version. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually now I consider simply reading the file better. 100% agnostic of whatever is installed in the OS and hides the error from callers. If the file doesn't exist it means not WSL 2.

if err != nil {
return false, errors.Wrapf(err, "detecting WSL2: %s", string(out))
}
return bytes.Contains(out, []byte("WSL2")), nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to check if it contains WSL2 or Microsoft? Referriing to similar issue microsoft/WSL#4071

Copy link
Contributor Author

@douglascamata douglascamata Nov 30, 2022

Choose a reason for hiding this comment

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

I'm aware of that issue. I believe it is better to check for WSL2 because:

  • I don't know and didn't test if this works with WSL 1. Likely it doesn't as networking is very different between WSL 1 and 2.
  • We are future proof in case Microsoft releases WSL 3 and it requires a different workaround (😂 😭 )
  • No false positives if one day a wild Microsoft Linux distribution appears.

Copy link

Choose a reason for hiding this comment

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

I'm just imagining someone having to come back to this code in 5 years like 'wth is a WSL?'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Documented what is WSL at ad6f833

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
env.go Outdated
// Close shutdowns isolated environment and cleans its resources.
Close()
// WSL2 returns true if the environment is WSL2.
WSL2() bool
Copy link

Choose a reason for hiding this comment

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

I'm not sure i understand why we need this. If WSL2 is not compatible with cadvisor we should either:

  • Remove cadvisor (to remove a dependency on linux for e2e)
  • Keep cadvisor, and encourage people who use linux subsystem on linux to disable cadvisor.

Adding a field to Environment configuration for this very specific edge case stinks of 'leaky abstraction'.

Conversely you can invert this and have a 'useCadvisor' env variable that will determine if it should be enabled or not so there isn't this conceptual hop from:
' Why is it relevant if i'm using WSL2?'
greps WSL2
'Oh, WSL doesn't support cadvisor.'
'I'll just disable cadvisor'

Just my thoughts, not willing to die on this hill though. IMHO i'd rather avoid embedding OS checks in configuration for very specific feature compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got rid of the WSL() function in the Environment interface, as indeed I agree it leaks details from the DockerEnvironment type. Instead now I just import e2einteractive and use it from there. Note that as I mentioned in another comment, I would like to move the function to efficientgo/core if maintainers agree. Changes are in 4330eb9.

Keep cadvisor, and encourage people who use linux subsystem on linux to disable cadvisor.

I talked with @bwplotka about this and our conclusion is that we should "panic" and stop execution in the scenario where we're running in WSL 2 and the option to run cAdvisor is set to true.

env_docker.go Outdated
d.hostAddr = dockerMacOSGatewayAddr
d.hostAddr = dockerGatewayAddr
default:
if d.WSL2() {
Copy link

Choose a reason for hiding this comment

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

What does runtime.GOOS return if running on WSL? If it isn't linux what is it?

This switch statement is also redundant, you really shouldn't have to branch conditionals inside of a switch. This should either be in a case or should not be set here.

I also know it was like this before so 🤷🏽

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What does runtime.GOOS return if running on WSL? If it isn't linux what is it?

It does return linux, that's why the implementation checks for the kernel version by reading from /proc/version. Also it uses runtime.GOOS != linux as a condition to fail quickly returning false.

// via IPv4 to confirm Prometheus can scrape the local endpoint.
// Explicitly asking for an IPv4 listener works.
if env.WSL2() {
networkType = "tcp4"
Copy link

@moadz moadz Nov 30, 2022

Choose a reason for hiding this comment

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

Is tcp4 the more specific option (specifies you want an ipv4 connection)? If so we can just make that the default and get rid of the branching conditionals.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can do this. Only downside would be that Docker one day might drop support for ipv4, but I bet that's likely going to happen only after our lifetimes. Will change!

Copy link
Contributor Author

@douglascamata douglascamata Nov 30, 2022

Choose a reason for hiding this comment

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

Oh, this might also not work on servers/machines with only ipv6 stack (might some cloud/on-prem CI runners?). 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you think, @moadz?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use IPv6 then for network port later on? If the latter step requires IPv4, we might as well put tcp4 here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is only affecting the listener that is used for a quick test of the host-container networking. It won't affect anything after this point.

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Comment on lines 41 to 60
// HostOSPlatform returns the host's OS platform akin to `runtime.GOOS`, with
// added awareness of Windows Subsystem for Linux (WSL) 2 environments.
// The possible values are the same as `runtime.GOOS`, plus "WSL2".
func HostOSPlatform() string {
if wsl2() {
return "WSL2"
}
return runtime.GOOS
}

func wsl2() bool {
if runtime.GOOS != "linux" {
return false
}
version, err := os.ReadFile("/proc/version")
if err != nil {
return false
}
return bytes.Contains(version, []byte("WSL2"))
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Repeating old comment here for visibility after some updates: I would love to move these two functions into https://github.com/efficientgo/core. If maintainers agree, I'll open a PR there and update this one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm... We could consider that. Feel free to add TODO with this, but I ideally we test it's usability (how useful it is for others) in e2e first.

Copy link
Contributor

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Some thoughts, otherwise LGTM. Thanks!

Comment on lines 41 to 60
// HostOSPlatform returns the host's OS platform akin to `runtime.GOOS`, with
// added awareness of Windows Subsystem for Linux (WSL) 2 environments.
// The possible values are the same as `runtime.GOOS`, plus "WSL2".
func HostOSPlatform() string {
if wsl2() {
return "WSL2"
}
return runtime.GOOS
}

func wsl2() bool {
if runtime.GOOS != "linux" {
return false
}
version, err := os.ReadFile("/proc/version")
if err != nil {
return false
}
return bytes.Contains(version, []byte("WSL2"))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm... We could consider that. Feel free to add TODO with this, but I ideally we test it's usability (how useful it is for others) in e2e first.

// via IPv4 to confirm Prometheus can scrape the local endpoint.
// Explicitly asking for an IPv4 listener works.
if env.WSL2() {
networkType = "tcp4"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use IPv6 then for network port later on? If the latter step requires IPv4, we might as well put tcp4 here.

env.AddListener(l)

if opt.useCadvisor {
if e2einteractive.HostOSPlatform() == "WSL2" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, this is ideally in separate package or e2e.

Copy link
Contributor

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Amazing, thanks! 💪🏽

return false
}
version, err := os.ReadFile("/proc/version")
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we could be more strict on error cases, but we can improve later (:

@bwplotka bwplotka merged commit 596a48e into efficientgo:main Dec 7, 2022
Rasek91 pushed a commit to Rasek91/efficientgo-e2e that referenced this pull request May 13, 2023
* Detect WSL2 and open in browser from Windows

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Fix hostAddr in WSL2

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Fix host-local endpoint test in WSL2

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Fail fast to start in WSL2 with cadvisor

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Inline some conditionals

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Detect WSL 2 without relying on OS binaries/tools

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Fix var name

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Drop environment.WSL2() and use func from package

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Improve how OS platform is checked

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Document what is WSL

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Fix WSL2 check for network test

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Fix networkType deletion

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>

* Separate OS platform function into its own package

* Reorder imports

* Add TODO to new `host` package

* Please the copyright linter

Signed-off-by: Douglas Camata <159076+douglascamata@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants