Unable to set location for jar files in node.js app

Not applicable

I have the following in my Node.js:

var __dirname = '/opt/../customjars';

var files = fs.readdirSync(__dirname);

the value I set in __dirname is ignored.

Below are the options I have tried and both failed. Option 1 is my preference. Option 1 works using trireme as a standalone but fails when I run in Apigee.

Option 1: I load in a custom directory and set __dirname and yet, the Apigee tries ignores the directory I have set and reads from a different default.

Option 2: When I try fs.readdir instead of fs.readdirSync, and put all jars in resources/node and I don't over ride __dirname so it can point to /organization/environment/api/ and I pace my jars under that directory, I get error:

I have jar.loading.allowed=true set in nodejs.properties.

My preference is Option 1. Can you find the resolution to Option 1

3 29 4,191
29 REPLIES 29

ok, i did a bit of playing around with it in the cloud..

so it looks like 'organization/environment/api' is kind of a temporary namespace your script is copied to.. so inorder to access your files bundled into your resources/node -- you will have to declare them explicitly in package.json

for eg, i have a simple script like this,

var fs = require('fs')
var express = require('express');
var app = express();
app.get('/', function(req, res) {
    console.log(fs.readdirSync(__dirname))
    res.send('Hello from Express');
});

app.listen(3000);

and i had put 2 files in the resources/node folder.. but I was only seeing the script file being executed in the console logs

Then i created a package.json that had something like this

{
    "name":"usergrid-node",
    "version":"0.0.0",
    "description":"Usergrid Proxy",
    "main":"server-usergrid.js",
    "dependencies": {
        "express":"3.x.x",
        "usergrid":"x.x.x",
        "argo":"x.x.x",
        "test":"file:./test.js"  
           
    }
}

where file:./test.js is my other file

When i deployed and ran again, both the files are now displayed in the console logs.

So the local files are copied to runtime workspace only when they are declared as dependencies in your package.json

Hope this helps!

Thanks,

I forgot to mention that placing the jar files which exceeds 15 mb in resources/node folder returns the error: "code" : "repository.cassandra.BundleSizeLimitExceeded", "message" : "Bundle size is greater than the 15 MB. Use large resources at organization/environment level"

Option 1 is the preference where I point to a custom directory containing my jar files. Please confirm how I can point to a custom directory different from the Apigee directory?

Thanks

Not applicable

Hi @Mike Harris , @vb878 ,

Before answering whether that is possible or not , want to add few details on what really happens in the backend

#1 As @mukundha@apigee.com mentioned the __dirname always points to organization/environment/api which is actually

/opt/apigee4/var/log/apigee/message-processor/work/o/$orgname/e/$envname/a/$apiProxy__1/node/organization/environment/api

where all your resources will be placed when ever you deploy/redeploy the apiproxy containing the resources .

#2 If you want to read any resources you can always read them as below

fs.readFile(__dirname + '/hack.txt','utf8',  function (err, data) {
 if (err)
   console.log('test data err'+ err);;
 console.log('test data'+data);
});  // should work with Sync aswell

#3 There are 3 ways you can read the resource files (jars in your case)

a) You can import them through UI b) Bundle them with the apiproxy (the size limit is 15MB ) but not sure if thats the best option. c) Upload the jars using the management API mentioned here

http://apigee.com/docs/management/apis/post/organizations/%7Borg_name%7D/apis/%7Bapi_name%7D/revisio...

#4 If you use the above API or create from UI , the resources are stored in Cassandra and they only get copied to message processors when you redeploy the apiproxy , make sure you do that whenever you add a resource and that is the reason why if you place jars directly under organization/environment/api will disappear after every redeploy .

#5 Coming to your Option 1 , I believe we can't load dir or files by providing the absolute path from the node.js in the apiproxy . If you directly run trireme script.js from the organization/environment/api , it will work but looks like at runtime it will not work .

This is my understanding so far , will explore more and let you know if I find anything different .

See if you can use #3 c) to upload the jars .

BTW @mukundha@apigee.com I think we don't need to add the resource files to package.json , probably you didnot redeploy and thats the reason you were unable to read all files under __dirname ?

Here is what I am tring to accomplish and the goal is trying to avoid class conflict between java libraries used in trireme and the api proxy. This code works locally in Trireme but fails to read from the custom dir when deployed in Apigee. How can I make this work in Apigee where it read in the custom dir below?

var __dirname = '/var/globaljars/customappjars';

var trireme = require('trireme-support');

var fs= require('fs');

var files = fs.readdirSync(__dirname);

var filelist_ = [];

for (var a in files){

var name = __dirname + '/' + files[a];

filelist_.push(name);

}

trireme.loadJars(filelist_);

The point of using load jars is to achieve class loader isolation like Mike has mentioned. Specially in our case we definitely have the jars files that sum up to more than 15mb. And manually copying the files to the api folder each time the api is deployed is tedious task. There is always a risk of missing a file or two.

We cannot place the jars in a path for APIGEE EDGE since that will beat the purpose of having class loader isolation , hence we have to place these in a directory which is not on the path for APIGEE Edge but load them for trireme using load jars so Node js components can work.

Hi @Vasudeva , I got what you saying but looks like placing the jars directly on Message processors and trying to read them using fs is not possible .

As @Ozan Seymen pointed out the link here http://apigee.com/docs/api-services/content/understanding-edge-support-nodejs-modules clearly says that using fs you can only read files that are placed in resources/node .

The problem is what ever path you specify it gets translated to full path

example

if you try to read a file copied at /opt/apigee4 , the process during runtime will look for the path

/opt/apigee4/var/log/apigee/message-processor/work/o/$orgname/e/$envname/a/$apiProxy__1/node/opt/apigee4

Looks like this is the file that does that https://github.com/apigee/trireme/blob/3b8a78596b46b711fb9f306b160ebc1168100203/kernel/src/main/java...

Can you see if you can use management API to upload the resources and then load from there ?

What path does management UI copy the files to? And is that path on the classpath for MP?

Hi @Vasudeva , if you load the jars from the node.js that should be enough in your case and it doesn't matter where the jars are copied as per the https://www.npmjs.com/package/trireme-support#jar-file-loading Let me know if I am missing any thing .

If the files are on the classpath for the MP then the MP would load the jar files into JVM which we are trying to avoid. Thats the reason why i asked that question.

Maruti, are u saying that with this approach, there will be no class conflict between the classes loaded in trireme and the classes used by processes outside of trireme?

Secondly, given that node.js serves as an enablement for back end services on the same MP, why are the node.js resources restricted to use Apigee dir?

If there is restriction to use Apigee dir and there will be no class conflict, is there a way to simplify process to add the jar files to the Apigee jar directory instead of the manual process via the UI?

@Vasudeva @Mike Harris ,

Yes you can place the jars under resources/node which is not in the classpath and when you do loadjars

Trireme will create a new classloader. (Each invocation of "loadJars" will create a new classloader.) The classloader will not be shared with any other invocations of "loadJar" within the same Node.js application or with any other Node.js application on the same JVM.

Instead of importing the jars from UI , you can use the management API .

Pls try with 1 or 2 jars and if you think that is what you wanted , you can load more jars .

@Maruti Chand

We tried uploading a jar file using the management UI you mentioned above. When using POST (curl), we are getting a 405 - method not allowed error. When trying a PUT, we get an invalid media type error (tried both application/java-archive and multipart/form-data). Can you please tell us which method and content-type we should be using for jar files?

@Maruti Chand can you tell the syntax that should be used for this? As Vicki mentioned above we get various errors with the curl options we have been trying.

@Mike Harris - please see here:

http://apigee.com/docs/api-services/content/understanding-edge-support-nodejs-modules

Filesystem access is restricted to the directory where the script was launched: the /resources/node directory. Node.js scripts may read and write files within this directory, for instance as a temporary scratch area, but there are no guarantees as to how long the files will persist.

So I don't think you can read/write to /var.

Not applicable

For our on premise (private cloud) solution I just put the jar files I needed for the jdbc-trireme node module in /opt/apigee4/data/apigee/custom_jars.

$ls -l /opt/apigee4/data/apigee/custom_jars
-rwxr-xr-x 1 apigee apigee 2714189 Mar 10 15:52 ojdbc6.jar
-rwxr-xr-x 1 apigee apigee    2455 Apr  2 16:37 tdgssconfig.jar
-rwxr-xr-x 1 apigee apigee  963400 Apr  2 16:37 terajdbc4.jar

No muss no fuss.

@Kurt Kanaskie , Are you able to read them using the fs in your script ?

@Maruti Chand, I haven't tried because I just need to have them in the classpath so trireme can find them.

this seems like a really simple solution to the problem.

@Kurt Kanaskie, @Dino The ask is different here , they want to load jars as mentioned here

https://www.npmjs.com/package/trireme-support and that can only be possible if you have them as resource files .

Yeah, works for us. I figured it out by looking at the classpath setting shown in a `ps -eaf` for the message processor. 🙂

@Kurt Kanaskie , Can you copy the sample code on how you are able to place the jars in custom_jars and load them in the script ?

@Maruti Chand, No because I don't load them explicitly, just put them in the classpath and the message-processor load them. I did have to restart the message processor though. Maybe this isn't the right approach.

Kurt,

we use the custom_jars solution in a different scenario. Here we are trying to get class loader isolation. So its important for us that the MP would not load the files into JVM but we would like to explicity load them using the trireme load jars.

Hi @Mike Harris,

Ok makes sense, I think you would still need to upload your jars as environment resources / organization level resources

For eg, I tried this to upload resource files at environment level

http://apigee.com/docs/management/apis/post/organizations/%7Borg_name%7D/environments/%7Benv_name%7D...

[eg params, name=test1.js&type=node]

I guess, you would upload the jars using this API first -- then you don need to include them in resources/node folder.

Then in your node script.. you can access it like,

fs.readdirSync(__dirname + '/../') -> for environment level resources

fs.readdirSync(__dirname + '/../../') -> for organization level resources

I tested this for js files, it works -- but have not checked for jar files..

Thanks,

See my response I posted to Maruti. I will repost

with your approach, will there be class conflict between the classes loaded in trireme and the classes used by processes outside of trireme?

Secondly, given that node.js serves as an enablement for back end services on the same MP, why are the node.js resources restricted to use Apigee dir?

If there is restriction to use Apigee dir and there will be no class conflict, is there a way to simplify process to add the jar files to the Apigee jar directory instead of the manual process via the UI?

Not applicable

Hi @Mike Harris thanks for the question. As @Maruti Chand recommends above, the right way to import those files is through the Resources file resource #3.c. However, I understand your concerns about importing each one of the files manually, which could lead to error- prone deployments. Enter Apigeetool. Apigeetool will handle importing of all these jars for you through independents API calls to the management API. The steps below are taken from one of my Apigee Tutorials, which will take you throughout the deployment of an API proxy by importing n jar files in a single shot.

I hope it helps!

If you want to take all this to the next level, take advantage by integrating a127, Apigeetool and Apigee Grunt!

a127-apigeetool

All a127 projects can be imported using apigeetool. Here's how:

Step 1

cd into the folder:

$ cd a127-apigeetool

Step 2 - Deploy the a127 Node.js App

Run apigeetool command to deploy this a127 Node.js App:

$ apigeetool deploynodeapp --username $ae_username --password $ae_password --organization testmyapi --api a127-with-apigeetool --environment test --directory . -m app.js -b /a127-with-apigeetool -U

Apigeetool can be download using NPM. Check it out here.

Step 3 - Test it with cURL

You can test it by calling this resource:

$ curl http://testmyapi-test.apigee.net/a127-with-apigeetool/hello\?name\=Diego
"Hello, Diego!"%

Uploading Large API Bundles

Apigeetool will separately upload files through the management API. So, you'll notice under API Proxy History how all resources are being imported a separated resource files, instead of a single zip api bundle.

alt text

Note that jar files and NPM module are uploaded independently by Apigeetool:

alt text

Hi @Mike Harris,

I have to admit- i have not tried this in Edge, but have used trireme-support earlier - in the context of loading custom extension node modules written in java. Is this what you were trying to do?

are you trying to override some of the core trireme libs or just providing extensions to the core?

loadJars create a new classloader - copying the text from documentation

"1) Check to see if all the JAR files are readable, and throw an exception otherwise. 2) Create a classloader that contains all the specified JARs. 3) Using the classloader from the previous step, look for classes that implement the "NodeModule" and "NodeScriptModule" interfaces, and make those modules available to the current script."

After 3, you could use the modules declared in your jars using require

===

I am not sure of the behavior if you try to load a node module which is already loaded by the core. For eg, if module A is already loaded, when you call loadJars and loadJars also contains a module named 'A'. 'I think' require('A'), would result in the new module loaded by loadJars, but not sure

Since its a new classloader, i don think there will be a conflict

==

"Secondly, given that node.js serves as an enablement for back end services on the same MP, why are the node.js resources restricted to use Apigee dir?"

Not sure if i understand this correctly, the platform itself is multi-tenant and restricting the resource dir serves like a namespace isolation for several apis running nodejs scritps both within an org and across orgs

When you create resources at org level, it will also be usable to all apis deployed to the org - you don need to copy these jars for all the APIs

Hope this helps

Not applicable

There are a lot of questions here...

One question that I recall being asked was how to upload a "resource" file to an organization.

The API is documented here:

http://apigee.com/docs/management/apis/post/organizations/%7Borg_name%7D/resourcefiles

It is a POST and the content type should be "application/octet-stream".

Example:

curl -i -n -H 'Content-Type: application/octet-stream' -X POST \
 "$mgmtserver/v1/o/$orgname/e/$envname/resourcefiles?type=java&name=$jarfile"\
 -d $jarfile