Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-01 09:34:22

0001 """Seed operator-written descriptions for PanDA compute queues.
0002 
0003 Descriptions live in ``PandaQueue.metadata['description']``. The CRIC sync
0004 writers replace ``config_data`` and leave ``metadata`` untouched, so text
0005 seeded here survives every refresh. The queue detail page allows editing,
0006 so by default this command fills only queues with no description; pass
0007 ``--force`` to overwrite from this file.
0008 
0009 Each line answers what the queue is for and, where sibling queues exist,
0010 how it differs from them.
0011 """
0012 
0013 from django.core.management.base import BaseCommand
0014 from django.utils import timezone
0015 
0016 from monitor_app.models import PandaQueue
0017 
0018 DESCRIPTIONS = {
0019     # BNL grid and production
0020     'BNL_OSG_EPIC_PROD_1': (
0021         'Main ePIC production queue at BNL, reached through OSG. Carries the '
0022         'bulk of campaign simulation and reconstruction.'
0023     ),
0024     'BNL_EPIC_PROD_1': (
0025         'Earlier BNL production queue, direct rather than through OSG. '
0026         'Superseded by BNL_OSG_EPIC_PROD_1 for campaign work.'
0027     ),
0028     'BNL_PanDA_1': (
0029         'General-purpose BNL queue for user analysis and ad-hoc work, with a '
0030         '4-day limit. Not a campaign production queue.'
0031     ),
0032     'BNL_PanDA_test': (
0033         'Test twin of BNL_PanDA_1 for trying queue and pilot changes before '
0034         'they reach the production queues.'
0035     ),
0036     'BNL_OSG_PanDA_1': (
0037         'Short-job OSG queue at BNL, 3-hour limit. Suited to quick work that '
0038         'should not wait behind long production jobs.'
0039     ),
0040     'BNL_OSG_PanDA_CI': (
0041         'Continuous-integration queue: automated software validation jobs, '
0042         '3-hour limit.'
0043     ),
0044     'BNL_OSG_PanDA_pilotest': (
0045         'Reserved for pilot software testing. Used only when a new pilot '
0046         'release needs exercising against a real queue.'
0047     ),
0048 
0049     # GPU
0050     'BNL_NPPS_GPU': (
0051         'NPPS GPU server npps0 (2x RTX 4090), for the GPU optical-photon '
0052         'simulation. Outside the SCDF perimeter with a standalone pilot and '
0053         'object-store stage-out, so it doubles as the volunteer-computing '
0054         'model. See VOLUNTEER_GPU_PLAN in swf-epicprod.'
0055     ),
0056     'NERSC_Perlmutter_epic_gpu_mps': (
0057         'Perlmutter GPU queue running NVIDIA MPS, which lets several small '
0058         'jobs share one A100 concurrently instead of time-slicing it. Cloned '
0059         'from the gpu_test queue and now the busier of the two GPU queues.'
0060     ),
0061     'NERSC_Perlmutter_epic_gpu_test': (
0062         'Identical to NERSC_Perlmutter_epic_gpu_mps in schedconfig: every '
0063         'field matches, and the MPS queue was cloned from this one. Any '
0064         'difference between them is in the NERSC-side batch submission, not '
0065         'in PanDA. The earlier of the two GPU queues and now the quieter.'
0066     ),
0067 
0068     # NERSC Perlmutter CPU
0069     'NERSC_Perlmutter_epic': (
0070         'Main Perlmutter production queue for ePIC, 4-day limit. Second '
0071         'largest source of campaign processing after BNL.'
0072     ),
0073     'NERSC_Perlmutter_epic_mcore': (
0074         'Multi-core variant of the main Perlmutter queue, for jobs that use '
0075         'a whole node. Currently idle.'
0076     ),
0077     'NERSC_Perlmutter_epic_test': (
0078         'Test queue on Perlmutter for validating configuration and workflow '
0079         'changes before they reach the production queue.'
0080     ),
0081     'NERSC_Perlmutter_epic_test_mcore': (
0082         'Multi-core test queue on Perlmutter. Rarely used.'
0083     ),
0084     'NERSC_Perlmutter_epic_dev': (
0085         'Development queue on Perlmutter for work in progress, kept separate '
0086         'from both production and the shared test queue.'
0087     ),
0088     'NERSC_Perlmutter_iri': (
0089         'Perlmutter queue for the Integrated Research Infrastructure '
0090         'programme, which targets near-real-time processing of experimental '
0091         'data on HPC. Occasional use.'
0092     ),
0093 
0094     # Cloud
0095     'BNL_ePIC_GOOGLE': (
0096         'ePIC queue on Google Cloud, elastic and paid. Large memory and disk '
0097         'per job; used when demand exceeds the free resources.'
0098     ),
0099     'BNL_ePIC_GOOGLE_test': (
0100         'Test twin of the Google Cloud queue, for validating cloud '
0101         'configuration without spending on production volume.'
0102     ),
0103 
0104     # Echelon-1 and partner sites
0105     'E1_BNL': (
0106         'Echelon-1 queue at BNL for the streaming workflow testbed, handling '
0107         'prompt processing of simulated data-taking.'
0108     ),
0109     'E1_JLAB': (
0110         'Echelon-1 queue at Jefferson Lab, the second facility in the '
0111         'streaming workflow testbed alongside E1_BNL.'
0112     ),
0113     'UM_GREX_PanDA_1': (
0114         'University of Manitoba GREX cluster, a Canadian contribution to ePIC '
0115         'production. Large memory and disk per job, 4-day limit.'
0116     ),
0117 }
0118 
0119 
0120 # Tier as the facility providing the cycles, which is what a reader wants and
0121 # what schedconfig does not give: its value follows the parent site, so nearly
0122 # every ePIC queue reports T1 whatever the hardware behind it. BNL and JLab are
0123 # the Tier-1 facilities, Manitoba GREX is Tier-2, and everything else is
0124 # opportunistic capacity ("Opp"): NERSC allocations and commercial cloud.
0125 TIERS = {
0126     'BNL_EPIC_PROD_1': 'T1',
0127     'BNL_NPPS_GPU': 'T1',
0128     'BNL_OSG_EPIC_PROD_1': 'T1',
0129     'BNL_OSG_PanDA_1': 'T1',
0130     'BNL_OSG_PanDA_CI': 'T1',
0131     'BNL_OSG_PanDA_pilotest': 'T1',
0132     'BNL_PanDA_1': 'T1',
0133     'BNL_PanDA_test': 'T1',
0134     'E1_BNL': 'T1',
0135     'E1_JLAB': 'T1',
0136     'UM_GREX_PanDA_1': 'T2',
0137     'BNL_ePIC_GOOGLE': 'Opp',
0138     'BNL_ePIC_GOOGLE_test': 'Opp',
0139     'NERSC_Perlmutter_epic': 'Opp',
0140     'NERSC_Perlmutter_epic_dev': 'Opp',
0141     'NERSC_Perlmutter_epic_gpu_mps': 'Opp',
0142     'NERSC_Perlmutter_epic_gpu_test': 'Opp',
0143     'NERSC_Perlmutter_epic_mcore': 'Opp',
0144     'NERSC_Perlmutter_epic_test': 'Opp',
0145     'NERSC_Perlmutter_epic_test_mcore': 'Opp',
0146     'NERSC_Perlmutter_iri': 'Opp',
0147 }
0148 
0149 
0150 class Command(BaseCommand):
0151     help = 'Seed queue descriptions and facility tiers into PandaQueue.metadata'
0152 
0153     def add_arguments(self, parser):
0154         parser.add_argument(
0155             '--force', action='store_true',
0156             help='Overwrite descriptions that already exist',
0157         )
0158         parser.add_argument(
0159             '--dry-run', action='store_true',
0160             help='Report what would change without writing',
0161         )
0162 
0163     def handle(self, *args, **options):
0164         force = options['force']
0165         dry_run = options['dry_run']
0166         written = skipped = 0
0167         missing = 0
0168 
0169         for queue_name, text in DESCRIPTIONS.items():
0170             queue = PandaQueue.objects.filter(queue_name=queue_name).first()
0171             if queue is None and dry_run:
0172                 self.stdout.write(f'would create and set: {queue_name}')
0173                 written += 1
0174                 continue
0175             if queue is None:
0176                 # Most queues are known only from schedconfig; the local row
0177                 # exists to carry annotation, so create it on demand.
0178                 queue = PandaQueue(queue_name=queue_name, config_data={}, metadata={})
0179 
0180             metadata = dict(queue.metadata or {})
0181             existing = (metadata.get('description') or '').strip()
0182             tier = TIERS.get(queue_name)
0183             tier_current = (metadata.get('tier') or '').strip()
0184             tier_stale = bool(tier) and tier_current != tier
0185             if existing and not force and not tier_stale:
0186                 skipped += 1
0187                 continue
0188             if existing == text and not tier_stale:
0189                 skipped += 1
0190                 continue
0191 
0192             if dry_run:
0193                 self.stdout.write(f'would set: {queue_name}')
0194                 written += 1
0195                 continue
0196 
0197             if tier:
0198                 metadata['tier'] = tier
0199             metadata['description'] = text
0200             metadata['description_updated_by'] = 'set_queue_descriptions'
0201             metadata['description_updated_at'] = timezone.now().isoformat()
0202             queue.metadata = metadata
0203             if queue._state.adding:
0204                 queue.save()
0205             else:
0206                 queue.save(update_fields=['metadata', 'updated_at'])
0207             written += 1
0208 
0209         verb = 'would write' if dry_run else 'wrote'
0210         self.stdout.write(self.style.SUCCESS(
0211             f'{verb} {written}, skipped {skipped}, missing {missing}'
0212         ))