[NETCONF-938] Cannot generate API docs for Junos device Created: 05/Jan/23 Updated: 01/Feb/24 |
|
| Status: | In Review |
| Project: | netconf |
| Component/s: | restconf-openapi |
| Affects Version/s: | None |
| Fix Version/s: | 7.0.0 |
| Type: | Bug | Priority: | Highest |
| Reporter: | Ivan Hrasko | Assignee: | Ivan Hrasko |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | pt | ||
| Σ Remaining Estimate: | Not Specified | Remaining Estimate: | Not Specified |
| Σ Time Spent: | Not Specified | Time Spent: | Not Specified |
| Σ Original Estimate: | Not Specified | Original Estimate: | Not Specified |
| Attachments: |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Issue Links: |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Sub-Tasks: |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Description |
|
When we have device that contains Junos [common models|https://github.com/Juniper/yang/tree/master/22.3/22.3R1/common] and [junos family models|https://github.com/Juniper/yang/tree/master/22.3/22.3R1/junos] its not possible to access [its mount point documentation|http://localhost:8181/apidoc/explorer/index.html?urls.primaryName=17830-sim-device%20resources%20-%20RestConf%20RFC%208040]. The docs are never loaded and we can see that Java VM is out of heap space. After some time the running ODL instance is killed. |
| Comments |
| Comment by Peter Suna [ 15/Mar/23 ] |
|
The DefinitionGenerator class is encountering a heap space issue while processing the "junos-conf-root" model. The problem lies in the fact that other models create approximately 62 augmentations to the configuration container within the "junos-conf-root" model. This results in the creation of a large number of JSON body examples, around 1 million, which contain a total of 10 million JSON elements. The Swagger API docs process each model and append all examples from every model to an object called "definition" inside SwaggerObject. This involves appending 10 million elements, which can take up all available memory. It is likely that creating 1 million examples for augmentations is not correct and needs to be addressed to fix the issue. Code in master branch after creating configuration containers. I have replaced ObjectNodes with DefinitionObjects and results in numbers for this model: |
| Comment by Ivan Hrasko [ 14/Jun/23 ] |
|
We assume that the high memory consumption is caused by:
|
| Comment by Robert Varga [ 27/Jul/23 ] |
|
Right, but at the end of the day the core problem is how JSON serialization is implemented. What we have is:
@Override
public synchronized Response getAllModulesDoc(final UriInfo uriInfo) {
final DefinitionNames definitionNames = new DefinitionNames();
final OpenApiObject doc = openApiGeneratorRFC8040.getAllModulesDoc(uriInfo, definitionNames);
return Response.ok(doc).build();
}
which then routes to JaxbContextResolver, which then uses ObjectMapper to turn OpenApiObject into JSON (and uses reflection to determine how to do that). The problem is the fact OpenApiObject exists. It is the moral equivalent of doing Stream.collect() before doing further processing of the stream contents. You have to realize that on input there is EffectiveModelContext and on output there are bytes (which happen to represent a JSON a document). Response.ok(doc) does this:
public static ResponseBuilder ok(Object entity) {
ResponseBuilder b = ok();
b.entity(entity);
return b;
}
and ResponseBuilder.entity() explicitly says: Note that the entity can be also set as an {@link java.io.InputStream input stream}.
So we really want to integrate at this level – e.g. provide an InputStream whose bytes are the JSON representation of what OpenApiObject models. At the end of the day, BaseYangOpenApiGenerator, needs to return an OpenApiInputStream from getFilledDoc() – the constructor of which just captures stuff from UriInfo and the EffectiveModelContext. As for the implementation of said OpenApiInputStream, think of it as a more complicated java.util.Iterator. Essentially it iterates over EffectiveModelContext (maybe multiple times) and each time it gets a piece of the JSON document, which it caches internally and gives out its bytes on InputStream.read(). Whenever it runs out of bytes to give out, it moves to the next bit of EffectiveModelContext, turns it into a JSON fragment, and hands that out – until it runs out of EffectiveModelContext, at which point it reports -1 to indicate it is all done. I think the core interface here ends up being https://www.javadoc.io/static/com.fasterxml.jackson.core/jackson-core/2.15.2/com/fasterxml/jackson/core/JsonGenerator.html, where the generator is pointed to something that holds bytes for OpenApiInputStream to consume and the EffectiveModelContext-processing logic is provided with the JsonGenerator to use methods writeStartObject() and similar. This obviously needs further investigation, but the goal is clear I think. We already have stuff working on this general principle: see how https://github.com/opendaylight/yangtools/blob/master/model/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/DeclaredStatementFormatter.java works once it is configured. toYangTextSnippet() takes either a module, or a submodule and returns an Iterable – and that in turn defers to https://github.com/opendaylight/yangtools/blob/master/model/yang-model-export/src/main/java/org/opendaylight/yangtools/yang/model/export/YangTextSnippetIterator.java – which converts a DeclaredStatement (and its subtree) to a sequence of Strings. We want to do a similar thing, except:
Perhaps a critical thing to understand: OpenApiInputStream here is consumed by framework is a similar fashion that YangTextSnippet users consume it. InputStream should be viewed as Interator<Integer>, where 'int read()' combines the function of hasNext() and next() (via the return of -1). Plus, obviously, it provides methods to consume bytes in bulk. At the end of the day, there must be no ObjectMapper or com.fasterxml.jackson.annotation in sight. Also, to drive this point home even more, think of EffectiveModelContext as a source of events (like "we have a module", "we have an RPC", "we have an RPC input", "we have a leaf", "we ended the RPC input", "we have ended the RPC", "we have ended the module") and we are turning them into JSON events (like beginObject(), endObject(), etc.) and then turning them into bytes. This is stream processing at its purest. |
| Comment by Robert Varga [ 31/Jul/23 ] |
|
Assuming JAX-RS, we can eliminate some of the complexity by shifing computation to a MessageBodyWriter, when we have the OutputStream available. https://git.opendaylight.org/gerrit/c/netconf/+/107149 provides the basic things, but it highlights we really should think about how the model is actually laid out (centered around OpenApiEntity and its specializations). |
| Comment by Ivan Hrasko [ 17/Jan/24 ] |
|
With https://git.opendaylight.org/gerrit/c/netconf/+/109803 we can generate and download OpenAPI docs for junos device within 30 seconds with: curl -o /home/odl/Desktop/openapi.json --location 'http://localhost:8181/openapi/api/v3/mounts/1' --header 'Authorization: Basic YWRtaW46YWRtaW4=' |
| Comment by Ivan Hrasko [ 17/Jan/24 ] |
|
Anyway the whole junos model (1.3 GiB) is not parsable by browsers. We wil try to handle this in NETCONF-1225. |