diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index 40c12250792..7c7fa3c31fd 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -1686,6 +1686,244 @@ func TestValidatePSPVolumes(t *testing.T) { } } +func TestValidateNetworkPolicy(t *testing.T) { + successCases := []extensions.NetworkPolicy{ + { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{}, + Ports: []extensions.NetworkPolicyPort{}, + }, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + PodSelector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + }, + }, + }, + }, + }, + }, + { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + NamespaceSelector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + }, + }, + }, + }, + }, + }, + } + + // Success cases are expected to pass validation. + for k, v := range successCases { + if errs := ValidateNetworkPolicy(&v); len(errs) != 0 { + t.Errorf("Expected success for %d, got %v", k, errs) + } + } + + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + errorCases := map[string]extensions.NetworkPolicy{ + "namespaceSelector and podSelector": { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + PodSelector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + NamespaceSelector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + }, + }, + }, + }, + }, + }, + "invalid spec.podSelector": { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: invalidSelector, + }, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + NamespaceSelector: &unversioned.LabelSelector{ + MatchLabels: map[string]string{"c": "d"}, + }, + }, + }, + }, + }, + }, + }, + "invalid ingress.from.podSelector": { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{}, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + PodSelector: &unversioned.LabelSelector{ + MatchLabels: invalidSelector, + }, + }, + }, + }, + }, + }, + }, + "invalid ingress.from.namespaceSelector": { + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{}, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + NamespaceSelector: &unversioned.LabelSelector{ + MatchLabels: invalidSelector, + }, + }, + }, + }, + }, + }, + }, + } + + // Error cases are not expected to pass validation. + for testName, networkPolicy := range errorCases { + if errs := ValidateNetworkPolicy(&networkPolicy); len(errs) == 0 { + t.Errorf("Expected failure for test: %s", testName) + } + } +} + +func TestValidateNetworkPolicyUpdate(t *testing.T) { + type npUpdateTest struct { + old extensions.NetworkPolicy + update extensions.NetworkPolicy + } + successCases := []npUpdateTest{ + { + old: extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + update: extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + }, + } + + for _, successCase := range successCases { + successCase.old.ObjectMeta.ResourceVersion = "1" + successCase.update.ObjectMeta.ResourceVersion = "1" + if errs := ValidateNetworkPolicyUpdate(&successCase.update, &successCase.old); len(errs) != 0 { + t.Errorf("expected success: %v", errs) + } + } + errorCases := map[string]npUpdateTest{ + "change name": { + old: extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{}, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + update: extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{}, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + }, + "change spec": { + old: extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{}, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + update: extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "bar"}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{ + MatchLabels: map[string]string{"a": "b"}, + }, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + }, + } + + for testName, errorCase := range errorCases { + if errs := ValidateNetworkPolicyUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 { + t.Errorf("expected failure: %s", testName) + } + } +} + func newBool(val bool) *bool { p := new(bool) *p = val diff --git a/pkg/registry/networkpolicy/etcd/etcd_test.go b/pkg/registry/networkpolicy/etcd/etcd_test.go new file mode 100644 index 00000000000..7d2e456032a --- /dev/null +++ b/pkg/registry/networkpolicy/etcd/etcd_test.go @@ -0,0 +1,174 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package etcd + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/registrytest" + "k8s.io/kubernetes/pkg/runtime" + etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing" + "k8s.io/kubernetes/pkg/util/intstr" +) + +func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) { + etcdStorage, server := registrytest.NewEtcdStorage(t, "extensions") + restOptions := generic.RESTOptions{Storage: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1} + return NewREST(restOptions), server +} + +// createNetworkPolicy is a helper function that returns a NetworkPolicy with the updated resource version. +func createNetworkPolicy(storage *REST, np extensions.NetworkPolicy, t *testing.T) (extensions.NetworkPolicy, error) { + ctx := api.WithNamespace(api.NewContext(), np.Namespace) + obj, err := storage.Create(ctx, &np) + if err != nil { + t.Errorf("Failed to create NetworkPolicy, %v", err) + } + newNP := obj.(*extensions.NetworkPolicy) + return *newNP, nil +} + +func validNewNetworkPolicy() *extensions.NetworkPolicy { + port := intstr.FromInt(80) + return &extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + Labels: map[string]string{"a": "b"}, + }, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, + Ingress: []extensions.NetworkPolicyIngressRule{ + { + From: []extensions.NetworkPolicyPeer{ + { + PodSelector: &unversioned.LabelSelector{MatchLabels: map[string]string{"c": "d"}}, + }, + }, + Ports: []extensions.NetworkPolicyPort{ + { + Port: &port, + }, + }, + }, + }, + }, + } +} + +var validNetworkPolicy = *validNewNetworkPolicy() + +func TestCreate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store) + np := validNewNetworkPolicy() + np.ObjectMeta = api.ObjectMeta{} + + invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"} + test.TestCreate( + // valid + np, + // invalid (invalid selector) + &extensions.NetworkPolicy{ + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{MatchLabels: invalidSelector}, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + }, + ) +} + +func TestUpdate(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store) + test.TestUpdate( + // valid + validNewNetworkPolicy(), + // valid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.NetworkPolicy) + return object + }, + // invalid updateFunc + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.NetworkPolicy) + object.Name = "" + return object + }, + func(obj runtime.Object) runtime.Object { + object := obj.(*extensions.NetworkPolicy) + object.Spec.PodSelector = unversioned.LabelSelector{MatchLabels: map[string]string{}} + return object + }, + ) +} + +func TestDelete(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store) + test.TestDelete(validNewNetworkPolicy()) +} + +func TestGet(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store) + test.TestGet(validNewNetworkPolicy()) +} + +func TestList(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store) + test.TestList(validNewNetworkPolicy()) +} + +func TestWatch(t *testing.T) { + storage, server := newStorage(t) + defer server.Terminate(t) + test := registrytest.New(t, storage.Store) + test.TestWatch( + validNewNetworkPolicy(), + // matching labels + []labels.Set{ + {"a": "b"}, + }, + // not matching labels + []labels.Set{ + {"a": "c"}, + {"foo": "bar"}, + }, + // matching fields + []fields.Set{ + {"metadata.name": "foo"}, + }, + // not matchin fields + []fields.Set{ + {"metadata.name": "bar"}, + {"name": "foo"}, + }, + ) +} diff --git a/pkg/registry/networkpolicy/strategy_test.go b/pkg/registry/networkpolicy/strategy_test.go new file mode 100644 index 00000000000..b3a95d3de6d --- /dev/null +++ b/pkg/registry/networkpolicy/strategy_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package networkpolicy + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/extensions" +) + +func TestNetworkPolicyStrategy(t *testing.T) { + ctx := api.NewDefaultContext() + if !Strategy.NamespaceScoped() { + t.Errorf("NetworkPolicy must be namespace scoped") + } + if Strategy.AllowCreateOnUpdate() { + t.Errorf("NetworkPolicy should not allow create on update") + } + + validMatchLabels := map[string]string{"a": "b"} + np := &extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: api.NamespaceDefault}, + Spec: extensions.NetworkPolicySpec{ + PodSelector: unversioned.LabelSelector{MatchLabels: validMatchLabels}, + Ingress: []extensions.NetworkPolicyIngressRule{}, + }, + } + + Strategy.PrepareForCreate(np) + errs := Strategy.Validate(ctx, np) + if len(errs) != 0 { + t.Errorf("Unexpected error validating %v", errs) + } + + invalidNp := &extensions.NetworkPolicy{ + ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "4"}, + } + Strategy.PrepareForUpdate(invalidNp, np) + errs = Strategy.ValidateUpdate(ctx, invalidNp, np) + if len(errs) == 0 { + t.Errorf("Expected a validation error") + } + if invalidNp.ResourceVersion != "4" { + t.Errorf("Incoming resource version on update should not be mutated") + } +}