Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get all mapsets locks #200

Merged
merged 17 commits into from
May 27, 2021
32 changes: 28 additions & 4 deletions src/actinia_core/common/response_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@
from .process_chain import GrassModule

__license__ = "GPLv3"
__author__ = "Sören Gebbert"
__copyright__ = "Copyright 2016-2018, Sören Gebbert and mundialis GmbH & Co. KG"
__maintainer__ = "Sören Gebbert"
__email__ = "soerengebbert@googlemail.com"
__author__ = "Sören Gebbert, Julia Haas, Guido Riembauer"
__copyright__ = "Copyright 2016-2021, Sören Gebbert and mundialis GmbH & Co. KG"
__maintainer__ = "mundialis"


class ProgressInfoModel(Schema):
Expand Down Expand Up @@ -185,6 +184,31 @@ class SimpleResponseModel(Schema):
required = ["status", "message"]


class LockedMapsetListResponseModel(Schema):
"""Response schema that is used to list all locked mapsets.

"""
type = 'object'
properties = {
'status': {
'type': 'string',
'description': 'The status of the request'
},
'locked_mapsets_list': {
'type': 'array',
'items': {'type': 'string'},
'description': 'The names of all locked mapsets'
},
'message': {
'type': 'string',
'description': 'A simple message to describes the status of the resource'
}
}
required = ["status", "locked_mapsets_list", "message"]
example = {"status": "success", "locked_mapsets_list": ["utm32n/test_mapset"],
"message": "number of locked mapsets: 1"}


class ApiInfoModel(Schema):
"""Response schema that contains API information of the called endpoint.

Expand Down
7 changes: 7 additions & 0 deletions src/actinia_core/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from actinia_core.rest.location_management import \
ListLocationsResource, LocationManagementResourceUser
from actinia_core.rest.location_management import LocationManagementResourceAdmin
from actinia_core.rest.mapsets import AllMapsetsListingResourceAdmin
from actinia_core.rest.mapset_management import \
ListMapsetsResource, MapsetManagementResourceUser
from actinia_core.rest.mapset_management import \
Expand Down Expand Up @@ -106,6 +107,7 @@ def create_core_endpoints():
flask_api.add_resource(
MapsetLockManagementResource,
'/locations/<string:location_name>/mapsets/<string:mapset_name>/lock')

# Raster management
flask_api.add_resource(
RasterLayersResource, '/locations/<string:location_name>/mapsets/'
Expand Down Expand Up @@ -200,6 +202,11 @@ def create_core_endpoints():
AsyncEphemeralRasterLayerRegionExporterResource,
'/locations/<string:location_name>/mapsets/<string:mapset_name>'
'/raster_layers/<string:raster_name>/geotiff_async_orig')

# all mapsets across all locations listing
flask_api.add_resource(
AllMapsetsListingResourceAdmin, '/mapsets')

# User management
flask_api.add_resource(UserListResource, '/users')
flask_api.add_resource(UserManagementResource, '/users/<string:user_id>')
Expand Down
112 changes: 112 additions & 0 deletions src/actinia_core/rest/mapsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
#######
# actinia-core - an open source REST API for scalable, distributed, high
# performance processing of geographical data that uses GRASS GIS for
# computational tasks. For details, see https://actinia.mundialis.de/
#
# Copyright (c) 2021 mundialis GmbH & Co. KG
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#######

"""
Mapset resources for information across all locations

* List all mapset locks
* TODO: List all mapsets in all locations
"""

from flask import jsonify, make_response
from flask_restful_swagger_2 import swagger
from flask import request
from .resource_base import ResourceBase
from actinia_core.common.app import auth
from actinia_core.common.api_logger import log_api_call
from actinia_core.common.config import global_config
from actinia_core.common.redis_lock import RedisLockingInterface
from actinia_core.common.response_models import SimpleResponseModel, \
LockedMapsetListResponseModel
from .user_auth import check_user_permissions
from .user_auth import very_admin_role
# from .common.response_models import MapsetInfoModel


__license__ = "GPLv3"
__author__ = "Julia Haas, Guido Riembauer"
__copyright__ = "Copyright 2021 mundialis GmbH & Co. KG"
__maintainer__ = "mundialis"


class AllMapsetsListingResourceAdmin(ResourceBase):
""" Get all locked mapsets
"""
decorators = [log_api_call, check_user_permissions,
very_admin_role, auth.login_required]

@swagger.doc({
'tags': ['Mapsets'],
'description': 'Get all locked mapsets. '
'Minimum required user role: admin.',
'parameters': [
{
'in': 'path',
'name': 'status',
'type': 'string',
'description': ("If set to 'locked', list all locked mapsets across"
" all locations.")
}],
'responses': {
'200': {
'description': 'Get a list of (locked) mapsets ',
'schema': LockedMapsetListResponseModel
},
'500': {
'description': 'The error message and a detailed error log',
'schema': SimpleResponseModel
}
}
})
def get(self):
if 'status' in request.args:
if request.args['status'] == "locked":
redis_interface = RedisLockingInterface()
kwargs = dict()
kwargs["host"] = global_config.REDIS_SERVER_URL
kwargs["port"] = global_config.REDIS_SERVER_PORT
if (global_config.REDIS_SERVER_PW
and global_config.REDIS_SERVER_PW is not None):
kwargs["password"] = global_config.REDIS_SERVER_PW

redis_interface.connect(**kwargs)
redis_connection = redis_interface.redis_server
keys_locked = redis_connection.keys("RESOURCE-LOCK*")
redis_interface.disconnect()
keys_locked_dec = [key.decode() for key in keys_locked]
mapsets_locked = ["/".join(key.split("/")[-2:])
for key in keys_locked_dec]
try:
return make_response(jsonify(LockedMapsetListResponseModel(
status="success",
message="number of locked mapsets: %s" % len(mapsets_locked),
locked_mapsets_list=mapsets_locked)), 200)

except Exception as e:
return make_response(jsonify(SimpleResponseModel(
status="error",
message="Unable to list locked mapsets: Exception %s"
% (str(e)))), 500)
else:
# TODO: https://github.com/mundialis/actinia_core/issues/162
pass
92 changes: 92 additions & 0 deletions tests/test_mapsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
#######
# actinia-core - an open source REST API for scalable, distributed, high
# performance processing of geographical data that uses GRASS GIS for
# computational tasks. For details, see https://actinia.mundialis.de/
#
# Copyright (c) 2021 mundialis GmbH & Co. KG
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#######

"""
Tests: Mapset test case
"""
from flask.json import loads as json_load
import uuid
import unittest
try:
from .test_resource_base import ActiniaResourceTestCaseBase, URL_PREFIX
except:
from test_resource_base import ActiniaResourceTestCaseBase, URL_PREFIX

__license__ = "GPLv3"
__author__ = "Julia Haas, Guido Riembauer"
__copyright__ = "Copyright 2021 mundialis GmbH & Co. KG"
__maintainer__ = "mundialis"


class MapsetsTestCase(ActiniaResourceTestCaseBase):

test_mapsets = [str(uuid.uuid4()), str(uuid.uuid4())]

def tearDown(self):
# unlock and delete the test mapsets
rv = self.server.get(URL_PREFIX + '/locations/nc_spm_08/mapsets',
headers=self.user_auth_header)
existing_mapsets = json_load(rv.data)["process_results"]
for mapset in self.test_mapsets:
if mapset in existing_mapsets:
rvdellock = self.server.delete(
URL_PREFIX + '/locations/nc_spm_08/mapsets/%s/lock' % mapset,
headers=self.admin_auth_header)
print(rvdellock.data.decode())

rvdel = self.server.delete(
URL_PREFIX + '/locations/nc_spm_08/mapsets/%s' % mapset,
headers=self.admin_auth_header)
print(rvdel.data.decode())

def test_two_locked_mapsets(self):
# Test correct behaviour if two mapsets are locked
for mapset in self.test_mapsets:
self.create_new_mapset(mapset)
rvpost = self.server.post(URL_PREFIX + '/locations/nc_spm_08/mapsets/%s/lock' % mapset,
headers=self.admin_auth_header)
rv = self.server.get(URL_PREFIX + '/mapsets?status=locked',
headers=self.admin_auth_header)
self.assertEqual(rv.status_code, 200, "HTML status code is wrong %i" % rv.status_code)
self.assertEqual(rv.mimetype, "application/json", "Wrong mimetype %s" % rv.mimetype)
rvdata = json_load(rv.data)
mapset_list = rvdata["locked_mapsets_list"]
ref_list = ["nc_spm_08/{}".format(ms) for ms in self.test_mapsets]
for ref_mapset in ref_list:
self.assertIn(ref_mapset, mapset_list, "%s is not in the list of locked mapsets" % ref_mapset)

message = rvdata["message"]
mapsets_no = int(message.split(":")[-1])
self.assertGreaterEqual(mapsets_no, 2, "Number of locked mapsets is smaller than 2")

print("Currently there are %s locked mapsets:\n %s" % (str(mapsets_no), "\n".join(mapset_list)))

def test_user_error(self):
# Test correct behaviour if user role is not admin
rv = self.server.get(URL_PREFIX + '/mapsets?status=locked',
headers=self.user_auth_header)
self.assertEqual(rv.status_code, 401, "Status code is not 401: %s" % rv.status_code)


if __name__ == '__main__':
unittest.main()