diff --git a/test/images/agnhost/BUILD b/test/images/agnhost/BUILD index 351b0c1ce9c..f9faaaeeaaa 100644 --- a/test/images/agnhost/BUILD +++ b/test/images/agnhost/BUILD @@ -17,6 +17,7 @@ go_library( importpath = "k8s.io/kubernetes/test/images/agnhost", deps = [ "//test/images/agnhost/audit-proxy:go_default_library", + "//test/images/agnhost/connect:go_default_library", "//test/images/agnhost/crd-conversion-webhook:go_default_library", "//test/images/agnhost/dns:go_default_library", "//test/images/agnhost/entrypoint-tester:go_default_library", @@ -51,6 +52,7 @@ filegroup( srcs = [ ":package-srcs", "//test/images/agnhost/audit-proxy:all-srcs", + "//test/images/agnhost/connect:all-srcs", "//test/images/agnhost/crd-conversion-webhook:all-srcs", "//test/images/agnhost/dns:all-srcs", "//test/images/agnhost/entrypoint-tester:all-srcs", diff --git a/test/images/agnhost/README.md b/test/images/agnhost/README.md index 7f90017d78e..771d53967ee 100644 --- a/test/images/agnhost/README.md +++ b/test/images/agnhost/README.md @@ -10,7 +10,7 @@ information could retrieved through other means. To combat those differences, `agnhost` is an extendable CLI that behaves and outputs the same expected content, no matter the underlying OS. The name itself reflects this idea, being a portmanteau -word of the words agnost and host. +word of the words agnostic and host. The image was created for testing purposes, reducing the need for having different test cases for the same tested behaviour. @@ -77,6 +77,29 @@ Usage: ``` +### connect + +Tries to open a TCP connection to the given host and port. On error it +prints an error message prefixed with a specific fixed string that +test cases can check for: + +* `UNKNOWN` - Generic/unknown (non-network) error (eg, bad arguments) +* `TIMEOUT` - The connection attempt timed out +* `DNS` - An error in DNS resolution +* `REFUSED` - Connection refused +* `OTHER` - Other networking error (eg, "no route to host") + +(Theoretically it would be nicer for it to distinguish these by exit +code, but it's much easier for test programs to compare strings in the +output than to check the exit code.) + +Usage: + +```console + kubectl exec test-agnost -- /agnost connect [--timeout=] : +``` + + ### crd-conversion-webhook The subcommand tests `CustomResourceConversionWebhook`. After deploying it to Kubernetes cluster, diff --git a/test/images/agnhost/VERSION b/test/images/agnhost/VERSION index 8bbe6cf74a1..6b4950e3de2 100644 --- a/test/images/agnhost/VERSION +++ b/test/images/agnhost/VERSION @@ -1 +1 @@ -2.2 +2.4 diff --git a/test/images/agnhost/agnhost.go b/test/images/agnhost/agnhost.go index 98e63482391..12150ed7994 100644 --- a/test/images/agnhost/agnhost.go +++ b/test/images/agnhost/agnhost.go @@ -23,6 +23,7 @@ import ( "k8s.io/klog" "k8s.io/kubernetes/test/images/agnhost/audit-proxy" + "k8s.io/kubernetes/test/images/agnhost/connect" "k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook" "k8s.io/kubernetes/test/images/agnhost/dns" "k8s.io/kubernetes/test/images/agnhost/entrypoint-tester" @@ -45,6 +46,7 @@ import ( func main() { rootCmd := &cobra.Command{Use: "app"} rootCmd.AddCommand(auditproxy.CmdAuditProxy) + rootCmd.AddCommand(connect.CmdConnect) rootCmd.AddCommand(crdconvwebhook.CmdCrdConversionWebhook) rootCmd.AddCommand(dns.CmdDNSSuffix) rootCmd.AddCommand(dns.CmdDNSServerList) diff --git a/test/images/agnhost/connect/BUILD b/test/images/agnhost/connect/BUILD new file mode 100644 index 00000000000..edd63dbcd39 --- /dev/null +++ b/test/images/agnhost/connect/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["connect.go"], + importpath = "k8s.io/kubernetes/test/images/agnhost/connect", + deps = ["//vendor/github.com/spf13/cobra:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/test/images/agnhost/connect/connect.go b/test/images/agnhost/connect/connect.go new file mode 100644 index 00000000000..48db0415e68 --- /dev/null +++ b/test/images/agnhost/connect/connect.go @@ -0,0 +1,83 @@ +/* +Copyright 2019 The Kubernetes 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 connect + +import ( + "fmt" + "net" + "os" + "syscall" + "time" + + "github.com/spf13/cobra" +) + +// CmdConnect is used by agnhost Cobra. +var CmdConnect = &cobra.Command{ + Use: "connect [host:port]", + Short: "Attempts a TCP connection and returns useful errors", + Long: `Tries to open a TCP connection to the given host and port. On error it prints an error message prefixed with a specific fixed string that test cases can check for: + +* UNKNOWN - Generic/unknown (non-network) error (eg, bad arguments) +* TIMEOUT - The connection attempt timed out +* DNS - An error in DNS resolution +* REFUSED - Connection refused +* OTHER - Other networking error (eg, "no route to host")`, + Args: cobra.ExactArgs(1), + Run: main, +} + +var timeout time.Duration + +func init() { + CmdConnect.Flags().DurationVar(&timeout, "timeout", time.Duration(0), "Maximum time before returning an error") +} + +func main(cmd *cobra.Command, args []string) { + dest := args[0] + + // Redundantly parse and resolve the destination so we can return the correct + // errors if there's a problem. + if _, _, err := net.SplitHostPort(dest); err != nil { + fmt.Fprintf(os.Stderr, "UNKNOWN: %v\n", err) + os.Exit(1) + } + if _, err := net.ResolveTCPAddr("tcp", dest); err != nil { + fmt.Fprintf(os.Stderr, "DNS: %v\n", err) + os.Exit(1) + } + + conn, err := net.DialTimeout("tcp", dest, timeout) + if err == nil { + conn.Close() + os.Exit(0) + } + if opErr, ok := err.(*net.OpError); ok { + if opErr.Timeout() { + fmt.Fprintf(os.Stderr, "TIMEOUT\n") + os.Exit(1) + } else if syscallErr, ok := opErr.Err.(*os.SyscallError); ok { + if syscallErr.Err == syscall.ECONNREFUSED { + fmt.Fprintf(os.Stderr, "REFUSED\n") + os.Exit(1) + } + } + } + + fmt.Fprintf(os.Stderr, "OTHER: %v\n", err) + os.Exit(1) +}