-
Notifications
You must be signed in to change notification settings - Fork 706
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
Initial implementation of the remaining core apis operations #3376
Conversation
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
cmd/kubeapps-apis/server/packages.go
Outdated
} | ||
|
||
// Build the response | ||
// TODO: Sort via default sort order or that specified in request. |
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.
// TODO: pagination support (we still need to update the existing pagination in helm to use item offsets rather than page offsets to do this)
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.
Indeed. In fact, we need GetAvailablePackagesSummaries
to be paginated; otherwise, the catalog just won't work (I mean, well fetch the first n
elements).
I've implemented a dirty initial way just fetching all the plugin's responses w/o pagination, and then, paginate the aggregated 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.
Yeah, OK. Good for now but let's not leave it that way for long.
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Tested it against #3377 and seems to work :) The test suite (with the mocked plugin, thx Michael!) could be improved to test more cases, but I'd rather like to do it in a follow-up PR and start using the core APIs in the UI as soon as possible to discover more issues or edge 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.
Thanks Antonio. Looking great with the tests. I'm OK with this landing without pagination or filtering/ordering, but am keen to make sure that the data returned by a test plugin can be specified by the test itself, rather than relying on the hard-coded values set in the code for the test plugin. See inline for details.
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
package 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.
We probably want this to be a _test package? (ie. something that can be imported in tests, but not in non-test 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.
Good idea. Done ;)
}, | ||
Categories: []string{"cat1"}, | ||
NextPageToken: "1", | ||
}, 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.
So with this approach, GetAvailablePackageSummaries
and the other methods always return the same data. Another approach is to extend your TestPackagingPlugin
struct with:
type TestPackagingPlugin struct {
packages.UnimplementedPackagesServiceServer
plugin *plugins.Plugin
availablePackageSummaries []*corev1.AvailablePackageSummary
availablePackageDetail *corev1.AvailablePackageDetail
installedPackageSummaries []*corev1.InstalledPackageSummary
installedPackageDetail *corev1.InstalledPackageDetail
}
and have each function return whatever data you test has explicitly set the test plugin to use (we can later easily add support for pagination in the test plugin too).
return &packages.GetAvailablePackageVersionsResponse{ | ||
PackageAppVersions: []*corev1.PackageAppVersion{ | ||
{ | ||
PkgVersion: "3.0.0/" + s.plugin.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.
Why is s.plugin.Name
relevant here? Shouldn't PkgVersion
just be the semver2 version of the package?
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 really was for checking the core operation is returning the appropriate PackageAppVersion
of the requested plugin, but now we are customizing the responses, we can just add this thing for the required test, and not for every test.
} | ||
|
||
// TODO(agamez): temporarily fetching all the results (size=0) and then paginate them | ||
// ideally, paginate each plugin request and then aggregate results. |
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 really should update the existing pagination to use the number of items for the page offset ASAP. If we keep adding more code that assumes the existing number of pages as the page offset, it's going to get harder to change. Not for this PR, but let's ensure we follow up with that soon.
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.
Uff, totally agree... snowball effect :( Added as a note for the iteration discussion.
cmd/kubeapps-apis/server/packages.go
Outdated
// TODO: We can do these in parallel in separate go routines. | ||
for _, p := range s.plugins { | ||
response, err := p.server.GetAvailablePackageSummaries(ctx, request) | ||
response, err := p.server.GetAvailablePackageSummaries(ctx, requestN) |
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 currently we're just appending the results from each plugin, we can actually exit this loop if the size of pkgs
already exceeds the offset + page size, I think? (We could also do something smart with the page token to avoid re-grabbing results from plugins, but probably not worth it as we'd be better off just implementing the correct pagination.)
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 idea, at least we prevent unnecessary plugin requests. Done. But certainly we have to come up with a proper way of pagination :S
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 we already have one in mind, we just need to update the existing pagination (throughout) to use the number of items as the offset, rather than the number of pages, to then be able to update the next page tokens in the plugins.
AvailablePackageDetail: makeAvailablePackageDetail("pkg-1", makeTestPackagingPlugin("mock1").plugin), | ||
}, | ||
statusCode: codes.OK, | ||
}, |
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 could add a case here where the plugin will return a 404... and the aggregate will currently return a 500 - whereas we'll want a 404.
} | ||
|
||
if tc.statusCode == codes.OK { | ||
opt1 := cmpopts.IgnoreUnexported(corev1.InstalledPackageStatus{}, corev1.VersionReference{}, corev1.GetInstalledPackageSummariesResponse{}, corev1.InstalledPackageSummary{}, corev1.Maintainer{}, corev1.GetAvailablePackageDetailResponse{}, corev1.AvailablePackageReference{}, corev1.AvailablePackageSummary{}, corev1.InstalledPackageDetail{}, corev1.GetInstalledPackageDetailResponse{}, corev1.GetInstalledPackageSummariesResponse{}, corev1.AvailablePackageDetail{}, corev1.GetAvailablePackageDetailResponse{}, corev1.GetAvailablePackageSummariesResponse{}, corev1.GetAvailablePackageVersionsResponse{}, corev1.GetAvailablePackageVersionsResponse{}, corev1.InstalledPackageSummary{}, corev1.InstalledPackageReference{}, corev1.Context{}, plugins.Plugin{}, corev1.PackageAppVersion{}) |
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.
Fine either way, but I've started defining this option once for the set of tests (could even be for the file) and just using it in tests. For eg, see: https://github.com/kubeapps/kubeapps/blob/master/cmd/kubeapps-apis/plugins/helm/packages/v1alpha1/create_test.go#L98
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 point! I just wrote everything quickly to get it ready for review as soon as possible (it was a bit late yesterday :P), and didn't revisit it to check for refactors and improvements. Thanks for pointing it out!
InstalledPackageDetail: makeInstalledPackageDetail("pkg-1", makeTestPackagingPlugin("mock1").plugin), | ||
}, | ||
statusCode: codes.OK, | ||
}, |
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.
Same with the NotFound
test.
expectedResponse: &corev1.GetAvailablePackageVersionsResponse{ | ||
PackageAppVersions: []*corev1.PackageAppVersion{ | ||
{ | ||
PkgVersion: "3.0.0/mock1", |
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.
Ah - that answers an earlier question: you're setting the plugin name in the package version in the other file just so you can verify the correct versions here (ie. coming from the correct plugin). Again, this would be better if the test itself specified the []*corev1.PackageAppVersions
for each plugin, then verified the result (ie. all info is here in the test specification).
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
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.
Great, thanks Antonio!
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
package _test |
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.
Sorry, my reply had a typo... I had meant write server_test
(ie. rather than having it as part of the server
package directly as earlier), but it probably belongs best as plugin_test
? Anyway, the key point is that the module ends in _test
, but it shouldn't just be _test
.
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.
Ah, got it! I assumed it was a sort of convention for mocks :D, no problem, moving to plugin_test
instead.
|
||
func MakeTestPackagingPlugin( | ||
plugin *plugins.Plugin, | ||
availablePackageSummaries []*corev1.AvailablePackageSummary, |
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.
Up to you, but it might be easier to use if, rather than requiring all this data (and lack of data) when creating the test plugin, if the callsite can just create it without any data, then set what they want through public fields.
return &packages.GetAvailablePackageVersionsResponse{ | ||
PackageAppVersions: s.packageAppVersions, | ||
}, 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.
Nice.
cmd/kubeapps-apis/server/packages.go
Outdated
contextMsg := "" | ||
if request.Context != nil { | ||
contextMsg = fmt.Sprintf("(cluster=[%s], namespace=[%s])", request.Context.Cluster, request.Context.Namespace) | ||
} |
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 could just replace the above 4 lines with:
contextMsg = fmt.Sprintf("(cluster=[%s], namespace=[%s])", request.GetContext().GetCluster(), request.GetContext().GetNamespace())
if you're OK with "(cluster=[], namespace=[])" when they're not defined.
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.
Great, this also applies to the rest of the helm/kapp already implemented operations. I'll send an additional PR for that.
OrderBy(func(pkg interface{}) interface{} { | ||
return pkg.(*packages.InstalledPackageSummary).Name + pkg.(*packages.InstalledPackageSummary).InstalledPackageRef.Plugin.Name | ||
}). | ||
ToSlice(&pkgs) |
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.
When we do the pagination correctly, we'll need different sortiing here anyway (since we'll need to keep pulling from each plugin and appending the next item in order. Sounds fun :)
} | ||
|
||
if tc.statusCode == codes.OK { | ||
opt1 := ignoreUnexportedOpts |
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.
Fine as is, but really no need for opt1
? (you can just pass in ignoreUnexportedOpts
below).
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.
Oops, just a leftover, thanks!
statusCode: codes.OK, | ||
}, | ||
{ | ||
name: "it should fail when calling the core GetAvailablePackageDetail operation with one of the plugin failing", |
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.
Copy-n-paste error? I think this should be "it should fail when calling the core GetAvailablePackageDetail operation when the package is not present" or something?
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.
Renamed to (...) when the package is not present in a plugin. However, what I really wanted to highlight with this name is that, currently, the core APIs are failing if one (or more) plugin(s) fails.
If, in the future, we add a fail-safe mode (eg. ignoring failing plugins or letting users configure the strict: true/false
when enabling plugins), we might want to revisit these tests.
t.Run(tc.name, func(t *testing.T) { | ||
server := &packagesServer{ | ||
plugins: tc.configuredPlugins, | ||
} |
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.
So this is fine as is, but just for a bit of explanation of what I'd meant in the previous review: If it's easy to configure the test plugin for each test, then each test above could instead be very explicit rather than referring to pre-built objects defined elsewhere in the file. I'll try to make some time to have a play later in the week perhaps and show you what I mean.
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'm getting now what you're suggesting. Whereas I do agree on being as explicit as possible in each test case, for a low-specificity test (I mean, just aimed at testing it works), I consider using pre-built objects a more compact and readable was.
But, on the other hand, it requires reading what the object/fn is really doing... tradeoffs
I guess that building the plugin in each test case with the proper returned values (via SetXXX()) should do the trick. I can perform the changes if you want.
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 was fine as is, but thanks for updating. Note that you didn't need to add setters I don't think? What I was suggesting earlier is just making those fields (of the TestPackagingPluginServer
as public fields (ie. capital first letter) so that call-sites can just set them. But fine with setters too. Still +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 consider using pre-built objects a more compact and readable was.
But, on the other hand, it requires reading what the object/fn is really doing... tradeoffs
Yes, they are trade-offs. For tests, there is the point you mentioned (that you have to look elsewhere to see what the test input or output actually is), and also that you have to munge data into that one object for specific tests (like you were earlier for the PkgVersion
), and that changing that one reference object effects many tests.
The opposite of having every object explicitly declared in each test also has its obvious downsides. I usually try for a good test object factory (simple make functions if i've nothing at hand), that enable defining just what is required to be read for the test with good defaults for the rest. But not always easy to get that balance.
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com> Conflicts: cmd/kubeapps-apis/server/packages.go
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Description of the change
This PR brings an initial implementation for the already discussed and agreed operations. I've been testing these APIs in the frontend to ensure everything works as expected after the change (helm->core).
Also, I've created an initial test suite for these operations, but I'm having some issues I wanna discuss later.
Benefits
Core (plugin-agnostic) APIs finally!
Possible drawbacks
I still have to check everything works as we expect once we enable the Flux plugin.
Applicable issues
Additional information
Marking it as a draft as I still have to implement the recently added
CreateInstalledPackage
Also, I'm having troubles with the tests; let's discuss it offline, but I'd some advice on how to fake the loaded plugins for each test case. Also, I'm unsure of the scope of these tests (ex: whether we have to fake each plugin response (aka, faking postgres, faking redis etc...)