Skip to content
This repository was archived by the owner on May 6, 2022. It is now read-only.

Add admission control to support default service plans. Resolves issue 1111 #1131

Merged
merged 12 commits into from
Aug 16, 2017

Conversation

vaikas
Copy link
Contributor

@vaikas vaikas commented Aug 14, 2017

Closes #1111

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Aug 14, 2017
Copy link
Contributor

@kibbles-n-bytes kibbles-n-bytes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks fine by me, a few nits.

@@ -0,0 +1,11 @@
apiVersion: servicecatalog.k8s.io/v1alpha1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be fine with making this the one-and-only ups-instance.yaml for the walkthrough. I don't think we need multiple walkthrough yamls for every possible code path; there's plenty we're not covering as is.

In fact, for the very same user experience issues @duglin had been describing with the user provided services, I think it would be ideal to have this be our preferred story.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that it would be a great story, I'm just worried when a user tries to create their own instance they won't have a complete example. Any case, I'd prefer to keep this here, but again if you feel strongly, I'll yank it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly. I think in the future we will want to migrate away from using user provided services as our walkthrough example anyway, as it is a bit of a special case (potentially bundled with catalog, no auth, plan concept not making sense, doesn't tell the main "consuming third party services is easy!" OSB story).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vaikas-google what do you think about moving the deployment of ups to be part of the svc-cat chart so its just deployed by default? As @kibbles-n-bytes was saying, having a walkthru (which is meant to be a learning example) not show the use of plans might not be the best example. Of course we'd need a real broker to be deployed as part of the walkthru instead.

if !errors.IsNotFound(err) {
return errors.NewInternalError(err)
}
msg := fmt.Sprintf("ServiceClass %q does not exist, PlanName must be specified", instance.Spec.ServiceClassName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "PlanName must be specified" part of the error seems irrelevant to this particular situation where we simply could not get the service class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

talked offline, changed as per that discussion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To document for posterity: it's a bit of a strange error case, as normally an error with the service class would still be allowed to reach the API server, and the user is responsible for getting the service class working correct so that the instance will eventually succeed.

In this case, the error is actually representative of the inability to assign a default plan due to the fetching of the service class having an error. We changed the message to more clearly address that nuance.

}

// newInstance returns a new instance for the specified namespace.
func newInstance(namespace string) servicecatalog.Instance {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is duplicated from the namespace lifecycle admission controller tests. It seems to me it'd be useful to have a util package shared across all admission controller tests, as functions such as these seem to be common, and we will be adding more admission controllers as time goes on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totes agree if there are others, seems more clutter to add another package right now just for this one function however? If you feel strongly though, I can do it.

Copy link
Contributor

@kibbles-n-bytes kibbles-n-bytes Aug 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thoughts were that newMockClientForTest(), newBroker(), and newInstance(namespace) from namespace/lifecycle/admission_test.go would move there, as well as newServiceClass(name, plans...) (and the now-duplicate newInstance(namespace)) from this PR.

I don't have super strong feelings about it, just that it'll pretty much have to happen once we hit a third admission controller, so might as well get it out of the way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it'd be nice. @kibbles-n-bytes how about in a follow-up, in the interest of getting this functionality + its tests in?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arschles Yeah that's totally fine.

@@ -0,0 +1,122 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This admission controller's behavior as it stands would be completely undocumented. Could we get a short message somewhere in our docs that mentions it?

I guess the most appropriate place would be: https://github.com/kubernetes-incubator/service-catalog/blob/master/docs/design.md#creating-a-service-instance . It's rough around the edges for now, but at least having a mention would remind us to include it in a rewrite should we do so later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you be ok with a follow on PR for this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine. Could you make an issue for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess no followup happened then... @kibbles-n-bytes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😢

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

What's going to happen if someone forgets to deploy the ac?

@pmorie
Copy link
Contributor

pmorie commented Aug 15, 2017 via email

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

yea but what will the user see? It seems like the Instance should have a condition saying that it can't proceed because the plan is empty. Otherwise the user will never know what's going on and can't try to fix it.

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

From a quick look at the code it seems like we'll just call the broker with a blank planID, which will probably result in the broker generating an error - maybe. It seems that as part of this PR we should check for an empty planID before we try to provision the instance, since the spec mandates that it not be blank, and set an appropriate Condition.

sc, err := d.scLister.Get(instance.Spec.ServiceClassName)
if err != nil {
if !errors.IsNotFound(err) {
return errors.NewInternalError(err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason this one isn't wrapped by the "NewForbidden" logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

}
msg := fmt.Sprintf("ServiceClass %q does not exist, can not figure out the default Service Plan.", instance.Spec.ServiceClassName)
glog.V(4).Info(msg)
return admission.NewForbidden(a, fmt.Errorf(msg))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't the fmt.Errorf() just be errors.New() since by that point in the flow "msg" isn't a string needing %'s substitutions any more? I kind of expected lint/vet to catch it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI: vet doesn't detect this because they claim it's a style preference, not one of correctness (golang/go#17173).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's really weird because I know I've seen some complaint from some golang checking tool complain about using things like Printf() w/o any variable args. Maybe it was just a dream... :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

if len(sc.Plans) > 1 {
msg := fmt.Sprintf("ServiceClass %q has more than one plan, PlanName must be specified", instance.Spec.ServiceClassName)
glog.V(4).Info(msg)
return admission.NewForbidden(a, fmt.Errorf(msg))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors.New()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


func (d *defaultServicePlan) Validate() error {
if d.scLister == nil {
return fmt.Errorf("missing service class lister")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors.New()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

err = handler.Admit(admission.NewAttributesRecord(&instance, nil, servicecatalog.Kind("Instance").WithVersion("version"), instance.Namespace, instance.Name, servicecatalog.Resource("instances").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("unexpected success with no ServiceClasses.List succeeding")
} else if !strings.Contains(err.Error(), "not yet ready to handle request") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't express how much doing strcmp on error string like this in golang disturbs me :-( nothing to change, just venting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, it doesn't have to be this way. We can create specific error types and compare types (i.e. err != errNotYetReadyToHandleRequest). Nothing needed right now; I'm willing to go back and correct instances where we have these comparisons post-beta.

@pmorie
Copy link
Contributor

pmorie commented Aug 15, 2017 via email

@kibbles-n-bytes
Copy link
Contributor

kibbles-n-bytes commented Aug 15, 2017

@duglin As Paul said, the apiserver validates that the plan is non-empty, so we wouldn't call the broker.

When we have more mature docs, it'd probably be helpful to mention in the section where we say that the catalog defaults to a plan if there's only one something like: if you're getting an error saying "planName is required" when instantiating a service from a class with only a single plan, make sure that the `DefaultServicePlan` admission controller is enabled.

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

@kibbles-n-bytes - agreed, I just couldn't find that spot in the code yesterday. Got a file/line ref?

@kibbles-n-bytes
Copy link
Contributor

kibbles-n-bytes commented Aug 15, 2017

@duglin Lines 56-57 in pkg/apis/servicecatalog/validation/instance.go

@vaikas
Copy link
Contributor Author

vaikas commented Aug 15, 2017

I believe I've addressed all the comments, PTAL.

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

@vaikas-google thanks for the ptr

}

p := sc.Plans[0]
glog.V(4).Infof("Using default plan %s for Service Class %s for instance %s",
Copy link
Contributor

@duglin duglin Aug 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you convert the %s here to %q's so things are quoted and easier to read?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

one very minor nit and then its good to me

@vaikas
Copy link
Contributor Author

vaikas commented Aug 15, 2017

Thanks for the reviews, PTAL.

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

mucho thanks! LGTM

@duglin
Copy link
Contributor

duglin commented Aug 15, 2017

just waiting on CI then I'll merge

Copy link
Contributor

@arschles arschles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks Ville!

}

// newInstance returns a new instance for the specified namespace.
func newInstance(namespace string) servicecatalog.Instance {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it'd be nice. @kibbles-n-bytes how about in a follow-up, in the interest of getting this functionality + its tests in?

err = handler.Admit(admission.NewAttributesRecord(&instance, nil, servicecatalog.Kind("Instance").WithVersion("version"), instance.Namespace, instance.Name, servicecatalog.Resource("instances").WithVersion("version"), "", admission.Create, nil))
if err == nil {
t.Errorf("unexpected success with no ServiceClasses.List succeeding")
} else if !strings.Contains(err.Error(), "not yet ready to handle request") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, it doesn't have to be this way. We can create specific error types and compare types (i.e. err != errNotYetReadyToHandleRequest). Nothing needed right now; I'm willing to go back and correct instances where we have these comparisons post-beta.

@kibbles-n-bytes
Copy link
Contributor

Just FYI @duglin, @arschles , it looks like there's a backlog of work in Travis at the moment since we share our concurrent build allotment across all the incubator projects, so it will probably take quite a while before Travis actually runs.

Jenkins passed, though, so up to you whether you feel that's enough!

@arschles
Copy link
Contributor

@kibbles-n-bytes let's wait for travis. @vaikas-google I will merge this when it's ready to go. If you happen to be watching it, let me know when it's ready

@MHBauer MHBauer merged commit e7c5ab3 into kubernetes-retired:master Aug 16, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
awaiting-ci cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. LGTM1 LGTM2
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make "plan" optional when there is only one plan to choose from
9 participants