mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
Merge pull request #13347 from nikhiljindal/expDeployment
Auto commit by PR queue bot
This commit is contained in:
commit
044e4854e1
@ -445,7 +445,7 @@ func (t *Tester) testUpdateInvokesValidation(obj runtime.Object, setFn SetFunc,
|
|||||||
toUpdate := update(copyOrDie(foo))
|
toUpdate := update(copyOrDie(foo))
|
||||||
got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdate)
|
got, created, err := t.storage.(rest.Updater).Update(t.TestContext(), toUpdate)
|
||||||
if got != nil || created {
|
if got != nil || created {
|
||||||
t.Errorf("expected nil object and no creation")
|
t.Errorf("expected nil object and no creation for object: %v", toUpdate)
|
||||||
}
|
}
|
||||||
if !errors.IsInvalid(err) && !errors.IsBadRequest(err) {
|
if !errors.IsInvalid(err) && !errors.IsBadRequest(err) {
|
||||||
t.Errorf("expected invalid or bad request error, got %v", err)
|
t.Errorf("expected invalid or bad request error, got %v", err)
|
||||||
|
@ -212,7 +212,9 @@ func convert_v1_DeploymentSpec_To_expapi_DeploymentSpec(in *DeploymentSpec, out
|
|||||||
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
|
||||||
defaulting.(func(*DeploymentSpec))(in)
|
defaulting.(func(*DeploymentSpec))(in)
|
||||||
}
|
}
|
||||||
|
if in.Replicas != nil {
|
||||||
out.Replicas = *in.Replicas
|
out.Replicas = *in.Replicas
|
||||||
|
}
|
||||||
if in.Selector != nil {
|
if in.Selector != nil {
|
||||||
out.Selector = make(map[string]string)
|
out.Selector = make(map[string]string)
|
||||||
for key, val := range in.Selector {
|
for key, val := range in.Selector {
|
||||||
|
@ -49,6 +49,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/master/ports"
|
"k8s.io/kubernetes/pkg/master/ports"
|
||||||
"k8s.io/kubernetes/pkg/registry/componentstatus"
|
"k8s.io/kubernetes/pkg/registry/componentstatus"
|
||||||
controlleretcd "k8s.io/kubernetes/pkg/registry/controller/etcd"
|
controlleretcd "k8s.io/kubernetes/pkg/registry/controller/etcd"
|
||||||
|
deploymentetcd "k8s.io/kubernetes/pkg/registry/deployment/etcd"
|
||||||
"k8s.io/kubernetes/pkg/registry/endpoint"
|
"k8s.io/kubernetes/pkg/registry/endpoint"
|
||||||
endpointsetcd "k8s.io/kubernetes/pkg/registry/endpoint/etcd"
|
endpointsetcd "k8s.io/kubernetes/pkg/registry/endpoint/etcd"
|
||||||
eventetcd "k8s.io/kubernetes/pkg/registry/event/etcd"
|
eventetcd "k8s.io/kubernetes/pkg/registry/event/etcd"
|
||||||
@ -770,6 +771,7 @@ func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion {
|
|||||||
autoscalerStorage := horizontalpodautoscaleretcd.NewREST(c.ExpDatabaseStorage)
|
autoscalerStorage := horizontalpodautoscaleretcd.NewREST(c.ExpDatabaseStorage)
|
||||||
thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(c.ExpDatabaseStorage)
|
thirdPartyResourceStorage := thirdpartyresourceetcd.NewREST(c.ExpDatabaseStorage)
|
||||||
daemonStorage := daemonetcd.NewREST(c.ExpDatabaseStorage)
|
daemonStorage := daemonetcd.NewREST(c.ExpDatabaseStorage)
|
||||||
|
deploymentStorage := deploymentetcd.NewREST(c.ExpDatabaseStorage)
|
||||||
|
|
||||||
storage := map[string]rest.Storage{
|
storage := map[string]rest.Storage{
|
||||||
strings.ToLower("replicationControllers"): controllerStorage.ReplicationController,
|
strings.ToLower("replicationControllers"): controllerStorage.ReplicationController,
|
||||||
@ -777,6 +779,7 @@ func (m *Master) expapi(c *Config) *apiserver.APIGroupVersion {
|
|||||||
strings.ToLower("horizontalpodautoscalers"): autoscalerStorage,
|
strings.ToLower("horizontalpodautoscalers"): autoscalerStorage,
|
||||||
strings.ToLower("thirdpartyresources"): thirdPartyResourceStorage,
|
strings.ToLower("thirdpartyresources"): thirdPartyResourceStorage,
|
||||||
strings.ToLower("daemons"): daemonStorage,
|
strings.ToLower("daemons"): daemonStorage,
|
||||||
|
strings.ToLower("deployments"): deploymentStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &apiserver.APIGroupVersion{
|
return &apiserver.APIGroupVersion{
|
||||||
|
17
pkg/registry/deployment/doc.go
Normal file
17
pkg/registry/deployment/doc.go
Normal 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 deployment
|
71
pkg/registry/deployment/etcd/etcd.go
Normal file
71
pkg/registry/deployment/etcd/etcd.go
Normal 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 etcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/expapi"
|
||||||
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/deployment"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/generic"
|
||||||
|
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
type REST struct {
|
||||||
|
*etcdgeneric.Etcd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewREST returns a RESTStorage object that will work against deployments.
|
||||||
|
func NewREST(s storage.Interface) *REST {
|
||||||
|
prefix := "/deployments"
|
||||||
|
store := &etcdgeneric.Etcd{
|
||||||
|
NewFunc: func() runtime.Object { return &expapi.Deployment{} },
|
||||||
|
// NewListFunc returns an object capable of storing results of an etcd list.
|
||||||
|
NewListFunc: func() runtime.Object { return &expapi.DeploymentList{} },
|
||||||
|
// Produces a path 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 path 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 deployment.
|
||||||
|
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
||||||
|
return obj.(*expapi.Deployment).Name, nil
|
||||||
|
},
|
||||||
|
// Used to match objects based on labels/fields for list.
|
||||||
|
PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher {
|
||||||
|
return deployment.MatchDeployment(label, field)
|
||||||
|
},
|
||||||
|
EndpointName: "deployments",
|
||||||
|
|
||||||
|
// Used to validate deployment creation.
|
||||||
|
CreateStrategy: deployment.Strategy,
|
||||||
|
|
||||||
|
// Used to validate deployment updates.
|
||||||
|
UpdateStrategy: deployment.Strategy,
|
||||||
|
|
||||||
|
Storage: s,
|
||||||
|
}
|
||||||
|
return &REST{store}
|
||||||
|
}
|
408
pkg/registry/deployment/etcd/etcd_test.go
Executable file
408
pkg/registry/deployment/etcd/etcd_test.go
Executable file
@ -0,0 +1,408 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-etcd/etcd"
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/rest/resttest"
|
||||||
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
|
"k8s.io/kubernetes/pkg/expapi"
|
||||||
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
|
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
|
||||||
|
"k8s.io/kubernetes/pkg/tools"
|
||||||
|
"k8s.io/kubernetes/pkg/tools/etcdtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PASS = iota
|
||||||
|
FAIL
|
||||||
|
)
|
||||||
|
|
||||||
|
func newStorage(t *testing.T) (*REST, *tools.FakeEtcdClient) {
|
||||||
|
etcdStorage, fakeClient := registrytest.NewEtcdStorage(t)
|
||||||
|
return NewREST(etcdStorage), fakeClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func validNewDeployment() *expapi.Deployment {
|
||||||
|
return &expapi.Deployment{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: expapi.DeploymentSpec{
|
||||||
|
Selector: 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var validDeployment = *validNewDeployment()
|
||||||
|
|
||||||
|
func TestCreate(t *testing.T) {
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
test := resttest.New(t, storage, fakeClient.SetError)
|
||||||
|
deployment := validNewDeployment()
|
||||||
|
deployment.ObjectMeta = api.ObjectMeta{}
|
||||||
|
test.TestCreate(
|
||||||
|
// valid
|
||||||
|
deployment,
|
||||||
|
func(ctx api.Context, obj runtime.Object) error {
|
||||||
|
return registrytest.SetObject(fakeClient, storage.KeyFunc, ctx, obj)
|
||||||
|
},
|
||||||
|
func(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return registrytest.GetObject(fakeClient, storage.KeyFunc, storage.NewFunc, ctx, obj)
|
||||||
|
},
|
||||||
|
// invalid (invalid selector)
|
||||||
|
&expapi.Deployment{
|
||||||
|
Spec: expapi.DeploymentSpec{
|
||||||
|
Selector: map[string]string{},
|
||||||
|
Template: validDeployment.Spec.Template,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
test := resttest.New(t, storage, fakeClient.SetError)
|
||||||
|
test.TestUpdate(
|
||||||
|
// valid
|
||||||
|
validNewDeployment(),
|
||||||
|
func(ctx api.Context, obj runtime.Object) error {
|
||||||
|
return registrytest.SetObject(fakeClient, storage.KeyFunc, ctx, obj)
|
||||||
|
},
|
||||||
|
func(resourceVersion uint64) {
|
||||||
|
registrytest.SetResourceVersion(fakeClient, resourceVersion)
|
||||||
|
},
|
||||||
|
func(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
|
||||||
|
return registrytest.GetObject(fakeClient, storage.KeyFunc, storage.NewFunc, ctx, obj)
|
||||||
|
},
|
||||||
|
// updateFunc
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*expapi.Deployment)
|
||||||
|
object.Spec.Template.Spec.NodeSelector = map[string]string{"c": "d"}
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
// invalid updateFunc
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*expapi.Deployment)
|
||||||
|
object.UID = "newUID"
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*expapi.Deployment)
|
||||||
|
object.Name = ""
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*expapi.Deployment)
|
||||||
|
object.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
func(obj runtime.Object) runtime.Object {
|
||||||
|
object := obj.(*expapi.Deployment)
|
||||||
|
object.Spec.Selector = map[string]string{}
|
||||||
|
return object
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcdGet(t *testing.T) {
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
test := resttest.New(t, storage, fakeClient.SetError)
|
||||||
|
test.TestGet(validNewDeployment())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcdList(t *testing.T) {
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
test := resttest.New(t, storage, fakeClient.SetError)
|
||||||
|
key := etcdtest.AddPrefix(storage.KeyRootFunc(test.TestContext()))
|
||||||
|
test.TestList(
|
||||||
|
validNewDeployment(),
|
||||||
|
func(objects []runtime.Object) []runtime.Object {
|
||||||
|
return registrytest.SetObjectsForKey(fakeClient, key, objects)
|
||||||
|
},
|
||||||
|
func(resourceVersion uint64) {
|
||||||
|
registrytest.SetResourceVersion(fakeClient, resourceVersion)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcdDelete(t *testing.T) {
|
||||||
|
ctx := api.NewDefaultContext()
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
key, err := storage.KeyFunc(ctx, validDeployment.Name)
|
||||||
|
key = etcdtest.AddPrefix(key)
|
||||||
|
|
||||||
|
fakeClient.Set(key, runtime.EncodeOrDie(testapi.Codec(), validNewDeployment()), 0)
|
||||||
|
obj, err := storage.Delete(ctx, validDeployment.Name, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if status, ok := obj.(*api.Status); !ok {
|
||||||
|
t.Errorf("Expected status of delete, got %#v", status)
|
||||||
|
} else if status.Status != api.StatusSuccess {
|
||||||
|
t.Errorf("Expected success, got %#v", status.Status)
|
||||||
|
}
|
||||||
|
if len(fakeClient.DeletedKeys) != 1 {
|
||||||
|
t.Errorf("Expected 1 delete, found %#v", fakeClient.DeletedKeys)
|
||||||
|
}
|
||||||
|
if fakeClient.DeletedKeys[0] != key {
|
||||||
|
t.Errorf("Unexpected key: %s, expected %s", fakeClient.DeletedKeys[0], key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcdWatch(t *testing.T) {
|
||||||
|
ctx := api.NewDefaultContext()
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
watching, err := storage.Watch(ctx,
|
||||||
|
labels.Everything(),
|
||||||
|
fields.Everything(),
|
||||||
|
"1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-watching.ResultChan():
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("watching channel should be open")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
fakeClient.WatchInjectError <- nil
|
||||||
|
if _, ok := <-watching.ResultChan(); ok {
|
||||||
|
t.Errorf("watching channel should be closed")
|
||||||
|
}
|
||||||
|
watching.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that we can watch for the creation of Deployment with specified labels.
|
||||||
|
func TestEtcdWatchWithLabels(t *testing.T) {
|
||||||
|
ctx := api.WithNamespace(api.NewDefaultContext(), validDeployment.Namespace)
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
fakeClient.ExpectNotFoundGet(etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/pods"))
|
||||||
|
|
||||||
|
watching, err := storage.Watch(ctx,
|
||||||
|
labels.SelectorFromSet(validDeployment.Spec.Selector),
|
||||||
|
fields.Everything(),
|
||||||
|
"1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
|
||||||
|
// The watcher above is waiting for these Labels, on receiving them it should
|
||||||
|
// apply the deploymentStatus decorator, which lists pods, causing a query against
|
||||||
|
// the /registry/pods endpoint of the etcd client.
|
||||||
|
deployment := &expapi.Deployment{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: validDeployment.Spec.Selector,
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deploymentBytes, _ := testapi.Codec().Encode(deployment)
|
||||||
|
fakeClient.WatchResponse <- &etcd.Response{
|
||||||
|
Action: "create",
|
||||||
|
Node: &etcd.Node{
|
||||||
|
Value: string(deploymentBytes),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case _, ok := <-watching.ResultChan():
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("watching channel should be open")
|
||||||
|
}
|
||||||
|
case <-time.After(time.Millisecond * 100):
|
||||||
|
t.Error("unexpected timeout from result channel")
|
||||||
|
}
|
||||||
|
watching.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that we can watch for Deployment with specified fields.
|
||||||
|
func TestEtcdWatchWithFields(t *testing.T) {
|
||||||
|
ctx := api.WithNamespace(api.NewDefaultContext(), validDeployment.Namespace)
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
fakeClient.ExpectNotFoundGet(etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/pods"))
|
||||||
|
|
||||||
|
testFieldMap := map[int][]fields.Set{
|
||||||
|
PASS: {
|
||||||
|
{"metadata.name": "foo"},
|
||||||
|
},
|
||||||
|
FAIL: {
|
||||||
|
{"metadata.name": "bar"},
|
||||||
|
{"name": "foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testEtcdActions := []string{
|
||||||
|
etcdstorage.EtcdCreate,
|
||||||
|
etcdstorage.EtcdSet,
|
||||||
|
etcdstorage.EtcdCAS,
|
||||||
|
etcdstorage.EtcdDelete}
|
||||||
|
|
||||||
|
deployment := &expapi.Deployment{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Labels: validDeployment.Spec.Selector,
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Status: expapi.DeploymentStatus{
|
||||||
|
Replicas: 1,
|
||||||
|
UpdatedReplicas: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deploymentBytes, _ := testapi.Codec().Encode(deployment)
|
||||||
|
|
||||||
|
for expectedResult, fieldSet := range testFieldMap {
|
||||||
|
for _, field := range fieldSet {
|
||||||
|
for _, action := range testEtcdActions {
|
||||||
|
watching, err := storage.Watch(ctx,
|
||||||
|
labels.Everything(),
|
||||||
|
field.AsSelector(),
|
||||||
|
"1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
var prevNode *etcd.Node = nil
|
||||||
|
node := &etcd.Node{
|
||||||
|
Value: string(deploymentBytes),
|
||||||
|
}
|
||||||
|
if action == etcdstorage.EtcdDelete {
|
||||||
|
prevNode = node
|
||||||
|
}
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
fakeClient.WatchResponse <- &etcd.Response{
|
||||||
|
Action: action,
|
||||||
|
Node: node,
|
||||||
|
PrevNode: prevNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case r, ok := <-watching.ResultChan():
|
||||||
|
if expectedResult == FAIL {
|
||||||
|
t.Errorf("Unexpected result from channel %#v. Field: %v", r, field)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("watching channel should be open")
|
||||||
|
}
|
||||||
|
case <-time.After(time.Millisecond * 100):
|
||||||
|
if expectedResult == PASS {
|
||||||
|
t.Error("unexpected timeout from result channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watching.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEtcdWatchNotMatch(t *testing.T) {
|
||||||
|
ctx := api.NewDefaultContext()
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
fakeClient.ExpectNotFoundGet(etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/pods"))
|
||||||
|
|
||||||
|
watching, err := storage.Watch(ctx,
|
||||||
|
labels.SelectorFromSet(labels.Set{"name": "foo"}),
|
||||||
|
fields.Everything(),
|
||||||
|
"1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
|
||||||
|
deployment := &expapi.Deployment{
|
||||||
|
ObjectMeta: api.ObjectMeta{
|
||||||
|
Name: "bar",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"name": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
deploymentBytes, _ := testapi.Codec().Encode(deployment)
|
||||||
|
fakeClient.WatchResponse <- &etcd.Response{
|
||||||
|
Action: "create",
|
||||||
|
Node: &etcd.Node{
|
||||||
|
Value: string(deploymentBytes),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-watching.ResultChan():
|
||||||
|
t.Error("unexpected result from result channel")
|
||||||
|
case <-time.After(time.Millisecond * 100):
|
||||||
|
// expected case
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
ctx := api.NewDefaultContext()
|
||||||
|
storage, fakeClient := newStorage(t)
|
||||||
|
test := resttest.New(t, storage, fakeClient.SetError)
|
||||||
|
key, _ := storage.KeyFunc(ctx, validDeployment.Name)
|
||||||
|
key = etcdtest.AddPrefix(key)
|
||||||
|
|
||||||
|
createFn := func() runtime.Object {
|
||||||
|
dc := validNewDeployment()
|
||||||
|
dc.ResourceVersion = "1"
|
||||||
|
fakeClient.Data[key] = tools.EtcdResponseWithError{
|
||||||
|
R: &etcd.Response{
|
||||||
|
Node: &etcd.Node{
|
||||||
|
Value: runtime.EncodeOrDie(testapi.Codec(), dc),
|
||||||
|
ModifiedIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return dc
|
||||||
|
}
|
||||||
|
gracefulSetFn := func() bool {
|
||||||
|
// If the deployment is still around after trying to delete either the delete
|
||||||
|
// failed, or we're deleting it gracefully.
|
||||||
|
if fakeClient.Data[key].R.Node != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
test.TestDelete(createFn, gracefulSetFn)
|
||||||
|
}
|
97
pkg/registry/deployment/strategy.go
Normal file
97
pkg/registry/deployment/strategy.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
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 deployment
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/expapi"
|
||||||
|
"k8s.io/kubernetes/pkg/expapi/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/fields"
|
||||||
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
|
"k8s.io/kubernetes/pkg/registry/generic"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
errs "k8s.io/kubernetes/pkg/util/fielderrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deploymentStrategy implements behavior for Deployments.
|
||||||
|
type deploymentStrategy struct {
|
||||||
|
runtime.ObjectTyper
|
||||||
|
api.NameGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy is the default logic that applies when creating and updating Deployment
|
||||||
|
// objects via the REST API.
|
||||||
|
var Strategy = deploymentStrategy{api.Scheme, api.SimpleNameGenerator}
|
||||||
|
|
||||||
|
// NamespaceScoped is true for deployment.
|
||||||
|
func (deploymentStrategy) NamespaceScoped() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
|
||||||
|
func (deploymentStrategy) PrepareForCreate(obj runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates a new deployment.
|
||||||
|
func (deploymentStrategy) Validate(ctx api.Context, obj runtime.Object) errs.ValidationErrorList {
|
||||||
|
deployment := obj.(*expapi.Deployment)
|
||||||
|
return validation.ValidateDeployment(deployment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllowCreateOnUpdate is false for deployments.
|
||||||
|
func (deploymentStrategy) AllowCreateOnUpdate() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||||
|
func (deploymentStrategy) PrepareForUpdate(obj, old runtime.Object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
|
func (deploymentStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) errs.ValidationErrorList {
|
||||||
|
return validation.ValidateDeploymentUpdate(old.(*expapi.Deployment), obj.(*expapi.Deployment))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (deploymentStrategy) AllowUnconditionalUpdate() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeploymentToSelectableFields returns a field set that represents the object.
|
||||||
|
func DeploymentToSelectableFields(deployment *expapi.Deployment) fields.Set {
|
||||||
|
return fields.Set{
|
||||||
|
"metadata.name": deployment.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchDeployment is the filter used by the generic etcd backend to route
|
||||||
|
// watch events from etcd to clients of the apiserver only interested in specific
|
||||||
|
// labels/fields.
|
||||||
|
func MatchDeployment(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) {
|
||||||
|
deployment, ok := obj.(*expapi.Deployment)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("given object is not a deployment.")
|
||||||
|
}
|
||||||
|
return labels.Set(deployment.ObjectMeta.Labels), DeploymentToSelectableFields(deployment), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user