Merge pull request #2878 from smarterclayton/enable_v1beta3

Enable v1beta3 API via --runtime_config=api/v1beta3 flag
This commit is contained in:
bgrant0607 2015-01-08 11:04:06 -08:00
commit 7bff03fb41
12 changed files with 327 additions and 155 deletions

View File

@ -82,6 +82,7 @@ var (
allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.") allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.")
portalNet util.IPNet // TODO: make this a list portalNet util.IPNet // TODO: make this a list
enableLogsSupport = flag.Bool("enable_logs_support", true, "Enables server endpoint for log collection") enableLogsSupport = flag.Bool("enable_logs_support", true, "Enables server endpoint for log collection")
runtimeConfig util.ConfigurationMap
kubeletConfig = client.KubeletConfig{ kubeletConfig = client.KubeletConfig{
Port: 10250, Port: 10250,
EnableHttps: false, EnableHttps: false,
@ -89,10 +90,13 @@ var (
) )
func init() { func init() {
runtimeConfig = make(util.ConfigurationMap)
flag.Var(&address, "address", "The IP address on to serve on (set to 0.0.0.0 for all interfaces)") flag.Var(&address, "address", "The IP address on to serve on (set to 0.0.0.0 for all interfaces)")
flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated. Mutually exclusive with -etcd_config") flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated. Mutually exclusive with -etcd_config")
flag.Var(&corsAllowedOriginList, "cors_allowed_origins", "List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. If this list is empty CORS will not be enabled.") flag.Var(&corsAllowedOriginList, "cors_allowed_origins", "List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. If this list is empty CORS will not be enabled.")
flag.Var(&portalNet, "portal_net", "A CIDR notation IP range from which to assign portal IPs. This must not overlap with any IP ranges assigned to nodes for pods.") flag.Var(&portalNet, "portal_net", "A CIDR notation IP range from which to assign portal IPs. This must not overlap with any IP ranges assigned to nodes for pods.")
flag.Var(&runtimeConfig, "runtime_config", "A set of key=value pairs that describe runtime configuration that may be passed to the apiserver.")
client.BindKubeletClientConfigFlags(flag.CommandLine, &kubeletConfig) client.BindKubeletClientConfigFlags(flag.CommandLine, &kubeletConfig)
} }
@ -140,6 +144,8 @@ func main() {
glog.Fatalf("Failure to start kubelet client: %v", err) glog.Fatalf("Failure to start kubelet client: %v", err)
} }
_, v1beta3 := runtimeConfig["api/v1beta3"]
// TODO: expose same flags as client.BindClientConfigFlags but for a server // TODO: expose same flags as client.BindClientConfigFlags but for a server
clientConfig := &client.Config{ clientConfig := &client.Config{
Host: net.JoinHostPort(address.String(), strconv.Itoa(int(*port))), Host: net.JoinHostPort(address.String(), strconv.Itoa(int(*port))),
@ -189,6 +195,7 @@ func main() {
Authenticator: authenticator, Authenticator: authenticator,
Authorizer: authorizer, Authorizer: authorizer,
AdmissionControl: admissionController, AdmissionControl: admissionController,
EnableV1Beta3: v1beta3,
} }
m := master.New(config) m := master.New(config)

View File

@ -92,6 +92,7 @@ sudo "${GO_OUT}/kube-apiserver" \
-v=${LOG_LEVEL} \ -v=${LOG_LEVEL} \
--address="${API_HOST}" \ --address="${API_HOST}" \
--port="${API_PORT}" \ --port="${API_PORT}" \
--runtime_config=api/v1beta3 \
--etcd_servers="http://127.0.0.1:4001" \ --etcd_servers="http://127.0.0.1:4001" \
--portal_net="10.0.0.0/24" \ --portal_net="10.0.0.0/24" \
--cors_allowed_origins="${API_CORS_ALLOWED_ORIGINS}" >"${APISERVER_LOG}" 2>&1 & --cors_allowed_origins="${API_CORS_ALLOWED_ORIGINS}" >"${APISERVER_LOG}" 2>&1 &

View File

@ -74,6 +74,7 @@ kube::log::status "Starting kube-apiserver"
--etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \ --etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \
--public_address_override="127.0.0.1" \ --public_address_override="127.0.0.1" \
--kubelet_port=${KUBELET_PORT} \ --kubelet_port=${KUBELET_PORT} \
--runtime_config=api/v1beta3 \
--portal_net="10.0.0.0/24" 1>&2 & --portal_net="10.0.0.0/24" 1>&2 &
APISERVER_PID=$! APISERVER_PID=$!
@ -87,6 +88,7 @@ kube::log::status "Starting CONTROLLER-MANAGER"
CTLRMGR_PID=$! CTLRMGR_PID=$!
kube::util::wait_for_url "http://127.0.0.1:${CTLRMGR_PORT}/healthz" "controller-manager: " kube::util::wait_for_url "http://127.0.0.1:${CTLRMGR_PORT}/healthz" "controller-manager: "
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/api/v1beta1/minions/127.0.0.1" "apiserver(minions): " 0.2 25
kube_cmd=( kube_cmd=(
"${KUBE_OUTPUT_HOSTBIN}/kubectl" "${KUBE_OUTPUT_HOSTBIN}/kubectl"
@ -94,6 +96,7 @@ kube_cmd=(
kube_api_versions=( kube_api_versions=(
v1beta1 v1beta1
v1beta2 v1beta2
v1beta3
) )
for version in "${kube_api_versions[@]}"; do for version in "${kube_api_versions[@]}"; do
kube_flags=( kube_flags=(
@ -102,7 +105,7 @@ for version in "${kube_api_versions[@]}"; do
--api-version="${version}" --api-version="${version}"
) )
kube::log::status "Testing kubectl(pods)" kube::log::status "Testing kubectl(${version}:pods)"
"${kube_cmd[@]}" get pods "${kube_flags[@]}" "${kube_cmd[@]}" get pods "${kube_flags[@]}"
"${kube_cmd[@]}" create -f examples/guestbook/redis-master.json "${kube_flags[@]}" "${kube_cmd[@]}" create -f examples/guestbook/redis-master.json "${kube_flags[@]}"
"${kube_cmd[@]}" get pods "${kube_flags[@]}" "${kube_cmd[@]}" get pods "${kube_flags[@]}"
@ -110,34 +113,36 @@ for version in "${kube_api_versions[@]}"; do
[[ "$("${kube_cmd[@]}" get pod redis-master -o template --output-version=v1beta1 -t '{{ .id }}' "${kube_flags[@]}")" == "redis-master" ]] [[ "$("${kube_cmd[@]}" get pod redis-master -o template --output-version=v1beta1 -t '{{ .id }}' "${kube_flags[@]}")" == "redis-master" ]]
output_pod=$("${kube_cmd[@]}" get pod redis-master -o json --output-version=v1beta1 "${kube_flags[@]}") output_pod=$("${kube_cmd[@]}" get pod redis-master -o json --output-version=v1beta1 "${kube_flags[@]}")
"${kube_cmd[@]}" delete pod redis-master "${kube_flags[@]}" "${kube_cmd[@]}" delete pod redis-master "${kube_flags[@]}"
before="$("${kube_cmd[@]}" get pods -o template -t '{{ len .items }}' "${kube_flags[@]}")" before="$("${kube_cmd[@]}" get pods -o template -t "{{ len .items }}" "${kube_flags[@]}")"
echo $output_pod | "${kube_cmd[@]}" create -f - "${kube_flags[@]}" echo $output_pod | "${kube_cmd[@]}" create -f - "${kube_flags[@]}"
after="$("${kube_cmd[@]}" get pods -o template -t '{{ len .items }}' "${kube_flags[@]}")" after="$("${kube_cmd[@]}" get pods -o template -t "{{ len .items }}" "${kube_flags[@]}")"
[[ "$((${after} - ${before}))" -eq 1 ]] [[ "$((${after} - ${before}))" -eq 1 ]]
"${kube_cmd[@]}" get pods -o yaml "${kube_flags[@]}" | grep -q "id: redis-master" "${kube_cmd[@]}" get pods -o yaml --output-version=v1beta1 "${kube_flags[@]}" | grep -q "id: redis-master"
"${kube_cmd[@]}" describe pod redis-master "${kube_flags[@]}" | grep -q 'Name:.*redis-master' "${kube_cmd[@]}" describe pod redis-master "${kube_flags[@]}" | grep -q 'Name:.*redis-master'
"${kube_cmd[@]}" delete -f examples/guestbook/redis-master.json "${kube_flags[@]}" "${kube_cmd[@]}" delete -f examples/guestbook/redis-master.json "${kube_flags[@]}"
kube::log::status "Testing kubectl(services)" kube::log::status "Testing kubectl(${version}:services)"
"${kube_cmd[@]}" get services "${kube_flags[@]}" "${kube_cmd[@]}" get services "${kube_flags[@]}"
"${kube_cmd[@]}" create -f examples/guestbook/frontend-service.json "${kube_flags[@]}" "${kube_cmd[@]}" create -f examples/guestbook/frontend-service.json "${kube_flags[@]}"
"${kube_cmd[@]}" get services "${kube_flags[@]}" "${kube_cmd[@]}" get services "${kube_flags[@]}"
"${kube_cmd[@]}" delete service frontend "${kube_flags[@]}" "${kube_cmd[@]}" delete service frontend "${kube_flags[@]}"
kube::log::status "Testing kubectl(replicationcontrollers)" kube::log::status "Testing kubectl(${version}:replicationcontrollers)"
"${kube_cmd[@]}" get replicationcontrollers "${kube_flags[@]}" "${kube_cmd[@]}" get replicationcontrollers "${kube_flags[@]}"
"${kube_cmd[@]}" create -f examples/guestbook/frontend-controller.json "${kube_flags[@]}" "${kube_cmd[@]}" create -f examples/guestbook/frontend-controller.json "${kube_flags[@]}"
"${kube_cmd[@]}" get replicationcontrollers "${kube_flags[@]}" "${kube_cmd[@]}" get replicationcontrollers "${kube_flags[@]}"
"${kube_cmd[@]}" describe replicationcontroller frontendController "${kube_flags[@]}" | grep -q 'Replicas:.*3 desired' "${kube_cmd[@]}" describe replicationcontroller frontendController "${kube_flags[@]}" | grep -q 'Replicas:.*3 desired'
"${kube_cmd[@]}" delete rc frontendController "${kube_flags[@]}" "${kube_cmd[@]}" delete rc frontendController "${kube_flags[@]}"
kube::log::status "Testing kubectl(minions)" kube::log::status "Testing kubectl(${version}:nodes)"
"${kube_cmd[@]}" get minions "${kube_flags[@]}"
"${kube_cmd[@]}" get minions 127.0.0.1 "${kube_flags[@]}"
kube::log::status "Testing kubectl(nodes)"
"${kube_cmd[@]}" get nodes "${kube_flags[@]}" "${kube_cmd[@]}" get nodes "${kube_flags[@]}"
"${kube_cmd[@]}" describe nodes 127.0.0.1 "${kube_flags[@]}" "${kube_cmd[@]}" describe nodes 127.0.0.1 "${kube_flags[@]}"
if [[ "${version}" != "v1beta3" ]]; then
kube::log::status "Testing kubectl(${version}:minions)"
"${kube_cmd[@]}" get minions "${kube_flags[@]}"
"${kube_cmd[@]}" get minions 127.0.0.1 "${kube_flags[@]}"
fi
done done
kube::log::status "TEST PASSED" kube::log::status "TEST PASSED"

View File

@ -24,6 +24,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
) )
@ -38,7 +39,7 @@ const OldestVersion = "v1beta1"
// may be assumed to be least feature rich to most feature rich, and clients may // may be assumed to be least feature rich to most feature rich, and clients may
// choose to prefer the latter items in the list over the former items when presented // choose to prefer the latter items in the list over the former items when presented
// with a set of versions to choose. // with a set of versions to choose.
var Versions = []string{"v1beta1", "v1beta2"} var Versions = []string{"v1beta1", "v1beta2", "v1beta3"}
// Codec is the default codec for serializing output that should use // Codec is the default codec for serializing output that should use
// the latest supported version. Use this Codec when writing to // the latest supported version. Use this Codec when writing to
@ -80,6 +81,12 @@ func InterfacesFor(version string) (*meta.VersionInterfaces, error) {
ObjectConvertor: api.Scheme, ObjectConvertor: api.Scheme,
MetadataAccessor: accessor, MetadataAccessor: accessor,
}, nil }, nil
case "v1beta3":
return &meta.VersionInterfaces{
Codec: v1beta3.Codec,
ObjectConvertor: api.Scheme,
MetadataAccessor: accessor,
}, nil
default: default:
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", ")) return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", "))
} }
@ -96,7 +103,7 @@ func init() {
return interfaces, true return interfaces, true
}, },
) )
mapper.Add(api.Scheme, true, Versions...) mapper.Add(api.Scheme, true, "v1beta1", "v1beta2")
// TODO: when v1beta3 is added it will not use mixed case. mapper.Add(api.Scheme, false, "v1beta3")
RESTMapper = mapper RESTMapper = mapper
} }

View File

@ -18,6 +18,7 @@ package api_test
import ( import (
"encoding/json" "encoding/json"
"flag" "flag"
"math/rand" "math/rand"
"reflect" "reflect"
@ -30,6 +31,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -38,10 +40,15 @@ import (
"speter.net/go/exp/math/dec/inf" "speter.net/go/exp/math/dec/inf"
) )
var fuzzIters = flag.Int("fuzz_iters", 40, "How many fuzzing iterations to do.") var fuzzIters = flag.Int("fuzz_iters", 20, "How many fuzzing iterations to do.")
// apiObjectFuzzer can randomly populate api objects. // fuzzerFor can randomly populate api objects that are destined for version.
var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( func fuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
f := fuzz.New().NilChance(.5).NumElements(1, 1)
if src != nil {
f.RandSource(src)
}
f.Funcs(
func(j *runtime.PluginBase, c fuzz.Continue) { func(j *runtime.PluginBase, c fuzz.Continue) {
// Do nothing; this struct has only a Kind field and it must stay blank in memory. // Do nothing; this struct has only a Kind field and it must stay blank in memory.
}, },
@ -149,77 +156,104 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
//q.Amount.SetScale(inf.Scale(-c.Intn(12))) //q.Amount.SetScale(inf.Scale(-c.Intn(12)))
q.Amount.SetUnscaled(c.Int63n(1000)) q.Amount.SetUnscaled(c.Int63n(1000))
}, },
) )
return f
}
func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object {
name := reflect.TypeOf(source).Elem().Name() fuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item)
apiObjectFuzzer.Fuzz(source)
j, err := meta.Accessor(source) j, err := meta.Accessor(item)
if err != nil { if err != nil {
t.Fatalf("Unexpected error %v for %#v", err, source) t.Fatalf("Unexpected error %v for %#v", err, item)
} }
j.SetKind("") j.SetKind("")
j.SetAPIVersion("") j.SetAPIVersion("")
data, err := codec.Encode(source) return item
}
func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) {
name := reflect.TypeOf(item).Elem().Name()
data, err := codec.Encode(item)
if err != nil { if err != nil {
t.Errorf("%v: %v (%#v)", name, err, source) t.Errorf("%v: %v (%#v)", name, err, item)
return return
} }
obj2, err := codec.Decode(data) obj2, err := codec.Decode(data)
if err != nil { if err != nil {
t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), source) t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), item)
return return
} }
if !api.Semantic.DeepEqual(source, obj2) { if !api.Semantic.DeepEqual(item, obj2) {
t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v", name, util.ObjectGoPrintDiff(source, obj2), codec, string(data), source) t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v\nFinal: %#v", name, util.ObjectGoPrintDiff(item, obj2), codec, string(data), item, obj2)
return return
} }
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface().(runtime.Object) obj3 := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object)
err = codec.DecodeInto(data, obj3) err = codec.DecodeInto(data, obj3)
if err != nil { if err != nil {
t.Errorf("2: %v: %v", name, err) t.Errorf("2: %v: %v", name, err)
return return
} }
if !api.Semantic.DeepEqual(source, obj3) { if !api.Semantic.DeepEqual(item, obj3) {
t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(source, obj3), codec) t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(item, obj3), codec)
return return
} }
} }
// roundTripSame verifies the same source object is tested in all API versions.
func roundTripSame(t *testing.T, item runtime.Object) {
seed := rand.Int63()
fuzzInternalObject(t, "", item, seed)
roundTrip(t, v1beta1.Codec, item)
roundTrip(t, v1beta2.Codec, item)
fuzzInternalObject(t, "v1beta3", item, seed)
roundTrip(t, v1beta3.Codec, item)
}
func roundTripAll(t *testing.T, item runtime.Object) {
seed := rand.Int63()
roundTrip(t, v1beta1.Codec, fuzzInternalObject(t, "v1beta1", item, seed))
roundTrip(t, v1beta2.Codec, fuzzInternalObject(t, "v1beta2", item, seed))
roundTrip(t, v1beta3.Codec, fuzzInternalObject(t, "v1beta3", item, seed))
}
// For debugging problems // For debugging problems
func TestSpecificKind(t *testing.T) { func TestSpecificKind(t *testing.T) {
api.Scheme.Log(t) api.Scheme.Log(t)
defer api.Scheme.Log(nil)
kind := "PodList" kind := "PodList"
item, err := api.Scheme.New("", kind) item, err := api.Scheme.New("", kind)
if err != nil { if err != nil {
t.Errorf("Couldn't make a %v? %v", kind, err) t.Errorf("Couldn't make a %v? %v", kind, err)
return return
} }
runTest(t, v1beta1.Codec, item) roundTripSame(t, item)
runTest(t, v1beta2.Codec, item)
api.Scheme.Log(nil)
} }
func TestList(t *testing.T) { func TestList(t *testing.T) {
api.Scheme.Log(t) api.Scheme.Log(t)
defer api.Scheme.Log(nil)
kind := "List" kind := "List"
item, err := api.Scheme.New("", kind) item, err := api.Scheme.New("", kind)
if err != nil { if err != nil {
t.Errorf("Couldn't make a %v? %v", kind, err) t.Errorf("Couldn't make a %v? %v", kind, err)
return return
} }
runTest(t, v1beta1.Codec, item) roundTripSame(t, item)
runTest(t, v1beta2.Codec, item)
api.Scheme.Log(nil)
} }
var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest") var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList")
var nonInternalRoundTrippableTypes = util.NewStringSet("List") var nonInternalRoundTrippableTypes = util.NewStringSet("List")
func TestRoundTripTypes(t *testing.T) { func TestRoundTripTypes(t *testing.T) {
// api.Scheme.Log(t)
// defer api.Scheme.Log(nil)
for kind := range api.Scheme.KnownTypes("") { for kind := range api.Scheme.KnownTypes("") {
if nonRoundTrippableTypes.Has(kind) { if nonRoundTrippableTypes.Has(kind) {
continue continue
@ -233,10 +267,9 @@ func TestRoundTripTypes(t *testing.T) {
if _, err := meta.Accessor(item); err != nil { if _, err := meta.Accessor(item); err != nil {
t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err) t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err)
} }
runTest(t, v1beta1.Codec, item) roundTripSame(t, item)
runTest(t, v1beta2.Codec, item)
if !nonInternalRoundTrippableTypes.Has(kind) { if !nonInternalRoundTrippableTypes.Has(kind) {
runTest(t, api.Codec, item) roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63()))
} }
} }
} }
@ -281,7 +314,7 @@ const benchmarkSeed = 100
func BenchmarkEncode(b *testing.B) { func BenchmarkEncode(b *testing.B) {
pod := api.Pod{} pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed)) apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod) apiObjectFuzzer.Fuzz(&pod)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
latest.Codec.Encode(&pod) latest.Codec.Encode(&pod)
@ -291,7 +324,7 @@ func BenchmarkEncode(b *testing.B) {
// BenchmarkEncodeJSON provides a baseline for regular JSON encode performance // BenchmarkEncodeJSON provides a baseline for regular JSON encode performance
func BenchmarkEncodeJSON(b *testing.B) { func BenchmarkEncodeJSON(b *testing.B) {
pod := api.Pod{} pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed)) apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod) apiObjectFuzzer.Fuzz(&pod)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
json.Marshal(&pod) json.Marshal(&pod)
@ -300,7 +333,7 @@ func BenchmarkEncodeJSON(b *testing.B) {
func BenchmarkDecode(b *testing.B) { func BenchmarkDecode(b *testing.B) {
pod := api.Pod{} pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed)) apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod) apiObjectFuzzer.Fuzz(&pod)
data, _ := latest.Codec.Encode(&pod) data, _ := latest.Codec.Encode(&pod)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -310,7 +343,7 @@ func BenchmarkDecode(b *testing.B) {
func BenchmarkDecodeInto(b *testing.B) { func BenchmarkDecodeInto(b *testing.B) {
pod := api.Pod{} pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed)) apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod) apiObjectFuzzer.Fuzz(&pod)
data, _ := latest.Codec.Encode(&pod) data, _ := latest.Codec.Encode(&pod)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -322,7 +355,7 @@ func BenchmarkDecodeInto(b *testing.B) {
// BenchmarkDecodeJSON provides a baseline for regular JSON decode performance // BenchmarkDecodeJSON provides a baseline for regular JSON decode performance
func BenchmarkDecodeJSON(b *testing.B) { func BenchmarkDecodeJSON(b *testing.B) {
pod := api.Pod{} pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed)) apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod) apiObjectFuzzer.Fuzz(&pod)
data, _ := latest.Codec.Encode(&pod) data, _ := latest.Codec.Encode(&pod)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {

View File

@ -103,6 +103,7 @@ type Request struct {
// structural elements of the request that are part of the Kubernetes API conventions // structural elements of the request that are part of the Kubernetes API conventions
namespace string namespace string
namespaceSet bool
resource string resource string
resourceName string resourceName string
selector labels.Selector selector labels.Selector
@ -190,10 +191,11 @@ func (r *Request) Namespace(namespace string) *Request {
if r.err != nil { if r.err != nil {
return r return r
} }
if len(r.namespace) != 0 { if r.namespaceSet {
r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace) r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace)
return r return r
} }
r.namespaceSet = true
r.namespace = namespace r.namespace = namespace
return r return r
} }
@ -330,7 +332,7 @@ func (r *Request) Poller(poller PollFunc) *Request {
func (r *Request) finalURL() string { func (r *Request) finalURL() string {
p := r.path p := r.path
if !r.namespaceInQuery { if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 {
p = path.Join(p, "ns", r.namespace) p = path.Join(p, "ns", r.namespace)
} }
if len(r.resource) != 0 { if len(r.resource) != 0 {
@ -353,7 +355,7 @@ func (r *Request) finalURL() string {
query.Add(key, value) query.Add(key, value)
} }
if r.namespaceInQuery && len(r.namespace) > 0 { if r.namespaceSet && r.namespaceInQuery && len(r.namespace) > 0 {
query.Add("namespace", r.namespace) query.Add("namespace", r.namespace)
} }

View File

@ -86,6 +86,11 @@ func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
if s := r.finalURL(); s != "/foo/" { if s := r.finalURL(); s != "/foo/" {
t.Errorf("trailing slash should be preserved: %s", s) t.Errorf("trailing slash should be preserved: %s", s)
} }
r = (&Request{baseURL: &url.URL{}}).AbsPath("/foo/")
if s := r.finalURL(); s != "/foo/" {
t.Errorf("trailing slash should be preserved: %s", s)
}
} }
func TestRequestAbsPathJoins(t *testing.T) { func TestRequestAbsPathJoins(t *testing.T) {

View File

@ -27,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
@ -40,7 +41,8 @@ func TestSetsCodec(t *testing.T) {
"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec}, "v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
"": {false, "/api/v1beta1/", v1beta1.Codec}, "": {false, "/api/v1beta1/", v1beta1.Codec},
"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec}, "v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
"v1beta3": {true, "", nil}, "v1beta3": {false, "/api/v1beta3/", v1beta3.Codec},
"v1beta4": {true, "", nil},
} }
for version, expected := range testCases { for version, expected := range testCases {
client, err := New(&Config{Host: "127.0.0.1", Version: version}) client, err := New(&Config{Host: "127.0.0.1", Version: version})

View File

@ -457,6 +457,10 @@ func (c *Converter) defaultConvert(sv, dv reflect.Value, scope *scope) error {
} }
dkv := reflect.New(dt.Elem()).Elem() dkv := reflect.New(dt.Elem()).Elem()
scope.setKeys(sk.Interface(), dk.Interface()) scope.setKeys(sk.Interface(), dk.Interface())
// TODO: sv.MapIndex(sk) may return a value with CanAddr() == false,
// because a map[string]struct{} does not allow a pointer reference.
// Calling a custom conversion function defined for the map value
// will panic. Example is PodInfo map[string]ContainerStatus.
if err := c.convert(sv.MapIndex(sk), dkv, scope); err != nil { if err := c.convert(sv.MapIndex(sk), dkv, scope); err != nil {
return err return err
} }

View File

@ -21,9 +21,13 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
"strings"
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
@ -50,3 +54,31 @@ func TestGetUnknownSchemaObject(t *testing.T) {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
} }
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestGetSchemaObject(t *testing.T) {
f, tf, _ := NewTestFactory()
f.Mapper = latest.RESTMapper
f.Typer = api.Scheme
codec := latest.Codec
tf.Printer = &testPrinter{}
tf.Client = &client.FakeRESTClient{
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})},
}
buf := bytes.NewBuffer([]byte{})
cmd := f.NewCmdGet(buf)
cmd.Flags().String("api-version", "v1beta3", "")
cmd.Flags().String("namespace", "test", "")
cmd.Run(cmd, []string{"replicationcontrollers", "foo"})
expected := &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ReplicationControllerSpec{Template: &api.PodTemplateSpec{}}}
actual := tf.Printer.(*testPrinter).Obj
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected object: %s", util.ObjectGoPrintDiff(expected, actual))
}
if !strings.Contains(buf.String(), "\"foo\"") {
t.Errorf("unexpected output: %s", buf.String())
}
}

View File

@ -33,6 +33,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
@ -72,6 +73,7 @@ type Config struct {
EnableLogsSupport bool EnableLogsSupport bool
EnableUISupport bool EnableUISupport bool
EnableSwaggerSupport bool EnableSwaggerSupport bool
EnableV1Beta3 bool
APIPrefix string APIPrefix string
CorsAllowedOriginList util.StringList CorsAllowedOriginList util.StringList
Authenticator authenticator.Request Authenticator authenticator.Request
@ -122,6 +124,7 @@ type Master struct {
authorizer authorizer.Authorizer authorizer authorizer.Authorizer
admissionControl admission.Interface admissionControl admission.Interface
masterCount int masterCount int
v1beta3 bool
readOnlyServer string readOnlyServer string
readWriteServer string readWriteServer string
@ -252,6 +255,7 @@ func New(c *Config) *Master {
authenticator: c.Authenticator, authenticator: c.Authenticator,
authorizer: c.Authorizer, authorizer: c.Authorizer,
admissionControl: c.AdmissionControl, admissionControl: c.AdmissionControl,
v1beta3: c.EnableV1Beta3,
masterCount: c.MasterCount, masterCount: c.MasterCount,
readOnlyServer: net.JoinHostPort(c.PublicAddress, strconv.Itoa(int(c.ReadOnlyPort))), readOnlyServer: net.JoinHostPort(c.PublicAddress, strconv.Itoa(int(c.ReadOnlyPort))),
@ -353,11 +357,16 @@ func (m *Master) init(c *Config) {
"bindings": binding.NewREST(m.bindingRegistry), "bindings": binding.NewREST(m.bindingRegistry),
} }
versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
apiserver.NewAPIGroupVersion(m.API_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1") apiserver.NewAPIGroupVersion(m.API_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1")
apiserver.NewAPIGroupVersion(m.API_v1beta2()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta2") apiserver.NewAPIGroupVersion(m.API_v1beta2()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta2")
if c.EnableV1Beta3 {
apiserver.NewAPIGroupVersion(m.API_v1beta3()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta3")
versionHandler = apiserver.APIVersionHandler("v1beta1", "v1beta2", "v1beta3")
}
// TODO: InstallREST should register each version automatically // TODO: InstallREST should register each version automatically
versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
m.rootWebService.Route(m.rootWebService.GET(c.APIPrefix).To(versionHandler)) m.rootWebService.Route(m.rootWebService.GET(c.APIPrefix).To(versionHandler))
apiserver.InstallSupport(m.handlerContainer, m.rootWebService) apiserver.InstallSupport(m.handlerContainer, m.rootWebService)
@ -482,3 +491,15 @@ func (m *Master) API_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec,
} }
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl
} }
// API_v1beta3 returns the resources and codec for API version v1beta3.
func (m *Master) API_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface) {
storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage {
if k == "minions" {
continue
}
storage[strings.ToLower(k)] = v
}
return storage, v1beta3.Codec, "/api/v1beta3", latest.SelfLinker, m.admissionControl
}

View File

@ -0,0 +1,53 @@
/*
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 util
import (
"fmt"
"sort"
"strings"
)
type ConfigurationMap map[string]string
func (m *ConfigurationMap) String() string {
pairs := []string{}
for k, v := range *m {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(pairs)
return strings.Join(pairs, ",")
}
func (m *ConfigurationMap) Set(value string) error {
for _, s := range strings.Split(value, ",") {
if len(s) == 0 {
continue
}
arr := strings.SplitN(s, "=", 2)
if len(arr) == 2 {
(*m)[arr[0]] = arr[1]
} else {
(*m)[arr[0]] = ""
}
}
return nil
}
func (*ConfigurationMap) Type() string {
return "mapStringString"
}