From 7fd887df61f890e8d39f6f10624074a0b5e8323f Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Thu, 8 Jan 2015 12:42:20 -0500 Subject: [PATCH] Enable v1beta3 API via --runtime_config=api/v1beta3 flag This exposes the proper v1beta3 API endpoint when the user specifies the --runtime_config=api/v1beta3 argument to the apiserver. v1beta3 is still considered experimental and subject to change. --runtime_config is a map of string keys and values, that can be specified by providing --runtime_config=a=b,b=c,d,e Only the key must be specified, the value can be omitted. Enables v1beta3 in hack/local-up-cluster.sh and hack/test-cmd.sh --- cmd/kube-apiserver/apiserver.go | 7 +++++ hack/local-up-cluster.sh | 1 + hack/test-cmd.sh | 29 ++++++++++-------- pkg/api/latest/latest.go | 13 ++++++-- pkg/client/restclient_test.go | 4 ++- pkg/master/master.go | 23 +++++++++++++- pkg/util/configuration_map.go | 53 +++++++++++++++++++++++++++++++++ 7 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 pkg/util/configuration_map.go diff --git a/cmd/kube-apiserver/apiserver.go b/cmd/kube-apiserver/apiserver.go index cdcc5d997d9..1ee5c938af7 100644 --- a/cmd/kube-apiserver/apiserver.go +++ b/cmd/kube-apiserver/apiserver.go @@ -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) diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index 4fc990c860b..4408ace88e8 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -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 & diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 9f63254b7f9..cd244bcddc1 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -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,7 +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): " +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" @@ -95,6 +96,7 @@ kube_cmd=( kube_api_versions=( v1beta1 v1beta2 + v1beta3 ) for version in "${kube_api_versions[@]}"; do kube_flags=( @@ -103,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[@]}" @@ -111,33 +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[@]}" - [[ "$("${kube_cmd[@]}" get pods -o template -t \"{{ len .items - ${before}}}\" "${kube_flags[@]}")" -eq 1 ]] - "${kube_cmd[@]}" get pods -o yaml "${kube_flags[@]}" | grep -q "id: redis-master" + after="$("${kube_cmd[@]}" get pods -o template -t "{{ len .items }}" "${kube_flags[@]}")" + [[ "$((${after} - ${before}))" -eq 1 ]] + "${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" diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index afed8ce60c1..2a13f1a3561 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -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 } diff --git a/pkg/client/restclient_test.go b/pkg/client/restclient_test.go index 31756d72adb..d0e2e1ebb6f 100644 --- a/pkg/client/restclient_test.go +++ b/pkg/client/restclient_test.go @@ -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}) diff --git a/pkg/master/master.go b/pkg/master/master.go index 1ad09d5c542..2329e959ce8 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -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 +} diff --git a/pkg/util/configuration_map.go b/pkg/util/configuration_map.go new file mode 100644 index 00000000000..a27a9844314 --- /dev/null +++ b/pkg/util/configuration_map.go @@ -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" +}