Skip to content

Commit ef90c10

Browse files
authored
Merge pull request #21 from simvue-io/offline_mode
Add offline mode
2 parents 4a0e40f + 111cbb2 commit ef90c10

File tree

10 files changed

+594
-90
lines changed

10 files changed

+594
-90
lines changed

bin/simvue_sender

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Send runs to server"""
2+
import getpass
3+
import os
4+
import logging
5+
import sys
6+
7+
from simvue.sender import sender
8+
from simvue.utilities import create_file, remove_file
9+
10+
logger = logging.getLogger()
11+
logger.setLevel(logging.DEBUG)
12+
13+
handler = logging.StreamHandler(sys.stdout)
14+
handler.setLevel(logging.INFO)
15+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
16+
handler.setFormatter(formatter)
17+
logger.addHandler(handler)
18+
19+
if __name__ == "__main__":
20+
lockfile = f"/tmp/simvue-{getpass.getuser()}.lock"
21+
if not os.path.isfile(lockfile):
22+
create_file(lockfile)
23+
24+
try:
25+
sender()
26+
except Exception as err:
27+
logger.error('Exception running sender: %s', str(err))
28+
29+
remove_file(lockfile)

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212
version=version,
1313
author="Andrew Lahiff",
1414
author_email="[email protected]",
15-
description="Simulation tracking",
15+
description="Simulation tracking and monitoring",
1616
long_description=long_description,
1717
long_description_content_type="text/markdown",
1818
url="https://github.com/simvue-io/client",
1919
platforms=["any"],
2020
install_requires=["requests", "randomname", "msgpack", "tenacity"],
2121
package_dir={'': '.'},
22-
packages=['simvue'],
22+
packages=["simvue"],
2323
package_data={"": ["README.md"]},
24+
scripts=["bin/simvue_sender"]
2425
)

simvue/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import os
33
import requests
44

5-
from .utilities import get_config
5+
from .utilities import get_auth
66

77
CONCURRENT_DOWNLOADS = 10
88
DOWNLOAD_CHUNK_SIZE = 8192
@@ -31,7 +31,7 @@ class Client(object):
3131
Class for querying Simvue
3232
"""
3333
def __init__(self):
34-
self._url, self._token = get_config()
34+
self._url, self._token = get_auth()
3535
self._headers = {"Authorization": f"Bearer {self._token}"}
3636

3737
def list_artifacts(self, run, category=None):

simvue/offline.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import json
2+
import logging
3+
import os
4+
import time
5+
6+
from .utilities import get_offline_directory, get_directory_name, create_file
7+
8+
logger = logging.getLogger(__name__)
9+
10+
class Offline(object):
11+
"""
12+
Class for offline runs
13+
"""
14+
def __init__(self, name, suppress_errors=False):
15+
self._name = name
16+
self._directory = os.path.join(get_offline_directory(), get_directory_name(name))
17+
self._suppress_errors = suppress_errors
18+
19+
def _error(self, message):
20+
"""
21+
Raise an exception if necessary and log error
22+
"""
23+
if not self._suppress_errors:
24+
raise RuntimeError(message)
25+
else:
26+
logger.error(message)
27+
28+
def _write_json(self, filename, data):
29+
"""
30+
Write JSON to file
31+
"""
32+
try:
33+
with open(filename, 'w') as fh:
34+
json.dump(data, fh)
35+
except Exception as err:
36+
self._error(f"Unable to write file {filename} due to {str(err)}")
37+
38+
def create_run(self, data):
39+
"""
40+
Create a run
41+
"""
42+
try:
43+
os.makedirs(self._directory, exist_ok=True)
44+
except Exception as err:
45+
logger.error('Unable to create directory %s due to: %s', self._directory, str(err))
46+
47+
filename = f"{self._directory}/run.json"
48+
self._write_json(filename, data)
49+
50+
status = data['status']
51+
filename = f"{self._directory}/{status}"
52+
create_file(filename)
53+
54+
return True
55+
56+
def update(self, data):
57+
"""
58+
Update metadata, tags or status
59+
"""
60+
unique_id = time.time()
61+
filename = f"{self._directory}/update-{unique_id}.json"
62+
self._write_json(filename, data)
63+
64+
if 'status' in data:
65+
status = data['status']
66+
filename = f"{self._directory}/{status}"
67+
create_file(filename)
68+
69+
if status == 'completed':
70+
status_running = f"{self._directory}/running"
71+
if os.path.isfile(status_running):
72+
os.remove(status_running)
73+
74+
return True
75+
76+
def set_folder_details(self, data):
77+
"""
78+
Set folder details
79+
"""
80+
unique_id = time.time()
81+
filename = f"{self._directory}/folder-{unique_id}.json"
82+
self._write_json(filename, data)
83+
return True
84+
85+
def save_file(self, data):
86+
"""
87+
Save file
88+
"""
89+
unique_id = time.time()
90+
filename = f"{self._directory}/file-{unique_id}.json"
91+
self._write_json(filename, data)
92+
return True
93+
94+
def add_alert(self, data):
95+
"""
96+
Add an alert
97+
"""
98+
unique_id = time.time()
99+
filename = f"{self._directory}/alert-{unique_id}.json"
100+
self._write_json(filename, data)
101+
return True

simvue/remote.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import logging
2+
import requests
3+
4+
from .utilities import get_auth
5+
6+
logger = logging.getLogger(__name__)
7+
8+
UPLOAD_TIMEOUT = 30
9+
DEFAULT_API_TIMEOUT = 10
10+
11+
class Remote(object):
12+
"""
13+
Class which interacts with Simvue REST API
14+
"""
15+
def __init__(self, name, suppress_errors=False):
16+
self._name = name
17+
self._suppress_errors = suppress_errors
18+
self._url, self._token = get_auth()
19+
self._headers = {"Authorization": f"Bearer {self._token}"}
20+
self._headers_mp = self._headers.copy()
21+
self._headers_mp['Content-Type'] = 'application/msgpack'
22+
23+
def _error(self, message):
24+
"""
25+
Raise an exception if necessary and log error
26+
"""
27+
if not self._suppress_errors:
28+
raise RuntimeError(message)
29+
else:
30+
logger.error(message)
31+
32+
def create_run(self, data):
33+
"""
34+
Create a run
35+
"""
36+
try:
37+
response = requests.post(f"{self._url}/api/runs",
38+
headers=self._headers,
39+
json=data,
40+
timeout=DEFAULT_API_TIMEOUT)
41+
except requests.exceptions.RequestException:
42+
return False
43+
44+
if response.status_code != 200:
45+
self._error('Unable to reconnect to run')
46+
return False
47+
48+
return True
49+
50+
def update(self, data):
51+
"""
52+
Update metadata, tags or status
53+
"""
54+
try:
55+
response = requests.put(f"{self._url}/api/runs",
56+
headers=self._headers,
57+
json=data,
58+
timeout=DEFAULT_API_TIMEOUT)
59+
except requests.exceptions.RequestException:
60+
pass
61+
62+
if response.status_code == 200:
63+
return True
64+
65+
return False
66+
67+
def set_folder_details(self, data):
68+
"""
69+
Set folder details
70+
"""
71+
try:
72+
response = requests.put(f"{self._url}/api/folders",
73+
headers=self._headers,
74+
json=data,
75+
timeout=DEFAULT_API_TIMEOUT)
76+
except requests.exceptions.RequestException:
77+
pass
78+
79+
if response.status_code == 200:
80+
return True
81+
82+
return False
83+
84+
def save_file(self, data):
85+
"""
86+
Save file
87+
"""
88+
# Get presigned URL
89+
try:
90+
response = requests.post(f"{self._url}/api/data",
91+
headers=self._headers,
92+
json=data,
93+
timeout=DEFAULT_API_TIMEOUT)
94+
except requests.exceptions.RequestException as err:
95+
self._error(f"Got exception when uploading file {data['name']} to object storage: {str(err)}")
96+
return False
97+
98+
if response.status_code == 409:
99+
return True
100+
101+
if response.status_code != 200:
102+
self._error(f"Got status code {response.status_code} when registering file {data['name']}")
103+
return False
104+
105+
if 'url' in response.json():
106+
try:
107+
with open(data['originalPath'], 'rb') as fh:
108+
response = requests.put(response.json()['url'],
109+
data=fh,
110+
timeout=UPLOAD_TIMEOUT)
111+
if response.status_code != 200:
112+
self._error(f"Got status code {response.status_code} when uploading file {data['name']} to object storage")
113+
return None
114+
except Exception as err:
115+
self._error(f"Got exception when uploading file {data['name']} to object storage: {str(err)}")
116+
return None
117+
118+
return True
119+
120+
def add_alert(self, data):
121+
"""
122+
Add an alert
123+
"""
124+
try:
125+
response = requests.put(f"{self._url}/api/runs",
126+
headers=self._headers,
127+
json=data,
128+
timeout=DEFAULT_API_TIMEOUT)
129+
except requests.exceptions.RequestException:
130+
pass
131+
132+
if response.status_code == 200:
133+
return True
134+
135+
return False
136+
137+
def send_metrics(self, data):
138+
"""
139+
Send metrics
140+
"""
141+
try:
142+
response = requests.post(f"{self._url}/api/metrics",
143+
headers=self._headers_mp,
144+
data=data,
145+
timeout=DEFAULT_API_TIMEOUT)
146+
except:
147+
pass
148+
149+
def send_event(self, data):
150+
"""
151+
Send events
152+
"""
153+
try:
154+
response = requests.post(f"{self._url}/api/events",
155+
headers=self._headers_mp,
156+
data=data,
157+
timeout=DEFAULT_API_TIMEOUT)
158+
except:
159+
pass
160+
161+
def send_heartbeat(self):
162+
"""
163+
Send heartbeat
164+
"""
165+
try:
166+
response = requests.put(f"{self._url}/api/runs/heartbeat",
167+
headers=self._headers,
168+
json={'name': self._name},
169+
timeout=DEFAULT_API_TIMEOUT)
170+
except:
171+
pass

0 commit comments

Comments
 (0)