Introducing Engine SaaS

Posted on: 20 Jan, 2022 API Gams Engine Release News Examples

Last year we released our model deployment solution GAMS Engine One, which allows scheduling and running jobs on a central compute server via a REST API .

Engine One is the perfect add-on for a machine based GAMS license. It makes development and integration of complex modeling scenarios into IT environments much easier than before, and helps reduce development time and cost.

In the meantime, our developers took it up one notch, and we are very excited to present GAMS Engine SaaS, which transfers the principles and existing benefits of GAMS Engine One to the cloud. Based on Kubernetes, Engine SaaS makes use of the compute infrastructure offered by Amazon Web Services (AWS). As a consequence Engine SaaS has some major added benefits:

Horizontal Auto Scaling

Each GAMS job on Engine SaaS runs on it’s own virtual EC2 instance, or node in Kubernetes speak. In practical terms, AWS is able to provide an unlimited number of EC2 instances, so if you need to run one job, or 100 jobs in parallel, the infrastructure will scale automatically and without any complex configuration required on your part.

Instance Sizing

In addition to horizontal scaling, EC2 offers a wide range of instance types, both in terms of memory and CPU. We can therefore offer instances with as little as 16GB, up to a huge 4TB, and many sizes in between. The user can select a different instance type for each job, if that is required. We currently use the AWS z1d type for our smaller instances (up to 192GB), and the x1e types for everything above, up to 4TB.

Depending on your requirements, we can also add different instance types, for example if memory is not so important, but the number of cores is.

Zero Maintenance and High Reliability

We take care of operating the infrastructure for our customers, including keeping the software up to date. Our customers can focus on their end user application, and the optimization part “just works”, backed by our expertise and by the AWS infrastructure with it’s high reliability.

Simplified License Handling

IT admins have their own login to Engine SaaS, and can add an unlimited number of individual users. Each of those users will automatically inherit the same license the admin owns, so there is only one license to be aware of, and nothing the user needs to install.

A simple web user interface

The Engine SaaS web UI allows you to manage users, groups, namespaces, and jobs in a simple and intuitive way. This is great for getting started and getting a quick overview of what is happening in your account. You can also do all of those things directly using the Engine API , some of which I will demonstrate further down.

Pricing Considerations

If you have any questions regarding GAMS Engine, don’t hesitate to contact us at sales@gams.com . We are happy to schedule a demo for you and discuss your individual use case.

Among other things, we will then discuss with you your requirements in terms of compute hours and instance size requirements:

  • What are the typical memory requirements for your GAMS jobs?
  • How many hours each year do you expect to run those jobs?
  • Which solvers will you need?

With this information we will create a tailor made package offer for you. This offer will include an hourly quota on your preferred instance size, be it 32GB or 4TB. You will always have the option to run jobs on other instance sizes on a case by case basis, giving you total flexibility.

The only thing you need to know is that there is a multiplier attached to each instance size, starting with a factor of 1 for the smallest instance, and then going up to higher values with larger instances. The multiplier determines how fast you burn through your quota relative to “wall time”. The exact multipliers will depend on the solvers you choose to license, and the total annual size of your quota.

A Simple Example in Python

I will use a contrived example to show the bare minimum of what you can do with GAMS Engine, in order to give you an idea of how straight-forward it is to use (if you know some Python and understand the requests library). You will see that it takes only a few lines of code to get started!

Assume we have a user called jon_doe registered on Engine SaaS. This user has access to the namespace tests, and to two different instance sizes with 16GB and 32GB labelled ‘GAMS_z1d.large_A’ and ‘GAMS_z1d.xlarge_A’, respectively.

We will use the requests library and a Jupyter Notebook to explore the REST API, and submit a simple GAMS job and fetch the results.

Authentication

First we need to import the request library, and take care of authenticating our user:

import requests
from requests.auth import HTTPBasicAuth
import time


au = HTTPBasicAuth("john_doe","some_password")
url = "https://engine.gams.com/api"

You will have received your username and password in an email from our sales team after signing up for Engine Saas.

Making the First Request

We can then query the API to get information about the instances our user has access to. The results are available in json format. The value for cpu_request corresponds to the number of vCPUs available in each instance, and is slightly lower than the nominal value you will find on the AWS homepage. This is because the Engine software stack (in particular Kubernetes) requires some resources to work properly. The same is true for the memory_request values, which correspond to the available memory and also reserve a small proportion to Kubernetes. The workspace_request values show the amount of disk space (50 GB) available for each job. Finally, multiplier is the factor that determines how fast an instance type will consume the quota compared to wall time.

r = requests.get(url + '/usage/instances/john_doe', auth=au)
r.json()
{'instances_inherited_from': 'john_doe',
 'default_inherited_from': 'john_doe',
 'instances_available': [{'label': 'GAMS_z1d.large_A',
   'cpu_request': 1.8,
   'memory_request': 15070,
   'workspace_request': 50000,
   'node_selectors': [{'key': 'gams.com/instanceType', 'value': 'z1d.large'}],
   'tolerations': [],
   'multiplier': 1.0},
  {'label': 'GAMS_z1d.xlarge_A',
   'cpu_request': 3.8,
   'memory_request': 30710,
   'workspace_request': 50000,
   'node_selectors': [{'key': 'gams.com/instanceType', 'value': 'z1d.xlarge'}],
   'tolerations': [],
   'multiplier': 1.1}],
 'default_instance': {'label': 'GAMS_z1d.large_A',
  'cpu_request': 1.8,
  'memory_request': 15070,
  'workspace_request': 50000,
  'node_selectors': [{'key': 'gams.com/instanceType', 'value': 'z1d.large'}],
  'tolerations': [],
  'multiplier': 1.0}}

Submitting a Job

Lets move on and submit a GAMS job to Engine. We will use the trnsport model, which we have copied into the current directory, and which will need to be zipped before we can submit it. The zipped file is then used in a post request to the API. Also, we will tell Engine to use the GAMS_z1d.large_A instance type.

The response contains the job token, which we need to identify our job.

from zipfile import ZipFile

with ZipFile('model.zip','w') as zip:
            zip.write('trnsport.gms')

query_params = {
    'model': 'trnsport',
    'namespace': 'tests',
    'labels': 'instance=GAMS_z1d.large_A'
}



# Create dict with model zip file
job_files = {'model_data': open('model.zip','rb')}

       
r = requests.post(url + '/jobs/', params=query_params, files=job_files, auth=au)
token =  r.json()['token']

When you follow along this example, please make sure to adjust the value for namespace to the one that was sent to you via email by our sales team together with your user information. If you have been invited to Engine SaaS by someone else in your organization, you will have to request this information from them.

The job runs asynchronously in the background, and we could now add more jobs or do other things.

Getting Job Results

We have to give Engine SaaS approximately 2 minutes, which is the time it takes to spin up a fresh EC2 instance for our job. This is required only for the first job in a row of successive jobs, because freshly vacated instances will be re-used and will be available immediately.

Lets now check the status of our simple job, by sending a get request.

r = requests.get(url + '/jobs/' + token, auth=au)
r.json()
{'token': '842b85cd-d7e2-42dc-8268-cc25ed3d66ce',
 'model': 'trnsport',
 'is_temporary_model': True,
 'is_data_provided': False,
 'status': 10,
 'process_status': 0,
 'stdout_filename': 'log_stdout.txt',
 'namespace': 'tests',
 'stream_entries': [],
 'arguments': [],
 'submitted_at': '2022-01-13T15:46:32.749866+00:00',
 'finished_at': '2022-01-13T15:48:39.520392+00:00',
 'user': {'username': 'john_doe', 'deleted': False, 'old_username': None},
 'text_entries': [],
 'dep_tokens': [],
 'labels': {'cpu_request': 1.8,
  'memory_request': 15070,
  'workspace_request': 50000,
  'tolerations': [],
  'node_selectors': [{'key': 'gams.com/instanceType', 'value': 'z1d.large'}]},
 'result_exists': True}

Amongst other information, we can see that the result_exists field reports True, and that the process_status field reports a value of zero, which means the job did finish successfully. We can now download the results in the form of a zip file, by sending another get request. The content field of the return object contains the raw byte string representing the zip file, so we can just write the field to disk ‘as is’.

r = requests.get(url + '/jobs/' + token + '/result', auth=au)

file = open('results.zip','wb')
file.write(r.content)
file.close()

By default, the zip file contains the GAMS log for the run, a copy of the model file, and the lst file. Here is a section of the log that shows we did indeed successfully solve the model on GAMS Engine:

Iteration      Dual Objective            In Variable           Out Variable  
     1              73.125000    x(seattle,new-york) demand(new-york) slack  
     2             119.025000     x(seattle,chicago)  demand(chicago) slack  
     3             153.675000    x(san-diego,topeka)   demand(topeka) slack  
     4             153.675000  x(san-diego,new-york)  supply(seattle) slack  

--- LP status (1): optimal.
--- Cplex Time: 0.11sec (det. 0.01 ticks)


Optimal solution found
Objective:          153.675000

Some Remarks on Security

Our development team takes software security serious, especially given the fact that GAMS Engine runs in the cloud. This is why we frequently update the underlying software components to include bug-fixes as soon as they come out.

Below is a high level summary of some of the design choices that will keep your data safe.

General best practices for web applications

First up, of course we make use of industry standard encryption methods. This means that all data is transported through the internet TLS encrypted. Your user credentials are stored salted and hashed in the Engine user database (we use the PBKDF2 algorithm for that). Data is AES-256 encrypted at rest in our database, and backups are encrypted as well. Before releasing any updates to GAMS Engine, the code changes are peer reviewed by our team, and additionally scanned for known vulnerabilities and tested in our CI pipeline.

Engine specific considerations

We use AWS EKS , which is a production grade Kubernetes cluster that is professionally managed by AWS, to run our Engine infrastructure. This ensures that data from our different users is properly encapsulated, and user A cannot see under any circumstances what user B is doing. Each job that is scheduled on GAMS Engine is run in a new, isolated, containerized environment, which has never been used before to solve any model. You can however specifically instruct Engine to make job B dependent on job A, in which case the data from job A will be accessible to job B.

On top of that, our namespace structure allows you to choose who can access which model, and what they can do with the model. We follow the standard permission system present on unix operating systems (read, write, execute).

With Quotas, you can limit usage of individual users in your organization to prevent people from burning through your compute volume too quickly. Quotas can be set for the number of simultaneous jobs started, for the number of hours available to a user, for the types of instances a user is allowed to use, and also for the amount of storage available.

If this overview has piqued your interest, do not hesitate to contact us. We are happy to discuss your requirements and give you more information in a personal call.