From dd809e80882abc906ebfc2ec0103ad8b17b15c0b Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Mon, 3 Oct 2016 14:49:19 -0400 Subject: [PATCH] Add global timeout flag This patch adds a global timeout flag (viewable with `oc options`) with a default value of `0s` (meaning no timeout). It renames all local `timeout` flags previously declared in specific commands, such as `kubectl delete` and `kubectl scale`, to avoid conflict with the new global timeout flag. No new functionality is added to enforce a timeout. The timeout value is added to the default http client, so that zero values and default behavior is enforced by the client. **Example** ``` $ kubectl get pods # no timeout flag set - default to 0s (which means no timeout) NAME READY STATUS RESTARTS AGE docker-registry-1-h7etw 1/1 Running 1 2h router-1-uv0f9 1/1 Running 1 2h $ kubectl get pods --timeout=0 # zero means no timeout no timeout flag set NAME READY STATUS RESTARTS AGE docker-registry-1-h7etw 1/1 Running 1 2h router-1-uv0f9 1/1 Running 1 2h $kubectl get pods --timeout=1ms Unable to connect to the server: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) ``` --- pkg/client/restclient/config.go | 10 ++++++++ .../unversioned/clientcmd/client_config.go | 1 + pkg/client/unversioned/clientcmd/overrides.go | 23 ++++++++++++++++++- pkg/kubectl/cmd/delete.go | 4 ++-- pkg/kubectl/cmd/replace.go | 8 +++---- pkg/kubectl/cmd/rollingupdate.go | 4 ++-- pkg/kubectl/cmd/scale.go | 4 ++-- pkg/kubectl/cmd/stop.go | 4 ++-- 8 files changed, 45 insertions(+), 13 deletions(-) diff --git a/pkg/client/restclient/config.go b/pkg/client/restclient/config.go index ae1c8e7b77fd..c8f6a0ba4885 100644 --- a/pkg/client/restclient/config.go +++ b/pkg/client/restclient/config.go @@ -25,6 +25,7 @@ import ( "path" gruntime "runtime" "strings" + "time" "github.com/golang/glog" @@ -109,6 +110,9 @@ type Config struct { // Rate limiter for limiting connections to the master from this client. If present overwrites QPS/Burst RateLimiter flowcontrol.RateLimiter + // The maximum length of time to wait before giving up on a server request. A value of zero means no timeout. + Timeout time.Duration + // Version forces a specific version to be used (if registered) // Do we need this? // Version string @@ -185,6 +189,9 @@ func RESTClientFor(config *Config) (*RESTClient, error) { var httpClient *http.Client if transport != http.DefaultTransport { httpClient = &http.Client{Transport: transport} + if config.Timeout > 0 { + httpClient.Timeout = config.Timeout + } } return NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, qps, burst, config.RateLimiter, httpClient) @@ -210,6 +217,9 @@ func UnversionedRESTClientFor(config *Config) (*RESTClient, error) { var httpClient *http.Client if transport != http.DefaultTransport { httpClient = &http.Client{Transport: transport} + if config.Timeout > 0 { + httpClient.Timeout = config.Timeout + } } versionConfig := config.ContentConfig diff --git a/pkg/client/unversioned/clientcmd/client_config.go b/pkg/client/unversioned/clientcmd/client_config.go index 8223bad94efa..68f3a2dbfe77 100644 --- a/pkg/client/unversioned/clientcmd/client_config.go +++ b/pkg/client/unversioned/clientcmd/client_config.go @@ -108,6 +108,7 @@ func (config *DirectClientConfig) ClientConfig() (*restclient.Config, error) { clientConfig := &restclient.Config{} clientConfig.Host = configClusterInfo.Server + clientConfig.Timeout = config.overrides.Timeout if u, err := url.ParseRequestURI(clientConfig.Host); err == nil && u.Opaque == "" && len(u.Path) > 1 { u.RawQuery = "" u.Fragment = "" diff --git a/pkg/client/unversioned/clientcmd/overrides.go b/pkg/client/unversioned/clientcmd/overrides.go index 9c117ea3548e..6d3269cca82e 100644 --- a/pkg/client/unversioned/clientcmd/overrides.go +++ b/pkg/client/unversioned/clientcmd/overrides.go @@ -18,6 +18,7 @@ package clientcmd import ( "strconv" + "time" "github.com/spf13/pflag" @@ -33,6 +34,7 @@ type ConfigOverrides struct { ClusterInfo clientcmdapi.Cluster Context clientcmdapi.Context CurrentContext string + Timeout time.Duration } // ConfigOverrideFlags holds the flag names to be used for binding command line flags. Notice that this structure tightly @@ -42,6 +44,7 @@ type ConfigOverrideFlags struct { ClusterOverrideFlags ClusterOverrideFlags ContextOverrideFlags ContextOverrideFlags CurrentContext FlagInfo + Timeout FlagInfo } // AuthOverrideFlags holds the flag names to be used for binding command line flags for AuthInfo objects @@ -105,6 +108,20 @@ func (f FlagInfo) BindBoolFlag(flags *pflag.FlagSet, target *bool) { } } +// BindDurationFlag binds the flag based on the provided info. If LongName == "", nothing is registered +func (f FlagInfo) BindDurationFlag(flags *pflag.FlagSet, target *time.Duration) { + // you can't register a flag without a long name + if len(f.LongName) > 0 { + // try to parse Default as a Duration. If it fails, default to 0s + durationVal, err := time.ParseDuration(f.Default) + if err != nil { + durationVal = 0 + } + + flags.DurationVarP(target, f.LongName, f.ShortName, durationVal, f.Description) + } +} + const ( FlagClusterName = "cluster" FlagAuthInfoName = "user" @@ -121,6 +138,7 @@ const ( FlagImpersonate = "as" FlagUsername = "username" FlagPassword = "password" + FlagTimeout = "timeout" ) // RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing @@ -151,7 +169,9 @@ func RecommendedConfigOverrideFlags(prefix string) ConfigOverrideFlags { AuthOverrideFlags: RecommendedAuthOverrideFlags(prefix), ClusterOverrideFlags: RecommendedClusterOverrideFlags(prefix), ContextOverrideFlags: RecommendedContextOverrideFlags(prefix), - CurrentContext: FlagInfo{prefix + FlagContext, "", "", "The name of the kubeconfig context to use"}, + + CurrentContext: FlagInfo{prefix + FlagContext, "", "", "The name of the kubeconfig context to use"}, + Timeout: FlagInfo{prefix + FlagTimeout, "", "", "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests."}, } } @@ -190,6 +210,7 @@ func BindOverrideFlags(overrides *ConfigOverrides, flags *pflag.FlagSet, flagNam BindClusterFlags(&overrides.ClusterInfo, flags, flagNames.ClusterOverrideFlags) BindContextFlags(&overrides.Context, flags, flagNames.ContextOverrideFlags) flagNames.CurrentContext.BindStringFlag(flags, &overrides.CurrentContext) + flagNames.Timeout.BindDurationFlag(flags, &overrides.Timeout) } // BindFlags is a convenience method to bind the specified flags to their associated variables diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index daf7f8087417..81fb966a6926 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -102,7 +102,7 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("cascade", true, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.") cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") cmd.Flags().Bool("now", false, "If true, resources are force terminated without graceful deletion (same as --grace-period=0).") - cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") + cmd.Flags().Duration("delete-timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) return cmd @@ -153,7 +153,7 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" // By default use a reaper to delete all related resources. if cmdutil.GetFlagBool(cmd, "cascade") { - return ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), gracePeriod, shortOutput, mapper, false) + return ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "delete-timeout"), gracePeriod, shortOutput, mapper, false) } return DeleteResult(r, out, ignoreNotFound, shortOutput, mapper) } diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index 12ce0b1257ee..27e001143e32 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -80,7 +80,7 @@ func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("force", false, "Delete and re-create the specified resource") cmd.Flags().Bool("cascade", false, "Only relevant during a force replace. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController).") cmd.Flags().Int("grace-period", -1, "Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.") - cmd.Flags().Duration("timeout", 0, "Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") + cmd.Flags().Duration("delete-timeout", 0, "Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") cmdutil.AddValidateFlags(cmd) cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddApplyAnnotationFlags(cmd) @@ -118,8 +118,8 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st return fmt.Errorf("--grace-period must have --force specified") } - if cmdutil.GetFlagDuration(cmd, "timeout") != 0 { - return fmt.Errorf("--timeout must have --force specified") + if cmdutil.GetFlagDuration(cmd, "delete-timeout") != 0 { + return fmt.Errorf("--delete-timeout must have --force specified") } mapper, typer := f.Object() @@ -207,7 +207,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] } //Replace will create a resource if it doesn't exist already, so ignore not found error ignoreNotFound := true - timeout := cmdutil.GetFlagDuration(cmd, "timeout") + timeout := cmdutil.GetFlagDuration(cmd, "delete-timeout") // By default use a reaper to delete all related resources. if cmdutil.GetFlagBool(cmd, "cascade") { glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.") diff --git a/pkg/kubectl/cmd/rollingupdate.go b/pkg/kubectl/cmd/rollingupdate.go index dc37cc8eefa6..e31d6dc78c36 100644 --- a/pkg/kubectl/cmd/rollingupdate.go +++ b/pkg/kubectl/cmd/rollingupdate.go @@ -91,7 +91,7 @@ func NewCmdRollingUpdate(f *cmdutil.Factory, out io.Writer) *cobra.Command { } cmd.Flags().Duration("update-period", updatePeriod, `Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) cmd.Flags().Duration("poll-interval", pollInterval, `Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) - cmd.Flags().Duration("timeout", timeout, `Max time to wait for a replication controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) + cmd.Flags().Duration("update-timeout", timeout, `Max time to wait for a replication controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`) usage := "Filename or URL to file to use to create the new replication controller." kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) cmd.MarkFlagRequired("filename") @@ -158,7 +158,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg rollback := cmdutil.GetFlagBool(cmd, "rollback") period := cmdutil.GetFlagDuration(cmd, "update-period") interval := cmdutil.GetFlagDuration(cmd, "poll-interval") - timeout := cmdutil.GetFlagDuration(cmd, "timeout") + timeout := cmdutil.GetFlagDuration(cmd, "update-timeout") dryrun := cmdutil.GetDryRunFlag(cmd) outputFormat := cmdutil.GetFlagString(cmd, "output") container := cmdutil.GetFlagString(cmd, "container") diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index c850d19afdd7..441ca4294c0a 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -82,7 +82,7 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int("current-replicas", -1, "Precondition for current size. Requires that the current size of the resource match this value in order to scale.") cmd.Flags().Int("replicas", -1, "The new desired number of replicas. Required.") cmd.MarkFlagRequired("replicas") - cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") + cmd.Flags().Duration("scale-timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).") cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddRecordFlag(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) @@ -154,7 +154,7 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri retry := kubectl.NewRetryParams(kubectl.Interval, kubectl.Timeout) var waitForReplicas *kubectl.RetryParams - if timeout := cmdutil.GetFlagDuration(cmd, "timeout"); timeout != 0 { + if timeout := cmdutil.GetFlagDuration(cmd, "scale-timeout"); timeout != 0 { waitForReplicas = kubectl.NewRetryParams(kubectl.Interval, timeout) } diff --git a/pkg/kubectl/cmd/stop.go b/pkg/kubectl/cmd/stop.go index fbfb33fc70bc..340eaa88bc66 100644 --- a/pkg/kubectl/cmd/stop.go +++ b/pkg/kubectl/cmd/stop.go @@ -69,7 +69,7 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.") cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful stop.") cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") - cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") + cmd.Flags().Duration("delete-timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") cmdutil.AddOutputFlagsForMutation(cmd) cmdutil.AddInclude3rdPartyFlags(cmd) return cmd @@ -95,5 +95,5 @@ func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, out io.Write return r.Err() } shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" - return ReapResult(r, f, out, false, cmdutil.GetFlagBool(cmd, "ignore-not-found"), cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper, false) + return ReapResult(r, f, out, false, cmdutil.GetFlagBool(cmd, "ignore-not-found"), cmdutil.GetFlagDuration(cmd, "delete-timeout"), cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper, false) }