-
Notifications
You must be signed in to change notification settings - Fork 382
Support provisioning by external id #1756
Support provisioning by external id #1756
Conversation
This looks good but my goodness it is large. Neat trick with Format method!! I need to look at this again. |
@n3wscott Apologies for the mega PR. If I need to break it up, let me know! |
A quick nit: All new files need the 2018 K8s Copyright |
@@ -344,8 +344,8 @@ func TestReconcileServiceInstanceNonExistentClusterServicePlanK8SName(t *testing | |||
events := getRecordedEvents(testController) | |||
|
|||
expectedEvent := warningEventBuilder(errorNonexistentClusterServicePlanReason).msgf( | |||
"References a non-existent ClusterServicePlan with K8S name %q on ClusterServiceClass with K8S name %q", | |||
"nothereplan", testClusterServiceClassGUID, | |||
"References a non-existent ClusterServicePlan {ClassName:%q, PlanName:%q}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you have already tested the formatting of the output using Format method, I don't see a need to reformat it in the same way in the tests. I think you can just use the nice Format method output in the tests too.
This LGTM, I will tag it after the copyrights are added. |
I've added the missing 2018 copyright headers. |
Thanks for this work! |
// * ClusterServiceClassExternalName | ||
// * ClusterServiceClassExternalID | ||
// * ClusterServiceClassName | ||
func (pr PlanReference) GetSpecifiedClass() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for formatting purposes? I get the impression that it is, and that's fine. If it is, we should specify in the method godoc that this is for presentation purposes only, since we are conflating different types of information in the return value of this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it's for formatting in our logs and in the svcat cli. I'll add that to the godoc.
pr.ClusterServicePlanName != "" | ||
} | ||
|
||
// GetSpecifiedClass returns the user-specified class value from either: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/either/one of/
|
||
// GetPlanFilterFieldName returns the appropriate field name for filtering | ||
// a list of service catalog plans by the PlanReference. | ||
func (pr PlanReference) GetPlanFilterFieldName() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this also return metadata.name
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
metadata.name
isn't one of the supported fields for filtering during a list. Can we change that? It would be great to not have to use a Get sometimes and a filtered List other times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from a user perspective, it doesn't make that much sense to filter by name because those are UUIDs, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the name is a uuid. If we added filtering by metadata.name, it would be purely for our own convenience in the controller code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into a situation where I wanted to filter by name as a uuid just the other day
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, cool 👍
// {ClassExternalName:"foo", PlanExternalName:"bar"} | ||
// {ClassExternalID:"foo123", PlanExternalID:"bar456"} | ||
// {ClassName:"k8s-foo123", PlanName:"k8s-bar456"} | ||
func (pr PlanReference) Format(s fmt.State, verb rune) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not know this was a thing in go and this is awesome
// Immutable. | ||
ClusterServiceClassExternalID string | ||
|
||
// ClusterServicePlanExternalID is the broker's external id for the plan. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add that this is immutable? also in v1beta1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not immutable though, right? Just like ClusterServicePlanExternalName and ClusterServicePlan, you can upgrade the plan later depending on the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iirc you can upgrade the plan later, so I'd agree
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right
allErrs = append(allErrs, field.Required(fldPath.Child("clusterServicePlanExternalName"), "exactly one of clusterServicePlanExternalName or clusterServicePlanName required")) | ||
allErrs = append(allErrs, field.Required(fldPath.Child("clusterServicePlanName"), "exactly one of clusterServicePlanExternalName or clusterServicePlanName required")) | ||
// Must specify exactly one source of the plan: external id, external name, k8s name. | ||
if (b2i(externalPlanNameSet) + b2i(externalPlanIDSet) + b2i(k8sPlanSet)) != 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm too tired to tell whether this is correct or not, so stopping here - wanted to get you preliminary feedback.
I've incorporated the suggested godoc changes. |
// - ClusterServiceClassName and ClusterServicePlanName | ||
// | ||
// For both of these ways, if a ClusterServiceClass only has one plan | ||
// For any of these ways, if a ClusterServiceClass only has one plan | ||
// then leaving the *ServicePlanName is optional. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this comment count for the plan ID like plan name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes thanks for noticing that, I'll update the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All fixed!
allErrs = append(allErrs, field.Required(fldPath.Child("clusterServiceClassExternalName"), "exactly one of clusterServiceClassExternalName or clusterServiceClassName required")) | ||
allErrs = append(allErrs, field.Required(fldPath.Child("clusterServiceClassName"), "exactly one of clusterServiceClassExternalName or clusterServiceClassName required")) | ||
// Must specify exactly one source of the class: external id, external name, k8s name. | ||
if (b2i(externalClassNameSet) + b2i(externalClassIDSet) + b2i(k8sClassSet)) != 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no xor in golang, I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is, I don't know about it! 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Talked with Paul, ^
is bitwise xor and only works on ints, not bool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya, bool xor
1f88f07
to
2835211
Compare
@carolynvs probably want to rebase this one and verify that CI is good - LGTM from me |
I'll add integration tests tomorrow and rebase. |
a1089b9
to
c7f2a50
Compare
@pmorie Any idea why the Jenkins build timed-out? I'm not sure where to start looking. 😀
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went through this twice and I have an ok understanding of what's going on here. hopefully my comments etc... were reasonable
Thanks! 😄
|
||
// GetPlanFilterFieldName returns the appropriate field name for filtering | ||
// a list of service catalog plans by the PlanReference. | ||
func (pr PlanReference) GetPlanFilterFieldName() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from a user perspective, it doesn't make that much sense to filter by name because those are UUIDs, no?
classFields = append(classFields, fmt.Sprintf("ClassName:%q", pr.ClusterServiceClassName)) | ||
} | ||
|
||
planFields := make([]string, 0, 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it'd make sense to just create a []string{}
and append to it as necessary. preallocating memory seems like an optimization we don't need (and we might not use any of it at all). am I missing something about how this works?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I just did that so y'all would think I was cool. 😀 Since only 1 at most is usually ever set, I'll switch to an empty slice.
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always 😢 when code has to be copied across versioned/unversioned types. C'est la vie for now
// Immutable. | ||
ClusterServiceClassExternalID string | ||
|
||
// ClusterServicePlanExternalID is the broker's external id for the plan. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iirc you can upgrade the plan later, so I'd agree
allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterServicePlanExternalName"), p.ClusterServicePlanExternalName, msg)) | ||
} | ||
} | ||
if externalClassIDSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be an else if
? if name isn't set, seems like we'd need to check ID instead, not in addition to
@@ -889,25 +889,31 @@ func (c *controller) resolveReferences(instance *v1beta1.ServiceInstance) (bool, | |||
// If ClusterServiceClass can not be resolved, returns an error, records an | |||
// Event, and sets the InstanceCondition with the appropriate error message. | |||
func (c *controller) resolveClusterServiceClassRef(instance *v1beta1.ServiceInstance) (*v1beta1.ServiceInstance, *v1beta1.ClusterServiceClass, error) { | |||
if !instance.Spec.ClassSpecified() { | |||
// ServiceInstance is in invalid state, should not ever happen. check | |||
return nil, nil, fmt.Errorf("ServiceInstance is in inconsistent state, neither ClusterServiceClassExternalName, ClusterServiceClassExternalID, nor ClusterServiceClassName is set: %+v", instance.Spec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outputting the entire spec is a lot, does it make sense to output it at all?
@@ -92,7 +92,7 @@ func TestReconcileServiceInstanceNonExistentClusterServiceClass(t *testing.T) { | |||
events := getRecordedEvents(testController) | |||
|
|||
expectedEvent := warningEventBuilder(errorNonexistentClusterServiceClassReason).msgf( | |||
"References a non-existent ClusterServiceClass (ExternalName: %q) or there is more than one (found: %d)", | |||
"References a non-existent ClusterServiceClass {ClassExternalName:%q} or there is more than one (found: %d)", | |||
"nothere", 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
necessary to format the 0
, or can you put it into the string? same with a few below
@carolynvs I restarted the jenkins build, it's pending now |
@arschles I've incorporated everyone's review feedback, but the build is still failing on a flake. I checked and other PRs, not just mine, are hitting that timeout during the walkthrough too occasionally. |
@carolynvs looks like CI failed |
This is easily the least helpful error message I have seen today. @carolynvs does |
@pmorie Yes, it passes when run locally. Here's the command I used to run it:
EDIT: I'm a huge dork and just realized that didn't test anything. 😊 I'm figuring out how to run that properly locally and will report back. |
@pmorie Okay, now I think I've run it properly locally and it passed. Let me know if I actually got it right this time! 😊
|
* Add functions to the PlanReference type instead of requring everyone who works with PlanReference to know of the various ways a class/plan could be specified. * Implement fmt.Formatter so that we can consistently format messages about the plan. Many of the log messages assumed that a certain field, like ClusterServicePlanExternalName would be set when now there are multiple fields. Formatting options for PlanReference * %c - Print specified class fields only Examples: {ClassExternalName:"foo"} {ClassExternalID:"foo123"} {ClassName:"k8s-foo123"} * %b - Print specified plan fields only NOTE: %p is a reserved verb so we can't use it, and go vet fails for non-standard verbs Examples: {PlanExternalName:"bar"} {PlanExternalID:"bar456"} {PlanName:"k8s-bar456"} * %s - Print a short form of the plan and class Examples: foo/bar foo123/bar456 k8s-foo123/k8s-bar456 * %v - Print all specified fields Examples: {ClassExternalName:"foo", PlanExternalName:"bar"} {ClassExternalID:"foo123", PlanExternalID:"bar456"} {ClassName:"k8s-foo123", PlanName:"k8s-bar456"}
5c6c300
to
8af0e94
Compare
@pmorie The jenkins build problem was caused by other changes that I had picked up when rebasing, that caused the build to go over 30 minutes. After I rebased again, I picked up their change to bump the timeout on the build, and now my PR builds fine. 🤷♀️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks @carolynvs
* master: Support provisioning by external id (kubernetes-retired#1756) Add namespaced ServiceBroker API (kubernetes-retired#1881) Enable verbose logging in svcat (kubernetes-retired#1822) Adding ServicePlan (namespaced plans) API (kubernetes-retired#1894) Svcat bind params now supports --params-json (kubernetes-retired#1889) words discussing the automated builds (kubernetes-retired#1885) v0.1.12 release changes don't overwrite generated files before verifying (kubernetes-retired#1891) Update registry code from broker to clusterservicebroker (kubernetes-retired#1880) Credentials remapping (kubernetes-retired#1868) Rename/move existing "ServicePlan" things (kubernetes-retired#1883) Start orphan mitigation after receiving a last operation status for async provisioning (kubernetes-retired#1886) Update of name in example ServiceClass (kubernetes-retired#1878) Cluster-id configmap (kubernetes-retired#1658) Add namespaced ServiceClass API (kubernetes-retired#1859) Fix common validations regression (kubernetes-retired#1882)
@@ -83,7 +83,8 @@ func (d *defaultServicePlan) Admit(a admission.Attributes) error { | |||
if !apierrors.IsNotFound(err) { | |||
return admission.NewForbidden(a, err) | |||
} | |||
msg := fmt.Sprintf("ClusterServiceClass %q does not exist, can not figure out the default ClusterServicePlan.", instance.Spec.ClusterServiceClassExternalName) | |||
msg := fmt.Sprintf("ClusterServiceClass %c does not exist, can not figure out the default ClusterServicePlan.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am getting an error from this line today.
plugin/pkg/admission/serviceplan/defaultserviceplan/admission.go:86: arg instance.Spec.PlanReference for printf verb %c of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog.PlanReference
exit status 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Verify that vendor/ is in sync with Gopkg.lock
docker run --security-opt label:disable --rm -v /Users/nicholss/go/src/github.com/kubernetes-incubator/service-catalog:/go/src/github.com/kubernetes-incubator/service-catalog -v /Users/nicholss/go/src/github.com/kubernetes-incubator/service-catalog/.pkg:/go/pkg --env AZURE_STORAGE_CONNECTION_STRING scbuildimage build/verify-vendor.sh
Verified that vendor/ is in sync with Gopkg.lock
Running gofmt:
Running golint and go vet:
docker run --security-opt label:disable --rm -v /Users/nicholss/go/src/github.com/kubernetes-incubator/service-catalog:/go/src/github.com/kubernetes-incubator/service-catalog -v /Users/nicholss/go/src/github.com/kubernetes-incubator/service-catalog/.pkg:/go/pkg --env AZURE_STORAGE_CONNECTION_STRING scbuildimage go vet github.com/kubernetes-incubator/service-catalog/...
warning: ignoring symlink /go/src/github.com/kubernetes-incubator/service-catalog/docsite/docs
warning: ignoring symlink /go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/coreos/etcd/cmd/etcd
warning: ignoring symlink /go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/prometheus/procfs/fixtures/self
pkg/controller/controller.go:506: arg instance.Spec.PlanReference for printf verb %c of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1.PlanReference
pkg/controller/controller_instance.go:931: arg instance.Spec.PlanReference for printf verb %c of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1.PlanReference
pkg/controller/controller_instance.go:965: arg instance.Spec.PlanReference for printf verb %c of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1.PlanReference
pkg/controller/controller_instance.go:1041: arg instance.Spec.PlanReference for printf verb %b of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1.PlanReference
pkg/controller/controller_clusterid_test.go:85: github.com/kubernetes-incubator/service-catalog/vendor/k8s.io/apimachinery/pkg/runtime/schema.GroupResource composite literal uses unkeyed fields
pkg/controller/controller_clusterid_test.go:110: github.com/kubernetes-incubator/service-catalog/vendor/k8s.io/apimachinery/pkg/runtime/schema.GroupResource composite literal uses unkeyed fields
pkg/controller/controller_instance_test.go:4332: arg instance.Spec.PlanReference for printf verb %c of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1.PlanReference
pkg/controller/controller_test.go:556: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1.ClusterServiceClassStatus composite literal uses unkeyed fields
exit status 1
plugin/pkg/admission/serviceplan/defaultserviceplan/admission.go:86: arg instance.Spec.PlanReference for printf verb %c of wrong type: github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog.PlanReference
exit status 1
make: *** [verify] Error 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was caused by a stale build image and was fixed after running make clean-build-image
.
Closes #1286.
In addition to provisioning by external id, I consolidated logic for interacting with
PlanReference
:PlanReference
type instead of requiring everyone whoworks with
PlanReference
to know of the various ways a class/plan couldbe specified.
fmt.Formatter
so that we can consistently format messagesabout the plan. Many of the log messages assumed that a certain field,
like
ClusterServicePlanExternalName
would be set when now there aremultiple fields.
Formatting options for
PlanReference
%c
- Print specified class fields onlyExamples:
{ClassExternalName:"foo"}
{ClassExternalID:"foo123"}
{ClassName:"k8s-foo123"}
%b
- Print specified plan fields onlyNOTE:
%p
is a reserved verb so we can't use it, and go vet fails for non-standard verbsExamples:
{PlanExternalName:"bar"}
{PlanExternalID:"bar456"}
{PlanName:"k8s-bar456"}
%s
- Print a short form of the plan and classExamples:
foo/bar
foo123/bar456
k8s-foo123/k8s-bar456
%v
- Print all specified fieldsExamples:
{ClassExternalName:"foo", PlanExternalName:"bar"}
{ClassExternalID:"foo123", PlanExternalID:"bar456"}
{ClassName:"k8s-foo123", PlanName:"k8s-bar456"}