Pod Templates

pull/6/head
Clayton Coleman 2015-03-03 19:54:17 -05:00
parent 8b04e7f737
commit 10c2ace6bf
15 changed files with 443 additions and 18 deletions

View File

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

View File

@ -0,0 +1,22 @@
{
"apiVersion": "v1beta3",
"kind": "PodTemplate",
"metadata": {
"name": "nginx"
},
"spec": {
"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}}" '' 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 # # Services #
############ ############

View File

@ -80,6 +80,10 @@ func TestRESTMapper(t *testing.T) {
t.Errorf("unexpected version mapping: %s %s %v", v, k, err) 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 { for _, version := range Versions {
mapping, err := RESTMapper.RESTMapping("ReplicationController", version) mapping, err := RESTMapper.RESTMapping("ReplicationController", version)
if err != nil { if err != nil {

View File

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

View File

@ -179,7 +179,7 @@ func (t *Tester) TestCreateRejectsMismatchedNamespace(valid runtime.Object) {
if err == nil { if err == nil {
t.Errorf("Expected an error, but we didn't get one") 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") { } 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 { if err == nil {
t.Errorf("Expected an error, but we didn't get one") 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") { } 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. // 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() seed := rand.Int63()
fuzzInternalObject(t, "", item, seed) fuzzInternalObject(t, "", item, seed)
roundTrip(t, v1beta1.Codec, item) if !set.Has("v1beta1") {
roundTrip(t, v1beta2.Codec, item) roundTrip(t, v1beta1.Codec, item)
fuzzInternalObject(t, "v1beta3", item, seed) }
roundTrip(t, v1beta3.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) { func roundTripAll(t *testing.T, item runtime.Object) {
@ -130,6 +137,10 @@ func TestList(t *testing.T) {
var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList") var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest", "ContainerManifestList")
var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions", "PodExecOptions") var nonInternalRoundTrippableTypes = util.NewStringSet("List", "ListOptions", "PodExecOptions")
var nonRoundTrippableTypesByVersion = map[string][]string{
"PodTemplate": {"v1beta1", "v1beta2"},
"PodTemplateList": {"v1beta1", "v1beta2"},
}
func TestRoundTripTypes(t *testing.T) { func TestRoundTripTypes(t *testing.T) {
// api.Scheme.Log(t) // api.Scheme.Log(t)
@ -148,7 +159,7 @@ func TestRoundTripTypes(t *testing.T) {
if _, err := meta.TypeAccessor(item); err != nil { 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) 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) { if !nonInternalRoundTrippableTypes.Has(kind) {
roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63())) roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63()))
} }

View File

@ -888,6 +888,24 @@ func ValidatePodStatusUpdate(newPod, oldPod *api.Pod) errs.ValidationErrorList {
return allErrs 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.Spec, 0).Prefix("spec")...)
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.Spec, 0).Prefix("spec")...)
return allErrs
}
var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone)) var supportedSessionAffinityType = util.NewStringSet(string(api.AffinityTypeClientIP), string(api.AffinityTypeNone))
// ValidateService tests if required fields in the service are set. // ValidateService tests if required fields in the service are set.

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 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 replicationControllerColumns = []string{"CONTROLLER", "CONTAINER(S)", "IMAGE(S)", "SELECTOR", "REPLICAS"}
var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT(S)"} var serviceColumns = []string{"NAME", "LABELS", "SELECTOR", "IP", "PORT(S)"}
var endpointColumns = []string{"NAME", "ENDPOINTS"} var endpointColumns = []string{"NAME", "ENDPOINTS"}
@ -263,6 +264,8 @@ var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"}
func (h *HumanReadablePrinter) addDefaultHandlers() { func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(podColumns, printPod) h.Handler(podColumns, printPod)
h.Handler(podColumns, printPodList) h.Handler(podColumns, printPodList)
h.Handler(podTemplateColumns, printPodTemplate)
h.Handler(podTemplateColumns, printPodTemplateList)
h.Handler(replicationControllerColumns, printReplicationController) h.Handler(replicationControllerColumns, printReplicationController)
h.Handler(replicationControllerColumns, printReplicationControllerList) h.Handler(replicationControllerColumns, printReplicationControllerList)
h.Handler(serviceColumns, printService) 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 { 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", _, 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.Name,
pod.Status.PodIP, pod.Status.PodIP,
@ -447,6 +445,40 @@ func printPodList(podList *api.PodList, w io.Writer) error {
return nil return nil
} }
func printPodTemplate(pod *api.PodTemplate, w io.Writer) error {
containers := pod.Spec.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.Spec.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 { func printReplicationController(controller *api.ReplicationController, w io.Writer) error {
containers := controller.Spec.Template.Spec.Containers containers := controller.Spec.Template.Spec.Containers
var firstContainer api.Container var firstContainer api.Container

View File

@ -58,6 +58,7 @@ import (
pvcetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolumeclaim/etcd" pvcetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/persistentvolumeclaim/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod"
podetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod/etcd" 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" resourcequotaetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/resourcequota/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/service"
@ -360,9 +361,18 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter)
// init initializes master. // init initializes master.
func (m *Master) init(c *Config) { 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) podStorage := podetcd.NewStorage(c.EtcdHelper, c.KubeletClient)
podRegistry := pod.NewRegistry(podStorage.Pod) podRegistry := pod.NewRegistry(podStorage.Pod)
podTemplateStorage := podtemplateetcd.NewREST(newerHelper)
eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())) eventRegistry := event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds()))
limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper) limitRangeRegistry := limitrange.NewEtcdRegistry(c.EtcdHelper)
@ -397,6 +407,8 @@ func (m *Master) init(c *Config) {
"pods/binding": podStorage.Binding, "pods/binding": podStorage.Binding,
"bindings": podStorage.Binding, "bindings": podStorage.Binding,
"podTemplates": podTemplateStorage,
"replicationControllers": controllerStorage, "replicationControllers": controllerStorage,
"services": service.NewStorage(m.serviceRegistry, m.nodeRegistry, m.endpointRegistry, m.portalNet, c.ClusterName), "services": service.NewStorage(m.serviceRegistry, m.nodeRegistry, m.endpointRegistry, m.portalNet, c.ClusterName),
"endpoints": endpointsStorage, "endpoints": endpointsStorage,
@ -606,6 +618,9 @@ func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
func (m *Master) api_v1beta1() *apiserver.APIGroupVersion { func (m *Master) api_v1beta1() *apiserver.APIGroupVersion {
storage := make(map[string]rest.Storage) storage := make(map[string]rest.Storage)
for k, v := range m.storage { for k, v := range m.storage {
if k == "podTemplates" {
continue
}
storage[k] = v storage[k] = v
} }
version := m.defaultAPIGroupVersion() version := m.defaultAPIGroupVersion()
@ -619,6 +634,9 @@ func (m *Master) api_v1beta1() *apiserver.APIGroupVersion {
func (m *Master) api_v1beta2() *apiserver.APIGroupVersion { func (m *Master) api_v1beta2() *apiserver.APIGroupVersion {
storage := make(map[string]rest.Storage) storage := make(map[string]rest.Storage)
for k, v := range m.storage { for k, v := range m.storage {
if k == "podTemplates" {
continue
}
storage[k] = v storage[k] = v
} }
version := m.defaultAPIGroupVersion() version := m.defaultAPIGroupVersion()

View File

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