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

Defer API version negotiation #1747

Closed

Conversation

simonferquel
Copy link
Contributor

@simonferquel simonferquel commented Mar 18, 2019

- What I did

Fix #1739 by deferring the API version negotiation to the point we actually need access to either the ServerInfo or the API client interface.

Also introduce a no-remote annotation for marking commands that do not require anything on the daemon side to work. Commands annotated as such are not evaluated by the conditional hiding feature (which relies on ServerInfo to get access to the daemon OS, experimental flag, etc.).

Context commands are annotated as no-remote

- How to verify it

  • Create a docker context to an unreachable endpoint : docker context create faulty --docker host=tcp://42.42.42.41:4242
  • Makes it your default context: docker context use faulty
  • Commands that require interaction with the daemon should trigger a 1m timeout (e.g docker info)
  • Commands that do not require any interaction with the daemon should run quickly (e.g. docker context ls)

CC @silvin-lubecki @thaJeztah

@thaJeztah
Copy link
Member

Can you s/negociation/negotiation in your commit-message? 🤗 (Otherwise I'm sure I won't be able to find back this change 😅 )


func isNoRemote(cmd *cobra.Command) bool {
for cmd != nil {
if _, ok := cmd.Annotations["no-remote"]; ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, but I don't like negations in names, because you can end up with a double negations, like if !isNoRemote(...) ... and double negations are generally brain fucked 😅
That said, I don't have better name to propose... I thought about "local" but I think it is too vague.
@thaJeztah any idea? 😅

Copy link
Member

Choose a reason for hiding this comment

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

IsLocal ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IsDaemonLess ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rename the annotation too? no-remote -> local-only ?

@codecov-io
Copy link

codecov-io commented Mar 18, 2019

Codecov Report

Merging #1747 into master will increase coverage by 0.18%.
The diff coverage is 76.36%.

@@            Coverage Diff             @@
##           master    #1747      +/-   ##
==========================================
+ Coverage   56.26%   56.44%   +0.18%     
==========================================
  Files         308      308              
  Lines       21293    21354      +61     
==========================================
+ Hits        11980    12053      +73     
- Misses       8439     8442       +3     
+ Partials      874      859      -15

@simonferquel
Copy link
Contributor Author

Rebased, rewrote commit text, small refactoring to support checks for both local-only and experimental CLI

@thaJeztah thaJeztah changed the title Defer API version negociation Defer API version negotiation Mar 18, 2019
Copy link
Contributor

@silvin-lubecki silvin-lubecki left a comment

Choose a reason for hiding this comment

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

Code looks good, but I fear some regressions as it is like a huge refactoring, and we already saw many regressions on this part before + there are not much tests 😞
Do you think you could add some?

@simonferquel
Copy link
Contributor Author

Added a LOT of tests. PTAL @silvin-lubecki, @thaJeztah

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

left some comments inline, but looks good overall

cmd/docker/docker_test.go Outdated Show resolved Hide resolved
cli/command/config/create.go Outdated Show resolved Hide resolved
cmd/docker/docker.go Show resolved Hide resolved
@thaJeztah
Copy link
Member

ping @simonferquel 🤗

@simonferquel
Copy link
Contributor Author

@thaJeztah @silvin-lubecki I just rebased, extracted the fixes in different PRs (which I am based onto).
I had a problem because recently, docker build --platform has changed its logic around annotations and requires the serverInfo while constructing the command (putting different annotations depending on buildkit enabled state).

To fix the problem, I added a notion of "LazyFlagCheck" which allows to add custom validation logic on a flag, which will get executed after the cli is actually initialized. It is a bit ugly around the edges though, and I'd be glad if we could have a different approach.
@vdemeester any thought about this one ?

This commit defers API version negotiation within the dockerCli structure to the point where we need to access
server info or the API client itself. This also introduce a "no-remote"
annotation, to skip the evaluation of server-side support for those
commands (which requires access to server-info). The context subcommands
are marked with this "no-remote" annotation so that they respond quickly
even if the current context points to an unreachable endpoint.

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
@simonferquel
Copy link
Contributor Author

@thaJeztah PTAL :) (and @silvin-lubecki as well)

Copy link
Contributor

@silvin-lubecki silvin-lubecki left a comment

Choose a reason for hiding this comment

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

I approve this PR, even if I find this lazycheck logic too complicated. I think it was introduced due to this PR, we should try to fix/simplify that in a follow-up.

Otherwise thank you @simonferquel for the bunch of tests 😍

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
@thaJeztah
Copy link
Member

Hm.. not sure if it's introduced in this PR, but trying this;

docker -H tcp://localhost:1234 version
Please specify only one -H

Instead of

docker -H tcp://localhost:1234 version

Client: Docker Engine - Community
 Version:           18.09.3
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        774a1f4
 Built:             Thu Feb 28 06:32:50 2019
 OS/Arch:           darwin/amd64
 Experimental:      true
Cannot connect to the Docker daemon at tcp://localhost:1234. Is the docker daemon running?

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

Some nits, but I agree with @silvin-lubecki that this adds a lot of complexity; I want to give this another look and see if we can somehow make this less complex. (e.g. just add a local-only and/or lazy-evaluate annotation where needed, and skip those if serverinfo is not present? I'd have to give that some thought.

}

// EvaluateFlagLazyChacks evaluates the lazy checks associated with a flag depending on client/server info
func EvaluateFlagLazyChacks(flag *pflag.Flag, clientInfo command.ClientInfo, serverInfo command.ServerInfo, clientVersion string) error {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
func EvaluateFlagLazyChacks(flag *pflag.Flag, clientInfo command.ClientInfo, serverInfo command.ServerInfo, clientVersion string) error {
func EvaluateFlagLazyChecks(flag *pflag.Flag, clientInfo command.ClientInfo, serverInfo command.ServerInfo, clientVersion string) error {

f.Annotations[lazyCheckAnnotation] = append(f.Annotations[lazyCheckAnnotation], fmt.Sprintf("%d", index))
}

// EvaluateFlagLazyChacks evaluates the lazy checks associated with a flag depending on client/server info
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// EvaluateFlagLazyChacks evaluates the lazy checks associated with a flag depending on client/server info
// EvaluateFlagLazyChecks evaluates the lazy checks associated with a flag depending on client/server info

@@ -8,6 +8,8 @@ import (
"strings"
"syscall"

"github.com/docker/cli/cli/command/commands/lazychecks"
Copy link
Member

Choose a reason for hiding this comment

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

this should be grouped with the imports below

@simonferquel
Copy link
Contributor Author

@thaJeztah bad news, it does the same thing on master. Toplevel flags seem to be set twice for some reason. For flags of type "list" it insert the same value twice.

ijc pushed a commit to ijc/docker-cli that referenced this pull request May 13, 2019
This partially mitigates docker#1739 ("Docker commands take 1 minute to timeout if
context endpoint is unreachable") and is a simpler alternative to docker#1747 (which
completely defers the client connection until an actual call is attempted).

Note that the previous 60s delay was the culmination of two separate 30s
timeouts since the ping is tried twice. This with this patch the overall
timeout is 20s. moby/moby#39206 will remove the second
ping and once that propagates to this tree the timeout will be 10s.

Signed-off-by: Ian Campbell <ijc@docker.com>
@gtardif
Copy link
Contributor

gtardif commented Apr 7, 2020

@thaJeztah hi, digging a bit in history, any reason for holding this one ? It looks like something that might be needed for #2420.

@thaJeztah
Copy link
Member

I'd have to look into this one again; I know I wasn't particularly happy with the amount of complexity involved. Wondering if using the WithAPIVersionNegotiation (moby/moby#39032) option would do the trick 🤔

@thaJeztah
Copy link
Member

I see this PR is on master, but not in the 19.03 release; #1845. Haven't tested if it solves the delay though

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.

Docker commands take 1 minute to timeout if context endpoint is unreachable
6 participants