Skip to content

Commit c6c7999

Browse files
committed
admin-shell: bulk send commands to zone
Motivation: There was no way to scope a bulk admin-shell command to a zones. Modification: Add a '#<zone>' suffix to '\s' command patterns that filters targets to cells whose domain is registered under that zone in ZooKeeper. Domains register themselves on startup when dcache.zone is set. Result: Admins can now run e.g. '\s pool*#<zone> rep ls' to target a zone. Target: master Require-book: yes
1 parent b067326 commit c6c7999

5 files changed

Lines changed: 144 additions & 26 deletions

File tree

‎docs/TheBook/src/main/markdown/config-admin.md‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ send supported cell commands to other cells. Once logged in you are prompted to
181181
\h [command]... # display help for cell commands
182182
\l [cell[@domain]|pool/poolgroup]... # list cells
183183
\q # quit
184-
\s [OPTIONS] (cell[@domain]|pool/poolgroup)[,(cell[@domain]|pool/poolgroup)]... command... # send command
184+
\s [OPTIONS] (cell[@domain]|pool[#zone][/poolgroup])[,(cell[@domain]|pool[#zone][/poolgroup])]... command... # send command
185185
\sl [options] pnfsid|path command... # send to locations
186186
\sn [options] command... # send pnfsmanager command
187187
\sp [options] command... # send poolmanager command
@@ -191,7 +191,7 @@ send supported cell commands to other cells. Once logged in you are prompted to
191191
```
192192

193193
Shell commands are always available at command prompt, whereas in order to execute cell commands you have to either connect to the cell
194-
using `\c cell[@domain]` and execute command or send command to the cell using `\s [OPTIONS] (cell[@domain]|pool/poolgroup)[,(cell[@domain]|pool/poolgroup)]... command...`. For instance:
194+
using `\c cell[@domain]` and execute command or send command to the cell using `\s [OPTIONS] (cell[@domain]|pool[#zone][/poolgroup])[,...] command...`. For instance:
195195

196196
```
197197
[headnode] (local) enstore > \? \c
@@ -355,6 +355,41 @@ pool_2={Tag={{hostname=example.org}};size=0;SC=2.7939677238464355E-4;CC=0.0;}
355355

356356
While the first line for each pool gives the information stored in the cache of the cost module, the second line gives the costs (SC: [space cost](rf-glossary.md#space-cost), CC: [performance cost](rf-glossary.md#performance-cost)) calculated for a (hypothetical) file of zero size. For details on how these are calculated and their meaning, see [the section called “Classic Partitions”](#config-poolmanager.md#classic-partitions).
357357

358+
## Zone-scoped bulk commands
359+
360+
When dCache is deployed with zones (`dcache.zone` property), the `\s` command supports a `#<zone>` suffix to restrict a bulk command to cells that belong to a specific zone.
361+
362+
The general syntax is:
363+
364+
```
365+
\s [OPTIONS] <cellPattern>#<zone>[/<poolgroup>] command...
366+
```
367+
368+
For example, to send `rep ls` to all pools in zone `zone`:
369+
370+
```
371+
(local) admin > \s pool*#zone rep ls
372+
```
373+
374+
To further restrict to a pool group within the zone:
375+
376+
```
377+
(local) admin > \s pool*#zone/poolGroup rep ls
378+
```
379+
380+
The zone filter is resolved through ZooKeeper: each domain registers itself under
381+
`/dcache/zones/<zone>/<domainName>` at startup when `dcache.zone` is configured.
382+
The admin shell looks up the children of that ZooKeeper path and keeps only those
383+
cells whose domain is listed there.
384+
385+
You can combine the zone filter with glob patterns as usual:
386+
387+
```
388+
(local) admin > \s *#zone st set max-active 10
389+
```
390+
391+
This sends the command to every well-known cell in the selected zone.
392+
358393
## Creating a new user
359394

360395
To create a new user, <new-user> and set a new password for the user `\c` from the local prompt `((local) admin >)` to the acm, the access control manager, and run following command sequence:

‎modules/dcache/src/main/java/diskCacheV111/admin/UserAdminShell.java‎

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.common.base.Supplier;
2626
import com.google.common.base.Suppliers;
2727
import com.google.common.base.Throwables;
28+
import com.google.common.collect.ImmutableList;
2829
import com.google.common.collect.Iterables;
2930
import com.google.common.collect.Range;
3031
import com.google.common.util.concurrent.Futures;
@@ -80,6 +81,8 @@
8081
import java.util.stream.StreamSupport;
8182
import jline.console.completer.Completer;
8283
import jline.console.completer.StringsCompleter;
84+
import org.apache.curator.framework.CuratorFramework;
85+
import org.checkerframework.checker.nullness.qual.NonNull;
8386
import org.dcache.auth.Subjects;
8487
import org.dcache.auth.attributes.Restrictions;
8588
import org.dcache.cells.CellStub;
@@ -157,6 +160,8 @@ public class UserAdminShell
157160
*/
158161
private Completer _pnfsManagerCompleter;
159162

163+
private CuratorFramework _curator;
164+
160165
/**
161166
* Client handler for listing directories in the dCache name space.
162167
*/
@@ -238,6 +243,10 @@ public void setPnfsManager(CellStub stub) {
238243
_pnfsManagerCompleter = createRemoteCompleter(_pnfsManager.getDestinationPath());
239244
}
240245

246+
public void setCurator(CuratorFramework curator){
247+
_curator = curator;
248+
}
249+
241250
public void setListHandler(ListDirectoryHandler list) {
242251
_list = list;
243252
}
@@ -390,8 +399,7 @@ private ListenableFuture<List<String>> getPoolsInGroups(Predicate<String> predic
390399
* The result is sorted lexicographically and case insensitive, but the order of the input
391400
* patterns is preserved (ie. the output contains matching cells in the same order).
392401
*/
393-
private List<String> expandCellPatterns(List<String> patterns)
394-
throws CacheException, InterruptedException, ExecutionException, NoRouteToCellException {
402+
private List<String> expandCellPatterns(List<String> patterns) throws CommandException, CacheException, NoRouteToCellException, InterruptedException, ExecutionException {
395403
/* Query domains and well-known cells on demand. */
396404
Supplier<Future<Map<String, Collection<String>>>> domains =
397405
Suppliers.memoize(() -> transform(_cellStub.send(new CellPath("RoutingMgr"),
@@ -414,32 +422,66 @@ private List<String> expandCellPatterns(List<String> patterns)
414422
continue;
415423
}
416424

417-
i = pattern.indexOf('/');
418-
if (i >= 0) {
419-
Predicate<String> matchesPool = toGlobPredicate(pattern.substring(0, i));
425+
List<ListenableFuture<List<String>>> zoneCellFutures;
426+
List<String> zoneCells;
427+
428+
int zoneIdx = pattern.indexOf('#');
429+
int pGroupIdx = pattern.indexOf('/');
430+
if (zoneIdx >= 0) {
431+
String zone = pGroupIdx < 0 ? pattern.substring(zoneIdx + 1) : pattern.substring(zoneIdx + 1, pGroupIdx);
432+
try {
433+
List<String> zoneDomains = _curator.getChildren().forPath("/dcache/zones/" + zone);
434+
Predicate<String> matchesCellName = toGlobPredicate(pattern.substring(0, zoneIdx));
435+
zoneCellFutures = zoneDomains.stream()
436+
.sorted(CASE_INSENSITIVE_ORDER)
437+
.map(domain -> getCells(domain, matchesCellName))
438+
.toList();
439+
440+
zoneCells = Objects.requireNonNull(allAsList(zoneCellFutures).get()).stream()
441+
.flatMap(List::stream)
442+
.map(addr -> addr.contains("@") ? addr.substring(0, addr.indexOf('@')) : addr)
443+
.toList();
444+
445+
} catch (Exception e) {
446+
throw new CommandException("Can not send to zone: " + zone, e.getCause());
447+
}
448+
449+
} else {
450+
zoneCells = null;
451+
zoneCellFutures = null;
452+
}
420453

421-
if (i + 1 == pattern.length()) {
454+
if (pGroupIdx >= 0) {
455+
int endPoolName = (zoneIdx >= 0) ? zoneIdx : pGroupIdx;
456+
Predicate<String> matchesPool = toGlobPredicate(pattern.substring(0, endPoolName));
457+
Predicate<String> isInZone = cell -> zoneCells == null || zoneCells.contains(cell);
458+
if (pGroupIdx + 1 == pattern.length()) {
422459
/* Special case when no pool group is specified - matches over all pools, even those
423460
* not in a pool group.
424461
*/
425462
futures.add(transform(getPools(matchesPool),
426463
(List<String> pools) ->
427464
pools.stream()
428-
.sorted(CASE_INSENSITIVE_ORDER)
429-
.collect(toList()), MoreExecutors.directExecutor()));
465+
.filter(isInZone)
466+
.sorted(CASE_INSENSITIVE_ORDER)
467+
.collect(toList()), MoreExecutors.directExecutor()));
430468
} else {
431469
/* Find the pools of each matching pool group.
432470
*/
433-
Predicate<String> matchesPoolGroup = toGlobPredicate(pattern.substring(i + 1));
471+
Predicate<String> matchesPoolGroup = toGlobPredicate(pattern.substring(pGroupIdx + 1));
434472
futures.add(
435473
transform(getPoolsInGroups(matchesPoolGroup),
436474
(List<String> pools) ->
437475
pools.stream()
438-
.filter(matchesPool)
439-
.sorted(CASE_INSENSITIVE_ORDER)
440-
.collect(toList()), MoreExecutors.directExecutor()));
476+
.filter(matchesPool)
477+
.filter(isInZone)
478+
.sorted(CASE_INSENSITIVE_ORDER)
479+
.collect(toList()), MoreExecutors.directExecutor()));
441480
}
442481
continue;
482+
} else if (zoneCellFutures != null) {
483+
futures.addAll(zoneCellFutures);
484+
continue;
443485
}
444486

445487
Predicate<String> matchesCellName = toGlobPredicate(pattern);
@@ -463,7 +505,7 @@ private List<String> expandCellPatterns(List<String> patterns)
463505
*/
464506
private static boolean isExpandable(String s) {
465507
return !s.contains(":") && (s.startsWith("@") || s.endsWith("@") || Glob.isGlob(s)
466-
|| s.indexOf('/') > -1);
508+
|| s.indexOf('/') > -1 || s.indexOf('#') > -1);
467509
}
468510

469511
/**
@@ -734,7 +776,7 @@ class PoolManagerCommand implements Callable<Serializable> {
734776

735777
@Override
736778
public Serializable call()
737-
throws InterruptedException, NoRouteToCellException, CommandException, AclException {
779+
throws InterruptedException, NoRouteToCellException, CommandException, AclException {
738780
return sendObject(_poolManager.getDestinationPath(), args.toString());
739781
}
740782
}
@@ -756,8 +798,7 @@ class SendCommand implements Callable<Serializable> {
756798

757799
@Override
758800
public Serializable call()
759-
throws InterruptedException, ExecutionException, CacheException, AclException,
760-
CommandException, NoRouteToCellException {
801+
throws Exception {
761802
args.shift();
762803
AuthorizedString command = new AuthorizedString(_user, args.toString());
763804

@@ -768,18 +809,24 @@ public Serializable call()
768809
return sendObject(destination, command);
769810
}
770811

771-
/* Expand wildcards.
772-
*/
773812
Map<Boolean, List<String>> expandable =
774-
StreamSupport.stream(Glob.expandList(destination).spliterator(), false)
775-
.collect(partitioningBy(UserAdminShell::isExpandable));
813+
StreamSupport.stream(Glob.expandList(destination).spliterator(), false)
814+
.collect(partitioningBy(UserAdminShell::isExpandable));
776815
Iterable<String> destinations = concat(expandable.get(false),
777-
expandCellPatterns(expandable.get(true)));
816+
expandCellPatterns(expandable.get(true)));
778817

779818
return sendToMany(destinations, command);
780819
}
781820
}
782821

822+
private @NonNull Iterable<String> expandWildcards(String destination) throws CacheException, InterruptedException, ExecutionException, NoRouteToCellException, CommandException {Map<Boolean, List<String>> expandable =
823+
StreamSupport.stream(Glob.expandList(destination).spliterator(), false)
824+
.collect(partitioningBy(UserAdminShell::isExpandable));
825+
826+
return concat(expandable.get(false), expandCellPatterns(expandable.get(true)));
827+
}
828+
829+
783830
@Command(name = "\\sl", hint = "send to locations",
784831
description = "Sends COMMAND to all pools hosting a copy of the given file. If the " +
785832
"string $1 occurs in the command, the string is replaced by the PNFS ID " +
@@ -884,6 +931,9 @@ private int completeConnectCommand(String buffer, int cursor, List<CharSequence>
884931
return -1;
885932
} catch (InterruptedException e) {
886933
return -1;
934+
} catch (CommandException e) {
935+
LOGGER.warn(e.getMessage());
936+
return -1;
887937
}
888938
}
889939

@@ -923,6 +973,9 @@ private int completeCellWildcard(String buffer, int cursor, List<CharSequence> c
923973
return -1;
924974
} catch (InterruptedException e) {
925975
return -1;
976+
} catch (CommandException e) {
977+
LOGGER.warn(e.getMessage());
978+
return -1;
926979
}
927980
}
928981

‎modules/dcache/src/main/java/org/dcache/boot/Domain.java‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.dcache.boot;
22

33
import static com.google.common.base.Strings.emptyToNull;
4+
45
import static org.dcache.boot.Properties.PATH_DELIMITER;
56
import static org.dcache.boot.Properties.PROPERTY_DOMAIN_NAME;
67
import static org.dcache.boot.Properties.PROPERTY_DOMAIN_PRELOAD;
@@ -26,7 +27,6 @@
2627
import static org.dcache.boot.Properties.PROPERTY_ZOOKEEPER_PEM_KEY;
2728
import static org.dcache.boot.Properties.PROPERTY_CRL_MODE;
2829
import static org.dcache.boot.Properties.PROPERTY_OCSP_MODE;
29-
3030
import static org.dcache.util.Exceptions.genericCheck;
3131

3232
import ch.qos.logback.classic.LoggerContext;
@@ -41,6 +41,7 @@
4141
import dmg.cells.nucleus.CellShell;
4242
import dmg.cells.nucleus.SerializationHandler;
4343
import dmg.cells.nucleus.SystemCell;
44+
import dmg.cells.zookeeper.LmPersistentNode;
4445
import dmg.util.CommandException;
4546
import java.io.File;
4647
import java.io.IOException;
@@ -56,13 +57,15 @@
5657
import java.util.concurrent.Callable;
5758
import java.util.concurrent.ExecutionException;
5859
import java.util.concurrent.TimeUnit;
60+
import java.util.function.Function;
5961

6062
import eu.emi.security.authn.x509.CrlCheckingMode;
6163
import eu.emi.security.authn.x509.OCSPCheckingMode;
6264
import org.apache.curator.RetryPolicy;
6365
import org.apache.curator.framework.CuratorFramework;
6466
import org.apache.curator.framework.CuratorFrameworkFactory;
6567
import org.apache.curator.retry.ExponentialBackoffRetry;
68+
import org.apache.zookeeper.CreateMode;
6669
import org.apache.zookeeper.client.ZKClientConfig;
6770
import org.dcache.ssl.CanlContextFactory;
6871
import org.dcache.util.Args;
@@ -165,8 +168,9 @@ public void start() throws Exception {
165168

166169
SerializationHandler.Serializer cellSerializer = SerializationHandler.enumFromConfigString(
167170
_properties.getValue(PROPERTY_MSG_PAYLOAD_SERIALIZER));
171+
CuratorFramework curator = createCuratorFramework();
168172
SystemCell systemCell = SystemCell.create(domainName,
169-
createCuratorFramework(), zone, cellSerializer);
173+
curator, zone, cellSerializer);
170174
try {
171175
systemCell.start().get();
172176
} catch (ExecutionException e) {
@@ -176,6 +180,20 @@ public void start() throws Exception {
176180
}
177181
LOGGER.info("Starting {}", domainName);
178182

183+
if(zone.isPresent()){
184+
try{
185+
LmPersistentNode.createOrUpdate(
186+
curator,
187+
"/dcache/zones/" + zone.get() + "/" + domainName,
188+
new byte[0],
189+
Function.identity(),
190+
null
191+
);
192+
} catch (Exception e) {
193+
LOGGER.warn("Failed to add zone {} in ZooKeeper", zone.get(), e);
194+
}
195+
}
196+
179197
executePreload(systemCell);
180198
for (ConfigurationProperties serviceConfig : _services) {
181199
executeService(systemCell, serviceConfig);

‎modules/dcache/src/main/java/org/dcache/boot/Properties.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class Properties {
2424
public static final String PROPERTY_DOMAIN_PRELOAD = "dcache.domain.preload";
2525
public static final String PROPERTY_LOG_CONFIG = "dcache.log.configuration";
2626
public static final String PROPERTY_ZONE = "dcache.zone";
27+
public static final String PROPERTY_ZONE_IS_MAIN = "dcache.zone.main";
2728
public static final String PROPERTY_MSG_PAYLOAD_SERIALIZER = "dcache.broker.channel.msg-payload-serializer";
2829

2930
public static final String PROPERTY_ZOOKEPER_CONNECTION = "dcache.zookeeper.connection";

‎modules/dcache/src/main/java/org/dcache/services/ssh2/AdminShellFactory.java‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44
import dmg.cells.nucleus.CellEndpoint;
55
import dmg.cells.nucleus.CellMessageSender;
66
import java.io.File;
7+
8+
import org.apache.curator.framework.CuratorFramework;
79
import org.apache.sshd.server.channel.ChannelSession;
810
import org.apache.sshd.server.command.Command;
911
import org.apache.sshd.server.shell.ShellFactory;
1012
import org.dcache.cells.CellStub;
13+
import org.dcache.cells.CuratorFrameworkAware;
14+
import org.dcache.http.CustomResponseHeadersHandler;
1115
import org.dcache.util.list.ListDirectoryHandler;
1216
import org.springframework.beans.factory.annotation.Required;
1317

14-
public class AdminShellFactory implements ShellFactory, CellMessageSender {
18+
public class AdminShellFactory implements ShellFactory, CellMessageSender, CuratorFrameworkAware {
1519

1620
private CellEndpoint _endpoint;
1721
private File _historyFile;
@@ -20,6 +24,7 @@ public class AdminShellFactory implements ShellFactory, CellMessageSender {
2024
private CellStub _pnfsManager;
2125
private CellStub _poolManager;
2226
private CellStub _acm;
27+
private CuratorFramework _curator;
2328
private String _prompt;
2429
private ListDirectoryHandler _list;
2530

@@ -68,6 +73,10 @@ public void setCellEndpoint(CellEndpoint endpoint) {
6873
_endpoint = endpoint;
6974
}
7075

76+
public void setCuratorFramework(CuratorFramework curator) {
77+
_curator = curator;
78+
}
79+
7180
@Override
7281
public Command createShell(ChannelSession channelSession) {
7382
return new ShellCommand(_historyFile, _historySize, _useColor, createAdminShell());
@@ -78,8 +87,10 @@ private UserAdminShell createAdminShell() {
7887
shell.setCellEndpoint(_endpoint);
7988
shell.setPnfsManager(_pnfsManager);
8089
shell.setPoolManager(_poolManager);
90+
shell.setCurator(_curator);
8191
shell.setAcm(_acm);
8292
shell.setListHandler(_list);
8393
return shell;
8494
}
95+
8596
}

0 commit comments

Comments
 (0)