7 import urllib.parse as urlparse # Python 3 |
7 import urllib.parse as urlparse # Python 3 |
8 except ImportError: |
8 except ImportError: |
9 import urlparse # Python 2 |
9 import urlparse # Python 2 |
10 |
10 |
11 import argparse |
11 import argparse |
|
12 import datetime |
|
13 import logging |
12 import os |
14 import os |
13 import re |
15 import re |
|
16 from io import StringIO |
|
17 import sys |
14 import time |
18 import time |
15 from io import StringIO |
19 import urlparse |
16 from zipfile import ZipFile |
20 from zipfile import ZipFile |
17 import datetime |
21 |
18 import logging |
22 import requests |
19 import yaml |
23 import yaml |
20 |
24 |
|
25 requests.packages.urllib3.disable_warnings() |
21 |
26 |
22 logger = logging.getLogger(__name__) |
27 logger = logging.getLogger(__name__) |
23 |
28 |
24 # The regex to find the measurement id from the measurement page |
29 # The regex to find the measurement id from the measurement page |
25 # This should be read from the uploaded file, but would require an extra NetCDF module. |
30 # This should be read from the uploaded file, but would require an extra NetCDF module. |
26 regex = "<h3>Measurement (?P<measurement_id>.{12}) <small>" |
31 regex = "<h3>Measurement (?P<measurement_id>.{12}) <small>" |
27 |
32 |
28 |
33 |
29 class SCC: |
34 class SCC: |
30 """ A simple class that will attempt to upload a file on the SCC server. |
35 """A simple class that will attempt to upload a file on the SCC server. |
31 |
36 |
32 The uploading is done by simulating a normal browser session. In the current |
37 The uploading is done by simulating a normal browser session. In the current |
33 version no check is performed, and no feedback is given if the upload |
38 version no check is performed, and no feedback is given if the upload |
34 was successful. If everything is setup correctly, it will work. |
39 was successful. If everything is setup correctly, it will work. |
35 """ |
40 """ |
36 |
41 |
37 def __init__(self, auth, output_dir, base_url): |
42 def __init__(self, auth, output_dir, base_url): |
38 self.auth = auth |
43 self.auth = auth |
39 self.output_dir = output_dir |
44 self.output_dir = output_dir |
66 if login_page.status_code != 200: |
71 if login_page.status_code != 200: |
67 logger.error('Could not access login pages. Status code %s' % login_page.status_code) |
72 logger.error('Could not access login pages. Status code %s' % login_page.status_code) |
68 sys.exit(1) |
73 sys.exit(1) |
69 |
74 |
70 logger.debug("Submiting credentials.") |
75 logger.debug("Submiting credentials.") |
71 |
76 |
72 # Submit the login data |
77 # Submit the login data |
73 login_submit = self.session.post(self.login_url, |
78 login_submit = self.session.post(self.login_url, |
74 data=self.login_credentials, |
79 data=self.login_credentials, |
75 headers={'X-CSRFToken': login_page.cookies['csrftoken'], |
80 headers={'X-CSRFToken': login_page.cookies['csrftoken'], |
76 'referer': self.login_url}, |
81 'referer': self.login_url}, |
79 return login_submit |
84 return login_submit |
80 |
85 |
81 def logout(self): |
86 def logout(self): |
82 pass |
87 pass |
83 |
88 |
84 def upload_file(self, filename, system_id): |
89 def upload_file(self, filename, system_id, rs_filename=None): |
85 """ Upload a filename for processing with a specific system. If the |
90 """ Upload a filename for processing with a specific system. If the |
86 upload is successful, it returns the measurement id. """ |
91 upload is successful, it returns the measurement id. """ |
87 # Get submit page |
92 # Get submit page |
88 upload_page = self.session.get(self.upload_url, |
93 upload_page = self.session.get(self.upload_url, |
89 auth=self.auth, |
94 auth=self.auth, |
90 verify=False) |
95 verify=False) |
91 |
96 |
92 # Submit the data |
97 # Submit the data |
93 upload_data = {'system': system_id} |
98 upload_data = {'system': system_id} |
94 files = {'data': open(filename, 'rb')} |
99 files = {'data': open(filename, 'rb')} |
|
100 |
|
101 if rs_filename is not None: |
|
102 files['sounding_file'] = open(rs_filename, 'rb') |
95 |
103 |
96 logger.info("Uploading of file %s started." % filename) |
104 logger.info("Uploading of file %s started." % filename) |
97 |
105 |
98 upload_submit = self.session.post(self.upload_url, |
106 upload_submit = self.session.post(self.upload_url, |
99 data=upload_data, |
107 data=upload_data, |
117 |
125 |
118 return measurement_id |
126 return measurement_id |
119 |
127 |
120 def download_files(self, measurement_id, subdir, download_url): |
128 def download_files(self, measurement_id, subdir, download_url): |
121 """ Downloads some files from the download_url to the specified |
129 """ Downloads some files from the download_url to the specified |
122 subdir. This method is used to download preprocessed file, optical |
130 subdir. This method is used to download preprocessed file, optical |
123 files etc. |
131 files etc. |
124 """ |
132 """ |
125 # Get the file |
133 # Get the file |
126 request = self.session.get(download_url, auth=self.auth, |
134 request = self.session.get(download_url, auth=self.auth, |
127 verify=False, |
135 verify=False, |
167 # Construct the download url |
175 # Construct the download url |
168 download_url = self.download_graph_pattern.format(measurement_id) |
176 download_url = self.download_graph_pattern.format(measurement_id) |
169 self.download_files(measurement_id, 'scc_plots', download_url) |
177 self.download_files(measurement_id, 'scc_plots', download_url) |
170 |
178 |
171 def rerun_processing(self, measurement_id, monitor=True): |
179 def rerun_processing(self, measurement_id, monitor=True): |
172 measurement = self.get_measurement(measurement_id) |
180 measurement, status = self.get_measurement(measurement_id) |
173 |
181 |
174 if measurement: |
182 if measurement: |
175 request = self.session.get(measurement.rerun_processing_url, auth=self.auth, |
183 request = self.session.get(measurement.rerun_processing_url, auth=self.auth, |
176 verify=False, |
184 verify=False, |
177 stream=True) |
185 stream=True) |
186 |
194 |
187 def rerun_all(self, measurement_id, monitor=True): |
195 def rerun_all(self, measurement_id, monitor=True): |
188 logger.debug("Started rerun_all procedure.") |
196 logger.debug("Started rerun_all procedure.") |
189 |
197 |
190 logger.debug("Getting measurement %s" % measurement_id) |
198 logger.debug("Getting measurement %s" % measurement_id) |
191 measurement = self.get_measurement(measurement_id) |
199 measurement, status = self.get_measurement(measurement_id) |
192 |
200 |
193 if measurement: |
201 if measurement: |
194 logger.debug("Attempting to rerun all processing through %s." % measurement.rerun_all_url) |
202 logger.debug("Attempting to rerun all processing through %s." % measurement.rerun_all_url) |
195 |
203 |
196 request = self.session.get(measurement.rerun_all_url, auth=self.auth, |
204 request = self.session.get(measurement.rerun_all_url, auth=self.auth, |
203 return |
211 return |
204 |
212 |
205 if monitor: |
213 if monitor: |
206 self.monitor_processing(measurement_id) |
214 self.monitor_processing(measurement_id) |
207 |
215 |
208 def process(self, filename, system_id): |
216 def process(self, filename, system_id, rs_filename=None): |
209 """ Upload a file for processing and wait for the processing to finish. |
217 """ Upload a file for processing and wait for the processing to finish. |
210 If the processing is successful, it will download all produced files. |
218 If the processing is successful, it will download all produced files. |
211 """ |
219 """ |
212 logger.info("--- Processing started on %s. ---" % datetime.datetime.now()) |
220 logger.info("--- Processing started on %s. ---" % datetime.datetime.now()) |
213 # Upload file |
221 # Upload file |
214 measurement_id = self.upload_file(filename, system_id) |
222 logger.info("--- Uploading file") |
215 |
223 measurement_id = self.upload_file(filename, system_id, rs_filename=rs_filename) |
|
224 |
|
225 logger.info("--- Monitoring processing") |
216 measurement = self.monitor_processing(measurement_id) |
226 measurement = self.monitor_processing(measurement_id) |
217 return measurement |
227 return measurement |
218 |
228 |
219 def monitor_processing(self, measurement_id): |
229 def monitor_processing(self, measurement_id): |
220 """ Monitor the processing progress of a measurement id""" |
230 """ Monitor the processing progress of a measurement id""" |
221 |
231 |
222 measurement = self.get_measurement(measurement_id) |
232 # try to deal with error 404 |
|
233 error_count = 0 |
|
234 error_max = 6 |
|
235 time_sleep = 10 |
|
236 |
|
237 # try to wait for measurement to appear in API |
|
238 measurement = None |
|
239 logger.info("looking for measurement %s in SCC", measurement_id) |
|
240 while error_count < error_max: |
|
241 time.sleep(time_sleep) |
|
242 measurement, status = self.get_measurement(measurement_id) |
|
243 if status != 200 and error_count < error_max: |
|
244 logger.error("measurement not found. waiting %ds", time_sleep) |
|
245 error_count += 1 |
|
246 else: |
|
247 break |
|
248 |
|
249 if error_count == error_max: |
|
250 logger.critical("measurement %s doesn't seem to exist", measurement_id) |
|
251 sys.exit(1) |
|
252 |
|
253 logger.info('measurement %s found', measurement_id) |
|
254 |
223 if measurement is not None: |
255 if measurement is not None: |
|
256 error_count = 0 |
224 while measurement.is_running: |
257 while measurement.is_running: |
225 logger.info("Measurement is being processed (status: %s, %s, %s). Please wait." % (measurement.upload, |
258 logger.info("Measurement is being processed (status: %s, %s, %s). Please wait.", measurement.upload, measurement.pre_processing, measurement.processing) |
226 measurement.pre_processing, |
|
227 measurement.processing)) |
|
228 time.sleep(10) |
259 time.sleep(10) |
229 measurement = self.get_measurement(measurement_id) |
260 measurement, status = self.get_measurement(measurement_id) |
230 logger.info("Measurement processing finished (status: %s, %s, %s)." % (measurement.upload, |
261 |
231 measurement.pre_processing, |
262 logger.info("Measurement processing finished (status: %s, %s, %s).",measurement.upload, measurement.pre_processing, measurement.processing) |
232 measurement.processing)) |
|
233 if measurement.pre_processing == 127: |
263 if measurement.pre_processing == 127: |
234 logger.info("Downloading preprocessed files.") |
264 logger.info("Downloading preprocessed files.") |
235 self.download_preprocessed(measurement_id) |
265 self.download_preprocessed(measurement_id) |
236 if measurement.processing == 127: |
266 if measurement.processing == 127: |
237 logger.info("Downloading optical files.") |
267 logger.info("Downloading optical files.") |
264 |
294 |
265 response = self.session.get(measurement_url, |
295 response = self.session.get(measurement_url, |
266 auth=self.auth, |
296 auth=self.auth, |
267 verify=False) |
297 verify=False) |
268 |
298 |
|
299 # maybe the measurements isn't already available on the database. |
|
300 if response.status_code == 404: |
|
301 return None, 404 |
|
302 |
269 if response.status_code != 200: |
303 if response.status_code != 200: |
270 logger.error('Could not access API. Status code %s.' % response.status_code) |
304 logger.error('Could not access API. Status code %s.' % response.status_code) |
271 sys.exit(1) |
305 return None, response.status_code |
272 |
306 |
273 response_dict = response.json() |
307 response_dict = response.json() |
274 |
308 |
275 if response_dict: |
309 if response_dict: |
276 measurement = Measurement(self.base_url,response_dict) |
310 measurement = Measurement(self.base_url, response_dict) |
277 return measurement |
311 return measurement, response.status_code |
278 else: |
312 else: |
279 logger.error("No measurement with id %s found on the SCC." % measurement_id) |
313 logger.error("No measurement with id %s found on the SCC." % measurement_id) |
280 return None |
314 return None, response.status_code |
281 |
315 |
282 def delete_measurement(self, measurement_id): |
316 def delete_measurement(self, measurement_id): |
283 """ Deletes a measurement with the provided measurement id. The user |
317 """ Deletes a measurement with the provided measurement id. The user |
284 should have the appropriate permissions. |
318 should have the appropriate permissions. |
285 |
319 |
286 The procedures is performed directly through the web interface and |
320 The procedures is performed directly through the web interface and |
287 NOT through the API. |
321 NOT through the API. |
288 """ |
322 """ |
289 # Get the measurement object |
323 # Get the measurement object |
290 measurement = self.get_measurement(measurement_id) |
324 measurement, status = self.get_measurement(measurement_id) |
291 |
325 |
292 # Check that it exists |
326 # Check that it exists |
293 if measurement is None: |
327 if measurement is None: |
294 logger.warning("Nothing to delete.") |
328 logger.warning("Nothing to delete.") |
295 return None |
329 return None |
340 |
374 |
341 return measurements |
375 return measurements |
342 |
376 |
343 def measurement_id_for_date(self, t1, call_sign='bu', base_number=0): |
377 def measurement_id_for_date(self, t1, call_sign='bu', base_number=0): |
344 """ Give the first available measurement id on the SCC for the specific |
378 """ Give the first available measurement id on the SCC for the specific |
345 date. |
379 date. |
346 """ |
380 """ |
347 date_str = t1.strftime('%Y%m%d') |
381 date_str = t1.strftime('%Y%m%d') |
348 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str) |
382 search_url = urlparse.urljoin(self.api_base_url, 'measurements/?id__startswith=%s' % date_str) |
349 |
383 |
350 response = self.session.get(search_url, |
384 response = self.session.get(search_url, |
422 self.upload, |
456 self.upload, |
423 self.pre_processing, |
457 self.pre_processing, |
424 self.processing) |
458 self.processing) |
425 |
459 |
426 |
460 |
427 def upload_file(filename, system_id, settings): |
461 def upload_file(filename, system_id, settings, rs_filename=None): |
428 """ Shortcut function to upload a file to the SCC. """ |
462 """ Shortcut function to upload a file to the SCC. """ |
429 logger.info("Uploading file %s, using sytem %s" % (filename, system_id)) |
463 logger.info("Uploading file %s, using sytem %s" % (filename, system_id)) |
430 |
464 |
431 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
465 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
432 scc.login(settings['website_credentials']) |
466 scc.login(settings['website_credentials']) |
433 measurement_id = scc.upload_file(filename, system_id) |
467 measurement_id = scc.upload_file(filename, system_id, rs_filename=rs_filename) |
434 scc.logout() |
468 scc.logout() |
435 return measurement_id |
469 return measurement_id |
436 |
470 |
437 |
471 |
438 def process_file(filename, system_id, settings): |
472 def process_file(filename, system_id, settings, rs_filename=None): |
439 """ Shortcut function to process a file to the SCC. """ |
473 """ Shortcut function to process a file to the SCC. """ |
440 logger.info("Processing file %s, using sytem %s" % (filename, system_id)) |
474 logger.info("Processing file %s, using sytem %s" % (filename, system_id)) |
441 |
475 |
442 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
476 scc = SCC(settings['basic_credentials'], settings['output_dir'], settings['base_url']) |
443 scc.login(settings['website_credentials']) |
477 scc.login(settings['website_credentials']) |
444 measurement = scc.process(filename, system_id) |
478 measurement = scc.process(filename, system_id, rs_filename=rs_filename) |
445 scc.logout() |
479 scc.logout() |
446 return measurement |
480 return measurement |
447 |
481 |
448 |
482 |
449 def delete_measurement(measurement_id, settings): |
483 def delete_measurement(measurement_id, settings): |
509 action="store_true") |
543 action="store_true") |
510 parser.add_argument("--delete", help="Measurement ID to delete.") |
544 parser.add_argument("--delete", help="Measurement ID to delete.") |
511 parser.add_argument("--rerun-all", help="Measurement ID to rerun.") |
545 parser.add_argument("--rerun-all", help="Measurement ID to rerun.") |
512 parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.") |
546 parser.add_argument("--rerun-processing", help="Measurement ID to rerun processing routines.") |
513 |
547 |
|
548 # others files |
|
549 parser.add_argument("--radiosounding", default=None, help="Radiosounding file name or path") |
|
550 |
514 # Verbosity settings from http://stackoverflow.com/a/20663028 |
551 # Verbosity settings from http://stackoverflow.com/a/20663028 |
515 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const", |
552 parser.add_argument('-d', '--debug', help="Print debugging information.", action="store_const", |
516 dest="loglevel", const=logging.DEBUG, default=logging.INFO, |
553 dest="loglevel", const=logging.DEBUG, default=logging.INFO, |
517 ) |
554 ) |
518 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const", |
555 parser.add_argument('-s', '--silent', help="Show only warning and error messages.", action="store_const", |