In the blog post, we will take a look at the VAC REST API integration. First of all, all the code specifically for VAC can be found on VeeamHub in app/backend/restIntegration-vac.js. You will see that this file is only 100 lines big, but still packs all the functionality to talk to the REST API. For this blog post, I'm using slightly altered code which can be found here
Once you install node-fetch, you can start node and execute the JS code to test the module. I have to say that the first time I saw the Fetch code, it was quite confusing for me. Fetch uses a "pretty new" JavaScript feature called a Promise. Promises are you used when you want to execute some code asynchronously, and when it's done, it will run or the resolve code (everything went OK) or the reject code. This is pretty weird, but it means that Fetch doesn't block the main thread. This also means that if you try to fetch an URL that doesn't exists, at first it looks like nothing is happening, but only after the timeout has happened, your reject code will be ran. So be patient, and wait for the error if it looks like nothing happened
In the app, the GUI basically passes a success function (for example, moving to the next screen) and a fail function (alerting the user that something went wrong). For this example, we will just print some messages to the console.
What is also pretty weird, is the statement ".then(function(res) { return res.text() })". Basically this part creates a new Promise, that will succeed if we can parse the body content, and if yes, run the next then clause. This seems pretty trivial when you are parsing text, but for example, if you try to parse JSON, errors might occur while parsing. So this chains the promises. First you get the "Promise" that the webpage was downloaded or the fail code is ran. Then you get that the "Promise" that the body is parsed or otherwise the fail code while be ran.
In the app I use the short hand arrow functions which are also quite "new" in JS. Basically it allows you to create anonymous function but in a more compact way. Compare "function(res) { return res.text() }" which roughly compares to "res => res.text()"
If this sounds like Chinese to you, that's fine, then focus mostly on what is between the fetch function, and the second then. The result is quite clear, we get "http://dewin.me/vcoffee/jobs.json" and then print out the body to the console.
Now the nice thing about Fetch is that if we just alter the first then, and change text() to json(), fetch will parse the JSON body to a JavaScript object. So now the second then, does not receive plain text, but real objects if parsing was successful.
The maincontext object is something that is created in the GUI and then passed to the backend. Here we are just creating it on the command line. In this code, there is a first fetch to "/v2/jobs", you can ignore this one. Basically it is some probe code to see that the server is really a VAC server before trying to authenticate. More importantly is the second fetch.
Here we do a fetch of apiurl+"/token". But instead of just doing a plain download of the webpage, we are altering the request a bit. First of all, we need to use the "POST" method. Secondly, we add some headers to the request. Content-type should be set to "application/json" and we add the "Authorization" set to "Bearer" as specified by the API documentation
Finally in the body of the request, we tell the API that we want to authenticate with a password (grand_type), and supply the username and password. This is supplied as an URL encoded format, and honestly, the way the code is written in this example is not that clean. It would have been safer to use something that takes a JavaScript Object and parse it to an URL encoded format.
If all went well, you should get some JSON. Again we can parse the text and then we can extract the access_token. The code prints out the access code to the console and set's it to maincontext.sessionid for later reuse.
I don't want to go too much into detail but your access_token expires every hour. In the same reply, there is also a refresh_token you can reuse, to get a new access_token before it expires. I'm not going to cover this, but if you want your application to stay logged in, you would need to have some timed code that runs every, let's say, 45 mins, and that renews your token. In this case, your grant_type is not password but refresh but this also covered in the documentation. Finally, once you are done, you should also log out. Again, we will not cover this in this blog post.
Step 1 : Understanding fetch (JavaScript Specific)
If you want, you can play with this code by installing NodeJS (in this demo v6.11). The demo is dependent on one framework called fetch that is automatically included in NativeScript (the framework used to build the app), but is not installed by default in NodeJS. So the first thing we need to do is install fetch and check if it works. Installing fetch can be done with "npm install node-fetch" once you have installed NodeJSOnce you install node-fetch, you can start node and execute the JS code to test the module. I have to say that the first time I saw the Fetch code, it was quite confusing for me. Fetch uses a "pretty new" JavaScript feature called a Promise. Promises are you used when you want to execute some code asynchronously, and when it's done, it will run or the resolve code (everything went OK) or the reject code. This is pretty weird, but it means that Fetch doesn't block the main thread. This also means that if you try to fetch an URL that doesn't exists, at first it looks like nothing is happening, but only after the timeout has happened, your reject code will be ran. So be patient, and wait for the error if it looks like nothing happened
In the app, the GUI basically passes a success function (for example, moving to the next screen) and a fail function (alerting the user that something went wrong). For this example, we will just print some messages to the console.
What is also pretty weird, is the statement ".then(function(res) { return res.text() })". Basically this part creates a new Promise, that will succeed if we can parse the body content, and if yes, run the next then clause. This seems pretty trivial when you are parsing text, but for example, if you try to parse JSON, errors might occur while parsing. So this chains the promises. First you get the "Promise" that the webpage was downloaded or the fail code is ran. Then you get that the "Promise" that the body is parsed or otherwise the fail code while be ran.
In the app I use the short hand arrow functions which are also quite "new" in JS. Basically it allows you to create anonymous function but in a more compact way. Compare "function(res) { return res.text() }" which roughly compares to "res => res.text()"
If this sounds like Chinese to you, that's fine, then focus mostly on what is between the fetch function, and the second then. The result is quite clear, we get "http://dewin.me/vcoffee/jobs.json" and then print out the body to the console.
Now the nice thing about Fetch is that if we just alter the first then, and change text() to json(), fetch will parse the JSON body to a JavaScript object. So now the second then, does not receive plain text, but real objects if parsing was successful.
Step 2 : Logging into VAC, the real deal
OK, so let's look at the VAC Rest API. To login, the GUI passes an object with the URL, Username and Password. The goal is to login and get a token. When we get a token, we can are basically logged in, and next time we want to fire a request, we can just use the token, instead of sending a username and password. The theory is defined in the VAC REST API documentation, but let's put this into practiceThe maincontext object is something that is created in the GUI and then passed to the backend. Here we are just creating it on the command line. In this code, there is a first fetch to "/v2/jobs", you can ignore this one. Basically it is some probe code to see that the server is really a VAC server before trying to authenticate. More importantly is the second fetch.
Here we do a fetch of apiurl+"/token". But instead of just doing a plain download of the webpage, we are altering the request a bit. First of all, we need to use the "POST" method. Secondly, we add some headers to the request. Content-type should be set to "application/json" and we add the "Authorization" set to "Bearer" as specified by the API documentation
Finally in the body of the request, we tell the API that we want to authenticate with a password (grand_type), and supply the username and password. This is supplied as an URL encoded format, and honestly, the way the code is written in this example is not that clean. It would have been safer to use something that takes a JavaScript Object and parse it to an URL encoded format.
If all went well, you should get some JSON. Again we can parse the text and then we can extract the access_token. The code prints out the access code to the console and set's it to maincontext.sessionid for later reuse.
I don't want to go too much into detail but your access_token expires every hour. In the same reply, there is also a refresh_token you can reuse, to get a new access_token before it expires. I'm not going to cover this, but if you want your application to stay logged in, you would need to have some timed code that runs every, let's say, 45 mins, and that renews your token. In this case, your grant_type is not password but refresh but this also covered in the documentation. Finally, once you are done, you should also log out. Again, we will not cover this in this blog post.
Step 3 : Getting the jobs
Now that we are logged in, we can actually do some work. Every time we need to do a request, we should specify that we are using JSON, and also that we are authenticating via the Bearer access token. Since we need to do this for every request, let's make a functions, that just make the headers object based on the access_token, so we can reuse it over and over again. Basically we need to set the "Authorization" header to "Bearer "+access_token. What is really important is that if you are building this code in another language, there is a space between the word "Bearer" and the access_token.
Then, let's get a list of the jobs. Following the documentation, we should be able to get this by executing a "GET" to /v2/Jobs (watch out for caps). However, since other types like "Tenants" or "BackupServers" are requested in exactly the same way, just a different URI ending, we will just make a generic "getList" function, and then just a small wrapper function that use the "getList" function.
Here is where the VAC API in combination with JavaScript really shines in my humble opinion. We get the page, using the authentication header, we parse the JSON text to objects and the we run success. In success, we get an array of jobs which are passed. We can select the first element by using jobs[0], and use it properties to print the name and the id. For me this code is quite compact and extremely easy to read.
Step 4 : Starting a job
The last step is to start the job. Be careful, this code will actually start the job! Following the API documentation, we need to a "POST" to /v2/Jobs//action . For example, in the previous screenshot, we can see that "Replication Job 1" has an id of 16, so if we want to start it, we need to a POST to /v2/Jobs/16/action.
Now we also have to specify the request body (which is also JSON). To start the job, the documentation states we need to send '{"start":"null"}'. Again, this scenario, can be made generic for other actions like start, stop, disable, etc. by just replacing the word start. So let's build another more generic function called actionJob. We will use the jobaction parameter to modify the JSON text we are sending. Again, this is not the most clean example.
You should actually take a JS object, and stringify it to JSON. And in fact, I should update the code to something like "JSON.stringify({[action]:null})", but this is just to keep the example as simple as possible
You should actually take a JS object, and stringify it to JSON. And in fact, I should update the code to something like "JSON.stringify({[action]:null})", but this is just to keep the example as simple as possible
In the success function, we can now use the console.dir function to dump the content of the reply object. If the job started succesfully, you will get a reply like "Action Started"
Finally: where to go from here
Well, first of all, the API documentation is really good. I'm pretty sure that by modifying this sample code, you can get a long way. I hope it also shows that there is no really rocket science involved, especially if you want to use the API to dump the status data to another system for example. I could imagine that you can use this sample code, to get the data out of VAC, and automatically create tickets for failed jobs in your Helpdesk system.