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.")
portalNet util.IPNet // TODO: make this a list
enableLogsSupport = flag.Bool("enable_logs_support", true, "Enables server endpoint for log collection")
runtimeConfig util.ConfigurationMap
kubeletConfig = client.KubeletConfig{
Port: 10250,
EnableHttps: false,
@ -89,10 +90,13 @@ var (
)
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(&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(&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)
}
@ -140,6 +144,8 @@ func main() {
glog.Fatalf("Failure to start kubelet client: %v", err)
}
_, v1beta3 := runtimeConfig["api/v1beta3"]
// TODO: expose same flags as client.BindClientConfigFlags but for a server
clientConfig := &client.Config{
Host: net.JoinHostPort(address.String(), strconv.Itoa(int(*port))),
@ -189,6 +195,7 @@ func main() {
Authenticator: authenticator,
Authorizer: authorizer,
AdmissionControl: admissionController,
EnableV1Beta3: v1beta3,
}
m := master.New(config)

View File

@ -92,6 +92,7 @@ sudo "${GO_OUT}/kube-apiserver" \
-v=${LOG_LEVEL} \
--address="${API_HOST}" \
--port="${API_PORT}" \
--runtime_config=api/v1beta3 \
--etcd_servers="http://127.0.0.1:4001" \
--portal_net="10.0.0.0/24" \
--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}" \
--public_address_override="127.0.0.1" \
--kubelet_port=${KUBELET_PORT} \
--runtime_config=api/v1beta3 \
--portal_net="10.0.0.0/24" 1>&2 &
APISERVER_PID=$!
@ -87,6 +88,7 @@ kube::log::status "Starting CONTROLLER-MANAGER"
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:${API_PORT}/api/v1beta1/minions/127.0.0.1" "apiserver(minions): " 0.2 25
kube_cmd=(
"${KUBE_OUTPUT_HOSTBIN}/kubectl"
@ -94,6 +96,7 @@ kube_cmd=(
kube_api_versions=(
v1beta1
v1beta2
v1beta3
)
for version in "${kube_api_versions[@]}"; do
kube_flags=(
@ -102,7 +105,7 @@ for version in "${kube_api_versions[@]}"; do
--api-version="${version}"
)
kube::log::status "Testing kubectl(pods)"
kube::log::status "Testing kubectl(${version}:pods)"
"${kube_cmd[@]}" get pods "${kube_flags[@]}"
"${kube_cmd[@]}" create -f examples/guestbook/redis-master.json "${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" ]]
output_pod=$("${kube_cmd[@]}" get pod redis-master -o json --output-version=v1beta1 "${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[@]}"
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 ]]
"${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[@]}" 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[@]}" create -f examples/guestbook/frontend-service.json "${kube_flags[@]}"
"${kube_cmd[@]}" get services "${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[@]}" create -f examples/guestbook/frontend-controller.json "${kube_flags[@]}"
"${kube_cmd[@]}" get replicationcontrollers "${kube_flags[@]}"
"${kube_cmd[@]}" describe replicationcontroller frontendController "${kube_flags[@]}" | grep -q 'Replicas:.*3 desired'
"${kube_cmd[@]}" delete rc frontendController "${kube_flags[@]}"
kube::log::status "Testing kubectl(minions)"
"${kube_cmd[@]}" get minions "${kube_flags[@]}"
"${kube_cmd[@]}" get minions 127.0.0.1 "${kube_flags[@]}"
kube::log::status "Testing kubectl(nodes)"
kube::log::status "Testing kubectl(${version}:nodes)"
"${kube_cmd[@]}" get nodes "${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
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/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"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
// choose to prefer the latter items in the list over the former items when presented
// 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
// the latest supported version. Use this Codec when writing to
@ -80,6 +81,12 @@ func InterfacesFor(version string) (*meta.VersionInterfaces, error) {
ObjectConvertor: api.Scheme,
MetadataAccessor: accessor,
}, nil
case "v1beta3":
return &meta.VersionInterfaces{
Codec: v1beta3.Codec,
ObjectConvertor: api.Scheme,
MetadataAccessor: accessor,
}, nil
default:
return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", "))
}
@ -96,7 +103,7 @@ func init() {
return interfaces, true
},
)
mapper.Add(api.Scheme, true, Versions...)
// TODO: when v1beta3 is added it will not use mixed case.
mapper.Add(api.Scheme, true, "v1beta1", "v1beta2")
mapper.Add(api.Scheme, false, "v1beta3")
RESTMapper = mapper
}

View File

@ -18,6 +18,7 @@ package api_test
import (
"encoding/json"
"flag"
"math/rand"
"reflect"
@ -30,6 +31,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"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/util"
@ -38,188 +40,220 @@ import (
"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.
var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
func(j *runtime.PluginBase, c fuzz.Continue) {
// Do nothing; this struct has only a Kind field and it must stay blank in memory.
},
func(j *runtime.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
},
func(j *api.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
},
func(j *api.ObjectMeta, c fuzz.Continue) {
j.Name = c.RandString()
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.SelfLink = c.RandString()
// fuzzerFor can randomly populate api objects that are destined for version.
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) {
// Do nothing; this struct has only a Kind field and it must stay blank in memory.
},
func(j *runtime.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
},
func(j *api.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
},
func(j *api.ObjectMeta, c fuzz.Continue) {
j.Name = c.RandString()
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.SelfLink = c.RandString()
var sec, nsec int64
c.Fuzz(&sec)
c.Fuzz(&nsec)
j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy()
},
func(j *api.ListMeta, c fuzz.Continue) {
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.SelfLink = c.RandString()
},
func(j *api.PodPhase, c fuzz.Continue) {
statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
*j = statuses[c.Rand.Intn(len(statuses))]
},
func(j *api.ReplicationControllerSpec, c fuzz.Continue) {
// TemplateRef must be nil for round trip
c.Fuzz(&j.Template)
if j.Template == nil {
// TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2
// conversion compare converted object to nil via DeepEqual
j.Template = &api.PodTemplateSpec{}
}
j.Template.ObjectMeta = api.ObjectMeta{Labels: j.Template.ObjectMeta.Labels}
j.Template.Spec.NodeSelector = nil
c.Fuzz(&j.Selector)
j.Replicas = int(c.RandUint64())
},
func(j *api.ReplicationControllerStatus, c fuzz.Continue) {
// only replicas round trips
j.Replicas = int(c.RandUint64())
},
func(j *api.List, c fuzz.Continue) {
c.Fuzz(&j.ListMeta)
c.Fuzz(&j.Items)
if j.Items == nil {
j.Items = []runtime.Object{}
}
},
func(j *runtime.Object, c fuzz.Continue) {
if c.RandBool() {
*j = &runtime.Unknown{
TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"},
RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`),
var sec, nsec int64
c.Fuzz(&sec)
c.Fuzz(&nsec)
j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy()
},
func(j *api.ListMeta, c fuzz.Continue) {
j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10)
j.SelfLink = c.RandString()
},
func(j *api.PodPhase, c fuzz.Continue) {
statuses := []api.PodPhase{api.PodPending, api.PodRunning, api.PodFailed, api.PodUnknown}
*j = statuses[c.Rand.Intn(len(statuses))]
},
func(j *api.ReplicationControllerSpec, c fuzz.Continue) {
// TemplateRef must be nil for round trip
c.Fuzz(&j.Template)
if j.Template == nil {
// TODO: v1beta1/2 can't round trip a nil template correctly, fix by having v1beta1/2
// conversion compare converted object to nil via DeepEqual
j.Template = &api.PodTemplateSpec{}
}
} else {
types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}}
t := types[c.Rand.Intn(len(types))]
c.Fuzz(t)
*j = t
}
},
func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong.
if c.RandBool() {
intstr.Kind = util.IntstrInt
intstr.IntVal = int(c.RandUint64())
intstr.StrVal = ""
} else {
intstr.Kind = util.IntstrString
intstr.IntVal = 0
intstr.StrVal = c.RandString()
}
},
func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pb[docker.Port(c.RandString())] = []docker.PortBinding{
{c.RandString(), c.RandString()},
{c.RandString(), c.RandString()},
}
},
func(pm map[string]docker.PortMapping, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pm[c.RandString()] = docker.PortMapping{
c.RandString(): c.RandString(),
}
},
j.Template.ObjectMeta = api.ObjectMeta{Labels: j.Template.ObjectMeta.Labels}
j.Template.Spec.NodeSelector = nil
c.Fuzz(&j.Selector)
j.Replicas = int(c.RandUint64())
},
func(j *api.ReplicationControllerStatus, c fuzz.Continue) {
// only replicas round trips
j.Replicas = int(c.RandUint64())
},
func(j *api.List, c fuzz.Continue) {
c.Fuzz(&j.ListMeta)
c.Fuzz(&j.Items)
if j.Items == nil {
j.Items = []runtime.Object{}
}
},
func(j *runtime.Object, c fuzz.Continue) {
if c.RandBool() {
*j = &runtime.Unknown{
TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"},
RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`),
}
} else {
types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}}
t := types[c.Rand.Intn(len(types))]
c.Fuzz(t)
*j = t
}
},
func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong.
if c.RandBool() {
intstr.Kind = util.IntstrInt
intstr.IntVal = int(c.RandUint64())
intstr.StrVal = ""
} else {
intstr.Kind = util.IntstrString
intstr.IntVal = 0
intstr.StrVal = c.RandString()
}
},
func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pb[docker.Port(c.RandString())] = []docker.PortBinding{
{c.RandString(), c.RandString()},
{c.RandString(), c.RandString()},
}
},
func(pm map[string]docker.PortMapping, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pm[c.RandString()] = docker.PortMapping{
c.RandString(): c.RandString(),
}
},
func(q *resource.Quantity, c fuzz.Continue) {
// Real Quantity fuzz testing is done elsewhere;
// this limited subset of functionality survives
// round-tripping to v1beta1/2.
q.Amount = &inf.Dec{}
q.Format = resource.DecimalExponent
//q.Amount.SetScale(inf.Scale(-c.Intn(12)))
q.Amount.SetUnscaled(c.Int63n(1000))
},
)
func(q *resource.Quantity, c fuzz.Continue) {
// Real Quantity fuzz testing is done elsewhere;
// this limited subset of functionality survives
// round-tripping to v1beta1/2.
q.Amount = &inf.Dec{}
q.Format = resource.DecimalExponent
//q.Amount.SetScale(inf.Scale(-c.Intn(12)))
q.Amount.SetUnscaled(c.Int63n(1000))
},
)
return f
}
func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
name := reflect.TypeOf(source).Elem().Name()
apiObjectFuzzer.Fuzz(source)
j, err := meta.Accessor(source)
func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object {
fuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item)
j, err := meta.Accessor(item)
if err != nil {
t.Fatalf("Unexpected error %v for %#v", err, source)
t.Fatalf("Unexpected error %v for %#v", err, item)
}
j.SetKind("")
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 {
t.Errorf("%v: %v (%#v)", name, err, source)
t.Errorf("%v: %v (%#v)", name, err, item)
return
}
obj2, err := codec.Decode(data)
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
}
if !api.Semantic.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v\nCodec: %v\nData: %s\nSource: %#v", name, util.ObjectGoPrintDiff(source, obj2), codec, string(data), source)
if !api.Semantic.DeepEqual(item, obj2) {
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
}
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)
if err != nil {
t.Errorf("2: %v: %v", name, err)
return
}
if !api.Semantic.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(source, obj3), codec)
if !api.Semantic.DeepEqual(item, obj3) {
t.Errorf("3: %v: diff: %v\nCodec: %v", name, util.ObjectDiff(item, obj3), codec)
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
func TestSpecificKind(t *testing.T) {
api.Scheme.Log(t)
defer api.Scheme.Log(nil)
kind := "PodList"
item, err := api.Scheme.New("", kind)
if err != nil {
t.Errorf("Couldn't make a %v? %v", kind, err)
return
}
runTest(t, v1beta1.Codec, item)
runTest(t, v1beta2.Codec, item)
api.Scheme.Log(nil)
roundTripSame(t, item)
}
func TestList(t *testing.T) {
api.Scheme.Log(t)
defer api.Scheme.Log(nil)
kind := "List"
item, err := api.Scheme.New("", kind)
if err != nil {
t.Errorf("Couldn't make a %v? %v", kind, err)
return
}
runTest(t, v1beta1.Codec, item)
runTest(t, v1beta2.Codec, item)
api.Scheme.Log(nil)
roundTripSame(t, item)
}
var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest")
var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList")
var nonInternalRoundTrippableTypes = util.NewStringSet("List")
func TestRoundTripTypes(t *testing.T) {
// api.Scheme.Log(t)
// defer api.Scheme.Log(nil)
for kind := range api.Scheme.KnownTypes("") {
if nonRoundTrippableTypes.Has(kind) {
continue
@ -233,10 +267,9 @@ func TestRoundTripTypes(t *testing.T) {
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)
}
runTest(t, v1beta1.Codec, item)
runTest(t, v1beta2.Codec, item)
roundTripSame(t, item)
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) {
pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed))
apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
for i := 0; i < b.N; i++ {
latest.Codec.Encode(&pod)
@ -291,7 +324,7 @@ func BenchmarkEncode(b *testing.B) {
// BenchmarkEncodeJSON provides a baseline for regular JSON encode performance
func BenchmarkEncodeJSON(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed))
apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
for i := 0; i < b.N; i++ {
json.Marshal(&pod)
@ -300,7 +333,7 @@ func BenchmarkEncodeJSON(b *testing.B) {
func BenchmarkDecode(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed))
apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
data, _ := latest.Codec.Encode(&pod)
for i := 0; i < b.N; i++ {
@ -310,7 +343,7 @@ func BenchmarkDecode(b *testing.B) {
func BenchmarkDecodeInto(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed))
apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
data, _ := latest.Codec.Encode(&pod)
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
func BenchmarkDecodeJSON(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer.RandSource(rand.NewSource(benchmarkSeed))
apiObjectFuzzer := fuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
data, _ := latest.Codec.Encode(&pod)
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
namespace string
namespaceSet bool
resource string
resourceName string
selector labels.Selector
@ -190,10 +191,11 @@ func (r *Request) Namespace(namespace string) *Request {
if r.err != nil {
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)
return r
}
r.namespaceSet = true
r.namespace = namespace
return r
}
@ -330,7 +332,7 @@ func (r *Request) Poller(poller PollFunc) *Request {
func (r *Request) finalURL() string {
p := r.path
if !r.namespaceInQuery {
if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 {
p = path.Join(p, "ns", r.namespace)
}
if len(r.resource) != 0 {
@ -353,7 +355,7 @@ func (r *Request) finalURL() string {
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)
}

View File

@ -86,6 +86,11 @@ func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
if s := r.finalURL(); s != "/foo/" {
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) {

View File

@ -27,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"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/util"
)
@ -40,7 +41,8 @@ func TestSetsCodec(t *testing.T) {
"v1beta1": {false, "/api/v1beta1/", v1beta1.Codec},
"": {false, "/api/v1beta1/", v1beta1.Codec},
"v1beta2": {false, "/api/v1beta2/", v1beta2.Codec},
"v1beta3": {true, "", nil},
"v1beta3": {false, "/api/v1beta3/", v1beta3.Codec},
"v1beta4": {true, "", nil},
}
for version, expected := range testCases {
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()
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 {
return err
}

View File

@ -21,9 +21,13 @@ import (
"fmt"
"net/http"
"reflect"
"strings"
"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/util"
)
// 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())
}
}
// 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/v1beta1"
"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/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
@ -72,6 +73,7 @@ type Config struct {
EnableLogsSupport bool
EnableUISupport bool
EnableSwaggerSupport bool
EnableV1Beta3 bool
APIPrefix string
CorsAllowedOriginList util.StringList
Authenticator authenticator.Request
@ -122,6 +124,7 @@ type Master struct {
authorizer authorizer.Authorizer
admissionControl admission.Interface
masterCount int
v1beta3 bool
readOnlyServer string
readWriteServer string
@ -252,6 +255,7 @@ func New(c *Config) *Master {
authenticator: c.Authenticator,
authorizer: c.Authorizer,
admissionControl: c.AdmissionControl,
v1beta3: c.EnableV1Beta3,
masterCount: c.MasterCount,
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),
}
versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
apiserver.NewAPIGroupVersion(m.API_v1beta1()).InstallREST(m.handlerContainer, c.APIPrefix, "v1beta1")
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
versionHandler := apiserver.APIVersionHandler("v1beta1", "v1beta2")
m.rootWebService.Route(m.rootWebService.GET(c.APIPrefix).To(versionHandler))
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
}
// 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"
}