From b7763c00a143bad7e5d5e66706910ad0a288ba51 Mon Sep 17 00:00:00 2001 From: Markus Scheidgen <markus.scheidgen@gmail.com> Date: Fri, 16 Nov 2018 12:32:33 +0100 Subject: [PATCH] Refactored user management to use the NOMAD-coe repository database. --- .vscode/launch.json | 2 +- .vscode/temp.sql | 0 docs/components.graffle/data.plist | Bin 22253 -> 30860 bytes gui/src/api.js | 2 +- .../nomad/docker-compose.override.yml | 5 + infrastructure/nomad/docker-compose.yml | 13 + nomad/api.py | 874 ------ nomad/api/__init__.py | 20 + nomad/api/app.py | 91 + nomad/api/archive.py | 123 + nomad/api/repository.py | 305 +++ nomad/api/upload.py | 424 +++ nomad/client.py | 12 +- nomad/dependencies.py | 11 +- nomad/infrastructure.py | 23 +- nomad/processing/data.py | 2 +- nomad/user.py | 157 +- repository/repo_postgres_schema | 1 + repository/repository_es_example.js | 2375 +++++++++++++++++ repository/utils.http | 19 + requirements.txt | 3 + tests/conftest.py | 17 +- tests/processing/test_data.py | 26 +- tests/test_api.py | 21 +- tests/test_repo.py | 8 +- tests/test_user.py | 16 + 26 files changed, 3564 insertions(+), 986 deletions(-) create mode 100644 .vscode/temp.sql delete mode 100644 nomad/api.py create mode 100644 nomad/api/__init__.py create mode 100644 nomad/api/app.py create mode 100644 nomad/api/archive.py create mode 100644 nomad/api/repository.py create mode 100644 nomad/api/upload.py create mode 100644 repository/repo_postgres_schema create mode 100644 repository/repository_es_example.js create mode 100644 repository/utils.http create mode 100644 tests/test_user.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 443afd0ee3..ff04f5a491 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -44,7 +44,7 @@ "cwd": "${workspaceFolder}", "program": "${workspaceFolder}/.pyenv/bin/pytest", "args": [ - "-sv", "tests/test_parsing.py::TestLocalBackend::test_two_sections" + "-sv", "tests/test_api.py::test_repo_calcs_user" ] }, { diff --git a/.vscode/temp.sql b/.vscode/temp.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/components.graffle/data.plist b/docs/components.graffle/data.plist index c2ca48b1353cb7f2b210ed48d90c049eb9a82462..8573b8e2771c2ab3090f048854a0f2d7eeb1becc 100644 GIT binary patch literal 30860 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 literal 22253 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 OssHhyK)fi0g#iGeyhF$U diff --git a/gui/src/api.js b/gui/src/api.js index 94957c9026..dca128fa9b 100644 --- a/gui/src/api.js +++ b/gui/src/api.js @@ -2,7 +2,7 @@ import { UploadRequest } from '@navjobs/upload' import { apiBase, appStaticBase } from './config' const auth_headers = { - Authorization: 'Basic ' + btoa('me@gmail.com:nomad') + Authorization: 'Basic ' + btoa('sheldon.cooper@nomad-fairdi.tests.de:password') } const networkError = () => { diff --git a/infrastructure/nomad/docker-compose.override.yml b/infrastructure/nomad/docker-compose.override.yml index 32b59b9678..ce472558e5 100644 --- a/infrastructure/nomad/docker-compose.override.yml +++ b/infrastructure/nomad/docker-compose.override.yml @@ -15,6 +15,11 @@ version: '3.4' services: + # postgres for NOMAD-coe repository API and GUI + postgres: + ports: + - 5432:5432 + # broker for celery rabbitmq: ports: diff --git a/infrastructure/nomad/docker-compose.yml b/infrastructure/nomad/docker-compose.yml index 2f8972ae32..e6e54ae19c 100644 --- a/infrastructure/nomad/docker-compose.yml +++ b/infrastructure/nomad/docker-compose.yml @@ -21,6 +21,18 @@ x-common-variables: &nomad_backend_env NOMAD_MONGO_HOST: mongo services: + # postgres for NOMAD-coe repository API and GUI + postgres: + restart: always + image: postgres:9.4 + container_name: nomad_postgres + environment: + POSTGRES_PASSWORD: 'nomad' + POSTGRES_USER: 'postgres' + POSTGRES_DB: 'nomad' + volumes: + - nomad_postgres:/var/lib/postgresql/data + # broker for celery rabbitmq: restart: always @@ -110,6 +122,7 @@ services: command: nginx -g 'daemon off;' volumes: + nomad_postgres: nomad_mongo: nomad_elastic: nomad_rabbitmq: diff --git a/nomad/api.py b/nomad/api.py deleted file mode 100644 index 2eae8570aa..0000000000 --- a/nomad/api.py +++ /dev/null @@ -1,874 +0,0 @@ -# Copyright 2018 Markus Scheidgen -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an"AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from werkzeug.exceptions import HTTPException -from flask import Flask, request, g, jsonify, send_file, Response -from flask_restful import Resource, Api, abort -from flask_cors import CORS -from flask_httpauth import HTTPBasicAuth -from elasticsearch.exceptions import NotFoundError -from datetime import datetime -import os.path -import zipstream -from zipfile import ZIP_DEFLATED -from contextlib import contextmanager - -from nomad import config, infrastructure -from nomad.files import UploadFile, ArchiveFile, ArchiveLogFile, RepositoryFile -from nomad.utils import get_logger -from nomad.processing import Upload, NotAllowedDuringProcessing -from nomad.repo import RepoCalc -from nomad.user import User - -base_path = config.services.api_base_path - -app = Flask( - __name__, - static_url_path='%s/docs' % base_path, - static_folder=os.path.abspath(os.path.join(os.path.dirname(__file__), '../docs/.build/html'))) -CORS(app) - -app.config['SECRET_KEY'] = config.services.api_secret - -auth = HTTPBasicAuth() -api = Api(app) - - -@app.before_first_request -def setup(): - infrastructure.setup() - - -@auth.verify_password -def verify_password(username_or_token, password): - # first try to authenticate by token - user = User.verify_auth_token(username_or_token) - if not user: - # try to authenticate with username/password - user = User.objects(email=username_or_token).first() - if not user: - g.user = None - return True # anonymous access - - if not user or not user.verify_password(password): - return False - - g.user = user - return True - - -def login_really_required(func): - @auth.login_required - def wrapper(*args, **kwargs): - if g.user is None: - abort(401, message='Anonymous access is forbidden, authorization required') - else: - return func(*args, **kwargs) - wrapper.__name__ = func.__name__ - wrapper.__doc__ = func.__doc__ - return wrapper - - -@app.route('/api/token') -@login_really_required -def get_auth_token(): - token = g.user.generate_auth_token(600) - return jsonify({'token': token.decode('ascii'), 'duration': 600}) - - -class UploadsRes(Resource): - """ Uploads """ - @login_really_required - def get(self): - """ - Get a list of current users uploads. - - .. :quickref: upload; Get a list of current users uploads. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/uploads HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - [ - { - "name": "examples_vasp_6.zip", - "upload_id": "5b89469e0d80d40008077dbc", - "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", - "create_time": "2018-08-31T13:46:06.781000", - "upload_time": "2018-08-31T13:46:07.531000", - "is_stale": false, - "completed": true, - "status": "SUCCESS", - "current_task": "cleanup", - "tasks": ["uploading", "extracting", "parse_all", "cleanup"] - "errors": [], - "warnings": [] - } - ] - - :resheader Content-Type: application/json - :status 200: uploads successfully provided - :returns: list of :class:`nomad.data.Upload` - """ - return [upload.json_dict for upload in Upload.user_uploads(g.user)], 200 - - @login_really_required - def post(self): - """ - Create a new upload. Creating an upload on its own wont do much, but provide - a *presigned* upload URL. PUT a file to this URL to do the actual upload and - initiate the processing. - - .. :quickref: upload; Create a new upload. - - **Example request**: - - .. sourcecode:: http - - POST /nomad/api/uploads HTTP/1.1 - Accept: application/json - Content-Type: application/json - - { - "name": "vasp_data.zip" - } - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "name": "vasp_data.zip", - "upload_id": "5b89469e0d80d40008077dbc", - "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", - "create_time": "2018-08-31T13:46:06.781000", - "upload_time": "2018-08-31T13:46:07.531000", - "is_stale": false, - "completed": true, - "status": "SUCCESS", - "current_task": "cleanup", - "tasks": ["uploading", "extracting", "parse_all", "cleanup"] - "errors": [], - "warnings": [], - "calcs": [ - { - "current_task": "archiving", - "tasks": ["parsing", "normalizing", "archiving"] - "status": "SUCCESS", - "errors": [], - "warnings": [], - "parser": "parsers/vasp", - "mainfile": "Si.xml" - } - ] - } - - :jsonparam string name: An optional name for the upload. - :jsonparem string local_path: An optional path the a file that is already on the server. - In this case, uploading a file won't be possible, the local file is processed - immediatly as if it was uploaded. - :reqheader Content-Type: application/json - :resheader Content-Type: application/json - :status 200: upload successfully created - :returns: a new instance of :class:`nomad.data.Upload` - """ - json_data = request.get_json() - if json_data is None: - json_data = {} - - upload = Upload.create( - user=g.user, - name=json_data.get('name'), - local_path=json_data.get('local_path')) - - if upload.local_path is not None: - logger = get_logger( - __name__, endpoint='uploads', action='post', upload_id=upload.upload_id) - logger.info('file uploaded offline') - upload.upload_time = datetime.now() - upload.process() - logger.info('initiated processing') - - return upload.json_dict, 200 - - -class UploadRes(Resource): - """ Uploads """ - @login_really_required - def get(self, upload_id): - """ - Get an update on an existing upload. Will not only return the upload, but - also its calculations paginated. Use the pagination params to determine - the page. - - .. :quickref: upload; Get an update for an existing upload. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/uploads/5b89469e0d80d40008077dbc HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "name": "vasp_data.zip", - "upload_id": "5b89469e0d80d40008077dbc", - "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", - "create_time": "2018-08-31T13:46:06.781000", - "upload_time": "2018-08-31T13:46:07.531000", - "is_stale": false, - "completed": true, - "status": "SUCCESS", - "current_task": "cleanup", - "tasks": ["uploading", "extracting", "parse_all", "cleanup"] - "errors": [], - "warnings": [], - "calcs": { - "pagination": { - "total": 1, - "page": 1, - "per_page": 25 - }, - "results": [ - { - "current_task": "archiving", - "tasks": ["parsing", "normalizing", "archiving"] - "status": "SUCCESS", - "errors": [], - "warnings": [], - "parser": "parsers/vasp", - "mainfile": "Si.xml" - } - ] - } - } - - :param string upload_id: the id for the upload - :qparam int page: the page starting with 1 - :qparam int per_page: desired calcs per page - :qparam str order_by: the field to sort the calcs by, use [status,mainfile] - :resheader Content-Type: application/json - :status 200: upload successfully updated and retrieved - :status 404: upload with id does not exist - :returns: the :class:`nomad.data.Upload` instance - """ - try: - upload = Upload.get(upload_id) - except KeyError: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - if upload.user_id != g.user.email: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - try: - page = int(request.args.get('page', 1)) - per_page = int(request.args.get('per_page', 10)) - order_by = str(request.args.get('order_by', 'mainfile')) - order = int(str(request.args.get('order', -1))) - except Exception: - abort(400, message='invalid pagination or ordering') - - try: - assert page >= 1 - assert per_page > 0 - except AssertionError: - abort(400, message='invalid pagination') - - if order_by not in ['mainfile', 'status', 'parser']: - abort(400, message='invalid order_by field %s' % order_by) - - order_by = ('-%s' if order == -1 else '+%s') % order_by - - calcs = upload.all_calcs((page - 1) * per_page, page * per_page, order_by) - failed_calcs = upload.failed_calcs - result = upload.json_dict - result['calcs'] = { - 'pagination': dict( - total=upload.total_calcs, page=page, per_page=per_page, - successes=upload.processed_calcs - failed_calcs, failures=failed_calcs), - 'results': [calc.json_dict for calc in calcs] - } - - return result, 200 - - @login_really_required - def post(self, upload_id): - """ - Move an upload out of the staging area. This changes the visibility of the upload. - Clients can specify, if the calcs should be restricted. - - .. :quickref: upload; Move an upload out of the staging area. - - **Example request**: - - .. sourcecode:: http - - POST /nomad/api/uploads HTTP/1.1 - Accept: application/json - Content-Type: application/json - - { - "operation": "unstage" - } - - - :param string upload_id: the upload id - :resheader Content-Type: application/json - :status 200: upload unstaged successfully - :status 404: upload could not be found - :status 400: if the operation is not supported - :returns: the upload record - """ - try: - upload = Upload.get(upload_id) - except KeyError: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - if upload.user_id != g.user.email: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - json_data = request.get_json() - if json_data is None: - json_data = {} - - operation = json_data.get('operation') - if operation == 'unstage': - upload.unstage() - return upload.json_dict, 200 - - abort(400, message='Unsuported operation %s.' % operation) - - @login_really_required - def delete(self, upload_id): - """ - Deletes an existing upload. Only ``is_ready`` or ``is_stale`` uploads - can be deleted. Deleting an upload in processing is not allowed. - - .. :quickref: upload; Delete an existing upload. - - **Example request**: - - .. sourcecode:: http - - DELETE /nomad/api/uploads/5b89469e0d80d40008077dbc HTTP/1.1 - Accept: application/json - - :param string upload_id: the id for the upload - :resheader Content-Type: application/json - :status 200: upload successfully deleted - :status 400: upload cannot be deleted - :status 404: upload with id does not exist - :returns: the :class:`nomad.data.Upload` instance with the latest processing state - """ - try: - upload = Upload.get(upload_id) - except KeyError: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - if upload.user_id != g.user.email: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - try: - upload.delete() - return upload.json_dict, 200 - except NotAllowedDuringProcessing: - abort(400, message='You must not delete an upload during processing.') - - -class UploadFileRes(Resource): - """ - Upload a file to an existing upload. Can be used to upload files via bowser - or other http clients like curl. This will start the processing of the upload. - - There are two basic ways to upload a file: multipart-formdata or simply streaming - the file data. Both are supported. The later one does not allow to transfer a - filename or other meta-data. If a filename is available, it will become the - name of the upload. - - .. :quickref: upload; Upload a file to an existing upload. - - **Curl examples for both approaches**: - - .. sourcecode:: sh - - curl -X put "/nomad/api/uploads/5b89469e0d80d40008077dbc/file" -F file=@local_file - curl "/nomad/api/uploads/5b89469e0d80d40008077dbc/file" --upload-file local_file - - :param string upload_id: the upload_id of the upload - :resheader Content-Type: application/json - :status 200: upload successfully received. - :status 404: upload with given id does not exist - :status 400: if the fileformat is not supported or the form data is different than expected. - :returns: the upload (see GET /uploads/<upload_id>) - """ - @login_really_required - def put(self, upload_id): - logger = get_logger(__name__, endpoint='upload', action='put', upload_id=upload_id) - - try: - upload = Upload.get(upload_id) - except KeyError: - abort(404, message='Upload with id %s does not exist.' % upload_id) - - if upload.upload_time is not None: - abort(400, message='A file was already uploaded to this uploade before.') - - uploadFile = UploadFile(upload_id) - - if request.mimetype == 'application/multipart-formdata': - # multipart formdata, e.g. with curl -X put "url" -F file=@local_file - # might have performance issues for large files: https://github.com/pallets/flask/issues/2086 - if 'file' in request.files: - abort(400, message='Bad multipart-formdata, there is no file part.') - file = request.files['file'] - if upload.name is '': - upload.name = file.filename - - file.save(uploadFile.os_path) - else: - # simple streaming data in HTTP body, e.g. with curl "url" -T local_file - try: - with uploadFile.open('wb') as f: - while not request.stream.is_exhausted: - f.write(request.stream.read(1024)) - - except Exception as e: - logger.error('Error on streaming upload', exc_info=e) - abort(400, message='Some IO went wrong, download probably aborted/disrupted.') - - if not uploadFile.is_valid: - uploadFile.delete() - abort(400, message='Bad file format, excpected %s.' % ", ".join(UploadFile.formats)) - - logger.info('received uploaded file') - upload.upload_time = datetime.now() - upload.process() - logger.info('initiated processing') - - return upload.json_dict, 200 - - -class RepoCalcRes(Resource): - def get(self, upload_hash, calc_hash): - """ - Get calculation data in repository form, which only entails the quanties shown - in the repository. This is basically the elastic search index entry for the - requested calculations. Calcs are references via *upload_hash*, *calc_hash* - pairs. - - .. :quickref: repo; Get calculation data in repository form. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/repo/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "calc_hash":"7ddvtfRfZAvc3Crr7jOJ8UH0T34I", - "upload_time":"2018-08-30T08:41:51.771367", - "upload_id":"5b87adb813a441000a70a968", - "upload_hash":"W36aqCzAKxOCfIiMFsBJh3nHPb4a", - "mainfile":"RopD3Mo8oMV_-E5bh8uW5PiiCRkH1/data/BrK_svSi/TFCC010.CAB/vasprun.xml.relax1", - "program_name":"VASP", - "program_version":"4.6.35 3Apr08 complex parallel LinuxIFC", - "chemical_composition_bulk_reduced":"BrKSi2", - "program_basis_set_type":"plane waves", - "atom_species":[ - 35, - 19, - 14, - 14 - ], - "system_type":"Bulk", - "crystal_system":"orthorhombic", - "space_group_number":47, - "configuration_raw_gid":"sq6wTJjRKb2VTajoDLVWDxHCgyN6i", - "XC_functional_name":"GGA_X_PBE" - } - - :param string upload_hash: the hash of the upload (from uploaded file contents) - :param string calc_hash: the hash of the calculation (from mainfile) - :resheader Content-Type: application/json - :status 200: calc successfully retrieved - :status 404: calc with given hashes does not exist - :returns: the repository calculation entry - """ - try: - return RepoCalc.get(id='%s/%s' % (upload_hash, calc_hash)).json_dict, 200 - except NotFoundError: - abort(404, message='There is no calculation for %s/%s' % (upload_hash, calc_hash)) - except Exception as e: - abort(500, message=str(e)) - - -class RepoCalcsRes(Resource): - @auth.login_required - def get(self): - """ - Get *'all'* calculations in repository from, paginated. - - .. :quickref: repo; Get *'all'* calculations in repository from, paginated. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/repo?page=1&per_page=25 HTTP/1.1 - Accept: application/json - - **Example response**: - - .. sourcecode:: http - - HTTP/1.1 200 OK - Vary: Accept - Content-Type: application/json - - { - "pagination":{ - "total":1, - "page":1, - "per_page":25 - }, - "results":[ - { - "calc_hash":"7ddvtfRfZAvc3Crr7jOJ8UH0T34I", - "upload_time":"2018-08-30T08:41:51.771367", - "upload_id":"5b87adb813a441000a70a968", - "upload_hash":"W36aqCzAKxOCfIiMFsBJh3nHPb4a", - "mainfile":"RopD3Mo8oMV_-E5bh8uW5PiiCRkH1/data/BrK_svSi/TFCC010.CAB/vasprun.xml.relax1", - "program_name":"VASP", - "program_version":"4.6.35 3Apr08 complex parallel LinuxIFC", - "chemical_composition_bulk_reduced":"BrKSi2", - "program_basis_set_type":"plane waves", - "atom_species":[ - 35, - 19, - 14, - 14 - ], - "system_type":"Bulk", - "crystal_system":"orthorhombic", - "space_group_number":47, - "configuration_raw_gid":"sq6wTJjRKb2VTajoDLVWDxHCgyN6i", - "XC_functional_name":"GGA_X_PBE" - } - ] - } - - :qparam int page: the page starting with 1 - :qparam int per_page: desired calcs per page - :qparam string owner: specifies which cals to return: all|user|staging, default is all - :resheader Content-Type: application/json - :status 200: calcs successfully retrieved - :returns: a list of repository entries in ``results`` and pagination info - """ - # TODO use argparse? bad request reponse an bad params, pagination as decorator - page = int(request.args.get('page', 1)) - per_page = int(request.args.get('per_page', 10)) - owner = request.args.get('owner', 'all') - - try: - assert page >= 1 - assert per_page > 0 - except AssertionError: - abort(400, message='invalid pagination') - - if owner == 'all': - search = RepoCalc.search().query('match_all') - elif owner == 'user': - if g.user is None: - abort(401, message='Authentication required for owner value user.') - search = RepoCalc.search().query('match_all') - search = search.filter('term', user_id=g.user.email) - elif owner == 'staging': - if g.user is None: - abort(401, message='Authentication required for owner value user.') - search = RepoCalc.search().query('match_all') - search = search.filter('term', user_id=g.user.email).filter('term', staging=True) - else: - abort(400, message='Invalid owner value. Valid values are all|user|staging, default is all') - - search = search[(page - 1) * per_page: page * per_page] - return { - 'pagination': { - 'total': search.count(), - 'page': page, - 'per_page': per_page - }, - 'results': [result.json_dict for result in search] - } - - -@app.route('%s/logs/<string:upload_hash>/<string:calc_hash>' % base_path, methods=['GET']) -def get_calc_proc_log(upload_hash, calc_hash): - """ - Get calculation processing log. Calcs are references via *upload_hash*, *calc_hash* - pairs. - - .. :quickref: archive; Get calculation processing logs. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/logs/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 - Accept: application/json - - :param string upload_hash: the hash of the upload (from uploaded file contents) - :param string calc_hash: the hash of the calculation (from mainfile) - :resheader Content-Type: application/json - :status 200: calc successfully retrieved - :status 404: calc with given hashes does not exist - :returns: the log data, a line by line sequence of structured logs - """ - archive_id = '%s/%s' % (upload_hash, calc_hash) - - try: - archive = ArchiveLogFile(archive_id) - archive_path = archive.os_path - - rv = send_file( - archive_path, - mimetype='application/text', - as_attachment=True, - attachment_filename=os.path.basename(archive_path)) - - return rv - except KeyError: - abort(404, message='Archive/calculation %s does not exist.' % archive_id) - except FileNotFoundError: - abort(404, message='Archive/calculation %s does not exist.' % archive_id) - except Exception as e: - logger = get_logger( - __name__, endpoint='logs', action='get', - upload_hash=upload_hash, calc_hash=calc_hash) - logger.error('Exception on accessing calc proc log', exc_info=e) - abort(500, message='Could not accessing the logs.') - - -@app.route('%s/archive/<string:upload_hash>/<string:calc_hash>' % base_path, methods=['GET']) -def get_calc(upload_hash, calc_hash): - """ - Get calculation data in archive form. Calcs are references via *upload_hash*, *calc_hash* - pairs. - - .. :quickref: archive; Get calculation data in archive form. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/archive/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 - Accept: application/json - - :param string upload_hash: the hash of the upload (from uploaded file contents) - :param string calc_hash: the hash of the calculation (from mainfile) - :resheader Content-Type: application/json - :status 200: calc successfully retrieved - :status 404: calc with given hashes does not exist - :returns: the metainfo formated JSON data of the requested calculation - """ - archive_id = '%s/%s' % (upload_hash, calc_hash) - - try: - archive = ArchiveFile(archive_id) - archive_path = archive.os_path - - rv = send_file( - archive_path, - mimetype='application/json', - as_attachment=True, - attachment_filename=os.path.basename(archive_path)) - - if config.files.compress_archive: - rv.headers['Content-Encoding'] = 'gzip' - - return rv - except KeyError: - abort(404, message='Archive %s does not exist.' % archive_id) - except FileNotFoundError: - abort(404, message='Archive %s does not exist.' % archive_id) - except Exception as e: - logger = get_logger( - __name__, endpoint='archive', action='get', - upload_hash=upload_hash, calc_hash=calc_hash) - logger.error('Exception on accessing archive', exc_info=e) - abort(500, message='Could not accessing the archive.') - - -@app.route('%s/raw/<string:upload_hash>/<string:calc_hash>' % base_path, methods=['GET']) -def get_raw(upload_hash, calc_hash): - """ - Get calculation mainfile raw data. Calcs are references via *upload_hash*, *calc_hash* - pairs. Returns the mainfile, unless an aux_file is specified. Aux files are stored - in repository entries. See ``/repo`` endpoint. - - .. :quickref: repo; Get calculation raw data. - - **Example request**: - - .. sourcecode:: http - - GET /nomad/api/raw/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 - Accept: application/gz - - :param string upload_hash: the hash of the upload (from uploaded file contents) - :param string calc_hash: the hash of the calculation (from mainfile) - :qparam str auxfile: an optional aux_file to download the respective aux file, default is mainfile - :qparam all: set any value to get a .zip with main and aux files instead of an individual file - :resheader Content-Type: application/json - :status 200: calc raw data successfully retrieved - :status 404: calc with given hashes does not exist or the given aux file does not exist - :returns: the raw data in body - """ - archive_id = '%s/%s' % (upload_hash, calc_hash) - logger = get_logger(__name__, endpoint='raw', action='get', archive_id=archive_id) - - try: - repo = RepoCalc.get(id=archive_id) - except NotFoundError: - abort(404, message='There is no calculation for %s/%s' % (upload_hash, calc_hash)) - except Exception as e: - abort(500, message=str(e)) - - repository_file = RepositoryFile(upload_hash) - - @contextmanager - def raw_file(filename): - try: - the_file = repository_file.get_file(filename) - with the_file.open() as f: - yield f - except KeyError: - abort(404, message='The file %s does not exist.' % filename) - except FileNotFoundError: - abort(404, message='The file %s does not exist.' % filename) - - get_all = request.args.get('all', None) is not None - if get_all: - # retrieve the 'whole' calculation, meaning the mainfile and all aux files as - # a .zip archive - def generator(): - """ Stream a zip file with all files using zipstream. """ - def iterator(): - """ Replace the directory based iter of zipstream with an iter over all raw files. """ - def write(filename): - """ Write a raw file to the zipstream. """ - def iter_content(): - """ Iterate the raw file contents. """ - with raw_file(filename) as file_object: - while True: - data = file_object.read(1024) - if not data: - break - yield data - return dict(arcname=filename, iterable=iter_content()) - - yield write(repo.mainfile) - try: - for auxfile in repo.aux_files: - yield write(os.path.join(os.path.dirname(repo.mainfile), auxfile)) - except Exception as e: - logger.error('Exception while accessing auxfiles.', exc_info=e) - - zip_stream = zipstream.ZipFile(mode='w', compression=ZIP_DEFLATED) - zip_stream.paths_to_write = iterator() - - for chunk in zip_stream: - yield chunk - - response = Response(generator(), mimetype='application/zip') - response.headers['Content-Disposition'] = 'attachment; filename={}'.format('%s.zip' % archive_id) - return response - else: - # retrieve an individual raw file - auxfile = request.args.get('auxfile', None) - if auxfile: - filename = os.path.join(os.path.dirname(repo.mainfile), auxfile) - else: - filename = repo.mainfile - - try: - with raw_file(filename) as f: - rv = send_file( - f, - mimetype='application/octet-stream', - as_attachment=True, - attachment_filename=os.path.basename(filename)) - return rv - except HTTPException as e: - raise e - except Exception as e: - logger = get_logger( - __name__, endpoint='archive', action='get', - upload_hash=upload_hash, calc_hash=calc_hash) - logger.error('Exception on accessing archive', exc_info=e) - abort(500, message='Could not accessing the archive.') - - -@app.route('%s/admin/<string:operation>' % base_path, methods=['POST']) -def call_admin_operation(operation): - if operation == 'repair_uploads': - Upload.repair_all() - if operation == 'reset': - infrastructure.reset() - else: - abort(400, message='Unknown operation %s' % operation) - - return 'done', 200 - - -api.add_resource(UploadsRes, '%s/uploads' % base_path) -api.add_resource(UploadRes, '%s/uploads/<string:upload_id>' % base_path) -api.add_resource(UploadFileRes, '%s/uploads/<string:upload_id>/file' % base_path) -api.add_resource(RepoCalcsRes, '%s/repo' % base_path) -api.add_resource(RepoCalcRes, '%s/repo/<string:upload_hash>/<string:calc_hash>' % base_path) - - -if __name__ == '__main__': - app.run(debug=True, port=8000) diff --git a/nomad/api/__init__.py b/nomad/api/__init__.py new file mode 100644 index 0000000000..b837a030c8 --- /dev/null +++ b/nomad/api/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2018 Markus Scheidgen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an"AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .app import app +from . import upload, repository, archive + + +if __name__ == '__main__': + app.run(debug=True, port=8000) diff --git a/nomad/api/app.py b/nomad/api/app.py new file mode 100644 index 0000000000..ff96d31fd6 --- /dev/null +++ b/nomad/api/app.py @@ -0,0 +1,91 @@ +# Copyright 2018 Markus Scheidgen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an"AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from flask import Flask, g +from flask_restful import Api, abort +from flask_cors import CORS +from flask_httpauth import HTTPBasicAuth +import os.path + +from nomad import config, infrastructure +from nomad.user import User +from nomad.processing import Upload + +base_path = config.services.api_base_path + +app = Flask( + __name__, + static_url_path='%s/docs' % base_path, + static_folder=os.path.abspath(os.path.join(os.path.dirname(__file__), '../docs/.build/html'))) +CORS(app) + +app.config['SECRET_KEY'] = config.services.api_secret + +auth = HTTPBasicAuth() +api = Api(app) + + +@app.before_first_request +def setup(): + infrastructure.setup() + + +@auth.verify_password +def verify_password(username_or_token, password): + # first try to authenticate by token + g.user = User.verify_auth_token(username_or_token) + if not g.user: + # try to authenticate with username/password + try: + g.user = User.verify_user_password(username_or_token, password) + except Exception: + return False + + if not g.user: + return True # anonymous access + + return True + + +def login_really_required(func): + @auth.login_required + def wrapper(*args, **kwargs): + if g.user is None: + abort(401, message='Anonymous access is forbidden, authorization required') + else: + return func(*args, **kwargs) + wrapper.__name__ = func.__name__ + wrapper.__doc__ = func.__doc__ + return wrapper + + +@app.route('/api/token') +@login_really_required +def get_auth_token(): + assert False, 'All authorization is none via NOMAD-coe repository GUI' + # TODO all authorization is done via NOMAD-coe repository GUI + # token = g.user.generate_auth_token(600) + # return jsonify({'token': token.decode('ascii'), 'duration': 600}) + + +@app.route('%s/admin/<string:operation>' % base_path, methods=['POST']) +def call_admin_operation(operation): + if operation == 'repair_uploads': + Upload.repair_all() + if operation == 'reset': + infrastructure.reset() + else: + abort(400, message='Unknown operation %s' % operation) + + return 'done', 200 diff --git a/nomad/api/archive.py b/nomad/api/archive.py new file mode 100644 index 0000000000..4cf9347ee7 --- /dev/null +++ b/nomad/api/archive.py @@ -0,0 +1,123 @@ +# Copyright 2018 Markus Scheidgen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an"AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + +from flask import send_file +from flask_restful import abort + +from nomad import config +from nomad.files import ArchiveFile, ArchiveLogFile +from nomad.utils import get_logger + +from .app import app, base_path + + +@app.route('%s/logs/<string:upload_hash>/<string:calc_hash>' % base_path, methods=['GET']) +def get_calc_proc_log(upload_hash, calc_hash): + """ + Get calculation processing log. Calcs are references via *upload_hash*, *calc_hash* + pairs. + + .. :quickref: archive; Get calculation processing logs. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/logs/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 + Accept: application/json + + :param string upload_hash: the hash of the upload (from uploaded file contents) + :param string calc_hash: the hash of the calculation (from mainfile) + :resheader Content-Type: application/json + :status 200: calc successfully retrieved + :status 404: calc with given hashes does not exist + :returns: the log data, a line by line sequence of structured logs + """ + archive_id = '%s/%s' % (upload_hash, calc_hash) + + try: + archive = ArchiveLogFile(archive_id) + if not archive.exists(): + raise FileNotFoundError() + + archive_path = archive.os_path + + rv = send_file( + archive_path, + mimetype='application/text', + as_attachment=True, + attachment_filename=os.path.basename(archive_path)) + + return rv + except FileNotFoundError: + abort(404, message='Archive/calculation %s does not exist.' % archive_id) + except Exception as e: + logger = get_logger( + __name__, endpoint='logs', action='get', + upload_hash=upload_hash, calc_hash=calc_hash) + logger.error('Exception on accessing calc proc log', exc_info=e) + abort(500, message='Could not accessing the logs.') + + +@app.route('%s/archive/<string:upload_hash>/<string:calc_hash>' % base_path, methods=['GET']) +def get_calc(upload_hash, calc_hash): + """ + Get calculation data in archive form. Calcs are references via *upload_hash*, *calc_hash* + pairs. + + .. :quickref: archive; Get calculation data in archive form. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/archive/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 + Accept: application/json + + :param string upload_hash: the hash of the upload (from uploaded file contents) + :param string calc_hash: the hash of the calculation (from mainfile) + :resheader Content-Type: application/json + :status 200: calc successfully retrieved + :status 404: calc with given hashes does not exist + :returns: the metainfo formated JSON data of the requested calculation + """ + archive_id = '%s/%s' % (upload_hash, calc_hash) + + try: + archive = ArchiveFile(archive_id) + if not archive.exists(): + raise FileNotFoundError() + + archive_path = archive.os_path + + rv = send_file( + archive_path, + mimetype='application/json', + as_attachment=True, + attachment_filename=os.path.basename(archive_path)) + + if config.files.compress_archive: + rv.headers['Content-Encoding'] = 'gzip' + + return rv + except FileNotFoundError: + abort(404, message='Archive %s does not exist.' % archive_id) + except Exception as e: + logger = get_logger( + __name__, endpoint='archive', action='get', + upload_hash=upload_hash, calc_hash=calc_hash) + logger.error('Exception on accessing archive', exc_info=e) + abort(500, message='Could not accessing the archive.') diff --git a/nomad/api/repository.py b/nomad/api/repository.py new file mode 100644 index 0000000000..c5f887668c --- /dev/null +++ b/nomad/api/repository.py @@ -0,0 +1,305 @@ +# Copyright 2018 Markus Scheidgen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an"AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path +from contextlib import contextmanager +from zipfile import ZIP_DEFLATED + +import zipstream +from elasticsearch.exceptions import NotFoundError +from flask import Response, g, request, send_file +from flask_restful import Resource, abort +from werkzeug.exceptions import HTTPException + +from nomad.files import RepositoryFile +from nomad.repo import RepoCalc +from nomad.utils import get_logger + +from .app import api, app, auth, base_path + + +class RepoCalcRes(Resource): + def get(self, upload_hash, calc_hash): + """ + Get calculation data in repository form, which only entails the quanties shown + in the repository. This is basically the elastic search index entry for the + requested calculations. Calcs are references via *upload_hash*, *calc_hash* + pairs. + + .. :quickref: repo; Get calculation data in repository form. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/repo/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "calc_hash":"7ddvtfRfZAvc3Crr7jOJ8UH0T34I", + "upload_time":"2018-08-30T08:41:51.771367", + "upload_id":"5b87adb813a441000a70a968", + "upload_hash":"W36aqCzAKxOCfIiMFsBJh3nHPb4a", + "mainfile":"RopD3Mo8oMV_-E5bh8uW5PiiCRkH1/data/BrK_svSi/TFCC010.CAB/vasprun.xml.relax1", + "program_name":"VASP", + "program_version":"4.6.35 3Apr08 complex parallel LinuxIFC", + "chemical_composition_bulk_reduced":"BrKSi2", + "program_basis_set_type":"plane waves", + "atom_species":[ + 35, + 19, + 14, + 14 + ], + "system_type":"Bulk", + "crystal_system":"orthorhombic", + "space_group_number":47, + "configuration_raw_gid":"sq6wTJjRKb2VTajoDLVWDxHCgyN6i", + "XC_functional_name":"GGA_X_PBE" + } + + :param string upload_hash: the hash of the upload (from uploaded file contents) + :param string calc_hash: the hash of the calculation (from mainfile) + :resheader Content-Type: application/json + :status 200: calc successfully retrieved + :status 404: calc with given hashes does not exist + :returns: the repository calculation entry + """ + try: + return RepoCalc.get(id='%s/%s' % (upload_hash, calc_hash)).json_dict, 200 + except NotFoundError: + abort(404, message='There is no calculation for %s/%s' % (upload_hash, calc_hash)) + except Exception as e: + abort(500, message=str(e)) + + +class RepoCalcsRes(Resource): + @auth.login_required + def get(self): + """ + Get *'all'* calculations in repository from, paginated. + + .. :quickref: repo; Get *'all'* calculations in repository from, paginated. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/repo?page=1&per_page=25 HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "pagination":{ + "total":1, + "page":1, + "per_page":25 + }, + "results":[ + { + "calc_hash":"7ddvtfRfZAvc3Crr7jOJ8UH0T34I", + "upload_time":"2018-08-30T08:41:51.771367", + "upload_id":"5b87adb813a441000a70a968", + "upload_hash":"W36aqCzAKxOCfIiMFsBJh3nHPb4a", + "mainfile":"RopD3Mo8oMV_-E5bh8uW5PiiCRkH1/data/BrK_svSi/TFCC010.CAB/vasprun.xml.relax1", + "program_name":"VASP", + "program_version":"4.6.35 3Apr08 complex parallel LinuxIFC", + "chemical_composition_bulk_reduced":"BrKSi2", + "program_basis_set_type":"plane waves", + "atom_species":[ + 35, + 19, + 14, + 14 + ], + "system_type":"Bulk", + "crystal_system":"orthorhombic", + "space_group_number":47, + "configuration_raw_gid":"sq6wTJjRKb2VTajoDLVWDxHCgyN6i", + "XC_functional_name":"GGA_X_PBE" + } + ] + } + + :qparam int page: the page starting with 1 + :qparam int per_page: desired calcs per page + :qparam string owner: specifies which cals to return: all|user|staging, default is all + :resheader Content-Type: application/json + :status 200: calcs successfully retrieved + :returns: a list of repository entries in ``results`` and pagination info + """ + # TODO use argparse? bad request reponse an bad params, pagination as decorator + page = int(request.args.get('page', 1)) + per_page = int(request.args.get('per_page', 10)) + owner = request.args.get('owner', 'all') + + try: + assert page >= 1 + assert per_page > 0 + except AssertionError: + abort(400, message='invalid pagination') + + if owner == 'all': + search = RepoCalc.search().query('match_all') + elif owner == 'user': + if g.user is None: + abort(401, message='Authentication required for owner value user.') + search = RepoCalc.search().query('match_all') + search = search.filter('term', user_id=g.user.email) + elif owner == 'staging': + if g.user is None: + abort(401, message='Authentication required for owner value user.') + search = RepoCalc.search().query('match_all') + search = search.filter('term', user_id=g.user.email).filter('term', staging=True) + else: + abort(400, message='Invalid owner value. Valid values are all|user|staging, default is all') + + search = search[(page - 1) * per_page: page * per_page] + return { + 'pagination': { + 'total': search.count(), + 'page': page, + 'per_page': per_page + }, + 'results': [result.json_dict for result in search] + } + + +@app.route('%s/raw/<string:upload_hash>/<string:calc_hash>' % base_path, methods=['GET']) +def get_raw(upload_hash, calc_hash): + """ + Get calculation mainfile raw data. Calcs are references via *upload_hash*, *calc_hash* + pairs. Returns the mainfile, unless an aux_file is specified. Aux files are stored + in repository entries. See ``/repo`` endpoint. + + .. :quickref: repo; Get calculation raw data. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/raw/W36aqCzAKxOCfIiMFsBJh3nHPb4a/7ddvtfRfZAvc3Crr7jOJ8UH0T34I HTTP/1.1 + Accept: application/gz + + :param string upload_hash: the hash of the upload (from uploaded file contents) + :param string calc_hash: the hash of the calculation (from mainfile) + :qparam str auxfile: an optional aux_file to download the respective aux file, default is mainfile + :qparam all: set any value to get a .zip with main and aux files instead of an individual file + :resheader Content-Type: application/json + :status 200: calc raw data successfully retrieved + :status 404: calc with given hashes does not exist or the given aux file does not exist + :returns: the raw data in body + """ + archive_id = '%s/%s' % (upload_hash, calc_hash) + logger = get_logger(__name__, endpoint='raw', action='get', archive_id=archive_id) + + try: + repo = RepoCalc.get(id=archive_id) + except NotFoundError: + abort(404, message='There is no calculation for %s/%s' % (upload_hash, calc_hash)) + except Exception as e: + abort(500, message=str(e)) + + repository_file = RepositoryFile(upload_hash) + + @contextmanager + def raw_file(filename): + try: + the_file = repository_file.get_file(filename) + with the_file.open() as f: + yield f + except KeyError: + abort(404, message='The file %s does not exist.' % filename) + except FileNotFoundError: + abort(404, message='The file %s does not exist.' % filename) + + get_all = request.args.get('all', None) is not None + if get_all: + # retrieve the 'whole' calculation, meaning the mainfile and all aux files as + # a .zip archive + def generator(): + """ Stream a zip file with all files using zipstream. """ + def iterator(): + """ Replace the directory based iter of zipstream with an iter over all raw files. """ + def write(filename): + """ Write a raw file to the zipstream. """ + def iter_content(): + """ Iterate the raw file contents. """ + with raw_file(filename) as file_object: + while True: + data = file_object.read(1024) + if not data: + break + yield data + return dict(arcname=filename, iterable=iter_content()) + + yield write(repo.mainfile) + try: + for auxfile in repo.aux_files: + yield write(os.path.join(os.path.dirname(repo.mainfile), auxfile)) + except Exception as e: + logger.error('Exception while accessing auxfiles.', exc_info=e) + + zip_stream = zipstream.ZipFile(mode='w', compression=ZIP_DEFLATED) + zip_stream.paths_to_write = iterator() + + for chunk in zip_stream: + yield chunk + + response = Response(generator(), mimetype='application/zip') + response.headers['Content-Disposition'] = 'attachment; filename={}'.format('%s.zip' % archive_id) + return response + else: + # retrieve an individual raw file + auxfile = request.args.get('auxfile', None) + if auxfile: + filename = os.path.join(os.path.dirname(repo.mainfile), auxfile) + else: + filename = repo.mainfile + + try: + with raw_file(filename) as f: + rv = send_file( + f, + mimetype='application/octet-stream', + as_attachment=True, + attachment_filename=os.path.basename(filename)) + return rv + except HTTPException as e: + raise e + except Exception as e: + logger = get_logger( + __name__, endpoint='archive', action='get', + upload_hash=upload_hash, calc_hash=calc_hash) + logger.error('Exception on accessing archive', exc_info=e) + abort(500, message='Could not accessing the archive.') + + +api.add_resource(RepoCalcsRes, '%s/repo' % base_path) +api.add_resource(RepoCalcRes, '%s/repo/<string:upload_hash>/<string:calc_hash>' % base_path) diff --git a/nomad/api/upload.py b/nomad/api/upload.py new file mode 100644 index 0000000000..eb5541862b --- /dev/null +++ b/nomad/api/upload.py @@ -0,0 +1,424 @@ +# Copyright 2018 Markus Scheidgen +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an"AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import datetime + +from flask import g, request +from flask_restful import Resource, abort + +from nomad.files import UploadFile +from nomad.processing import NotAllowedDuringProcessing, Upload +from nomad.utils import get_logger + +from .app import api, base_path, login_really_required + + +class UploadsRes(Resource): + """ Uploads """ + @login_really_required + def get(self): + """ + Get a list of current users uploads. + + .. :quickref: upload; Get a list of current users uploads. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/uploads HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + [ + { + "name": "examples_vasp_6.zip", + "upload_id": "5b89469e0d80d40008077dbc", + "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", + "create_time": "2018-08-31T13:46:06.781000", + "upload_time": "2018-08-31T13:46:07.531000", + "is_stale": false, + "completed": true, + "status": "SUCCESS", + "current_task": "cleanup", + "tasks": ["uploading", "extracting", "parse_all", "cleanup"] + "errors": [], + "warnings": [] + } + ] + + :resheader Content-Type: application/json + :status 200: uploads successfully provided + :returns: list of :class:`nomad.data.Upload` + """ + return [upload.json_dict for upload in Upload.user_uploads(g.user)], 200 + + @login_really_required + def post(self): + """ + Create a new upload. Creating an upload on its own wont do much, but provide + a *presigned* upload URL. PUT a file to this URL to do the actual upload and + initiate the processing. + + .. :quickref: upload; Create a new upload. + + **Example request**: + + .. sourcecode:: http + + POST /nomad/api/uploads HTTP/1.1 + Accept: application/json + Content-Type: application/json + + { + "name": "vasp_data.zip" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "name": "vasp_data.zip", + "upload_id": "5b89469e0d80d40008077dbc", + "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", + "create_time": "2018-08-31T13:46:06.781000", + "upload_time": "2018-08-31T13:46:07.531000", + "is_stale": false, + "completed": true, + "status": "SUCCESS", + "current_task": "cleanup", + "tasks": ["uploading", "extracting", "parse_all", "cleanup"] + "errors": [], + "warnings": [], + "calcs": [ + { + "current_task": "archiving", + "tasks": ["parsing", "normalizing", "archiving"] + "status": "SUCCESS", + "errors": [], + "warnings": [], + "parser": "parsers/vasp", + "mainfile": "Si.xml" + } + ] + } + + :jsonparam string name: An optional name for the upload. + :jsonparem string local_path: An optional path the a file that is already on the server. + In this case, uploading a file won't be possible, the local file is processed + immediatly as if it was uploaded. + :reqheader Content-Type: application/json + :resheader Content-Type: application/json + :status 200: upload successfully created + :returns: a new instance of :class:`nomad.data.Upload` + """ + json_data = request.get_json() + if json_data is None: + json_data = {} + + upload = Upload.create( + user=g.user, + name=json_data.get('name'), + local_path=json_data.get('local_path')) + + if upload.local_path is not None: + logger = get_logger( + __name__, endpoint='uploads', action='post', upload_id=upload.upload_id) + logger.info('file uploaded offline') + upload.upload_time = datetime.now() + upload.process() + logger.info('initiated processing') + + return upload.json_dict, 200 + + +class UploadRes(Resource): + """ Uploads """ + @login_really_required + def get(self, upload_id): + """ + Get an update on an existing upload. Will not only return the upload, but + also its calculations paginated. Use the pagination params to determine + the page. + + .. :quickref: upload; Get an update for an existing upload. + + **Example request**: + + .. sourcecode:: http + + GET /nomad/api/uploads/5b89469e0d80d40008077dbc HTTP/1.1 + Accept: application/json + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Vary: Accept + Content-Type: application/json + + { + "name": "vasp_data.zip", + "upload_id": "5b89469e0d80d40008077dbc", + "presigned_url": "http://minio:9000/uploads/5b89469e0d80d40008077dbc?X-Amz-Algorithm=AWS4-...", + "create_time": "2018-08-31T13:46:06.781000", + "upload_time": "2018-08-31T13:46:07.531000", + "is_stale": false, + "completed": true, + "status": "SUCCESS", + "current_task": "cleanup", + "tasks": ["uploading", "extracting", "parse_all", "cleanup"] + "errors": [], + "warnings": [], + "calcs": { + "pagination": { + "total": 1, + "page": 1, + "per_page": 25 + }, + "results": [ + { + "current_task": "archiving", + "tasks": ["parsing", "normalizing", "archiving"] + "status": "SUCCESS", + "errors": [], + "warnings": [], + "parser": "parsers/vasp", + "mainfile": "Si.xml" + } + ] + } + } + + :param string upload_id: the id for the upload + :qparam int page: the page starting with 1 + :qparam int per_page: desired calcs per page + :qparam str order_by: the field to sort the calcs by, use [status,mainfile] + :resheader Content-Type: application/json + :status 200: upload successfully updated and retrieved + :status 404: upload with id does not exist + :returns: the :class:`nomad.data.Upload` instance + """ + try: + upload = Upload.get(upload_id) + except KeyError: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + if upload.user_id != g.user.email: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + try: + page = int(request.args.get('page', 1)) + per_page = int(request.args.get('per_page', 10)) + order_by = str(request.args.get('order_by', 'mainfile')) + order = int(str(request.args.get('order', -1))) + except Exception: + abort(400, message='invalid pagination or ordering') + + try: + assert page >= 1 + assert per_page > 0 + except AssertionError: + abort(400, message='invalid pagination') + + if order_by not in ['mainfile', 'status', 'parser']: + abort(400, message='invalid order_by field %s' % order_by) + + order_by = ('-%s' if order == -1 else '+%s') % order_by + + calcs = upload.all_calcs((page - 1) * per_page, page * per_page, order_by) + failed_calcs = upload.failed_calcs + result = upload.json_dict + result['calcs'] = { + 'pagination': dict( + total=upload.total_calcs, page=page, per_page=per_page, + successes=upload.processed_calcs - failed_calcs, failures=failed_calcs), + 'results': [calc.json_dict for calc in calcs] + } + + return result, 200 + + @login_really_required + def post(self, upload_id): + """ + Move an upload out of the staging area. This changes the visibility of the upload. + Clients can specify, if the calcs should be restricted. + + .. :quickref: upload; Move an upload out of the staging area. + + **Example request**: + + .. sourcecode:: http + + POST /nomad/api/uploads HTTP/1.1 + Accept: application/json + Content-Type: application/json + + { + "operation": "unstage" + } + + + :param string upload_id: the upload id + :resheader Content-Type: application/json + :status 200: upload unstaged successfully + :status 404: upload could not be found + :status 400: if the operation is not supported + :returns: the upload record + """ + try: + upload = Upload.get(upload_id) + except KeyError: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + if upload.user_id != g.user.email: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + json_data = request.get_json() + if json_data is None: + json_data = {} + + operation = json_data.get('operation') + if operation == 'unstage': + upload.unstage() + return upload.json_dict, 200 + + abort(400, message='Unsuported operation %s.' % operation) + + @login_really_required + def delete(self, upload_id): + """ + Deletes an existing upload. Only ``is_ready`` or ``is_stale`` uploads + can be deleted. Deleting an upload in processing is not allowed. + + .. :quickref: upload; Delete an existing upload. + + **Example request**: + + .. sourcecode:: http + + DELETE /nomad/api/uploads/5b89469e0d80d40008077dbc HTTP/1.1 + Accept: application/json + + :param string upload_id: the id for the upload + :resheader Content-Type: application/json + :status 200: upload successfully deleted + :status 400: upload cannot be deleted + :status 404: upload with id does not exist + :returns: the :class:`nomad.data.Upload` instance with the latest processing state + """ + try: + upload = Upload.get(upload_id) + except KeyError: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + if upload.user_id != g.user.email: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + try: + upload.delete() + return upload.json_dict, 200 + except NotAllowedDuringProcessing: + abort(400, message='You must not delete an upload during processing.') + + +class UploadFileRes(Resource): + """ + Upload a file to an existing upload. Can be used to upload files via bowser + or other http clients like curl. This will start the processing of the upload. + + There are two basic ways to upload a file: multipart-formdata or simply streaming + the file data. Both are supported. The later one does not allow to transfer a + filename or other meta-data. If a filename is available, it will become the + name of the upload. + + .. :quickref: upload; Upload a file to an existing upload. + + **Curl examples for both approaches**: + + .. sourcecode:: sh + + curl -X put "/nomad/api/uploads/5b89469e0d80d40008077dbc/file" -F file=@local_file + curl "/nomad/api/uploads/5b89469e0d80d40008077dbc/file" --upload-file local_file + + :param string upload_id: the upload_id of the upload + :resheader Content-Type: application/json + :status 200: upload successfully received. + :status 404: upload with given id does not exist + :status 400: if the fileformat is not supported or the form data is different than expected. + :returns: the upload (see GET /uploads/<upload_id>) + """ + @login_really_required + def put(self, upload_id): + logger = get_logger(__name__, endpoint='upload', action='put', upload_id=upload_id) + + try: + upload = Upload.get(upload_id) + except KeyError: + abort(404, message='Upload with id %s does not exist.' % upload_id) + + if upload.upload_time is not None: + abort(400, message='A file was already uploaded to this uploade before.') + + uploadFile = UploadFile(upload_id) + + if request.mimetype == 'application/multipart-formdata': + # multipart formdata, e.g. with curl -X put "url" -F file=@local_file + # might have performance issues for large files: https://github.com/pallets/flask/issues/2086 + if 'file' in request.files: + abort(400, message='Bad multipart-formdata, there is no file part.') + file = request.files['file'] + if upload.name is '': + upload.name = file.filename + + file.save(uploadFile.os_path) + else: + # simple streaming data in HTTP body, e.g. with curl "url" -T local_file + try: + with uploadFile.open('wb') as f: + while not request.stream.is_exhausted: + f.write(request.stream.read(1024)) + + except Exception as e: + logger.error('Error on streaming upload', exc_info=e) + abort(400, message='Some IO went wrong, download probably aborted/disrupted.') + + if not uploadFile.is_valid: + uploadFile.delete() + abort(400, message='Bad file format, excpected %s.' % ", ".join(UploadFile.formats)) + + logger.info('received uploaded file') + upload.upload_time = datetime.now() + upload.process() + logger.info('initiated processing') + + return upload.json_dict, 200 + + +api.add_resource(UploadsRes, '%s/uploads' % base_path) +api.add_resource(UploadRes, '%s/uploads/<string:upload_id>' % base_path) +api.add_resource(UploadFileRes, '%s/uploads/<string:upload_id>/file' % base_path) diff --git a/nomad/client.py b/nomad/client.py index a602884b0e..8893bf8a5a 100644 --- a/nomad/client.py +++ b/nomad/client.py @@ -35,8 +35,8 @@ from nomad.normalizing import normalizers api_base = 'http://localhost/nomad/api' -user = 'other@gmail.com' -pw = 'nomad' +user = 'leonard.hofstadter@nomad-fairdi.tests.de' +pw = 'password' def handle_common_errors(func): @@ -325,11 +325,13 @@ def api(): @cli.command(help='Runs tests and linting. Useful before commit code.') -def qa(): +@click.option('--skip-tests', help='Do not test, just do code checks.', is_flag=True) +def qa(skip_tests: bool): os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) ret_code = 0 - click.echo('Run tests ...') - ret_code += os.system('python -m pytest tests') + if not skip_tests: + click.echo('Run tests ...') + ret_code += os.system('python -m pytest tests') click.echo('Run code style checks ...') ret_code += os.system('python -m pycodestyle --ignore=E501,E701 nomad tests') click.echo('Run linter ...') diff --git a/nomad/dependencies.py b/nomad/dependencies.py index f6d7ce65d5..f910cfceb0 100644 --- a/nomad/dependencies.py +++ b/nomad/dependencies.py @@ -158,11 +158,12 @@ class PythonGit(): dependencies = [ - PythonGit( - name='repository-api', - git_url='https://gitlab.mpcdf.mpg.de/NoMaD/NomadRepositoryParser.git', - git_branch='v2.1' - ), + # repository api is not really usuable, because it is written in python 2.x + # PythonGit( + # name='repository-api', + # git_url='https://gitlab.mpcdf.mpg.de/NoMaD/NomadRepositoryParser.git', + # git_branch='v2.1' + # ), PythonGit( name='nomad-meta-info', git_url='https://gitlab.mpcdf.mpg.de/nomad-lab/nomad-meta-info.git', diff --git a/nomad/infrastructure.py b/nomad/infrastructure.py index 7faca085a8..8f60712d34 100644 --- a/nomad/infrastructure.py +++ b/nomad/infrastructure.py @@ -33,15 +33,17 @@ mongo_client = None """ The pymongo mongodb client. """ +repository_db = None +""" The repository postgres db sqlalchemy client. """ + + def setup(): """ Creates connections to mongodb and elastic search. """ global elastic_client setup_logging() setup_mongo() setup_elastic() - - from nomad import user - user.ensure_test_users() + setup_repository_db() def setup_logging(): @@ -79,11 +81,20 @@ def setup_elastic(): logger.info('init elastic index') +def setup_repository_db(): + """ Creates a sqlalchemy session for the NOMAD-coe repository postgres db. """ + from sqlalchemy import create_engine + from sqlalchemy.orm import sessionmaker + + global repository_db + + engine = create_engine('postgresql://postgres:nomad@localhost:5432/nomad', echo=False) + repository_db = sessionmaker(bind=engine)() + + def reset(): - """ Resets the databases mongo/user and elastic/calcs. Be careful. """ + """ Resets the databases mongo and elastic/calcs. Be careful. """ mongo_client.drop_database(config.mongo.users_db) - from nomad import user - user.ensure_test_users() elastic_client.indices.delete(index=config.elastic.calc_index) from nomad.repo import RepoCalc diff --git a/nomad/processing/data.py b/nomad/processing/data.py index 02a78b6c8b..25ab0d6099 100644 --- a/nomad/processing/data.py +++ b/nomad/processing/data.py @@ -422,7 +422,7 @@ class Upload(Chord): kwargs.update(user_id=user.email) self = super().create(**kwargs) - basic_auth_token = base64.b64encode(b'%s:' % user.generate_auth_token()).decode('utf-8') + basic_auth_token = base64.b64encode(b'%s:' % user.get_auth_token()).decode('utf-8') self.upload_url = cls._external_objects_url('/uploads/%s/file' % self.upload_id) self.upload_command = 'curl -H "Authorization: Basic %s" "%s" --upload-file local_file' % ( diff --git a/nomad/user.py b/nomad/user.py index 16351bbb25..a006be6a24 100644 --- a/nomad/user.py +++ b/nomad/user.py @@ -16,79 +16,102 @@ Module with some prototypes/placeholder for future user management in nomad@FAIR. """ -from mongoengine import Document, EmailField, StringField, ReferenceField, ListField -from passlib.apps import custom_app_context as pwd_context -from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired +from passlib.hash import bcrypt +from sqlalchemy import Column, Integer, String +from sqlalchemy.ext.declarative import declarative_base -from nomad import config +from nomad import infrastructure -class User(Document): - """ Represents users in the database. """ - email = EmailField(primary_key=True) - name = StringField() - password_hash = StringField() +Base = declarative_base() - def hash_password(self, password): - self.password_hash = pwd_context.encrypt(password) - def verify_password(self, password): - return pwd_context.verify(password, self.password_hash) +class Session(Base): # type: ignore + __tablename__ = 'sessions' - def generate_auth_token(self, expiration=600): - s = Serializer(config.services.api_secret, expires_in=expiration) - return s.dumps({'id': self.id}) + token = Column(String, primary_key=True) + user_id = Column(String) + + +class LoginException(Exception): + pass + + +class User(Base): # type: ignore + """ + SQLAlchemy model class that represents NOMAD-coe repository postgresdb *users*. + Provides functions for authenticating via password or session token. + + It is not intended to create or update users. This should be done via the + NOMAD-coe repository GUI. + """ + __tablename__ = 'users' + + user_id = Column(Integer, primary_key=True) + email = Column(String) + firstname = Column(String) + lastname = Column(String) + password = Column(String) + + def __repr__(self): + return '<User(email="%s")>' % self.email + + def _hash_password(self, password): + assert False, 'Login functions are done by the NOMAD-coe repository GUI' + # password_hash = bcrypt.encrypt(password, ident='2y') + # self.password = password_hash + + def _verify_password(self, password): + return bcrypt.verify(password, self.password) + + def _generate_auth_token(self, expiration=600): + assert False, 'Login functions are done by the NOMAD-coe repository GUI' + + def get_auth_token(self): + repository_db = infrastructure.repository_db + session = repository_db.query(Session).filter_by(user_id=self.user_id).first() + if not session: + raise LoginException('No session, user probably not logged in at NOMAD-coe repository GUI') + + return session.token.encode('utf-8') + + @staticmethod + def verify_user_password(email, password): + repository_db = infrastructure.repository_db + user = repository_db.query(User).filter_by(email=email).first() + if not user: + return None + + if user._verify_password(password): + return user + else: + raise LoginException('Wrong password') @staticmethod def verify_auth_token(token): - s = Serializer(config.services.api_secret) - try: - data = s.loads(token) - except SignatureExpired: - return None # valid token, but expired - except BadSignature: - return None # invalid token - - return User.objects(email=data['id']).first() - - -class DataSet(Document): - name = StringField() - description = StringField() - doi = StringField() - - user = ReferenceField(User) - calcs = ListField(StringField) - - meta = { - 'indexes': [ - 'user', - 'doi', - 'calcs' - ] - } - - -# provid a test user for testing -me = None -other = None - - -def ensure_test_users(): - global me - me = User.objects(email='me@gmail.com').first() - if me is None: - me = User( - email='me@gmail.com', - name='Me Meyer') - me.hash_password('nomad') - me.save() - - global other - me = User.objects(email='other@gmail.com').first() - if me is None: - me = User( - email='other@gmail.com', - name='Other User') - me.hash_password('nomad') - me.save() + repository_db = infrastructure.repository_db + session = repository_db.query(Session).filter_by(token=token).first() + if session is None: + return None + + user = repository_db.query(User).filter_by(user_id=session.user_id).first() + assert user, 'User in sessions must exist.' + return user + + +def ensure_test_user(email): + """ + Allows tests to make sure that the default test users exist in the database. + Returns: + The user as :class:`User` instance. + """ + existing = infrastructure.repository_db.query(User).filter_by( + email=email).first() + assert existing, 'Test user %s does not exist.' % email + + session = infrastructure.repository_db.query(Session).filter_by( + user_id=existing.user_id).first() + assert session, 'Test user %s has no session.' % email + assert session.token == email, 'Test user %s session has unexpected token.' % email + + return existing diff --git a/repository/repo_postgres_schema b/repository/repo_postgres_schema new file mode 100644 index 0000000000..c473a17891 --- /dev/null +++ b/repository/repo_postgres_schema @@ -0,0 +1 @@ +- "login_token" or "sessions", whats the difference, what should be used for authenticating uploader. \ No newline at end of file diff --git a/repository/repository_es_example.js b/repository/repository_es_example.js new file mode 100644 index 0000000000..8a17b9e54b --- /dev/null +++ b/repository/repository_es_example.js @@ -0,0 +1,2375 @@ +{ + "took": 20, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "failed": 0 + }, + "hits": { + "total": 6349890, + "max_score": 1.0, + "hits": [ + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506001", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gt38MgRcejX1YUVjFk-cE2UNXDK88", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/vasprun.xml.relax2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/CHG.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/CHGCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/CONTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/DOSCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/EIGENVAL.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/INCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/KPOINTS.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/OUTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC010.CAB/POSCAR.relax2.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/CHG.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/CHGCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/CONTCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/DOSCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/EIGENVAL.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/INCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/KPOINTS.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/OUTCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/POSCAR.relax2.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC010.CAB/vasprun.xml.relax2", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506001, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 47 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "orthorhombic" + ], + "repository_checksum": "IQP6EKSSDVCBTOJZY6LNHCMFWVOCIDATBMCHPYQ6CVD6KCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "Tl2SnAg" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avqh", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506002", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gXRBk5EF_HmbWd4MWy6UHcub9R2YX", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/vasprun.xml.relax1", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/CHG.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/CHGCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/CONTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/DOSCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/EIGENVAL.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/INCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/KPOINTS.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/OUTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC011.ABC/POSCAR.relax1.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/CHG.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/CHGCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/CONTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/DOSCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/EIGENVAL.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/INCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/KPOINTS.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/OUTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/POSCAR.relax1.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC011.ABC/vasprun.xml.relax1", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506002, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 12 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "monoclinic" + ], + "repository_checksum": "MEMSZ2VNDBLPDCZAJFBSYL755HVXXRSMCULKBYS6UKLSSCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "TlSnAg2" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avqi", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506008", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gSrMDRLr5oWRwHc7KoDBDibWSJ3zm", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/vasprun.xml.relax1", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/CHG.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/CHGCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/CONTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/DOSCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/EIGENVAL.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/INCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/KPOINTS.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/OUTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC012.ABC/POSCAR.relax1.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/CHG.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/CHGCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/CONTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/DOSCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/EIGENVAL.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/INCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/KPOINTS.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/OUTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/POSCAR.relax1.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC012.ABC/vasprun.xml.relax1", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506008, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 123 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "tetragonal" + ], + "repository_checksum": "DWZ5L2NBIRSEEPMPCXA6N52X2KYWOE3FZPST2XQI5CK7MCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "TlSnAg2" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avqo", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506018", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gv8gAUiLhjMmiawnsSLzzuNqTE15o", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/vasprun.xml.relax1", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/CHG.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/CHGCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/CONTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/DOSCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/EIGENVAL.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/INCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/KPOINTS.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/OUTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC013.CAB/POSCAR.relax1.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/CHG.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/CHGCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/CONTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/DOSCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/EIGENVAL.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/INCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/KPOINTS.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/OUTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/POSCAR.relax1.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC013.CAB/vasprun.xml.relax1", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506018, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 139 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "tetragonal" + ], + "repository_checksum": "2TIEJZHCINQKA3RW52V7CYLXTVD2HZGXJBU2BV7LGP76OCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "Tl2SnAg" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avr2", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506021", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gWGK1JBXx3OD3cHewyDnM_wArEdux", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/vasprun.xml.relax2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/CHG.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/CHGCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/CONTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/DOSCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/EIGENVAL.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/INCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/KPOINTS.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/OUTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.ABC/POSCAR.relax2.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/CHG.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/CHGCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/CONTCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/DOSCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/EIGENVAL.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/INCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/KPOINTS.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/OUTCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/POSCAR.relax2.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.ABC/vasprun.xml.relax2", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506021, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 65 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "orthorhombic" + ], + "repository_checksum": "ZYTKKS2K6FZTJM5B4U5J7XMATBP6ZRTBFK6JJERDHA66ECI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "TlSnAg2" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avr5", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506022", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gvvel66ot1HiVN1LufST5WUWZ2t8K", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/vasprun.xml.relax1", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/CHG.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/CHGCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/CONTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/DOSCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/EIGENVAL.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/INCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/KPOINTS.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/OUTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC015.BCA/POSCAR.relax1.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/CHG.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/CHGCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/CONTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/DOSCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/EIGENVAL.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/INCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/KPOINTS.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/OUTCAR.relax1.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/POSCAR.relax1.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC015.BCA/vasprun.xml.relax1", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506022, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 65 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "orthorhombic" + ], + "repository_checksum": "ISXGCXRJOWV3MCCR5WILUXKFTD5LJLGVR4RDMNF46YPHGCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "TlSn2Ag" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avr6", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506031", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "ggrNZUdog7TC1060EzCsDuagBTxNN", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/vasprun.xml.relax2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/CHG.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/CHGCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/CONTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/DOSCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/EIGENVAL.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/INCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/KPOINTS.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/OUTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnTl_d/TFCC016.CAB/POSCAR.relax2.bz2" + ], + "secondary_file_uris": [ + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/CHG.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/CHGCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/CONTCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/DOSCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/EIGENVAL.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/INCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/KPOINTS.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/OUTCAR.relax2.bz2", + "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/POSCAR.relax2.bz2" + ], + "main_file_uri": "nmd://Re2j2mzMtvO08obtU3op-6fd1v7yl/data/AgSnTl_d/TFCC016.CAB/vasprun.xml.relax2", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506031, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "Tl" + ], + "repository_spacegroup_nr": [ + 123 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "tetragonal" + ], + "repository_checksum": "YWUJLRKDVHVCPFYW6T2D2XV7IUXGBEX6RFBC2HMWZKI36CI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "Tl2SnAg" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avrf", + "repository_archive_gid": [ + "Re2j2mzMtvO08obtU3op-6fd1v7yl" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506033", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "gQ91ORbXW-kxlqmPVqqa9PnIEngF8", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/vasprun.xml.relax2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/CHG.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/CHGCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/CONTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/DOSCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/EIGENVAL.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/INCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/KPOINTS.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/OUTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.ABC/POSCAR.relax2.bz2" + ], + "secondary_file_uris": [ + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/CHG.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/CHGCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/CONTCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/DOSCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/EIGENVAL.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/INCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/KPOINTS.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/OUTCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/POSCAR.relax2.bz2" + ], + "main_file_uri": "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.ABC/vasprun.xml.relax2", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506033, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "V" + ], + "repository_spacegroup_nr": [ + 99 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "tetragonal" + ], + "repository_checksum": "XRHLXUZL26HPJ2NQGUM7A5ZTZMEWZIFEXTCR5G4PAXZCKCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "SnAg2V" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avrh", + "repository_archive_gid": [ + "RB1HuRB75DziRkYLw2loQ5HBBerDR" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506035", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "grM4KBBhHBnPMVo7mm8YjR-kbzrA0", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/vasprun.xml.relax2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/CHG.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/CHGCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/CONTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/DOSCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/EIGENVAL.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/INCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/KPOINTS.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/OUTCAR.relax2.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.BCA/POSCAR.relax2.bz2" + ], + "secondary_file_uris": [ + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/CHG.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/CHGCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/CONTCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/DOSCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/EIGENVAL.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/INCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/KPOINTS.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/OUTCAR.relax2.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/POSCAR.relax2.bz2" + ], + "main_file_uri": "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.BCA/vasprun.xml.relax2", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506035, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "V" + ], + "repository_spacegroup_nr": [ + 99 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "tetragonal" + ], + "repository_checksum": "6H4ELR3CI62MTU32OYTUKPZGS74FXQ4ASB3WVSQBAWVSICI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "Sn2AgV" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avrj", + "repository_archive_gid": [ + "RB1HuRB75DziRkYLw2loQ5HBBerDR" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + }, + { + "_index": "index2018-11-14", + "_type": "repository_calculation", + "_id": "3506036", + "_score": 1.0, + "_source": { + "section_repository_info": { + "repository_grouping_checksum": "glj1RLL0DacCCtlvEboognORlpKHp", + "repository_filepaths": [ + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/vasprun.xml.relax1", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/CHG.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/CHGCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/CONTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/DOSCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/EIGENVAL.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/INCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/KPOINTS.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/OUTCAR.relax1.bz2", + "$EXTRACTED/ftp_upload_for_uid_125/aflowlib_data/LIB3_LIB/AgSnV_sv/TBCC006.CAB/POSCAR.relax1.bz2" + ], + "secondary_file_uris": [ + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/CHG.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/CHGCAR.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/CONTCAR.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/DOSCAR.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/EIGENVAL.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/INCAR.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/KPOINTS.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/OUTCAR.relax1.bz2", + "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/POSCAR.relax1.bz2" + ], + "main_file_uri": "nmd://RB1HuRB75DziRkYLw2loQ5HBBerDR/data/AgSnV_sv/TBCC006.CAB/vasprun.xml.relax1", + "section_repository_userdata": { + "section_citation": [ + { + "citation_repo_id": 52, + "citation_value": "http://aflowlib.org" + }, + { + "citation_repo_id": 53, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025614003322" + }, + { + "citation_repo_id": 54, + "citation_value": "http://www.sciencedirect.com/science/article/pii/S0927025612000687" + } + ], + "repository_access_now": "Open", + "section_author_info": [ + { + "author_repo_id": 134, + "author_first_name": "Frisco", + "author_last_name": "Rose", + "author_name": "Frisco Rose" + }, + { + "author_repo_id": 135, + "author_first_name": "Richard", + "author_last_name": "Taylor", + "author_name": "Richard Taylor" + }, + { + "author_repo_id": 136, + "author_first_name": "Ohad", + "author_last_name": "Levy", + "author_name": "Ohad Levy" + }, + { + "author_repo_id": 137, + "author_first_name": "Cormac", + "author_last_name": "Toher", + "author_name": "Cormac Toher" + }, + { + "author_repo_id": 138, + "author_first_name": "Marco Buongiorno", + "author_last_name": "Nardelli", + "author_name": "Marco Buongiorno Nardelli" + }, + { + "author_repo_id": 145, + "author_first_name": "Wahyu", + "author_last_name": "Setyawan", + "author_name": "Wahyu Setyawan" + }, + { + "author_repo_id": 146, + "author_first_name": "Shidong", + "author_last_name": "Wang", + "author_name": "Shidong Wang" + }, + { + "author_repo_id": 147, + "author_first_name": "Junkai", + "author_last_name": "Xue", + "author_name": "Junkai Xue" + }, + { + "author_repo_id": 148, + "author_first_name": "Kesong", + "author_last_name": "Yang", + "author_name": "Kesong Yang" + }, + { + "author_repo_id": 149, + "author_first_name": "Lance", + "author_last_name": "Nelson", + "author_name": "Lance Nelson" + }, + { + "author_repo_id": 150, + "author_first_name": "Gus", + "author_last_name": "Hart", + "author_name": "Gus Hart" + }, + { + "author_repo_id": 151, + "author_first_name": "Stefano", + "author_last_name": "Sanvito", + "author_name": "Stefano Sanvito" + }, + { + "author_repo_id": 152, + "author_first_name": "Natalio", + "author_last_name": "Mingo", + "author_name": "Natalio Mingo" + }, + { + "author_repo_id": 125, + "author_first_name": "Stefano", + "author_last_name": "Curtarolo", + "author_name": "Stefano Curtarolo" + } + ] + }, + "repository_calc_id": 3506036, + "upload_id": 967, + "section_repository_parserdata": { + "repository_code_version": [ + "VASP 4.6.35" + ], + "repository_atomic_elements": [ + "Ag", + "Sn", + "V" + ], + "repository_spacegroup_nr": [ + 99 + ], + "repository_system_type": [ + "Bulk" + ], + "repository_basis_set_type": [ + "plane waves" + ], + "repository_crystal_system": [ + "tetragonal" + ], + "repository_checksum": "LFXP7M7US6WD7WEE2YEERH5CJQRSFKGPO7RBAXAA67KWWCI", + "repository_parser_id": "RepoBaseParser", + "repository_chemical_formula": [ + "SnAgV2" + ], + "repository_atomic_elements_count": 3, + "repository_xc_treatment": [ + "GGA" + ], + "repository_program_name": [ + "VASP" + ] + }, + "section_uploader_info": { + "uploader_username": "stefano", + "uploader_last_name": "Curtarolo", + "uploader_repo_id": 125, + "uploader_first_name": "Stefano" + }, + "upload_date": 1425207600000, + "repository_calc_pid": "3avrk", + "repository_archive_gid": [ + "RB1HuRB75DziRkYLw2loQ5HBBerDR" + ] + }, + "query_auxiliary_data": { + "value_counts": { + "section_repository_info": { + "repository_grouping_checksum": 1, + "repository_filepaths": 10, + "secondary_file_uris": 9, + "main_file_uri": 1, + "section_repository_userdata": { + "section_citation": { + "citation_repo_id": 3, + "citation_value": 3 + }, + "repository_open_date": 0, + "repository_access_now": 1, + "repository_comment": 0, + "section_author_info": { + "author_name": 14, + "author_last_name": 14, + "author_first_name": 13, + "author_repo_id": 14 + } + }, + "repository_calc_id": 1, + "upload_id": 1, + "section_repository_parserdata": { + "repository_code_version": 1, + "repository_atomic_elements": 3, + "repository_spacegroup_nr": 1, + "repository_system_type": 1, + "repository_basis_set_type": 1, + "repository_crystal_system": 1, + "repository_checksum": 1, + "repository_parser_id": 1, + "repository_chemical_formula": 1, + "repository_atomic_elements_count": 1, + "repository_xc_treatment": 1, + "repository_program_name": 1 + }, + "section_uploader_info": { + "uploader_username": 1, + "uploader_last_name": 1, + "uploader_name": 0, + "uploader_repo_id": 1, + "uploader_first_name": 1 + }, + "upload_date": 1, + "repository_calc_pid": 1, + "repository_archive_gid": 1 + } + } + } + } + } + ] + } +} \ No newline at end of file diff --git a/repository/utils.http b/repository/utils.http new file mode 100644 index 0000000000..afa0e7a6b8 --- /dev/null +++ b/repository/utils.http @@ -0,0 +1,19 @@ +DELETE http://localhost:9200/index2018-11-15 HTTP/1.1; + +### +DELETE http://localhost:9200/topics2018-11-15 HTTP/1.1; + +### +GET http://localhost:9200/_cat/indices HTTP/1.1; + +### +GET http://localhost:9200/index2018-11-15/_search HTTP/1.1; + + +### +POST http://localhost:8111/repo/repo-update/index/calculation HTTP/1.1; +content-type: application/json + +{ + "calc_ids": [10] +} diff --git a/requirements.txt b/requirements.txt index f2e266a72d..a143a73875 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,6 @@ sphinxcontrib.httpdomain sphinx_rtd_theme zipstream bagit +psycopg2 +sqlalchemy +bcrypt diff --git a/tests/conftest.py b/tests/conftest.py index 3f711d1469..06aa62d576 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,7 +81,6 @@ def mockmongo(monkeypatch): disconnect() connection = connect('test_db', host='mongomock://localhost') monkeypatch.setattr('nomad.infrastructure.setup_mongo', lambda **kwargs: None) - user.ensure_test_users() yield @@ -94,6 +93,22 @@ def elastic(): assert infrastructure.elastic_client is not None +@pytest.fixture(scope='session') +def repository_db(): + infrastructure.setup_repository_db() + assert infrastructure.repository_db is not None + + +@pytest.fixture(scope='session') +def test_user(repository_db): + return user.ensure_test_user(email='sheldon.cooper@nomad-fairdi.tests.de') + + +@pytest.fixture(scope='session') +def other_test_user(repository_db): + return user.ensure_test_user(email='leonard.hofstadter@nomad-fairdi.tests.de') + + @pytest.fixture(scope='function') def mocksearch(monkeypatch): uploads_by_hash = {} diff --git a/tests/processing/test_data.py b/tests/processing/test_data.py index fd69a6728a..14033040c4 100644 --- a/tests/processing/test_data.py +++ b/tests/processing/test_data.py @@ -64,8 +64,8 @@ def uploaded_id_with_warning(request, clear_files) -> Generator[str, None, None] yield example_upload_id -def run_processing(uploaded_id: str) -> Upload: - upload = Upload.create(upload_id=uploaded_id, user=user.me) +def run_processing(uploaded_id: str, test_user) -> Upload: + upload = Upload.create(upload_id=uploaded_id, user=test_user) upload.upload_time = datetime.now() assert upload.status == 'RUNNING' @@ -110,33 +110,33 @@ def assert_processing(upload: Upload, mocksearch=None): @pytest.mark.timeout(30) -def test_processing(uploaded_id, worker, mocksearch, no_warn): - upload = run_processing(uploaded_id) +def test_processing(uploaded_id, worker, mocksearch, test_user, no_warn): + upload = run_processing(uploaded_id, test_user) assert_processing(upload, mocksearch) @pytest.mark.timeout(30) -def test_processing_with_warning(uploaded_id_with_warning, worker, mocksearch): - upload = run_processing(uploaded_id_with_warning) +def test_processing_with_warning(uploaded_id_with_warning, worker, test_user, mocksearch): + upload = run_processing(uploaded_id_with_warning, test_user) assert_processing(upload, mocksearch) @pytest.mark.parametrize('uploaded_id', [example_files[1]], indirect=True) -def test_processing_doublets(uploaded_id, worker, with_error): +def test_processing_doublets(uploaded_id, worker, test_user, with_error): - upload = run_processing(uploaded_id) + upload = run_processing(uploaded_id, test_user) assert upload.status == 'SUCCESS' assert RepoCalc.upload_exists(upload.upload_hash) # pylint: disable=E1101 - upload = run_processing(uploaded_id) + upload = run_processing(uploaded_id, test_user) assert upload.status == 'FAILURE' assert len(upload.errors) > 0 assert 'already' in upload.errors[0] @pytest.mark.timeout(30) -def test_process_non_existing(worker, with_error): - upload = run_processing('__does_not_exist') +def test_process_non_existing(worker, test_user, with_error): + upload = run_processing('__does_not_exist', test_user) assert upload.completed assert upload.current_task == 'extracting' @@ -146,7 +146,7 @@ def test_process_non_existing(worker, with_error): @pytest.mark.parametrize('task', ['extracting', 'parse_all', 'cleanup', 'parsing']) @pytest.mark.timeout(30) -def test_task_failure(monkeypatch, uploaded_id, worker, task, with_error): +def test_task_failure(monkeypatch, uploaded_id, worker, task, test_user, with_error): # mock the task method to through exceptions if hasattr(Upload, task): cls = Upload @@ -163,7 +163,7 @@ def test_task_failure(monkeypatch, uploaded_id, worker, task, with_error): monkeypatch.setattr('nomad.processing.data.%s.%s' % (cls.__name__, task), mock) # run the test - upload = run_processing(uploaded_id) + upload = run_processing(uploaded_id, test_user) assert upload.completed diff --git a/tests/test_api.py b/tests/test_api.py index 6e6624fbe1..e68e43ed52 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -32,7 +32,7 @@ from tests.test_repo import example_elastic_calc # noqa pylint: disable=unused- @pytest.fixture(scope='function') -def client(mockmongo): +def client(mockmongo, repository_db): disconnect() connect('users_test', host=config.mongo.host, port=config.mongo.port, is_mock=True) @@ -43,18 +43,23 @@ def client(mockmongo): Upload._get_collection().drop() -@pytest.fixture(scope='session') -def test_user_auth(): +def create_auth_headers(user): + basic_auth_str = '%s:password' % user.email + basic_auth_bytes = basic_auth_str.encode('utf-8') + basic_auth_base64 = base64.b64encode(basic_auth_bytes).decode('utf-8') return { - 'Authorization': 'Basic %s' % base64.b64encode(b'me@gmail.com:nomad').decode('utf-8') + 'Authorization': 'Basic %s' % basic_auth_base64 } @pytest.fixture(scope='session') -def test_other_user_auth(): - return { - 'Authorization': 'Basic %s' % base64.b64encode(b'other@gmail.com:nomad').decode('utf-8') - } +def test_user_auth(test_user): + return create_auth_headers(test_user) + + +@pytest.fixture(scope='session') +def test_other_user_auth(other_test_user): + return create_auth_headers(other_test_user) def assert_uploads(upload_json_str, count=0, **kwargs): diff --git a/tests/test_repo.py b/tests/test_repo.py index d789890029..ef572bf29a 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -27,7 +27,7 @@ from tests.test_parsing import parsed_template_example # pylint: disable=unused @pytest.fixture(scope='function') -def example_elastic_calc(normalized_template_example: LocalBackend, elastic) \ +def example_elastic_calc(normalized_template_example: LocalBackend, elastic, test_user) \ -> Generator[RepoCalc, None, None]: upload_file = UploadFile('test_upload_id', local_path=example_file) @@ -49,7 +49,7 @@ def example_elastic_calc(normalized_template_example: LocalBackend, elastic) \ additional=dict( mainfile=mainfile, upload_time=datetime.now(), - staging=True, restricted=False, user_id='me@gmail.com', + staging=True, restricted=False, user_id=test_user.email, aux_files=auxfiles), refresh='true') @@ -82,7 +82,7 @@ def test_create_elastic_calc(example_elastic_calc: RepoCalc, no_warn): def test_create_existing_elastic_calc( - example_elastic_calc: RepoCalc, normalized_template_example): + example_elastic_calc: RepoCalc, normalized_template_example, test_user): try: RepoCalc.create_from_backend( normalized_template_example, @@ -92,7 +92,7 @@ def test_create_existing_elastic_calc( additional=dict( mainfile='/test/mainfile', upload_time=datetime.now(), - staging=True, restricted=False, user_id='me'), + staging=True, restricted=False, user_id=test_user.email), refresh='true') assert False except AlreadyExists: diff --git a/tests/test_user.py b/tests/test_user.py new file mode 100644 index 0000000000..84b0b200bf --- /dev/null +++ b/tests/test_user.py @@ -0,0 +1,16 @@ +from nomad.user import User + + +def assert_user(user, reference): + assert user is not None + assert user.user_id == reference.user_id + + +def test_token_authorize(test_user): + user = User.verify_auth_token(test_user.email) + assert_user(user, test_user) + + +def test_password_authorize(test_user): + user = User.verify_user_password(test_user.email, 'password') + assert_user(user, test_user) -- GitLab