Chapter 1. Pluggable Discovery API You can use the Pluggable Discovery API endpoint, /api/v2.2/discovery , to register an external plug-in with Cryostat and provide information about discoverable application targets to Cryostat. Note As an alternative to the Pluggable Discovery API, you can use the Cryostat agent as a Cryostat discovery plug-in. The Cryostat agent is implemented as a Java Instrumentation Agent that acts as a plug-in for applications that run on the JVM. The Cryostat agent provides an HTTP API that offers greater deployment flexibility than a JMX port due to the agent's dual role as a discovery plug-in. You can configure your target applications to use the agent's HTTP API for both detection and connectivity with Cryostat. For more information about configuring your target applications to use the Cryostat agent, see Configuring Java applications . 1.1. Pluggable Discovery API overview You can use the Pluggable Discovery API endpoint, /api/v2.2/discovery , to register an external plug-in with Cryostat and then provide information about discoverable application targets to Cryostat. A plug-in can either unregister itself after successfully registering with Cryostat or continually push updates to Cryostat about a target application. Note The purpose of the registration operation is to enhance Cryostat security and to maintain data consistency between the plug-in and Cryostat. The Pluggable Discovery API provides a more flexible way to integrate Cryostat into your deployment schema that the Red Hat OpenShift service account mechanism. Consider an example where you need to write a plug-in program that creates a static map of application IP addresses to port numbers. The plug-in can use the Pluggable Discovery API to transport this information to Cryostat, so that Cryostat can better connect with the target application. The Pluggable Discovery API depends on the following handlers to manage requests sent from the /api/v2.2/discovery endpoint and Cryostat: DiscoveryRegistrationHandler DiscoveryRegistrationCheckHandler DiscoveryGetHandler DiscoveryPostHandler DiscoveryDeregistrationHandler Note For more information about these handlers, see the subsequent document sections. Before you can use the API's endpoint to interact with Cryostat, you must ensure that the client, which is the plug-in's source, meets the following prerequisites: Accepts JSON responses. Can send HTTP requests to Cryostat. Can enter the correct Cryostat credentials in a POST request's Authorization header. Can receive GET and POST requests from Cryostat. Publishes information about discoverable targets in JSON by sending POST requests to Cryostat. If you need to register your own discovery plug-in program with Cryostat, you can disable the Cryostat built-in discovery mechanism. Disabling the built-in discovery mechanism helps to reduce duplicate definitions that might open in the Cryostat web console if both the plug-in and Cryostat can access the same target application information. Note If Cryostat detects that two similar definitions point to the same JVM, Cryostat stores any archived recordings in the same storage location that each definition accesses. You can set the following environment variable in your Red Hat OpenShift command-line console (CLI) to disable the Cryostat built-in discovery mechanism: You can also consider preserving the setting for the Cryostat built-in discovery implementation and then complete one of the following actions: Create a program that attaches a service locator to the implementation. Modify a target application to send target information directly to the implementation. Note The previously listed actions are outside the scope of the Configuring advanced Cryostat configurations document. 1.2. DiscoveryRegistrationHandler The Pluggable Discovery API uses DisoveryRegistrationHandler to register a discovered plug-in with Cryostat. The handler manages GET and POST requests from the plug-in and Cryostat. If you do not register a plug-in with Cryostat, the plug-in cannot provide target information to Cryostat. When your discovery plug-in program sends a POST request to Cryostat for registration purposes, Cryostat reads the callback URL and sends a GET request to the plug-in. If the plug-in correctly responds to the GET request then Cryostat accepts the registration request by responding to the initial POST request. This process ensures that the plug-in is active and available to Cryostat. A failed request might indicate that the plug-in failed or went offline. In this case, the endpoint removes the plug-in's information from the database on Red Hat OpenShift. Note After the registration process, Cryostat sends regular POST requests to the plug-in to ensure that the plug-in is still running. You can also specify id and token elements in your GET or POST request. These elements are optional, but you can consider using them in situations where you want to reuse registration information for a plug-in that was previously registered with Cryostat. Cryostat creates a token for the registered plug-in, and this token contains expiry and authorization information. If a POST request contains valid id and token information, Cryostat can reuse the plug-in registration information and refreshes the token. If a request contains only an id element or a token element, you must re-register the plug-in with Cryostat. After Cryostat sends a POST request to the plug-in's callback component, the plug-in might send a POST request to Cryostat to refresh the plug-in's registration details. The plug-in must include its id and token information in its request. Cryostat can then use DiscoveryRegistrationHandler to refresh the plug-in's details. Cryostat sends a response that includes the updated token to the plug-in, and the plug-in can use this token for future requests to Cryostat. Note Cryostat can issue a POST request to the plug-in at regular intervals to remind the plug-in about re-registering with Cryostat by using the same id and token information. If the plug-in ignores this request, the token might expire and the plug-in must complete a full registration with Cryostat. Example of a POST request sent from an external plug-in { "realm": "my-plugin", "callback": "http://my-plugin.my-namespace.svc.local:1234/callback" } Example of a POST response that Cryostat sends to a plug-in { "data": { "result": { "id": "922dd4f4-9d7c-4ae2-8982-0903868226a6", "token": " <key_value> " } }, "meta": { "status": "Created", "type": "application/json" } 1.3. DiscoveryRegistrationCheckHandler The Pluggable Discovery API uses the DisoveryRegistrationCheckHandler handler to enable plug-ins to periodically check their own registration status on a Cryostat server instance. The DisoveryRegistrationCheckHandler handler manages GET requests from the plug-in to Cryostat. By using the handler, external plug-ins can periodically verify the Cryostat server instance to which they are registered is still active and recognizes the prior registration of the plug-in. Similar to how the Cryostat callback URL endpoint checks plug-in instances, where Cryostat reads the callback URL and sends a GET request to the plug-in, the DisoveryRegistrationCheckHandler handler works in the same way, but sends the request in the opposite direction. That is, the plug-in sends a GET request to the Cryostat server to check its registration status on the Cryostat server. If the request fails, for example, if an Unexpected 401 or Unexpected 404 error response is received, the plug-in can discard its existing registration information and attempt to register again. Example of a GET request sent from an external plug-in Example of a Cryostat response when the GET request check succeeds and Cryostat recognizes the current registration of the plug-in HTTP/1.1 200 OK content-encoding: gzip content-length: 86 content-type: application/json { "data": { "result": null }, "meta": { "mimeType": "JSON", "status": "OK" } } Example of a Cryostat response when the GET request check fails because Cryostat does not recognize the plug-in registration details HTTP/1.1 404 Not Found content-encoding: gzip content-length: 95 content-type: application/json { "data": { "reason": null }, "meta": { "status": "Not Found", "type": "text/plain" } } 1.4. DiscoveryGetHandler The DiscoveryGetHandler collates deployment schemas and opens these schemas in a hierarchical tree view, so that Cryostat can interact with any registered discoverable plug-ins to integrate with a specific deployment schema. When you create a deployment with at least one pod, and a service maps to the deployment or pod on Red Hat OpenShift, Red Hat OpenShift creates an endpoint object, /api/v2.2/discovery , for all combinations of the Pod IP address and the Service port. The DiscoveryGetHandler receives information from an endpoint GET request and then collates deployment schema information in JSON format. The following example demonstrates how the handler opens results in a hierarchical tree view in JSON format. In the example, the root of the tree is the UNIVERSE node. This node contains child Realm node types, which stem from Cryostat's built-in discovery mechanism and the plug-ins discovered by the Pluggable Discovery API. { "data": { "result": { "children": [ { "children": [], "labels": {}, "name": "Custom Targets", "nodeType": "Realm" }, { "children": [ { "labels": {}, "name": "service:jmx:rmi:///jndi/rmi://cryostat:9091/jmxrmi", "nodeType": "JVM", "target": { "alias": "io.cryostat.Cryostat", "annotations": { "cryostat": { "HOST": "cryostat", "JAVA_MAIN": "io.cryostat.Cryostat", "PORT": "9091", "REALM": "JDP" }, "platform": {} }, "connectUrl": "service:jmx:rmi:///jndi/rmi://cryostat:9091/jmxrmi", "labels": {} } } ], "labels": {}, "name": "JDP", "nodeType": "Realm" } ], "labels": {}, "name": "Universe", "nodeType": "Universe" } }, "meta": { "status": "OK", "type": "application/json" } } 1.5. DiscoveryPostHandler A plug-in registered with Cryostat sends a POST /api/v2.2/discovery/:id request to DiscoveryPostHandler . The id parameter of the request refers to the ID of the registered plug-in. This handler processes any subtrees associated with the plug-in and then maps a deployment schema. The Cryostat POST request defines a subtree REALM node, so that the plug-in handler can publish its child node types in the REALM node during the request process. The plug-in takes responsibility for providing the correct information for theCryostat REALM node and for placing node types in the correct subtree positions. The plug-in must provide a token with the POST request that it sends to the DiscoveryPostHandler , so that the plug-in can bypass the authorization header check that the plug-in previously passed. Note After a plug-in sends updates to Cryostat, Cryostat replaces the information that a plug-in has sent. Cryostat stores this information in a database. A plug-in must send a complete list or tree of discoverable targets to Cryostat on each request. Example of a plug-in's POST request that details important target application information [ { "labels": {}, "nodeType": "JVM", "name": "service:jmx:rmi:///jndi/rmi://myapp.svc.local:9091/jmxrmi", "nodeType": "JVM", "target": { "alias": "com.MyApp", "annotations": { "cryostat": {}, "platform": {} }, "connectUrl": "service:jmx:rmi:///jndi/rmi://myapp.svc.local:9091/jmxrmi", "labels": {} } } ] Example of Cryostat response to the plug-in's POST request { "data": { "result": null }, "meta": { "mimeType": "JSON", "status": "OK" } } 1.6. DiscoveryDeregistrationHandler If a plug-in registered with Cryostat as a plug-in , and then the plug-in needs to shutdown, the plug-in typically issues a request to un-register itself as a plug-in from Cryostat. Cryostat sends a DELETE /api/v2.2/discovery/:id?token=:token request to DiscoveryDeregistrationHandler that then unregisters the plug-in from Cryostat. Note Cryostat sends regular POST requests to the plug-in after the registration process to ensure that the plug-in is still running. If the plug-in does not respond to one of these requests, Cryostat starts the process of unregistering the plug-in. The handler removes both the plug-in's REALM subtree from the discovery schema and the plug-in's registration information from Cryostat. Example of DELETE /api/v2.2/discovery/:id?token=:token request that the handler processes { "data": { "result": "bcc0f3a6-dc48-402e-a3d6-9fbb63beff78" }, "meta": { "mimeType": "JSON", "status": "OK" } } 1.7. Error handler codes The Pluggable Discovery API returns messages when any of its handlers either completes a task or experiences issues when registering a plug-in with Cryostat. A handler can return any of the following message types when the handler attempts to register a plug-in with Cryostat based on GET and POST requests: 200 : The handler successfully completes the task. For example, DiscoveryDeregistrationHandler returns a message in JSON format with the unregistered plug-in defined in the id element of the message. 400 : JSON document structure is invalid or the id element was written in an invalid format. 401 : Plug-in did not pass the Authorization header step for registration. If the token expired or you unregistered the plug-in, the handler also returns this error message. 404 : Plug-in id element was not found. Plug-in might have failed a callback check. Consider re-registering the plug-in.

Commands:
CRYOSTAT_DISABLE_BUILTIN_DISCOVERY=true
realm: my-plugin
callback: http://my-plugin.my-namespace.svc.local:1234/callback
{ "data": { "result": { "id": "922dd4f4-9d7c-4ae2-8982-0903868226a6", "token": " <key_value> " } }, "meta": { "status": "Created", "type": "application/json" }
http -v https://my-cryostat.my-namespace.cluster.local:8181/api/v2.2/discovery/<plugin-registration-id>?token=<current-plugin-registration-token>
HTTP/1.1 200 OK content-encoding: gzip content-length: 86 content-type: application/json { "data": { "result": null }, "meta": { "mimeType": "JSON", "status": "OK" } }
HTTP/1.1 404 Not Found content-encoding: gzip content-length: 95 content-type: application/json { "data": { "reason": null }, "meta": { "status": "Not Found", "type": "text/plain" } }
{ "data": { "result": { "children": [ { "children": [], "labels": {}, "name": "Custom Targets", "nodeType": "Realm" }, { "children": [ { "labels": {}, "name": "service:jmx:rmi:///jndi/rmi://cryostat:9091/jmxrmi", "nodeType": "JVM", "target": { "alias": "io.cryostat.Cryostat", "annotations": { "cryostat": { "HOST": "cryostat", "JAVA_MAIN": "io.cryostat.Cryostat", "PORT": "9091", "REALM": "JDP" }, "platform": {} }, "connectUrl": "service:jmx:rmi:///jndi/rmi://cryostat:9091/jmxrmi", "labels": {} } } ], "labels": {}, "name": "JDP", "nodeType": "Realm" } ], "labels": {}, "name": "Universe", "nodeType": "Universe" } }, "meta": { "status": "OK", "type": "application/json" } }
[ { "labels": {}, "nodeType": "JVM", "name": "service:jmx:rmi:///jndi/rmi://myapp.svc.local:9091/jmxrmi", "nodeType": "JVM", "target": { "alias": "com.MyApp", "annotations": { "cryostat": {}, "platform": {} }, "connectUrl": "service:jmx:rmi:///jndi/rmi://myapp.svc.local:9091/jmxrmi", "labels": {} } } ]
{ "data": { "result": null }, "meta": { "mimeType": "JSON", "status": "OK" } }
{ "data": { "result": "bcc0f3a6-dc48-402e-a3d6-9fbb63beff78" }, "meta": { "mimeType": "JSON", "status": "OK" } }