diff --git a/lib/vsc/administration/vo.py b/lib/vsc/administration/vo.py index 38a4aaa7..3935ed58 100644 --- a/lib/vsc/administration/vo.py +++ b/lib/vsc/administration/vo.py @@ -36,7 +36,7 @@ from vsc.config.base import ( VSC, VSC_HOME, VSC_DATA, VSC_DATA_SHARED, NEW, MODIFIED, MODIFY, ACTIVE, GENT, DATA_KEY, SCRATCH_KEY, DEFAULT_VOS_ALL, VSC_PRODUCTION_SCRATCH, INSTITUTE_VOS_BY_INSTITUTE, VO_SHARED_PREFIX_BY_INSTITUTE, - VO_PREFIX_BY_INSTITUTE, STORAGE_SHARED_SUFFIX + VO_PREFIX_BY_INSTITUTE, STORAGE_ACL_VO_MOD_GRP_KEY, STORAGE_SHARED_SUFFIX, ) from vsc.utils.missing import Monoid, MonoidDict @@ -92,7 +92,7 @@ def __init__(self, vo_id, storage=None, rest_client=None, host_institute=GENT): self._vo_data_shared_quota_cache = None self._vo_scratch_quota_cache = None self._institute_quota_cache = None - + self._modgroup_cache = None self._sharing_group_cache = None @property @@ -164,6 +164,20 @@ def members(self): """Return a list with all the VO members in it.""" return self.vo.members + @property + def modgroup(self): + """ + Return dict with Moderator group of the VO + {vsc_id: str, vsc_id_number: int, status: str, institute: {name: str}, + members: [], moderators: [], description: str, active: bool, isactive: bool} + """ + if not self._modgroup_cache: + self._modgroup_cache = whenHTTPErrorRaise( + self.rest_client.vo[self.vo.vsc_id].modgroup.get, + f"Could not get modgroup information from accountpage for VO {self.vo.vsc_id}" + )[1] + return self._modgroup_cache + def _get_path(self, storage_name, mount_point=MOUNT_POINT_DEFAULT): """Get the path for the (if any) user directory on the given storage.""" (path, _) = self.storage.path_templates[self.host_institute][storage_name]['vo'](self.vo.vsc_id) @@ -200,6 +214,16 @@ def _create_vo_fileset(self, storage, path, parent_fileset=None, fileset_name=No else: storage.operator().chown(moderator.vsc_id_number, fileset_group_owner_id, path) + # add ACLs to control access to VO by moderator group + if storage.acl_permissions_vo: + # replace placeholders in ACLs with actual values + acl_template_values = { + STORAGE_ACL_VO_MOD_GRP_KEY: self.modgroup['vsc_id_number'], + } + acl_rules = [rule.format(**acl_template_values) for rule in storage.acl_permissions_vo] + logging.debug(f"Setting ACLs on VO {self.vo.vsc_id} root directory: {', '.join(acl_rules)}") + storage.operator().replace_acl(path, acl_rules) + def create_data_fileset(self): """Create the VO's directory on the HPC data filesystem. Always set the quota.""" path = self._data_path() diff --git a/test/filesystem_info.conf b/test/filesystem_info.conf index 80924a75..88a7dfd2 100644 --- a/test/filesystem_info.conf +++ b/test/filesystem_info.conf @@ -113,6 +113,9 @@ quota_user_inode=40000 quota_vo_inode=1048576 user_path=/data operator_config_file=test/oceanstor_api.conf +acl_permissions_vo= + A:d:OWNER@:rwaDdxtTnNcoy + A:fdg:{vo_mod_gid}:rwaDdxtTnNcoy [BRUSSEL_VSC_SCRATCH_RHEA] cluster=storage @@ -127,6 +130,9 @@ quota_user_inode=40000 quota_vo_inode=1048576 data_replication_factor=1 user_path=/rhea/scratch +acl_permissions_vo= + A:d:OWNER@:rwaDdxtTnNcoy + A:fdg:{vo_mod_gid}:rwaDdxtTnNcoy [GENT_VSC_SCRATCH_MUK] cluster=muk diff --git a/test/vo.py b/test/vo.py index 2dc13397..52f5c679 100644 --- a/test/vo.py +++ b/test/vo.py @@ -455,6 +455,13 @@ def test_process_brussel_vo(self, mock_client): }, ], ) + mc.vo[test_vo_id].modgroup.get.return_value = ( + 200, + { + 'vsc_id': 'bvo99999_mods', + 'vsc_id_number': 9999999, + }, + ) with patch('vsc.administration.base.StorageOperator') as mock_storage_operator: operator = mock.MagicMock() @@ -489,6 +496,10 @@ def test_process_brussel_vo(self, mock_client): operator().create_stat_directory.assert_called_with( "/vscmnt/brussel_pixiu_data/_data_brussel/brussel/vo/000/bvo00005/vsc10001", 448, 2510001, 1, override_permissions=False ) + operator().replace_acl.assert_called_with( + "/vscmnt/brussel_pixiu_data/_data_brussel/brussel/vo/000/bvo00005", + ['A:d:OWNER@:rwaDdxtTnNcoy', 'A:fdg:9999999:rwaDdxtTnNcoy'] + ) # VSC_SCRATCH test ok, errors = vo.process_vos( @@ -510,6 +521,10 @@ def test_process_brussel_vo(self, mock_client): operator().create_stat_directory.assert_called_with( "/rhea/scratch/brussel/vo/000/bvo00005/vsc10001", 448, 2510001, 1, override_permissions=False ) + operator().replace_acl.assert_called_with( + "/rhea/scratch/brussel/vo/000/bvo00005", + ['A:d:OWNER@:rwaDdxtTnNcoy', 'A:fdg:9999999:rwaDdxtTnNcoy'] + ) @patch("vsc.accountpage.client.AccountpageClient", autospec=True) def test_process_brussel_default_vo(self, mock_client): @@ -793,6 +808,13 @@ def test_process_brussel_default_vo_gent_user(self, mock_client): }, ], ) + mc.vo[test_vo_id].modgroup.get.return_value = ( + 200, + { + 'vsc_id': 'bvo99999_mods', + 'vsc_id_number': 9999999, + }, + ) with patch('vsc.administration.base.StorageOperator') as mock_storage_operator: operator = mock.MagicMock() @@ -828,3 +850,7 @@ def test_process_brussel_default_vo_gent_user(self, mock_client): operator().create_stat_directory.assert_called_with( "/rhea/scratch/brussel/vo/000/bvo00003/vsc40002", 448, 2540002, 1, override_permissions=False ) + operator().replace_acl.assert_called_with( + "/rhea/scratch/brussel/vo/000/bvo00003", + ['A:d:OWNER@:rwaDdxtTnNcoy', 'A:fdg:9999999:rwaDdxtTnNcoy'] + )