From fa9f86478240022fdc275324c15b9a0767fd4517 Mon Sep 17 00:00:00 2001 From: nikhiljindal Date: Tue, 5 May 2015 16:53:22 -0700 Subject: [PATCH] Adding a script to update etcd objects --- .travis.yml | 1 + cluster/update-storage-objects.sh | 112 ++++++++++++++++++++++++ cmd/kube-apiserver/app/server.go | 52 ++++++++++-- docs/cluster_management.md | 8 +- hack/test-cmd.sh | 1 - hack/test-update-storage-objects.sh | 127 ++++++++++++++++++++++++++++ pkg/api/latest/latest.go | 94 +++++++++----------- pkg/api/latest/latest_test.go | 2 +- pkg/api/registered/registered.go | 68 +++++++++++++++ pkg/api/v1/conversion.go | 2 +- pkg/api/v1/defaults.go | 2 +- pkg/api/v1/register.go | 14 +++ pkg/api/v1beta1/conversion.go | 2 +- pkg/api/v1beta1/defaults.go | 2 +- pkg/api/v1beta1/register.go | 14 +++ pkg/api/v1beta2/conversion.go | 2 +- pkg/api/v1beta2/defaults.go | 2 +- pkg/api/v1beta2/register.go | 14 +++ pkg/api/v1beta3/conversion.go | 2 +- pkg/api/v1beta3/defaults.go | 2 +- pkg/api/v1beta3/register.go | 14 +++ pkg/master/master.go | 16 ++-- shippable.yml | 1 + 23 files changed, 472 insertions(+), 82 deletions(-) create mode 100755 cluster/update-storage-objects.sh create mode 100755 hack/test-update-storage-objects.sh create mode 100644 pkg/api/registered/registered.go diff --git a/.travis.yml b/.travis.yml index b38db6ae40e..5a39fa3a841 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ script: - node_modules/karma/bin/karma start www/master/karma.conf.js --single-run --browsers PhantomJS - PATH=$HOME/gopath/bin:./third_party/etcd:$PATH ./hack/test-cmd.sh - PATH=$HOME/gopath/bin:./third_party/etcd:$PATH KUBE_TEST_API_VERSIONS="${KUBE_TEST_API_VERSIONS}" KUBE_INTEGRATION_TEST_MAX_CONCURRENCY=4 LOG_LEVEL=4 ./hack/test-integration.sh + - PATH=$HOME/gopath/bin:./third_party/etcd:$PATH ./hack/test-update-storage-objects.sh notifications: irc: "chat.freenode.net#google-containers" diff --git a/cluster/update-storage-objects.sh b/cluster/update-storage-objects.sh new file mode 100755 index 00000000000..928f7984104 --- /dev/null +++ b/cluster/update-storage-objects.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# Copyright 2015 The Kubernetes Authors 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. + +# Script to update etcd objects as per the latest API Version. +# This just reads all objects and then writes them back as is to ensure that +# they are written using the latest API version. +# +# Steps to use this script to upgrade the cluster to a new version: +# https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/cluster_management.md#updgrading-to-a-different-api-version + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + +KUBECTL="${KUBE_OUTPUT_HOSTBIN}/kubectl" + +# List of resources to be updated. +# TODO: Get this list of resources from server once +# https://github.com/GoogleCloudPlatform/kubernetes/issues/2057 is fixed. +declare -a resources=( + "endpoints" + "events" + "limitranges" + "namespaces" + "nodes" + "pods" + "persistentvolumes" + "persistentvolumeclaims" + "replicationcontrollers" + "resourcequotas" + "secrets" + "services" +) + +# Find all the namespaces. +namespaces=( $("${KUBECTL}" get namespaces -o template -t "{{range.items}}{{.metadata.name}} {{end}}")) +if [ -z "${namespaces:-}" ] +then + echo "Unexpected: No namespace found. Nothing to do." + exit 1 +fi +for resource in "${resources[@]}" +do + for namespace in "${namespaces[@]}" + do + instances=( $("${KUBECTL}" get "${resource}" --namespace="${namespace}" -o template -t "{{range.items}}{{.metadata.name}} {{end}}")) + # Nothing to do if there is no instance of that resource. + if [[ -z "${instances:-}" ]] + then + continue + fi + for instance in "${instances[@]}" + do + # Read and then write it back as is. + # Update can fail if the object was updated after we fetched the + # object, but before we could update it. We, hence, try the update + # operation multiple times. But 5 continuous failures indicate some other + # problem. + success=0 + for (( tries=0; tries<5; ++tries )) + do + filename="/tmp/k8s-${namespace}-${resource}-${instance}.json" + ( "${KUBECTL}" get "${resource}" "${instance}" --namespace="${namespace}" -o json > "${filename}" ) || true + if [[ ! -s "${filename}" ]] + then + # This happens when the instance has been deleted. We can hence ignore + # this instance. + echo "Looks like ${instance} got deleted. Ignoring it" + continue + fi + output=$("${KUBECTL}" update -f "${filename}" --namespace="${namespace}") || true + rm "${filename}" + if [ -n "${output:-}" ] + then + success=1 + break + fi + done + if [[ "${success}" -eq 0 ]] + then + echo "Error: failed to update ${resource}/${instance} in ${namespace} namespace after 5 tries" + exit 1 + fi + done + if [[ "${resource}" == "namespaces" ]] || [[ "${resource}" == "nodes" ]] + then + # These resources are namespace agnostic. No need to update them for every + # namespace. + break + fi + done +done + +echo "All objects updated successfully!!" + +exit 0 diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 26fc6ac46d9..11c4b3f42d5 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -178,7 +178,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.AllowPrivileged, "allow-privileged", s.AllowPrivileged, "If true, allow privileged containers.") fs.Var(&s.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.") fs.StringVar(&s.MasterServiceNamespace, "master-service-namespace", s.MasterServiceNamespace, "The namespace from which the kubernetes master services should be injected into pods") - fs.Var(&s.RuntimeConfig, "runtime-config", "A set of key=value pairs that describe runtime configuration that may be passed to the apiserver.") + fs.Var(&s.RuntimeConfig, "runtime-config", "A set of key=value pairs that describe runtime configuration that may be passed to the apiserver. api/ key can be used to turn on/off specific api versions. api/all and api/legacy are special keys to control all and legacy api versions respectively.") client.BindKubeletClientConfigFlags(fs, &s.KubeletConfig) fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster") fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/") @@ -238,19 +238,39 @@ func (s *APIServer) Run(_ []string) error { glog.Fatalf("Failure to start kubelet client: %v", err) } - disableV1beta3 := false - v1beta3FlagValue, ok := s.RuntimeConfig["api/v1beta3"] - if ok && v1beta3FlagValue == "false" { - disableV1beta3 = true + // "api/all=false" allows users to selectively enable specific api versions. + disableAllAPIs := false + allAPIFlagValue, ok := s.RuntimeConfig["api/all"] + if ok && allAPIFlagValue == "false" { + disableAllAPIs = true } - _, enableV1 := s.RuntimeConfig["api/v1"] - + // "api/legacy=false" allows users to disable legacy api versions. + // Right now, v1beta1 and v1beta2 are considered legacy. disableLegacyAPIs := false legacyAPIFlagValue, ok := s.RuntimeConfig["api/legacy"] if ok && legacyAPIFlagValue == "false" { disableLegacyAPIs = true } + + // "api/v1beta1={true|false} allows users to enable/disable v1beta1 API. + // This takes preference over api/all and api/legacy, if specified. + disableV1beta1 := disableAllAPIs || disableLegacyAPIs + disableV1beta1 = !s.getRuntimeConfigValue("api/v1beta1", !disableV1beta1) + + // "api/v1beta2={true|false} allows users to enable/disable v1beta2 API. + // This takes preference over api/all and api/legacy, if specified. + disableV1beta2 := disableAllAPIs || disableLegacyAPIs + disableV1beta2 = !s.getRuntimeConfigValue("api/v1beta2", !disableV1beta2) + + // "api/v1beta3={true|false} allows users to enable/disable v1beta3 API. + // This takes preference over api/all and api/legacy, if specified. + disableV1beta3 := disableAllAPIs + disableV1beta3 = !s.getRuntimeConfigValue("api/v1beta3", !disableV1beta3) + + // V1 is disabled by default. Users can enable it using "api/v1={true}". + _, enableV1 := s.RuntimeConfig["api/v1"] + // TODO: expose same flags as client.BindClientConfigFlags but for a server clientConfig := &client.Config{ Host: net.JoinHostPort(s.InsecureBindAddress.String(), strconv.Itoa(s.InsecurePort)), @@ -337,7 +357,8 @@ func (s *APIServer) Run(_ []string) error { SupportsBasicAuth: len(s.BasicAuthFile) > 0, Authorizer: authorizer, AdmissionControl: admissionController, - DisableLegacyAPIs: disableLegacyAPIs, + DisableV1Beta1: disableV1beta1, + DisableV1Beta2: disableV1beta2, DisableV1Beta3: disableV1beta3, EnableV1: enableV1, MasterServiceNamespace: s.MasterServiceNamespace, @@ -445,3 +466,18 @@ func (s *APIServer) Run(_ []string) error { glog.Fatal(http.ListenAndServe()) return nil } + +func (s *APIServer) getRuntimeConfigValue(apiKey string, defaultValue bool) bool { + flagValue, ok := s.RuntimeConfig[apiKey] + if ok { + if flagValue == "" { + return true + } + boolValue, err := strconv.ParseBool(flagValue) + if err != nil { + glog.Fatalf("Invalid value of %s: %s", apiKey, flagValue) + } + return boolValue + } + return defaultValue +} diff --git a/docs/cluster_management.md b/docs/cluster_management.md index 6b7284b8e28..f0461cea854 100644 --- a/docs/cluster_management.md +++ b/docs/cluster_management.md @@ -10,18 +10,20 @@ The `cluster/kube-push.sh` script will do a rudimentary update; it is a 1.0 road There is a sequence of steps to upgrade to a new API version. -1. Turn on the new version. +1. Turn on the new api version 2. Upgrade the cluster's storage to use the new version. 3. Upgrade all config files. Identify users of the old api version endpoints. +4. Update existing objects in the storage to new version by running cluster/update-storage-objects.sh 3. Turn off the old version. ### Turn on or off an API version for your cluster -TODO: There's an apiserver flag for this. +Specific API versions can be turned on or off by passing --runtime-config=api/ flag while bringing up the server. For example: to turn off v1beta3 API, pass --runtime-config=api/v1beta3=false. +runtime-config also supports 2 special keys: api/all and api/legacy to control all and legacy APIs respectively. For example, for turning off all api versions except v1beta3, pass --runtime-config=api/all=false,api/v1beta3=true. ### Switching your cluster's storage API version -TODO: This functionality hasn't been written yet. +KUBE_API_VERSIONS env var controls the API versions that are supported in the cluster. The first version in the list is used as the cluster's storage version. Hence, to set a specific version as the storage version, bring it to the front of list of versions in the value of KUBE_API_VERSIONS. ### Switching your config files to a new API version diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 1fa8fc77b21..65a6b931acb 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -30,7 +30,6 @@ function cleanup() [[ -n ${APISERVER_PID-} ]] && kill ${APISERVER_PID} 1>&2 2>/dev/null [[ -n ${CTLRMGR_PID-} ]] && kill ${CTLRMGR_PID} 1>&2 2>/dev/null [[ -n ${KUBELET_PID-} ]] && kill ${KUBELET_PID} 1>&2 2>/dev/null - [[ -n ${PROXY_PID-} ]] && kill ${PROXY_PID} 1>&2 2>/dev/null kube::etcd::cleanup rm -rf "${KUBE_TEMP}" diff --git a/hack/test-update-storage-objects.sh b/hack/test-update-storage-objects.sh new file mode 100755 index 00000000000..a595c69816d --- /dev/null +++ b/hack/test-update-storage-objects.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +# Copyright 2014 The Kubernetes Authors 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. + +# Script to test cluster/update-storage-objects.sh works as expected. + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + +# The api version in which objects are currently stored in etcd. +KUBE_OLD_API_VERSION=${KUBE_OLD_API_VERSION:-"v1beta1"} +# The api version in which our etcd objects should be converted to. +# The new api version +KUBE_NEW_API_VERSION=${KUBE_NEW_API_VERSION:-"v1beta3"} + +ETCD_HOST=${ETCD_HOST:-127.0.0.1} +ETCD_PORT=${ETCD_PORT:-4001} +API_PORT=${API_PORT:-8080} +API_HOST=${API_HOST:-127.0.0.1} +KUBELET_PORT=${KUBELET_PORT:-10250} +KUBE_API_VERSIONS="" +RUNTIME_CONFIG="" + +KUBECTL="${KUBE_OUTPUT_HOSTBIN}/kubectl" +UPDATE_ETCD_OBJECTS_SCRIPT="${KUBE_ROOT}/cluster/update-storage-objects.sh" + +function startApiServer() { + kube::log::status "Starting kube-apiserver with KUBE_API_VERSIONS: ${KUBE_API_VERSIONS} and runtime_config: ${RUNTIME_CONFIG}" + + KUBE_API_VERSIONS="${KUBE_API_VERSIONS}" \ + "${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \ + --address="127.0.0.1" \ + --public_address_override="127.0.0.1" \ + --port="${API_PORT}" \ + --etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \ + --public_address_override="127.0.0.1" \ + --kubelet_port=${KUBELET_PORT} \ + --runtime_config="${RUNTIME_CONFIG}" \ + --cert_dir="${TMPDIR:-/tmp/}" \ + --portal_net="10.0.0.0/24" 1>&2 & + APISERVER_PID=$! + + kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: " +} + +function killApiServer() { + kube::log::status "Killing api server" + [[ -n ${APISERVER_PID-} ]] && kill ${APISERVER_PID} 1>&2 2>/dev/null + unset APISERVER_PID +} + +function cleanup() { + killApiServer + + kube::etcd::cleanup + + kube::log::status "Clean up complete" +} + +trap cleanup EXIT SIGINT + +kube::etcd::start + +kube::log::status "Running test for update etcd object scenario" + +"${KUBE_ROOT}/hack/build-go.sh" + + +####################################################### +# Step 1: Start a server which supports both the old and new api versions, +# but KUBE_OLD_API_VERSION is the latest (storage) version. +####################################################### + +KUBE_API_VERSIONS="${KUBE_OLD_API_VERSION},${KUBE_NEW_API_VERSION}" +RUNTIME_CONFIG="api/all=false,api/${KUBE_OLD_API_VERSION}=true,api/${KUBE_NEW_API_VERSION}=true" +startApiServer + +# Create a pod +kube::log::status "Creating a pod" +${KUBECTL} create -f examples/pod.yaml + +killApiServer + + +####################################################### +# Step 2: Start a server which supports both the old and new api versions, +# but KUBE_NEW_API_VERSION is the latest (storage) version. +####################################################### + +KUBE_API_VERSIONS="${KUBE_NEW_API_VERSION},${KUBE_OLD_API_VERSION}" +RUNTIME_CONFIG="api/all=false,api/${KUBE_OLD_API_VERSION}=true,api/${KUBE_NEW_API_VERSION}=true" +startApiServer + +# Update etcd objects, so that will now be stored in the new api version. +${UPDATE_ETCD_OBJECTS_SCRIPT} + +killApiServer + + +####################################################### +# Step 3 : Start a server which supports only the new api version. +####################################################### + +KUBE_API_VERSIONS="${KUBE_NEW_API_VERSION}" +RUNTIME_CONFIG="api/all=false,api/${KUBE_NEW_API_VERSION}=true" +startApiServer + +# Verify that the server is able to read the object. +# This will fail if the object is in a version that is not understood by the +# master. +${KUBECTL} get pods diff --git a/pkg/api/latest/latest.go b/pkg/api/latest/latest.go index 6bb83517366..ae01d17c16c 100644 --- a/pkg/api/latest/latest.go +++ b/pkg/api/latest/latest.go @@ -18,19 +18,17 @@ package latest import ( "fmt" - "os" - "sort" "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1" "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" - "github.com/golang/glog" ) // Version is the string that represents the current external default version. @@ -68,63 +66,14 @@ var RESTMapper meta.RESTMapper // userResources is a group of resources mostly used by a kubectl user var userResources = []string{"rc", "svc", "pods", "pvc"} -// InterfacesFor returns the default Codec and ResourceVersioner for a given version -// string, or an error if the version is not known. -func InterfacesFor(version string) (*meta.VersionInterfaces, error) { - switch version { - case "v1beta1": - return &meta.VersionInterfaces{ - Codec: v1beta1.Codec, - ObjectConvertor: api.Scheme, - MetadataAccessor: accessor, - }, nil - case "v1beta2": - return &meta.VersionInterfaces{ - Codec: v1beta2.Codec, - ObjectConvertor: api.Scheme, - MetadataAccessor: accessor, - }, nil - case "v1beta3": - return &meta.VersionInterfaces{ - Codec: v1beta3.Codec, - ObjectConvertor: api.Scheme, - MetadataAccessor: accessor, - }, nil - case "v1": - return &meta.VersionInterfaces{ - Codec: v1.Codec, - ObjectConvertor: api.Scheme, - MetadataAccessor: accessor, - }, nil - default: - return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", ")) - } -} - func init() { - // The list of valid API versions to test that KUBE_API_VERSIONS does not contain an invalid version. - // Note that the list is in ascending sorted order. - validAPIVersions := []string{"v1", "v1beta1", "v1beta2", "v1beta3"} - // Env var KUBE_API_VERSIONS is a comma separated list of supported API versions. - // The versions should be in the order of most preferred to the least. - supportedVersions := os.Getenv("KUBE_API_VERSIONS") - if supportedVersions == "" { - supportedVersions = "v1beta3,v1beta1,v1beta2,v1" - } - versions := strings.Split(supportedVersions, ",") - // The first version in the list is the latest version. - Version = versions[0] + // Use the first API version in the list of registered versions as the latest. + Version = registered.RegisteredVersions[0] Codec = runtime.CodecFor(api.Scheme, Version) - // Put the versions in Versions in reverse order. + // Put the registered versions in Versions in reverse order. + versions := registered.RegisteredVersions Versions = []string{} for i := len(versions) - 1; i >= 0; i-- { - version := versions[i] - // Verify that the version is valid. - searchIndex := sort.SearchStrings(validAPIVersions, version) - if searchIndex == len(validAPIVersions) || validAPIVersions[searchIndex] != version { - // Not a valid API version. - glog.Fatalf("invalid api version: %s in KUBE_API_VERSIONS: %s. List of valid API versions: %s", version, os.Getenv("KUBE_API_VERSIONS"), strings.Join(validAPIVersions, ", ")) - } Versions = append(Versions, versions[i]) } @@ -195,3 +144,36 @@ func init() { } RESTMapper = mapper } + +// InterfacesFor returns the default Codec and ResourceVersioner for a given version +// string, or an error if the version is not known. +func InterfacesFor(version string) (*meta.VersionInterfaces, error) { + switch version { + case "v1beta1": + return &meta.VersionInterfaces{ + Codec: v1beta1.Codec, + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + case "v1beta2": + return &meta.VersionInterfaces{ + Codec: v1beta2.Codec, + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + case "v1beta3": + return &meta.VersionInterfaces{ + Codec: v1beta3.Codec, + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + case "v1": + return &meta.VersionInterfaces{ + Codec: v1.Codec, + ObjectConvertor: api.Scheme, + MetadataAccessor: accessor, + }, nil + default: + return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", version, strings.Join(Versions, ", ")) + } +} diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index ab1597d0b45..c196b96f75e 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -98,7 +98,7 @@ func TestRESTMapper(t *testing.T) { interfaces, _ := InterfacesFor(version) if mapping.Codec != interfaces.Codec { - t.Errorf("unexpected codec: %#v", mapping) + t.Errorf("unexpected codec: %#v, expected: %#v", mapping, interfaces) } rc := &internal.ReplicationController{ObjectMeta: internal.ObjectMeta{Name: "foo"}} diff --git a/pkg/api/registered/registered.go b/pkg/api/registered/registered.go new file mode 100644 index 00000000000..ef70a01ea6f --- /dev/null +++ b/pkg/api/registered/registered.go @@ -0,0 +1,68 @@ +/* +Copyright 2015 The Kubernetes Authors 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 to keep track of API Versions that should be registered in api.Scheme. +package registered + +import ( + "os" + "strings" + + "github.com/golang/glog" +) + +// List of registered API versions. +// The list is in the order of most preferred to the least. +var RegisteredVersions []string + +func init() { + validAPIVersions := map[string]bool{ + "v1": true, + "v1beta1": true, + "v1beta2": true, + "v1beta3": true, + } + + // The default list of supported api versions, in order of most preferred to the least. + defaultSupportedVersions := "v1beta3,v1beta1,v1beta2,v1" + // Env var KUBE_API_VERSIONS is a comma separated list of API versions that should be registered in the scheme. + // The versions should be in the order of most preferred to the least. + supportedVersions := os.Getenv("KUBE_API_VERSIONS") + if supportedVersions == "" { + supportedVersions = defaultSupportedVersions + } + versions := strings.Split(supportedVersions, ",") + for _, version := range versions { + // Verify that the version is valid. + valid, ok := validAPIVersions[version] + if !ok || !valid { + // Not a valid API version. + glog.Fatalf("invalid api version: %s in KUBE_API_VERSIONS: %s. List of valid API versions: %v", + version, os.Getenv("KUBE_API_VERSIONS"), validAPIVersions) + } + RegisteredVersions = append(RegisteredVersions, version) + } +} + +// Returns true if the given api version is one of the registered api versions. +func IsRegisteredAPIVersion(version string) bool { + for _, apiVersion := range RegisteredVersions { + if apiVersion == version { + return true + } + } + return false +} diff --git a/pkg/api/v1/conversion.go b/pkg/api/v1/conversion.go index 74704d5e1a9..673d93a650d 100644 --- a/pkg/api/v1/conversion.go +++ b/pkg/api/v1/conversion.go @@ -24,7 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" ) -func init() { +func addConversionFuncs() { err := newer.Scheme.AddConversionFuncs( convert_v1_Container_To_api_Container, convert_api_Container_To_v1_Container, diff --git a/pkg/api/v1/defaults.go b/pkg/api/v1/defaults.go index 443e9101016..ab5ab09fa6a 100644 --- a/pkg/api/v1/defaults.go +++ b/pkg/api/v1/defaults.go @@ -25,7 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -func init() { +func addDefaultingFuncs() { api.Scheme.AddDefaultingFuncs( func(obj *ReplicationController) { var labels map[string]string diff --git a/pkg/api/v1/register.go b/pkg/api/v1/register.go index 10d8423c604..082ac87897d 100644 --- a/pkg/api/v1/register.go +++ b/pkg/api/v1/register.go @@ -18,6 +18,7 @@ package v1 import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -25,6 +26,19 @@ import ( var Codec = runtime.CodecFor(api.Scheme, "v1") func init() { + // Check if v1 is in the list of supported API versions. + if !registered.IsRegisteredAPIVersion("v1") { + return + } + + // Register the API. + addKnownTypes() + addConversionFuncs() + addDefaultingFuncs() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes() { api.Scheme.AddKnownTypes("v1", &Pod{}, &PodList{}, diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index 0181e721a43..dea7f90d79a 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -28,7 +28,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -func init() { +func addConversionFuncs() { // Our TypeMeta was split into two different structs. newer.Scheme.AddStructFieldConversion(TypeMeta{}, "TypeMeta", newer.TypeMeta{}, "TypeMeta") newer.Scheme.AddStructFieldConversion(TypeMeta{}, "TypeMeta", newer.ObjectMeta{}, "ObjectMeta") diff --git a/pkg/api/v1beta1/defaults.go b/pkg/api/v1beta1/defaults.go index f217c694bf0..4f8c775c5fc 100644 --- a/pkg/api/v1beta1/defaults.go +++ b/pkg/api/v1beta1/defaults.go @@ -26,7 +26,7 @@ import ( "github.com/golang/glog" ) -func init() { +func addDefaultingFuncs() { api.Scheme.AddDefaultingFuncs( func(obj *ReplicationController) { if len(obj.DesiredState.ReplicaSelector) == 0 { diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index 870246e5d20..29d29844961 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -18,6 +18,7 @@ package v1beta1 import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -31,6 +32,19 @@ var Codec = runtime.CodecFor(api.Scheme, "v1beta1") const Dependency = true func init() { + // Check if v1beta1 is in the list of supported API versions. + if !registered.IsRegisteredAPIVersion("v1beta1") { + return + } + + // Register the API. + addKnownTypes() + addConversionFuncs() + addDefaultingFuncs() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes() { api.Scheme.AddKnownTypes("v1beta1", &Pod{}, &PodStatusResult{}, diff --git a/pkg/api/v1beta2/conversion.go b/pkg/api/v1beta2/conversion.go index 5f200ca7099..27b29e90194 100644 --- a/pkg/api/v1beta2/conversion.go +++ b/pkg/api/v1beta2/conversion.go @@ -28,7 +28,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -func init() { +func addConversionFuncs() { // Our TypeMeta was split into two different structs. newer.Scheme.AddStructFieldConversion(TypeMeta{}, "TypeMeta", newer.TypeMeta{}, "TypeMeta") newer.Scheme.AddStructFieldConversion(TypeMeta{}, "TypeMeta", newer.ObjectMeta{}, "ObjectMeta") diff --git a/pkg/api/v1beta2/defaults.go b/pkg/api/v1beta2/defaults.go index 53c3ab0a89c..10f548eaeb0 100644 --- a/pkg/api/v1beta2/defaults.go +++ b/pkg/api/v1beta2/defaults.go @@ -26,7 +26,7 @@ import ( "github.com/golang/glog" ) -func init() { +func addDefaultingFuncs() { api.Scheme.AddDefaultingFuncs( func(obj *ReplicationController) { if len(obj.DesiredState.ReplicaSelector) == 0 { diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go index 4182ae4419c..595a1fb14f9 100644 --- a/pkg/api/v1beta2/register.go +++ b/pkg/api/v1beta2/register.go @@ -18,6 +18,7 @@ package v1beta2 import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -31,6 +32,19 @@ var Codec = runtime.CodecFor(api.Scheme, "v1beta2") const Dependency = true func init() { + // Check if v1beta2 is in the list of supported API versions. + if !registered.IsRegisteredAPIVersion("v1beta2") { + return + } + + // Register the API. + addKnownTypes() + addConversionFuncs() + addDefaultingFuncs() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes() { api.Scheme.AddKnownTypes("v1beta2", &Pod{}, &PodStatusResult{}, diff --git a/pkg/api/v1beta3/conversion.go b/pkg/api/v1beta3/conversion.go index 7a3659e4052..45bf0ef562f 100644 --- a/pkg/api/v1beta3/conversion.go +++ b/pkg/api/v1beta3/conversion.go @@ -24,7 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" ) -func init() { +func addConversionFuncs() { // Add non-generated conversion functions err := newer.Scheme.AddConversionFuncs( convert_v1beta3_Container_To_api_Container, diff --git a/pkg/api/v1beta3/defaults.go b/pkg/api/v1beta3/defaults.go index 0eb7d7d0991..70d56bcb440 100644 --- a/pkg/api/v1beta3/defaults.go +++ b/pkg/api/v1beta3/defaults.go @@ -24,7 +24,7 @@ import ( "github.com/golang/glog" ) -func init() { +func addDefaultingFuncs() { api.Scheme.AddDefaultingFuncs( func(obj *ReplicationController) { var labels map[string]string diff --git a/pkg/api/v1beta3/register.go b/pkg/api/v1beta3/register.go index 83edfede61f..e7b61aaf46b 100644 --- a/pkg/api/v1beta3/register.go +++ b/pkg/api/v1beta3/register.go @@ -18,6 +18,7 @@ package v1beta3 import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) @@ -25,6 +26,19 @@ import ( var Codec = runtime.CodecFor(api.Scheme, "v1beta3") func init() { + // Check if v1beta3 is in the list of supported API versions. + if !registered.IsRegisteredAPIVersion("v1beta3") { + return + } + + // Register the API. + addKnownTypes() + addConversionFuncs() + addDefaultingFuncs() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes() { api.Scheme.AddKnownTypes("v1beta3", &Pod{}, &PodList{}, diff --git a/pkg/master/master.go b/pkg/master/master.go index bfeb411692a..4af27c1191e 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -92,8 +92,10 @@ type Config struct { EnableUISupport bool // allow downstream consumers to disable swagger EnableSwaggerSupport bool - // allow v1beta1 and v1beta2 to be conditionally disabled - DisableLegacyAPIs bool + // allow v1beta1 to be conditionally disabled + DisableV1Beta1 bool + // allow v1beta2 to be conditionally disabled + DisableV1Beta2 bool // allow v1beta3 to be conditionally disabled DisableV1Beta3 bool // allow v1 to be conditionally enabled @@ -162,7 +164,8 @@ type Master struct { authorizer authorizer.Authorizer admissionControl admission.Interface masterCount int - legacyAPIs bool + v1beta1 bool + v1beta2 bool v1beta3 bool v1 bool requestContextMapper api.RequestContextMapper @@ -307,7 +310,8 @@ func New(c *Config) *Master { authenticator: c.Authenticator, authorizer: c.Authorizer, admissionControl: c.AdmissionControl, - legacyAPIs: !c.DisableLegacyAPIs, + v1beta1: !c.DisableV1Beta1, + v1beta2: !c.DisableV1Beta2, v1beta3: !c.DisableV1Beta3, v1: c.EnableV1, requestContextMapper: c.RequestContextMapper, @@ -465,11 +469,13 @@ func (m *Master) init(c *Config) { } apiVersions := []string{} - if m.legacyAPIs { + if m.v1beta1 { if err := m.api_v1beta1().InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup API v1beta1: %v", err) } apiVersions = append(apiVersions, "v1beta1") + } + if m.v1beta2 { if err := m.api_v1beta2().InstallREST(m.handlerContainer); err != nil { glog.Fatalf("Unable to setup API v1beta2: %v", err) } diff --git a/shippable.yml b/shippable.yml index 585ba0bef0f..5232a59bd15 100644 --- a/shippable.yml +++ b/shippable.yml @@ -36,6 +36,7 @@ script: - KUBE_RACE="-race" KUBE_COVER="y" KUBE_GOVERALLS_BIN="$HOME/gopath/bin/goveralls" KUBE_TIMEOUT='-timeout 300s' KUBE_COVERPROCS=8 KUBE_TEST_ETCD_PREFIXES="${KUBE_TEST_ETCD_PREFIXES}" KUBE_TEST_API_VERSIONS="${KUBE_TEST_API_VERSIONS}" ./hack/test-go.sh -- -p=2 - PATH=$HOME/gopath/bin:./third_party/etcd:$PATH ./hack/test-cmd.sh - PATH=$HOME/gopath/bin:./third_party/etcd:$PATH KUBE_TEST_API_VERSIONS="${KUBE_TEST_API_VERSIONS}" KUBE_INTEGRATION_TEST_MAX_CONCURRENCY=4 LOG_LEVEL=4 ./hack/test-integration.sh + - PATH=$HOME/gopath/bin:./third_party/etcd:$PATH ./hack/test-update-storage-objects.sh notifications: irc: "chat.freenode.net#google-containers"