Skip to content

Commit 6559da9

Browse files
committed
doc: add cookbook recipe syncing remote application directories
Add a cookbook recipe showing a before-modulefile-eval hook that copies, with rsync, the application directory of a module hosted on a network share to local disk the first time it is loaded, tracked with a marker file so later loads skip the copy. The modulepath root .modulerc tags a module 'remote' while its application directory has not been synced yet, and the 'remote' tag is added to non_exportable_tags so it does not stick once the module is actually loaded. Configuration for this tag (abbreviation, color, non_exportable_tags) is set with module config calls in the initrc configuration file, evaluated once when the module shell function initializes with autoinit -- the only context a global rc file evaluated by Modules can call module config from, since such a file is otherwise re-evaluated ahead of every other module command afterward. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: Xavier Delaruelle <xavier.delaruelle@cea.fr>
1 parent abb96e1 commit 6559da9

8 files changed

Lines changed: 287 additions & 0 deletions

File tree

‎.hunspell.en.dic‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,3 +1534,6 @@ fd
15341534
lindex
15351535
hookEvents
15361536
runHooks
1537+
appdir
1538+
build3
1539+
rsync

‎NEWS.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ Modules 5.7.0 (not yet released)
189189
#607)
190190
* Doc: add :ref:`add-new-hook-event` guide describing how to contribute a
191191
new hook event.
192+
* Doc: add :ref:`sync-remote-appdir` cookbook recipe.
192193

193194

194195
.. _5.6 release notes:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo/2.1 foo-2.1-build3
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#%Module5.7
2+
3+
# give the 'remote' module tag its own abbreviation and color, and make
4+
# sure it is not persisted onto a module once it gets loaded (see the "Sync
5+
# remote application directories on first load" cookbook recipe)
6+
module config tag_abbrev "+remote=R"
7+
module config colors "+R=38;5;202"
8+
module config tag_color_name +remote
9+
module config non_exportable_tags +remote
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#%Module5.7
2+
3+
# tag every module listed in the remote application directory map as
4+
# 'remote', unless its application directory has already been synced to
5+
# local disk (see siteconfig.tcl for how the sync itself is triggered)
6+
set mapfile [file join /remote_apps .module_appdir_map]
7+
if {[file readable $mapfile]} {
8+
set fid [open $mapfile r]
9+
set fdata [split [read $fid] "\n"]
10+
close $fid
11+
foreach fline $fdata {
12+
if {[llength $fline] != 2} {
13+
continue
14+
}
15+
lassign $fline modnamevr appdir
16+
set syncedfile [file join /local_apps ".$appdir.synced"]
17+
if {![file exists $syncedfile]} {
18+
module-tag remote $modnamevr
19+
}
20+
}
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#%Module5.7
2+
3+
set appdir foo-2.1-build3
4+
prepend-path PATH /local_apps/$appdir/bin
5+
prepend-path LD_LIBRARY_PATH /local_apps/$appdir/lib
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#
2+
# siteconfig.tcl - Site specific configuration script that copies, the first
3+
# time a module tagged 'remote' is loaded, its application directory from
4+
# a remote network share to local disk with rsync, so this and every later
5+
# load of the same module read from local disk instead of the network
6+
# share.
7+
#
8+
# Author: Xavier Delaruelle <xavier.delaruelle@cea.fr>
9+
# Compatibility: Modules v5.7+
10+
#
11+
# Installation: put this file in the 'etc' directory of your Modules
12+
# installation. Refer to the "Modulecmd startup" section in the
13+
# module(1) man page to get this location.
14+
15+
# root of the mounted network share and of its local counterpart
16+
set g_remoteAppDir /remote_apps
17+
set g_localAppDir /local_apps
18+
19+
# return the application directory basename mapped to given bare module name
20+
# and version, or an empty string if this module has no mapped directory
21+
proc getAppDirBasename {modname} {
22+
set mapfile [file join $::g_remoteAppDir .module_appdir_map]
23+
if {![file readable $mapfile]} {
24+
return {}
25+
}
26+
set fid [open $mapfile r]
27+
set fdata [split [read $fid] "\n"]
28+
close $fid
29+
foreach fline $fdata {
30+
if {[llength $fline] == 2 && [lindex $fline 0] eq $modname} {
31+
return [lindex $fline 1]
32+
}
33+
}
34+
return {}
35+
}
36+
37+
# copy application directory from the remote network share to local disk, on
38+
# the first load of a module tagged 'remote' (see the modulepath root
39+
# .modulerc file for how this tag gets applied)
40+
proc syncRemoteAppDir {modfile modname modnamevr modspec mode requested} {
41+
if {$mode ne {load}} {
42+
return
43+
}
44+
set itrp [getCurrentModfileInterpName]
45+
if {![interp eval $itrp {module-info tags remote}]} {
46+
return
47+
}
48+
49+
set appdir [getAppDirBasename $modname]
50+
if {$appdir eq {}} {
51+
return
52+
}
53+
54+
set syncedfile [file join $::g_localAppDir ".$appdir.synced"]
55+
if {[file exists $syncedfile]} {
56+
return
57+
}
58+
59+
report "Syncing '$appdir' application directory from remote share..."
60+
file mkdir $::g_localAppDir
61+
set srcdir [file join $::g_remoteAppDir $appdir]
62+
set destdir [file join $::g_localAppDir $appdir]
63+
if {[catch {exec rsync -a --delete $srcdir/ $destdir/} errMsg]} {
64+
reportError "Failed to sync '$appdir' from remote share\n$errMsg"
65+
return
66+
}
67+
68+
# mark this application directory as synced so it does not get copied
69+
# again on a later load
70+
close [open $syncedfile w]
71+
}
72+
add-hook before-modulefile-eval syncRemoteAppDir
73+
74+
# vim:set tabstop=3 shiftwidth=3 expandtab autoindent:
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
.. _sync-remote-appdir:
2+
3+
Sync remote application directories on first load
4+
=================================================
5+
6+
Some workstations and laptops mount a network share holding all kind of
7+
software installations that may be used on this machine, alongside their own
8+
local, faster disk. Reading application files directly from this network
9+
share on every use works, but it is slower than reading them from local
10+
disk, and it stops working entirely when the machine goes offline.
11+
12+
This recipe describes how to keep modulefiles pointing at application
13+
directories on a network share mounted under :file:`/remote_apps`, while
14+
transparently copying, with the `rsync`_ tool, the directory of a given
15+
application to :file:`/local_apps` the first time the corresponding module
16+
is loaded. Every later load of the same module finds its application
17+
directory already synced locally and skips the copy.
18+
19+
.. _rsync: https://rsync.samba.org/
20+
21+
Implementation
22+
--------------
23+
24+
A :file:`.module_appdir_map` file, located at the root of the
25+
:file:`/remote_apps` network share, maps each module name and version to the
26+
basename of its application directory under :file:`/remote_apps`:
27+
28+
.. literalinclude:: ../../example/sync-remote-appdir/.module_appdir_map
29+
:caption: /remote_apps/.module_appdir_map
30+
31+
The :file:`.modulerc` file at the root of the modulepath reads this map file
32+
and applies the ``remote`` tag, with the :mfcmd:`module-tag` modulefile
33+
command, to every module listed in it whose application directory has not
34+
been synced locally yet -- tracked by the presence of a
35+
:file:`.<basename>.synced` marker file under :file:`/local_apps`:
36+
37+
.. literalinclude:: ../../example/sync-remote-appdir/modulefiles/.modulerc
38+
:language: tcl
39+
:caption: modulefiles/.modulerc
40+
41+
The actual sync is performed by a :mhook:`before-modulefile-eval` hook,
42+
registered with the ``add-hook`` siteconfig command (see the :ref:`hook-api`
43+
design notes and the *Hooks* section of :manpage:`module(1)` man page). The
44+
hook procedure returns immediately if the evaluation mode is not ``load``,
45+
or if the module being evaluated is not tagged ``remote`` -- checked with
46+
:mfcmd:`module-info tags<module-info>`, run in the modulefile Tcl
47+
interpreter reached through ``getCurrentModfileInterpName``, exactly as
48+
already documented for a hook procedure that needs to run modulefile
49+
commands. Otherwise, it looks up the application directory basename mapped
50+
to the module in :file:`.module_appdir_map`, and skips the sync if this
51+
directory was already marked as synced by a previous load. Otherwise it
52+
copies the application directory with ``rsync`` and, on success, touches
53+
the :file:`.<basename>.synced` marker file so later loads skip the copy.
54+
55+
Because the modulepath root :file:`.modulerc` tags a module ``remote`` ahead
56+
of the sync actually happening, this tag would otherwise still be recorded
57+
as applying to the module once loaded, which is misleading once its
58+
application directory has just been synced locally. The ``remote`` tag is
59+
therefore added to the :mconfig:`non_exportable_tags` configuration option,
60+
introduced in Modules v5.7 together with the hook API, so it is dropped from
61+
the tag list persisted once a module is loaded, without affecting how it is
62+
reported beforehand, for instance on an :subcmd:`avail` listing. The
63+
``remote`` tag is also given its own abbreviation and color, so modules
64+
whose application directory has not been synced yet stand out on an
65+
:subcmd:`avail` or :subcmd:`spider` listing. These ``module config`` calls
66+
are set in |file etcdir_initrc|, evaluated once when the ``module`` shell
67+
function initializes with :subcmd:`autoinit`, the only context ``module
68+
config`` is usable from within a file evaluated by Modules itself:
69+
70+
.. literalinclude:: ../../example/sync-remote-appdir/initrc
71+
:language: tcl
72+
:caption: initrc
73+
74+
The sync itself is triggered by the hook procedure, defined and registered
75+
in :file:`siteconfig.tcl`:
76+
77+
.. literalinclude:: ../../example/sync-remote-appdir/siteconfig.tcl
78+
:language: tcl
79+
:caption: siteconfig.tcl
80+
81+
Since the hook fires before the modulefile itself is evaluated, and the sync
82+
is performed with a blocking ``exec`` call, ``module load`` waits for the
83+
copy to complete before the modulefile that relies on the now-local
84+
application directory gets evaluated. A hook procedure cannot abort the
85+
evaluation it wraps, so a sync failure is reported as an error but does not
86+
prevent the module from loading afterward, even though its application
87+
directory may still be missing locally in that case.
88+
89+
**Compatible with Modules v5.7+**
90+
91+
Installation
92+
------------
93+
94+
Create site-specific configuration directory if it does not exist yet:
95+
96+
.. parsed-literal::
97+
98+
$ mkdir \ |etcdir|
99+
100+
Copy the site-specific configuration script and initialization file of this
101+
recipe:
102+
103+
.. parsed-literal::
104+
105+
$ cp example/sync-remote-appdir/siteconfig.tcl \ |etcdir|\ /
106+
$ cp example/sync-remote-appdir/initrc \ |etcdir|\ /
107+
108+
.. note::
109+
110+
Defined location for the site-specific configuration script may vary from
111+
one installation to another. To determine the expected location for this
112+
file on your setup, check the value of the ``siteconfig`` configuration
113+
option:
114+
115+
.. parsed-literal::
116+
117+
:ps:`$` module config siteconfig
118+
119+
Adapt :file:`modulefiles/.modulerc` to your modulepath, and copy it at its
120+
root, next to the modulefiles it applies to. Finally, create
121+
:file:`/remote_apps/.module_appdir_map` on the network share, with one
122+
``<module_name_and_version> <software_install_directory_basename>`` entry
123+
per line for each application directory that should be synced this way.
124+
125+
Usage example
126+
-------------
127+
128+
The application directory of ``foo/2.1`` has not been synced locally yet, so
129+
it shows up tagged ``remote`` on an :subcmd:`avail` listing, using the
130+
abbreviation and color configured for this tag:
131+
132+
.. parsed-literal::
133+
134+
:ps:`$` module avail foo
135+
--------------- :sgrdi:`/path/to/modulefiles` ---------------
136+
foo/2.1 <R>
137+
138+
Key:
139+
<module-tag> <R>=remote
140+
141+
Loading it triggers the sync, then proceeds with the load once the copy
142+
completes:
143+
144+
.. parsed-literal::
145+
146+
:ps:`$` module load foo/2.1
147+
Syncing 'foo-2.1-build3' application directory from remote share...
148+
Loading :sgrhi:`foo/2.1`
149+
150+
Once loaded, the module no longer carries the ``remote`` tag, since its
151+
application directory now lives on local disk:
152+
153+
.. parsed-literal::
154+
155+
:ps:`$` module list
156+
Currently Loaded Modulefiles:
157+
1) foo/2.1
158+
159+
A later load, after the module has been unloaded, finds the
160+
:file:`.foo-2.1-build3.synced` marker file and skips the sync entirely,
161+
and the module no longer shows up tagged ``remote`` on :subcmd:`avail`
162+
either:
163+
164+
.. parsed-literal::
165+
166+
:ps:`$` module unload foo/2.1
167+
:ps:`$` module avail foo
168+
--------------- :sgrdi:`/path/to/modulefiles` ---------------
169+
foo/2.1
170+
:ps:`$` module load foo/2.1
171+
Loading :sgrhi:`foo/2.1`
172+
173+
.. vim:set tabstop=2 shiftwidth=2 expandtab autoindent:

0 commit comments

Comments
 (0)