Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce namespace unique constraints #1564

Merged

Conversation

derekwaynecarr
Copy link
Member

This is the update to actually enable namespace support.

At the moment, I pass a namespace as a query parameter in order to support namespace scoping on the v1beta urls. Once v1beta3 lands, I will modify to pass namespace in the path. Note the client libraries were modified to make this easy to do, so should be a trivial change once ready.

I broke the commits into logical sections to improve readability.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch 2 times, most recently from 0994cbf to 197c7ac Compare October 3, 2014 16:12
@derekwaynecarr
Copy link
Member Author

Not sure why Go 1.2.2 is showing an error, but Go 1.3 is building and passing Travis without issue.

@smarterclayton
Copy link
Contributor

Probably using a Go 1.3 specific feature

@derekwaynecarr
Copy link
Member Author

Travis flaked, all things pass now.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from 197c7ac to f9c3e19 Compare October 6, 2014 16:51
@erictune
Copy link
Member

erictune commented Oct 6, 2014

Have a test that sets and gets a non-default namespace? Is hack/test-cmd.sh the right place to add that?

@erictune
Copy link
Member

erictune commented Oct 6, 2014

I can see three possible "features" that require some notion of a "default namespace":

  1. the one you use for your pods, etc if you don't bother to explicitly create and reference another namespace.
  2. the value of the Namespace property you use when you want to operate on cluster-wide resource (one which is not naturally associated with a single namespace) such as minions or (not nested) namespaces.
  3. the Namespace of authorization policies that affect all namespaces, or are about namespaces (not really defaults, since they probably have precedence over policies that are set in specific namespaces.

Do you see "default" being used for all of those? Not something that has to be determined in this PR, but something to think about.

sync := req.URL.Query().Get("sync") == "true"
timeout := parseTimeout(req.URL.Query().Get("timeout"))
// if a namespace if specified, it's always used.
// for list/watch operations, a namespace is not required if omitted.
Copy link
Member

Choose a reason for hiding this comment

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

I think it will seem odd for the "average" user that normal GET and POST operate only on one namespace, but list and watch can operate on all namespaces. The things that need to list/watch all objects are special cases like controllers. I wonder if we should put the burden on those special cases to explicitly say they want to see all namespaces.

To that end, what if namespace is required to be specified for list and watch, but you can optionally specify "__ALL" in the namespace parameter to match all namespaces? What do you think?

Also thinking about the three different types of "default" namespaces and whether those should have their own special identifiers.

Copy link
Member Author

Choose a reason for hiding this comment

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

My take on this is its less of a concern when we move away from the query parameter and instead namespace is in the resource path.

@erictune
Copy link
Member

erictune commented Oct 6, 2014

one minor addition to a test is requested. LGTM after that.
Can wait for future PR for changes to "default" values, if any.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from f4330d0 to 998a549 Compare October 6, 2014 18:47
@derekwaynecarr
Copy link
Member Author

Made following updates:

  1. Cleaned up makeEtcdKey per @smarterclayton request to makeEtcdListKey and makeEtcdItemKey
  2. Added unit test for kubecfg LoadNamespaceInfo per @erictune
  3. Improved code style for resthandler.go and default namespace per @smarterclayton request
  4. Improved etcd_test.go TestEtcdCreatePodFailsWithoutNamespace per @erictune

Thanks for feedback.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from 998a549 to 7fdc31f Compare October 6, 2014 20:23
@erictune
Copy link
Member

erictune commented Oct 7, 2014

LGTM. One tiny suggestion. And please squash for merge.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from 7fdc31f to 1764a03 Compare October 7, 2014 15:09
@derekwaynecarr
Copy link
Member Author

@erictune - squashed commits at appropriate boundaries. made last requested change.

@smarterclayton
Copy link
Contributor

Hold on merge until we close the discussion about this requiring downtime and recreation of all resources.

@derekwaynecarr
Copy link
Member Author

My preference is to not migrate existing resources into the new etcd key-paths, but I do think we need to start the discussion on how we can do both on and offline migration of etcd content in the future in case we move resources again.

In reviewing the following from @thockin, it appears that services v2 is taking a similar statement here: https://groups.google.com/forum/#!topic/kubernetes-announce/oO7Bs_c16lI

In a future PR, I think its imperative we can ask a registry object what schema version it is running [ which is different than api version ], and that we can ask a registry object to go from schema version N to N+1.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from 1764a03 to e7ceb34 Compare October 7, 2014 15:26
@thockin
Copy link
Member

thockin commented Oct 7, 2014

If a live update is doable without crippling the feature (which services v2
is not) and not too hard to do (say O(2 days) of eng time), we should
consider it.

But at this point it sounds like we have a few such things which together
add up to more than O(2 days) work to live migrate AND we're early enough
that losing momentum is worse than asking customers to nuke.

On Tue, Oct 7, 2014 at 8:25 AM, Derek Carr notifications@github.com wrote:

My preference is to not migrate existing resources into the new etcd
key-paths, but I do think we need to start the discussion on how we can do
both on and offline migration of etcd content in the future in case we move
resources again.

In reviewing the following from @thockin https://github.com/thockin, it
appears that services v2 is taking a similar statement here:
https://groups.google.com/forum/#!topic/kubernetes-announce/oO7Bs_c16lI

In a future PR, I think its imperative we can ask a registry object what
schema version it is running [ which is different than api version ], and
that we can ask a registry object to go from schema version N to N+1.

Reply to this email directly or view it on GitHub
#1564 (comment)
.

@derekwaynecarr
Copy link
Member Author

The migration process for this would require the following:

Resources Affected:

  1. Pods
  2. ReplicationControllers
  3. Services
  4. ServiceEndpoints

To perform a migration, we would need to do the following:

  1. Stop services on kubernetes-master (apiserver, controller-manager, scheduler)
  2. Run some undefined script from somewhere that modifies etcd as follows:

Move resources
a. /registry/pods to /registry/pods/default
b. /registry/controllers to /registry/controllers/default
c. /registry/services/specs to /registry/services/specs/default
d. /registry/services/endpoints to /registry/services/endpoints/default

Update each resource in above and ensure:
resource.Namespace = "default"

  1. Update kubernetes-master to latest source
  2. Start apiserver, controller-manager, scheduler

@derekwaynecarr
Copy link
Member Author

Rebase completed to update changes to api.JSONBase becoming api.TypeMeta

if err != nil {
return err
if node.Dir {
h.decodeNodeList(node.Nodes, slicePtr)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you return here to avoid the deep else?

Copy link
Member Author

Choose a reason for hiding this comment

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

@smarterclayton No. It's possible in the future that we will have a node structure in etcd where a directory and a node exist at the same depth in the hierarchy, so we need to process all non-directory sibling nodes.

For example:

/key/registry/foo/bar <- Node.Dir=false
/key/registry/foo/abc <- Node.Dir=true
/key/registry/foo/abc/def <- Node.Dir = false

calling List nodes recursively on /key/registry/foo would need to return {bar,def}

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant continue

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure thing - update complete.

----- Original Message -----
From: "Clayton Coleman" notifications@github.com
To: "GoogleCloudPlatform/kubernetes" kubernetes@noreply.github.com
Cc: "Derek Carr" decarr@redhat.com
Sent: Wednesday, October 8, 2014 1:36:47 PM
Subject: Re: [kubernetes] Enforce namespace unique constraints (#1564)

@@ -139,16 +145,20 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersi
}
v := pv.Elem()
for _, node := range nodes {

  • obj := reflect.New(v.Type().Elem())
    
  • err = h.Codec.DecodeInto([]byte(node.Value), obj.Interface().(runtime.Object))
    
  • if h.ResourceVersioner != nil {
    
  •     _ = h.ResourceVersioner.SetResourceVersion(obj.Interface().(runtime.Object), node.ModifiedIndex)
    
  •     // being unable to set the version does not prevent the object from being extracted
    
  • }
    
  • if err != nil {
    
  •     return err
    
  • if node.Dir {
    
  •     h.decodeNodeList(node.Nodes, slicePtr)
    

I meant continue


Reply to this email directly or view it on GitHub:
https://github.com/GoogleCloudPlatform/kubernetes/pull/1564/files#r18598677

@smarterclayton
Copy link
Contributor

One last comment, then LGTM

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from f2ca39a to e72bf13 Compare October 8, 2014 17:42
@derekwaynecarr
Copy link
Member Author

rebased again.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from 567bb1f to 2132c11 Compare October 10, 2014 15:58
@derekwaynecarr
Copy link
Member Author

rebased again.

@lavalamp
Copy link
Member

I would like to look at this change, please hold for a bit. ETA 4 hours

@@ -74,6 +74,15 @@ func (r *Request) Sync(sync bool) *Request {
return r
}

// Namespace sets a namespace parameter on a request based on the value in the context
func (r *Request) Namespace(ctx api.Context) *Request {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason why this function takes an api.Context? That is for tracking client requests server side. There is not much sense forcing clients to use it, no? I think this should just take a string. I also think it's something you should set on the client object, not the request object-- if you're doing namespaced operations, you probably want many in a row with the same namespace.

Very sorry for not noticing this earlier, I was out all last week and have just been catching up this week.

Copy link
Contributor

Choose a reason for hiding this comment

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

I had the same argument earlier - derek had something that half convinced me (convenience for client code writers), although that may be solvable. Derek, sway me back??? :)

On Oct 10, 2014, at 2:17 PM, Daniel Smith notifications@github.com wrote:

In pkg/client/request.go:

@@ -74,6 +74,15 @@ func (r *Request) Sync(sync bool) *Request {
return r
}

+// Namespace sets a namespace parameter on a request based on the value in the context
+func (r *Request) Namespace(ctx api.Context) *Request {
Is there a reason why this function takes an api.Context? That is for tracking client requests server side. There is not much sense forcing clients to use it, no? I think this should just take a string. I also think it's something you should set on the client object, not the request object-- if you're doing namespaced operations, you probably want many in a row with the same namespace.

Very sorry for not noticing this earlier, I was out all last week and have just been catching up this week.


Reply to this email directly or view it on GitHub.

Copy link
Member Author

Choose a reason for hiding this comment

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

Is there a reason why this function takes an api.Context? That is for tracking client requests server side. There is not much sense forcing clients to use it, no? 

If you just think about the kubecfg client or the end-user of kubernetes, then sure, you tend to work in a single namespace, and this argument holds. In fact, its how I first wrote the code, but if you take a step back, you realize the majority of our existing clients are server-side and need to operate across namespace boundaries to perform their operation. It then becomes a matter of wanting to make it easier for those clients to operate.

The replication-controller and scheduler both follow a similar pattern:

  1. initialize the daemon with an interface X that is subset of kubeclient
  2. run a sync loop
  3. List/Watch over all name-spaces using X to resource type Y
  4. In response, do some series of CRUD operations in Y.Namespace

In OpenShift Origin, we have a similar pattern for build-controllers and deployment-controllers. I suspect others may choose to write plug-ins that follow the same convention to meet a particular need.

If we kept context on Client as a means of passing namespace around, you are left with the following when looking at the above style-use case.

A. Create new client objects when you change namespace boundary
B. Require the ability to mutate a context on a client interface

(A) seems like an anti-pattern when its an obvious control loop in our internal usage of Kubernetes to require so many throw-away clients when we have callers that work in multiple namespace boundaries.

(B) has issues with concurrency because we share the same client, and so we would need locking, and things get ugly quick.

That left me with (C) pass context as argument - which lets me reuse the client object, and keep clean enough reading code after actually writing all the other options for callers that work on resources across namespace boundaries.

As for this particular function taking a api.Context object instead of a string, it actually keeps the LIST/WATCH operations clean when working in the no namespace-boundary versus passing the empty string as input around.

Copy link
Member

Choose a reason for hiding this comment

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

It should be possible to override the namespace on a particular request, if necessary. Just like we have defaults for other things, the client has a namespace setting which requests get by default, but if you need to change the namespace for a particular request, you can.

The common client functions should be able to get the namespace out of the object, anyway, you shouldn't have to pass it again in a context. I can't think of a reason why server components should even need to reference namespaces, other than perhaps copying it from a {replicationController,pod} to a {pod,binding}.

Copy link
Member Author

Choose a reason for hiding this comment

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

As long as we agree with override on a particular request, then we get what we both want, and then its a manner of finding the best way to provide that override into the operation. Can we evolve this in a subsequent PR and not hold up this support? I know there are many things waiting on this feature making it in from our end, and it seems like this type of thing can get further discussion after the fact.

Copy link
Member

Choose a reason for hiding this comment

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

The thing I want is for this to not take an api.Context. There's no reason not to take a string, is there?

(also, all other client methods shouldn't take api.Context, but I won't block this PR on that.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I can update Namespace to take a string, and callers will do things like the following for now:

err = c.Put().Namespace(api.Namespace(ctx)).Path("services").Path(svc.ID).Body(svc).Do().Into(result)

Will open a separate PR to discuss alternate approaches to providing an override to client context in a thread-safe manner.

Copy link
Member

Choose a reason for hiding this comment

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

I can live with that for the moment, thanks.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually we can use #1708 to discuss possible alternative solutions now that you understand the current requirements I worked against.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from 2132c11 to 5e8d0bc Compare October 10, 2014 21:24
@derekwaynecarr
Copy link
Member Author

Updates requested by @lavalamp completed.

@smarterclayton
Copy link
Contributor

LGTM

@erictune
Copy link
Member

Still LGTM. Not merging because my understanding is that this is to be merged on the "big breakage day", which is not chosen yet. Assigning to @thockin who I think will do the merges on that day.

@erictune erictune assigned thockin and unassigned erictune Oct 14, 2014
@@ -71,3 +77,12 @@ func ValidNamespace(ctx Context, resource *TypeMeta) bool {
}
return ns == resource.Namespace && ok
}

// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value
Copy link
Member

Choose a reason for hiding this comment

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

What is the difference between the default and not being set?

Copy link
Member Author

Choose a reason for hiding this comment

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

The default Namespace is literally, Namespace="default".

It is the assumed namespace context when calling the legacy API patterns where namespace was not in the resource path.

A namespace not being set means the operation context has no namespace scope at all. It is useful for LIST and WATCH operations when working across namespace boundaries.

Copy link
Member

Choose a reason for hiding this comment

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

I find this really confusing. Is it too late to ask for a system where "" == "default", and if you want to span namespaces, you have to explicitly say namespace "*"?

Copy link
Contributor

Choose a reason for hiding this comment

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

From a REST perspective, GET /pods returning all pods seems more correct than GET /pods/ns/* returning all pods.

----- Original Message -----

@@ -71,3 +77,12 @@ func ValidNamespace(ctx Context, resource *TypeMeta)
bool {
}
return ns == resource.Namespace && ok
}
+
+// WithNamespaceDefaultIfNone returns a context whose namespace is the
default if and only if the parent context has no namespace value

I find this really confusing. Is it too late to ask for a system where "" ==
"default", and if you want to span namespaces, you have to explicitly say
namespace "*"?


Reply to this email directly or view it on GitHub:
https://github.com/GoogleCloudPlatform/kubernetes/pull/1564/files#r18857208

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'm not arguing about the REST path, I'm arguing about not using the go type's default value of "" to mean the default. Perhaps we need to make a custom type to represent namespaces.

Copy link
Member

Choose a reason for hiding this comment

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

Fine with me, we can merge and fix up later.

Copy link
Member

Choose a reason for hiding this comment

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

Final answer: is this PR done enough to commit tomorrow?

@lavalamp @derekwaynecarr @smarterclayton

On Wed, Oct 15, 2014 at 10:54 AM, Daniel Smith notifications@github.com
wrote:

In pkg/api/context.go:

@@ -71,3 +77,12 @@ func ValidNamespace(ctx Context, resource *TypeMeta) bool {
}
return ns == resource.Namespace && ok
}
+
+// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value

Fine with me, we can merge and fix up later.

Reply to this email directly or view it on GitHub
https://github.com/GoogleCloudPlatform/kubernetes/pull/1564/files#r18910297
.

Copy link
Member

Choose a reason for hiding this comment

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

Yes.

Copy link
Member Author

Choose a reason for hiding this comment

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

I vote yes

Sent from my iPhone

On Oct 15, 2014, at 4:29 PM, Tim Hockin notifications@github.com wrote:

In pkg/api/context.go:

@@ -71,3 +77,12 @@ func ValidNamespace(ctx Context, resource *TypeMeta) bool {
}
return ns == resource.Namespace && ok
}
+
+// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value
Final answer: is this PR done enough to commit tomorrow? @lavalamp @derekwaynecarr @smarterclayton


Reply to this email directly or view it on GitHub.

Copy link
Member

Choose a reason for hiding this comment

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

yes

On Wed, Oct 15, 2014 at 1:34 PM, Derek Carr notifications@github.com
wrote:

In pkg/api/context.go:

@@ -71,3 +77,12 @@ func ValidNamespace(ctx Context, resource *TypeMeta) bool {
}
return ns == resource.Namespace && ok
}
+
+// WithNamespaceDefaultIfNone returns a context whose namespace is the default if and only if the parent context has no namespace value

I vote yes
… <#149158515c230ada_>
Sent from my iPhone
On Oct 15, 2014, at 4:29 PM, Tim Hockin notifications@github.com
wrote: In pkg/api/context.go: > @@ -71,3 +77,12 @@ func ValidNamespace(ctx
Context, resource *TypeMeta) bool { > } > return ns == resource.Namespace
&& ok > } > + > +// WithNamespaceDefaultIfNone returns a context whose
namespace is the default if and only if the parent context has no namespace
value Final answer: is this PR done enough to commit tomorrow? @lavalamp
https://github.com/lavalamp @derekwaynecarr
https://github.com/derekwaynecarr @smarterclayton
https://github.com/smarterclayton … — Reply to this email directly or
view it on GitHub.


Reply to this email directly or view it on GitHub
https://github.com/GoogleCloudPlatform/kubernetes/pull/1564/files#r18920554
.

@derekwaynecarr
Copy link
Member Author

Rebased branch to latest.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch 2 times, most recently from a28f129 to f4bf740 Compare October 16, 2014 15:45
@derekwaynecarr
Copy link
Member Author

Forced a push to try to fix Travis flake on go 1.2, where go 1.3 worked. Go 1.2 was reporting an error reaching etcd in integration tests.

@thockin
Copy link
Member

thockin commented Oct 16, 2014

I will kick travis until i get a green or get tired.

On Thu, Oct 16, 2014 at 8:46 AM, Derek Carr notifications@github.com
wrote:

Forced a push to try to fix Travis flake on go 1.2, where go 1.3 worked.
Go 1.2 was reporting an error reaching etcd in integration tests.

Reply to this email directly or view it on GitHub
#1564 (comment)
.

@derekwaynecarr derekwaynecarr force-pushed the enforce_namespace_unique_constraints branch from f4bf740 to 085ca40 Compare October 16, 2014 17:06
@thockin
Copy link
Member

thockin commented Oct 16, 2014

Thar she blows!

thockin added a commit that referenced this pull request Oct 16, 2014
…_constraints

Enforce namespace unique constraints
@thockin thockin merged commit 8a9c40c into kubernetes:master Oct 16, 2014
@derekwaynecarr derekwaynecarr deleted the enforce_namespace_unique_constraints branch April 17, 2015 17:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants