diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 032033ad5e1..5d391b003fa 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,14 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima [[release-4-0-0]] === TinkerPop 4.0.0 (Release Date: NOT OFFICIALLY RELEASED YET) +* Added declarative `traversalSources` configuration to Gremlin Server YAML for creating `TraversalSource` instances with optional strategy configuration queries. +* Added Java-based `lifecycleHooks` configuration to Gremlin Server YAML, replacing Groovy init script `LifeCycleHook` creation. +* Added `TinkerFactoryDataLoader` `LifeCycleHook` implementation for loading sample datasets without Groovy. +* Added auto-creation of `TraversalSource` bindings from `graphs` configuration (`graph` maps to `g`, others to `g_`). +* Added `GraphManager` to `LifeCycleHook.Context` for Java-based hooks to access configured graphs. +* Deprecated Groovy-based `LifeCycleHook` and `TraversalSource` creation via init scripts in favor of YAML configuration. +* Updated all default Gremlin Server configs to remove Groovy dependency from initialization. + [[release-4-0-0-beta-2]] === TinkerPop 4.0.0-beta.2 (April 1, 2026) diff --git a/docker/gremlin-server/gremlin-server-integration-krb5.yaml b/docker/gremlin-server/gremlin-server-integration-krb5.yaml index 6dfd94dfc50..974c94a8cc5 100644 --- a/docker/gremlin-server/gremlin-server-integration-krb5.yaml +++ b/docker/gremlin-server/gremlin-server-integration-krb5.yaml @@ -27,14 +27,21 @@ graphs: { sink: conf/tinkergraph-empty.properties, tx: conf/tinkertransactiongraph-empty.properties } -scriptEngines: { - gremlin-lang : {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {expectedCompilationTime: 30000}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-all.groovy]}}}} +traversalSources: { + gclassic: {graph: classic}, + gmodern: {graph: modern}, + g: {graph: graph}, + gcrew: {graph: crew}, + ggraph: {graph: graph}, + ggrateful: {graph: grateful}, + gsink: {graph: sink}, + gtx: {graph: tx}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: classic, dataset: classic}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: modern, dataset: modern}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: crew, dataset: crew}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: grateful, dataset: grateful}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: sink, dataset: sink}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV2] }} diff --git a/docker/gremlin-server/gremlin-server-integration-secure.yaml b/docker/gremlin-server/gremlin-server-integration-secure.yaml index 6528551cb9b..9dfa96903fb 100644 --- a/docker/gremlin-server/gremlin-server-integration-secure.yaml +++ b/docker/gremlin-server/gremlin-server-integration-secure.yaml @@ -27,14 +27,21 @@ graphs: { sink: conf/tinkergraph-empty.properties, tx: conf/tinkertransactiongraph-empty.properties } -scriptEngines: { - gremlin-lang : {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {expectedCompilationTime: 30000}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-all.groovy]}}}} +traversalSources: { + gclassic: {graph: classic}, + gmodern: {graph: modern}, + g: {graph: graph}, + gcrew: {graph: crew}, + ggraph: {graph: graph}, + ggrateful: {graph: grateful}, + gsink: {graph: sink}, + gtx: {graph: tx}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: classic, dataset: classic}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: modern, dataset: modern}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: crew, dataset: crew}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: grateful, dataset: grateful}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: sink, dataset: sink}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 } # application/vnd.graphbinary-v1.0 diff --git a/docker/gremlin-server/gremlin-server-integration.yaml b/docker/gremlin-server/gremlin-server-integration.yaml index 8f5d2f74f83..dcaff57e8b1 100644 --- a/docker/gremlin-server/gremlin-server-integration.yaml +++ b/docker/gremlin-server/gremlin-server-integration.yaml @@ -29,14 +29,22 @@ graphs: { sink: conf/tinkergraph-service.properties, tx: conf/tinkertransactiongraph-service.properties } -scriptEngines: { - gremlin-lang : {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {expectedCompilationTime: 30000}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-all.groovy]}}}} +traversalSources: { + gclassic: {graph: classic}, + gmodern: {graph: modern}, + g: {graph: graph}, + gcrew: {graph: crew}, + ggraph: {graph: graph}, + ggrateful: {graph: grateful}, + gsink: {graph: sink}, + gtx: {graph: tx}, + gimmutable: {graph: immutable}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: classic, dataset: classic}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: modern, dataset: modern}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: crew, dataset: crew}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: grateful, dataset: grateful}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: sink, dataset: sink}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 } # application/vnd.graphbinary-v1.0 diff --git a/docs/src/reference/gremlin-applications.asciidoc b/docs/src/reference/gremlin-applications.asciidoc index 8039afff907..573a7c41999 100644 --- a/docs/src/reference/gremlin-applications.asciidoc +++ b/docs/src/reference/gremlin-applications.asciidoc @@ -444,22 +444,22 @@ $ bin/gremlin-server.sh conf/gremlin-server-modern.yaml (o o) -----oOOo-(4)-oOOo----- -[INFO] GremlinServer - Configuring Gremlin Server from conf/gremlin-server-modern.yaml -[INFO] MetricManager - Configured Metrics Slf4jReporter configured with interval=180000ms and loggerName=org.apache.tinkerpop.gremlin.server.Settings$Slf4jReporterMetrics -[INFO] DefaultGraphManager - Graph [graph] was successfully configured via [conf/tinkergraph-empty.properties]. -[INFO] ServerGremlinExecutor - Initialized Gremlin thread pool. Threads in pool named with pattern gremlin-* -[INFO] ServerGremlinExecutor - Initialized GremlinExecutor and preparing GremlinScriptEngines instances. -[INFO] ServerGremlinExecutor - Initialized gremlin-groovy GremlinScriptEngine and registered metrics -[INFO] ServerGremlinExecutor - A GraphTraversalSource is now bound to [g] with graphtraversalsource[tinkergraph[vertices:0 edges:0], standard] -[INFO] GremlinServer - Executing start up LifeCycleHook -[INFO] Logger$info - Loading 'modern' graph data. -[INFO] GremlinServer - idleConnectionTimeout was set to 0 which resolves to 0 seconds when configuring this value - this feature will be disabled -[INFO] GremlinServer - keepAliveInterval was set to 0 which resolves to 0 seconds when configuring this value - this feature will be disabled -[INFO] AbstractChannelizer - Configured application/vnd.gremlin-v4.0+json with org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4 -[INFO] AbstractChannelizer - Configured application/json with org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4 -[INFO] AbstractChannelizer - Configured application/vnd.graphbinary-v4.0 with org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 -[INFO] GremlinServer$1 - Gremlin Server configured with worker thread pool of 1, gremlin pool of 4 and boss thread pool of 1. -[INFO] GremlinServer$1 - Channel started at port 8182. +[INFO] o.a.t.g.s.GremlinServer - Configuring Gremlin Server from conf/gremlin-server-modern.yaml +[INFO] o.a.t.g.s.u.MetricManager - Configured Metrics Slf4jReporter configured with interval=180000ms and loggerName=org.apache.tinkerpop.gremlin.server.Settings$Slf4jReporterMetrics +[INFO] o.a.t.g.s.u.DefaultGraphManager - Graph [graph] was successfully configured via [conf/tinkergraph-empty.properties]. +[INFO] o.a.t.g.s.u.ServerGremlinExecutor - Initialized Gremlin thread pool. Threads in pool named with pattern gremlin-* +[INFO] o.a.t.g.s.u.ServerGremlinExecutor - Initialized GremlinExecutor and preparing GremlinScriptEngines instances. +[INFO] o.a.t.g.s.u.ServerGremlinExecutor - Initialized gremlin-lang GremlinScriptEngine and registered metrics +[INFO] o.a.t.g.s.u.ServerGremlinExecutor - A GraphTraversalSource is now auto-bound to [g] for graph [graph] +[INFO] o.a.t.g.s.u.ServerGremlinExecutor - Instantiated LifeCycleHook: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader +[INFO] o.a.t.g.s.h.TransactionManager - TransactionManager initialized with timeout=600000ms, maxTransactions=1000 +[INFO] o.a.t.g.s.GremlinServer - Executing start up LifeCycleHook +[INFO] o.a.t.g.s.u.TinkerFactoryDataLoader - TinkerFactoryDataLoader loaded [modern] dataset into graph [graph] +[INFO] o.a.t.g.s.AbstractChannelizer - Configured application/vnd.gremlin-v4.0+json with org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4 +[INFO] o.a.t.g.s.AbstractChannelizer - Configured application/json with org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4 +[INFO] o.a.t.g.s.AbstractChannelizer - Configured application/vnd.graphbinary-v4.0 with org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 +[INFO] o.a.t.g.s.GremlinServer - Gremlin Server configured with worker thread pool of 1, gremlin pool of 10 and boss thread pool of 1. +[INFO] o.a.t.g.s.GremlinServer - Channel started at port 8182. ---- Gremlin Server is configured by the provided link:http://www.yaml.org/[YAML] file `conf/gremlin-server-modern.yaml`. @@ -469,31 +469,83 @@ That file tells Gremlin Server many things such as: * Thread pool sizes * Where to report metrics gathered by the server * The serializers to make available -* The Gremlin `ScriptEngine` instances to expose and external dependencies to inject into them * `Graph` instances to expose +* `TraversalSource` bindings (auto-created or explicitly declared) +* `LifeCycleHook` implementations for startup/shutdown logic The log messages that printed above show a number of things, but most importantly, there is a `Graph` instance named `graph` that is exposed in Gremlin Server. This graph is an in-memory TinkerGraph and was empty at the start of the -server. An initialization script at `scripts/generate-modern.groovy` was executed during startup. Its contents are -as follows: +server. A `TinkerFactoryDataLoader` lifecycle hook loaded the "modern" dataset into it during startup, and a +`TraversalSource` named `g` was auto-created from the `graph` entry. -[source,groovy] +[[server-auto-traversal-sources]] +==== Auto-Created TraversalSources + +When Gremlin Server starts, it automatically creates a `TraversalSource` for each graph in the `graphs` configuration +that does not have an explicit entry in the `traversalSources` section. The naming convention is: + +* A graph named `graph` gets a `TraversalSource` named `g` +* All other graphs get `g_` (e.g. a graph named `modern` gets `g_modern`) + +This means a minimal configuration like the following is fully functional: + +[source,yaml] +---- +graphs: { + graph: conf/tinkergraph-empty.properties} +---- + +[[server-traversal-sources]] +==== Declarative TraversalSources + +For more control, the `traversalSources` YAML section allows explicit `TraversalSource` creation with optional +strategy configuration via a Gremlin query: + +[source,yaml] +---- +traversalSources: { + g: {graph: graph}, + gReadOnly: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)", language: "gremlin-lang"}} +---- + +Each entry supports: + +* `graph` (required) — references a key in the `graphs` section +* `query` (optional) — a Gremlin expression evaluated with a base traversal source bound as `g`; the result becomes + the final `TraversalSource` +* `language` (optional) — which script engine to use for query evaluation; defaults to `gremlin-lang`, or the sole + configured engine if only one is present + +Graphs with explicit `traversalSources` entries are excluded from auto-creation. + +[[server-lifecycle-hooks]] +==== LifeCycleHooks + +The `lifecycleHooks` YAML section configures Java-based `LifeCycleHook` implementations that execute during server +startup and shutdown: + +[source,yaml] ---- -include::{basedir}/gremlin-server/scripts/generate-modern.groovy[] +lifecycleHooks: + - className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader + config: {graph: graph, dataset: modern} ---- -The script above initializes a `Map` and assigns two key/values to it. The first, assigned to "hook", defines a -`LifeCycleHook` for Gremlin Server. The "hook" provides a way to tie script code into the Gremlin Server startup and -shutdown sequences. The `LifeCycleHook` has two methods that can be implemented: `onStartUp` and `onShutDown`. -These events are called once at Gremlin Server start and once at Gremlin Server stop. This is an important point -because code outside of the "hook" is executed for each `ScriptEngine` creation (multiple may be created when -"sessions" are enabled) and therefore the `LifeCycleHook` provides a way to ensure that a script is only executed a -single time. In this case, the startup hook loads the "modern" graph into the empty TinkerGraph instance, preparing -it for use. The second key/value pair assigned to the `Map`, named "g", defines a `TraversalSource` from the `Graph` -bound to the "graph" variable in the YAML configuration file. This variable `g`, as well as any other variable -assigned to the `Map`, will be made available as variables for future remote script executions. In more general -terms, any key/value pairs assigned to a `Map` returned from the initialization script will become variables that -are global to all requests. In addition, any functions that are defined will be cached for future use. +Each entry specifies: + +* `className` (required) — the fully qualified class name of a `LifeCycleHook` implementation +* `config` (optional) — a `Map` of key/value pairs passed to the hook's `init(Map)` method + +The built-in `TinkerFactoryDataLoader` loads TinkerFactory sample datasets into a graph. It accepts two config +parameters: `graph` (the name of the graph as defined in `graphs`) and `dataset` (one of `modern`, `classic`, `crew`, +`grateful`, `sink`, or `airroutes`). + +Custom `LifeCycleHook` implementations must have a no-arg constructor and implement the `onStartUp(Context)` and +`onShutDown(Context)` methods. The `Context` provides access to a `Logger` and the `GraphManager`. + +IMPORTANT: Creating `LifeCycleHook` and `TraversalSource` instances via Groovy init scripts is deprecated. Use the +`lifecycleHooks` and `traversalSources` YAML sections instead. Existing Groovy scripts continue to work but will +produce a deprecation warning at startup. WARNING: Transactions on graphs in initialization scripts are not closed automatically after the script finishes executing. It is up to the script to properly commit or rollback transactions in the script itself. @@ -842,8 +894,15 @@ The following table describes the various YAML configuration options that Gremli |scriptEngines |A `Map` of `ScriptEngine` implementations to expose through Gremlin Server, where the key is the name given by the `ScriptEngine` implementation. The key must match the name exactly for the `ScriptEngine` to be constructed. The value paired with this key is itself a `Map` of configuration for that `ScriptEngine`. If this value is not set, it will default to "gremlin-lang". |_gremlin-lang_ |scriptEngines..imports |A comma separated list of classes/packages to make available to the `ScriptEngine`. |_none_ |scriptEngines..staticImports |A comma separated list of "static" imports to make available to the `ScriptEngine`. |_none_ -|scriptEngines..scripts |A comma separated list of script files to execute on `ScriptEngine` initialization. `Graph` and `TraversalSource` instance references produced from scripts will be stored globally in Gremlin Server, therefore it is possible to use initialization scripts to add Traversal Strategies or create entirely new `Graph` instances all together. Instantiating a `LifeCycleHook` in a script provides a way to execute scripts when Gremlin Server starts and stops.|_none_ +|scriptEngines..scripts |A comma separated list of script files to execute on `ScriptEngine` initialization. Deprecated — use `traversalSources` and `lifecycleHooks` instead.|_none_ |scriptEngines..config |A `Map` of configuration settings for the `ScriptEngine`. These settings are dependent on the `ScriptEngine` implementation being used. |_none_ +|traversalSources |A `Map` of `TraversalSource` configurations keyed by binding name. Each entry specifies a `graph` reference and optionally a `query` to configure strategies. See <>. |_none (auto-created from graphs)_ +|traversalSources..graph |The name of the graph (as defined in `graphs`) to create the traversal source from. |_none_ +|traversalSources..query |An optional Gremlin query evaluated with a base traversal source bound as `g`. The result becomes the final `TraversalSource`. |_none_ +|traversalSources..language |The script engine language to use for evaluating `query`. Falls back to the sole configured engine if only one is present, or `gremlin-lang` otherwise. |_auto-detected_ +|lifecycleHooks |A `List` of Java-based `LifeCycleHook` implementations to instantiate and execute during server startup and shutdown. See <>. |_none_ +|lifecycleHooks[X].className |The fully qualified class name of the `LifeCycleHook` implementation. |_none_ +|lifecycleHooks[X].config |A `Map` of configuration passed to the hook's `init(Map)` method. |_none_ |evaluationTimeout |The amount of time in milliseconds before a request evaluation and iteration of result times out. This feature can be turned off by setting the value to `0`. |30000 |serializers |A `List` of `Map` settings, where each `Map` represents a `MessageSerializer` implementation to use along with its configuration. If this value is not set, then Gremlin Server will configure with GraphSON and GraphBinary but will not register any `ioRegistries` for configured graphs. |_empty_ |serializers[X].className |The full class name of the `MessageSerializer` implementation. |_none_ @@ -1278,14 +1337,20 @@ consult the documentation of the graph you are using to determine what authoriza Gremlin Server supports three mechanisms to configure authorization: -. With the `ScriptFileGremlinPlugin` a groovy script is configured that instantiates the `GraphTraversalSources` that -can be accessed by client requests. Using the `withStrategies()` gremlin -link:https://tinkerpop.apache.org/docs/x.y.z/reference/#start-steps[start step], one can apply so-called -link:https://tinkerpop.apache.org/docs/x.y.z/reference/#traversalstrategy[TraversalStrategy instances] to these -`GraphTraversalSource` instances, some of which can serve for authorization purposes (`ReadOnlyStrategy`, +. With the `traversalSources` YAML configuration, one can declare `TraversalSource` instances with +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#traversalstrategy[TraversalStrategy instances] applied via +a Gremlin query, some of which can serve for authorization purposes (`ReadOnlyStrategy`, `LambdaRestrictionStrategy`, `VertexProgramRestrictionStrategy`, `SubgraphStrategy`, `PartitionStrategy`, `EdgeLabelVerificationStrategy`), provided that users are not allowed to remove or modify these `TraversalStrategy` -instances afterwards. The `ScriptFileGremlinPlugin` is found in the yaml configuration file for Gremlin Server: +instances afterwards. For example: ++ +[source,yaml] +---- +traversalSources: { + g: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)"}} +---- ++ +Alternatively, the deprecated `ScriptFileGremlinPlugin` approach can still be used with a Groovy script: + [source,yaml] ---- @@ -1314,12 +1379,13 @@ gives an overview. [width="95%",cols="5,2,2,4",options="header"] |========================================================= |Type (mechanism) |GraphTraversalSources |Groups |Bytecode analysis -|Implicit (init script) | all accessible |one |`withStrategies()` +|Implicit (YAML config or init script) | all accessible |one |`withStrategies()` |Passive (pass/deny) | selected access |few |hybrid |Active (inject) |selected access |many |hybrid |========================================================= -With implicit authorization (only adding restricting `TraversalStrategy` instances in the initialization script of +With implicit authorization (configuring restricting `TraversalStrategy` instances via the `traversalSources` YAML +section or in a Groovy initialization script of Gremlin Server) all authenticated users can access all hosted `GraphTraversalSources` and all face the same restrictions. One would need separate Gremlin Server instances for each authorization policy and apply an authenticator that restricts access to a group of users (that is, supports in authorization). @@ -1330,7 +1396,8 @@ the most flexible and can support an almost unlimited number of authorization po implement. In particular, applying the `SubgraphStrategy` requires knowledge about the schema of the graph. The passive authorization solution perhaps provides a middle ground to start implementing authorization. This -solution assumes that the `SubgraphStrategy` is applied in the Gremlin Server initialization script, because compliance +solution assumes that the `SubgraphStrategy` is applied in the Gremlin Server `traversalSources` configuration (or +initialization script), because compliance with a subgraph restriction can only be determined during the actual execution of the gremlin traversal. Note that the same graph can be reused with different `SubgraphStrategies`. Now, authorization policies can be defined in terms of accessible `GraphTraversalSources` and the authorizer can simply match the requested access to a `GraphTraversalSource` @@ -1589,6 +1656,11 @@ groupsandbox: [usersandbox, marko] [[script-execution]] ==== Protecting Script Execution +NOTE: As of 4.0.0, Groovy is no longer required for server initialization. The `scriptEngines` configuration +described in this section is only needed when Groovy script execution is explicitly enabled for remote script +submission. See <> and <> for the preferred YAML-based +initialization approach. + It is important to remember that Gremlin Server exposes `GremlinScriptEngine` instances that allows for remote execution of arbitrary code on the server. Obviously, this situation can represent a security risk or, more minimally, provide ways for "bad" scripts to be inadvertently executed. A simple example of a "valid" Gremlin script that would cause @@ -1982,7 +2054,15 @@ Another option, `all`, can be used to indicate that all properties should be ret In some cases it can be inconvenient to load Elements with properties due to large data size or for compatibility reasons. That can be solved by utilizing `ReferenceElementStrategy` when creating the out-of-the-box `GraphTraversalSource`. As the name suggests, this means that elements will be detached by reference and will therefore not have properties -included. The relevant configuration from the Gremlin Server initialization script looks like this: +included. The relevant configuration from the Gremlin Server YAML looks like this: + +[source,yaml] +---- +traversalSources: { + g: {graph: graph, query: "g.withStrategies(ReferenceElementStrategy)"}} +---- + +Alternatively, using the deprecated Groovy init script approach: [source,groovy] ---- @@ -2233,7 +2313,7 @@ NOTE: This plugin is typically only useful to the Gremlin Console and is enabled The Server Plugin for remoting with the Gremlin Console should not be confused with a plugin of similar name that is used by the server. `GremlinServerGremlinPlugin` is typically only configured in Gremlin Server and provides a number -of imports that are required for writing <>. +of imports that are required for writing Groovy initialization scripts (if Groovy is enabled). [[spark-plugin]] === Spark Plugin diff --git a/docs/src/upgrade/release-4.x.x.asciidoc b/docs/src/upgrade/release-4.x.x.asciidoc index ebc2bbb1cc5..9decb6ded97 100644 --- a/docs/src/upgrade/release-4.x.x.asciidoc +++ b/docs/src/upgrade/release-4.x.x.asciidoc @@ -30,6 +30,95 @@ image::gremlins-wildest-dreams.png[width=185] Please see the link:https://github.com/apache/tinkerpop/blob/4.0.0/CHANGELOG.asciidoc#release-4-0-0[changelog] for a complete list of all the modifications that are part of this release. +=== Upgrading for Users + +==== Gremlin Server Initialization Without Groovy + +Gremlin Server no longer requires Groovy for server initialization. Three new YAML configuration mechanisms replace +the Groovy init scripts that were previously used to create `TraversalSource` bindings, load data, and run lifecycle +hooks. + +===== Auto-Created TraversalSources + +Any graph defined in the `graphs` section that does not have an explicit `traversalSources` entry will automatically +get a `TraversalSource` binding. A graph named `graph` is bound to `g`; all others are bound to `g_`. + +A minimal server configuration is now: + +[source,yaml] +---- +graphs: { + graph: conf/tinkergraph-empty.properties} +---- + +This automatically creates a `g` binding — no init script needed. + +===== Declarative `traversalSources` + +A new `traversalSources` YAML section allows explicit `TraversalSource` creation with optional strategy configuration: + +[source,yaml] +---- +traversalSources: { + g: {graph: graph}, + gReadOnly: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)", language: "gremlin-lang"}} +---- + +Each entry specifies: + +- `graph` (required) — references a key in the `graphs` section +- `query` (optional) — a Gremlin expression evaluated with a base traversal source bound as `g` +- `language` (optional) — which script engine to use for the query (defaults to `gremlin-lang`, or the sole + configured engine if only one is present) + +===== Java-Based `lifecycleHooks` + +A new `lifecycleHooks` YAML section replaces Groovy-based `LifeCycleHook` creation: + +[source,yaml] +---- +lifecycleHooks: + - className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader + config: {graph: graph, dataset: modern} +---- + +Each entry specifies a `className` implementing `LifeCycleHook` and an optional `config` map passed to the hook's +`init()` method. The built-in `TinkerFactoryDataLoader` supports datasets: `modern`, `classic`, `crew`, `grateful`, +`sink`, and `airroutes`. + +===== Deprecation of Groovy Init Scripts + +Creating `TraversalSource` and `LifeCycleHook` instances via Groovy init scripts is now deprecated. Existing scripts +continue to work, but a deprecation warning is logged at startup. Migrate to the YAML-based configuration described +above. + +===== Migration Examples + +*Before (Groovy init script):* + +[source,groovy] +---- +def globals = [:] +globals << [hook : [ + onStartUp: { ctx -> + org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory.generateModern(graph) + } +] as LifeCycleHook] +globals << [g : traversal().withEmbedded(graph)] +---- + +*After (YAML only):* + +[source,yaml] +---- +graphs: { + graph: conf/tinkergraph-empty.properties} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}} +---- + +The `g` binding is auto-created from the `graph` entry. No `scriptEngines` section is needed. + == TinkerPop 4.0.0-beta.2 *Release Date: April 1, 2026* diff --git a/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/AbstractGremlinServerIntegrationTest.java b/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/AbstractGremlinServerIntegrationTest.java index f9fc1465516..393373b89ab 100644 --- a/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/AbstractGremlinServerIntegrationTest.java +++ b/gremlin-console/src/test/java/org/apache/tinkerpop/gremlin/console/jsr223/AbstractGremlinServerIntegrationTest.java @@ -25,7 +25,6 @@ import java.io.InputStream; import java.nio.file.Paths; -import java.util.Collections; /** * Starts and stops an instance for each executed test. @@ -51,10 +50,6 @@ public void setUp() throws Exception { final Settings overridenSettings = overrideSettings(settings); final String prop = Paths.get(AbstractGremlinServerIntegrationTest.class.getResource("tinkergraph-empty.properties").toURI()).toString(); overridenSettings.graphs.put("graph", prop); - final String script = Paths.get(AbstractGremlinServerIntegrationTest.class.getResource("generate.groovy").toURI()).toString(); - overridenSettings.scriptEngines.get("gremlin-groovy").plugins - .get("org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin") - .put("files", Collections.singletonList(script)); this.server = new GremlinServer(overridenSettings); diff --git a/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/gremlin-server-integration.yaml b/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/gremlin-server-integration.yaml index 4bb6dcf7549..cb16c1fa8fa 100644 --- a/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/gremlin-server-integration.yaml +++ b/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/gremlin-server-integration.yaml @@ -19,12 +19,10 @@ host: localhost port: 45940 evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer -scriptEngines: { - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {}}}} +graphs: { + graph: conf/tinkergraph-empty.properties} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} # application/vnd.graphbinary-v4.0 metrics: { diff --git a/gremlin-server/conf/gremlin-server-airroutes.yaml b/gremlin-server/conf/gremlin-server-airroutes.yaml new file mode 100644 index 00000000000..c4c4529f8fd --- /dev/null +++ b/gremlin-server/conf/gremlin-server-airroutes.yaml @@ -0,0 +1,43 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +host: localhost +port: 8182 +evaluationTimeout: 30000 +channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer +graphs: { + graph: conf/tinkergraph-empty.properties} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: airroutes}} +serializers: + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} # application/vnd.graphbinary-v4.0 +metrics: { + slf4jReporter: {enabled: true, interval: 180000}} +strictTransactionManagement: false +idleConnectionTimeout: 0 +keepAliveInterval: 0 +maxInitialLineLength: 4096 +maxHeaderSize: 8192 +maxChunkSize: 8192 +maxRequestContentLength: 10485760 +maxAccumulationBufferComponents: 1024 +resultIterationBatchSize: 64 +writeBufferLowWaterMark: 32768 +writeBufferHighWaterMark: 65536 +ssl: { + enabled: false} diff --git a/gremlin-server/conf/gremlin-server-classic.yaml b/gremlin-server/conf/gremlin-server-classic.yaml index 37b51f53808..b421c3f2763 100644 --- a/gremlin-server/conf/gremlin-server-classic.yaml +++ b/gremlin-server/conf/gremlin-server-classic.yaml @@ -20,13 +20,8 @@ port: 8182 evaluationTimeout: 30000 graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-classic.groovy]}}}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: classic}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 } # application/vnd.graphbinary-v1.0 diff --git a/gremlin-server/conf/gremlin-server-modern-readonly.yaml b/gremlin-server/conf/gremlin-server-modern-readonly.yaml index 3c6ad637b74..e0c87103eb1 100644 --- a/gremlin-server/conf/gremlin-server-modern-readonly.yaml +++ b/gremlin-server/conf/gremlin-server-modern-readonly.yaml @@ -20,13 +20,10 @@ port: 8182 evaluationTimeout: 30000 graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-modern-readonly.groovy]}}}} +traversalSources: { + g: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)"}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 } # application/vnd.graphbinary-v1.0 diff --git a/gremlin-server/conf/gremlin-server-modern.yaml b/gremlin-server/conf/gremlin-server-modern.yaml index 4551023d804..4a1fe9803ec 100644 --- a/gremlin-server/conf/gremlin-server-modern.yaml +++ b/gremlin-server/conf/gremlin-server-modern.yaml @@ -21,14 +21,8 @@ evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {enableThreadInterrupt: true}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-modern.groovy]}}}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4} # application/vnd.graphbinary-v4.0 diff --git a/gremlin-server/conf/gremlin-server-rest-modern.yaml b/gremlin-server/conf/gremlin-server-rest-modern.yaml index 905a9ca002b..3b66b58177d 100644 --- a/gremlin-server/conf/gremlin-server-rest-modern.yaml +++ b/gremlin-server/conf/gremlin-server-rest-modern.yaml @@ -21,13 +21,8 @@ evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-modern.groovy]}}}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/vnd.gremlin-v3.0+json;types=false - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/vnd.gremlin-v3.0+json diff --git a/gremlin-server/conf/gremlin-server-rest-secure.yaml b/gremlin-server/conf/gremlin-server-rest-secure.yaml index 8a62795351f..642033b31d1 100644 --- a/gremlin-server/conf/gremlin-server-rest-secure.yaml +++ b/gremlin-server/conf/gremlin-server-rest-secure.yaml @@ -29,14 +29,6 @@ evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {enableThreadInterrupt: true, timedInterrupt: 10000, compilation: COMPILE_STATIC, extensions: org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.SimpleSandboxExtension}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample-secure.groovy]}}}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json metrics: { diff --git a/gremlin-server/conf/gremlin-server-secure.yaml b/gremlin-server/conf/gremlin-server-secure.yaml index ddc57c0d85a..644b275f702 100644 --- a/gremlin-server/conf/gremlin-server-secure.yaml +++ b/gremlin-server/conf/gremlin-server-secure.yaml @@ -29,14 +29,6 @@ evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {enableThreadInterrupt: true, timedInterrupt: 10000, compilation: COMPILE_STATIC, extensions: org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.SimpleSandboxExtension}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample-secure.groovy]}}}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 } # application/vnd.graphbinary-v1.0 diff --git a/gremlin-server/conf/gremlin-server-transaction.yaml b/gremlin-server/conf/gremlin-server-transaction.yaml index e4cc804ac23..6b61f40a255 100644 --- a/gremlin-server/conf/gremlin-server-transaction.yaml +++ b/gremlin-server/conf/gremlin-server-transaction.yaml @@ -21,13 +21,6 @@ evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer graphs: { graph: conf/tinkertransactiongraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1 } # application/vnd.graphbinary-v1.0 diff --git a/gremlin-server/conf/gremlin-server.yaml b/gremlin-server/conf/gremlin-server.yaml index 286f7f18ea8..163d807849b 100644 --- a/gremlin-server/conf/gremlin-server.yaml +++ b/gremlin-server/conf/gremlin-server.yaml @@ -21,13 +21,6 @@ evaluationTimeout: 30000 channelizer: org.apache.tinkerpop.gremlin.server.channel.HttpChannelizer graphs: { graph: conf/tinkergraph-empty.properties} -scriptEngines: { - gremlin-lang: {}, - gremlin-groovy: { - plugins: { org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, - org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} # application/json - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4 } # application/vnd.graphbinary-v1.0 diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java index 16d8a2d045d..a0b68e5e3ab 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/GremlinServer.java @@ -159,7 +159,7 @@ public synchronized CompletableFuture start() throws Exce serverGremlinExecutor.getHooks().forEach(hook -> { logger.info("Executing start up {}", LifeCycleHook.class.getSimpleName()); try { - hook.onStartUp(new LifeCycleHook.Context(logger)); + hook.onStartUp(new LifeCycleHook.Context(logger, serverGremlinExecutor.getGraphManager())); } catch (UnsupportedOperationException uoe) { // if the user doesn't implement onStartUp the scriptengine will throw // this exception. it can safely be ignored. @@ -269,7 +269,7 @@ public synchronized CompletableFuture stop() { serverGremlinExecutor.getHooks().forEach(hook -> { logger.info("Executing shutdown {}", LifeCycleHook.class.getSimpleName()); try { - hook.onShutDown(new LifeCycleHook.Context(logger)); + hook.onShutDown(new LifeCycleHook.Context(logger, serverGremlinExecutor.getGraphManager())); } catch (UnsupportedOperationException | UndeclaredThrowableException uoe) { // if the user doesn't implement onShutDown the scriptengine will throw // this exception. it can safely be ignored. diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java index 3489bd511e2..5805c649f1a 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Settings.java @@ -244,6 +244,18 @@ public Settings() { */ public Map scriptEngines; + /** + * {@link Map} of {@link TraversalSource} configurations keyed by the binding name. Each entry specifies a + * graph reference and optionally a Gremlin query to configure strategies. + */ + public Map traversalSources = new LinkedHashMap<>(); + + /** + * List of {@link LifeCycleHook} implementations to instantiate via reflection and execute during server + * initialization and shutdown. + */ + public List lifecycleHooks = new ArrayList<>(); + /** * List of {@link MessageSerializer} to configure. If no serializers are specified then default serializers for * the most current versions of "application/json" and "application/vnd.gremlin-v1.0+gryo" are applied. @@ -300,6 +312,8 @@ protected static Constructor createDefaultYamlConstructor() { settingsDescription.addPropertyParameters("graphs", String.class, String.class); settingsDescription.addPropertyParameters("scriptEngines", String.class, ScriptEngineSettings.class); settingsDescription.addPropertyParameters("serializers", SerializerSettings.class); + settingsDescription.addPropertyParameters("traversalSources", String.class, TraversalSourceSettings.class); + settingsDescription.addPropertyParameters("lifecycleHooks", LifeCycleHookSettings.class); constructor.addTypeDescription(settingsDescription); final TypeDescription serializerSettingsDescription = new TypeDescription(SerializerSettings.class); @@ -314,6 +328,13 @@ protected static Constructor createDefaultYamlConstructor() { scriptEngineSettingsDescription.addPropertyParameters("plugins", String.class, Object.class); constructor.addTypeDescription(scriptEngineSettingsDescription); + final TypeDescription traversalSourceSettingsDescription = new TypeDescription(TraversalSourceSettings.class); + constructor.addTypeDescription(traversalSourceSettingsDescription); + + final TypeDescription lifeCycleHookSettingsDescription = new TypeDescription(LifeCycleHookSettings.class); + lifeCycleHookSettingsDescription.addPropertyParameters("config", String.class, Object.class); + constructor.addTypeDescription(lifeCycleHookSettingsDescription); + final TypeDescription sslSettings = new TypeDescription(SslSettings.class); constructor.addTypeDescription(sslSettings); @@ -393,6 +414,43 @@ public static class ScriptEngineSettings { public Map> plugins = new LinkedHashMap<>(); } + /** + * Settings for a declarative {@link TraversalSource} entry in the server YAML. + */ + public static class TraversalSourceSettings { + /** + * The name of the graph (as defined in {@code graphs}) to create the traversal source from. + */ + public String graph; + + /** + * An optional Gremlin query evaluated with a base traversal source bound as {@code g}. + * The result of the query becomes the final {@link TraversalSource}. + */ + public String query = null; + + /** + * The script engine language to use for evaluating {@link #query}. If not specified, resolution + * falls back to the single configured engine or {@code gremlin-lang}. + */ + public String language = null; + } + + /** + * Settings for a Java-based {@link LifeCycleHook} configured in the server YAML. + */ + public static class LifeCycleHookSettings { + /** + * The fully qualified class name of the {@link LifeCycleHook} implementation. + */ + public String className; + + /** + * Optional configuration passed to {@link LifeCycleHook#init(java.util.Map)}. + */ + public Map config = null; + } + /** * Settings for the {@link MessageSerializer} implementations. */ diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java index 95884d8d7f3..80696686b84 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/LifeCycleHook.java @@ -18,8 +18,11 @@ */ package org.apache.tinkerpop.gremlin.server.util; +import org.apache.tinkerpop.gremlin.server.GraphManager; import org.slf4j.Logger; +import java.util.Map; + /** * Provides a method in which users can hook into the startup and shutdown lifecycle of Gremlin Server. Creating * an instance of this interface in a Gremlin Server initialization script enables the ability to get a callback @@ -29,6 +32,15 @@ */ public interface LifeCycleHook { + /** + * Called once after instantiation to pass configuration from the {@code lifecycleHooks} YAML section. + * Implementations that require configuration should override this method. The default implementation + * is a no-op so that existing hooks are not forced to implement it. + * + * @param config the key/value pairs from the {@code config} block in the YAML entry + */ + public default void init(final Map config) {} + /** * Called when the server starts up. The graph collection will have been initialized at this point * and all initialization scripts will have been executed when this callback is called. @@ -45,13 +57,30 @@ public interface LifeCycleHook { */ public static class Context { private final Logger logger; + private final GraphManager graphManager; + /** + * @deprecated As of release 4.0.0, replaced by {@link #Context(Logger, GraphManager)}. + */ + @Deprecated public Context(final Logger logger) { + this(logger, null); + } + + public Context(final Logger logger, final GraphManager graphManager) { this.logger = logger; + this.graphManager = graphManager; } public Logger getLogger() { return logger; } + + /** + * Gets the {@link GraphManager} which provides access to all configured graphs and traversal sources. + */ + public GraphManager getGraphManager() { + return graphManager; + } } } diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java index dce5fb931a5..8f8b88e9a84 100644 --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutor.java @@ -36,9 +36,12 @@ import javax.script.SimpleBindings; import java.lang.reflect.Constructor; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; @@ -186,20 +189,108 @@ public ServerGremlinExecutor(final Settings settings, final ExecutorService grem .forEach(kv -> this.graphManager.putGraph(kv.getKey(), (Graph) kv.getValue())); // script engine init may have constructed the TraversalSource bindings - store them in Graphs object - gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream() - .filter(kv -> kv.getValue() instanceof TraversalSource) - .forEach(kv -> { - logger.info("A {} is now bound to [{}] with {}", kv.getValue().getClass().getSimpleName(), kv.getKey(), kv.getValue()); - this.graphManager.putTraversalSource(kv.getKey(), (TraversalSource) kv.getValue()); - }); + final boolean hasScriptTraversalSources = gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream() + .anyMatch(kv -> kv.getValue() instanceof TraversalSource); + if (hasScriptTraversalSources) { + logger.warn("TraversalSource instances were created via script engine initialization scripts. " + + "This approach is deprecated - use the 'traversalSources' YAML configuration instead."); + gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream() + .filter(kv -> kv.getValue() instanceof TraversalSource) + .forEach(kv -> { + logger.info("A {} is now bound to [{}] with {}", kv.getValue().getClass().getSimpleName(), kv.getKey(), kv.getValue()); + this.graphManager.putTraversalSource(kv.getKey(), (TraversalSource) kv.getValue()); + }); + } // determine if the initialization scripts introduced LifeCycleHook objects - if so we need to gather them // up for execution - hooks = gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream() + final boolean hasScriptHooks = gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream() + .anyMatch(kv -> kv.getValue() instanceof LifeCycleHook); + if (hasScriptHooks) { + logger.warn("LifeCycleHook instances were created via script engine initialization scripts. " + + "This approach is deprecated - use the 'lifecycleHooks' YAML configuration instead."); + } + final List scriptHooks = gremlinExecutor.getScriptEngineManager().getBindings().entrySet().stream() .filter(kv -> kv.getValue() instanceof LifeCycleHook) .map(kv -> (LifeCycleHook) kv.getValue()) .collect(Collectors.toList()); + // process declarative traversalSources from YAML config - track which graphs have explicit entries + final Set graphsWithExplicitTraversalSources = new HashSet<>(); + if (settings.traversalSources != null && !settings.traversalSources.isEmpty()) { + settings.traversalSources.forEach((tsName, tsSettings) -> { + final Graph graph = this.graphManager.getGraph(tsSettings.graph); + if (null == graph) { + logger.warn("Could not create TraversalSource [{}] - graph [{}] not found in graphs configuration", + tsName, tsSettings.graph); + return; + } + + graphsWithExplicitTraversalSources.add(tsSettings.graph); + + if (tsSettings.query != null && !tsSettings.query.isEmpty()) { + // resolve which script engine to use for the query + final String language = resolveLanguage(tsSettings.language); + try { + // bind a base traversal source as 'g' for the query to operate on + final SimpleBindings bindings = new SimpleBindings(); + bindings.put("g", graph.traversal()); + final TraversalSource ts = (TraversalSource) gremlinExecutor.eval( + tsSettings.query, language, bindings).join(); + this.graphManager.putTraversalSource(tsName, ts); + logger.info("A {} is now bound to [{}] via query", ts.getClass().getSimpleName(), tsName); + } catch (Exception ex) { + logger.warn(String.format("Could not create TraversalSource [%s] from query - %s", + tsName, ex.getMessage()), ex); + } + } else { + final TraversalSource ts = graph.traversal(); + this.graphManager.putTraversalSource(tsName, ts); + logger.info("A {} is now bound to [{}]", ts.getClass().getSimpleName(), tsName); + } + }); + } + + // auto-create TraversalSources for graphs that don't have explicit traversalSources entries and + // were not already bound by script engine initialization + for (final String graphName : this.graphManager.getGraphNames()) { + if (graphsWithExplicitTraversalSources.contains(graphName)) + continue; + + final String tsName = graphName.equals("graph") ? "g" : "g_" + graphName; + if (this.graphManager.getTraversalSource(tsName) != null) + continue; + + final Graph graph = this.graphManager.getGraph(graphName); + final TraversalSource ts = graph.traversal(); + this.graphManager.putTraversalSource(tsName, ts); + logger.info("A {} is now auto-bound to [{}] for graph [{}]", + ts.getClass().getSimpleName(), tsName, graphName); + } + + // instantiate Java-based lifecycle hooks from YAML config + final List yamlHooks = new ArrayList<>(); + if (settings.lifecycleHooks != null) { + for (final Settings.LifeCycleHookSettings hookSettings : settings.lifecycleHooks) { + try { + final Class clazz = Class.forName(hookSettings.className); + final LifeCycleHook hook = (LifeCycleHook) clazz.getDeclaredConstructor().newInstance(); + if (hookSettings.config != null) { + hook.init(hookSettings.config); + } + yamlHooks.add(hook); + logger.info("Instantiated LifeCycleHook: {}", hookSettings.className); + } catch (Exception ex) { + logger.error(String.format("Could not instantiate LifeCycleHook %s", hookSettings.className), ex); + } + } + } + + // combine YAML-configured hooks with any script-produced hooks (YAML hooks execute first) + final List allHooks = new ArrayList<>(yamlHooks); + allHooks.addAll(scriptHooks); + hooks = allHooks; + transactionManager = new TransactionManager( scheduledExecutorService, graphManager, @@ -214,6 +305,21 @@ private void registerMetrics(final String engineName) { MetricManager.INSTANCE.registerGremlinScriptEngineMetrics(engine, engineName, "sessionless", "class-cache"); } + /** + * Resolves the script engine language to use for evaluating a traversal source query. If an explicit language + * is provided, it is used directly. Otherwise, if exactly one script engine is configured, that engine is used. + * Falls back to {@code gremlin-lang}. + */ + private String resolveLanguage(final String explicitLanguage) { + if (explicitLanguage != null && !explicitLanguage.isEmpty()) + return explicitLanguage; + + if (settings.scriptEngines.size() == 1) + return settings.scriptEngines.keySet().iterator().next(); + + return "gremlin-lang"; + } + public void addHostOption(final String key, final Object value) { hostOptions.put(key, value); } diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java new file mode 100644 index 00000000000..a211d814263 --- /dev/null +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoader.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.server.util; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory; + +import java.util.Map; + +/** + * A {@link LifeCycleHook} that loads TinkerFactory sample datasets into a graph during server startup. + * Replaces the Groovy init scripts (e.g. generate-modern.groovy) for default server configurations. + * + *

Configuration parameters (via {@code config} in YAML): + *

    + *
  • {@code graph} — name of the graph (as defined in {@code graphs:} config) to load data into
  • + *
  • {@code dataset} — which dataset to load: modern, classic, crew, grateful, sink, airroutes
  • + *
+ * + *

Example YAML usage: + *

+ * lifecycleHooks:
+ *   - className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader
+ *     config: {graph: graph, dataset: modern}
+ * 
+ */ +public class TinkerFactoryDataLoader implements LifeCycleHook { + + private String graphName; + private String dataset; + + @Override + public void init(final Map config) { + graphName = (String) config.get("graph"); + dataset = (String) config.get("dataset"); + if (graphName == null || dataset == null) { + throw new IllegalArgumentException("TinkerFactoryDataLoader requires 'graph' and 'dataset' config parameters"); + } + } + + @Override + public void onStartUp(final Context c) { + final Graph graph = c.getGraphManager().getGraph(graphName); + if (null == graph) { + c.getLogger().warn("TinkerFactoryDataLoader could not find graph [{}]", graphName); + return; + } + if (!(graph instanceof AbstractTinkerGraph)) { + c.getLogger().warn("TinkerFactoryDataLoader requires an AbstractTinkerGraph but [{}] is {}", + graphName, graph.getClass().getName()); + return; + } + + final AbstractTinkerGraph tinkerGraph = (AbstractTinkerGraph) graph; + switch (dataset) { + case "modern": + TinkerFactory.generateModern(tinkerGraph); + break; + case "classic": + TinkerFactory.generateClassic(tinkerGraph); + break; + case "crew": + TinkerFactory.generateTheCrew(tinkerGraph); + break; + case "grateful": + TinkerFactory.generateGratefulDead(tinkerGraph); + break; + case "sink": + TinkerFactory.generateKitchenSink(tinkerGraph); + break; + case "airroutes": + TinkerFactory.generateAirRoutes(tinkerGraph); + break; + default: + c.getLogger().warn("TinkerFactoryDataLoader unknown dataset [{}]", dataset); + return; + } + c.getLogger().info("TinkerFactoryDataLoader loaded [{}] dataset into graph [{}]", dataset, graphName); + } + + @Override + public void onShutDown(final Context c) { + // nothing to do on shutdown + } +} diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java index c4cfa266199..71e4dfba046 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinDriverIntegrateTest.java @@ -119,16 +119,13 @@ public Settings overrideSettings(final Settings settings) { settings.channelizer = HttpChannelizer.class.getName(); break; case "shouldAliasTraversalSourceVariables": - try { - final String p = Storage.toPath(TestHelper.generateTempFileFromResource( - GremlinDriverIntegrateTest.class, - "generate-shouldRebindTraversalSourceVariables.groovy", "")); - final Map m = new HashMap<>(); - m.put("files", Collections.singletonList(p)); - settings.scriptEngines.get("gremlin-groovy").plugins.put(ScriptFileGremlinPlugin.class.getName(), m); - } catch (Exception ex) { - throw new RuntimeException(ex); - } + final Settings.TraversalSourceSettings readOnlyG = new Settings.TraversalSourceSettings(); + readOnlyG.graph = "graph"; + readOnlyG.query = "g.withStrategies(ReadOnlyStrategy)"; + settings.traversalSources.put("g", readOnlyG); + final Settings.TraversalSourceSettings writableG1 = new Settings.TraversalSourceSettings(); + writableG1.graph = "graph"; + settings.traversalSources.put("g1", writableG1); break; case "shouldFailWithBadClientSideSerialization": // add custom gryo config for Color diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerConfigIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerConfigIntegrateTest.java new file mode 100644 index 00000000000..835aad5732d --- /dev/null +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerConfigIntegrateTest.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.server; + +import org.apache.tinkerpop.gremlin.driver.Client; +import org.apache.tinkerpop.gremlin.driver.Cluster; +import org.apache.tinkerpop.gremlin.driver.Result; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.AbstractTinkerGraph; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Validates that each shipped server config can start successfully and serve a basic query. + * Configs requiring SSL or authentication infrastructure are excluded. + */ +@RunWith(Parameterized.class) +public class GremlinServerConfigIntegrateTest { + + private static final Logger logger = LoggerFactory.getLogger(GremlinServerConfigIntegrateTest.class); + + @Parameterized.Parameter + public String configName; + + @Parameterized.Parameters(name = "{0}") + public static Collection configs() { + return Arrays.asList( + "gremlin-server.yaml", + "gremlin-server-min.yaml", + "gremlin-server-modern.yaml", + "gremlin-server-classic.yaml", + "gremlin-server-airroutes.yaml", + "gremlin-server-modern-readonly.yaml", + "gremlin-server-rest-modern.yaml", + "gremlin-server-transaction.yaml" + ); + } + + @Test + public void shouldStartAndServeQuery() throws Exception { + final File confDir = new File(System.getProperty("build.dir"), "../conf"); + final Settings settings = Settings.read(new FileInputStream(new File(confDir, configName))); + + settings.serializers = Collections.emptyList(); + ServerTestHelper.rewritePathsInGremlinServerSettings(settings); + + final GremlinServer server = new GremlinServer(settings); + try { + server.start().join(); + logger.info("Started server with config: {}", configName); + + final Cluster cluster = Cluster.build("localhost").port(settings.port).create(); + final Client client = cluster.connect(); + try { + final List results = client.submit("g.inject(1)").all().get(); + assertThat(results.size(), is(1)); + assertThat(results.get(0).getInt(), is(1)); + } finally { + client.close(); + cluster.close(); + } + } finally { + server.getServerGremlinExecutor().getGraphManager().getAsBindings().values().stream() + .filter(g -> g instanceof AbstractTinkerGraph) + .forEach(g -> ((AbstractTinkerGraph) g).clear()); + server.stop().join(); + } + } +} diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ServerTestHelper.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ServerTestHelper.java index 0d642ffd7fd..1a3dc91f920 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ServerTestHelper.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/ServerTestHelper.java @@ -38,24 +38,25 @@ public class ServerTestHelper { * If an overriden path is determined to be absolute then the path is not re-written. */ public static void rewritePathsInGremlinServerSettings(final Settings overridenSettings) { - final Map> plugins; - final Map scriptFileGremlinPlugin; final File homeDir; homeDir = new File( getBuildDir(), "../src/test/scripts" ); - plugins = overridenSettings.scriptEngines.get("gremlin-groovy").plugins; - scriptFileGremlinPlugin = plugins.get(ScriptFileGremlinPlugin.class.getName()); + final Settings.ScriptEngineSettings groovyEngine = overridenSettings.scriptEngines.get("gremlin-groovy"); + if (groovyEngine != null) { + final Map> plugins = groovyEngine.plugins; + final Map scriptFileGremlinPlugin = plugins.get(ScriptFileGremlinPlugin.class.getName()); - if (scriptFileGremlinPlugin != null) { - scriptFileGremlinPlugin - .put("files", - ((List) scriptFileGremlinPlugin.get("files")).stream() - .map(s -> new File(s)) - .map(f -> f.isAbsolute() ? f - : new File(relocateFile( homeDir, f))) - .map(f -> Storage.toPath(f)) - .collect(Collectors.toList())); + if (scriptFileGremlinPlugin != null) { + scriptFileGremlinPlugin + .put("files", + ((List) scriptFileGremlinPlugin.get("files")).stream() + .map(s -> new File(s)) + .map(f -> f.isAbsolute() ? f + : new File(relocateFile( homeDir, f))) + .map(f -> Storage.toPath(f)) + .collect(Collectors.toList())); + } } overridenSettings.graphs = overridenSettings.graphs.entrySet().stream() diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/SettingsTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/SettingsTest.java index d399a5ad657..3eedf8f24c1 100644 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/SettingsTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/SettingsTest.java @@ -24,6 +24,11 @@ import java.io.InputStream; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; public class SettingsTest { @@ -56,4 +61,77 @@ public void defaultCustomValuesAreHandledCorrectly() throws Exception { assertEquals("localhost", settings.customValue); } + + @Test + public void scriptEnginesDefaultsToGremlinLangWhenAbsentFromYaml() throws Exception { + final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-minimal.yaml"); + final Settings settings = Settings.read(stream); + + assertThat(settings.scriptEngines, is(notNullValue())); + assertThat(settings.scriptEngines.size(), is(1)); + assertThat(settings.scriptEngines, hasKey("gremlin-lang")); + } + + @Test + public void scriptEnginesPopulatedWhenPresentInYaml() throws Exception { + final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-integration.yaml"); + final Settings settings = Settings.read(stream); + + assertThat(settings.scriptEngines, hasKey("gremlin-groovy")); + assertThat(settings.scriptEngines, hasKey("gremlin-lang")); + } + + @Test + public void traversalSourcesParsedFromYaml() throws Exception { + final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-with-traversal-sources.yaml"); + final Settings settings = Settings.read(stream); + + assertThat(settings.traversalSources.size(), is(2)); + assertThat(settings.traversalSources, hasKey("g")); + assertThat(settings.traversalSources, hasKey("gReadOnly")); + + final Settings.TraversalSourceSettings gSettings = settings.traversalSources.get("g"); + assertThat(gSettings.graph, is("graph")); + assertThat(gSettings.query, is(nullValue())); + assertThat(gSettings.language, is(nullValue())); + + final Settings.TraversalSourceSettings roSettings = settings.traversalSources.get("gReadOnly"); + assertThat(roSettings.graph, is("graph")); + assertThat(roSettings.query, is("g.withStrategies(ReadOnlyStrategy)")); + assertThat(roSettings.language, is("gremlin-groovy")); + } + + @Test + public void traversalSourcesDefaultsToEmptyMapWhenAbsentFromYaml() throws Exception { + final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-minimal.yaml"); + final Settings settings = Settings.read(stream); + + assertThat(settings.traversalSources, is(notNullValue())); + assertThat(settings.traversalSources.isEmpty(), is(true)); + } + + @Test + public void lifecycleHooksParsedFromYaml() throws Exception { + final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-with-traversal-sources.yaml"); + final Settings settings = Settings.read(stream); + + assertThat(settings.lifecycleHooks.size(), is(2)); + + final Settings.LifeCycleHookSettings first = settings.lifecycleHooks.get(0); + assertThat(first.className, is("org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader")); + assertThat(first.config, hasKey("graph")); + assertThat(first.config.get("dataset"), is("modern")); + + final Settings.LifeCycleHookSettings second = settings.lifecycleHooks.get(1); + assertThat(second.config.get("dataset"), is("classic")); + } + + @Test + public void lifecycleHooksDefaultsToEmptyListWhenAbsentFromYaml() throws Exception { + final InputStream stream = SettingsTest.class.getResourceAsStream("gremlin-server-minimal.yaml"); + final Settings settings = Settings.read(stream); + + assertThat(settings.lifecycleHooks, is(notNullValue())); + assertThat(settings.lifecycleHooks.isEmpty(), is(true)); + } } diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutorTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutorTest.java new file mode 100644 index 00000000000..489d4a6a5fd --- /dev/null +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/ServerGremlinExecutorTest.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.server.util; + +import org.apache.tinkerpop.gremlin.server.Settings; +import org.junit.After; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +public class ServerGremlinExecutorTest { + + private static final String TINKERGRAPH_PROPERTIES = + new java.io.File(System.getProperty("build.dir"), "../src/test/scripts/tinkergraph-empty.properties") + .getAbsolutePath(); + + private ServerGremlinExecutor serverGremlinExecutor; + + @After + public void tearDown() { + if (serverGremlinExecutor != null) { + serverGremlinExecutor.getGremlinExecutorService().shutdownNow(); + serverGremlinExecutor.getGraphManager().getGraphNames().forEach(name -> { + try { + serverGremlinExecutor.getGraphManager().getGraph(name).close(); + } catch (Exception ignored) { + } + }); + } + } + + private Settings baseSettings() { + final Settings settings = new Settings(); + settings.graphs.put("graph", TINKERGRAPH_PROPERTIES); + settings.gremlinPool = 1; + return settings; + } + + @Test + public void shouldAutoCreateTraversalSourceForSingleGraph() { + serverGremlinExecutor = new ServerGremlinExecutor(baseSettings(), null, null); + + assertThat(serverGremlinExecutor.getGraphManager().getTraversalSource("g"), is(notNullValue())); + } + + @Test + public void shouldAutoCreateTraversalSourceWithPrefixForNonDefaultGraph() { + final Settings settings = baseSettings(); + settings.graphs.put("myGraph", TINKERGRAPH_PROPERTIES); + serverGremlinExecutor = new ServerGremlinExecutor(settings, null, null); + + assertThat(serverGremlinExecutor.getGraphManager().getTraversalSource("g"), is(notNullValue())); + assertThat(serverGremlinExecutor.getGraphManager().getTraversalSource("g_myGraph"), is(notNullValue())); + } + + @Test + public void shouldNotAutoCreateTraversalSourceWhenExplicitEntryExists() { + final Settings settings = baseSettings(); + final Settings.TraversalSourceSettings tsSettings = new Settings.TraversalSourceSettings(); + tsSettings.graph = "graph"; + settings.traversalSources.put("g", tsSettings); + serverGremlinExecutor = new ServerGremlinExecutor(settings, null, null); + + assertThat(serverGremlinExecutor.getGraphManager().getTraversalSource("g"), is(notNullValue())); + assertThat(serverGremlinExecutor.getGraphManager().getTraversalSource("g_graph"), is(nullValue())); + } + + @Test + public void shouldInstantiateLifecycleHooksFromYaml() { + final Settings settings = baseSettings(); + final Settings.LifeCycleHookSettings hook1 = new Settings.LifeCycleHookSettings(); + hook1.className = TinkerFactoryDataLoader.class.getName(); + hook1.config = new LinkedHashMap<>(); + hook1.config.put("graph", "graph"); + hook1.config.put("dataset", "modern"); + final Settings.LifeCycleHookSettings hook2 = new Settings.LifeCycleHookSettings(); + hook2.className = TinkerFactoryDataLoader.class.getName(); + hook2.config = new LinkedHashMap<>(); + hook2.config.put("graph", "graph"); + hook2.config.put("dataset", "classic"); + final List hooks = new ArrayList<>(); + hooks.add(hook1); + hooks.add(hook2); + settings.lifecycleHooks = hooks; + serverGremlinExecutor = new ServerGremlinExecutor(settings, null, null); + + assertThat(serverGremlinExecutor.getHooks().size(), is(2)); + assertThat(serverGremlinExecutor.getHooks().get(0) instanceof TinkerFactoryDataLoader, is(true)); + assertThat(serverGremlinExecutor.getHooks().get(1) instanceof TinkerFactoryDataLoader, is(true)); + } + + @Test + public void shouldHaveEmptyHooksWhenNoneConfigured() { + serverGremlinExecutor = new ServerGremlinExecutor(baseSettings(), null, null); + + assertThat(serverGremlinExecutor.getHooks().isEmpty(), is(true)); + } + + @Test + public void resolveLanguageShouldReturnExplicitLanguage() throws Exception { + serverGremlinExecutor = new ServerGremlinExecutor(baseSettings(), null, null); + + final Method resolveLanguage = ServerGremlinExecutor.class.getDeclaredMethod("resolveLanguage", String.class); + resolveLanguage.setAccessible(true); + + assertThat(resolveLanguage.invoke(serverGremlinExecutor, "gremlin-groovy"), is("gremlin-groovy")); + } + + @Test + public void resolveLanguageShouldFallBackToGremlinLangWhenNoExplicitLanguage() throws Exception { + serverGremlinExecutor = new ServerGremlinExecutor(baseSettings(), null, null); + + final Method resolveLanguage = ServerGremlinExecutor.class.getDeclaredMethod("resolveLanguage", String.class); + resolveLanguage.setAccessible(true); + + assertThat(resolveLanguage.invoke(serverGremlinExecutor, (String) null), is("gremlin-lang")); + assertThat(resolveLanguage.invoke(serverGremlinExecutor, ""), is("gremlin-lang")); + } + + @Test + public void resolveLanguageShouldFallBackToGremlinLangWhenMultipleEngines() throws Exception { + final Settings settings = baseSettings(); + settings.scriptEngines.put("gremlin-groovy", new Settings.ScriptEngineSettings()); + serverGremlinExecutor = new ServerGremlinExecutor(settings, null, null); + + final Method resolveLanguage = ServerGremlinExecutor.class.getDeclaredMethod("resolveLanguage", String.class); + resolveLanguage.setAccessible(true); + + assertThat(resolveLanguage.invoke(serverGremlinExecutor, (String) null), is("gremlin-lang")); + } + + @Test + public void resolveLanguageShouldUseSoleConfiguredEngine() throws Exception { + final Settings settings = baseSettings(); + settings.scriptEngines.clear(); + settings.scriptEngines.put("gremlin-groovy", new Settings.ScriptEngineSettings()); + serverGremlinExecutor = new ServerGremlinExecutor(settings, null, null); + + final Method resolveLanguage = ServerGremlinExecutor.class.getDeclaredMethod("resolveLanguage", String.class); + resolveLanguage.setAccessible(true); + + assertThat(resolveLanguage.invoke(serverGremlinExecutor, (String) null), is("gremlin-groovy")); + } +} diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoaderTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoaderTest.java new file mode 100644 index 00000000000..0700c48be3b --- /dev/null +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/util/TinkerFactoryDataLoaderTest.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.server.util; + +import org.apache.tinkerpop.gremlin.server.GraphManager; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TinkerFactoryDataLoaderTest { + + private LifeCycleHook.Context createContext(final GraphManager graphManager) { + return new LifeCycleHook.Context(LoggerFactory.getLogger(TinkerFactoryDataLoaderTest.class), graphManager); + } + + @Test(expected = IllegalArgumentException.class) + public void initShouldThrowWhenGraphMissing() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("dataset", "modern"); + loader.init(config); + } + + @Test(expected = IllegalArgumentException.class) + public void initShouldThrowWhenDatasetMissing() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + loader.init(config); + } + + @Test(expected = IllegalArgumentException.class) + public void initShouldThrowWhenConfigEmpty() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + loader.init(new HashMap<>()); + } + + @Test + public void onStartUpShouldHandleMissingGraph() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "nonexistent"); + config.put("dataset", "modern"); + loader.init(config); + + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("nonexistent")).thenReturn(null); + + // should not throw — just logs a warning + loader.onStartUp(createContext(gm)); + } + + @Test + public void onStartUpShouldHandleNonTinkerGraph() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "modern"); + loader.init(config); + + final Graph mockGraph = mock(Graph.class); + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(mockGraph); + + // should not throw — just logs a warning + loader.onStartUp(createContext(gm)); + } + + @Test + public void onStartUpShouldHandleUnknownDataset() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "bogus"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + // should not throw — just logs a warning + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), is(0)); + } finally { + graph.close(); + } + } + + @Test + public void onStartUpShouldLoadModern() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "modern"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), is(6)); + assertThat((int) graph.traversal().E().count().next().longValue(), is(6)); + } finally { + graph.close(); + } + } + + @Test + public void onStartUpShouldLoadClassic() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "classic"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), is(6)); + } finally { + graph.close(); + } + } + + @Test + public void onStartUpShouldLoadCrew() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "crew"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), greaterThan(0)); + } finally { + graph.close(); + } + } + + @Test + public void onStartUpShouldLoadGrateful() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "grateful"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), greaterThan(0)); + } finally { + graph.close(); + } + } + + @Test + public void onStartUpShouldLoadSink() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "sink"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), greaterThan(0)); + } finally { + graph.close(); + } + } + + @Test + public void onStartUpShouldLoadAirRoutes() { + final TinkerFactoryDataLoader loader = new TinkerFactoryDataLoader(); + final Map config = new HashMap<>(); + config.put("graph", "graph"); + config.put("dataset", "airroutes"); + loader.init(config); + + final TinkerGraph graph = TinkerGraph.open(); + try { + final GraphManager gm = mock(GraphManager.class); + when(gm.getGraph("graph")).thenReturn(graph); + + loader.onStartUp(createContext(gm)); + assertThat((int) graph.traversal().V().count().next().longValue(), greaterThan(0)); + } finally { + graph.close(); + } + } +} diff --git a/gremlin-server/src/test/resources/conf/remote-objects.yaml b/gremlin-server/src/test/resources/conf/remote-objects.yaml index a0f33187685..37e1caaa8da 100644 --- a/gremlin-server/src/test/resources/conf/remote-objects.yaml +++ b/gremlin-server/src/test/resources/conf/remote-objects.yaml @@ -23,8 +23,7 @@ # - docker/gremlin-server/conf/remote-objects.yaml # # Without such changes, the test docker server can't be started for independent -# testing with Gremlin Language Variants. Note this file's relation to -# gremlin-server/src/test/resources/scripts/test-server-start.groovy +# testing with Gremlin Language Variants. ############################################################################### hosts: [localhost] diff --git a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml index 13a1c6ffdb9..c13041f1dd0 100644 --- a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml +++ b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml @@ -25,8 +25,7 @@ # - docker/gremlin-server/gremlin-server-integration-secure.yaml # # Without such changes, the test docker server can't be started for independent -# testing with Gremlin Language Variants. Note this file's relation to -# gremlin-server/src/test/resources/scripts/test-server-start.groovy +# testing with Gremlin Language Variants. ############################################################################### host: 0.0.0.0 @@ -41,6 +40,21 @@ graphs: { sink: conf/tinkergraph-empty.properties, tx: conf/tinkertransactiongraph-empty.properties } +traversalSources: { + gclassic: {graph: classic}, + gmodern: {graph: modern}, + g: {graph: graph}, + gcrew: {graph: crew}, + ggraph: {graph: graph}, + ggrateful: {graph: grateful}, + gsink: {graph: sink}, + gtx: {graph: tx}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: classic, dataset: classic}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: modern, dataset: modern}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: crew, dataset: crew}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: grateful, dataset: grateful}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: sink, dataset: sink}} scriptEngines: { gremlin-lang : {}, gremlin-groovy: { @@ -48,7 +62,7 @@ scriptEngines: { org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin: {expectedCompilationTime: 30000}, org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, - org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/generate-all.groovy]}}}} + org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/init-functions.groovy]}}}} serializers: - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV4, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerIoRegistryV3] }} diff --git a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-minimal.yaml b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-minimal.yaml new file mode 100644 index 00000000000..e4cd0fe943b --- /dev/null +++ b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-minimal.yaml @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +host: localhost +port: 8182 +graphs: { + graph: conf/tinkergraph-empty.properties} diff --git a/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-with-traversal-sources.yaml b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-with-traversal-sources.yaml new file mode 100644 index 00000000000..74969d5e341 --- /dev/null +++ b/gremlin-server/src/test/resources/org/apache/tinkerpop/gremlin/server/gremlin-server-with-traversal-sources.yaml @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +host: localhost +port: 8182 +graphs: { + graph: conf/tinkergraph-empty.properties} +traversalSources: { + g: {graph: graph}, + gReadOnly: {graph: graph, query: "g.withStrategies(ReadOnlyStrategy)", language: gremlin-groovy}} +lifecycleHooks: + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: modern}} + - { className: org.apache.tinkerpop.gremlin.server.util.TinkerFactoryDataLoader, config: {graph: graph, dataset: classic}} diff --git a/gremlin-server/src/test/scripts/generate-all.groovy b/gremlin-server/src/test/scripts/generate-all.groovy deleted file mode 100644 index 7f3706f2d82..00000000000 --- a/gremlin-server/src/test/scripts/generate-all.groovy +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// An example of an initialization script that can be configured to run in Gremlin Server. -// Functions defined here will go into global cache and will not be removed from there -// unless there is a reset of the ScriptEngine. -def addItUp(x, y) { x + y } - -// an init script that returns a Map allows explicit setting of global bindings. -def globals = [:] - -// Generates the modern graph into an "empty" TinkerGraph via LifeCycleHook. -// Note that the name of the key in the "global" map is unimportant. -globals << [hook : [ - onStartUp: { ctx -> - // a wild bit of trickery here. the process tests use an INTEGER id manager when LoadGraphWith is used. this - // closure provides a way to to manually override the various id managers for TinkerGraph - the graph on which - // all of these remote tests are executed - so that the tests will pass nicely. an alternative might have been - // to have a special test TinkerGraph config for setting up the id manager properly, but based on how we do - // things now, that test config would have been mixed in with release artifacts and there would have been ugly - // exclusions to make packaging work properly. - allowSetOfIdManager = { graph, idManagerFieldName, idManager -> - java.lang.reflect.Field idManagerField = graph.class.getSuperclass().getDeclaredField(idManagerFieldName) - idManagerField.setAccessible(true) - idManagerField.set(graph, idManager) - } - - [classic, modern, crew, sink, grateful].each{ - allowSetOfIdManager(it, "vertexIdManager", TinkerGraph.DefaultIdManager.INTEGER) - allowSetOfIdManager(it, "edgeIdManager", TinkerGraph.DefaultIdManager.INTEGER) - allowSetOfIdManager(it, "vertexPropertyIdManager", TinkerGraph.DefaultIdManager.LONG) - } - TinkerFactory.generateClassic(classic) - TinkerFactory.generateModern(modern) - TinkerFactory.generateTheCrew(crew) - TinkerFactory.generateGratefulDead(grateful) - TinkerFactory.generateKitchenSink(sink) - } -] as LifeCycleHook] - -// add default TraversalSource instances for each graph instance -globals << [gclassic : traversal().withEmbedded(classic)] -globals << [gmodern : traversal().withEmbedded(modern)] -globals << [g : traversal().withEmbedded(graph)] -globals << [gcrew : traversal().withEmbedded(crew)] -globals << [ggraph : traversal().withEmbedded(graph)] -globals << [ggrateful : traversal().withEmbedded(grateful)] -globals << [gsink : traversal().withEmbedded(sink)] -globals << [gtx : traversal().withEmbedded(tx)] - -// dynamically detect existence of gimmutable as it is only used in gremlin-go testing suite -def dynamicGimmutable = context.getBindings(javax.script.ScriptContext.GLOBAL_SCOPE)["immutable"] -if (dynamicGimmutable != null) - globals << [gimmutable : traversal().withEmbedded(dynamicGimmutable)] - -globals diff --git a/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/generate.groovy b/gremlin-server/src/test/scripts/init-functions.groovy similarity index 57% rename from gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/generate.groovy rename to gremlin-server/src/test/scripts/init-functions.groovy index 5c2b791dbc3..5c105446dc4 100644 --- a/gremlin-console/src/test/resources/org/apache/tinkerpop/gremlin/console/jsr223/generate.groovy +++ b/gremlin-server/src/test/scripts/init-functions.groovy @@ -17,17 +17,4 @@ * under the License. */ -// an init script that returns a Map allows explicit setting of global bindings. -def globals = [:] - -// Generates the modern graph into an "empty" TinkerGraph via LifeCycleHook. -// Note that the name of the key in the "global" map is unimportant. -globals << [hook : [ - onStartUp: { ctx -> - ctx.logger.info("Loading 'modern' graph data.") - org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerFactory.generateModern(graph) - } -] as LifeCycleHook] - -// define the default TraversalSource to bind queries to - this one will be named "g". -globals << [g : traversal().withEmbedded(graph)] \ No newline at end of file +def addItUp(x, y) { x + y } diff --git a/gremlin-server/src/test/scripts/test-server-start.groovy b/gremlin-server/src/test/scripts/test-server-start.groovy deleted file mode 100644 index c7e7f5e954f..00000000000 --- a/gremlin-server/src/test/scripts/test-server-start.groovy +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import org.apache.tinkerpop.gremlin.server.GremlinServer -import org.apache.tinkerpop.gremlin.server.KdcFixture -import org.apache.tinkerpop.gremlin.server.Settings - -//////////////////////////////////////////////////////////////////////////////// -// IMPORTANT -//////////////////////////////////////////////////////////////////////////////// -// Changes to this file need to be appropriately replicated to -// -// - docker/gremlin-server/* -// - docker/gremlin-server.sh -// -// Without such changes, the test docker server can't be started for independent -// testing with Gremlin Language Variants. -//////////////////////////////////////////////////////////////////////////////// - -if (Boolean.parseBoolean(skipTests)) return - -log.info("Starting Gremlin Server instances for native testing of ${executionName}") - -def platformAgnosticBaseDirPath = "${tinkerpopRootDir}".replace("\\", "/") -def platformAgnosticGremlinServerDir = platformAgnosticBaseDirPath + "/gremlin-server" -def platformAgnosticSettingsFile = platformAgnosticGremlinServerDir + "/src/test/resources/org" + - "/apache/tinkerpop/gremlin/server/gremlin-server-integration.yaml" - -def settings = Settings.read("${platformAgnosticSettingsFile}") -settings.graphs.graph = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settings.graphs.classic = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settings.graphs.modern = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settings.graphs.crew = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settings.graphs.grateful = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settings.graphs.sink = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settings.graphs.tx = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkertransactiongraph-empty.properties" -settings.scriptEngines["gremlin-groovy"].plugins["org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin"].files = [platformAgnosticGremlinServerDir + "/src/test/scripts/generate-all.groovy"] -settings.port = 45940 - -def server = new GremlinServer(settings) -server.start().join() - -project.setContextValue("gremlin.server", server) -log.info("Gremlin Server without authentication started on port 45940") - - -def securePropsFile = new File("${platformAgnosticBaseDirPath}/target/tinkergraph-credentials.properties") -if (!securePropsFile.exists()) { - securePropsFile.createNewFile() - securePropsFile << "gremlin.graph=org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph\n" - securePropsFile << "gremlin.tinkergraph.vertexIdManager=LONG\n" - securePropsFile << "gremlin.tinkergraph.graphLocation=${platformAgnosticGremlinServerDir}/data/credentials.kryo\n" - securePropsFile << "gremlin.tinkergraph.graphFormat=gryo" -} - -def settingsSecure = Settings.read("${platformAgnosticSettingsFile}") -settingsSecure.graphs.graph = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsSecure.graphs.classic = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsSecure.graphs.modern = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsSecure.graphs.crew = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsSecure.graphs.grateful = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsSecure.graphs.sink = platformAgnosticGremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsSecure.scriptEngines["gremlin-groovy"].plugins["org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin"].files = [platformAgnosticGremlinServerDir + "/src/test/scripts/generate-all.groovy"] -settingsSecure.port = 45941 -settingsSecure.authentication.authenticator = "org.apache.tinkerpop.gremlin.server.auth.SimpleAuthenticator" -settingsSecure.authentication.config = [credentialsDb: platformAgnosticBaseDirPath + "/target/tinkergraph-credentials.properties"] -settingsSecure.ssl = new Settings.SslSettings() -settingsSecure.ssl.enabled = true -settingsSecure.ssl.sslEnabledProtocols = ["TLSv1.2"] -settingsSecure.ssl.keyStore = platformAgnosticGremlinServerDir + "/src/test/resources/server-key.jks" -settingsSecure.ssl.keyStorePassword = "changeit" - -def serverSecure = new GremlinServer(settingsSecure) -serverSecure.start().join() - -project.setContextValue("gremlin.server.secure", serverSecure) -log.info("Gremlin Server with password authentication started on port 45941") - - -def kdcServer = new KdcFixture(projectBaseDir) -kdcServer.setUp() - -project.setContextValue("gremlin.server.kdcserver", kdcServer) -log.info("KDC started with configuration ${projectBaseDir}/target/kdc/krb5.conf") - -def settingsKrb5 = Settings.read("${settingsFile}") -settingsKrb5.graphs.graph = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsKrb5.graphs.classic = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsKrb5.graphs.modern = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsKrb5.graphs.crew = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsKrb5.graphs.grateful = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsKrb5.graphs.sink = gremlinServerDir + "/src/test/scripts/tinkergraph-empty.properties" -settingsKrb5.scriptEngines["gremlin-groovy"].plugins["org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin"].files = [gremlinServerDir + "/src/test/scripts/generate-all.groovy"] -settingsKrb5.port = 45942 -settingsKrb5.authentication.authenticator = "org.apache.tinkerpop.gremlin.server.auth.Krb5Authenticator" -settingsKrb5.authentication.config = [ - "principal": kdcServer.serverPrincipal, - "keytab": kdcServer.serviceKeytabFile.getAbsolutePath()] - -def serverKrb5 = new GremlinServer(settingsKrb5) -serverKrb5.start().join() - -project.setContextValue("gremlin.server.krb5", serverKrb5) -log.info("Gremlin Server with kerberos authentication started on port 45942")