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

Allow the same user to edit an instance #1872

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions pkg/apis/servicecatalog/validation/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (

sc "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog"
"github.com/kubernetes-incubator/service-catalog/pkg/controller"
scfeatures "github.com/kubernetes-incubator/service-catalog/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
)

// validateServiceInstanceName is the validation function for Instance names.
Expand Down Expand Up @@ -254,13 +256,31 @@ func validateServiceInstanceUpdate(instance *sc.ServiceInstance) field.ErrorList
}

// internalValidateServiceInstanceUpdateAllowed ensures there is not a
Copy link
Contributor

Choose a reason for hiding this comment

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

You may want to update this comment to reflect how changes by the same user are treated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch- fixed

// pending update on-going with the spec of the instance before allowing an update
// to the spec to go through.
// pending update on-going with the spec of the instance before allowing an
// update to the spec to go through unless its the same user who made the
// original update.
func internalValidateServiceInstanceUpdateAllowed(new *sc.ServiceInstance, old *sc.ServiceInstance) field.ErrorList {
errors := field.ErrorList{}
if old.Generation != new.Generation && old.Status.CurrentOperation != "" {
errors = append(errors, field.Forbidden(field.NewPath("spec"), "Another update for this service instance is in progress"))

// If the OriginatingIdentityLocking feature is not set then only allow the
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrong comment, the condition is reverse: is not set -> is set

// same user to edit the Instance.
if utilfeature.DefaultFeatureGate.Enabled(scfeatures.OriginatingIdentityLocking) {
oldUID, newUID := "", ""

if old.Spec.UserInfo != nil {
oldUID = old.Spec.UserInfo.UID
}

if new.Spec.UserInfo != nil {
newUID = new.Spec.UserInfo.UID
}

if old.Generation != new.Generation && old.Status.CurrentOperation != "" && oldUID != newUID {
Copy link
Contributor

@nilebox nilebox Mar 29, 2018

Choose a reason for hiding this comment

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

@duglin I'm not sure if this condition is sufficient for you. The old.Status.CurrentOperation is being set at the beginning of reconciling the new generation. This means that:

  1. User B could slip through his changes before User A's changes are picked up by controller-manager
  2. If the controller-manager is down (while apiserver is not), there could be a lot of spec changes from different users collected in the meantime.

The condition for locking should not be based on whether there is an operation in progress. It should be based on whether controller has finished processing the current generation (i.e. either succeeded, or failed and won't retry).

So I would suggest to change the condition to something more strict.
I suggest to copy the isServiceInstanceProcessedAlready method from controller and change the condition to

if old.Generation != new.Generation && oldUID != newUID && !isServiceInstanceProcessedAlready(old) { ...

and copy (will also need to change v1beta1 -> sc package)
https://github.com/kubernetes-incubator/service-catalog/blob/e15b73719911853d3755b71f3d8b26b21296d0a3/pkg/controller/controller_instance.go#L818-L826

P.S. I know that the condition was written this way before, but given that we decided to change it, it's worth fixing it as well.

errors = append(errors, field.Forbidden(field.NewPath("spec"), "Another update for this service instance is in progress"))
}

}

if old.Spec.ClusterServicePlanExternalName != new.Spec.ClusterServicePlanExternalName && new.Spec.ClusterServicePlanRef != nil {
errors = append(errors, field.Forbidden(field.NewPath("spec").Child("clusterServicePlanRef"), "clusterServicePlanRef must not be present when clusterServicePlanExternalName is being changed"))
}
Expand Down
21 changes: 20 additions & 1 deletion pkg/apis/servicecatalog/validation/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,30 +744,42 @@ func TestInternalValidateServiceInstanceUpdateAllowed(t *testing.T) {
name string
specChange bool
onGoingOperation bool
setUser bool
valid bool
}{
{
name: "spec change when no on-going operation",
specChange: true,
onGoingOperation: false,
setUser: false,
valid: true,
},
{
name: "spec change when on-going operation",
name: "spec change when on-going operation, same user",
specChange: true,
onGoingOperation: true,
setUser: false,
valid: true,
},
{
name: "spec change when on-going operation, diff user",
specChange: true,
onGoingOperation: true,
setUser: true,
valid: false,
},
{
name: "meta change when no on-going operation",
specChange: false,
onGoingOperation: false,
setUser: false,
valid: true,
},
{
name: "meta change when on-going operation",
specChange: false,
onGoingOperation: true,
setUser: false,
valid: true,
},
}
Expand Down Expand Up @@ -808,6 +820,13 @@ func TestInternalValidateServiceInstanceUpdateAllowed(t *testing.T) {
newInstance.Generation = oldInstance.Generation
}

if tc.setUser {
newInstance.Spec.UserInfo = &servicecatalog.UserInfo{
Username: "JohnDoe",
UID: "123",
}
}

errs := internalValidateServiceInstanceUpdateAllowed(newInstance, oldInstance)
if len(errs) != 0 && tc.valid {
t.Errorf("%v: unexpected error: %v", tc.name, errs)
Expand Down
22 changes: 17 additions & 5 deletions pkg/features/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ const (
// owner: @luksa
// alpha: v0.1.12
ResponseSchema utilfeature.Feature = "ResponseSchema"

// OriginatingIdentityLocking controls whether we lock OSB API resources
// that are being updated while we are still processing the update request.
Copy link
Contributor

Choose a reason for hiding this comment

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

processing the update request -> haven't finished processing the current spec
(and rewrite the whole comment to reflect that)

// Meaning, we're still talking to the Broker to see if the requested
// change will be accepted or not. If locked, then no *other* user is
// allowed to update the resource.
// This lock was added in an attempt to fulfill the requirements
// of the OSBAPI OriginatingIdentity header.
// owner: @duglin
// alpha: v0.1.12
OriginatingIdentityLocking utilfeature.Feature = "OriginatingIdentityLocking"
)

func init() {
Expand All @@ -68,9 +79,10 @@ func init() {
// To add a new feature, define a key for it above and add it here. The features will be
// available throughout service catalog binaries.
var defaultServiceCatalogFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
PodPreset: {Default: false, PreRelease: utilfeature.Alpha},
OriginatingIdentity: {Default: false, PreRelease: utilfeature.Alpha},
AsyncBindingOperations: {Default: false, PreRelease: utilfeature.Alpha},
NamespacedServiceBroker: {Default: false, PreRelease: utilfeature.Alpha},
ResponseSchema: {Default: false, PreRelease: utilfeature.Alpha},
PodPreset: {Default: false, PreRelease: utilfeature.Alpha},
OriginatingIdentity: {Default: false, PreRelease: utilfeature.Alpha},
AsyncBindingOperations: {Default: false, PreRelease: utilfeature.Alpha},
NamespacedServiceBroker: {Default: false, PreRelease: utilfeature.Alpha},
ResponseSchema: {Default: false, PreRelease: utilfeature.Alpha},
OriginatingIdentityLocking: {Default: true, PreRelease: utilfeature.Alpha},
}