-
Notifications
You must be signed in to change notification settings - Fork 11
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
UPSTREAM: <carry>: with shard prefix support #96
UPSTREAM: <carry>: with shard prefix support #96
Conversation
@@ -238,15 +238,23 @@ const ( | |||
// to resource directories enforcing namespace rules. | |||
func NoNamespaceKeyRootFunc(ctx context.Context, prefix string) string { | |||
key := prefix | |||
shard, err := genericapirequest.ValidShardFrom(ctx) |
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.
we need a better way of plugging a shard name into the storage layer.
something that the kcp and the cache server could customize upon initialization.
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.
initially, I wanted to plug in different implementations but I realised the kcp and the cache server might share the same database instance.
|
||
type Shard struct { | ||
// Name is the name of the cluster. | ||
Name 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.
Given that we (likely) have shard name requirements, and *
will not be a valid shard name, is there any reason not just have type Shard string
? Otherwise, what is the meaning of:
Shard{
Name: "*",
Wildcard: false,
}
or
Shard{
Name: "sapphire",
Wildcard: true,
}
?
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.
a request like 'https://localhost:6443/shards/*/clusters/*/apis/apis.kcp.dev/v1alpha1/apiexports'
will set
Shard{
Name: "",
Wildcard: true
}
a request like https://localhost:6443/shards/sapphire/clusters/system:sapphire/apis/apis.kcp.dev/v1alpha1/apiexports'
will set
Shard{
Name: "sapphire",
Wildcard: false
}
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.
Right, those are the happy paths. My question was about the others, which are (I think) not possible. In e.g. kcp-dev/kcp#1841 you have a New('*")
value for wildcard... seems like this could also ?
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 do use New(*)
on the client side just to set the right URL path, then on the server side, it gets mapped to Wildcard: true, Name: ""
.
I think we need both fields. An empty Name:"", Wildcard: false
indicates no shard was specified.
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.
How is Name:"", Wildcard: false
different from New("")
?
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.
My argument: the Wildcard
field as you have written it is logically equivalent in every case to obj.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.
@stevekuznetsov I updated the type to type Shard string
, now Shard("*")
indicates wildcard and Shard("")
indicates a request without a shard name specified.
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 hope it is less confusing now.
e120a07
to
4147b2a
Compare
/assign @stevekuznetsov @sttts ready for review, PTAL. |
proof PR kcp-dev/kcp#1851 |
Name string | ||
|
||
// If true the query applies to all shards | ||
Wildcard 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.
why this complex struct again? Why not "empty shard string = wildcard" or "* shard string = wildcard" ?
Note: we should do the same for the cluster.
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.
we need both, I can replace with a string but then we need to compare to *
@@ -133,8 +134,12 @@ func (w *watcher) Watch(ctx context.Context, key string, rev int64, recursive bo | |||
if err != nil { | |||
return nil, err | |||
} | |||
shard, err := genericapirequest.ValidShardFrom(ctx) |
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.
what does this return if there is no shard in the context?
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.
// ValidShardFrom returns the value of the shard key from the context.
// An empty value is returned if there's no shard key,
// or if the shard name is empty, and it wasn't a wildcard request.
// ValidShardFrom returns the value of the shard key from the context. | ||
// An empty value is returned if there's no shard key, | ||
// or if the shard name is empty, and it wasn't a wildcard request. | ||
func ValidShardFrom(ctx context.Context) (*Shard, error) { |
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.
why "Valid"? ShardFromOrEmpty would be more idiomatic
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.
true
4147b2a
to
9333285
Compare
ba40ff6
to
97e00f1
Compare
@@ -45,26 +51,31 @@ var ( | |||
// adjustClusterNameIfWildcard determines the logical cluster name. If this is not a wildcard list/watch request, | |||
// the cluster name is returned unmodified. Otherwise, the cluster name is extracted from the key based on whether | |||
// it is a CR partial metadata request (prefix/identity/clusterName/remainder) or not (prefix/clusterName/remainder). | |||
func adjustClusterNameIfWildcard(cluster *genericapirequest.Cluster, crdRequest bool, keyPrefix, key string) logicalcluster.Name { | |||
func adjustClusterNameIfWildcard(shard genericapirequest.Shard, cluster *genericapirequest.Cluster, crdRequest bool, keyPrefix, key string) logicalcluster.Name { | |||
if cluster.Name != logicalcluster.Wildcard { | |||
return cluster.Name | |||
} | |||
|
|||
keyWithoutPrefix := strings.TrimPrefix(key, keyPrefix) | |||
|
|||
var regex *regexp.Regexp |
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.
move this into the first if.
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.
done.
97e00f1
to
6986f91
Compare
|
||
// ShardFrom returns the value of the shard key in the context, or an empty value if there is no shard key. | ||
func ShardFrom(ctx context.Context) Shard { | ||
shard, ok := ctx.Value(shardContextKey).(Shard) |
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.
nit: this simplifies to
func ShardFrom(ctx context.Context) Shard {
return ctx.Value(shardContextKey).(Shard)
}
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 could, it will just panic if the value under the key cannot be cast to Shard
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.
The shardContextKey
is private to this package. The only way that it would panic is if someone wrote code in this package to add the wrong thing. /shrug
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, I just don't like when the code panics but sure I can change 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.
and it broke the kcp server
I0901 10:30:42.331404 38583 etcd3.go:306] Start monitoring storage db size metric for endpoint https://localhost:45743 with polling interval 30s
panic: interface conversion: interface {} is nil, not request.Shard
goroutine 1 [running]:
k8s.io/apiserver/pkg/endpoints/request.ShardFrom(...)
/go/pkg/mod/github.com/p0lyn0mial/kubernetes/staging/src/k8s.io/apiserver@v0.0.0-20220901092652-1cf194952b52/pkg/endpoints/request/context_shard.go:56
cluster, err := genericapirequest.ValidClusterFrom(ctx) | ||
if err != nil { | ||
klog.Errorf("invalid context cluster value: %v", err) | ||
return key | ||
} | ||
if cluster.Wildcard { |
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 is a bit hard to follow. Is it equivalent:
if !shard.Empty() {
key += "/" + shard.String()
}
if !cluster.Wildcard {
key += "/" + cluster.Name.String()
}
return key
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 seems it is :)
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.
done
@@ -45,26 +51,31 @@ var ( | |||
// adjustClusterNameIfWildcard determines the logical cluster name. If this is not a wildcard list/watch request, | |||
// the cluster name is returned unmodified. Otherwise, the cluster name is extracted from the key based on whether | |||
// it is a CR partial metadata request (prefix/identity/clusterName/remainder) or not (prefix/clusterName/remainder). | |||
func adjustClusterNameIfWildcard(cluster *genericapirequest.Cluster, crdRequest bool, keyPrefix, key string) logicalcluster.Name { | |||
func adjustClusterNameIfWildcard(shard genericapirequest.Shard, cluster *genericapirequest.Cluster, crdRequest bool, keyPrefix, key string) logicalcluster.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.
IMHO this doesn't really make sense to do with regex. What about something like:
func adjustClusterNameIfWildcard(shard genericapirequest.Shard, cluster *genericapirequest.Cluster, crdRequest bool, keyPrefix, key string) logicalcluster.Name {
if cluster.Name != logicalcluster.Wildcard || !cluster.Wildcard { // TODO: fix this duplicity, as well
return cluster.Name
}
keyWithoutPrefix := strings.TrimPrefix(key, keyPrefix)
var extractCluster func([]string) logicalcluster.Name
extractor := func(numParts, clusterPart int) func([]string) logicalcluster.Name {
return func([]string) logicalcluster.Name {
if len(parts) < numParts {
klog.Warnf("shard=%s cluster=%s invalid key=%s had %d parts, not %d", shard, cluster, keyWIthoutPrefix, len(parts), numParts)
return logicalcluster.Name{}
}
return logicalcluster.New(parts[clusterPart])
}
}
switch {
case shard.Wildcard():
// expecting shardName/clusterName/<remainder>
extractCluster = extractor(3,1)
case cluster.PartialMetadataRequest && crdRequest:
// expecting 2699f4d273d342adccdc8a32663408226ecf66de7d191113ed3d4dc9bccec2f2/root:org:ws/<remainder>
// OR customresources/root:org:ws/<remainder>
extractCluster = extractor(3,1)
case cluster.Wildcard():
// expecting root:org:ws/<remainder>
extractCluster = extractor(2,0)
default:
panic("bad programmer")
}
parts := strings.Split(keyWithoutPrefix, "/")
return extractCluster(parts)
}
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.
uh, maybe :) It seems like what we have right now requires less code.
Maybe I could revisit this in the future and try to rewrite (refactor) 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.
Less code how exactly? Half of what I wrote were comments ;) Existing approach with your diff is L29-L80=51LoC. Mine is 35LoC
I think using regex to strings.Split(key, "/")
makes the current approach really hard to follow, especially as we grow more cases.
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 it about taste or a big improvement? Either way is not particularly pretty. But this func does not decide about maintainability of the code, does it? Would be fine with a minimal change. Would pretty much prefer a unit test for either implementation ;-)
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.
@stevekuznetsov would it be okay to add unit tests and leave using the regexps for now?
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.
btw: do we actually run k8s unit tests :) ?
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 don't think it's taste, no, because using regex to split a string on /
is really hard to understand - I think we all tripped over understanding this function when reviewing it, no? Indexing into a slice seems ... slightly more straightforward, as well as using switch/case
to be exhaustive on options?
No, we do not yet run the tests.
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.
@p0lyn0mial can you create an issue to track Steve's suggestion above? Would not like to block this PR on a refactoring that is kind of independent of this change because pre-existing.
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.
keep the suggested code snipped, or even create the follow-up PR just after if it compiles.
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.
done kcp-dev/kcp#1875
a5ea09c
to
4ac288e
Compare
clusterWildcard bool | ||
shardWildcard bool | ||
prefix string | ||
builtInType 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.
can this be pure table test with inputs and outputs? Irritating there is no "expected*" in here. Just put genericapirequest.Cluster
and genericapirequest.Shard
in here, no need for abstraction.
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.
updated
4ac288e
to
1cf1949
Compare
/lgtm |
This PR adds support for setting a shard prefix to the storage layer. The prefix is used by the cache server. The storage layer has to understand both the cluster and the shard prefixes because the database can be shared between the kcp and the cache server. Once a shard name is set, the storage layer will change the key under which a resource is stored, for example: amber/root:org/default/secret amber/root:org/secret Where: - amber: is a shard name - root:org: is a cluster name - default: is a namespace name - secret: is a name of a resource
1cf1949
to
c8d74a9
Compare
What type of PR is this?
What this PR does / why we need it:
This PR adds support for setting a shard prefix to the storage layer.
The prefix is used by the cache server.
The storage layer has to understand both the cluster and the shard prefixes
because the database can be shared between the kcp and the cache server.
Once a shard name is set, the storage layer will change the key under which a resource is stored, for example:
amber/root:org/default/secret
amber/root:org/secret
Where:
Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
Does this PR introduce a user-facing change?
Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.: