Add /version to server and check it in client.

Will help detect client/version skew and prevent e2e test from passing
while running a version other than the one you think it's running.
pull/6/head
Daniel Smith 2014-07-25 12:28:20 -07:00
parent 9fc52c8aaa
commit 3b8488028d
9 changed files with 148 additions and 31 deletions

View File

@ -22,6 +22,7 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os" "os"
"reflect"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@ -31,14 +32,14 @@ import (
kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client" kube_client "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubecfg"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog" "github.com/golang/glog"
) )
// AppVersion is the current version of kubecfg.
const AppVersion = "0.1"
var ( var (
versionFlag = flag.Bool("V", false, "Print the version number.") versionFlag = flag.Bool("V", false, "Print the version number.")
serverVersion = flag.Bool("server_version", false, "Print the server's version number.")
preventSkew = flag.Bool("expect_version_match", false, "Fail if server's version doesn't match own version.")
httpServer = flag.String("h", "", "The host to connect to.") httpServer = flag.String("h", "", "The host to connect to.")
config = flag.String("c", "", "Path to the config file.") config = flag.String("c", "", "Path to the config file.")
selector = flag.String("l", "", "Selector (label query) to use for listing") selector = flag.String("l", "", "Selector (label query) to use for listing")
@ -107,7 +108,7 @@ func main() {
defer util.FlushLogs() defer util.FlushLogs()
if *versionFlag { if *versionFlag {
fmt.Println("Version:", AppVersion) fmt.Printf("Version: %#v\n", version.Get())
os.Exit(0) os.Exit(0)
} }
@ -136,6 +137,30 @@ func main() {
} }
} }
client := kube_client.New(masterServer, auth)
if *serverVersion {
got, err := client.ServerVersion()
if err != nil {
fmt.Printf("Couldn't read version from server: %v\n", err)
os.Exit(1)
}
fmt.Printf("Server Version: %#v\n", got)
os.Exit(0)
}
if *preventSkew {
got, err := client.ServerVersion()
if err != nil {
fmt.Printf("Couldn't read version from server: %v\n", err)
os.Exit(1)
}
if c, s := version.Get(), *got; !reflect.DeepEqual(c, s) {
fmt.Printf("Server version (%#v) differs from client version (%#v)!\n", s, c)
os.Exit(1)
}
}
if *proxy { if *proxy {
glog.Info("Starting to serve on localhost:8001") glog.Info("Starting to serve on localhost:8001")
server := kubecfg.NewProxyServer(*www, masterServer, auth) server := kubecfg.NewProxyServer(*www, masterServer, auth)
@ -148,8 +173,6 @@ func main() {
} }
method := flag.Arg(0) method := flag.Arg(0)
client := kube_client.New(masterServer, auth)
matchFound := executeAPIRequest(method, client) || executeControllerRequest(method, client) matchFound := executeAPIRequest(method, client) || executeControllerRequest(method, client)
if matchFound == false { if matchFound == false {
glog.Fatalf("Unknown command %s", method) glog.Fatalf("Unknown command %s", method)

View File

@ -39,7 +39,7 @@ set -e
# Use testing config # Use testing config
export KUBE_CONFIG_FILE="config-test.sh" export KUBE_CONFIG_FILE="config-test.sh"
export KUBE_REPO_ROOT="$(dirname $0)/.." export KUBE_REPO_ROOT="$(dirname $0)/.."
export CLOUDCFG="${KUBE_REPO_ROOT}/cluster/kubecfg.sh" export CLOUDCFG="${KUBE_REPO_ROOT}/cluster/kubecfg.sh -expect_version_match"
# Build a release required by the test provider [if any] # Build a release required by the test provider [if any]
test-build-release test-build-release

View File

@ -106,7 +106,7 @@ APISERVER_PID=$!
wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: " wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: "
KUBE_CMD="${GO_OUT}/kubecfg -h http://127.0.0.1:${API_PORT}" KUBE_CMD="${GO_OUT}/kubecfg -h http://127.0.0.1:${API_PORT} -expect_version_match"
${KUBE_CMD} list pods ${KUBE_CMD} list pods
echo "kubecfg(pods): ok" echo "kubecfg(pods): ok"

View File

@ -39,6 +39,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -151,6 +152,7 @@ func New(storage map[string]RESTStorage, prefix string) *APIServer {
healthz.InstallHandler(s.mux) healthz.InstallHandler(s.mux)
s.mux.HandleFunc("/", s.handleIndex) s.mux.HandleFunc("/", s.handleIndex)
s.mux.HandleFunc("/version", s.handleVersionReq)
// Handle both operations and operations/* with the same handler // Handle both operations and operations/* with the same handler
s.mux.HandleFunc(s.operationPrefix(), s.handleOperationRequest) s.mux.HandleFunc(s.operationPrefix(), s.handleOperationRequest)
@ -182,6 +184,11 @@ func (server *APIServer) handleIndex(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, data) fmt.Fprint(w, data)
} }
// handleVersionReq writes the server's version information.
func (server *APIServer) handleVersionReq(w http.ResponseWriter, req *http.Request) {
server.writeRawJSON(http.StatusOK, version.Get(), w)
}
func (server *APIServer) handleMinionReq(w http.ResponseWriter, req *http.Request) { func (server *APIServer) handleMinionReq(w http.ResponseWriter, req *http.Request) {
minionPrefix := "/proxy/minion/" minionPrefix := "/proxy/minion/"
if !strings.HasPrefix(req.URL.Path, minionPrefix) { if !strings.HasPrefix(req.URL.Path, minionPrefix) {
@ -344,14 +351,27 @@ func (server *APIServer) notFound(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Not Found: %#v", req) fmt.Fprintf(w, "Not Found: %#v", req)
} }
// write writes an API object in wire format.
func (server *APIServer) write(statusCode int, object interface{}, w http.ResponseWriter) { func (server *APIServer) write(statusCode int, object interface{}, w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
output, err := api.Encode(object) output, err := api.Encode(object)
if err != nil { if err != nil {
server.error(err, w) server.error(err, w)
return return
} }
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
w.Write(output)
}
// writeRawJSON writes a non-API object in JSON.
func (server *APIServer) writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
output, err := json.Marshal(object)
if err != nil {
server.error(err, w)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
w.Write(output) w.Write(output)
} }

View File

@ -18,6 +18,7 @@ package client
import ( import (
"crypto/tls" "crypto/tls"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -26,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog" "github.com/golang/glog"
) )
@ -238,3 +240,17 @@ func (c *Client) UpdateService(svc api.Service) (result api.Service, err error)
func (c *Client) DeleteService(name string) error { func (c *Client) DeleteService(name string) error {
return c.Delete().Path("services").Path(name).Do().Error() return c.Delete().Path("services").Path(name).Do().Error()
} }
// ServerVersion retrieves and parses the server's version.
func (c *Client) ServerVersion() (*version.Info, error) {
body, err := c.Get().AbsPath("/version").Do().Raw()
if err != nil {
return nil, err
}
var info version.Info
err = json.Unmarshal(body, &info)
if err != nil {
return nil, fmt.Errorf("Got '%s': %v", string(body), err)
}
return &info, nil
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package client package client
import ( import (
"encoding/json"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -26,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
) )
// TODO: Move this to a common place, it's needed in multiple tests. // TODO: Move this to a common place, it's needed in multiple tests.
@ -456,3 +458,30 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
} }
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil) fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
} }
func TestGetServerVersion(t *testing.T) {
expect := version.Info{
Major: "foo",
Minor: "bar",
GitCommit: "baz",
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
output, err := json.Marshal(expect)
if err != nil {
t.Errorf("unexpected encoding error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(output)
}))
client := New(server.URL, nil)
got, err := client.ServerVersion()
if err != nil {
t.Fatalf("unexpected encoding error: %v", err)
}
if e, a := expect, *got; !reflect.DeepEqual(e, a) {
t.Errorf("expected %v, got %v", e, a)
}
}

View File

@ -29,10 +29,24 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/golang/glog" "github.com/golang/glog"
"gopkg.in/v1/yaml" "gopkg.in/v1/yaml"
) )
func GetServerVersion(client *client.Client) (*version.Info, error) {
body, err := client.Get().AbsPath("/version").Do().Raw()
if err != nil {
return nil, err
}
var info version.Info
err = json.Unmarshal(body, &info)
if err != nil {
return nil, fmt.Errorf("Got '%s': %v", string(body), err)
}
return &info, nil
}
func promptForString(field string, r io.Reader) string { func promptForString(field string, r io.Reader) string {
fmt.Printf("Please enter %s: ", field) fmt.Printf("Please enter %s: ", field)
var result string var result string

View File

@ -317,9 +317,9 @@ func TestMakePorts(t *testing.T) {
{ {
"8080:80,8081:8081,443:444", "8080:80,8081:8081,443:444",
[]api.Port{ []api.Port{
api.Port{HostPort: 8080, ContainerPort: 80}, {HostPort: 8080, ContainerPort: 80},
api.Port{HostPort: 8081, ContainerPort: 8081}, {HostPort: 8081, ContainerPort: 8081},
api.Port{HostPort: 443, ContainerPort: 444}, {HostPort: 443, ContainerPort: 444},
}, },
}, },
} }

View File

@ -16,6 +16,21 @@ limitations under the License.
package version package version
func Get() (major, minor, gitCommit string) { // Info contains versioning information.
return "v1beta", "1", commitFromGit // TODO: Add []string of api versions supported? It's still unclear
// how we'll want to distribute that information.
type Info struct {
Major string `json:"major" yaml:"major"`
Minor string `json:"minor" yaml:"minor"`
GitCommit string `json:"gitCommit" yaml:"gitCommit"`
}
// Get returns the overall codebase version. It's for detecting
// what code a binary was built from.
func Get() Info {
return Info{
Major: "0",
Minor: "1",
GitCommit: commitFromGit,
}
} }