Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 92 additions & 52 deletions docker/rucio_client/scripts/CMSRSE.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import json

from rucio.client.client import Client
from rucio.common.exception import RSEProtocolNotSupported, RSENotFound
from rucio.common.exception import RSEProtocolNotSupported, RSENotFound, Duplicate

APPROVAL_REQUIRED = ['T1_DE_KIT_Tape', 'T1_ES_PIC_Tape', 'T1_RU_JINR_Tape', 'T1_UK_RAL_Tape', 'T1_US_FNAL_Tape']
DOMAINS_BY_TYPE = {
Expand Down Expand Up @@ -40,17 +40,39 @@
'WebDAV': 'rucio.rse.protocols.gfal.Default'}
DEFAULT_PORTS = {'gsiftp': 2811, 'root': 1094, 'davs': 443}

# from rucio.core.rse
# RSE settings/properties that can be modified
MUTABLE_RSE_PROPERTIES = {
'name',
'availability_read',
'availability_write',
'availability_delete',
'latitude',
'longitude',
'time_zone',
'rse_type',
'volatile',
'deterministic',
'region_code',
'country_name',
'city',
'staging_area',
'qos_class',
'continent',
'availability'
}


class CMSRSE:
"""
Wrapping the definition of a CMS RSE. Gathering the information
from PhEDEx and translating them into the definition of a Rucio RSE
from JSON and translating them into the definition of a Rucio RSE
for the different expected types: real, test, temp.
"""

def __init__(self, json, dry=False, cms_type='real', deterministic=True):
def __init__(self, site_json: dict, attributes: dict, dry=False, cms_type='real', deterministic=True):

self.json = json
self.json = site_json
self.dry = dry
self.cms_type = cms_type

Expand All @@ -60,34 +82,41 @@ def __init__(self, json, dry=False, cms_type='real', deterministic=True):
self.attrs = {}
self.settings = {}
self.settings['deterministic'] = deterministic
self.rucio_rse_type = json['type'].upper()
self.rucio_rse_type = site_json['type'].upper()

xattrs = {}

# If we are building a _Test or _Temp instance add the special prefix
# If we are building a _Test or _Temp instance add the special suffix
if cms_type == "test":
self.rse_name = json['rse']+"_Test"
self.rse_name = site_json['rse']+"_Test"
elif cms_type == "temp":
self.rse_name = json['rse']+"_Temp"
self.rse_name = site_json['rse']+"_Temp"
else:
self.rse_name = json['rse']
if json.get('loadtest', None) is not None:
xattrs['loadtest'] = json['loadtest']
self.rse_name = site_json['rse']
if site_json.get('loadtest', None) is not None:
xattrs['loadtest'] = site_json['loadtest']

xattrs['fts'] = ','.join(json['fts'])
xattrs['fts'] = ','.join(site_json['fts'])
self._get_attributes(xattrs=xattrs)

"""
Parses either a prefix or a pfn within a rule in the storage.json
@url is something like:
- root://redirector.t2.ucsd.edu:1094//$1
- davs://xrootd.ultralight.org:1094
- srm://cmsrm-se01.roma1.infn.it:8443/srm/managerv2?SFN=/pnfs/roma1.infn.it/data/cms
@protocol_name. Is one of RUCIO_PROTOS = ['SRMv2', 'XRootD', 'WebDAV']
@is_prefix. Tell use whethere we are analyzing a prefix or a rule from the TFC
"""
for k, v in attributes:
if k in MUTABLE_RSE_PROPERTIES:
# extract settings
self.settings[k] = v
else:
self.attrs[k] = v

def _parse_url(self, url, protocol_name, is_prefix):
"""
Parses either a prefix or a pfn within a rule in the storage.json

@url is something like:
- root://redirector.t2.ucsd.edu:1094//$1
- davs://xrootd.ultralight.org:1094
- srm://cmsrm-se01.roma1.infn.it:8443/srm/managerv2?SFN=/pnfs/roma1.infn.it/data/cms
@protocol_name. Is one of RUCIO_PROTOS = ['SRMv2', 'XRootD', 'WebDAV']
@is_prefix. Tell use whethere we are analyzing a prefix or a rule from the TFC
"""
error = False
prefix_regexp_list = [
{'type': 1, 'regexp': re.compile(
Expand Down Expand Up @@ -204,10 +233,13 @@ def _verify_and_fix(self, rule_pfn, proto):

return status, pfn

def _get_settings(self):
pass

def _get_attributes(self, tier=None, country=None, xattrs=None):
"""
Gets the expected RSE attributes according to the
given cmsrse parameters and to the info from phedex
given cmsrse parameters and to the info from JSON
:fts: fts server. If None the server defined for
the pnn is taken.
:tier: tier. If None it is taken from pnn
Expand Down Expand Up @@ -254,47 +286,52 @@ def _get_attributes(self, tier=None, country=None, xattrs=None):
return

def _set_attributes(self):
"""
Sets the attributes from JSON to Rucio
"""
# Fetch the current rse attributes from Rucio
try:
rattrs = self.rcli.list_rse_attributes(rse=self.rse_name)
except RSENotFound:
rattrs = {}

changed = False

for (key, value) in self.attrs.items():
if key not in rattrs or rattrs[key] != value:
# Hack. I can find no way to define an attribute to 1
# (systematically reinterpreted as True)
if key in rattrs and rattrs[key] is True and \
(str(value) == '1' or str(value) == 'True'):
continue

if key not in rattrs:
rattrs[key] = 'None'
logging.debug('setting attribute %s from value %s to value %s for rse %s',
key, rattrs[key], value, self.rse_name)
changed = True
if self.dry:
logging.info('setting attribute %s to value %s for rse %s. Dry run, skipping',
key, value, self.rse_name)
else:
self.rcli.add_rse_attribute(rse=self.rse_name, key=key, value=value)
if rattrs.get('rse_attr_commit', None) and self.attrs['rse_attr_commit'] == rattrs.get('rse_attr_commit'):
logging.info('commit hash matches, skipping')
else:
for (key, value) in self.attrs.items():
if key not in rattrs or rattrs[key] != value:
# Hack. I can find no way to define an attribute to 1
# (systematically reinterpreted as True)
if key in rattrs and rattrs[key] is True and \
(str(value) == '1' or str(value) == 'True'):
continue

if key not in rattrs:
rattrs[key] = 'None'
logging.debug('setting attribute %s from value %s to value %s for rse %s',
key, rattrs[key], value, self.rse_name)
changed = True
if self.dry:
logging.info('setting attribute %s to value %s for rse %s. Dry run, skipping',
key, value, self.rse_name)
else:
try:
self.rcli.add_rse_attribute(rse=self.rse_name, key=key, value=value)
except Duplicate:
logging.info('attribute %s already exists. Updating to value %s for rse %s',
key, value, self.rse_name)
self.rcli.delete_rse_attribute(rse=self.rse_name, key=key)
self.rcli.add_rse_attribute(rse=self.rse_name, key=key, value=value)
return changed

def _get_protocol(self, proto_json, protos_json):
"""
Get the informations about the RSE protocol from creator argument or
from phedex
:seinfo: informations about the SE (in the form of the seinfo method of PhEDEx class).
If None the info is gathered from PhEDEx using the seinfo method.
:add_prefix: path to be added to the prefix in seinfo. if none
SE_ADD_PREFIX_BYTYPE is used.
:tfc: dictionnary with tfc rules. If None the info is gathered from PhEDEx using
the PhEDEx.tfc method,
:exclude: rules to be excluded from tfc (in case it is gathered from PhEDEx).
:domains: domains dictionnary. If none the DOMAINS_BYTYPE constant is used.
:token: space token. default None
:proto: protocol to be considered. default DEFAULT_PROTOCOL.
from JSON
:proto_json: specific protocol
:protos_json: all protocols
"""

protocol_name = proto_json['protocol']
Expand Down Expand Up @@ -544,6 +581,9 @@ def _set_protocols(self):
return new_changes

def _create_rse(self):
"""
Creates an RSE
"""

create = False

Expand All @@ -570,7 +610,7 @@ def _create_rse(self):
def update(self):
"""
Creates, if needed, and updates the RSE according
to CMS rules and PhEDEx data.
to CMS rules and JSON data.
"""
create_res = self._create_rse()

Expand Down
172 changes: 172 additions & 0 deletions docker/rucio_client/scripts/get_rse_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/bin/env python3

import base64
from collections import ChainMap, defaultdict
import json
import os
from pathlib import Path

import gitlab
from rucio.client import Client
from rucio.common.exception import Duplicate

IGNORED_RSE_INFO = ['id', 'protocols']

CMS_TYPES = ['test', 'temp']

# from rucio.core.rse
MUTABLE_RSE_PROPERTIES = {
'name',
'availability_read',
'availability_write',
'availability_delete',
'latitude',
'longitude',
'time_zone',
'rse_type',
'volatile',
'deterministic',
'region_code',
'country_name',
'city',
'staging_area',
'qos_class',
'continent',
'availability'
}

IGNORE = [
"cms_type",
"ddm_quota",
"dm_weight",
"freespace",
"fts",
"lfn2pfn_algorithm",
"loadtest",
"min_free_space_percentage",
"pnn",
"quota_approvers",
"region",
"rule_approvers",
"site_admins",
"tier",
"availability",
"availability_delete",
"availability_read",
"availability_write",
"deterministic",
"rse_type",
"volatile",
]


try:
client = Client()
except:
pass


def get_info_from_rucio(rse: str) -> dict:
'''
Gets RSE Settings and Attributes

Similar output to `rucio rse show`

returns a dict of settings and attributes
'''
rse_info = client.get_rse(rse=rse)
attributes = client.list_rse_attributes(rse=rse)

# Settings
settings = {key: rse_info[key] for i, key in enumerate(sorted(rse_info)) if key in MUTABLE_RSE_PROPERTIES}

return dict(ChainMap(settings, attributes))


def commit_to_gitlab(site: str, data: dict, dry_run: bool = True) -> bool:
private_token = os.environ['GITLAB_TOKEN']
gl = gitlab.Gitlab('https://gitlab.cern.ch', private_token=private_token)
group = gl.groups.get('siteconf')
projects = group.projects.list(all=True, search=site)
if len(projects) > 1:
print(f'multiple projects found for site: {site}')
return False
full_project = gl.projects.get(projects[0].id)
if not dry_run:
try:
commit = full_project.commits.create(data)
return True
except Exception as e:
print(e)
return False
else:
print(f"DRY RUN: {site}, {data}")
return False


def commit_to_personal_gitlab(site: str, data: dict, dry_run: bool = True) -> bool:
private_token = os.environ['GITLAB_TOKEN']
gl = gitlab.Gitlab('https://gitlab.cern.ch', private_token=private_token)
projects = gl.projects.list(owned=True, search=site)
if len(projects) > 1:
print(f'multiple projects found for site: {site}')
return False
full_project = gl.projects.get(projects[0].id)
if not dry_run:
try:
commit = full_project.commits.create(data)
return True
except Exception as e:
print(e)
return False
else:
print(f"DRY RUN: {site}, {data}")
return False


def extract_siteconf_project_name(rse: str) -> str:
to_remove = ['_Temp', '_Test', '_Tape', '_Disk']
for s in to_remove:
rse = rse.removesuffix(s)
return rse

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Settings is still coming?


if __name__ == '__main__':
rses_by_site = defaultdict(list)
rses = client.list_rses('update_from_json=True')
for rse in rses:
name = rse['rse']
rse_info = get_info_from_rucio(name)
file_dest = Path('rse_configs', f'{name}.json')

with open(file_dest, 'w') as f:
json.dump(rse_info, f, indent=4)

project_name = extract_siteconf_project_name(rse['rse'])
rses_by_site[project_name].append(rse['rse'])

#sites_to_commit = []
sites_to_ignore = ['T2_US_Nebraska']

for site, rses in rses_by_site.items():
if site in sites_to_commit:
print(rses)
a = {
'branch': 'master',
'commit_message': 'upload initial rse config',
'actions': []
}
for rse in rses:
file_src = Path('rse_configs', f'{rse}.json')
action = {
'action': 'create',
'file_path': f'rucio/{rse}.json',
'content': open(file_src, 'r').read(),
'author_email': 'dylee@fnal.gov',
'author_name': 'Dennis Lee'
}
a['actions'].append(action)

commit = commit_to_gitlab(site, a, dry_run=False)
#commit = commit_to_personal_gitlab(site, a, dry_run=False)

Loading