Forum Discussion

rthomasgww's avatar
2 years ago
Solved

GroovyScriptHelper issues - method missing

I’m trying to build a datasource for our health-check API that returns a system name, status, and response time. I believe I need to set up active discovery to map the system names as the instance keys, then query the API again to get that data. Issue I’m running into is that the GSH methods intended to handle the instancing won’t load, giving me the below error. The actual script does run successfully, it’s just the instancing bit that’s giving me trouble.

MissingMethodException: No signature of method: Script1.withBinding() is applicable for argument types: (groovy.lang.Binding) values: [groovy.lang.Binding@781fc692]
Possible solutions: setBinding(groovy.lang.Binding), getBinding()
com.logicmonitor.common.sse.utils.exception.ScriptExecutingFailedException: MissingMethodException: No signature of method: Script1.withBinding() is applicable for argument types: (groovy.lang.Binding) values: [groovy.lang.Binding@781fc692]
Possible solutions: setBinding(groovy.lang.Binding), getBinding()
at com.logicmonitor.common.sse.utils.GroovyScriptHelper.execute(GroovyScriptHelper.java:197)
at jdk.internal.reflect.GeneratedMethodAccessor17.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.logicmonitor.common.sse.executor.impl.GroovyScriptHelperWrapper.execute(GroovyScriptHelperWrapper.java:86)
at com.logicmonitor.common.sse.executor.GroovyScriptExecutor.execute(GroovyScriptExecutor.java:75)
at com.logicmonitor.common.sse.SSEScriptExecutor$ScriptExecutingTask.call(SSEScriptExecutor.java:212)
at com.logicmonitor.common.sse.SSEScriptExecutor$ScriptExecutingTask.call(SSEScriptExecutor.java:155)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
elapsed time: 0 seconds

Code-block I’m using for discovery:

import com.santaba.agent.groovyapi.http.*; 
import groovy.json.JsonBuilder;
import groovy.json.JsonSlurper;
import com.logicmonitor.common.sse.utils.GroovyScriptHelper as GSH
import com.logicmonitor.mod.Snippets

def modLoader = GSH.getInstance()._getScript("Snippets", Snippets.getLoader()).withBinding(getBinding())
def lmEmit = modLoader.load("lm.emit", "0") // Load LM emit module

def api_key = hostProps.get("boomi.client_id");
def api_secret = hostProps.get("boomi.client_secret");
def api_scope = hostProps.get("boomi.api_scope")
if (!api_key && ! api_secret) {
println "boomi.client_id and boomi.client_secret must be defined.";
return 1;
}

// used to get an OAuth2 token
def api_auth_host = "login.microsoftonline.com";
def api_tenant_id = hostProps.get("boomi.azure_tenant_id");
def api_auth_url = "https://login.microsoftonline.com/${api_tenant_id}/oauth2/v2.0/token"

// define API path to get info from
def api_base_host = hostProps.get("system.hostname");
def api_base_url = "https://${api_base_host}/healthcheck/v2";
def api_x_apikey = hostProps.get("boomi.apixkey");
// get OAuth2 token
def auth_token;
try {
def conn = HTTP.open(api_auth_host, 443);
def headers = [
"Content-Type": "application/x-www-form-urlencoded"
];
def postdata = "grant_type=client_credentials&client_id=${api_key}&client_secret=${api_secret}&scope=${api_scope}"
def response = HTTPPost(api_auth_host, api_auth_url, postdata, headers);
auth_token = ParseJsonResponse(response);
}
catch (Exception e) {
println "OAuth2 Exception: " + e.message;
return 1;
}

// get data from API using token
def systems;
try {
def conn = HTTP.open(api_auth_host, 443);
def headers = [
"Accept": "application/json",
"Authorization": "${auth_token.token_type} ${auth_token.access_token}",
"X-APIKey": api_x_apikey
];
def response = HTTPGet(api_base_host, api_base_url + "/oncloud", headers);
systems = ParseJsonResponse(response);
}
catch (Exception e) {
println "API Exception from oncloud: " + e.message;
return 1;
}

systems?.each {
val ->
def wildvalue = val.systemName
def wildalias = wildvalue
def description = val.systemName
lmEmit.instance(wildvalue, wildalias, description, [:])
}

try {
def conn = HTTP.open(api_auth_host, 443);
def headers = [
"Accept": "application/json",
"Authorization": "${auth_token.token_type} ${auth_token.access_token}",
"X-APIKey": api_x_apikey
];
def response = HTTPGet(api_base_host, api_base_url + "/onpremise", headers);
systems = ParseJsonResponse(response);
}
catch (Exception e) {
println "API Exception from onpremise: " + e.message;
return 1;
}

systems?.each {
val ->
def wildvalue = val.systemName
def wildalias = wildvalue
def description = val.systemName
lmEmit.instance(wildvalue, wildalias, description, [:])
}

return 0;

// Functions

// Convenience method for making HTTP POST requests to REST API
def HTTPPost(host, url, post_data, request_headers, user = null, pass = null) {
// Create an http client object
def conn = HTTP.open(host, 443)

// If we have credentials add them to the headers
if (user && pass) {
conn.setAuthentication(user, pass)
}

// Query the endpoint
def response = conn.post(url, post_data, request_headers)
def response_code = conn.getStatusCode()
def response_body = conn.getResponseBody()

// Close the connection
conn.close();

return
;
}

// Convenience method for making HTTP POST requests to REST API
def HTTPGet(host, url, request_headers, user = null, pass = null) {
// Create an http client object
def conn = HTTP.open(host, 443)

// If we have credentials add them to the headers
if (user && pass) {
conn.setAuthentication(user, pass)
}

// Query the endpoint
def response = conn.get(url, request_headers)
def response_code = conn.getStatusCode()
def response_body = conn.getResponseBody()

// Close the connection
conn.close();

return
;
}

// Convenience method for parsing REST responses.
def ParseJsonResponse(response) {
// Successful response?
if (response.code =~ /^2\d\d/) {
// yes, parse out a JSON object
return new JsonSlurper()?.parseText(response.body);
} else {
// No, error out
throw new Exception("There was an error fetching from the API: ${response.code}");
}
}
  • I’d return a different value on lines 15, 40, 57, & 80. Just so you can tell what the error was just by the return code. You can have the return code go into a datapoint and even set a threshold for non-zero return code. Always returning 1 negates the value of return codes.

    Is there a particular reason you’re trying to use LM’s undocumented GSH stuff? It’s overkill. On lines 65 and 88, just do:

    println("${wildvalue}##${wildalias}##${description}")

    No muss, no fuss.

2 Replies

  • Honestly I was just refactoring a datasource that looked like it would do what I needed it to and that’s how they were using it. All good now though, thanks!

  • I’d return a different value on lines 15, 40, 57, & 80. Just so you can tell what the error was just by the return code. You can have the return code go into a datapoint and even set a threshold for non-zero return code. Always returning 1 negates the value of return codes.

    Is there a particular reason you’re trying to use LM’s undocumented GSH stuff? It’s overkill. On lines 65 and 88, just do:

    println("${wildvalue}##${wildalias}##${description}")

    No muss, no fuss.