Note
It is now possible to schedule a maximum run duration for your virtual devices to avoid unexpected charges. Please, refer to Genymotion SaaS users' guide for more details.
This article has been published on behalf and with the authorization of its author, Dragos Campean.
This article aims to offer a detailed explanation of how to schedule automatic shutdown of Genymotion SaaS instances which were started via gmsaas CLI Tool.
The problem
The timeout which is set in the admin interface does not apply to instances which are started via gmsaas instances start
as mentioned in the gmsaas documentation.
Without a feature like automatic shutdown, unforeseen costs might appear. A situation like this arises when the tear-down which should stop the virtual device is not performed correctly and the instance remains running. For example, if you had a CI workflow which starts/stops virtual devices but the workflow was for some reason canceled or the CI tool crashed after the instance was started but before it was stopped.
The described situation will lead to that particular instance remaining booted until someone notices it and manually shuts it down from the admin dashboard.
The Genymotion team is planning to implement automatic shutdown of virtual devices which were started via the gmsaas CLI in the future. Alas, as there currently is no out of the box solution for the problem, a small workaround is needed.
The solution (TL;DR)
The approach is quite simple actually, you need to:
- Save the timestamp in the instance name when created. The first step is just a temporary workaround. The boot time will be made available in the instance details response in future releases.
- Run a recurrent job on a cheap low end machine which stops virtual devices
if (currentTimestamp - emulatorBootTime > setThreshold) {
stopInstance(EMULATOR_UUID);
Javascript will be used to implement the solution but it can easily be adapted to any programming language.
The detailed solution
No matter what programming language you choose, the machine which runs the cleanup job needs to have the gmsaas CLI tools installed.
You can easily bundle all dependencies together in a docker image which is probably the fastest way to go. For this example, a custom image was used which just installs gmsaas CLI tools on top of the circleci/android:api-29-node base image.
Once you have the environment set up, you’re ready to create the cleanup script:
function cleanupRogueEmulators() {
const RESULTS = await getRunningInstances();
for (const emulator of RESULTS.instances) {
const EMULATOR_UUID = emulator.uuid;
const EMULATOR_BOOT_TIME = extractTimestamp(emulator.name);
if (getCurrentTimestampSeconds() - EMULATOR_BOOT_TIME > SECONDS_IN_HOUR) {
await stopInstance(EMULATOR_UUID)
.catch(async function () {
await stopInstance(EMULATOR_UUID);
})
.catch(function (error) {
console.warn('Stop instance command failed 2 times with:\n', error);
});
}
}
}
For this example we make use of Promises and async/await. The functions getRunningInstances() and stopInstance() leverage Node’s child_process.exec to execute shell commands:
function execute(command) {
return new Promise(function (resolve, reject) {
childProcess.exec(command, function (error, standardOutput, standardError) {
if (error) {
reject(error);
return;
}
if (standardError) {
reject(standardError);
return;
}
resolve(standardOutput);
});
});
}
function stopInstance(uuid) {
return execute(`gmsaas --format json instances stop ${uuid}`);
}
function getRunningInstances() {
return execute('gmsaas --format json instances list').then(function (output) {
return JSON.parse(output);
});
}
And that’s it! Now you simply set up a cron job at the desired interval to execute this small script via: node path/to/cleanupScript.js
This can easily be replicated with other programming languages.
Using python or groovy might even make the solution cleaner and easier to write. For python for example you can leverage the powerful OS module to run commands and the json module to parse the gmsaas CLI output. Groovy’s equivalent is the execute function and JSON slurper.