Limit the number of candidates for string example 06/113706/2
authorSangwook Ha <sangwook.ha@verizon.com>
Wed, 25 Sep 2024 20:32:10 +0000 (13:32 -0700)
committerSangwook Ha <sangwook.ha@verizon.com>
Wed, 25 Sep 2024 21:38:28 +0000 (14:38 -0700)
The number of candidates tried when a string example is generated
with regex & length constraints may increase exponentially as the
string length increases and this may cause out-of-memory error.

For example, if there are 10 transition paths at each step, then
the number of candidates becomes 1 million after just 6 characters.

Limit the maximum number of transitions allowed at each step to prevent
the exponential growth in search paths. This reduces the chance of
generating a conformant example a little, but prevents excessive use
of memory and CPU cycles.

JIRA: NETCONF-1398
Change-Id: I2d926a438da14395f56f93d1a98a56507c1c7ff2
Signed-off-by: Sangwook Ha <sangwook.ha@verizon.com>
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PropertyEntity.java
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/NC1398Test.java [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1398/document/regex.json [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1398/yang/regex@2024-09-25.yang [new file with mode: 0644]

index 7c2b7edb6e73bcd01aecc1b7fabb157a150e4684..2459c6bf987d2a8770813f32714af276545d69a0 100644 (file)
@@ -93,6 +93,8 @@ public class PropertyEntity {
     private static final Map<String, String> PREDEFINED_CHARACTER_CLASSES = Map.of("\\\\d", "[0-9]",
         "\\\\D", "[^0-9]", "\\\\s", "[ \t\n\f\r]", "\\\\S", "[^ \t\n\f\r]",
         "\\\\w", "[a-zA-Z_0-9]", "\\\\W", "[^a-zA-Z_0-9]");
+    // Maximum number of candidates tried at each step when a string example is generated with constraints
+    private static final int MAX_CANDIDATES_ALLOWED = 100;
 
     private final @NonNull DataSchemaNode node;
     private final @NonNull JsonGenerator generator;
@@ -710,20 +712,29 @@ public class PropertyEntity {
 
     private static String prepareExample(final List<ExampleCandidate> candidates, final int minLength,
             final int maxLength) {
+        if (candidates.isEmpty()) {
+            throw new IllegalArgumentException("No candidates found");
+        }
+
         final var nextCandidates = new ArrayList<ExampleCandidate>();
         for (var candidate : candidates) {
             final var string = candidate.string();
             final var state = candidate.state();
 
             if (string.length() >= minLength && state.isAccept()) {
-                return candidate.string();
+                return string;
             }
 
             if (string.length() < maxLength) {
                 final var transitions = state.getSortedTransitions(false);
-                transitions.forEach(t -> nextCandidates.add(
+                final var toAdd = Math.min(MAX_CANDIDATES_ALLOWED - nextCandidates.size(), transitions.size());
+                transitions.subList(0, toAdd).forEach(t -> nextCandidates.add(
                     new ExampleCandidate(string + t.getMin(), t.getDest())));
             }
+
+            if (nextCandidates.size() >= MAX_CANDIDATES_ALLOWED) {
+                break;
+            }
         }
 
         if (nextCandidates.isEmpty()) {
diff --git a/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/NC1398Test.java b/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/NC1398Test.java
new file mode 100644 (file)
index 0000000..a2d0821
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2024 Verizon and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.openapi.impl;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+class NC1398Test extends AbstractDocumentTest {
+    @BeforeAll
+    static void beforeAll() {
+        initializeClass("/nc1398/yang/");
+    }
+
+    /**
+     * Tests the swagger document generated for the regex@2024-09-25.yang model.
+     */
+    @Test
+    void getDocByModuleTest() throws Exception {
+        final var expectedJson = getExpectedDoc("nc1398/document/regex.json");
+        final var moduleDoc = getDocByModule("regex", "2024-09-25");
+        JSONAssert.assertEquals(expectedJson, moduleDoc, IGNORE_ORDER);
+    }
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1398/document/regex.json b/restconf/restconf-openapi/src/test/resources/nc1398/document/regex.json
new file mode 100644 (file)
index 0000000..ffcc119
--- /dev/null
@@ -0,0 +1,213 @@
+{
+    "openapi": "3.0.3",
+    "info": {
+        "version": "1.0.0",
+        "title": "regex",
+        "description": "We are providing full API for configurational data which can be edited (by POST, PUT, PATCH and DELETE).\nFor operational data we only provide GET API.\n\nFor majority of request you can see only config data in examples. That's because we can show only one example\nper request. The exception when you can see operational data in example is when data are representing\noperational (config false) container with no config data in it."
+    },
+    "servers": [
+        {
+            "url": "http://localhost:8181/"
+        }
+    ],
+    "paths": {
+        "/rests/data": {
+            "post": {
+                "description": "\n\nNote:\nIn example payload, you can see only the first data node child of the resource to be created, following the\nguidelines of RFC 8040, which allows us to create only one resource in POST request.\n",
+                "summary": "POST - Controller - regex - regex",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "cntr": {
+                                        "$ref": "#/components/schemas/regex_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    }
+                },
+                "tags": [
+                    "Controller regex"
+                ],
+                "parameters": []
+            }
+        },
+        "/rests/data/regex:cntr": {
+            "put": {
+                "description": "",
+                "summary": "PUT - regex - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "regex:cntr": {
+                                        "$ref": "#/components/schemas/regex_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex"
+                ],
+                "parameters": []
+            },
+            "patch": {
+                "description": "",
+                "summary": "PATCH - regex - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/yang-data+json": {
+                            "schema": {
+                                "properties": {
+                                    "regex:cntr": {
+                                        "$ref": "#/components/schemas/regex_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/yang-data+xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "200": {
+                        "description": "OK"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex"
+                ],
+                "parameters": []
+            },
+            "delete": {
+                "description": "",
+                "summary": "DELETE - Controller - regex - cntr",
+                "responses": {
+                    "204": {
+                        "description": "Deleted"
+                    }
+                },
+                "tags": [
+                    "Controller regex"
+                ],
+                "parameters": []
+            },
+            "get": {
+                "description": "",
+                "summary": "GET - Controller - regex - cntr",
+                "responses": {
+                    "200": {
+                        "description": "200",
+                        "content": {
+                            "application/xml": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/regex_cntr"
+                                }
+                            },
+                            "application/json": {
+                                "schema": {
+                                    "properties": {
+                                        "cntr": {
+                                            "$ref": "#/components/schemas/regex_cntr",
+                                            "type": "object"
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "tags": [
+                    "Controller regex"
+                ],
+                "parameters": [
+                    {
+                        "name": "content",
+                        "in": "query",
+                        "required": false,
+                        "schema": {
+                            "enum": [
+                                "config",
+                                "nonconfig",
+                                "all"
+                            ],
+                            "type": "string"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "regex_cntr": {
+                "title": "regex_cntr",
+                "type": "object",
+                "properties": {
+                    "lf": {
+                        "description": "",
+                        "type": "string",
+                        "example": "!!!!!!!!!!!!!!!!!!!!",
+                        "minLength": 20,
+                        "maxLength": 100
+                    }
+                },
+                "xml": {
+                    "name": "cntr",
+                    "namespace": "urn:regex"
+                }
+            }
+        },
+        "securitySchemes": {
+            "basicAuth": {
+                "scheme": "basic",
+                "type": "http"
+            }
+        }
+    },
+    "security": [
+        {
+            "basicAuth": []
+        }
+    ]
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1398/yang/regex@2024-09-25.yang b/restconf/restconf-openapi/src/test/resources/nc1398/yang/regex@2024-09-25.yang
new file mode 100644 (file)
index 0000000..b148d01
--- /dev/null
@@ -0,0 +1,16 @@
+module regex {
+    namespace "urn:regex";
+    prefix "re";
+    revision 2024-09-25 {
+        description "Initial revision.";
+    }
+
+    container cntr {
+        leaf lf {
+            type string {
+                length "20..100";
+                pattern "[a-zA-Z0-9!$%^()\[\]_\-~{}.+]*";
+            }
+        }
+    }
+}