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
thirdpartyresource add multiple version support #36977
Conversation
a5489e4
to
c24d4e1
Compare
// any versions the user provides take priority | ||
priorities := m.KindPriority | ||
if len(versions) > 0 { | ||
priorities = make([]unversioned.GroupVersionKind, 0, len(m.KindPriority)+len(versions)) | ||
for _, version := range versions { | ||
if strings.Count(version, "/") == 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.
This doesn't look right. Doesn't ParseGroupVersion
already do this? Also, open an issue to fix this method signature. Either it gets a version or a groupversion, but we shouldn't have strings floating around anymore.
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 problem with ParseGroupVersion
is if version parameter doesn't match group/version
, for example v2
, it will return GroupVersion{"", "v2"}
with empty group. And this is not correct if a TPR schema has multi versions: v1
, v2
, and user provides v2
, this will fail to recognize. Why not use group field of groupkind?
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.
v2 with empty group is not valid. All TPR would have somegroup/v2
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 question is when version would be group/version
or version
when call restmapper?
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.
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 will open a separate issue to describe this.
@@ -108,6 +108,7 @@ func (a *APIInstaller) NewWebService() *restful.WebService { | |||
mediaTypes, streamMediaTypes := mediaTypesForSerializer(a.group.Serializer) | |||
ws.Produces(append(mediaTypes, streamMediaTypes...)...) | |||
ws.ApiVersion(a.group.GroupVersion.String()) | |||
ws.SetDynamicRoutes(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.
This change isn't obvious to me. Why would make this the new default?
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 our failed TPR integration test for example, if we create two TPR schemas: foo.company.com
and bar.company.com
, both with v1
, thirdparty server will create a WebService
, but if we delete bar.company.com
, thirdparty server does nothing. And of course we cannot remove the entire web service, but we can remove bar.company.com
related routes, as for go-restful, if we don't enable dynamic routes, we are note allowed to remove these routes.
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 wary of changes that affect things other than the TPR endpoints. What if we made the resource endpoint a nested web service? Those were allowed to be added/removed previously without this setting
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.
@liggitt this attribute is just webservice level, which gives us chance to remove routes handler of a specific webservice. I am not quite clear what you mean nested web service were allowed to be removed, would you mind explain 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.
@liggitt this attribute is just webservice level, which gives us chance to remove routes handler of a specific webservice. I am not quite clear what you mean nested web service were allowed to be removed, would you mind explain more?
Can you create a webservice with a narrower scope for each TPR group? This sort of change is why we're interested in breaking TPRs out of the main API server.
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.
+1 for putting TPRs in a separate ws, so that there's no way of accidentally removing a route for a built-in API.
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.
@lavalamp I will try to update this, but what I am really confused is whether I should go on this PR against current apiserver or I should start working on a add-on server?
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.
@adohe is there already an addon server in developing status?
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.
IMO it's not bad to make improvements to the existing TPR implementation. We won't be moving it this quarter, I'm pretty sure.
@@ -192,6 +193,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag | |||
if err != nil { | |||
return nil, err | |||
} | |||
fmt.Printf("register resource handler for path: %s and the fqKind is: %v\n", path, fqKindToRegister) |
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.
debug
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.
@@ -54,12 +54,14 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) { | |||
return nil, fmt.Errorf("unable to decode %q: %v", source, err) | |||
} | |||
|
|||
fmt.Printf("info for data gvk is: %v\n", gvk) |
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.
debug
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.
return false, nil | ||
} | ||
} | ||
return true, nil | ||
} | ||
|
||
func (m *ThirdPartyResourceServer) removeThirdPartyStorage(path, resource string) 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.
Can we change the signature so we get the info we need without string parsing?
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 about change this to removeThirdPartyStorage(group, version, resource string)
, wdyt?
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.
or gvr unversioned.GroupVersionResource
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.
} | ||
|
||
if ws != nil { | ||
pattern := path + "/.*/" + resource + ".*" |
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 is the trailing .*
needed
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 should be some problem with this regexp, I am fixing 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.
Done.
Jenkins GCE etcd3 e2e failed for commit 3d6a4c9. Full PR test history. The magic incantation to run this job again is |
Jenkins Kubemark GCE e2e failed for commit 3d6a4c9. Full PR test history. The magic incantation to run this job again is |
Jenkins GCE e2e failed for commit 3d6a4c9. Full PR test history. The magic incantation to run this job again is |
Jenkins GCI GKE smoke e2e failed for commit 3d6a4c9. Full PR test history. The magic incantation to run this job again is |
Jenkins kops AWS e2e failed for commit 3d6a4c9. Full PR test history. The magic incantation to run this job again is |
Jenkins GCE Node e2e failed for commit 3d6a4c9. Full PR test history. The magic incantation to run this job again is |
I get some time to get this updated, and fix all tests failure. @deads2k @liggitt @smarterclayton please take a look, and give a deep review. I would like to get this merged and then go on with TPR finalizers. |
@@ -76,19 +78,31 @@ func (t *ThirdPartyController) SyncResources() error { | |||
} | |||
|
|||
func (t *ThirdPartyController) syncResourceList(list runtime.Object) error { | |||
existing := sets.String{} | |||
existing := map[metav1.GroupVersionResource]sets.Empty{} |
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.
map[]struct{}
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.
switch list := list.(type) { | ||
case *extensions.ThirdPartyResourceList: | ||
// Loop across all schema objects for third party resources | ||
for ix := range list.Items { | ||
item := &list.Items[ix] | ||
// extract the api group and resource kind from the schema | ||
_, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(item) | ||
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(item) |
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 this API should return GroupKind
and GroupResource
for you.
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 will check this when I return from ooo.
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.
maybe we can return GroupKind
here, seems we cannot extract GroupResource
from item, wdyt?
ListThirdPartyResources() []string | ||
// List all currently installed third party resources, return | ||
// collection of all groupVersionResource | ||
ListThirdPartyResources() []metav1.GroupVersionResource |
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.
good change.
found = true | ||
break | ||
} | ||
} | ||
// not expected, delete the resource | ||
if !found { | ||
if err := t.master.RemoveThirdPartyResource(installedAPI); err != nil { | ||
if err := t.master.RemoveThirdPartyResource(installedGvr); err != nil { |
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 see the fake for this changed, and the interface, but I can't seem to find where the actual impl changed.
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.
nm, github helpfully hid the diff.
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.
actually, the implementation changed a lot :)
@@ -84,12 +88,25 @@ func NewThirdPartyResourceServer(genericAPIServer *genericapiserver.GenericAPISe | |||
return ret | |||
} | |||
|
|||
// thirdPartyEntry combines objects storage and API group into one struct | |||
type thirdPartyGroup struct { | |||
versionedEntry map[string]*thirdPartyEntry |
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.
document the key for this map. Version?
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.
group metav1.APIGroup | ||
} | ||
|
||
// thirdPartyEntry combines objects storage into one struct | ||
// for easy lookup. | ||
type thirdPartyEntry struct { |
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.
tprVersionStorage
?
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.
good suggestion :)
|
||
func (g *thirdPartyGroup) versionInstalled(version string) bool { | ||
for installedVersion := range g.versionedEntry { | ||
if reflect.DeepEqual(version, installedVersion) { |
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.
deep equal on string is weird.
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.
// map from api path to a tuple of (storage for the objects, APIGroup) | ||
thirdPartyResources map[string]*thirdPartyEntry | ||
// map from api path to a tuple of (versioned storage for the objects, APIGroup) | ||
thirdPartyResources map[string]*thirdPartyGroup |
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.
keying by path is weird. Why not key by group name and have the tprGroup hold a path value?
return false, nil | ||
} | ||
plural, _ := meta.KindToResource(schema.GroupVersionKind{ | ||
Group: group, | ||
Version: rsrc.Versions[0].Name, | ||
Kind: kind, | ||
}) | ||
_, found := entry.storage[plural.Resource] | ||
return found, nil | ||
for _, version := range rsrc.Versions { |
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.
Please rename rsrc
to tpr
. I keep having to look up what this 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.
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// HasThirdPartyResource returns true if a particular third party resource currently installed. |
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 its installed in one version and not another, what's the behavior?
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 think I actually expect this to take a GVR. That would make it unambiguous.
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 we change HasThirdPartyResource(rsrc)
to HasThirdPartyResource(gvr)
, then I would suppose we should make the same change to InstallThirdPartyResource
, which means we should update the thirdparty controller. wdyt?
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.
one TPR schema could define multiple GroupVersionResource
identified resource, so update method to accept gvr
parameter could make sense.
if !found { | ||
return nil | ||
} | ||
storage, found := entry.storage[resource] | ||
storage, found := entry.storage[gvr.Resource] | ||
if !found { | ||
return nil | ||
} | ||
if err := m.removeAllThirdPartyResources(storage); err != nil { |
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 method just misnamed? Is it deleting a single storage?
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.
actually it is deleting all TPR data in this storage registry.
} | ||
|
||
if ws != nil { | ||
pattern := path + "/.*" + gvr.Resource + ".*" |
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 the wildcarding? This looks like it would hit false positives.
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, this is still not correct.
I think this may just be too big to fix inside of our current API server. The changes are pervasive and are changing attributes our current RESTHandler to allow removal. I worry about accidentally removing something important. @lavalamp @kubernetes/sig-api-machinery-misc If we ran TPRs out of a addon API server, we could have a completely separate handler chain and have confidence of containment for changes like this. |
@adohe PR needs rebase |
@lavalamp could you please take a look? If we decide to run TPRs out of a addon API server, I would start building this addon server, and add this to that server. |
[APPROVALNOTIFIER] Needs approval from an approver in each of these OWNERS Files: We suggest the following people: |
See also #24299 |
Since TPRs cannot be converted between versions by the server, we cannot enable multiple versions in the API server and behave like a standard Kubernetes API. |
@liggitt yes, so I am going to close this and will re-implement it in an addon server. |
@adohe Did you get a chance to start work on the addon server? I'm interested in helping out with TPRs, so was interested in the current status of things |
We're looking at this proposal to move things forward: kubernetes/community#524. |
This PR hasn't been active in 31 days. It will be closed in 58 days (Jul 20, 2017). You can add 'keep-open' label to prevent this from happening, or add a comment to keep it open another 90 days |
This change is