From da8e07ab3ecd0abda03d7b8a7fbcfa706b02eebf Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 22 Dec 2021 11:17:13 +0100 Subject: [PATCH] L2SS-497: Add measures management capabilities and expose them in the Beam device (that will use the measures) --- .../tangostationcontrol/common/measures.py | 120 ++++++++++++++++++ .../tangostationcontrol/devices/beam.py | 47 ++++++- 2 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 tangostationcontrol/tangostationcontrol/common/measures.py diff --git a/tangostationcontrol/tangostationcontrol/common/measures.py b/tangostationcontrol/tangostationcontrol/common/measures.py new file mode 100644 index 000000000..efba34a91 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/common/measures.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" Utility functions for managing the casacore 'measures' tables. + +The 'measures' tables contain the ephemerides and geodetic calibrations +as input for its time and space calculations. These tables are externally +available (see MEASURES_URL). + +Casacore is expected to be configured to look in /opt/IERS/current for +its 'measures' tables, through setting this in the ~/.casarc file as: + + measures.directory: /opt/IERS/current + +Periodically new measures need to be installed, especially if a leap +second is introduced. Measures are maintained in directories called +/opt/IERS/IERS-YYYY-MM-DDTHH:MM:SS, and /opt/IERS/current is a symlink +to the active set. + +Usage: + +The download_measures() function can be used to download new measures, +which can then be activated using use_measures_directory(). If +casacore.measures already accessed the measures, the python program +needs to be restarted in order to clear the cache. + +""" + +import pathlib +import urllib.request +import tarfile +import datetime +import os +import sys + +# Where to store the measures table sets +IERS_ROOTDIR = "/opt/IERS" + +# Where to download files to +TEMPDIR = "/tmp" + +# Where new measures can be downloaded +MEASURES_URL = "ftp://ftp.astron.nl/outgoing/Measures/WSRT_Measures.ztar" + +def get_measures_directory(): + """ Return the directory of the current measures table in use. """ + + return str(pathlib.Path(IERS_ROOTDIR, "current").resolve()) + +def use_measures_directory(newdir): + """ Select a new set of measures tables to use. + + NOTE: Python must be restarted if the 'casacore.measures' module + already loaded the measures table before this switch. + + The 'restart_python()' function can be used for this purpose. + """ + + newdir = pathlib.Path(newdir) + + # newdir must be one of the available measures + if str(newdir) not in get_available_measures_directories(): + raise ValueError(f"Target is not an available measures directory: {newdir}") + + # be sure newdir must point to a directory containing measures + for subdir in ['ephemerides', 'geodetic']: + subdir = pathlib.Path(newdir, subdir) + + if not subdir.is_dir(): + raise ValueError(f"Subdirectory {subdir} does not exist") + + # switch to new directory + current_symlink = pathlib.Path(IERS_ROOTDIR, "current") + current_symlink.unlink() + current_symlink.symlink_to(newdir) + +def restart_python(): + """ Force a restart this python program. + + This function does not return. """ + + exe_path = pathlib.Path(sys.executable) + os.execv(exe_path, [exe_path.name] + sys.argv) + + # NOTE: Python 3.4+ closes all file descriptors > 2 automatically, see https://www.python.org/dev/peps/pep-0446/ + +def get_available_measures_directories() -> list: + """ Returns the set of installed measures tables. """ + return [str(d) for d in pathlib.Path(IERS_ROOTDIR).glob("IERS-*") if d.is_dir() and not d.is_symlink()] + +def download_measures() -> str: + """ Download new measures and return the directory in which they were installed. + """ + + # create target directory for new measures + now = datetime.datetime.now() + iers_dir = pathlib.Path(now.strftime(f"{IERS_ROOTDIR}/IERS-%FT%T")) + iers_dir.mkdir() + + try: + measures_filename = pathlib.Path(TEMPDIR, "WSRT_Measures.ztar") + + # download measures + urllib.request.urlretrieve(MEASURES_URL, str(measures_filename)) + + # untar measures + tarball = tarfile.open(str(measures_filename)) + tarball.extractall(path=str(iers_dir)) + + # remove download + measures_filename.unlink() + except Exception as e: + # don't linger our new directory if we could not install measures in it + iers_dir.rmdir() + + raise + + return str(iers_dir) diff --git a/tangostationcontrol/tangostationcontrol/devices/beam.py b/tangostationcontrol/tangostationcontrol/devices/beam.py index bbcea5f91..e271dcf04 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam.py @@ -8,10 +8,13 @@ """ # PyTango imports -from tango.server import attribute -from tango import AttrWriteType +from tango.server import attribute, command +from tango import AttrWriteType, DebugIt import numpy import pathlib +import urllib.request +import tarfile +import datetime # Additional import from tangostationcontrol.devices.device_decorators import * @@ -19,6 +22,10 @@ from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.common.measures import get_measures_directory, use_measures_directory, download_measures, restart_python, get_available_measures_directories + +import logging +logger = logging.getLogger() __all__ = ["Beam", "main"] @@ -34,8 +41,11 @@ class Beam(lofar_device): # ---------- # Directory where the casacore measures that we use, reside. We configure ~/.casarc to - # use /opt/IERS/current, but that is a symlink, so resolve it to get the actual location. - measures_directory_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: str(pathlib.Path("/opt/IERS/current").resolve())) + # use the symlink /opt/IERS/current, which we switch to the actual set of files to use. + measures_directory_R = attribute(dtype=str, access=AttrWriteType.READ, fget = lambda self: get_measures_directory()) + + # List of installed measures (the latest 64, anyway) + measures_directories_available_R = attribute(dtype=(str,), max_dim_x=64, access=AttrWriteType.READ, fget = lambda self: sorted(get_available_measures_directories())[-64:]) # -------- # overloaded functions @@ -46,6 +56,35 @@ class Beam(lofar_device): # Commands # -------- + @command(dtype_out=str, doc_out="Name of newly installed measures directory") + @DebugIt() + @log_exceptions() + def download_measures(self): + """ Download new measures into /opt/IERS. + + NOTE: This may take a while to complete. """ + + return download_measures() + + @command(dtype_in=str, doc_in="Measures directory to activate") + @DebugIt() + @log_exceptions() + def use_measures(self, newdir): + """ Activate a new measures directory. + + NOTE: This will turn off and restart this device!! """ + + # switch to requested measures + use_measures_directory(newdir) + logger.info(f"Switched measures table to {newdir}") + + # turn off our device, to prepare for a python restart + self.Off() + + # restart this program to force casacore to adopt + # the new tables + logger.warning("Restarting device to activate new measures tables") + restart_python() # ---------- # Run server -- GitLab