REST API for bundles, types, and subtypes#810
Conversation
8355661 to
71f43d5
Compare
also tweaks to other classes to make them more usable, eg VersionedName comparables and bundle removal giving an OsgiBundleInstallationResult test in BundleAndTypeAndSubtypeResourcesTest based on CatalogResourceTest, basically giving parity in terms of functionality and test coverage
71f43d5 to
9b33703
Compare
BrooklynCatalog api tidy minor conflict
|
|
||
| /** @deprecated since 0.12.0 use /bundle, /type, and /subtype */ | ||
| // but we will probably keep this around for a while as many places use it | ||
| @Deprecated |
There was a problem hiding this comment.
I strongly disagree with deprecating the entire /catalog endpoint. I think we should rather deprecate /catalog/(entities|applications|policies|enrichers) and add /bundle, /type and /subtype within /catalog as we have a "catalog" of bundles, types, etc. (that is effectivement what you did with the /type endpoint, by duplicating /application, /entities, etc) Promoting them to the root level feel way too loose.
Also, what is the difference between type and subtype? It's not clear to me and it looks like a subtype is just a subset of type. If that is a case, why bother having a endpoint for this? Cannot we just have a filter on the /type endpoint?
Regarding the bundle additions, the POST /catalog/create can already deal with bundles so I would just reuse that. For the deletion, I would use the DELETE /catalog/bundles/<symbolicName>/<version>.
There was a problem hiding this comment.
update: i think this is how we are going to do it now
| @JsonInclude(value=Include.NON_EMPTY) | ||
| private Set<String> aliases; | ||
| @JsonInclude(value=Include.NON_EMPTY) | ||
| private Set<Object> supertypes; |
| @Consumes(MediaType.APPLICATION_JSON) | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| public interface SubtypeApi { | ||
|
|
There was a problem hiding this comment.
DRY just add the below filters to the TypeApi as a query param
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
| import com.fasterxml.jackson.annotation.JsonInclude.Include; | ||
|
|
||
| public class TypeDetail extends TypeSummary { |
There was a problem hiding this comment.
Public api ... add java doc
| import com.fasterxml.jackson.annotation.JsonInclude.Include; | ||
| import com.google.common.collect.ComparisonChain; | ||
|
|
||
| public class TypeSummary implements Comparable<TypeSummary> { |
| @ApiOperation(value = "Get summaries for all versions and instances of a given type or alias, with best match first", | ||
| response = TypeSummary.class, | ||
| responseContainer = "List") | ||
| public List<TypeSummary> listVersions( |
There was a problem hiding this comment.
The response type seems odd to me ... I would expect it to only return 1 summary
If aliases can be re used i think this is a big mistake
There was a problem hiding this comment.
@m4rkmckenna I think it's a list because it's giving you info about each version (e.g. an entry for v1.0.0 and another for v1.1.0).
There was a problem hiding this comment.
Interesting point about reusing aliases. Hopefully that's not the case!
There was a problem hiding this comment.
yes this is for all verisons
| String versions, | ||
| @ApiParam(name = "regex", value = "Regular expression to search for") | ||
| @QueryParam("regex") @DefaultValue("") String regex, | ||
| @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") |
There was a problem hiding this comment.
Is regex and fragment not a duplication
"some-fragment" == ".*some-fragment.*"
There was a problem hiding this comment.
Not if some-fragment contains a . or other special character. Globs are more user friendly and search friendly. (This is borrowed from elsewhere in API incidentally where we do the same thing and there have been times I've been grateful for the easier fragment.)
There was a problem hiding this comment.
No strong feelings, but I wonder if we should err towards the smaller api (simpler to support and for someone to get their head around). You can achieve everything with regex. A UI could offer a fragment search on top of that, escaping things as needed.
There was a problem hiding this comment.
Could do with more description of what the fragment will search (think it's just the name or alias?).
Somewhere in the existing /v1/catalog api the fragment will search the entire yaml plan - that's surprising, something useful, and sometimes annoying when it returns a bunch of seemingly unrelated things!
There was a problem hiding this comment.
fragment is v useful as regex-escaping is tedious (esp for case insensitive!). it could be called glob to be clearer. agree we should say what is searched, will do that.
There was a problem hiding this comment.
(change to glob could be /v2beta item)
| String supertype, | ||
| @ApiParam(name = "versions", value = "Whether to list 'latest' of each symbolic-name or 'all' versions", required = false, defaultValue = "latest") | ||
| @QueryParam("versions") | ||
| String versions, |
There was a problem hiding this comment.
I think plural as version=all doesn't make sense and it implies wanting to do things like version=1.0. It could be boolean allVersions but my thinking was in future this might take a range.
|
TL;DR: I propose we split this PR into two. The I don't want to lose the word "catalog" in the REST api - it's so good at getting across the meaning for users! The alternative The multiple endpoints of If designing a The meaning of |
|
As per ML: Aled, Thomas, Mark - thanks for the good feedback. All - There are some good suggestions which I agree deserve discussion. Specific points. (1) Should /types and /bundles be top-level or under a /catalog prefix ? Thomas suggested the latter which also fits. My reason for doing top-level is simply that most REST APIs these days seem to have more top-level resources. /catalog is not necessary and in some ways it's cleaner having separate endpoints. On the other hand the /catalog prefix is nice to orient consumers, as Aled says: (2) Should we deprecate (3) Should we switch to /v2 ? Aled has suggested we do as the generic (4) Should (There are some other minor issues but I think I agree with the points made there.) My answers: (1) slight preference for the |
|
Regarding other point about template v application - should that be addressed in this PR @aledsage or could it be a subsequent one? |
|
For template versus application, we can defer that (as long as we're not making pain for ourselves in this api; I don't think we are). Good to separate things into more PRs. UIs that want to show a quick launch will continue to use |
| } | ||
|
|
||
| private static <T extends TypeSummary> T embellish(T result, RegisteredType item, boolean detail, BrooklynRestResourceUtils b, UriBuilder ub) { | ||
| result.getExtraFields().put("links", makeLinks(item, ub)); |
There was a problem hiding this comment.
This use of getXyz().put(key, val) is a really bad pattern, especially when there is a setExtraField(key, val) method you can call instead.
It makes it a lot less clear to people supporting the code how the BundleSummary is populated.
| for (Effector<?> x: type.getEffectors()) | ||
| effectors.add(EffectorTransformer.effectorSummaryForCatalog(x)); | ||
|
|
||
| result.getExtraFields().put("config", config); |
There was a problem hiding this comment.
Where do you imagine we'll document the names "config", "sensors" and "effectors" (or do people have to discover it by making some live calls and figuring out what looks to be there consistently for everything of a given type)?
Where would we document the type of the values for something like an effector?
I think the looseness won't be an issue for Brooklyn developers (because we can easily navigate the code base to find the EffectorTransformer, when working on the CLI or UI).
However, I think it makes it feel more scary for a user of Brooklyn who is thinking of writing an integration with our REST api. They won't know what fields to expect. They can make a few calls and then guess at what is optional versus mandatory, and then code as defensively as possible in case their guess was wrong.
There was a problem hiding this comment.
for now there is no doc for extra info for specific types. agree this is a gap but it is a lot more classes and maintenance to have all that detail completely type specific. i'm quite sure run-time discoverability for extra fields will be sufficient for a while -- and it's not at all uncommon in pragmatic REST APIs i have used -- and when users start wanting more precision then i think it's worth doing the extra work (and preference for extra fields added in this class with docs to say they are only for selected types, rather than lots of subtypes).
| @ApiOperation(value = "List bundles registered in the system including their types", | ||
| response = BundleSummary.class, | ||
| responseContainer = "List") | ||
| public List<BundleSummary> list( |
There was a problem hiding this comment.
Feels like this will return a huge amount of data (giving all the types that are inside the bundle).
Do we want a way to just list the bundles? I think we want to have a BundleSummary and a BundleDetail, where only the latter includes all the types. The list method could include support for ?detail=true if we think that's important for a UI to retrieve everything it needs to show in one go.
| @ApiResponse(code = 400, message = "Error processing the given archive, or the catalog.bom is invalid"), | ||
| @ApiResponse(code = 201, message = "Catalog items added successfully") | ||
| }) | ||
| public Response createAutodetecting( |
There was a problem hiding this comment.
I lean towards not doing this - instead rely on Content-Type: ... being supplied.
I say this because of the annoying behaviour in the existing api for things like POST /v1/catalog. If you post an invalid .bom file, it first tries to parse it as a .bom which fails, and then tries to parse it as a different format. The error that gets returned is often misleading (e.g. it gives you details of the second attempt, rather than the first attempt). I've hit this problem while trying to help real newbee (but smart) users of Brooklyn.
Looking at this new code, it would be quite easy to lose the error message that tells you your yaml was invalid, with it instead telling you that it's not a zip file.
What does this really buy us? The UI and CLI will always supply it. If using curl then you have to type a little bit more, but I think that's fine.
There was a problem hiding this comment.
this is all being removed as we will just have POST /catalog -- but i agree with you
| return data; | ||
| } | ||
| } | ||
| private final TypeImplementationPlanSummary plan; |
There was a problem hiding this comment.
I'd make this non-final, so that json-deserialization doesn't have to do nasty stuff.
|
|
||
| @Override | ||
| public BundleInstallationRestResult remove(String symbolicName, String version, Boolean force) { | ||
| ManagedBundle b = lookup(symbolicName, version); |
There was a problem hiding this comment.
Really want an entitlements check (as per your previous TODOs). Otherwise the presence of the /bundles endpoint will be a horrible backdoor for allowing a user to damage other people's apps.
| String versions, | ||
| @ApiParam(name = "regex", value = "Regular expression to search for") | ||
| @QueryParam("regex") @DefaultValue("") String regex, | ||
| @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") |
There was a problem hiding this comment.
No strong feelings, but I wonder if we should err towards the smaller api (simpler to support and for someone to get their head around). You can achieve everything with regex. A UI could offer a fragment search on top of that, escaping things as needed.
| String versions, | ||
| @ApiParam(name = "regex", value = "Regular expression to search for") | ||
| @QueryParam("regex") @DefaultValue("") String regex, | ||
| @ApiParam(name = "fragment", value = "Substring case-insensitive to search for") |
There was a problem hiding this comment.
Could do with more description of what the fragment will search (think it's just the name or alias?).
Somewhere in the existing /v1/catalog api the fragment will search the entire yaml plan - that's surprising, something useful, and sometimes annoying when it returns a bunch of seemingly unrelated things!
| @ApiOperation(value = "Get summaries for all versions and instances of a given type or alias, with best match first", | ||
| response = TypeSummary.class, | ||
| responseContainer = "List") | ||
| public List<TypeSummary> listVersions( |
There was a problem hiding this comment.
@m4rkmckenna I think it's a list because it's giving you info about each version (e.g. an entry for v1.0.0 and another for v1.1.0).
| @ApiOperation(value = "Get summaries for all versions and instances of a given type or alias, with best match first", | ||
| response = TypeSummary.class, | ||
| responseContainer = "List") | ||
| public List<TypeSummary> listVersions( |
There was a problem hiding this comment.
Interesting point about reusing aliases. Hopefully that's not the case!
still to do: supertypes as query param, and restructure to hang both of catalog (remove POST /catalog/bundles)
|
all updated as discussed, running tests now. misc notes:
|
move /bundles and /types under /catalog, remove subtype, remove autodetect create in POST /catalog/bundles
412ce6c to
8e69330
Compare
|
|
||
| private static class AnySuperTypeMatches implements Predicate<RegisteredType> { | ||
| private final Predicate<Class<?>> filter; | ||
| private final Predicate<Object> filter; |
There was a problem hiding this comment.
Is this definitely not persisted anywhere? If it is, then changing the generics could cause weird errors later.
There was a problem hiding this comment.
didn't think generics would affect persistence, but i've restored it and made a new class, just in case.
also removed inner classes elsewhere here where they might be returned to the caller.
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import com.google.common.base.Function; |
There was a problem hiding this comment.
I only just noticed that a bunch of this code is in core/mgmt/ha. Why is that? I'd have thought it would equally apply to non-ha (e.g. persisting your state locally, and then restarting Brooklyn).
There was a problem hiding this comment.
indeed, longstanding misplacement. would be good to move OsgiManager and the related classes.
|
|
||
| /** @deprecated since 0.13.0 delete the bundle via DELETE /catalog/bundles/xxx */ | ||
| // but we will probably keep this around for a while as many places use it | ||
| @Deprecated |
There was a problem hiding this comment.
I'm still uncomfortable about the way we're handling our versioning, and with deprecating /v1/catalog methods. To me, the old /catalog is the "v1 way of doing it". Once we do a properly versioned /v2 then it will be clear that v1 is deprecated (and we'll document it as such).
But we can go with this for now.
For future work, it would be great if we can get a process in place by which we version the api in a sensible way: to help people understand what they are using, what is replacing it, and how/when it will be deprecated.
A big problem with @Deprecated here is that I don't think an api user is told about it at all (it's not even in the swagger), so from the perspective of a rest-api-user it's as though it's not deprecated.
| import com.google.common.collect.ImmutableMap; | ||
|
|
||
| public class EnricherConfigSummary extends ConfigSummary { | ||
| // TODO remove? this class has no value over its super |
There was a problem hiding this comment.
Agreed - I think we can safely delete it. The json returned by AdjunctConfigSummary will be identical to EnricherConfigSummary etc. But we can do that in a future PR.
Not sure what the difference is between EntityConfigSummary and AdjunctConfigSummary (it has "pinned" and "constraints"). Adjuncts can have constraints, I believe. For "pinned", the CatalogConfig javadoc says "a pinned configuration means that the config key will always be displayed when presenting as editable in the catalog", so I think that also applies to adjuncts (well, most adjuncts and eventually all).
There was a problem hiding this comment.
agree, there is no reason to have more than one ConfigSummary
| import com.google.common.collect.ImmutableMap; | ||
| import com.google.common.collect.Iterables; | ||
|
|
||
| public class BundleAndTypeResourcesTest extends BrooklynRestResourceTest { |
There was a problem hiding this comment.
Great tests!
Class is getting pretty big. Maybe worth splitting into separate classes for bundle and type tests (or separating out the invalid jar/bundle/bom tests). But leave as-is for now.
|
addressed comments - tidied |

also tweaks to other classes to make them more usable, eg VersionedName comparables and bundle removal giving an OsgiBundleInstallationResult
test in BundleAndTypeAndSubtypeResourcesTest based on CatalogResourceTest, basically giving parity in terms of functionality and test coverage