PetSet type, apps apigroup

This commit is contained in:
Prashanth Balasubramanian 2016-04-15 15:30:15 -07:00
parent 2599607a64
commit 0ac10c6cc2
53 changed files with 8688 additions and 20 deletions

110
api/swagger-spec/apps.json Normal file
View File

@ -0,0 +1,110 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis/apps",
"apis": [
{
"path": "/apis/apps",
"description": "get information of a group",
"operations": [
{
"type": "unversioned.APIGroup",
"method": "GET",
"summary": "get information of a group",
"nickname": "getAPIGroup",
"parameters": [],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"consumes": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
]
}
]
}
],
"models": {
"unversioned.APIGroup": {
"id": "unversioned.APIGroup",
"description": "APIGroup contains the name, the supported versions, and the preferred version of a group.",
"required": [
"name",
"versions",
"serverAddressByClientCIDRs"
],
"properties": {
"kind": {
"type": "string",
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
},
"apiVersion": {
"type": "string",
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
},
"name": {
"type": "string",
"description": "name is the name of the group."
},
"versions": {
"type": "array",
"items": {
"$ref": "unversioned.GroupVersionForDiscovery"
},
"description": "versions are the versions supported in this group."
},
"preferredVersion": {
"$ref": "unversioned.GroupVersionForDiscovery",
"description": "preferredVersion is the version preferred by the API server, which probably is the storage version."
},
"serverAddressByClientCIDRs": {
"type": "array",
"items": {
"$ref": "unversioned.ServerAddressByClientCIDR"
},
"description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP."
}
}
},
"unversioned.GroupVersionForDiscovery": {
"id": "unversioned.GroupVersionForDiscovery",
"description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensiblity.",
"required": [
"groupVersion",
"version"
],
"properties": {
"groupVersion": {
"type": "string",
"description": "groupVersion specifies the API group and version in the form \"group/version\""
},
"version": {
"type": "string",
"description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion."
}
}
},
"unversioned.ServerAddressByClientCIDR": {
"id": "unversioned.ServerAddressByClientCIDR",
"description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.",
"required": [
"clientCIDR",
"serverAddress"
],
"properties": {
"clientCIDR": {
"type": "string",
"description": "The CIDR with which clients can match their IP to figure out the server address that they should use."
},
"serverAddress": {
"type": "string",
"description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port."
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,14 @@
{
"path": "/apis/batch",
"description": "get information of a group"
},
{
"path": "/apis/apps/v1alpha1",
"description": "API at /apis/apps/v1alpha1"
},
{
"path": "/apis/apps",
"description": "get information of a group"
}
],
"apiVersion": "",

View File

@ -38,6 +38,8 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1"
"k8s.io/kubernetes/pkg/apis/autoscaling"
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/batch"
@ -346,6 +348,29 @@ func Run(s *options.APIServer) error {
storageDestinations.AddAPIGroup(batch.GroupName, batchEtcdStorage)
}
if apiResourceConfigSource.AnyResourcesForVersionEnabled(appsapi.SchemeGroupVersion) {
glog.Infof("Configuring apps/v1alpha1 storage destination")
appsGroup, err := registered.Group(apps.GroupName)
if err != nil {
glog.Fatalf("Apps API is enabled in runtime config, but not enabled in the environment variable KUBE_API_VERSIONS. Error: %v", err)
}
// Figure out what storage group/version we should use.
storageGroupVersion, found := storageVersions[appsGroup.GroupVersion.Group]
if !found {
glog.Fatalf("Couldn't find the storage version for group: %q in storageVersions: %v", appsGroup.GroupVersion.Group, storageVersions)
}
if storageGroupVersion != "apps/v1alpha1" {
glog.Fatalf("The storage version for apps must be apps/v1alpha1")
}
glog.Infof("Using %v for petset group storage version", storageGroupVersion)
appsEtcdStorage, err := newEtcd(api.Codecs, storageGroupVersion, "apps/__internal", s.EtcdConfig)
if err != nil {
glog.Fatalf("Invalid extensions storage version or misconfigured etcd: %v", err)
}
storageDestinations.AddAPIGroup(apps.GroupName, appsEtcdStorage)
}
updateEtcdOverrides(s.EtcdServersOverrides, storageVersions, s.EtcdConfig, &storageDestinations, newEtcd)
// Default to the private server key for service account token signing
@ -477,6 +502,7 @@ func parseRuntimeConfig(s *options.APIServer) (genericapiserver.APIResourceConfi
extensionsapiv1beta1.SchemeGroupVersion: extensionsGroupVersionString,
batchapiv1.SchemeGroupVersion: batchapiv1.SchemeGroupVersion.String(),
autoscalingapiv1.SchemeGroupVersion: autoscalingapiv1.SchemeGroupVersion.String(),
appsapi.SchemeGroupVersion: appsapi.SchemeGroupVersion.String(),
}
resourceConfig := master.DefaultAPIResourceConfigSource()

View File

@ -41,6 +41,8 @@ func main() {
"k8s.io/kubernetes/pkg/apis/autoscaling/v1",
"k8s.io/kubernetes/pkg/apis/batch",
"k8s.io/kubernetes/pkg/apis/batch/v1",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/apps/v1alpha1",
"k8s.io/kubernetes/pkg/apis/componentconfig",
"k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1",
"k8s.io/kubernetes/pkg/apis/extensions",

View File

@ -41,6 +41,8 @@ func main() {
"k8s.io/kubernetes/pkg/apis/autoscaling/v1",
"k8s.io/kubernetes/pkg/apis/batch",
"k8s.io/kubernetes/pkg/apis/batch/v1",
"k8s.io/kubernetes/pkg/apis/apps",
"k8s.io/kubernetes/pkg/apis/apps/v1alpha1",
"k8s.io/kubernetes/pkg/apis/componentconfig",
"k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1",
"k8s.io/kubernetes/pkg/apis/extensions",

View File

@ -69,6 +69,7 @@ func New() *Generator {
`k8s.io/kubernetes/pkg/apis/extensions/v1beta1`,
`k8s.io/kubernetes/pkg/apis/autoscaling/v1`,
`k8s.io/kubernetes/pkg/apis/batch/v1`,
`k8s.io/kubernetes/pkg/apis/apps/v1alpha1`,
}, ","),
DropEmbeddedFields: "k8s.io/kubernetes/pkg/api/unversioned.TypeMeta",
}

View File

@ -421,6 +421,7 @@ _kubectl_get()
must_have_one_noun+=("node")
must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("petset")
must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate")
@ -456,6 +457,7 @@ _kubectl_get()
noun_aliases+=("ns")
noun_aliases+=("persistentvolumeclaims")
noun_aliases+=("persistentvolumes")
noun_aliases+=("petsets")
noun_aliases+=("po")
noun_aliases+=("pods")
noun_aliases+=("podsecuritypolicies")
@ -1170,6 +1172,7 @@ _kubectl_patch()
must_have_one_noun+=("node")
must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("petset")
must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate")
@ -1205,6 +1208,7 @@ _kubectl_patch()
noun_aliases+=("ns")
noun_aliases+=("persistentvolumeclaims")
noun_aliases+=("persistentvolumes")
noun_aliases+=("petsets")
noun_aliases+=("po")
noun_aliases+=("pods")
noun_aliases+=("podsecuritypolicies")
@ -1299,6 +1303,7 @@ _kubectl_delete()
must_have_one_noun+=("node")
must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("petset")
must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate")
@ -1334,6 +1339,7 @@ _kubectl_delete()
noun_aliases+=("ns")
noun_aliases+=("persistentvolumeclaims")
noun_aliases+=("persistentvolumes")
noun_aliases+=("petsets")
noun_aliases+=("po")
noun_aliases+=("pods")
noun_aliases+=("podsecuritypolicies")
@ -1424,6 +1430,7 @@ _kubectl_edit()
must_have_one_noun+=("node")
must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("petset")
must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate")
@ -1459,6 +1466,7 @@ _kubectl_edit()
noun_aliases+=("ns")
noun_aliases+=("persistentvolumeclaims")
noun_aliases+=("persistentvolumes")
noun_aliases+=("petsets")
noun_aliases+=("po")
noun_aliases+=("pods")
noun_aliases+=("podsecuritypolicies")
@ -2708,6 +2716,7 @@ _kubectl_label()
must_have_one_noun+=("node")
must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("petset")
must_have_one_noun+=("pod")
must_have_one_noun+=("podsecuritypolicy")
must_have_one_noun+=("podtemplate")
@ -2743,6 +2752,7 @@ _kubectl_label()
noun_aliases+=("ns")
noun_aliases+=("persistentvolumeclaims")
noun_aliases+=("persistentvolumes")
noun_aliases+=("petsets")
noun_aliases+=("po")
noun_aliases+=("pods")
noun_aliases+=("podsecuritypolicies")

View File

@ -109,7 +109,7 @@ kube-apiserver
--service-node-port-range=: A port range to reserve for services with NodePort visibility. Example: '30000-32767'. Inclusive at both ends of the range.
--ssh-keyfile="": If non-empty, use secure SSH proxy to the nodes, using this user keyfile
--ssh-user="": If non-empty, use secure SSH proxy to the nodes, using this user name
--storage-versions="authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,componentconfig/v1alpha1,extensions/v1beta1,metrics/v1alpha1,v1": The per-group version to store resources in. Specified in the format "group1/version1,group2/version2,...". In the case where objects are moved from one group to the other, you may specify the format "group1=group2/v1beta1,group3/v1beta1,...". You only need to pass the groups you wish to change from the defaults. It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.
--storage-versions="apps/v1alpha1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,componentconfig/v1alpha1,extensions/v1beta1,metrics/v1alpha1,v1": The per-group version to store resources in. Specified in the format "group1/version1,group2/version2,...". In the case where objects are moved from one group to the other, you may specify the format "group1=group2/v1beta1,group3/v1beta1,...". You only need to pass the groups you wish to change from the defaults. It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.
--tls-cert-file="": File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.
--tls-private-key-file="": File containing x509 private key matching --tls-cert-file.
--token-auth-file="": If set, the file that will be used to secure the secure port of the API server via token authentication.
@ -117,7 +117,7 @@ kube-apiserver
--watch-cache-sizes=[]: List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. The individual override format: resource#size, where size is a number. It takes effect when watch-cache is enabled.
```
###### Auto generated by spf13/cobra on 12-Apr-2016
###### Auto generated by spf13/cobra on 16-Apr-2016
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->

View File

@ -66,7 +66,7 @@ APISERVER_PID=$!
kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: "
SWAGGER_API_PATH="http://127.0.0.1:${API_PORT}/swaggerapi/"
DEFAULT_GROUP_VERSIONS="v1 autoscaling/v1 batch/v1 extensions/v1beta1"
DEFAULT_GROUP_VERSIONS="v1 autoscaling/v1 batch/v1 extensions/v1beta1 apps/v1alpha1"
VERSIONS=${VERSIONS:-$DEFAULT_GROUP_VERSIONS}
kube::log::status "Updating " ${SWAGGER_ROOT_DIR}

View File

@ -183,7 +183,7 @@ kube::log::status "Starting kube-apiserver"
# Admission Controllers to invoke prior to persisting objects in cluster
ADMISSION_CONTROL="NamespaceLifecycle,LimitRanger,ResourceQuota"
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1" "${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,apps/v1alpha1,extensions/v1beta1" "${KUBE_OUTPUT_HOSTBIN}/kube-apiserver" \
--address="127.0.0.1" \
--public-address-override="127.0.0.1" \
--port="${API_PORT}" \
@ -529,7 +529,7 @@ runTests() {
kubectl create -f hack/testdata/pod-with-precision.json "${kube_flags[@]}"
# Post-condition: valid-pod POD is running
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'pod-with-precision:'
## Patch preserves precision
# Command
kubectl patch "${kube_flags[@]}" pod pod-with-precision -p='{"metadata":{"annotations":{"patchkey": "patchvalue"}}}'
@ -544,7 +544,7 @@ runTests() {
# Post-condition: pod-with-precision POD has annotation
kube::test::get_object_assert 'pod pod-with-precision' "{{${annotations_field}.annotatekey}}" 'annotatevalue'
# Cleanup
kubectl delete pod pod-with-precision "${kube_flags[@]}"
kubectl delete pod pod-with-precision "${kube_flags[@]}"
### Create valid-pod POD
# Pre-condition: no POD exists
@ -1792,6 +1792,6 @@ __EOF__
kube::test::clear_all
}
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1" runTests "v1"
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,apps/v1alpha1,extensions/v1beta1" runTests "v1"
kube::log::status "TEST PASSED"

View File

@ -58,7 +58,7 @@ KUBE_GOVERALLS_BIN=${KUBE_GOVERALLS_BIN:-}
# Lists of API Versions of each groups that should be tested, groups are
# separated by comma, lists are separated by semicolon. e.g.,
# "v1,compute/v1alpha1,experimental/v1alpha2;v1,compute/v2,experimental/v1alpha3"
KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,extensions/v1beta1,metrics/v1alpha1;v1,autoscaling/v1,batch/v1,extensions/v1beta1,metrics/v1alpha1"}
KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,extensions/v1beta1,metrics/v1alpha1;v1,autoscaling/v1,batch/v1,extensions/v1beta1,apps/v1alpha1,metrics/v1alpha1"}
# once we have multiple group supports
# Run tests with the standard (registry) and a custom etcd prefix
# (kubernetes.io/registry).
@ -326,7 +326,7 @@ for (( i=0, j=0; ; )); do
# KUBE_TEST_API sets the version of each group to be tested. KUBE_API_VERSIONS
# register the groups/versions as supported by k8s. So KUBE_API_VERSIONS
# needs to be the superset of KUBE_TEST_API.
KUBE_TEST_API="${apiVersion}" KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1,componentconfig/v1alpha1,metrics/v1alpha1,authorization.k8s.io/v1beta1" ETCD_PREFIX=${etcdPrefix} runTests "$@"
KUBE_TEST_API="${apiVersion}" KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1,componentconfig/v1alpha1,metrics/v1alpha1,apps/v1alpha1,authorization.k8s.io/v1beta1" ETCD_PREFIX=${etcdPrefix} runTests "$@"
i=${i}+1
j=${j}+1
if [[ i -eq ${apiVersionsCount} ]] && [[ j -eq ${etcdPrefixesCount} ]]; then

View File

@ -29,7 +29,7 @@ source "${KUBE_ROOT}/hack/lib/init.sh"
# "v1,compute/v1alpha1,experimental/v1alpha2;v1,compute/v2,experimental/v1alpha3"
# TODO: It's going to be:
# KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,extensions/v1beta1"}
KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,extensions/v1beta1;v1,autoscaling/v1,batch/v1,extensions/v1beta1"}
KUBE_TEST_API_VERSIONS=${KUBE_TEST_API_VERSIONS:-"v1,extensions/v1beta1;v1,autoscaling/v1,batch/v1,apps/v1alpha1,extensions/v1beta1"}
# Give integration tests longer to run
KUBE_TIMEOUT=${KUBE_TIMEOUT:--timeout 240s}
@ -52,11 +52,11 @@ runTests() {
KUBE_RACE="" \
KUBE_TIMEOUT="${KUBE_TIMEOUT}" \
KUBE_TEST_API_VERSIONS="$1" \
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1" \
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,apps/v1alpha1,extensions/v1beta1" \
"${KUBE_ROOT}/hack/test-go.sh" test/integration
kube::log::status "Running integration test scenario with watch cache on"
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1" KUBE_TEST_API_VERSIONS="$1" "${KUBE_OUTPUT_HOSTBIN}/integration" --v=${LOG_LEVEL} \
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,apps/v1alpha1,extensions/v1beta1" KUBE_TEST_API_VERSIONS="$1" "${KUBE_OUTPUT_HOSTBIN}/integration" --v=${LOG_LEVEL} \
--max-concurrency="${KUBE_INTEGRATION_TEST_MAX_CONCURRENCY}" --watch-cache=true
cleanup
@ -73,7 +73,7 @@ checkEtcdOnPath() {
checkEtcdOnPath
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,extensions/v1beta1" "${KUBE_ROOT}/hack/build-go.sh" "$@" cmd/integration
KUBE_API_VERSIONS="v1,autoscaling/v1,batch/v1,apps/v1alpha1,extensions/v1beta1" "${KUBE_ROOT}/hack/build-go.sh" "$@" cmd/integration
# Run cleanup to stop etcd on interrupt or other kill signal.
trap cleanup EXIT

View File

@ -56,7 +56,7 @@ user_flags="-u $(id -u)"
if [[ $(uname) == "Darwin" ]]; then
# mapping in a uid from OS X doesn't make any sense
user_flags=""
fi
fi
for ver in $VERSIONS; do
TMP_IN_HOST="${OUTPUT_TMP_IN_HOST}/${ver}"
@ -94,7 +94,7 @@ while read file; do
generated=$(echo "${generated}" | grep -v "Last updated" || :)
# By now, the contents should be normalized and stripped of any
# auto-managed content.
# auto-managed content.
if diff -Bw >/dev/null <(echo "${original}") <(echo "${generated}"); then
# actual contents same, overwrite generated with original.
cp "${OUTPUT}/${file}" "${OUTPUT_TMP}/${file}"

View File

@ -56,7 +56,7 @@ EOF
mv "$TMPFILE" "pkg/$(kube::util::group-version-to-pkg-path "${group_version}")/types_swagger_doc_generated.go"
}
GROUP_VERSIONS=(unversioned v1 authorization/v1beta1 autoscaling/v1 batch/v1 extensions/v1beta1)
GROUP_VERSIONS=(unversioned v1 authorization/v1beta1 autoscaling/v1 batch/v1 extensions/v1beta1 apps/v1alpha1)
# To avoid compile errors, remove the currently existing files.
for group_version in "${GROUP_VERSIONS[@]}"; do
rm -f "pkg/$(kube::util::group-version-to-pkg-path "${group_version}")/types_swagger_doc_generated.go"

View File

@ -27,12 +27,14 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
_ "k8s.io/kubernetes/pkg/api/install"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/componentconfig/install"
@ -46,6 +48,7 @@ var (
Autoscaling TestGroup
Batch TestGroup
Extensions TestGroup
Apps TestGroup
)
type TestGroup struct {
@ -122,10 +125,18 @@ func init() {
internalTypes: api.Scheme.KnownTypes(extensions.SchemeGroupVersion),
}
}
if _, ok := Groups[apps.GroupName]; !ok {
Groups[apps.GroupName] = TestGroup{
externalGroupVersion: unversioned.GroupVersion{Group: apps.GroupName, Version: registered.GroupOrDie(apps.GroupName).GroupVersion.Version},
internalGroupVersion: extensions.SchemeGroupVersion,
internalTypes: api.Scheme.KnownTypes(extensions.SchemeGroupVersion),
}
}
Default = Groups[api.GroupName]
Autoscaling = Groups[autoscaling.GroupName]
Batch = Groups[batch.GroupName]
Apps = Groups[apps.GroupName]
Extensions = Groups[extensions.GroupName]
}

View File

@ -0,0 +1,117 @@
// +build !ignore_autogenerated
/*
Copyright 2016 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.
*/
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
package apps
import (
api "k8s.io/kubernetes/pkg/api"
unversioned "k8s.io/kubernetes/pkg/api/unversioned"
conversion "k8s.io/kubernetes/pkg/conversion"
)
func init() {
if err := api.Scheme.AddGeneratedDeepCopyFuncs(
DeepCopy_apps_PetSet,
DeepCopy_apps_PetSetList,
DeepCopy_apps_PetSetSpec,
DeepCopy_apps_PetSetStatus,
); err != nil {
// if one of the deep copy functions is malformed, detect it immediately.
panic(err)
}
}
func DeepCopy_apps_PetSet(in PetSet, out *PetSet, c *conversion.Cloner) error {
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
return err
}
if err := DeepCopy_apps_PetSetSpec(in.Spec, &out.Spec, c); err != nil {
return err
}
if err := DeepCopy_apps_PetSetStatus(in.Status, &out.Status, c); err != nil {
return err
}
return nil
}
func DeepCopy_apps_PetSetList(in PetSetList, out *PetSetList, c *conversion.Cloner) error {
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
return err
}
if in.Items != nil {
in, out := in.Items, &out.Items
*out = make([]PetSet, len(in))
for i := range in {
if err := DeepCopy_apps_PetSet(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func DeepCopy_apps_PetSetSpec(in PetSetSpec, out *PetSetSpec, c *conversion.Cloner) error {
out.Replicas = in.Replicas
if in.Selector != nil {
in, out := in.Selector, &out.Selector
*out = new(unversioned.LabelSelector)
if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil {
return err
}
} else {
out.Selector = nil
}
if err := api.DeepCopy_api_PodTemplateSpec(in.Template, &out.Template, c); err != nil {
return err
}
if in.VolumeClaimTemplates != nil {
in, out := in.VolumeClaimTemplates, &out.VolumeClaimTemplates
*out = make([]api.PersistentVolumeClaim, len(in))
for i := range in {
if err := api.DeepCopy_api_PersistentVolumeClaim(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.VolumeClaimTemplates = nil
}
out.ServiceName = in.ServiceName
return nil
}
func DeepCopy_apps_PetSetStatus(in PetSetStatus, out *PetSetStatus, c *conversion.Cloner) error {
if in.ObservedGeneration != nil {
in, out := in.ObservedGeneration, &out.ObservedGeneration
*out = new(int64)
**out = *in
} else {
out.ObservedGeneration = nil
}
out.Replicas = in.Replicas
return nil
}

View File

@ -0,0 +1,125 @@
/*
Copyright 2016 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 install installs the apps API group, making it available as
// an option to all of the API encoding/decoding machinery.
package install
import (
"fmt"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/v1alpha1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets"
)
const importPrefix = "k8s.io/kubernetes/pkg/apis/apps"
var accessor = meta.NewAccessor()
// availableVersions lists all known external versions for this group from most preferred to least preferred
var availableVersions = []unversioned.GroupVersion{v1alpha1.SchemeGroupVersion}
func init() {
registered.RegisterVersions(availableVersions)
externalVersions := []unversioned.GroupVersion{}
for _, v := range availableVersions {
if registered.IsAllowedVersion(v) {
externalVersions = append(externalVersions, v)
}
}
if len(externalVersions) == 0 {
glog.V(4).Infof("No version is registered for group %v", apps.GroupName)
return
}
if err := registered.EnableVersions(externalVersions...); err != nil {
glog.V(4).Infof("%v", err)
return
}
if err := enableVersions(externalVersions); err != nil {
glog.V(4).Infof("%v", err)
return
}
}
func enableVersions(externalVersions []unversioned.GroupVersion) error {
addVersionsToScheme(externalVersions...)
preferredExternalVersion := externalVersions[0]
groupMeta := apimachinery.GroupMeta{
GroupVersion: preferredExternalVersion,
GroupVersions: externalVersions,
RESTMapper: newRESTMapper(externalVersions),
SelfLinker: runtime.SelfLinker(accessor),
InterfacesFor: interfacesFor,
}
if err := registered.RegisterGroup(groupMeta); err != nil {
return err
}
api.RegisterRESTMapper(groupMeta.RESTMapper)
return nil
}
func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper {
// the list of kinds that are scoped at the root of the api hierarchy
// if a kind is not enumerated here, it is assumed to have a namespace scope
rootScoped := sets.NewString()
ignoredKinds := sets.NewString()
return api.NewDefaultRESTMapper(externalVersions, interfacesFor, importPrefix, ignoredKinds, rootScoped)
}
// interfacesFor returns the default Codec and ResourceVersioner for a given version
// string, or an error if the version is not known.
func interfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, error) {
switch version {
case v1alpha1.SchemeGroupVersion:
return &meta.VersionInterfaces{
ObjectConvertor: api.Scheme,
MetadataAccessor: accessor,
}, nil
default:
g, _ := registered.Group(apps.GroupName)
return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, g.GroupVersions)
}
}
func addVersionsToScheme(externalVersions ...unversioned.GroupVersion) {
// add the internal version to Scheme
apps.AddToScheme(api.Scheme)
// add the enabled external versions to Scheme
for _, v := range externalVersions {
if !registered.IsEnabledVersion(v) {
glog.Errorf("Version %s is not enabled, so it will not be added to the Scheme.", v)
continue
}
switch v {
case v1alpha1.SchemeGroupVersion:
v1alpha1.AddToScheme(api.Scheme)
}
}
}

57
pkg/apis/apps/register.go Normal file
View File

@ -0,0 +1,57 @@
/*
Copyright 2016 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 apps
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
func AddToScheme(scheme *runtime.Scheme) {
// Add the API to Scheme.
addKnownTypes(scheme)
}
// GroupName is the group name use in this package
const GroupName = "apps"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) unversioned.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
func Resource(resource string) unversioned.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) {
// TODO this will get cleaned up with the scheme types are fixed
scheme.AddKnownTypes(SchemeGroupVersion,
&PetSet{},
&PetSetList{},
&api.ListOptions{},
)
}
func (obj *PetSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PetSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }

File diff suppressed because it is too large Load Diff

94
pkg/apis/apps/types.go Normal file
View File

@ -0,0 +1,94 @@
/*
Copyright 2016 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 apps
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
)
// PetSet represents a set of pods with consistent identities.
// Identities are defined as:
// - Network: A single stable DNS and hostname.
// - Storage: As many VolumeClaims as requested.
// The PetSet guarantees that a given network identity will always
// map to the same storage identity. PetSet is currently in alpha and
// and subject to change without notice.
type PetSet struct {
unversioned.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata,omitempty"`
// Spec defines the desired identities of pets in this set.
Spec PetSetSpec `json:"spec,omitempty"`
// Status is the current status of Pets in this PetSet. This data
// may be out of date by some window of time.
Status PetSetStatus `json:"status,omitempty"`
}
// A PetSetSpec is the specification of a PetSet.
type PetSetSpec struct {
// Replicas is the desired number of replicas of the given Template.
// These are replicas in the sense that they are instantiations of the
// same Template, but individual replicas also have a consistent identity.
// If unspecified, defaults to 1.
// TODO: Consider a rename of this field.
Replicas int `json:"replicas,omitempty"`
// Selector is a label query over pods that should match the replica count.
// If empty, defaulted to labels on the pod template.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector *unversioned.LabelSelector `json:"selector,omitempty"`
// Template is the object that describes the pod that will be created if
// insufficient replicas are detected. Each pod stamped out by the PetSet
// will fulfill this Template, but have a unique identity from the rest
// of the PetSet.
Template api.PodTemplateSpec `json:"template"`
// VolumeClaimTemplates is a list of claims that pets are allowed to reference.
// The PetSet controller is responsible for mapping network identities to
// claims in a way that maintains the identity of a pet. Every claim in
// this list must have at least one matching (by name) volumeMount in one
// container in the template. A claim in this list takes precedence over
// any volumes in the template, with the same name.
// TODO: Define the behavior if a claim already exists with the same name.
VolumeClaimTemplates []api.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty"`
// ServiceName is the name of the service that governs this PetSet.
// This service must exist before the PetSet, and is responsible for
// the network identity of the set. Pets get DNS/hostnames that follow the
// pattern: pet-specific-string.serviceName.default.svc.cluster.local
// where "pet-specific-string" is managed by the PetSet controller.
ServiceName string `json:"serviceName"`
}
// PetSetStatus represents the current state of a PetSet.
type PetSetStatus struct {
// most recent generation observed by this autoscaler.
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
// Replicas is the number of actual replicas.
Replicas int `json:"replicas"`
}
// PetSetList is a collection of PetSets.
type PetSetList struct {
unversioned.TypeMeta `json:",inline"`
unversioned.ListMeta `json:"metadata,omitempty"`
Items []PetSet `json:"items"`
}

View File

@ -0,0 +1,125 @@
/*
Copyright 2016 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 v1alpha1
import (
"fmt"
"reflect"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
v1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
)
func addConversionFuncs(scheme *runtime.Scheme) {
// Add non-generated conversion functions to handle the *int32 -> int
// conversion. A pointer is useful in the versioned type so we can default
// it, but a plain int32 is more convenient in the internal type. These
// functions are the same as the autogenerated ones in every other way.
err := scheme.AddConversionFuncs(
Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec,
Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec,
)
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
err = api.Scheme.AddFieldLabelConversionFunc("apps/v1alpha1", "PetSet",
func(label, value string) (string, string, error) {
switch label {
case "metadata.name", "metadata.namespace", "status.successful":
return label, value, nil
default:
return "", "", fmt.Errorf("field label not supported: %s", label)
}
})
if err != nil {
// If one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}
func Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec(in *PetSetSpec, out *apps.PetSetSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*PetSetSpec))(in)
}
if in.Replicas != nil {
out.Replicas = int(*in.Replicas)
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(unversioned.LabelSelector)
if err := s.Convert(*in, *out, 0); err != nil {
return err
}
} else {
out.Selector = nil
}
if err := s.Convert(&in.Template, &out.Template, 0); err != nil {
return err
}
if in.VolumeClaimTemplates != nil {
in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates
*out = make([]api.PersistentVolumeClaim, len(*in))
for i := range *in {
if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil {
return err
}
}
} else {
out.VolumeClaimTemplates = nil
}
out.ServiceName = in.ServiceName
return nil
}
func Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec(in *apps.PetSetSpec, out *PetSetSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*apps.PetSetSpec))(in)
}
out.Replicas = new(int32)
*out.Replicas = int32(in.Replicas)
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
*out = new(unversioned.LabelSelector)
if err := s.Convert(*in, *out, 0); err != nil {
return err
}
} else {
out.Selector = nil
}
if err := s.Convert(&in.Template, &out.Template, 0); err != nil {
return err
}
if in.VolumeClaimTemplates != nil {
in, out := &in.VolumeClaimTemplates, &out.VolumeClaimTemplates
*out = make([]v1.PersistentVolumeClaim, len(*in))
for i := range *in {
if err := s.Convert(&(*in)[i], &(*out)[i], 0); err != nil {
return err
}
}
} else {
out.VolumeClaimTemplates = nil
}
out.ServiceName = in.ServiceName
return nil
}

View File

@ -0,0 +1,186 @@
// +build !ignore_autogenerated
/*
Copyright 2016 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.
*/
// This file was autogenerated by conversion-gen. Do not edit it manually!
package v1alpha1
import (
api "k8s.io/kubernetes/pkg/api"
apps "k8s.io/kubernetes/pkg/apis/apps"
conversion "k8s.io/kubernetes/pkg/conversion"
reflect "reflect"
)
func init() {
if err := api.Scheme.AddGeneratedConversionFuncs(
Convert_v1alpha1_PetSet_To_apps_PetSet,
Convert_apps_PetSet_To_v1alpha1_PetSet,
Convert_v1alpha1_PetSetList_To_apps_PetSetList,
Convert_apps_PetSetList_To_v1alpha1_PetSetList,
Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec,
Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec,
Convert_v1alpha1_PetSetStatus_To_apps_PetSetStatus,
Convert_apps_PetSetStatus_To_v1alpha1_PetSetStatus,
); err != nil {
// if one of the conversion functions is malformed, detect it immediately.
panic(err)
}
}
func autoConvert_v1alpha1_PetSet_To_apps_PetSet(in *PetSet, out *apps.PetSet, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*PetSet))(in)
}
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
// TODO: Inefficient conversion - can we improve it?
if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil {
return err
}
if err := Convert_v1alpha1_PetSetSpec_To_apps_PetSetSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
if err := Convert_v1alpha1_PetSetStatus_To_apps_PetSetStatus(&in.Status, &out.Status, s); err != nil {
return err
}
return nil
}
func Convert_v1alpha1_PetSet_To_apps_PetSet(in *PetSet, out *apps.PetSet, s conversion.Scope) error {
return autoConvert_v1alpha1_PetSet_To_apps_PetSet(in, out, s)
}
func autoConvert_apps_PetSet_To_v1alpha1_PetSet(in *apps.PetSet, out *PetSet, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*apps.PetSet))(in)
}
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
// TODO: Inefficient conversion - can we improve it?
if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil {
return err
}
if err := Convert_apps_PetSetSpec_To_v1alpha1_PetSetSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
if err := Convert_apps_PetSetStatus_To_v1alpha1_PetSetStatus(&in.Status, &out.Status, s); err != nil {
return err
}
return nil
}
func Convert_apps_PetSet_To_v1alpha1_PetSet(in *apps.PetSet, out *PetSet, s conversion.Scope) error {
return autoConvert_apps_PetSet_To_v1alpha1_PetSet(in, out, s)
}
func autoConvert_v1alpha1_PetSetList_To_apps_PetSetList(in *PetSetList, out *apps.PetSetList, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*PetSetList))(in)
}
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil {
return err
}
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]apps.PetSet, len(*in))
for i := range *in {
if err := Convert_v1alpha1_PetSet_To_apps_PetSet(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func Convert_v1alpha1_PetSetList_To_apps_PetSetList(in *PetSetList, out *apps.PetSetList, s conversion.Scope) error {
return autoConvert_v1alpha1_PetSetList_To_apps_PetSetList(in, out, s)
}
func autoConvert_apps_PetSetList_To_v1alpha1_PetSetList(in *apps.PetSetList, out *PetSetList, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*apps.PetSetList))(in)
}
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil {
return err
}
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PetSet, len(*in))
for i := range *in {
if err := Convert_apps_PetSet_To_v1alpha1_PetSet(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func Convert_apps_PetSetList_To_v1alpha1_PetSetList(in *apps.PetSetList, out *PetSetList, s conversion.Scope) error {
return autoConvert_apps_PetSetList_To_v1alpha1_PetSetList(in, out, s)
}
func autoConvert_v1alpha1_PetSetStatus_To_apps_PetSetStatus(in *PetSetStatus, out *apps.PetSetStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*PetSetStatus))(in)
}
if in.ObservedGeneration != nil {
in, out := &in.ObservedGeneration, &out.ObservedGeneration
*out = new(int64)
**out = **in
} else {
out.ObservedGeneration = nil
}
out.Replicas = int(in.Replicas)
return nil
}
func Convert_v1alpha1_PetSetStatus_To_apps_PetSetStatus(in *PetSetStatus, out *apps.PetSetStatus, s conversion.Scope) error {
return autoConvert_v1alpha1_PetSetStatus_To_apps_PetSetStatus(in, out, s)
}
func autoConvert_apps_PetSetStatus_To_v1alpha1_PetSetStatus(in *apps.PetSetStatus, out *PetSetStatus, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*apps.PetSetStatus))(in)
}
if in.ObservedGeneration != nil {
in, out := &in.ObservedGeneration, &out.ObservedGeneration
*out = new(int64)
**out = **in
} else {
out.ObservedGeneration = nil
}
out.Replicas = int32(in.Replicas)
return nil
}
func Convert_apps_PetSetStatus_To_v1alpha1_PetSetStatus(in *apps.PetSetStatus, out *PetSetStatus, s conversion.Scope) error {
return autoConvert_apps_PetSetStatus_To_v1alpha1_PetSetStatus(in, out, s)
}

View File

@ -0,0 +1,124 @@
// +build !ignore_autogenerated
/*
Copyright 2016 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.
*/
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
package v1alpha1
import (
api "k8s.io/kubernetes/pkg/api"
unversioned "k8s.io/kubernetes/pkg/api/unversioned"
v1 "k8s.io/kubernetes/pkg/api/v1"
conversion "k8s.io/kubernetes/pkg/conversion"
)
func init() {
if err := api.Scheme.AddGeneratedDeepCopyFuncs(
DeepCopy_v1alpha1_PetSet,
DeepCopy_v1alpha1_PetSetList,
DeepCopy_v1alpha1_PetSetSpec,
DeepCopy_v1alpha1_PetSetStatus,
); err != nil {
// if one of the deep copy functions is malformed, detect it immediately.
panic(err)
}
}
func DeepCopy_v1alpha1_PetSet(in PetSet, out *PetSet, c *conversion.Cloner) error {
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
return err
}
if err := DeepCopy_v1alpha1_PetSetSpec(in.Spec, &out.Spec, c); err != nil {
return err
}
if err := DeepCopy_v1alpha1_PetSetStatus(in.Status, &out.Status, c); err != nil {
return err
}
return nil
}
func DeepCopy_v1alpha1_PetSetList(in PetSetList, out *PetSetList, c *conversion.Cloner) error {
if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
return err
}
if in.Items != nil {
in, out := in.Items, &out.Items
*out = make([]PetSet, len(in))
for i := range in {
if err := DeepCopy_v1alpha1_PetSet(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
func DeepCopy_v1alpha1_PetSetSpec(in PetSetSpec, out *PetSetSpec, c *conversion.Cloner) error {
if in.Replicas != nil {
in, out := in.Replicas, &out.Replicas
*out = new(int32)
**out = *in
} else {
out.Replicas = nil
}
if in.Selector != nil {
in, out := in.Selector, &out.Selector
*out = new(unversioned.LabelSelector)
if err := unversioned.DeepCopy_unversioned_LabelSelector(*in, *out, c); err != nil {
return err
}
} else {
out.Selector = nil
}
if err := v1.DeepCopy_v1_PodTemplateSpec(in.Template, &out.Template, c); err != nil {
return err
}
if in.VolumeClaimTemplates != nil {
in, out := in.VolumeClaimTemplates, &out.VolumeClaimTemplates
*out = make([]v1.PersistentVolumeClaim, len(in))
for i := range in {
if err := v1.DeepCopy_v1_PersistentVolumeClaim(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.VolumeClaimTemplates = nil
}
out.ServiceName = in.ServiceName
return nil
}
func DeepCopy_v1alpha1_PetSetStatus(in PetSetStatus, out *PetSetStatus, c *conversion.Cloner) error {
if in.ObservedGeneration != nil {
in, out := in.ObservedGeneration, &out.ObservedGeneration
*out = new(int64)
**out = *in
} else {
out.ObservedGeneration = nil
}
out.Replicas = in.Replicas
return nil
}

View File

@ -0,0 +1,44 @@
/*
Copyright 2016 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 v1alpha1
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
func addDefaultingFuncs(scheme *runtime.Scheme) {
scheme.AddDefaultingFuncs(
func(obj *PetSet) {
labels := obj.Spec.Template.Labels
if labels != nil {
if obj.Spec.Selector == nil {
obj.Spec.Selector = &unversioned.LabelSelector{
MatchLabels: labels,
}
}
if len(obj.Labels) == 0 {
obj.Labels = labels
}
}
if obj.Spec.Replicas == nil {
obj.Spec.Replicas = new(int32)
*obj.Spec.Replicas = 1
}
},
)
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2016 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.
*/
// +genconversion=true
package v1alpha1

View File

@ -0,0 +1,49 @@
/*
Copyright 2016 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 v1alpha1
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/runtime"
versionedwatch "k8s.io/kubernetes/pkg/watch/versioned"
)
// GroupName is the group name use in this package
const GroupName = "apps"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = unversioned.GroupVersion{Group: GroupName, Version: "v1alpha1"}
func AddToScheme(scheme *runtime.Scheme) {
addKnownTypes(scheme)
addDefaultingFuncs(scheme)
addConversionFuncs(scheme)
}
// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) {
scheme.AddKnownTypes(SchemeGroupVersion,
&PetSet{},
&PetSetList{},
&v1.ListOptions{},
)
versionedwatch.AddToGroupVersion(scheme, SchemeGroupVersion)
}
func (obj *PetSet) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *PetSetList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,94 @@
/*
Copyright 2016 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 v1alpha1
import (
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
)
// PetSet represents a set of pods with consistent identities.
// Identities are defined as:
// - Network: A single stable DNS and hostname.
// - Storage: As many VolumeClaims as requested.
// The PetSet guarantees that a given network identity will always
// map to the same storage identity. PetSet is currently in alpha
// and subject to change without notice.
type PetSet struct {
unversioned.TypeMeta `json:",inline"`
v1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec defines the desired identities of pets in this set.
Spec PetSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// Status is the current status of Pets in this PetSet. This data
// may be out of date by some window of time.
Status PetSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
// A PetSetSpec is the specification of a PetSet.
type PetSetSpec struct {
// Replicas is the desired number of replicas of the given Template.
// These are replicas in the sense that they are instantiations of the
// same Template, but individual replicas also have a consistent identity.
// If unspecified, defaults to 1.
// TODO: Consider a rename of this field.
Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"`
// Selector is a label query over pods that should match the replica count.
// If empty, defaulted to labels on the pod template.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors
Selector *unversioned.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
// Template is the object that describes the pod that will be created if
// insufficient replicas are detected. Each pod stamped out by the PetSet
// will fulfill this Template, but have a unique identity from the rest
// of the PetSet.
Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"`
// VolumeClaimTemplates is a list of claims that pets are allowed to reference.
// The PetSet controller is responsible for mapping network identities to
// claims in a way that maintains the identity of a pet. Every claim in
// this list must have at least one matching (by name) volumeMount in one
// container in the template. A claim in this list takes precedence over
// any volumes in the template, with the same name.
// TODO: Define the behavior if a claim already exists with the same name.
VolumeClaimTemplates []v1.PersistentVolumeClaim `json:"volumeClaimTemplates,omitempty" protobuf:"bytes,4,rep,name=volumeClaimTemplates"`
// ServiceName is the name of the service that governs this PetSet.
// This service must exist before the PetSet, and is responsible for
// the network identity of the set. Pets get DNS/hostnames that follow the
// pattern: pet-specific-string.serviceName.default.svc.cluster.local
// where "pet-specific-string" is managed by the PetSet controller.
ServiceName string `json:"serviceName" protobuf:"bytes,5,opt,name=serviceName"`
}
// PetSetStatus represents the current state of a PetSet.
type PetSetStatus struct {
// most recent generation observed by this autoscaler.
ObservedGeneration *int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"`
// Replicas is the number of actual replicas.
Replicas int32 `json:"replicas" protobuf:"varint,2,opt,name=replicas"`
}
// PetSetList is a collection of PetSets.
type PetSetList struct {
unversioned.TypeMeta `json:",inline"`
unversioned.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Items []PetSet `json:"items" protobuf:"bytes,2,rep,name=items"`
}

View File

@ -0,0 +1,71 @@
/*
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 v1alpha1
// This file contains a collection of methods that can be used from go-restful to
// generate Swagger API documentation for its models. Please read this PR for more
// information on the implementation: https://github.com/emicklei/go-restful/pull/215
//
// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if
// they are on one line! For multiple line or blocks that you want to ignore use ---.
// Any context after a --- is ignored.
//
// Those methods can be generated by using hack/update-generated-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE
var map_PetSet = map[string]string{
"": "PetSet represents a set of pods with consistent identities. Identities are defined as:\n - Network: A single stable DNS and hostname.\n - Storage: As many VolumeClaims as requested.\nThe PetSet guarantees that a given network identity will always map to the same storage identity. PetSet is currently in alpha and subject to change without notice.",
"spec": "Spec defines the desired identities of pets in this set.",
"status": "Status is the current status of Pets in this PetSet. This data may be out of date by some window of time.",
}
func (PetSet) SwaggerDoc() map[string]string {
return map_PetSet
}
var map_PetSetList = map[string]string{
"": "PetSetList is a collection of PetSets.",
}
func (PetSetList) SwaggerDoc() map[string]string {
return map_PetSetList
}
var map_PetSetSpec = map[string]string{
"": "A PetSetSpec is the specification of a PetSet.",
"replicas": "Replicas is the desired number of replicas of the given Template. These are replicas in the sense that they are instantiations of the same Template, but individual replicas also have a consistent identity. If unspecified, defaults to 1.",
"selector": "Selector is a label query over pods that should match the replica count. If empty, defaulted to labels on the pod template. More info: http://releases.k8s.io/HEAD/docs/user-guide/labels.md#label-selectors",
"template": "Template is the object that describes the pod that will be created if insufficient replicas are detected. Each pod stamped out by the PetSet will fulfill this Template, but have a unique identity from the rest of the PetSet.",
"volumeClaimTemplates": "VolumeClaimTemplates is a list of claims that pets are allowed to reference. The PetSet controller is responsible for mapping network identities to claims in a way that maintains the identity of a pet. Every claim in this list must have at least one matching (by name) volumeMount in one container in the template. A claim in this list takes precedence over any volumes in the template, with the same name.",
"serviceName": "ServiceName is the name of the service that governs this PetSet. This service must exist before the PetSet, and is responsible for the network identity of the set. Pets get DNS/hostnames that follow the pattern: pet-specific-string.serviceName.default.svc.cluster.local where \"pet-specific-string\" is managed by the PetSet controller.",
}
func (PetSetSpec) SwaggerDoc() map[string]string {
return map_PetSetSpec
}
var map_PetSetStatus = map[string]string{
"": "PetSetStatus represents the current state of a PetSet.",
"observedGeneration": "most recent generation observed by this autoscaler.",
"replicas": "Replicas is the number of actual replicas.",
}
func (PetSetStatus) SwaggerDoc() map[string]string {
return map_PetSetStatus
}
// AUTO-GENERATED FUNCTIONS END HERE

View File

@ -0,0 +1,110 @@
/*
Copyright 2016 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 validation
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
unversionedvalidation "k8s.io/kubernetes/pkg/api/unversioned/validation"
apivalidation "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/validation/field"
)
// ValidatePetSetName can be used to check whether the given PetSet
// name is valid.
// Prefix indicates this name will be used as part of generation, in which case
// trailing dashes are allowed.
func ValidatePetSetName(name string, prefix bool) (bool, string) {
return apivalidation.NameIsDNSSubdomain(name, prefix)
}
// Validates the given template and ensures that it is in accordance with the desired selector.
func ValidatePodTemplateSpecForPetSet(template *api.PodTemplateSpec, selector labels.Selector, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if template == nil {
allErrs = append(allErrs, field.Required(fldPath, ""))
} else {
if !selector.Empty() {
// Verify that the PetSet selector matches the labels in template.
labels := labels.Set(template.Labels)
if !selector.Matches(labels) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("metadata", "labels"), template.Labels, "`selector` does not match template `labels`"))
}
}
// TODO: Add validation for PodSpec, currently this will check volumes, which we know will
// fail. We should really check that the union of the given volumes and volumeClaims match
// volume mounts in the containers.
// allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(template, fldPath)...)
allErrs = append(allErrs, apivalidation.ValidateLabels(template.Labels, fldPath.Child("labels"))...)
allErrs = append(allErrs, apivalidation.ValidateAnnotations(template.Annotations, fldPath.Child("annotations"))...)
allErrs = append(allErrs, apivalidation.ValidatePodSpecificAnnotations(template.Annotations, fldPath.Child("annotations"))...)
}
return allErrs
}
// ValidatePetSetSpec tests if required fields in the PetSet spec are set.
func ValidatePetSetSpec(spec *apps.PetSetSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...)
if spec.Selector == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
} else {
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for petset."))
}
}
selector, err := unversioned.LabelSelectorAsSelector(spec.Selector)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, ""))
} else {
allErrs = append(allErrs, ValidatePodTemplateSpecForPetSet(&spec.Template, selector, fldPath.Child("template"))...)
}
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
}
return allErrs
}
// ValidatePetSet validates a PetSet.
func ValidatePetSet(petSet *apps.PetSet) field.ErrorList {
allErrs := apivalidation.ValidateObjectMeta(&petSet.ObjectMeta, true, ValidatePetSetName, field.NewPath("metadata"))
allErrs = append(allErrs, ValidatePetSetSpec(&petSet.Spec, field.NewPath("spec"))...)
return allErrs
}
// ValidatePetSetUpdate tests if required fields in the PetSet are set.
func ValidatePetSetUpdate(petSet, oldPetSet *apps.PetSet) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&petSet.ObjectMeta, &oldPetSet.ObjectMeta, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePetSetSpec(&petSet.Spec, field.NewPath("spec"))...)
return allErrs
}
// ValidatePetSetStatusUpdate tests if required fields in the PetSet are set.
func ValidatePetSetStatusUpdate(petSet, oldPetSet *apps.PetSet) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&petSet.ObjectMeta, &oldPetSet.ObjectMeta, field.NewPath("metadata"))...)
// TODO: Validate status.
return allErrs
}

View File

@ -0,0 +1,379 @@
/*
Copyright 2016 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 validation
import (
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/apps"
)
func TestValidatePetSet(t *testing.T) {
validLabels := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
ObjectMeta: api.ObjectMeta{
Labels: invalidLabels,
},
},
}
successCases := []apps.PetSet{
{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
}
for _, successCase := range successCases {
if errs := ValidatePetSet(&successCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := map[string]apps.PetSet{
"zero-length ID": {
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
"missing-namespace": {
ObjectMeta: api.ObjectMeta{Name: "abc-123"},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
"empty selector": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Template: validPodTemplate.Template,
},
},
"selector_doesnt_match": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
Template: validPodTemplate.Template,
},
},
"invalid manifest": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
},
},
"negative_replicas": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: -1,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
},
},
"invalid_label": {
ObjectMeta: api.ObjectMeta{
Name: "abc-123",
Namespace: api.NamespaceDefault,
Labels: map[string]string{
"NoUppercaseOrSpecialCharsLike=Equals": "bar",
},
},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
"invalid_label 2": {
ObjectMeta: api.ObjectMeta{
Name: "abc-123",
Namespace: api.NamespaceDefault,
Labels: map[string]string{
"NoUppercaseOrSpecialCharsLike=Equals": "bar",
},
},
Spec: apps.PetSetSpec{
Template: invalidPodTemplate.Template,
},
},
"invalid_annotation": {
ObjectMeta: api.ObjectMeta{
Name: "abc-123",
Namespace: api.NamespaceDefault,
Annotations: map[string]string{
"NoUppercaseOrSpecialCharsLike=Equals": "bar",
},
},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
"invalid restart policy 1": {
ObjectMeta: api.ObjectMeta{
Name: "abc-123",
Namespace: api.NamespaceDefault,
},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
ObjectMeta: api.ObjectMeta{
Labels: validLabels,
},
},
},
},
"invalid restart policy 2": {
ObjectMeta: api.ObjectMeta{
Name: "abc-123",
Namespace: api.NamespaceDefault,
},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyNever,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
ObjectMeta: api.ObjectMeta{
Labels: validLabels,
},
},
},
},
}
for k, v := range errorCases {
errs := ValidatePetSet(&v)
if len(errs) == 0 {
t.Errorf("expected failure for %s", k)
}
for i := range errs {
field := errs[i].Field
if !strings.HasPrefix(field, "spec.template.") &&
field != "metadata.name" &&
field != "metadata.namespace" &&
field != "spec.selector" &&
field != "spec.template" &&
field != "GCEPersistentDisk.ReadOnly" &&
field != "spec.replicas" &&
field != "spec.template.labels" &&
field != "metadata.annotations" &&
field != "metadata.labels" &&
field != "status.replicas" {
t.Errorf("%s: missing prefix for: %v", k, errs[i])
}
}
}
}
func TestValidatePetSetUpdate(t *testing.T) {
validLabels := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
readWriteVolumePodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validLabels,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
Volumes: []api.Volume{{Name: "gcepd", VolumeSource: api.VolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{PDName: "my-PD", FSType: "ext4", Partition: 1, ReadOnly: false}}}},
},
},
}
invalidLabels := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
ObjectMeta: api.ObjectMeta{
Labels: invalidLabels,
},
},
}
type psUpdateTest struct {
old apps.PetSet
update apps.PetSet
}
successCases := []psUpdateTest{
{
old: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
update: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: 3,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
},
{
old: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
update: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: 1,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: readWriteVolumePodTemplate.Template,
},
},
},
}
for _, successCase := range successCases {
successCase.old.ObjectMeta.ResourceVersion = "1"
successCase.update.ObjectMeta.ResourceVersion = "1"
if errs := ValidatePetSetUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := map[string]psUpdateTest{
"more than one read/write": {
old: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
update: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: 2,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: readWriteVolumePodTemplate.Template,
},
},
},
"invalid selector": {
old: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
update: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: 2,
Selector: &unversioned.LabelSelector{MatchLabels: invalidLabels},
Template: validPodTemplate.Template,
},
},
},
"invalid pod": {
old: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
update: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: 2,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: invalidPodTemplate.Template,
},
},
},
"negative replicas": {
old: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
update: apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Replicas: -1,
Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
},
},
},
}
for testName, errorCase := range errorCases {
if errs := ValidatePetSetUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
t.Errorf("expected failure: %s", testName)
}
}
}

View File

@ -22,6 +22,7 @@ import (
_ "k8s.io/kubernetes/pkg/api/install"
"k8s.io/kubernetes/pkg/apimachinery/registered"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"

View File

@ -0,0 +1,82 @@
/*
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 unversioned
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/client/restclient"
)
type AppsInterface interface {
PetSetNamespacer
}
// AppsClient is used to interact with Kubernetes batch features.
type AppsClient struct {
*restclient.RESTClient
}
func (c *AppsClient) PetSet(namespace string) PetSetInterface {
return newPetSet(c, namespace)
}
func NewApps(c *restclient.Config) (*AppsClient, error) {
config := *c
if err := setAppsDefaults(&config); err != nil {
return nil, err
}
client, err := restclient.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &AppsClient{client}, nil
}
func NewAppsOrDie(c *restclient.Config) *AppsClient {
client, err := NewApps(c)
if err != nil {
panic(err)
}
return client
}
func setAppsDefaults(config *restclient.Config) error {
g, err := registered.Group(apps.GroupName)
if err != nil {
return err
}
config.APIPath = defaultAPIPath
if config.UserAgent == "" {
config.UserAgent = restclient.DefaultKubernetesUserAgent()
}
// TODO: Unconditionally set the config.Version, until we fix the config.
//if config.Version == "" {
copyGroupVersion := g.GroupVersion
config.GroupVersion = &copyGroupVersion
//}
config.Codec = api.Codecs.LegacyCodec(*config.GroupVersion)
if config.QPS == 0 {
config.QPS = 5
}
if config.Burst == 0 {
config.Burst = 10
}
return nil
}

View File

@ -119,6 +119,7 @@ type Client struct {
*AutoscalingClient
*BatchClient
*ExtensionsClient
*AppsClient
*discovery.DiscoveryClient
}
@ -156,6 +157,10 @@ func (c *Client) Extensions() ExtensionsInterface {
return c.ExtensionsClient
}
func (c *Client) Apps() AppsInterface {
return c.AppsClient
}
func (c *Client) Discovery() discovery.DiscoveryInterface {
return c.DiscoveryClient
}

View File

@ -22,6 +22,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -82,8 +83,16 @@ func New(c *restclient.Config) (*Client, error) {
return nil, err
}
}
var appsClient *AppsClient
if registered.IsRegistered(apps.GroupName) {
appsConfig := *c
appsClient, err = NewApps(&appsConfig)
if err != nil {
return nil, err
}
}
return &Client{RESTClient: client, AutoscalingClient: autoscalingClient, BatchClient: batchClient, ExtensionsClient: extensionsClient, DiscoveryClient: discoveryClient}, nil
return &Client{RESTClient: client, AutoscalingClient: autoscalingClient, BatchClient: batchClient, ExtensionsClient: extensionsClient, DiscoveryClient: discoveryClient, AppsClient: appsClient}, nil
}
// MatchesServerVersion queries the server to compares the build version

View File

@ -22,6 +22,7 @@ import (
_ "k8s.io/kubernetes/pkg/api/install"
"k8s.io/kubernetes/pkg/apimachinery/registered"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"

View File

@ -0,0 +1,100 @@
/*
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 unversioned
import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/watch"
)
// PetSetNamespacer has methods to work with PetSet resources in a namespace
type PetSetNamespacer interface {
PetSet(namespace string) PetSetInterface
}
// PetSetInterface exposes methods to work on PetSet resources.
type PetSetInterface interface {
List(opts api.ListOptions) (*apps.PetSetList, error)
Get(name string) (*apps.PetSet, error)
Create(petSet *apps.PetSet) (*apps.PetSet, error)
Update(petSet *apps.PetSet) (*apps.PetSet, error)
Delete(name string, options *api.DeleteOptions) error
Watch(opts api.ListOptions) (watch.Interface, error)
UpdateStatus(petSet *apps.PetSet) (*apps.PetSet, error)
}
// petSet implements PetSetNamespacer interface
type petSet struct {
r *AppsClient
ns string
}
// newPetSet returns a petSet
func newPetSet(c *AppsClient, namespace string) *petSet {
return &petSet{c, namespace}
}
// List returns a list of petSet that match the label and field selectors.
func (c *petSet) List(opts api.ListOptions) (result *apps.PetSetList, err error) {
result = &apps.PetSetList{}
err = c.r.Get().Namespace(c.ns).Resource("petsets").VersionedParams(&opts, api.ParameterCodec).Do().Into(result)
return
}
// Get returns information about a particular petSet.
func (c *petSet) Get(name string) (result *apps.PetSet, err error) {
result = &apps.PetSet{}
err = c.r.Get().Namespace(c.ns).Resource("petsets").Name(name).Do().Into(result)
return
}
// Create creates a new petSet.
func (c *petSet) Create(petSet *apps.PetSet) (result *apps.PetSet, err error) {
result = &apps.PetSet{}
err = c.r.Post().Namespace(c.ns).Resource("petsets").Body(petSet).Do().Into(result)
return
}
// Update updates an existing petSet.
func (c *petSet) Update(petSet *apps.PetSet) (result *apps.PetSet, err error) {
result = &apps.PetSet{}
err = c.r.Put().Namespace(c.ns).Resource("petsets").Name(petSet.Name).Body(petSet).Do().Into(result)
return
}
// Delete deletes a petSet, returns error if one occurs.
func (c *petSet) Delete(name string, options *api.DeleteOptions) (err error) {
return c.r.Delete().Namespace(c.ns).Resource("petsets").Name(name).Body(options).Do().Error()
}
// Watch returns a watch.Interface that watches the requested petSet.
func (c *petSet) Watch(opts api.ListOptions) (watch.Interface, error) {
return c.r.Get().
Prefix("watch").
Namespace(c.ns).
Resource("petsets").
VersionedParams(&opts, api.ParameterCodec).
Watch()
}
// UpdateStatus takes the name of the petSet and the new status. Returns the server's representation of the petSet, and an error, if it occurs.
func (c *petSet) UpdateStatus(petSet *apps.PetSet) (result *apps.PetSet, err error) {
result = &apps.PetSet{}
err = c.r.Put().Namespace(c.ns).Resource("petsets").Name(petSet.Name).SubResource("status").Body(petSet).Do().Into(result)
return
}

View File

@ -0,0 +1,165 @@
/*
Copyright 2016 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 unversioned_test
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/client/unversioned/testclient/simple"
)
func getPetSetResourceName() string {
return "petsets"
}
func TestListPetSets(t *testing.T) {
ns := api.NamespaceAll
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Apps.ResourcePath(getPetSetResourceName(), ns, ""),
},
Response: simple.Response{StatusCode: 200,
Body: &apps.PetSetList{
Items: []apps.PetSet{
{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.PetSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
},
},
}
receivedRSList, err := c.Setup(t).Apps().PetSet(ns).List(api.ListOptions{})
c.Validate(t, receivedRSList, err)
}
func TestGetPetSet(t *testing.T) {
ns := api.NamespaceDefault
c := &simple.Client{
Request: simple.Request{Method: "GET", Path: testapi.Apps.ResourcePath(getPetSetResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{
StatusCode: 200,
Body: &apps.PetSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.PetSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
}
receivedRS, err := c.Setup(t).Apps().PetSet(ns).Get("foo")
c.Validate(t, receivedRS, err)
}
func TestGetPetSetWithNoName(t *testing.T) {
ns := api.NamespaceDefault
c := &simple.Client{Error: true}
receivedPod, err := c.Setup(t).Apps().PetSet(ns).Get("")
if (err != nil) && (err.Error() != simple.NameRequiredError) {
t.Errorf("Expected error: %v, but got %v", simple.NameRequiredError, err)
}
c.Validate(t, receivedPod, err)
}
func TestUpdatePetSet(t *testing.T) {
ns := api.NamespaceDefault
requestRS := &apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"},
}
c := &simple.Client{
Request: simple.Request{Method: "PUT", Path: testapi.Apps.ResourcePath(getPetSetResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{
StatusCode: 200,
Body: &apps.PetSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.PetSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
}
receivedRS, err := c.Setup(t).Apps().PetSet(ns).Update(requestRS)
c.Validate(t, receivedRS, err)
}
func TestDeletePetSet(t *testing.T) {
ns := api.NamespaceDefault
c := &simple.Client{
Request: simple.Request{Method: "DELETE", Path: testapi.Apps.ResourcePath(getPetSetResourceName(), ns, "foo"), Query: simple.BuildQueryValues(nil)},
Response: simple.Response{StatusCode: 200},
}
err := c.Setup(t).Apps().PetSet(ns).Delete("foo", nil)
c.Validate(t, nil, err)
}
func TestCreatePetSet(t *testing.T) {
ns := api.NamespaceDefault
requestRS := &apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "foo"},
}
c := &simple.Client{
Request: simple.Request{Method: "POST", Path: testapi.Apps.ResourcePath(getPetSetResourceName(), ns, ""), Body: requestRS, Query: simple.BuildQueryValues(nil)},
Response: simple.Response{
StatusCode: 200,
Body: &apps.PetSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{
"foo": "bar",
"name": "baz",
},
},
Spec: apps.PetSetSpec{
Replicas: 2,
Template: api.PodTemplateSpec{},
},
},
},
}
receivedRS, err := c.Setup(t).Apps().PetSet(ns).Create(requestRS)
c.Validate(t, receivedRS, err)
}
// TODO: Test Status actions.

View File

@ -41,6 +41,7 @@ import (
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apimachinery"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -317,6 +318,8 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
return c.AutoscalingClient.RESTClient, nil
case batch.GroupName:
return c.BatchClient.RESTClient, nil
case apps.GroupName:
return c.AppsClient.RESTClient, nil
case extensions.GroupName:
return c.ExtensionsClient.RESTClient, nil
case api.SchemeGroupVersion.Group:
@ -892,6 +895,13 @@ func (c *clientSwaggerSchema) ValidateBytes(data []byte) error {
}
return getSchemaAndValidate(c.c.AutoscalingClient.RESTClient, data, "apis/", gvk.GroupVersion().String(), c.cacheDir)
}
if gvk.Group == apps.GroupName {
if c.c.AppsClient == nil {
return errors.New("unable to validate: no autoscaling client")
}
return getSchemaAndValidate(c.c.AppsClient.RESTClient, data, "apis/", gvk.GroupVersion().String(), c.cacheDir)
}
if gvk.Group == batch.GroupName {
if c.c.BatchClient == nil {
return errors.New("unable to validate: no batch client")

View File

@ -35,6 +35,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
@ -417,6 +418,7 @@ var replicaSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"}
var jobColumns = []string{"NAME", "DESIRED", "SUCCESSFUL", "AGE"}
var serviceColumns = []string{"NAME", "CLUSTER-IP", "EXTERNAL-IP", "PORT(S)", "AGE"}
var ingressColumns = []string{"NAME", "RULE", "BACKEND", "ADDRESS", "AGE"}
var petSetColumns = []string{"NAME", "DESIRED", "CURRENT", "AGE"}
var endpointColumns = []string{"NAME", "ENDPOINTS", "AGE"}
var nodeColumns = []string{"NAME", "STATUS", "AGE"}
var daemonSetColumns = []string{"NAME", "DESIRED", "CURRENT", "NODE-SELECTOR", "AGE"}
@ -457,6 +459,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(serviceColumns, printServiceList)
h.Handler(ingressColumns, printIngress)
h.Handler(ingressColumns, printIngressList)
h.Handler(petSetColumns, printPetSet)
h.Handler(petSetColumns, printPetSetList)
h.Handler(endpointColumns, printEndpoints)
h.Handler(endpointColumns, printEndpointsList)
h.Handler(nodeColumns, printNode)
@ -1015,6 +1019,53 @@ func printIngressList(ingressList *extensions.IngressList, w io.Writer, options
return nil
}
func printPetSet(ps *apps.PetSet, w io.Writer, options PrintOptions) error {
name := ps.Name
namespace := ps.Namespace
containers := ps.Spec.Template.Spec.Containers
if options.WithNamespace {
if _, err := fmt.Fprintf(w, "%s\t", namespace); err != nil {
return err
}
}
desiredReplicas := ps.Spec.Replicas
currentReplicas := ps.Status.Replicas
if _, err := fmt.Fprintf(w, "%s\t%d\t%d\t%s",
name,
desiredReplicas,
currentReplicas,
translateTimestamp(ps.CreationTimestamp),
); err != nil {
return err
}
if options.Wide {
if err := layoutContainers(containers, w); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "\t%s", unversioned.FormatLabelSelector(ps.Spec.Selector)); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, appendLabels(ps.Labels, options.ColumnLabels)); err != nil {
return err
}
if _, err := fmt.Fprint(w, appendAllLabels(options.ShowLabels, ps.Labels)); err != nil {
return err
}
return nil
}
func printPetSetList(petSetList *apps.PetSetList, w io.Writer, options PrintOptions) error {
for _, ps := range petSetList.Items {
if err := printPetSet(&ps, w, options); err != nil {
return err
}
}
return nil
}
func printDaemonSet(ds *extensions.DaemonSet, w io.Writer, options PrintOptions) error {
name := ds.Name
namespace := ds.Namespace

View File

@ -22,6 +22,7 @@ import (
_ "k8s.io/kubernetes/pkg/api/install"
"k8s.io/kubernetes/pkg/apimachinery/registered"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"

View File

@ -32,6 +32,8 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/apps"
appsapi "k8s.io/kubernetes/pkg/apis/apps/v1alpha1"
"k8s.io/kubernetes/pkg/apis/autoscaling"
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/batch"
@ -62,6 +64,7 @@ import (
nodeetcd "k8s.io/kubernetes/pkg/registry/node/etcd"
pvetcd "k8s.io/kubernetes/pkg/registry/persistentvolume/etcd"
pvcetcd "k8s.io/kubernetes/pkg/registry/persistentvolumeclaim/etcd"
petsetetcd "k8s.io/kubernetes/pkg/registry/petset/etcd"
podetcd "k8s.io/kubernetes/pkg/registry/pod/etcd"
pspetcd "k8s.io/kubernetes/pkg/registry/podsecuritypolicy/etcd"
podtemplateetcd "k8s.io/kubernetes/pkg/registry/podtemplate/etcd"
@ -349,6 +352,38 @@ func (m *Master) InstallAPIs(c *Config) {
allGroups = append(allGroups, group)
}
if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(appsapi.SchemeGroupVersion) {
appsResources := m.getAppsResources(c)
appsGroupMeta := registered.GroupOrDie(apps.GroupName)
// Hard code preferred group version to apps/v1alpha1
appsGroupMeta.GroupVersion = appsapi.SchemeGroupVersion
apiGroupInfo := genericapiserver.APIGroupInfo{
GroupMeta: *appsGroupMeta,
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{
"v1alpha1": appsResources,
},
OptionsExternalVersion: &registered.GroupOrDie(api.GroupName).GroupVersion,
Scheme: api.Scheme,
ParameterCodec: api.ParameterCodec,
NegotiatedSerializer: api.Codecs,
NegotiatedStreamSerializer: api.StreamCodecs,
}
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
appsGVForDiscovery := unversioned.GroupVersionForDiscovery{
GroupVersion: appsGroupMeta.GroupVersion.String(),
Version: appsGroupMeta.GroupVersion.Version,
}
group := unversioned.APIGroup{
Name: appsGroupMeta.GroupVersion.Group,
Versions: []unversioned.GroupVersionForDiscovery{appsGVForDiscovery},
PreferredVersion: appsGVForDiscovery,
}
allGroups = append(allGroups, group)
}
if err := m.InstallAPIGroups(apiGroupsInfo); err != nil {
glog.Fatalf("Error in registering group versions: %v", err)
}
@ -824,6 +859,27 @@ func (m *Master) getBatchResources(c *Config) map[string]rest.Storage {
return storage
}
// getPetSetResources returns the resources for batch api
func (m *Master) getAppsResources(c *Config) map[string]rest.Storage {
// TODO update when we support more than one version of this group
version := appsapi.SchemeGroupVersion
storage := map[string]rest.Storage{}
if c.APIResourceConfigSource.ResourceEnabled(version.WithResource("petsets")) {
restOptions := func(resource string) generic.RESTOptions {
return generic.RESTOptions{
Storage: c.StorageDestinations.Get(apps.GroupName, resource),
Decorator: m.StorageDecorator(),
DeleteCollectionWorkers: m.deleteCollectionWorkers,
}
}
petsetStorage, petsetStatusStorage := petsetetcd.NewREST(restOptions("petsets"))
storage["petsets"] = petsetStorage
storage["petsets/status"] = petsetStatusStorage
}
return storage
}
// findExternalAddress returns ExternalIP of provided node with fallback to LegacyHostIP.
func findExternalAddress(node *api.Node) (string, error) {
var fallback string
@ -876,7 +932,7 @@ func (m *Master) IsTunnelSyncHealthy(req *http.Request) error {
func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig {
ret := genericapiserver.NewResourceConfig()
ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion)
ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion, appsapi.SchemeGroupVersion)
// all extensions resources except these are disabled by default
ret.EnableResources(

View File

@ -38,6 +38,8 @@ import (
apiutil "k8s.io/kubernetes/pkg/api/util"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apis/apps"
appsapi "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
"k8s.io/kubernetes/pkg/apis/batch"
@ -78,6 +80,8 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.
autoscaling.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Autoscaling.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize))
storageDestinations.AddAPIGroup(
batch.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Batch.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize))
storageDestinations.AddAPIGroup(
apps.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Apps.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize))
storageDestinations.AddAPIGroup(
extensions.GroupName, etcdstorage.NewEtcdStorage(server.Client, testapi.Extensions.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize))
@ -85,6 +89,7 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.
storageVersions[api.GroupName] = testapi.Default.GroupVersion().String()
storageVersions[autoscaling.GroupName] = testapi.Autoscaling.GroupVersion().String()
storageVersions[batch.GroupName] = testapi.Batch.GroupVersion().String()
storageVersions[apps.GroupName] = testapi.Apps.GroupVersion().String()
storageVersions[extensions.GroupName] = testapi.Extensions.GroupVersion().String()
config.StorageVersions = storageVersions
config.PublicAddress = net.ParseIP("192.168.10.4")
@ -123,7 +128,7 @@ func newMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *ass
// limitedAPIResourceConfigSource only enables the core group, the extensions group, the batch group, and the autoscaling group.
func limitedAPIResourceConfigSource() *genericapiserver.ResourceConfig {
ret := genericapiserver.NewResourceConfig()
ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion)
ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, appsapi.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion)
return ret
}
@ -410,7 +415,7 @@ func TestDiscoveryAtAPIS(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
expectGroupNames := sets.NewString(autoscaling.GroupName, batch.GroupName, extensions.GroupName)
expectGroupNames := sets.NewString(autoscaling.GroupName, batch.GroupName, apps.GroupName, extensions.GroupName)
expectVersions := map[string][]unversioned.GroupVersionForDiscovery{
autoscaling.GroupName: {
{
@ -424,6 +429,12 @@ func TestDiscoveryAtAPIS(t *testing.T) {
Version: testapi.Batch.GroupVersion().Version,
},
},
apps.GroupName: {
{
GroupVersion: testapi.Apps.GroupVersion().String(),
Version: testapi.Apps.GroupVersion().Version,
},
},
extensions.GroupName: {
{
GroupVersion: testapi.Extensions.GroupVersion().String(),
@ -440,6 +451,10 @@ func TestDiscoveryAtAPIS(t *testing.T) {
GroupVersion: config.StorageVersions[batch.GroupName],
Version: apiutil.GetVersion(config.StorageVersions[batch.GroupName]),
},
apps.GroupName: {
GroupVersion: config.StorageVersions[apps.GroupName],
Version: apiutil.GetVersion(config.StorageVersions[apps.GroupName]),
},
extensions.GroupName: {
GroupVersion: config.StorageVersions[extensions.GroupName],
Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]),

View File

@ -34,6 +34,7 @@ const (
Endpoints Resource = "endpoints"
HorizontalPodAutoscalers Resource = "horizontalpodautoscalers"
Ingress Resource = "ingress"
PetSet Resource = "petset"
Jobs Resource = "jobs"
LimitRanges Resource = "limitranges"
Namespaces Resource = "namespaces"
@ -59,6 +60,7 @@ func init() {
watchCacheSizes[Endpoints] = 1000
watchCacheSizes[HorizontalPodAutoscalers] = 100
watchCacheSizes[Ingress] = 100
watchCacheSizes[PetSet] = 100
watchCacheSizes[Jobs] = 100
watchCacheSizes[LimitRanges] = 100
watchCacheSizes[Namespaces] = 100

View File

@ -0,0 +1,17 @@
/*
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 petset

View File

@ -0,0 +1,96 @@
/*
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 etcd
import (
"k8s.io/kubernetes/pkg/api"
appsapi "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/cachesize"
"k8s.io/kubernetes/pkg/registry/generic"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
"k8s.io/kubernetes/pkg/registry/petset"
"k8s.io/kubernetes/pkg/runtime"
)
// rest implements a RESTStorage for replication controllers against etcd
type REST struct {
*etcdgeneric.Etcd
}
// NewREST returns a RESTStorage object that will work against replication controllers.
func NewREST(opts generic.RESTOptions) (*REST, *StatusREST) {
prefix := "/petsets"
newListFunc := func() runtime.Object { return &appsapi.PetSetList{} }
storageInterface := opts.Decorator(
opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.PetSet), &appsapi.PetSet{}, prefix, petset.Strategy, newListFunc)
store := &etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &appsapi.PetSet{} },
// NewListFunc returns an object capable of storing results of an etcd list.
NewListFunc: newListFunc,
// Produces a petSet that etcd understands, to the root of the resource
// by combining the namespace in the context with the given prefix
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
},
// Produces a petSet that etcd understands, to the resource by combining
// the namespace in the context with the given prefix
KeyFunc: func(ctx api.Context, name string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name)
},
// Retrieve the name field of a replication controller
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*appsapi.PetSet).Name, nil
},
// Used to match objects based on labels/fields for list and watch
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return petset.MatchPetSet(label, field)
},
QualifiedResource: appsapi.Resource("petsets"),
DeleteCollectionWorkers: opts.DeleteCollectionWorkers,
// Used to validate controller creation
CreateStrategy: petset.Strategy,
// Used to validate controller updates
UpdateStrategy: petset.Strategy,
DeleteStrategy: petset.Strategy,
Storage: storageInterface,
}
statusStore := *store
statusStore.UpdateStrategy = petset.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}
}
// StatusREST implements the REST endpoint for changing the status of an petSet
type StatusREST struct {
store *etcdgeneric.Etcd
}
func (r *StatusREST) New() runtime.Object {
return &appsapi.PetSet{}
}
// Update alters the status subset of an object.
func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
return r.store.Update(ctx, obj)
}

View File

@ -0,0 +1,182 @@
/*
Copyright 2016 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 etcd
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/registry/registrytest"
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
)
func newStorage(t *testing.T) (*REST, *StatusREST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, apps.GroupName)
restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
petSetStorage, statusStorage := NewREST(restOptions)
return petSetStorage, statusStorage, server
}
// createPetSet is a helper function that returns a PetSet with the updated resource version.
func createPetSet(storage *REST, ps apps.PetSet, t *testing.T) (apps.PetSet, error) {
ctx := api.WithNamespace(api.NewContext(), ps.Namespace)
obj, err := storage.Create(ctx, &ps)
if err != nil {
t.Errorf("Failed to create PetSet, %v", err)
}
newPS := obj.(*apps.PetSet)
return *newPS, nil
}
func validNewPetSet() *apps.PetSet {
return &apps.PetSet{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
Labels: map[string]string{"a": "b"},
},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "test",
Image: "test_image",
ImagePullPolicy: api.PullIfNotPresent,
},
},
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
},
},
Replicas: 7,
},
Status: apps.PetSetStatus{},
}
}
func TestCreate(t *testing.T) {
storage, _, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd)
ps := validNewPetSet()
ps.ObjectMeta = api.ObjectMeta{}
test.TestCreate(
// valid
ps,
// TODO: Add an invalid case when we have validation.
)
}
// TODO: Test updates to spec when we allow them.
func TestStatusUpdate(t *testing.T) {
storage, statusStorage, server := newStorage(t)
defer server.Terminate(t)
ctx := api.WithNamespace(api.NewContext(), api.NamespaceDefault)
key := etcdtest.AddPrefix("/petsets/" + api.NamespaceDefault + "/foo")
validPetSet := validNewPetSet()
if err := storage.Storage.Create(ctx, key, validPetSet, nil, 0); err != nil {
t.Fatalf("unexpected error: %v", err)
}
update := apps.PetSet{
ObjectMeta: validPetSet.ObjectMeta,
Spec: apps.PetSetSpec{
Replicas: 7,
},
Status: apps.PetSetStatus{
Replicas: 7,
},
}
if _, _, err := statusStorage.Update(ctx, &update); err != nil {
t.Fatalf("unexpected error: %v", err)
}
obj, err := storage.Get(ctx, "foo")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ps := obj.(*apps.PetSet)
if ps.Spec.Replicas != 7 {
t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", ps.Spec.Replicas)
}
if ps.Status.Replicas != 7 {
t.Errorf("we expected .status.replicas to be updated to %d but it was %v", 7, ps.Status.Replicas)
}
}
func TestGet(t *testing.T) {
storage, _, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd)
test.TestGet(validNewPetSet())
}
func TestList(t *testing.T) {
storage, _, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd)
test.TestList(validNewPetSet())
}
func TestDelete(t *testing.T) {
storage, _, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd)
test.TestDelete(validNewPetSet())
}
func TestWatch(t *testing.T) {
storage, _, server := newStorage(t)
defer server.Terminate(t)
test := registrytest.New(t, storage.Etcd)
test.TestWatch(
validNewPetSet(),
// matching labels
[]labels.Set{
{"a": "b"},
},
// not matching labels
[]labels.Set{
{"a": "c"},
{"foo": "bar"},
},
// matching fields
[]fields.Set{
{"metadata.name": "foo"},
},
// not matching fields
[]fields.Set{
{"metadata.name": "bar"},
},
)
}
// TODO: Test generation number.

View File

@ -0,0 +1,142 @@
/*
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.
*/
package petset
import (
"fmt"
"reflect"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/validation"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/generic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/validation/field"
)
// petSetStrategy implements verification logic for Replication PetSets.
type petSetStrategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Strategy is the default logic that applies when creating and updating Replication PetSet objects.
var Strategy = petSetStrategy{api.Scheme, api.SimpleNameGenerator}
// NamespaceScoped returns true because all PetSet' need to be within a namespace.
func (petSetStrategy) NamespaceScoped() bool {
return true
}
// PrepareForCreate clears the status of an PetSet before creation.
func (petSetStrategy) PrepareForCreate(obj runtime.Object) {
petSet := obj.(*apps.PetSet)
// create cannot set status
petSet.Status = apps.PetSetStatus{}
petSet.Generation = 1
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (petSetStrategy) PrepareForUpdate(obj, old runtime.Object) {
newPetSet := obj.(*apps.PetSet)
oldPetSet := old.(*apps.PetSet)
// Update is not allowed to set status
newPetSet.Status = oldPetSet.Status
// Any changes to the spec increment the generation number, any changes to the
// status should reflect the generation number of the corresponding object.
// See api.ObjectMeta description for more information on Generation.
if !reflect.DeepEqual(oldPetSet.Spec, newPetSet.Spec) {
newPetSet.Generation = oldPetSet.Generation + 1
}
}
// Validate validates a new PetSet.
func (petSetStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
petSet := obj.(*apps.PetSet)
return validation.ValidatePetSet(petSet)
}
// Canonicalize normalizes the object after validation.
func (petSetStrategy) Canonicalize(obj runtime.Object) {
}
// AllowCreateOnUpdate is false for PetSet; this means POST is needed to create one.
func (petSetStrategy) AllowCreateOnUpdate() bool {
return false
}
// ValidateUpdate is the default update validation for an end user.
func (petSetStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
return field.ErrorList{field.Forbidden(field.NewPath("spec"), "updates to petset spec are forbidden.")}
// TODO: For now we're taking the safe route and disallowing all updates to spec.
// Enable on a case by case basis.
//validationErrorList := validation.ValidatePetSet(obj.(*apps.PetSet))
//updateErrorList := validation.ValidatePetSetUpdate(obj.(*apps.PetSet), old.(*apps.PetSet))
//return append(validationErrorList, updateErrorList...)
}
// AllowUnconditionalUpdate is the default update policy for PetSet objects.
func (petSetStrategy) AllowUnconditionalUpdate() bool {
return true
}
// PetSetToSelectableFields returns a field set that represents the object.
func PetSetToSelectableFields(petSet *apps.PetSet) fields.Set {
return generic.ObjectMetaFieldsSet(petSet.ObjectMeta, true)
}
// MatchPetSet is the filter used by the generic etcd backend to watch events
// from etcd to clients of the apiserver only interested in specific labels/fields.
func MatchPetSet(label labels.Selector, field fields.Selector) generic.Matcher {
return &generic.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
petSet, ok := obj.(*apps.PetSet)
if !ok {
return nil, nil, fmt.Errorf("given object is not an PetSet.")
}
return labels.Set(petSet.ObjectMeta.Labels), PetSetToSelectableFields(petSet), nil
},
}
}
type petSetStatusStrategy struct {
petSetStrategy
}
var StatusStrategy = petSetStatusStrategy{Strategy}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (petSetStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
newPetSet := obj.(*apps.PetSet)
oldPetSet := old.(*apps.PetSet)
// status changes are not allowed to update spec
newPetSet.Spec = oldPetSet.Spec
}
// ValidateUpdate is the default update validation for an end user updating status
func (petSetStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
// TODO: Validate status updates.
return validation.ValidatePetSetStatusUpdate(obj.(*apps.PetSet), old.(*apps.PetSet))
}

View File

@ -0,0 +1,136 @@
/*
Copyright 2016 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 petset
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/apps"
)
func TestPetSetStrategy(t *testing.T) {
ctx := api.NewDefaultContext()
if !Strategy.NamespaceScoped() {
t.Errorf("PetSet must be namespace scoped")
}
if Strategy.AllowCreateOnUpdate() {
t.Errorf("PetSet should not allow create on update")
}
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
ps := &apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: apps.PetSetSpec{
Selector: &unversioned.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
},
Status: apps.PetSetStatus{Replicas: 3},
}
Strategy.PrepareForCreate(ps)
if ps.Status.Replicas != 0 {
t.Error("PetSet should not allow setting status.pets on create")
}
errs := Strategy.Validate(ctx, ps)
if len(errs) != 0 {
t.Errorf("Unexpected error validating %v", errs)
}
validPs := &apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: ps.Name, Namespace: ps.Namespace},
Spec: apps.PetSetSpec{
Selector: ps.Spec.Selector,
Template: validPodTemplate.Template,
},
Status: apps.PetSetStatus{Replicas: 4},
}
Strategy.PrepareForUpdate(validPs, ps)
errs = Strategy.ValidateUpdate(ctx, validPs, ps)
if len(errs) == 0 {
t.Errorf("Expected a validation error since updates are disallowed on petsets.")
}
}
func TestPetSetStatusStrategy(t *testing.T) {
ctx := api.NewDefaultContext()
if !StatusStrategy.NamespaceScoped() {
t.Errorf("PetSet must be namespace scoped")
}
if StatusStrategy.AllowCreateOnUpdate() {
t.Errorf("PetSet should not allow create on update")
}
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent"}},
},
},
}
oldPS := &apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "10"},
Spec: apps.PetSetSpec{
Replicas: 3,
Selector: &unversioned.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
},
Status: apps.PetSetStatus{
Replicas: 1,
},
}
newPS := &apps.PetSet{
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault, ResourceVersion: "9"},
Spec: apps.PetSetSpec{
Replicas: 1,
Selector: &unversioned.LabelSelector{MatchLabels: validSelector},
Template: validPodTemplate.Template,
},
Status: apps.PetSetStatus{
Replicas: 2,
},
}
StatusStrategy.PrepareForUpdate(newPS, oldPS)
if newPS.Status.Replicas != 2 {
t.Errorf("PetSet status updates should allow change of pets: %v", newPS.Status.Replicas)
}
if newPS.Spec.Replicas != 3 {
t.Errorf("PetSet status updates should not clobber spec: %v", newPS.Spec)
}
errs := StatusStrategy.ValidateUpdate(ctx, newPS, oldPS)
if len(errs) != 0 {
t.Errorf("Unexpected error %v", errs)
}
}

View File

@ -62,6 +62,13 @@ func NewBatchEtcdStorage(client etcd.Client) storage.Interface {
return etcdstorage.NewEtcdStorage(client, testapi.Batch.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize)
}
func NewAppsEtcdStorage(client etcd.Client) storage.Interface {
if client == nil {
client = NewEtcdClient()
}
return etcdstorage.NewEtcdStorage(client, testapi.Apps.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize)
}
func NewExtensionsEtcdStorage(client etcd.Client) storage.Interface {
if client == nil {
client = NewEtcdClient()

View File

@ -29,6 +29,7 @@ import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -157,6 +158,8 @@ func NewMasterConfig() *master.Config {
storageVersions[autoscaling.GroupName] = testapi.Autoscaling.GroupVersion().String()
batchEtcdStorage := NewBatchEtcdStorage(etcdClient)
storageVersions[batch.GroupName] = testapi.Batch.GroupVersion().String()
appsEtcdStorage := NewAppsEtcdStorage(etcdClient)
storageVersions[apps.GroupName] = testapi.Apps.GroupVersion().String()
expEtcdStorage := NewExtensionsEtcdStorage(etcdClient)
storageVersions[extensions.GroupName] = testapi.Extensions.GroupVersion().String()
@ -164,6 +167,7 @@ func NewMasterConfig() *master.Config {
storageDestinations.AddAPIGroup(api.GroupName, etcdStorage)
storageDestinations.AddAPIGroup(autoscaling.GroupName, autoscalingEtcdStorage)
storageDestinations.AddAPIGroup(batch.GroupName, batchEtcdStorage)
storageDestinations.AddAPIGroup(apps.GroupName, appsEtcdStorage)
storageDestinations.AddAPIGroup(extensions.GroupName, expEtcdStorage)
return &master.Config{

View File

@ -55,6 +55,10 @@ func TestBatchPrefix(t *testing.T) {
testPrefix(t, "/apis/batch/")
}
func TestAppsPrefix(t *testing.T) {
testPrefix(t, "/apis/apps/")
}
func TestExtensionsPrefix(t *testing.T) {
testPrefix(t, "/apis/extensions/")
}