diff --git a/cluster/validate-cluster.sh b/cluster/validate-cluster.sh index 20c6f05d7f4..9c3d3c27dd2 100755 --- a/cluster/validate-cluster.sh +++ b/cluster/validate-cluster.sh @@ -33,7 +33,7 @@ detect-master > /dev/null detect-minions > /dev/null MINIONS_FILE=/tmp/minions -"${KUBE_ROOT}/cluster/kubecfg.sh" -template '{{range.Items}}{{.ID}}:{{end}}' list minions > ${MINIONS_FILE} +"${KUBE_ROOT}/cluster/kubecfg.sh" -template $'{{range.Items}}{{.ID}}\n{{end}}' list minions > ${MINIONS_FILE} # On vSphere, use minion IPs as their names if [ "$KUBERNETES_PROVIDER" == "vsphere" ]; then @@ -46,13 +46,19 @@ for (( i=0; i<${#MINION_NAMES[@]}; i++)); do # Grep returns an exit status of 1 when line is not found, so we need the : to always return a 0 exit status count=$(grep -c ${MINION_NAMES[i]} ${MINIONS_FILE}) || : if [[ "$count" == "0" ]]; then - echo "Failed to find ${MINION_NAMES[i]}, cluster is probably broken." + echo "Failed to find ${MINION_NAMES[$i]}, cluster is probably broken." exit 1 fi + NAME=${MINION_NAMES[i]} + if [ "$KUBERNETES_PROVIDER" != "vsphere" ]; then + # Grab fully qualified name + NAME=$(grep "${MINION_NAMES[i]}" ${MINIONS_FILE}) + fi + # Make sure the kubelet is healthy curl_output=$(curl -s --insecure --user "${KUBE_USER}:${KUBE_PASSWORD}" \ - "https://${KUBE_MASTER_IP}/proxy/minion/${MINION_NAMES[$i]}/healthz") + "https://${KUBE_MASTER_IP}/api/v1beta1/proxy/minions/${NAME}/healthz") if [[ "${curl_output}" != "ok" ]]; then echo "Kubelet failed to install on ${MINION_NAMES[$i]}. Your cluster is unlikely to work correctly." echo "Please run ./cluster/kube-down.sh and re-create the cluster. (sorry!)" diff --git a/cmd/controller-manager/controller-manager.go b/cmd/controller-manager/controller-manager.go index 6175f6efca3..8a2d7ca03e2 100644 --- a/cmd/controller-manager/controller-manager.go +++ b/cmd/controller-manager/controller-manager.go @@ -30,7 +30,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/controller" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" - masterPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/master" + "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" @@ -38,7 +38,7 @@ import ( ) var ( - port = flag.Int("port", masterPkg.ControllerManagerPort, "The port that the controller-manager's http service runs on") + port = flag.Int("port", ports.ControllerManagerPort, "The port that the controller-manager's http service runs on") address = util.IP(net.ParseIP("127.0.0.1")) clientConfig = &client.Config{} ) diff --git a/cmd/kubelet/kubelet.go b/cmd/kubelet/kubelet.go index 0b2f1cf4112..63394bb1585 100644 --- a/cmd/kubelet/kubelet.go +++ b/cmd/kubelet/kubelet.go @@ -36,7 +36,7 @@ import ( _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet" kconfig "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/config" - "github.com/GoogleCloudPlatform/kubernetes/pkg/master" + "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/coreos/go-etcd/etcd" @@ -55,7 +55,7 @@ var ( manifestURL = flag.String("manifest_url", "", "URL for accessing the container manifest") enableServer = flag.Bool("enable_server", true, "Enable the info server") address = util.IP(net.ParseIP("127.0.0.1")) - port = flag.Uint("port", master.KubeletPort, "The port for the info server to serve on") + port = flag.Uint("port", ports.KubeletPort, "The port for the info server to serve on") hostnameOverride = flag.String("hostname_override", "", "If non-empty, will use this string as identification instead of the actual hostname.") networkContainerImage = flag.String("network_container_image", kubelet.NetworkContainerImage, "The image that network containers in each pod will use.") dockerEndpoint = flag.String("docker_endpoint", "", "If non-empty, use this for the docker endpoint to communicate with") diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 5172da0a03f..625205f2e78 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -121,7 +121,6 @@ func (g *APIGroup) InstallREST(mux mux, paths ...string) { // InstallSupport registers the APIServer support functions into a mux. func InstallSupport(mux mux) { healthz.InstallHandler(mux) - mux.Handle("/proxy/minion/", http.StripPrefix("/proxy/minion", http.HandlerFunc(handleProxyMinion))) mux.HandleFunc("/version", handleVersion) mux.HandleFunc("/", handleIndex) } diff --git a/pkg/apiserver/minionproxy.go b/pkg/apiserver/minionproxy.go deleted file mode 100644 index 23ac515a367..00000000000 --- a/pkg/apiserver/minionproxy.go +++ /dev/null @@ -1,157 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -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 apiserver - -import ( - "bytes" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/http/httputil" - "net/url" - "strings" - - "code.google.com/p/go.net/html" - "code.google.com/p/go.net/html/atom" - "github.com/golang/glog" -) - -// TODO: replace with proxy handler on minions -func handleProxyMinion(w http.ResponseWriter, req *http.Request) { - path := strings.TrimLeft(req.URL.Path, "/") - rawQuery := req.URL.RawQuery - - // Expect path as: ${minion}/${query_to_minion} - // and query_to_minion can be any query that kubelet will accept. - // - // For example: - // To query stats of a minion or a pod or a container, - // path string can be ${minion}/stats// or - // ${minion}/podInfo?podID= - // - // To query logs on a minion, path string can be: - // ${minion}/logs/ - parts := strings.SplitN(path, "/", 2) - if len(parts) != 2 { - badGatewayError(w, req) - return - } - minionHost := parts[0] - _, port, _ := net.SplitHostPort(minionHost) - if port == "" { - // Couldn't retrieve port information - // TODO: Retrieve port info from a common object - minionHost += ":10250" - } - minionPath := "/" + parts[1] - - minionURL := &url.URL{ - Scheme: "http", - Host: minionHost, - } - newReq, err := http.NewRequest("GET", minionPath+"?"+rawQuery, nil) - if err != nil { - glog.Errorf("Failed to create request: %s", err) - } - - proxy := httputil.NewSingleHostReverseProxy(minionURL) - proxy.Transport = &minionTransport{} - proxy.ServeHTTP(w, newReq) -} - -type minionTransport struct{} - -func (t *minionTransport) RoundTrip(req *http.Request) (*http.Response, error) { - resp, err := http.DefaultTransport.RoundTrip(req) - - if err != nil { - if strings.Contains(err.Error(), "connection refused") { - message := fmt.Sprintf("Failed to connect to minion:%s", req.URL.Host) - resp = &http.Response{ - StatusCode: http.StatusServiceUnavailable, - Body: ioutil.NopCloser(strings.NewReader(message)), - } - return resp, nil - } - return nil, err - } - - if strings.Contains(resp.Header.Get("Content-Type"), "text/plain") { - // Do nothing, simply pass through - return resp, err - } - - resp, err = t.ProcessResponse(req, resp) - return resp, err -} - -func (t *minionTransport) ProcessResponse(req *http.Request, resp *http.Response) (*http.Response, error) { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - // copying the response body did not work - return nil, err - } - - bodyNode := &html.Node{ - Type: html.ElementNode, - Data: "body", - DataAtom: atom.Body, - } - nodes, err := html.ParseFragment(bytes.NewBuffer(body), bodyNode) - if err != nil { - glog.Errorf("Failed to found node: %v", err) - return resp, err - } - - // Define the method to traverse the doc tree and update href node to - // point to correct minion - var updateHRef func(*html.Node) - updateHRef = func(n *html.Node) { - if n.Type == html.ElementNode && n.Data == "a" { - for i, attr := range n.Attr { - if attr.Key == "href" { - Url := &url.URL{ - Path: "/proxy/minion/" + req.URL.Host + req.URL.Path + attr.Val, - } - n.Attr[i].Val = Url.String() - break - } - } - } - for c := n.FirstChild; c != nil; c = c.NextSibling { - updateHRef(c) - } - } - - newContent := &bytes.Buffer{} - for _, n := range nodes { - updateHRef(n) - err = html.Render(newContent, n) - if err != nil { - glog.Errorf("Failed to render: %v", err) - } - } - - resp.Body = ioutil.NopCloser(newContent) - // Update header node with new content-length - // TODO: Remove any hash/signature headers here? - resp.Header.Del("Content-Length") - resp.ContentLength = int64(newContent.Len()) - - return resp, err -} diff --git a/pkg/apiserver/minionproxy_test.go b/pkg/apiserver/minionproxy_test.go deleted file mode 100644 index d5b7eb10e73..00000000000 --- a/pkg/apiserver/minionproxy_test.go +++ /dev/null @@ -1,144 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -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 apiserver - -import ( - "bufio" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" -) - -func TestMinionTransport(t *testing.T) { - content := string(`
kubelet.loggoogle.log
`) - transport := &minionTransport{} - - // Test /logs/ - request := &http.Request{ - Method: "GET", - URL: &url.URL{ - Scheme: "http", - Host: "minion1:10250", - Path: "/logs/", - }, - } - response := &http.Response{ - Status: "200 OK", - StatusCode: http.StatusOK, - Body: ioutil.NopCloser(strings.NewReader(content)), - Close: true, - } - updated_resp, _ := transport.ProcessResponse(request, response) - body, _ := ioutil.ReadAll(updated_resp.Body) - expected := string(`
kubelet.loggoogle.log
`) - if !strings.Contains(string(body), expected) { - t.Errorf("Received wrong content: %s", string(body)) - } - - // Test subdir under /logs/ - request = &http.Request{ - Method: "GET", - URL: &url.URL{ - Scheme: "http", - Host: "minion1:8080", - Path: "/whatever/apt/", - }, - } - response = &http.Response{ - Status: "200 OK", - StatusCode: http.StatusOK, - Body: ioutil.NopCloser(strings.NewReader(content)), - Close: true, - } - updated_resp, _ = transport.ProcessResponse(request, response) - body, _ = ioutil.ReadAll(updated_resp.Body) - expected = string(`
kubelet.loggoogle.log
`) - if !strings.Contains(string(body), expected) { - t.Errorf("Received wrong content: %s", string(body)) - } -} - -func TestMinionProxy(t *testing.T) { - proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Write([]byte(req.URL.Path)) - })) - server := httptest.NewServer(http.HandlerFunc(handleProxyMinion)) - //client := http.Client{} - proxy, _ := url.Parse(proxyServer.URL) - - testCases := map[string]string{ - fmt.Sprintf("/%s/", proxy.Host): "/", - fmt.Sprintf("/%s/test", proxy.Host): "/test", - } - - for value, expected := range testCases { - resp, err := http.Get(fmt.Sprintf("%s%s", server.URL, value)) - if err != nil { - t.Errorf("unexpected error for %s: %v", value, err) - continue - } - if resp.StatusCode != http.StatusOK { - t.Errorf("expected successful request for %s: %#v", value, resp) - continue - } - defer resp.Body.Close() - actual, _ := bufio.NewReader(resp.Body).ReadString('\n') - if actual != expected { - t.Errorf("expected %s to become %s, got %s", value, expected, actual) - } - } - - failureCases := map[string]string{ - "": "", - fmt.Sprintf("/%s", proxy.Host): "/", - } - - for value := range failureCases { - resp, err := http.Get(fmt.Sprintf("%s%s", server.URL, value)) - if err != nil { - t.Errorf("unexpected error for %s: %v", value, err) - continue - } - if resp.StatusCode != http.StatusBadGateway { - t.Errorf("expected bad gateway response for %s: %#v", value, resp) - } - } -} - -func TestApiServerMinionProxy(t *testing.T) { - proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Write([]byte(req.URL.Path)) - })) - server := httptest.NewServer(Handle(nil, nil, "/prefix", selfLinker)) - proxy, _ := url.Parse(proxyServer.URL) - resp, err := http.Get(fmt.Sprintf("%s/proxy/minion/%s%s", server.URL, proxy.Host, "/test")) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("expected successful request, got %#v", resp) - } - defer resp.Body.Close() - actual, _ := bufio.NewReader(resp.Body).ReadString('\n') - if actual != "/test" { - t.Errorf("unexpected response body %s", actual) - } -} diff --git a/pkg/master/ports/doc.go b/pkg/master/ports/doc.go new file mode 100644 index 00000000000..1d9f002666f --- /dev/null +++ b/pkg/master/ports/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 ports defines ports used by various pieces of the kubernetes +// infrastructure. +package ports diff --git a/pkg/master/ports.go b/pkg/master/ports/ports.go similarity index 98% rename from pkg/master/ports.go rename to pkg/master/ports/ports.go index 2920ab7c777..8995bef1a81 100644 --- a/pkg/master/ports.go +++ b/pkg/master/ports/ports.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package master +package ports const ( // KubeletPort is the default port for the kubelet status server on each host machine. diff --git a/pkg/registry/minion/rest.go b/pkg/registry/minion/rest.go index 9c6e177a7a5..68f63365af9 100644 --- a/pkg/registry/minion/rest.go +++ b/pkg/registry/minion/rest.go @@ -17,11 +17,15 @@ limitations under the License. package minion import ( + "errors" "fmt" + "net" + "strconv" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -38,8 +42,8 @@ func NewREST(m Registry) *REST { } } -var ErrDoesNotExist = fmt.Errorf("The requested resource does not exist.") -var ErrNotHealty = fmt.Errorf("The requested minion is not healthy.") +var ErrDoesNotExist = errors.New("The requested resource does not exist.") +var ErrNotHealty = errors.New("The requested minion is not healthy.") func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Object, error) { minion, ok := obj.(*api.Minion) @@ -104,3 +108,17 @@ func (rs *REST) Update(ctx api.Context, minion runtime.Object) (<-chan runtime.O func (rs *REST) toApiMinion(name string) *api.Minion { return &api.Minion{TypeMeta: api.TypeMeta{ID: name}} } + +// ResourceLocation returns a URL to which one can send traffic for the specified minion. +func (rs *REST) ResourceLocation(ctx api.Context, id string) (string, error) { + minion, err := rs.registry.GetMinion(ctx, id) + if err != nil { + return "", err + } + host := minion.HostIP + if host == "" { + host = minion.ID + } + // TODO: Minion webservers should be secure! + return "http://" + net.JoinHostPort(host, strconv.Itoa(ports.KubeletPort)), nil +} diff --git a/pkg/registry/service/rest.go b/pkg/registry/service/rest.go index 42f26810d53..dce10514825 100644 --- a/pkg/registry/service/rest.go +++ b/pkg/registry/service/rest.go @@ -245,7 +245,9 @@ func (rs *REST) ResourceLocation(ctx api.Context, id string) (string, error) { if len(e.Endpoints) == 0 { return "", fmt.Errorf("no endpoints available for %v", id) } - return "http://" + e.Endpoints[rand.Intn(len(e.Endpoints))], nil + // We leave off the scheme ('http://') because we have no idea what sort of server + // is listening at this endpoint. + return e.Endpoints[rand.Intn(len(e.Endpoints))], nil } func (rs *REST) deleteExternalLoadBalancer(service *api.Service) error { diff --git a/pkg/registry/service/rest_test.go b/pkg/registry/service/rest_test.go index 5907146b016..fb99ad384c3 100644 --- a/pkg/registry/service/rest_test.go +++ b/pkg/registry/service/rest_test.go @@ -360,7 +360,7 @@ func TestServiceRegistryResourceLocation(t *testing.T) { if err != nil { t.Errorf("Unexpected error: %v", err) } - if e, a := "http://foo:80", location; e != a { + if e, a := "foo:80", location; e != a { t.Errorf("Expected %v, but got %v", e, a) } if e, a := "foo", registry.GottenID; e != a { diff --git a/plugin/cmd/scheduler/scheduler.go b/plugin/cmd/scheduler/scheduler.go index ead41b7e166..1fc0927e09a 100644 --- a/plugin/cmd/scheduler/scheduler.go +++ b/plugin/cmd/scheduler/scheduler.go @@ -25,7 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" - masterPkg "github.com/GoogleCloudPlatform/kubernetes/pkg/master" + "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler" @@ -34,7 +34,7 @@ import ( ) var ( - port = flag.Int("port", masterPkg.SchedulerPort, "The port that the scheduler's http service runs on") + port = flag.Int("port", ports.SchedulerPort, "The port that the scheduler's http service runs on") address = util.IP(net.ParseIP("127.0.0.1")) clientConfig = &client.Config{} )