Skip to content

Commit 01864f6

Browse files
committed
Major commit supporting the installation/setup of Atlas.ai models and the downloading of their results
1 parent b6115c4 commit 01864f6

File tree

9 files changed

+186
-7
lines changed

9 files changed

+186
-7
lines changed

Atlas-Integration/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Setting up Atlas.ai
2+
3+
You should ensure that the `DATA_PATH` in `config.ini` matches whatever you set as the `output_directory` when you run `atlas_data.py`.
4+
5+
Also, ensure that `URL` in `config.ini` matches your development or production server (e.g. `http://localhost:8080` or `https://model-service.worldmodelers.com`).

Atlas-Integration/atlas_data.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
import boto3
22
import os
33
import argparse
4+
import redis
5+
import configparser
6+
from hashlib import sha256
7+
import json
8+
9+
config = configparser.ConfigParser()
10+
config.read('../REST-Server/config.ini')
11+
12+
r = redis.Redis(host=config['REDIS']['HOST'],
13+
port=config['REDIS']['PORT'],
14+
db=config['REDIS']['DB'])
415

516
profile = "wmuser"
617
bucket_name = "world-modelers"
18+
models = ['consumption_model','asset_wealth_model']
19+
formats = ['tif','geojson']
20+
atlas_lookup = {
21+
'asset_wealth_model':{
22+
'geojson':'november_tests_asset_wealth.geojson',
23+
'tif':'november_tests_atlasai_assetwealth_allyears_2km.tif'
24+
},
25+
'consumption_model':{
26+
'geojson':'november_tests_consumption.geojson',
27+
'tif':'november_tests_atlasai_consumption_allyears_2km.tif'
28+
}
29+
}
730

831
session = boto3.Session(profile_name=profile)
932

@@ -14,9 +37,9 @@
1437

1538
if __name__ == "__main__":
1639
parser = argparse.ArgumentParser(description='Atlas.a Data Downloader')
17-
parser.add_argument('output_directory', type=str,
40+
parser.add_argument('--output_directory', type=str,
1841
help='Where should Atlas.ai data be downloaded?')
19-
parser.add_argument('key_file', type=str,
42+
parser.add_argument('--key_file', type=str,
2043
help='A text file containing the S3 keys for the Atlas files.')
2144

2245
args = parser.parse_args()
@@ -28,7 +51,40 @@
2851
# make Atlas.ai data directory
2952
if not os.path.exists(output_directory):
3053
os.mkdir(output_directory)
54+
print(f"Created {output_directory} directory.\n")
3155

56+
# download files
57+
print("Downloading data files...")
3258
for k in keys:
3359
file_name = k.split('/')[-1]
3460
bucket.download_file(k, f"{output_directory}/{file_name}")
61+
print("...download completed!\n")
62+
63+
# update redis with model run
64+
print("Updating Redis...")
65+
for model_name in models:
66+
for f in formats:
67+
model_config = {
68+
"config": {"format": f},
69+
"name": model_name
70+
}
71+
72+
run_id = sha256(json.dumps(model_config).encode('utf-8')).hexdigest()
73+
74+
# Add to model set in Redis
75+
r.sadd(model_name, run_id)
76+
77+
run_obj = {'status': 'SUCCESS',
78+
'name': model_name,
79+
'config': model_config["config"],
80+
}
81+
82+
run_obj['config']['run_id'] = run_id
83+
run_obj['config'] = json.dumps(run_obj['config'])
84+
85+
r.hmset(run_id, run_obj)
86+
87+
# rename files to correspond with model run_id
88+
file_name = atlas_lookup[model_name][f]
89+
os.rename(f"{output_directory}/{file_name}", f"{output_directory}/{run_id}.{f}")
90+
print("...Redis update completed!")

Atlas-Integration/maas_install.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# run Atlas setup script from this directory
2+
python atlas_data.py --output_directory=/home/ubuntu/data --key_file=atlas_s3_keys.txt

REST-Server/config.ini

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
[APP]
2+
DATA_PATH = /home/ubuntu/data
3+
URL = https://model-service.worldmodelers.com
4+
15
[S3]
26
BUCKET = world-modelers
37

@@ -20,4 +24,4 @@ OUTPUT_PATH = /home/ubuntu/dssat
2024

2125
[MALNUTRITION]
2226
INSTALL_PATH = /home/ubuntu/ModelService/Kimetrica-Integration/darpa
23-
S3_CRED_PATH = /home/ubuntu/.aws
27+
S3_CRED_PATH = /home/ubuntu/.aws

REST-Server/openapi_server/controllers/execution_controller.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import connexion
22
import six
3+
import flask
34

45
from openapi_server.models.model_config import ModelConfig # noqa: E501
56
from openapi_server.models.run_results import RunResults # noqa: E501
@@ -17,8 +18,12 @@
1718
import boto3
1819
import botocore
1920
import logging
21+
import os
2022
logging.basicConfig(level=logging.INFO)
2123

24+
data_file_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data')
25+
print(data_file_dir)
26+
2227
config = configparser.ConfigParser()
2328
config.read('config.ini')
2429

@@ -29,7 +34,15 @@
2934
client = docker.from_env()
3035
containers = client.containers
3136

32-
available_models = ['population_model', 'malnutrition_model', 'fsc', 'dssat']
37+
data_path = config['APP']['DATA_PATH']
38+
site_url = config['APP']['URL']
39+
40+
available_models = ['population_model',
41+
'malnutrition_model',
42+
'fsc',
43+
'dssat',
44+
'asset_wealth_model',
45+
'consumption_model']
3346

3447
def list_runs_model_name_get(ModelName): # noqa: E501
3548
"""Obtain a list of runs for a given model
@@ -131,16 +144,27 @@ def run_results_run_idget(RunID): # noqa: E501
131144
if not r.exists(RunID):
132145
return 'Run Not Found', 404, {'x-error': 'not found'}
133146

134-
update_run_status(RunID)
135147
run = r.hgetall(RunID)
136148
status = run[b'status'].decode('utf-8')
149+
150+
# Only update the run status if the status is still PENDING
151+
if status == 'PENDING':
152+
update_run_status(RunID)
153+
run = r.hgetall(RunID)
154+
status = run[b'status'].decode('utf-8')
155+
137156
config = json.loads(run[b'config'].decode('utf-8'))
138157
model_name = run[b'name'].decode('utf-8')
139158
output = ''
140159
output_config = {'config': config, 'name': model_name}
141160
results = {'status': status, 'config': output_config, 'output': output}
142161

143-
if status == 'SUCCESS':
162+
if model_name in ['consumption_model', 'asset_wealth_model']:
163+
# special handler for Atlas.ai models
164+
URI = f"{url}/result_file/{RunID}.{config['format']}"
165+
results['output'] = URI
166+
return results
167+
elif status == 'SUCCESS':
144168
bucket = run[b'bucket'].decode('utf-8')
145169
key = run[b'key'].decode('utf-8')
146170
URI = f"https://s3.amazonaws.com/{bucket}/{key}"
@@ -185,6 +209,28 @@ def available_results_get(): # noqa: E501
185209
return results
186210

187211

212+
def result_file_result_file_name_get(ResultFileName): # noqa: E501
213+
"""Obtain the result file for a given model run.
214+
215+
Submit a `ResultFileName` and receive model run result file. # noqa: E501
216+
217+
:param result_file_name: A file name of a result file.
218+
:type result_file_name: str
219+
220+
:rtype: None
221+
"""
222+
223+
# If the file does not exist:
224+
if not os.path.exists(f"{data_path}/{ResultFileName}"):
225+
return 'Result File Not Found', 404, {'x-error': 'not found'}
226+
227+
# Otherwise, serve the file:
228+
else:
229+
response = flask.send_from_directory(data_path, ResultFileName)
230+
response.direct_passthrough = False
231+
return response
232+
233+
188234
def update_run_status(RunID):
189235
if not r.exists(RunID):
190236
return 'Run Not Found', 404, {'x-error': 'not found'}

REST-Server/openapi_server/openapi/openapi.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,26 @@ paths:
268268
tags:
269269
- execution
270270
x-openapi-router-controller: openapi_server.controllers.execution_controller
271+
/result_file/{ResultFileName}:
272+
get:
273+
description: Submit a `ResultFileName` and receive model run result file.
274+
operationId: result_file_result_file_name_get
275+
parameters:
276+
- description: A file name of a result file.
277+
explode: false
278+
in: path
279+
name: ResultFileName
280+
required: true
281+
schema:
282+
$ref: '#/components/schemas/ResultFileName'
283+
style: simple
284+
responses:
285+
200:
286+
description: A result file
287+
summary: Obtain the result file for a given model run.
288+
tags:
289+
- execution
290+
x-openapi-router-controller: openapi_server.controllers.execution_controller
271291
components:
272292
schemas:
273293
ModelName:
@@ -604,6 +624,10 @@ components:
604624
description: ID associated with a model run. This is the SHA256 hash of the ModelConfig sent to the /run_model endpoint
605625
example: 3A3B3E0AE57AD4A7EF658C1F7832774F55E403F01FDF44B68B355EC4587D7A04
606626
type: string
627+
ResultFileName:
628+
description: The name of a model run result file.
629+
example: 3A3B3E0AE57AD4A7EF658C1F7832774F55E403F01FDF44B68B355EC4587D7A04.csv
630+
type: string
607631
RunStatus:
608632
description: Status information about a model run.
609633
enum:

REST-Server/openapi_server/test/test_execution_controller.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
class TestExecutionController(BaseTestCase):
1515
"""ExecutionController integration test stubs"""
1616

17+
def test_available_results_get(self):
18+
"""Test case for available_results_get
19+
20+
Obtain a list of run results
21+
"""
22+
response = self.client.open(
23+
'/available_results',
24+
method='GET')
25+
self.assert200(response,
26+
'Response body is : ' + response.data.decode('utf-8'))
27+
1728
def test_list_runs_model_name_get(self):
1829
"""Test case for list_runs_model_name_get
1930
@@ -25,6 +36,17 @@ def test_list_runs_model_name_get(self):
2536
self.assert200(response,
2637
'Response body is : ' + response.data.decode('utf-8'))
2738

39+
def test_result_file_run_idget(self):
40+
"""Test case for result_file_run_idget
41+
42+
Obtain the result file for a given model run.
43+
"""
44+
response = self.client.open(
45+
'/result_file/{RunID}'.format(run_id='run_id_example'),
46+
method='GET')
47+
self.assert200(response,
48+
'Response body is : ' + response.data.decode('utf-8'))
49+
2850
def test_run_model_post(self):
2951
"""Test case for run_model_post
3052

REST-Server/openapi_server/test/test_exploration_controller.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
from flask import json
66
from six import BytesIO
77

8+
from openapi_server.models.error import Error # noqa: E501
89
from openapi_server.models.io_file import IOFile # noqa: E501
910
from openapi_server.models.io_request import IORequest # noqa: E501
1011
from openapi_server.models.model import Model # noqa: E501
1112
from openapi_server.models.model_config import ModelConfig # noqa: E501
1213
from openapi_server.models.parameter import Parameter # noqa: E501
13-
from openapi_server.models.search_result import SearchResult # noqa: E501
1414
from openapi_server.models.unknownbasetype import UNKNOWN_BASE_TYPE # noqa: E501
1515
from openapi_server.test import BaseTestCase
1616

model_service_api.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,22 @@ paths:
236236
type: "array"
237237
items:
238238
$ref: '#/components/schemas/RunResults'
239+
/result_file/{ResultFileName}:
240+
get:
241+
tags:
242+
- "execution"
243+
summary: "Obtain the result file for a given model run."
244+
description: "Submit a `ResultFileName` and receive model run result file."
245+
parameters:
246+
- in: path
247+
name: ResultFileName
248+
description: "A file name of a result file."
249+
required: true
250+
schema:
251+
$ref: "#/components/schemas/ResultFileName"
252+
responses:
253+
'200':
254+
description: A result file
239255

240256
components:
241257
securitySchemes:
@@ -492,6 +508,10 @@ components:
492508
type: "string"
493509
description: "ID associated with a model run. This is the SHA256 hash of the ModelConfig sent to the /run_model endpoint"
494510
example: "3A3B3E0AE57AD4A7EF658C1F7832774F55E403F01FDF44B68B355EC4587D7A04"
511+
ResultFileName:
512+
type: "string"
513+
description: "The name of a model run result file."
514+
example: "3A3B3E0AE57AD4A7EF658C1F7832774F55E403F01FDF44B68B355EC4587D7A04.csv"
495515
RunStatus:
496516
type: "string"
497517
description: "Status information about a model run."

0 commit comments

Comments
 (0)