Skip to content

Commit e64bbd1

Browse files
Jay Boydpmorie
Jay Boyd
authored andcommitted
default plan admission controller: filter list of service plans/service classes by the class name (openshift#1351)
* filter list of service plans/service classes by the specified service class name * add success test determining default plan when there are multiple plans but each from a different classes
1 parent 6648c0e commit e64bbd1

File tree

2 files changed

+97
-47
lines changed

2 files changed

+97
-47
lines changed

plugin/pkg/admission/serviceplan/defaultserviceplan/admission.go

+43-36
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,11 @@ import (
2525

2626
apierrors "k8s.io/apimachinery/pkg/api/errors"
2727
apimachineryv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28-
"k8s.io/apimachinery/pkg/labels"
28+
"k8s.io/apimachinery/pkg/fields"
2929
"k8s.io/apiserver/pkg/admission"
3030

3131
"github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/internalclientset"
3232
servicecataloginternalversion "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/internalclientset/typed/servicecatalog/internalversion"
33-
informers "github.com/kubernetes-incubator/service-catalog/pkg/client/informers_generated/internalversion"
34-
internalversion "github.com/kubernetes-incubator/service-catalog/pkg/client/listers_generated/servicecatalog/internalversion"
3533

3634
"github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog"
3735
scadmission "github.com/kubernetes-incubator/service-catalog/pkg/apiserver/admission"
@@ -57,17 +55,12 @@ func Register(plugins *admission.Plugins) {
5755
type defaultServicePlan struct {
5856
*admission.Handler
5957
scClient servicecataloginternalversion.ClusterServiceClassInterface
60-
spLister internalversion.ClusterServicePlanLister
58+
spClient servicecataloginternalversion.ClusterServicePlanInterface
6159
}
6260

63-
var _ = scadmission.WantsInternalServiceCatalogInformerFactory(&defaultServicePlan{})
6461
var _ = scadmission.WantsInternalServiceCatalogClientSet(&defaultServicePlan{})
6562

6663
func (d *defaultServicePlan) Admit(a admission.Attributes) error {
67-
// we need to wait for our caches to warm
68-
if !d.WaitForReady() {
69-
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
70-
}
7164

7265
// We only care about service Instances
7366
if a.GetResource().Group != servicecatalog.GroupName || a.GetResource().GroupResource() != servicecatalog.Resource("serviceinstances") {
@@ -85,7 +78,7 @@ func (d *defaultServicePlan) Admit(a admission.Attributes) error {
8578
}
8679

8780
// cannot find what we're trying to create an instance of
88-
sc, err := d.getServiceClassByExternalName(a, instance.Spec.ExternalClusterServiceClassName)
81+
sc, err := d.getClusterServiceClassByExternalName(a, instance.Spec.ExternalClusterServiceClassName)
8982
if err != nil {
9083
if !apierrors.IsNotFound(err) {
9184
return admission.NewForbidden(a, err)
@@ -103,11 +96,12 @@ func (d *defaultServicePlan) Admit(a admission.Attributes) error {
10396
// the order changes, we will need to rethink the
10497
// implementation of this controller.
10598

106-
// implement field selector
107-
108-
// loop over all service plans accumulate into slice
109-
plans, err := d.spLister.List(labels.Everything())
110-
// TODO filter `plans` down to only those owned by `sc`.
99+
plans, err := d.getClusterServicePlansByClusterServiceClassName(sc.Name)
100+
if err != nil {
101+
msg := fmt.Sprintf("Error listing plans for service class (K8S: %v ExternalName: %v) - retry and specify desired ClusterServicePlan", sc.Name, instance.Spec.ExternalClusterServiceClassName)
102+
glog.V(4).Info(msg)
103+
return admission.NewForbidden(a, errors.New(msg))
104+
}
111105

112106
// check if there were any service plans
113107
// TODO: in combination with not allowing classes with no plans, this should be impossible
@@ -144,42 +138,55 @@ func NewDefaultClusterServicePlan() (admission.Interface, error) {
144138

145139
func (d *defaultServicePlan) SetInternalServiceCatalogClientSet(f internalclientset.Interface) {
146140
d.scClient = f.Servicecatalog().ClusterServiceClasses()
147-
}
148-
149-
func (d *defaultServicePlan) SetInternalServiceCatalogInformerFactory(f informers.SharedInformerFactory) {
150-
spInformer := f.Servicecatalog().InternalVersion().ClusterServicePlans()
151-
d.spLister = spInformer.Lister()
152-
153-
readyFunc := func() bool {
154-
return spInformer.Informer().HasSynced()
155-
}
156-
157-
d.SetReadyFunc(readyFunc)
141+
d.spClient = f.Servicecatalog().ClusterServicePlans()
158142
}
159143

160144
func (d *defaultServicePlan) Validate() error {
161145
if d.scClient == nil {
162146
return errors.New("missing service class interface")
163147
}
164-
if d.spLister == nil {
165-
return errors.New("missing service plan lister")
148+
if d.spClient == nil {
149+
return errors.New("missing serviceplan interface")
166150
}
167151
return nil
168152
}
169153

170-
func (d *defaultServicePlan) getServiceClassByExternalName(a admission.Attributes, scName string) (*servicecatalog.ClusterServiceClass, error) {
171-
glog.V(4).Infof("Fetching serviceclass as %q", scName)
172-
listOpts := apimachineryv1.ListOptions{FieldSelector: "spec.externalName==" + scName}
173-
servicePlans, err := d.scClient.List(listOpts)
154+
func (d *defaultServicePlan) getClusterServiceClassByExternalName(a admission.Attributes, scName string) (*servicecatalog.ClusterServiceClass, error) {
155+
glog.V(4).Infof("Fetching serviceclass filtered by class external name %q", scName)
156+
fieldSet := fields.Set{
157+
"spec.externalName": scName,
158+
}
159+
fieldSelector := fields.SelectorFromSet(fieldSet).String()
160+
listOpts := apimachineryv1.ListOptions{FieldSelector: fieldSelector}
161+
serviceClasses, err := d.scClient.List(listOpts)
174162
if err != nil {
175163
glog.V(4).Infof("List failed %q", err)
176164
return nil, err
177165
}
178-
if len(servicePlans.Items) == 1 {
179-
glog.V(4).Infof("Found Single item as %+v", servicePlans.Items[0])
180-
return &servicePlans.Items[0], nil
166+
if len(serviceClasses.Items) == 1 {
167+
glog.V(4).Infof("Found Single item as %+v", serviceClasses.Items[0])
168+
return &serviceClasses.Items[0], nil
181169
}
182-
msg := fmt.Sprintf("Could not find a single ServiceClass with name %q", scName)
170+
msg := fmt.Sprintf("Could not find a single ServiceClass with name %q, found %v", scName, len(serviceClasses.Items))
183171
glog.V(4).Info(msg)
184172
return nil, admission.NewNotFound(a)
185173
}
174+
175+
// getClusterServicePlansByClusterServiceClassName() returns a list of
176+
// ServicePlans for the specified service class name
177+
func (d *defaultServicePlan) getClusterServicePlansByClusterServiceClassName(scName string) ([]servicecatalog.ClusterServicePlan, error) {
178+
glog.V(4).Infof("Fetching ClusterServicePlans by class name %q", scName)
179+
fieldSet := fields.Set{
180+
"spec.clusterServiceClassRef.name": scName,
181+
}
182+
fieldSelector := fields.SelectorFromSet(fieldSet).String()
183+
listOpts := apimachineryv1.ListOptions{FieldSelector: fieldSelector}
184+
servicePlans, err := d.spClient.List(listOpts)
185+
if err != nil {
186+
glog.Infof("List failed %q", err)
187+
return nil, err
188+
}
189+
glog.V(4).Infof("plans fetched by filtering classname: %+v", servicePlans.Items)
190+
r := servicePlans.Items
191+
return r, err
192+
}

plugin/pkg/admission/serviceplan/defaultserviceplan/admission_test.go

+54-11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/golang/glog"
2626

27+
"k8s.io/api/core/v1"
2728
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2829
"k8s.io/apimachinery/pkg/runtime"
2930
"k8s.io/apimachinery/pkg/util/wait"
@@ -52,7 +53,7 @@ func newHandlerForTest(internalClient internalclientset.Interface) (admission.In
5253

5354
// newFakeServiceCatalogClientForTest creates a fake clientset that returns a
5455
// ClusterServiceClassList with the given ClusterServiceClass as the single list item.
55-
func newFakeServiceCatalogClientForTest(sc *servicecatalog.ClusterServiceClass, sps []*servicecatalog.ClusterServicePlan) *fake.Clientset {
56+
func newFakeServiceCatalogClientForTest(sc *servicecatalog.ClusterServiceClass, sps []*servicecatalog.ClusterServicePlan, classFilter string) *fake.Clientset {
5657
fakeClient := &fake.Clientset{}
5758

5859
// react to the given service classes
@@ -74,7 +75,9 @@ func newFakeServiceCatalogClientForTest(sc *servicecatalog.ClusterServiceClass,
7475
ResourceVersion: "1",
7576
}}
7677
for _, sp := range sps {
77-
spList.Items = append(spList.Items, *sp)
78+
if classFilter == "" || classFilter == sp.Spec.ClusterServiceClassRef.Name {
79+
spList.Items = append(spList.Items, *sp)
80+
}
7881
}
7982
fakeClient.AddReactor("list", "clusterserviceplans", func(action core.Action) (bool, runtime.Object, error) {
8083
return true, spList, nil
@@ -104,21 +107,30 @@ func newClusterServiceClass(id string, name string) *servicecatalog.ClusterServi
104107
return sc
105108
}
106109

107-
// newClusterServiceClass returns a new serviceclass.
108-
func newClusterServicePlans(count uint) []*servicecatalog.ClusterServicePlan {
110+
// newClusterServicePlans returns new serviceplans.
111+
func newClusterServicePlans(count uint, useDifferentClasses bool) []*servicecatalog.ClusterServicePlan {
112+
classname := "test-serviceclass"
109113
sp1 := &servicecatalog.ClusterServicePlan{
110114
ObjectMeta: metav1.ObjectMeta{Name: "bar-id"},
111115
Spec: servicecatalog.ClusterServicePlanSpec{
112116
ExternalName: "bar",
113117
ExternalID: "12345",
118+
ClusterServiceClassRef: v1.LocalObjectReference{
119+
Name: classname,
120+
},
114121
},
115122
}
116-
123+
if useDifferentClasses {
124+
classname = "different-serviceclass"
125+
}
117126
sp2 := &servicecatalog.ClusterServicePlan{
118127
ObjectMeta: metav1.ObjectMeta{Name: "baz-id"},
119128
Spec: servicecatalog.ClusterServicePlanSpec{
120129
ExternalName: "baz",
121130
ExternalID: "23456",
131+
ClusterServiceClassRef: v1.LocalObjectReference{
132+
Name: classname,
133+
},
122134
},
123135
}
124136

@@ -157,7 +169,7 @@ func TestWithListFailure(t *testing.T) {
157169
}
158170

159171
func TestWithPlanWorks(t *testing.T) {
160-
fakeClient := newFakeServiceCatalogClientForTest(nil, newClusterServicePlans(1))
172+
fakeClient := newFakeServiceCatalogClientForTest(nil, newClusterServicePlans(1, false), "")
161173
handler, informerFactory, err := newHandlerForTest(fakeClient)
162174
if err != nil {
163175
t.Errorf("unexpected error initializing handler: %v", err)
@@ -179,7 +191,7 @@ func TestWithPlanWorks(t *testing.T) {
179191
}
180192

181193
func TestWithNoPlanFailsWithNoClusterServiceClass(t *testing.T) {
182-
fakeClient := newFakeServiceCatalogClientForTest(nil, newClusterServicePlans(1))
194+
fakeClient := newFakeServiceCatalogClientForTest(nil, newClusterServicePlans(1, false), "")
183195
handler, informerFactory, err := newHandlerForTest(fakeClient)
184196
if err != nil {
185197
t.Errorf("unexpected error initializing handler: %v", err)
@@ -200,9 +212,9 @@ func TestWithNoPlanFailsWithNoClusterServiceClass(t *testing.T) {
200212
// checks that the defaulting action works when a service class only provides a single plan.
201213
func TestWithNoPlanWorksWithSinglePlan(t *testing.T) {
202214
sc := newClusterServiceClass("foo-id", "foo")
203-
sps := newClusterServicePlans(1)
215+
sps := newClusterServicePlans(1, false)
204216
glog.V(4).Infof("Created Service as %+v", sc)
205-
fakeClient := newFakeServiceCatalogClientForTest(sc, sps)
217+
fakeClient := newFakeServiceCatalogClientForTest(sc, sps, "")
206218

207219
handler, informerFactory, err := newHandlerForTest(fakeClient)
208220
if err != nil {
@@ -230,9 +242,9 @@ func TestWithNoPlanWorksWithSinglePlan(t *testing.T) {
230242
// checks that defaulting fails when there are multiple plans to choose from.
231243
func TestWithNoPlanFailsWithMultiplePlans(t *testing.T) {
232244
sc := newClusterServiceClass("foo-id", "foo")
233-
sps := newClusterServicePlans(2)
245+
sps := newClusterServicePlans(2, false)
234246
glog.V(4).Infof("Created Service as %+v", sc)
235-
fakeClient := newFakeServiceCatalogClientForTest(sc, sps)
247+
fakeClient := newFakeServiceCatalogClientForTest(sc, sps, "")
236248
handler, informerFactory, err := newHandlerForTest(fakeClient)
237249
if err != nil {
238250
t.Errorf("unexpected error initializing handler: %v", err)
@@ -250,3 +262,34 @@ func TestWithNoPlanFailsWithMultiplePlans(t *testing.T) {
250262
t.Errorf("did not find expected error, got %q", err)
251263
}
252264
}
265+
266+
// checks that defaulting succeeds when there are multiple plans but only a
267+
// single plan for the specified Service Class
268+
func TestWithNoPlanSucceedsWithMultiplePlansFromDifferentClasses(t *testing.T) {
269+
sc := newClusterServiceClass("foo-id", "foo")
270+
sps := newClusterServicePlans(2, true)
271+
glog.V(4).Infof("Created Service as %+v", sc)
272+
classFilter := "test-serviceclass"
273+
fakeClient := newFakeServiceCatalogClientForTest(sc, sps, classFilter)
274+
handler, informerFactory, err := newHandlerForTest(fakeClient)
275+
if err != nil {
276+
t.Errorf("unexpected error initializing handler: %v", err)
277+
}
278+
informerFactory.Start(wait.NeverStop)
279+
280+
instance := newServiceInstance("dummy")
281+
instance.Spec.ExternalClusterServiceClassName = "foo"
282+
283+
err = handler.Admit(admission.NewAttributesRecord(&instance, nil, servicecatalog.Kind("ServiceInstance").WithVersion("version"), instance.Namespace, instance.Name, servicecatalog.Resource("serviceinstances").WithVersion("version"), "", admission.Create, nil))
284+
if err != nil {
285+
actions := ""
286+
for _, action := range fakeClient.Actions() {
287+
actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", "
288+
}
289+
t.Errorf("unexpected error %q returned from admission handler: %v", err, actions)
290+
}
291+
// Make sure the ServiceInstance has been mutated to include the service plan name
292+
if instance.Spec.ExternalClusterServicePlanName != "bar" {
293+
t.Errorf("PlanName was not modified for the default plan")
294+
}
295+
}

0 commit comments

Comments
 (0)