Changed HTTPGetAction to allow user-defined schemes

pull/6/head
Steve Kuznetsov 2015-06-25 13:53:41 -04:00
parent d581d1f6c0
commit 3008ff6150
23 changed files with 160 additions and 95 deletions

View File

@ -12722,6 +12722,10 @@
"host": {
"type": "string",
"description": "hostname to connect to; defaults to pod IP"
},
"scheme": {
"type": "string",
"description": "scheme to connect with, must be HTTP or HTTPS, defaults to HTTP"
}
}
},

View File

@ -12724,6 +12724,10 @@
"host": {
"type": "string",
"description": "hostname to connect to; defaults to pod IP"
},
"scheme": {
"type": "string",
"description": "scheme to connect with, must be HTTP or HTTPS, defaults to HTTP"
}
}
},

View File

@ -88,7 +88,7 @@ func runApiServer(etcdClient tools.EtcdClient, addr net.IP, port int, masterServ
EtcdHelper: helper,
KubeletClient: &client.HTTPKubeletClient{
Client: http.DefaultClient,
Port: 10250,
Config: &client.KubeletConfig{Port: 10250},
},
EnableCoreControllers: true,
EnableLogsSupport: false,

View File

@ -1,2 +1,3 @@
config/
secret.json
*.log

View File

@ -505,6 +505,7 @@ func deepCopy_api_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *convers
return err
}
out.Host = in.Host
out.Scheme = in.Scheme
return nil
}

View File

@ -254,6 +254,7 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
func(http *api.HTTPGetAction, c fuzz.Continue) {
c.FuzzNoCustom(http) // fuzz self without calling this function again
http.Path = "/" + http.Path // can't be blank
http.Scheme = "x" + http.Scheme // can't be blank
},
func(ss *api.ServiceSpec, c fuzz.Continue) {
c.FuzzNoCustom(ss) // fuzz self without calling this function again

View File

@ -610,8 +610,20 @@ type HTTPGetAction struct {
Port util.IntOrString `json:"port,omitempty"`
// Optional: Host name to connect to, defaults to the pod IP.
Host string `json:"host,omitempty"`
// Optional: Scheme to use for connecting to the host, defaults to HTTP.
Scheme URIScheme `json:"scheme,omitempty"`
}
// URIScheme identifies the scheme used for connection to a host for Get actions
type URIScheme string
const (
// URISchemeHTTP means that the scheme used will be http://
URISchemeHTTP URIScheme = "HTTP"
// URISchemeHTTPS means that the scheme used will be https://
URISchemeHTTPS URIScheme = "HTTPS"
)
// TCPSocketAction describes an action based on opening a socket
type TCPSocketAction struct {
// Required: Port to connect to.

View File

@ -592,6 +592,7 @@ func convert_api_HTTPGetAction_To_v1_HTTPGetAction(in *api.HTTPGetAction, out *H
return err
}
out.Host = in.Host
out.Scheme = URIScheme(in.Scheme)
return nil
}
@ -2903,6 +2904,7 @@ func convert_v1_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *api.H
return err
}
out.Host = in.Host
out.Scheme = api.URIScheme(in.Scheme)
return nil
}

View File

@ -518,6 +518,7 @@ func deepCopy_v1_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *conversi
return err
}
out.Host = in.Host
out.Scheme = in.Scheme
return nil
}

View File

@ -137,6 +137,9 @@ func addDefaultingFuncs() {
if obj.Path == "" {
obj.Path = "/"
}
if obj.Scheme == "" {
obj.Scheme = URISchemeHTTP
}
},
func(obj *NamespaceStatus) {
if obj.Phase == "" {

View File

@ -586,8 +586,20 @@ type HTTPGetAction struct {
Port util.IntOrString `json:"port" description:"number or name of the port to access on the container"`
// Optional: Host name to connect to, defaults to the pod IP.
Host string `json:"host,omitempty" description:"hostname to connect to; defaults to pod IP"`
// Optional: Scheme to use for connecting to the host, defaults to HTTP.
Scheme URIScheme `json:"scheme,omitempty" description:"scheme to connect with, must be HTTP or HTTPS, defaults to HTTP"`
}
// URIScheme identifies the scheme used for connection to a host for Get actions
type URIScheme string
const (
// URISchemeHTTP means that the scheme used will be http://
URISchemeHTTP URIScheme = "HTTP"
// URISchemeHTTPS means that the scheme used will be https://
URISchemeHTTPS URIScheme = "HTTPS"
)
// TCPSocketAction describes an action based on opening a socket
type TCPSocketAction struct {
// Required: Port to connect to.

View File

@ -450,6 +450,7 @@ func convert_api_HTTPGetAction_To_v1beta3_HTTPGetAction(in *api.HTTPGetAction, o
return err
}
out.Host = in.Host
out.Scheme = URIScheme(in.Scheme)
return nil
}
@ -2515,6 +2516,7 @@ func convert_v1beta3_HTTPGetAction_To_api_HTTPGetAction(in *HTTPGetAction, out *
return err
}
out.Host = in.Host
out.Scheme = api.URIScheme(in.Scheme)
return nil
}

View File

@ -522,6 +522,7 @@ func deepCopy_v1beta3_HTTPGetAction(in HTTPGetAction, out *HTTPGetAction, c *con
return err
}
out.Host = in.Host
out.Scheme = in.Scheme
return nil
}

View File

@ -141,6 +141,9 @@ func addDefaultingFuncs() {
if obj.Path == "" {
obj.Path = "/"
}
if obj.Scheme == "" {
obj.Scheme = URISchemeHTTP
}
},
func(obj *NamespaceStatus) {
if obj.Phase == "" {

View File

@ -586,8 +586,20 @@ type HTTPGetAction struct {
Port util.IntOrString `json:"port" description:"number or name of the port to access on the container"`
// Optional: Host name to connect to, defaults to the pod IP.
Host string `json:"host,omitempty" description:"hostname to connect to; defaults to pod IP"`
// Optional: Scheme to use for connecting to the host, defaults to HTTP.
Scheme URIScheme `json:"scheme,omitempty" description:"scheme to connect with, must be HTTP or HTTPS, defaults to HTTP"`
}
// URIScheme identifies the scheme used for connection to a host for Get actions
type URIScheme string
const (
// URISchemeHTTP means that the scheme used will be http://
URISchemeHTTP URIScheme = "HTTP"
// URISchemeHTTPS means that the scheme used will be https://
URISchemeHTTPS URIScheme = "HTTPS"
)
// TCPSocketAction describes an action based on opening a socket
type TCPSocketAction struct {
// Required: Port to connect to.

View File

@ -762,6 +762,10 @@ func validateHTTPGetAction(http *api.HTTPGetAction) errs.ValidationErrorList {
} else if http.Port.Kind == util.IntstrString && len(http.Port.StrVal) == 0 {
allErrors = append(allErrors, errs.NewFieldRequired("port"))
}
supportedSchemes := util.NewStringSet(string(api.URISchemeHTTP), string(api.URISchemeHTTPS))
if !supportedSchemes.Has(string(http.Scheme)) {
allErrors = append(allErrors, errs.NewFieldInvalid("scheme", http.Scheme, fmt.Sprintf("must be one of %v", supportedSchemes.List())))
}
return allErrors
}

View File

@ -712,9 +712,9 @@ func TestValidateProbe(t *testing.T) {
func TestValidateHandler(t *testing.T) {
successCases := []api.Handler{
{Exec: &api.ExecAction{Command: []string{"echo"}}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: util.NewIntOrStringFromInt(1), Host: ""}},
{HTTPGet: &api.HTTPGetAction{Path: "/foo", Port: util.NewIntOrStringFromInt(65535), Host: "host"}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: util.NewIntOrStringFromString("port"), Host: ""}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: util.NewIntOrStringFromInt(1), Host: "", Scheme: "HTTP"}},
{HTTPGet: &api.HTTPGetAction{Path: "/foo", Port: util.NewIntOrStringFromInt(65535), Host: "host", Scheme: "HTTP"}},
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: util.NewIntOrStringFromString("port"), Host: "", Scheme: "HTTP"}},
}
for _, h := range successCases {
if errs := validateHandler(&h); len(errs) != 0 {

View File

@ -46,8 +46,6 @@ type ConnectionInfoGetter interface {
type HTTPKubeletClient struct {
Client *http.Client
Config *KubeletConfig
Port uint
EnableHttps bool
}
func MakeTransport(config *KubeletConfig) (http.RoundTripper, error) {
@ -85,31 +83,29 @@ func NewKubeletClient(config *KubeletConfig) (KubeletClient, error) {
return &HTTPKubeletClient{
Client: c,
Config: config,
Port: config.Port,
EnableHttps: config.EnableHttps,
}, nil
}
func (c *HTTPKubeletClient) GetConnectionInfo(host string) (string, uint, http.RoundTripper, error) {
scheme := "http"
if c.EnableHttps {
if c.Config.EnableHttps {
scheme = "https"
}
return scheme, c.Port, c.Client.Transport, nil
return scheme, c.Config.Port, c.Client.Transport, nil
}
func (c *HTTPKubeletClient) url(host, path, query string) string {
func (c *HTTPKubeletClient) url(host, path, query string) *url.URL {
scheme := "http"
if c.EnableHttps {
if c.Config.EnableHttps {
scheme = "https"
}
return (&url.URL{
return &url.URL{
Scheme: scheme,
Host: net.JoinHostPort(host, strconv.FormatUint(uint64(c.Port), 10)),
Host: net.JoinHostPort(host, strconv.FormatUint(uint64(c.Config.Port), 10)),
Path: path,
RawQuery: query,
}).String()
}
}
func (c *HTTPKubeletClient) HealthCheck(host string) (probe.Result, string, error) {

View File

@ -57,7 +57,7 @@ func TestHTTPKubeletClient(t *testing.T) {
c := &HTTPKubeletClient{
Client: http.DefaultClient,
Port: uint(port),
Config: &KubeletConfig{Port: uint(port)},
}
gotObj, _, err := c.HealthCheck(parts[0])
if err != nil {
@ -91,7 +91,7 @@ func TestHTTPKubeletClientError(t *testing.T) {
c := &HTTPKubeletClient{
Client: http.DefaultClient,
Port: uint(port),
Config: &KubeletConfig{Port: uint(port)},
}
gotObj, _, err := c.HealthCheck(parts[0])
if gotObj != expectObj {

View File

@ -18,7 +18,10 @@ package prober
import (
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -200,13 +203,19 @@ func (pb *prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, con
return pb.exec.Probe(pb.newExecInContainer(pod, container, containerID, p.Exec.Command))
}
if p.HTTPGet != nil {
scheme := strings.ToLower(string(p.HTTPGet.Scheme))
host := p.HTTPGet.Host
if host == "" {
host = status.PodIP
}
port, err := extractPort(p.HTTPGet.Port, container)
if err != nil {
return probe.Unknown, "", err
}
host, port, path := extractGetParams(p.HTTPGet, status, port)
glog.V(4).Infof("HTTP-Probe Host: %v, Port: %v, Path: %v", host, port, path)
return pb.http.Probe(host, port, path, timeout)
path := p.HTTPGet.Path
glog.V(4).Infof("HTTP-Probe Host: %v://%v, Port: %v, Path: %v", scheme, host, port, path)
url := formatURL(scheme, host, port, path)
return pb.http.Probe(url, timeout)
}
if p.TCPSocket != nil {
port, err := extractPort(p.TCPSocket.Port, container)
@ -220,50 +229,45 @@ func (pb *prober) runProbe(p *api.Probe, pod *api.Pod, status api.PodStatus, con
return probe.Unknown, "", nil
}
func extractGetParams(action *api.HTTPGetAction, status api.PodStatus, port int) (string, int, string) {
host := action.Host
if host == "" {
host = status.PodIP
}
return host, port, action.Path
}
func extractPort(param util.IntOrString, container api.Container) (int, error) {
port := -1
var err error
switch param.Kind {
case util.IntstrInt:
port := param.IntVal
if port > 0 && port < 65536 {
return port, nil
}
return port, fmt.Errorf("invalid port number: %v", port)
port = param.IntVal
case util.IntstrString:
port = findPortByName(container, param.StrVal)
if port == -1 {
if port, err = findPortByName(container, param.StrVal); err != nil {
// Last ditch effort - maybe it was an int stored as string?
if port, err = strconv.Atoi(param.StrVal); err != nil {
return port, err
}
}
default:
return port, fmt.Errorf("IntOrString had no kind: %+v", param)
}
if port > 0 && port < 65536 {
return port, nil
}
return port, fmt.Errorf("invalid port number: %v", port)
default:
return port, fmt.Errorf("IntOrString had no kind: %+v", param)
}
}
// findPortByName is a helper function to look up a port in a container by name.
// Returns the HostPort if found, -1 if not found.
func findPortByName(container api.Container, portName string) int {
func findPortByName(container api.Container, portName string) (int, error) {
for _, port := range container.Ports {
if port.Name == portName {
return port.HostPort
return port.HostPort, nil
}
}
return -1
return 0, fmt.Errorf("port %s not found", portName)
}
// formatURL formats a URL from args. For testability.
func formatURL(scheme string, host string, port int, path string) *url.URL {
return &url.URL{
Scheme: scheme,
Host: net.JoinHostPort(host, strconv.Itoa(port)),
Path: path,
}
}
type execInContainer struct {

View File

@ -25,6 +25,25 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"
)
func TestFormatURL(t *testing.T) {
testCases := []struct {
scheme string
host string
port int
path string
result string
}{
{"http", "localhost", 93, "", "http://localhost:93"},
{"https", "localhost", 93, "/path", "https://localhost:93/path"},
}
for _, test := range testCases {
url := formatURL(test.scheme, test.host, test.port, test.path)
if url.String() != test.result {
t.Errorf("Expected %s, got %s", test.result, url.String())
}
}
}
func TestFindPortByName(t *testing.T) {
container := api.Container{
Ports: []api.ContainerPort{
@ -39,9 +58,9 @@ func TestFindPortByName(t *testing.T) {
},
}
want := 8080
got := findPortByName(container, "foo")
if got != want {
t.Errorf("Expected %v, got %v", want, got)
got, err := findPortByName(container, "foo")
if got != want || err != nil {
t.Errorf("Expected %v, got %v, err: %v", want, got, err)
}
}
@ -73,13 +92,23 @@ func TestGetURLParts(t *testing.T) {
},
},
}
p, err := extractPort(test.probe.Port, container)
scheme := test.probe.Scheme
if scheme == "" {
scheme = api.URISchemeHTTP
}
host := test.probe.Host
if host == "" {
host = state.PodIP
}
port, err := extractPort(test.probe.Port, container)
if test.ok && err != nil {
t.Errorf("Unexpected error: %v", err)
}
host, port, path := extractGetParams(test.probe, state, p)
path := test.probe.Path
if !test.ok && err == nil {
t.Errorf("Expected error for %+v, got %s:%d/%s", test, host, port, path)
t.Errorf("Expected error for %+v, got %s%s:%d/%s", test, scheme, host, port, path)
}
if test.ok {
if host != test.host || port != test.port || path != test.path {

View File

@ -17,11 +17,10 @@ limitations under the License.
package http
import (
"crypto/tls"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
@ -30,12 +29,13 @@ import (
)
func New() HTTPProber {
transport := &http.Transport{}
tlsConfig := &tls.Config{InsecureSkipVerify: true}
transport := &http.Transport{TLSClientConfig: tlsConfig}
return httpProber{transport}
}
type HTTPProber interface {
Probe(host string, port int, path string, timeout time.Duration) (probe.Result, string, error)
Probe(url *url.URL, timeout time.Duration) (probe.Result, string, error)
}
type httpProber struct {
@ -43,8 +43,8 @@ type httpProber struct {
}
// Probe returns a ProbeRunner capable of running an http check.
func (pr httpProber) Probe(host string, port int, path string, timeout time.Duration) (probe.Result, string, error) {
return DoHTTPProbe(formatURL(host, port, path), &http.Client{Timeout: timeout, Transport: pr.transport})
func (pr httpProber) Probe(url *url.URL, timeout time.Duration) (probe.Result, string, error) {
return DoHTTPProbe(url, &http.Client{Timeout: timeout, Transport: pr.transport})
}
type HTTPGetInterface interface {
@ -55,8 +55,8 @@ type HTTPGetInterface interface {
// If the HTTP response code is successful (i.e. 400 > code >= 200), it returns Success.
// If the HTTP response code is unsuccessful or HTTP communication fails, it returns Failure.
// This is exported because some other packages may want to do direct HTTP probes.
func DoHTTPProbe(url string, client HTTPGetInterface) (probe.Result, string, error) {
res, err := client.Get(url)
func DoHTTPProbe(url *url.URL, client HTTPGetInterface) (probe.Result, string, error) {
res, err := client.Get(url.String())
if err != nil {
// Convert errors into failures to catch timeouts.
return probe.Failure, err.Error(), nil
@ -68,18 +68,9 @@ func DoHTTPProbe(url string, client HTTPGetInterface) (probe.Result, string, err
}
body := string(b)
if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest {
glog.V(4).Infof("Probe succeeded for %s, Response: %v", url.String(), *res)
return probe.Success, body, nil
}
glog.V(4).Infof("Probe failed for %s, Response: %v", url, *res)
glog.V(4).Infof("Probe failed for %s, Response: %v", url.String(), *res)
return probe.Failure, body, nil
}
// formatURL formats a URL from args. For testability.
func formatURL(host string, port int, path string) string {
u := url.URL{
Scheme: "http",
Host: net.JoinHostPort(host, strconv.Itoa(port)),
Path: path,
}
return u.String()
}

View File

@ -29,24 +29,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
)
func TestFormatURL(t *testing.T) {
testCases := []struct {
host string
port int
path string
result string
}{
{"localhost", 93, "", "http://localhost:93"},
{"localhost", 93, "/path", "http://localhost:93/path"},
}
for _, test := range testCases {
url := formatURL(test.host, test.port, test.path)
if url != test.result {
t.Errorf("Expected %s, got %s", test.result, url)
}
}
}
func TestHTTPProbeChecker(t *testing.T) {
handleReq := func(s int, body string) func(w http.ResponseWriter) {
return func(w http.ResponseWriter) {
@ -74,15 +56,15 @@ func TestHTTPProbeChecker(t *testing.T) {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
host, port, err := net.SplitHostPort(u.Host)
_, port, err := net.SplitHostPort(u.Host)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
p, err := strconv.Atoi(port)
_, err = strconv.Atoi(port)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
health, output, err := prober.Probe(host, p, "", 1*time.Second)
health, output, err := prober.Probe(u, 1*time.Second)
if test.health == probe.Unknown && err == nil {
t.Errorf("Expected error")
}