Merge pull request #5012 from smarterclayton/podtemplates

Pod templates as their own type
This commit is contained in:
Brian Grant 2015-04-22 11:50:24 -07:00
commit 97302af015
23 changed files with 1926 additions and 216 deletions

View File

@ -4742,21 +4742,40 @@
}
]
},
{
"path": "/api/v1beta1/pods/{name}/exec",
"description": "API at /api/v1beta1 version v1beta1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta1/pods/{name}/log",
"description": "API at /api/v1beta1 version v1beta1",
"operations": [
{
"type": "v1beta1.PodLogOptions",
"type": "v1beta1.Pod",
"method": "GET",
"summary": "read the specified PodLogOptions",
"nickname": "readPodLogOptions",
"summary": "read the specified Pod",
"nickname": "readPod",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the PodLogOptions",
"description": "name of the Pod",
"required": true,
"allowMultiple": false
},
@ -4773,7 +4792,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1beta1.PodLogOptions"
"responseModel": "v1beta1.Pod"
}
],
"produces": [
@ -4785,6 +4804,193 @@
}
]
},
{
"path": "/api/v1beta1/pods/{name}/portforward",
"description": "API at /api/v1beta1 version v1beta1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta1/pods/{name}/proxy",
"description": "API at /api/v1beta1 version v1beta1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "POST",
"summary": "connect POST requests to Pod",
"nickname": "connectPOSTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "PUT",
"summary": "connect PUT requests to Pod",
"nickname": "connectPUTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "DELETE",
"summary": "connect DELETE requests to Pod",
"nickname": "connectDELETEPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "HEAD",
"summary": "connect HEAD requests to Pod",
"nickname": "connectHEADPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "OPTIONS",
"summary": "connect OPTIONS requests to Pod",
"nickname": "connectOPTIONSPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta1/pods/{name}/proxy/{path:*}",
"description": "API at /api/v1beta1 version v1beta1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "POST",
"summary": "connect POST requests to Pod",
"nickname": "connectPOSTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "PUT",
"summary": "connect PUT requests to Pod",
"nickname": "connectPUTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "DELETE",
"summary": "connect DELETE requests to Pod",
"nickname": "connectDELETEPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "HEAD",
"summary": "connect HEAD requests to Pod",
"nickname": "connectHEADPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "OPTIONS",
"summary": "connect OPTIONS requests to Pod",
"nickname": "connectOPTIONSPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta1/pods/{name}/status",
"description": "API at /api/v1beta1 version v1beta1",
@ -6914,6 +7120,31 @@
"id": "",
"properties": null
},
"v1beta1.AWSElasticBlockStoreVolumeSource": {
"id": "v1beta1.AWSElasticBlockStoreVolumeSource",
"required": [
"volumeID"
],
"properties": {
"fsType": {
"type": "string",
"description": "file system type to mount, such as ext4, xfs, ntfs"
},
"partition": {
"type": "integer",
"format": "int32",
"description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted"
},
"readOnly": {
"type": "boolean",
"description": "read-only if true, read-write otherwise (false or unspecified)"
},
"volumeID": {
"type": "string",
"description": "unique id of the PD resource in AWS"
}
}
},
"v1beta1.AccessModeType": {
"id": "v1beta1.AccessModeType",
"properties": {}
@ -8668,6 +8899,19 @@
}
}
},
"v1beta1.PersistentVolumeClaimVolumeSource": {
"id": "v1beta1.PersistentVolumeClaimVolumeSource",
"properties": {
"claimName": {
"type": "string",
"description": "the name of the claim in the same namespace to be mounted as a volume"
},
"readOnly": {
"type": "boolean",
"description": "mount volume as read-only when true; default false"
}
}
},
"v1beta1.PersistentVolumeList": {
"id": "v1beta1.PersistentVolumeList",
"properties": {
@ -8727,9 +8971,10 @@
"v1beta1.PersistentVolumeSpec": {
"id": "v1beta1.PersistentVolumeSpec",
"required": [
"persistentDisk",
"hostPath",
"glusterfs"
"glusterfs",
"persistentDisk",
"awsElasticBlockStore"
],
"properties": {
"accessModes": {
@ -8739,6 +8984,10 @@
},
"description": "all ways the volume can be mounted"
},
"awsElasticBlockStore": {
"$ref": "v1beta1.AWSElasticBlockStoreVolumeSource",
"description": "AWS disk resource provisioned by an admin"
},
"capacity": {
"type": "any",
"description": "a description of the persistent volume's resources and capacity"
@ -8911,63 +9160,6 @@
}
}
},
"v1beta1.PodLogOptions": {
"id": "v1beta1.PodLogOptions",
"properties": {
"annotations": {
"type": "any",
"description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about the object"
},
"apiVersion": {
"type": "string",
"description": "version of the schema the object should have"
},
"container": {
"type": "string",
"description": "the container for which to stream logs; defaults to only container if there is one container in the pod"
},
"creationTimestamp": {
"type": "string",
"description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists"
},
"deletionTimestamp": {
"type": "string",
"description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested"
},
"follow": {
"type": "boolean",
"description": "follow the log stream of the pod; defaults to false"
},
"generateName": {
"type": "string",
"description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified"
},
"id": {
"type": "string",
"description": "name of the object; must be a DNS_SUBDOMAIN and unique among all objects of the same kind within the same namespace; used in resource URLs; cannot be updated"
},
"kind": {
"type": "string",
"description": "kind of object, in CamelCase; cannot be updated"
},
"namespace": {
"type": "string",
"description": "namespace to which the object belongs; must be a DNS_SUBDOMAIN; 'default' by default; cannot be updated"
},
"resourceVersion": {
"$ref": "uint64",
"description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency"
},
"selfLink": {
"type": "string",
"description": "URL for the object; populated by the system, read-only"
},
"uid": {
"type": "string",
"description": "unique UUID across space and time; populated by the system, read-only; cannot be updated"
}
}
},
"v1beta1.PodState": {
"id": "v1beta1.PodState",
"properties": {
@ -9812,7 +10004,7 @@
},
"source": {
"$ref": "v1beta1.VolumeSource",
"description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir"
"description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, AWSElasticBlockStore, or GitRepo; default is EmptyDir"
}
}
},
@ -9851,6 +10043,7 @@
"hostDir",
"emptyDir",
"persistentDisk",
"awsElasticBlockStore",
"gitRepo",
"secret",
"nfs",
@ -9858,6 +10051,10 @@
"glusterfs"
],
"properties": {
"awsElasticBlockStore": {
"$ref": "v1beta1.AWSElasticBlockStoreVolumeSource",
"description": "AWS disk resource attached to the host machine on demand"
},
"emptyDir": {
"$ref": "v1beta1.EmptyDirVolumeSource",
"description": "temporary directory that shares a pod's lifetime"
@ -9886,6 +10083,10 @@
"$ref": "v1beta1.GCEPersistentDiskVolumeSource",
"description": "GCE disk resource attached to the host machine on demand"
},
"persistentVolumeClaim": {
"$ref": "v1beta1.PersistentVolumeClaimVolumeSource",
"description": "a reference to a PersistentVolumeClaim in the same namespace"
},
"secret": {
"$ref": "v1beta1.SecretVolumeSource",
"description": "secret to populate volume with"

View File

@ -4742,21 +4742,40 @@
}
]
},
{
"path": "/api/v1beta2/pods/{name}/exec",
"description": "API at /api/v1beta2 version v1beta2",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta2/pods/{name}/log",
"description": "API at /api/v1beta2 version v1beta2",
"operations": [
{
"type": "v1beta2.PodLogOptions",
"type": "v1beta2.Pod",
"method": "GET",
"summary": "read the specified PodLogOptions",
"nickname": "readPodLogOptions",
"summary": "read the specified Pod",
"nickname": "readPod",
"parameters": [
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the PodLogOptions",
"description": "name of the Pod",
"required": true,
"allowMultiple": false
},
@ -4773,7 +4792,7 @@
{
"code": 200,
"message": "OK",
"responseModel": "v1beta2.PodLogOptions"
"responseModel": "v1beta2.Pod"
}
],
"produces": [
@ -4785,6 +4804,193 @@
}
]
},
{
"path": "/api/v1beta2/pods/{name}/portforward",
"description": "API at /api/v1beta2 version v1beta2",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta2/pods/{name}/proxy",
"description": "API at /api/v1beta2 version v1beta2",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "POST",
"summary": "connect POST requests to Pod",
"nickname": "connectPOSTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "PUT",
"summary": "connect PUT requests to Pod",
"nickname": "connectPUTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "DELETE",
"summary": "connect DELETE requests to Pod",
"nickname": "connectDELETEPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "HEAD",
"summary": "connect HEAD requests to Pod",
"nickname": "connectHEADPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "OPTIONS",
"summary": "connect OPTIONS requests to Pod",
"nickname": "connectOPTIONSPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta2/pods/{name}/proxy/{path:*}",
"description": "API at /api/v1beta2 version v1beta2",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to Pod",
"nickname": "connectGETPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "POST",
"summary": "connect POST requests to Pod",
"nickname": "connectPOSTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "PUT",
"summary": "connect PUT requests to Pod",
"nickname": "connectPUTPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "DELETE",
"summary": "connect DELETE requests to Pod",
"nickname": "connectDELETEPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "HEAD",
"summary": "connect HEAD requests to Pod",
"nickname": "connectHEADPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "OPTIONS",
"summary": "connect OPTIONS requests to Pod",
"nickname": "connectOPTIONSPod",
"parameters": [],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1beta2/pods/{name}/status",
"description": "API at /api/v1beta2 version v1beta2",
@ -6914,6 +7120,31 @@
"id": "",
"properties": null
},
"v1beta2.AWSElasticBlockStoreVolumeSource": {
"id": "v1beta2.AWSElasticBlockStoreVolumeSource",
"required": [
"volumeID"
],
"properties": {
"fsType": {
"type": "string",
"description": "file system type to mount, such as ext4, xfs, ntfs"
},
"partition": {
"type": "integer",
"format": "int32",
"description": "partition on the disk to mount (e.g., '1' for /dev/sda1); if omitted the plain device name (e.g., /dev/sda) will be mounted"
},
"readOnly": {
"type": "boolean",
"description": "read-only if true, read-write otherwise (false or unspecified)"
},
"volumeID": {
"type": "string",
"description": "unique id of the PD resource in AWS"
}
}
},
"v1beta2.AccessModeType": {
"id": "v1beta2.AccessModeType",
"properties": {}
@ -8657,6 +8888,19 @@
}
}
},
"v1beta2.PersistentVolumeClaimVolumeSource": {
"id": "v1beta2.PersistentVolumeClaimVolumeSource",
"properties": {
"claimName": {
"type": "string",
"description": "the name of the claim in the same namespace to be mounted as a volume"
},
"readOnly": {
"type": "boolean",
"description": "mount volume as read-only when true; default false"
}
}
},
"v1beta2.PersistentVolumeList": {
"id": "v1beta2.PersistentVolumeList",
"properties": {
@ -8717,6 +8961,7 @@
"id": "v1beta2.PersistentVolumeSpec",
"required": [
"persistentDisk",
"awsElasticBlockStore",
"hostPath",
"glusterfs"
],
@ -8728,6 +8973,10 @@
},
"description": "all ways the volume can be mounted"
},
"awsElasticBlockStore": {
"$ref": "v1beta2.AWSElasticBlockStoreVolumeSource",
"description": "AWS disk resource provisioned by an admin"
},
"capacity": {
"type": "any",
"description": "a description of the persistent volume's resources and capacity"
@ -8900,63 +9149,6 @@
}
}
},
"v1beta2.PodLogOptions": {
"id": "v1beta2.PodLogOptions",
"properties": {
"annotations": {
"type": "any",
"description": "map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about the object"
},
"apiVersion": {
"type": "string",
"description": "version of the schema the object should have"
},
"container": {
"type": "string",
"description": "the container for which to stream logs; defaults to only container if there is one container in the pod"
},
"creationTimestamp": {
"type": "string",
"description": "RFC 3339 date and time at which the object was created; populated by the system, read-only; null for lists"
},
"deletionTimestamp": {
"type": "string",
"description": "RFC 3339 date and time at which the object will be deleted; populated by the system when a graceful deletion is requested, read-only; if not set, graceful deletion of the object has not been requested"
},
"follow": {
"type": "boolean",
"description": "follow the log stream of the pod; defaults to false"
},
"generateName": {
"type": "string",
"description": "an optional prefix to use to generate a unique name; has the same validation rules as name; optional, and is applied only name if is not specified"
},
"id": {
"type": "string",
"description": "name of the object; must be a DNS_SUBDOMAIN and unique among all objects of the same kind within the same namespace; used in resource URLs; cannot be updated"
},
"kind": {
"type": "string",
"description": "kind of object, in CamelCase; cannot be updated"
},
"namespace": {
"type": "string",
"description": "namespace to which the object belongs; must be a DNS_SUBDOMAIN; 'default' by default; cannot be updated"
},
"resourceVersion": {
"$ref": "uint64",
"description": "string that identifies the internal version of this object that can be used by clients to determine when objects have changed; populated by the system, read-only; value must be treated as opaque by clients and passed unmodified back to the server: https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#concurrency-control-and-consistency"
},
"selfLink": {
"type": "string",
"description": "URL for the object; populated by the system, read-only"
},
"uid": {
"type": "string",
"description": "unique UUID across space and time; populated by the system, read-only"
}
}
},
"v1beta2.PodState": {
"id": "v1beta2.PodState",
"properties": {
@ -9801,7 +9993,7 @@
},
"source": {
"$ref": "v1beta2.VolumeSource",
"description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, or GitRepo; default is EmptyDir"
"description": "location and type of volume to mount; at most one of HostDir, EmptyDir, GCEPersistentDisk, AWSElasticBlockStore, or GitRepo; default is EmptyDir"
}
}
},
@ -9832,6 +10024,7 @@
"hostDir",
"emptyDir",
"persistentDisk",
"awsElasticBlockStore",
"gitRepo",
"secret",
"nfs",
@ -9839,6 +10032,10 @@
"glusterfs"
],
"properties": {
"awsElasticBlockStore": {
"$ref": "v1beta2.AWSElasticBlockStoreVolumeSource",
"description": "AWS disk resource attached to the host machine on demand"
},
"emptyDir": {
"$ref": "v1beta2.EmptyDirVolumeSource",
"description": "temporary directory that shares a pod's lifetime"
@ -9867,6 +10064,10 @@
"$ref": "v1beta2.GCEPersistentDiskVolumeSource",
"description": "GCE disk resource attached to the host machine on demand"
},
"persistentVolumeClaim": {
"$ref": "v1beta2.PersistentVolumeClaimVolumeSource",
"description": "a reference to a PersistentVolumeClaim in the same namespace"
},
"secret": {
"$ref": "v1beta2.SecretVolumeSource",
"description": "secret to populate volume"

File diff suppressed because it is too large Load Diff

View File

@ -251,6 +251,7 @@ _kubectl_get()
must_have_one_noun+=("persistentvolume")
must_have_one_noun+=("persistentvolumeclaim")
must_have_one_noun+=("pod")
must_have_one_noun+=("podtemplate")
must_have_one_noun+=("replicationcontroller")
must_have_one_noun+=("resourcequota")
must_have_one_noun+=("secret")

View File

@ -34,7 +34,6 @@ import (
)
func validateObject(obj runtime.Object) (errors []error) {
ctx := api.NewDefaultContext()
switch t := obj.(type) {
case *api.ReplicationController:
if t.Namespace == "" {
@ -49,7 +48,6 @@ func validateObject(obj runtime.Object) (errors []error) {
if t.Namespace == "" {
t.Namespace = api.NamespaceDefault
}
api.ValidNamespace(ctx, &t.ObjectMeta)
errors = validation.ValidateService(t)
case *api.ServiceList:
for i := range t.Items {
@ -59,7 +57,6 @@ func validateObject(obj runtime.Object) (errors []error) {
if t.Namespace == "" {
t.Namespace = api.NamespaceDefault
}
api.ValidNamespace(ctx, &t.ObjectMeta)
errors = validation.ValidatePod(t)
case *api.PodList:
for i := range t.Items {
@ -68,8 +65,15 @@ func validateObject(obj runtime.Object) (errors []error) {
case *api.PersistentVolume:
errors = validation.ValidatePersistentVolume(t)
case *api.PersistentVolumeClaim:
api.ValidNamespace(ctx, &t.ObjectMeta)
if t.Namespace == "" {
t.Namespace = api.NamespaceDefault
}
errors = validation.ValidatePersistentVolumeClaim(t)
case *api.PodTemplate:
if t.Namespace == "" {
t.Namespace = api.NamespaceDefault
}
errors = validation.ValidatePodTemplate(t)
default:
return []error{fmt.Errorf("no validation defined for %#v", obj)}
}
@ -156,6 +160,7 @@ func TestExampleObjectSchemas(t *testing.T) {
"pod-with-http-healthcheck": &api.Pod{},
"service": &api.Service{},
"replication-controller": &api.ReplicationController{},
"podtemplate": &api.PodTemplate{},
},
"../examples/update-demo": {
"kitten-rc": &api.ReplicationController{},

View File

@ -0,0 +1,22 @@
{
"apiVersion": "v1beta3",
"kind": "PodTemplate",
"metadata": {
"name": "nginx"
},
"template": {
"metadata": {
"labels": {
"name": "nginx"
},
"generateName": "nginx-"
},
"spec": {
"containers": [{
"name": "nginx",
"image": "dockerfile/nginx",
"ports": [{"containerPort": 80}]
}]
}
}
}

View File

@ -358,6 +358,34 @@ for version in "${kube_api_versions[@]}"; do
kube::test::get_object_assert 'pods --namespace=other' "{{range.items}}{{$id_field}}:{{end}}" ''
#################
# Pod templates #
#################
# Note: pod templates exist only in v1beta3 and above, so output will always be in that form
### Create PODTEMPLATE
# Pre-condition: no PODTEMPLATE
kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" ''
# Command
kubectl create -f examples/walkthrough/podtemplate.json "${kube_flags[@]}"
# Post-condition: nginx PODTEMPLATE is available
kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" 'nginx:'
### Printing pod templates works
kubectl get podtemplates "${kube_flags[@]}"
### Display of an object which doesn't existing in v1beta1 and v1beta2 works
[[ "$(kubectl get podtemplates -o yaml "${kube_flags[@]}" | grep nginx)" ]]
### Delete nginx pod template by name
# Pre-condition: nginx pod template is available
kube::test::get_object_assert podtemplates "{{range.items}}{{.metadata.name}}:{{end}}" 'nginx:'
# Command
kubectl delete podtemplate nginx "${kube_flags[@]}"
# Post-condition: No templates exist
kube::test::get_object_assert podtemplate "{{range.items}}{{.metadata.name}}:{{end}}" ''
############
# Services #
############

View File

@ -80,6 +80,10 @@ func TestRESTMapper(t *testing.T) {
t.Errorf("unexpected version mapping: %s %s %v", v, k, err)
}
if m, err := RESTMapper.RESTMapping("PodTemplate", ""); err != nil || m.APIVersion != "v1beta3" || m.Resource != "podtemplates" {
t.Errorf("unexpected version mapping: %#v %v", m, err)
}
for _, version := range Versions {
mapping, err := RESTMapper.RESTMapping("ReplicationController", version)
if err != nil {

View File

@ -28,6 +28,8 @@ func init() {
&Pod{},
&PodList{},
&PodStatusResult{},
&PodTemplate{},
&PodTemplateList{},
&ReplicationControllerList{},
&ReplicationController{},
&ServiceList{},
@ -71,6 +73,8 @@ func init() {
func (*Pod) IsAnAPIObject() {}
func (*PodList) IsAnAPIObject() {}
func (*PodStatusResult) IsAnAPIObject() {}
func (*PodTemplate) IsAnAPIObject() {}
func (*PodTemplateList) IsAnAPIObject() {}
func (*ReplicationController) IsAnAPIObject() {}
func (*ReplicationControllerList) IsAnAPIObject() {}
func (*Service) IsAnAPIObject() {}

View File

@ -179,7 +179,7 @@ func (t *Tester) TestCreateRejectsMismatchedNamespace(valid runtime.Object) {
if err == nil {
t.Errorf("Expected an error, but we didn't get one")
} else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") {
t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error())
t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err)
}
}
@ -195,7 +195,30 @@ func (t *Tester) TestCreateRejectsNamespace(valid runtime.Object) {
if err == nil {
t.Errorf("Expected an error, but we didn't get one")
} else if strings.Contains(err.Error(), "Controller.Namespace does not match the provided context") {
t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err.Error())
t.Errorf("Expected 'Controller.Namespace does not match the provided context' error, got '%v'", err)
}
}
func (t *Tester) TestUpdate(valid runtime.Object, existing, older runtime.Object) {
t.TestUpdateFailsOnNotFound(copyOrDie(valid))
t.TestUpdateFailsOnVersion(copyOrDie(older))
}
func (t *Tester) TestUpdateFailsOnNotFound(valid runtime.Object) {
_, _, err := t.storage.(rest.Updater).Update(api.NewDefaultContext(), valid)
if err == nil {
t.Errorf("Expected an error, but we didn't get one")
} else if !errors.IsNotFound(err) {
t.Errorf("Expected NotFound error, got '%v'", err)
}
}
func (t *Tester) TestUpdateFailsOnVersion(older runtime.Object) {
_, _, err := t.storage.(rest.Updater).Update(api.NewDefaultContext(), older)
if err == nil {
t.Errorf("Expected an error, but we didn't get one")
} else if !errors.IsConflict(err) {
t.Errorf("Expected Conflict error, got '%v'", err)
}
}

View File

@ -85,13 +85,20 @@ func roundTrip(t *testing.T, codec runtime.Codec, item runtime.Object) {
}
// roundTripSame verifies the same source object is tested in all API versions.
func roundTripSame(t *testing.T, item runtime.Object) {
func roundTripSame(t *testing.T, item runtime.Object, except ...string) {
set := util.NewStringSet(except...)
seed := rand.Int63()
fuzzInternalObject(t, "", item, seed)
roundTrip(t, v1beta1.Codec, item)
roundTrip(t, v1beta2.Codec, item)
fuzzInternalObject(t, "v1beta3", item, seed)
roundTrip(t, v1beta3.Codec, item)
if !set.Has("v1beta1") {
roundTrip(t, v1beta1.Codec, item)
}
if !set.Has("v1beta2") {
roundTrip(t, v1beta2.Codec, item)
}
if !set.Has("v1beta3") {
fuzzInternalObject(t, "v1beta3", item, seed)
roundTrip(t, v1beta3.Codec, item)
}
}
func roundTripAll(t *testing.T, item runtime.Object) {
@ -130,6 +137,10 @@ func TestList(t *testing.T) {
var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList")
var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions", "PodExecOptions")
var nonRoundTrippableTypesByVersion = map[string][]string{
"PodTemplate": {"v1beta1", "v1beta2"},
"PodTemplateList": {"v1beta1", "v1beta2"},
}
func TestRoundTripTypes(t *testing.T) {
// api.Scheme.Log(t)
@ -148,7 +159,7 @@ func TestRoundTripTypes(t *testing.T) {
if _, err := meta.TypeAccessor(item); err != nil {
t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err)
}
roundTripSame(t, item)
roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...)
if !nonInternalRoundTrippableTypes.Has(kind) {
roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63()))
}

View File

@ -848,8 +848,8 @@ type PodTemplate struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// Spec defines the pods that will be created from this template
Spec PodTemplateSpec `json:"spec,omitempty"`
// Template defines the pods that will be created from this pod template
Template PodTemplateSpec `json:"template,omitempty"`
}
// PodTemplateList is a list of PodTemplates.

View File

@ -854,8 +854,8 @@ type PodTemplate struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty" description:"standard object metadata; see https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#metadata"`
// Spec defines the behavior of a pod.
Spec PodTemplateSpec `json:"spec,omitempty" description:"specification of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"`
// Template defines the pods that will be created from this pod template
Template PodTemplateSpec `json:"template,omitempty" description:"the template of the desired behavior of the pod; https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/api-conventions.md#spec-and-status"`
}
// PodTemplateList is a list of PodTemplates.

View File

@ -888,6 +888,24 @@ func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList {
return allErrs
}
// ValidatePodTemplate tests if required fields in the pod template are set.
func ValidatePodTemplate(pod *api.PodTemplate) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName).Prefix("metadata")...)
allErrs = append(allErrs, ValidatePodTemplateSpec(&pod.Template, 0).Prefix("template")...)
return allErrs
}
// ValidatePodTemplateUpdate tests to see if the update is legal for an end user to make. newPod is updated with fields
// that cannot be changed.
func ValidatePodTemplateUpdate(newPod, oldPod *api.PodTemplate) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
allErrs = append(allErrs, ValidateObjectMetaUpdate(&oldPod.ObjectMeta, &newPod.ObjectMeta).Prefix("metadata")...)
allErrs = append(allErrs, ValidatePodTemplateSpec(&newPod.Template, 0).Prefix("template")...)
return allErrs
}
var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone))
// ValidateService tests if required fields in the service are set.

View File

@ -1676,7 +1676,7 @@ func TestValidateService(t *testing.T) {
func TestValidateReplicationControllerUpdate(t *testing.T) {
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
@ -1688,7 +1688,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
},
}
readWriteVolumePodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
@ -1702,7 +1702,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
}
invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
@ -1722,7 +1722,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
update: api.ReplicationController{
@ -1730,7 +1730,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 3,
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
},
@ -1739,7 +1739,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
update: api.ReplicationController{
@ -1747,7 +1747,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: validSelector,
Template: &readWriteVolumePodTemplate.Spec,
Template: &readWriteVolumePodTemplate.Template,
},
},
},
@ -1765,7 +1765,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
update: api.ReplicationController{
@ -1773,7 +1773,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: validSelector,
Template: &readWriteVolumePodTemplate.Spec,
Template: &readWriteVolumePodTemplate.Template,
},
},
},
@ -1782,7 +1782,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
update: api.ReplicationController{
@ -1790,7 +1790,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: invalidSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
},
@ -1799,7 +1799,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
update: api.ReplicationController{
@ -1807,7 +1807,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: validSelector,
Template: &invalidPodTemplate.Spec,
Template: &invalidPodTemplate.Template,
},
},
},
@ -1816,7 +1816,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
update: api.ReplicationController{
@ -1824,7 +1824,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: -1,
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
},
@ -1840,7 +1840,7 @@ func TestValidateReplicationControllerUpdate(t *testing.T) {
func TestValidateReplicationController(t *testing.T) {
validSelector := map[string]string{"a": "b"}
validPodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
@ -1852,7 +1852,7 @@ func TestValidateReplicationController(t *testing.T) {
},
}
readWriteVolumePodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: validSelector,
},
@ -1866,7 +1866,7 @@ func TestValidateReplicationController(t *testing.T) {
}
invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
invalidPodTemplate := api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
@ -1881,14 +1881,14 @@ func TestValidateReplicationController(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
{
ObjectMeta: api.ObjectMeta{Name: "abc-123", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
{
@ -1896,7 +1896,7 @@ func TestValidateReplicationController(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: validSelector,
Template: &readWriteVolumePodTemplate.Spec,
Template: &readWriteVolumePodTemplate.Template,
},
},
}
@ -1911,27 +1911,27 @@ func TestValidateReplicationController(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
"missing-namespace": {
ObjectMeta: api.ObjectMeta{Name: "abc-123"},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
"empty selector": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
"selector_doesnt_match": {
ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault},
Spec: api.ReplicationControllerSpec{
Selector: map[string]string{"foo": "bar"},
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
"invalid manifest": {
@ -1945,7 +1945,7 @@ func TestValidateReplicationController(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: validSelector,
Template: &readWriteVolumePodTemplate.Spec,
Template: &readWriteVolumePodTemplate.Template,
},
},
"negative_replicas": {
@ -1965,7 +1965,7 @@ func TestValidateReplicationController(t *testing.T) {
},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
"invalid_label 2": {
@ -1977,7 +1977,20 @@ func TestValidateReplicationController(t *testing.T) {
},
},
Spec: api.ReplicationControllerSpec{
Template: &invalidPodTemplate.Spec,
Template: &invalidPodTemplate.Template,
},
},
"invalid_annotation": {
ObjectMeta: api.ObjectMeta{
Name: "abc-123",
Namespace: api.NamespaceDefault,
Annotations: map[string]string{
"NoUppercaseOrSpecialCharsLike=Equals": "bar",
},
},
Spec: api.ReplicationControllerSpec{
Selector: validSelector,
Template: &validPodTemplate.Template,
},
},
"invalid restart policy 1": {

View File

@ -245,6 +245,7 @@ func (h *HumanReadablePrinter) HandledResources() []string {
}
var podColumns = []string{"POD", "IP", "CONTAINER(S)", "IMAGE(S)", "HOST", "LABELS", "STATUS", "CREATED", "MESSAGE"}
var podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"}
var replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS"}
var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT(S)"}
var endpointColumns = []string{"NAME", "ENDPOINTS"}
@ -263,6 +264,8 @@ var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"}
func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(podColumns, printPod)
h.Handler(podColumns, printPodList)
h.Handler(podTemplateColumns, printPodTemplate)
h.Handler(podTemplateColumns, printPodTemplateList)
h.Handler(replicationControllerColumns, printReplicationController)
h.Handler(replicationControllerColumns, printReplicationControllerList)
h.Handler(serviceColumns, printService)
@ -383,11 +386,6 @@ func interpretContainerStatus(status *api.ContainerStatus) (string, string, stri
}
func printPod(pod *api.Pod, w io.Writer) error {
// TODO: remove me when pods are converted
spec := &api.PodSpec{}
if err := api.Scheme.Convert(&pod.Spec, spec); err != nil {
glog.Errorf("Unable to convert pod manifest: %v", err)
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
pod.Name,
pod.Status.PodIP,
@ -447,6 +445,40 @@ func printPodList(podList *api.PodList, w io.Writer) error {
return nil
}
func printPodTemplate(pod *api.PodTemplate, w io.Writer) error {
containers := pod.Template.Spec.Containers
var firstContainer api.Container
if len(containers) > 0 {
firstContainer, containers = containers[0], containers[1:]
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
pod.Name,
firstContainer.Name,
firstContainer.Image,
formatLabels(pod.Template.Labels),
)
if err != nil {
return err
}
// Lay out all the other containers on separate lines.
for _, container := range containers {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "", container.Name, container.Image, "")
if err != nil {
return err
}
}
return nil
}
func printPodTemplateList(podList *api.PodTemplateList, w io.Writer) error {
for _, pod := range podList.Items {
if err := printPodTemplate(&pod, w); err != nil {
return err
}
}
return nil
}
func printReplicationController(controller *api.ReplicationController, w io.Writer) error {
containers := controller.Spec.Template.Spec.Containers
var firstContainer api.Container

View File

@ -58,6 +58,7 @@ import (
pvcetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolumeclaim/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd"
podtemplateetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate/etcd"
resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
@ -360,9 +361,18 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter)
// init initializes master.
func (m *Master) init(c *Config) {
// TODO: make initialization of the helper part of the Master, and allow some storage
// objects to have a newer storage version than the user's default.
newerHelper, err := NewEtcdHelper(c.EtcdHelper.Client, "v1beta3")
if err != nil {
glog.Fatalf("Unable to setup storage for v1beta3: %v", err)
}
podStorage := podetcd.NewStorage(c.EtcdHelper, c.KubeletClient)
podRegistry := pod.NewRegistry(podStorage.Pod)
podTemplateStorage := podtemplateetcd.NewREST(newerHelper)
eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds()))
limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper)
@ -397,6 +407,8 @@ func (m *Master) init(c *Config) {
"pods/binding": podStorage.Binding,
"bindings": podStorage.Binding,
"podTemplates": podTemplateStorage,
"replicationControllers": controllerStorage,
"services": service.NewStorage(m.serviceRegistry, m.nodeRegistry, m.endpointRegistry, m.portalNet, c.ClusterName),
"endpoints": endpointsStorage,
@ -606,6 +618,9 @@ func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
func (m *Master) api_v1beta1() *apiserver.APIGroupVersion {
storage := make(map[string]rest.Storage)
for k, v := range m.storage {
if k == "podTemplates" {
continue
}
storage[k] = v
}
version := m.defaultAPIGroupVersion()
@ -619,6 +634,9 @@ func (m *Master) api_v1beta1() *apiserver.APIGroupVersion {
func (m *Master) api_v1beta2() *apiserver.APIGroupVersion {
storage := make(map[string]rest.Storage)
for k, v := range m.storage {
if k == "podTemplates" {
continue
}
storage[k] = v
}
version := m.defaultAPIGroupVersion()

View File

@ -60,7 +60,7 @@ func createController(storage *REST, rc api.ReplicationController, t *testing.T)
}
var validPodTemplate = api.PodTemplate{
Spec: api.PodTemplateSpec{
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"a": "b"},
},
@ -79,8 +79,8 @@ var validPodTemplate = api.PodTemplate{
}
var validControllerSpec = api.ReplicationControllerSpec{
Selector: validPodTemplate.Spec.Labels,
Template: &validPodTemplate.Spec,
Selector: validPodTemplate.Template.Labels,
Template: &validPodTemplate.Template,
}
var validController = api.ReplicationController{
@ -161,7 +161,7 @@ func TestCreateControllerWithGeneratedName(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: map[string]string{"a": "b"},
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
}
@ -663,7 +663,7 @@ func TestCreate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: map[string]string{"a": "b"},
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
// invalid
@ -671,7 +671,7 @@ func TestCreate(t *testing.T) {
Spec: api.ReplicationControllerSpec{
Replicas: 2,
Selector: map[string]string{},
Template: &validPodTemplate.Spec,
Template: &validPodTemplate.Template,
},
},
)

View File

@ -35,7 +35,6 @@ import (
)
// podStrategy implements behavior for Pods
// TODO: move to a pod specific package.
type podStrategy struct {
runtime.ObjectTyper
api.NameGenerator

View File

@ -0,0 +1,18 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package podtemplate provides RESTStorage implementations for storing PodTemplate API objects.
package podtemplate

View File

@ -0,0 +1,63 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/podtemplate"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
// rest implements a RESTStorage for pod templates against etcd
type REST struct {
etcdgeneric.Etcd
}
// NewREST returns a RESTStorage object that will work against pod templates.
func NewREST(h tools.EtcdHelper) *REST {
prefix := "/registry/podtemplates"
store := etcdgeneric.Etcd{
NewFunc: func() runtime.Object { return &api.PodTemplate{} },
NewListFunc: func() runtime.Object { return &api.PodTemplateList{} },
KeyRootFunc: func(ctx api.Context) string {
return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix)
},
KeyFunc: func(ctx api.Context, name string) (string, error) {
return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name)
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
return obj.(*api.PodTemplate).Name, nil
},
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
return podtemplate.MatchPodTemplate(label, field)
},
EndpointName: "podtemplates",
CreateStrategy: podtemplate.Strategy,
UpdateStrategy: podtemplate.Strategy,
ReturnDeletedObject: true,
Helper: h,
}
return &REST{store}
}

View File

@ -0,0 +1,99 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package etcd
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest/resttest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta3"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
func newHelper(t *testing.T) (*tools.FakeEtcdClient, tools.EtcdHelper) {
fakeEtcdClient := tools.NewFakeEtcdClient(t)
fakeEtcdClient.TestIndex = true
helper := tools.NewEtcdHelper(fakeEtcdClient, v1beta3.Codec)
return fakeEtcdClient, helper
}
func validNewPodTemplate(name string) *api.PodTemplate {
return &api.PodTemplate{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"test": "foo"},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
{
Name: "foo",
Image: "test",
ImagePullPolicy: api.PullAlways,
TerminationMessagePath: api.TerminationMessagePathDefault,
},
},
},
},
}
}
func TestCreate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewREST(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
pod := validNewPodTemplate("foo")
pod.ObjectMeta = api.ObjectMeta{}
test.TestCreate(
// valid
pod,
// invalid
&api.PodTemplate{
Template: api.PodTemplateSpec{},
},
)
}
func TestUpdate(t *testing.T) {
fakeEtcdClient, helper := newHelper(t)
storage := NewREST(helper)
test := resttest.New(t, storage, fakeEtcdClient.SetError)
fakeEtcdClient.ExpectNotFoundGet("/registry/podtemplates/default/foo")
fakeEtcdClient.ChangeIndex = 2
pod := validNewPodTemplate("foo")
existing := validNewPodTemplate("exists")
obj, err := storage.Create(api.NewDefaultContext(), existing)
if err != nil {
t.Fatalf("unable to create object: %v", err)
}
older := obj.(*api.PodTemplate)
older.ResourceVersion = "1"
test.TestUpdate(
pod,
existing,
older,
)
}

View File

@ -0,0 +1,81 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package podtemplate
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
errs "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
)
// podTemplateStrategy implements behavior for PodTemplates
type podTemplateStrategy struct {
runtime.ObjectTyper
api.NameGenerator
}
// Strategy is the default logic that applies when creating and updating PodTemplate
// objects via the REST API.
var Strategy = podTemplateStrategy{api.Scheme, api.SimpleNameGenerator}
// NamespaceScoped is true for pod templates.
func (podTemplateStrategy) NamespaceScoped() bool {
return true
}
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (podTemplateStrategy) PrepareForCreate(obj runtime.Object) {
_ = obj.(*api.PodTemplate)
}
// Validate validates a new pod template.
func (podTemplateStrategy) Validate(ctx api.Context, obj runtime.Object) errs.ValidationErrorList {
pod := obj.(*api.PodTemplate)
return validation.ValidatePodTemplate(pod)
}
// AllowCreateOnUpdate is false for pod templates.
func (podTemplateStrategy) AllowCreateOnUpdate() bool {
return false
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (podTemplateStrategy) PrepareForUpdate(obj, old runtime.Object) {
_ = obj.(*api.PodTemplate)
}
// ValidateUpdate is the default update validation for an end user.
func (podTemplateStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList {
return validation.ValidatePodTemplateUpdate(obj.(*api.PodTemplate), old.(*api.PodTemplate))
}
// MatchPodTemplate returns a generic matcher for a given label and field selector.
func MatchPodTemplate(label labels.Selector, field fields.Selector) generic.Matcher {
return generic.MatcherFunc(func(obj runtime.Object) (bool, error) {
podObj, ok := obj.(*api.PodTemplate)
if !ok {
return false, fmt.Errorf("not a pod template")
}
return label.Matches(labels.Set(podObj.Labels)), nil
})
}