001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.wal;
019
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.io.UnsupportedEncodingException;
023import java.net.URLDecoder;
024import java.nio.charset.StandardCharsets;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Comparator;
028import java.util.List;
029import java.util.concurrent.atomic.AtomicBoolean;
030import java.util.concurrent.locks.ReadWriteLock;
031import java.util.concurrent.locks.ReentrantReadWriteLock;
032import java.util.regex.Matcher;
033import java.util.regex.Pattern;
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.fs.FileStatus;
036import org.apache.hadoop.fs.FileSystem;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.hbase.Abortable;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.ServerName;
041import org.apache.hadoop.hbase.client.RegionInfo;
042import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL;
043import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
044import org.apache.hadoop.hbase.util.Addressing;
045import org.apache.hadoop.hbase.util.CancelableProgressable;
046import org.apache.hadoop.hbase.util.CommonFSUtils;
047import org.apache.hadoop.hbase.util.RecoverLeaseFSUtils;
048import org.apache.yetus.audience.InterfaceAudience;
049import org.apache.yetus.audience.InterfaceStability;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
054
055/**
056 * Base class of a WAL Provider that returns a single thread safe WAL that writes to Hadoop FS. By
057 * default, this implementation picks a directory in Hadoop FS based on a combination of
058 * <ul>
059 * <li>the HBase root directory
060 * <li>HConstants.HREGION_LOGDIR_NAME
061 * <li>the given factory's factoryId (usually identifying the regionserver by host:port)
062 * </ul>
063 * It also uses the providerId to differentiate among files.
064 */
065@InterfaceAudience.Private
066@InterfaceStability.Evolving
067public abstract class AbstractFSWALProvider<T extends AbstractFSWAL<?>> implements WALProvider {
068
069  private static final Logger LOG = LoggerFactory.getLogger(AbstractFSWALProvider.class);
070
071  /** Separate old log into different dir by regionserver name **/
072  public static final String SEPARATE_OLDLOGDIR = "hbase.separate.oldlogdir.by.regionserver";
073  public static final boolean DEFAULT_SEPARATE_OLDLOGDIR = false;
074
075  public interface Initializer {
076    /**
077     * A method to initialize a WAL reader.
078     * @param startPosition the start position you want to read from, -1 means start reading from
079     *                      the first WAL entry. Notice that, the first entry is not started at
080     *                      position as we have several headers, so typically you should not pass 0
081     *                      here.
082     */
083    void init(FileSystem fs, Path path, Configuration c, long startPosition) throws IOException;
084  }
085
086  protected volatile T wal;
087  protected WALFactory factory;
088  protected Configuration conf;
089  protected List<WALActionsListener> listeners = new ArrayList<>();
090  protected String providerId;
091  protected AtomicBoolean initialized = new AtomicBoolean(false);
092  // for default wal provider, logPrefix won't change
093  protected String logPrefix;
094  protected Abortable abortable;
095
096  /**
097   * We use walCreateLock to prevent wal recreation in different threads, and also prevent getWALs
098   * missing the newly created WAL, see HBASE-21503 for more details.
099   */
100  private final ReadWriteLock walCreateLock = new ReentrantReadWriteLock();
101
102  /**
103   * @param factory    factory that made us, identity used for FS layout. may not be null
104   * @param conf       may not be null
105   * @param providerId differentiate between providers from one factory, used for FS layout. may be
106   *                   null
107   */
108  @Override
109  public void init(WALFactory factory, Configuration conf, String providerId, Abortable abortable)
110    throws IOException {
111    if (!initialized.compareAndSet(false, true)) {
112      throw new IllegalStateException("WALProvider.init should only be called once.");
113    }
114    this.factory = factory;
115    this.conf = conf;
116    this.providerId = providerId;
117    // get log prefix
118    StringBuilder sb = new StringBuilder().append(factory.factoryId);
119    if (providerId != null) {
120      if (providerId.startsWith(WAL_FILE_NAME_DELIMITER)) {
121        sb.append(providerId);
122      } else {
123        sb.append(WAL_FILE_NAME_DELIMITER).append(providerId);
124      }
125    }
126    logPrefix = sb.toString();
127    this.abortable = abortable;
128    doInit(conf);
129  }
130
131  @Override
132  public List<WAL> getWALs() {
133    if (wal != null) {
134      return Lists.newArrayList(wal);
135    }
136    walCreateLock.readLock().lock();
137    try {
138      if (wal == null) {
139        return Collections.emptyList();
140      } else {
141        return Lists.newArrayList(wal);
142      }
143    } finally {
144      walCreateLock.readLock().unlock();
145    }
146  }
147
148  @Override
149  public T getWAL(RegionInfo region) throws IOException {
150    T walCopy = wal;
151    if (walCopy != null) {
152      return walCopy;
153    }
154    walCreateLock.writeLock().lock();
155    try {
156      walCopy = wal;
157      if (walCopy != null) {
158        return walCopy;
159      }
160      walCopy = createWAL();
161      boolean succ = false;
162      try {
163        walCopy.init();
164        succ = true;
165      } finally {
166        if (!succ) {
167          walCopy.close();
168        }
169      }
170      wal = walCopy;
171      return walCopy;
172    } finally {
173      walCreateLock.writeLock().unlock();
174    }
175  }
176
177  protected abstract T createWAL() throws IOException;
178
179  protected abstract void doInit(Configuration conf) throws IOException;
180
181  @Override
182  public void shutdown() throws IOException {
183    T log = this.wal;
184    if (log != null) {
185      log.shutdown();
186    }
187  }
188
189  @Override
190  public void close() throws IOException {
191    T log = this.wal;
192    if (log != null) {
193      log.close();
194    }
195  }
196
197  /**
198   * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the
199   * number of files (rolled and active). if either of them aren't, count 0 for that provider.
200   */
201  @Override
202  public long getNumLogFiles() {
203    T log = this.wal;
204    return log == null ? 0 : log.getNumLogFiles();
205  }
206
207  /**
208   * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the
209   * size of files (only rolled). if either of them aren't, count 0 for that provider.
210   */
211  @Override
212  public long getLogFileSize() {
213    T log = this.wal;
214    return log == null ? 0 : log.getLogFileSize();
215  }
216
217  /**
218   * returns the number of rolled WAL files.
219   */
220  public static int getNumRolledLogFiles(WAL wal) {
221    return ((AbstractFSWAL<?>) wal).getNumRolledLogFiles();
222  }
223
224  /**
225   * returns the size of rolled WAL files.
226   */
227  public static long getLogFileSize(WAL wal) {
228    return ((AbstractFSWAL<?>) wal).getLogFileSize();
229  }
230
231  /**
232   * return the current filename from the current wal.
233   */
234  public static Path getCurrentFileName(final WAL wal) {
235    return ((AbstractFSWAL<?>) wal).getCurrentFileName();
236  }
237
238  /**
239   * request a log roll, but don't actually do it.
240   */
241  static void requestLogRoll(final WAL wal) {
242    ((AbstractFSWAL<?>) wal).requestLogRoll();
243  }
244
245  // should be package private; more visible for use in AbstractFSWAL
246  public static final String WAL_FILE_NAME_DELIMITER = ".";
247  /** The hbase:meta region's WAL filename extension */
248  public static final String META_WAL_PROVIDER_ID = ".meta";
249  static final String DEFAULT_PROVIDER_ID = "default";
250
251  // Implementation details that currently leak in tests or elsewhere follow
252  /** File Extension used while splitting an WAL into regions (HBASE-2312) */
253  public static final String SPLITTING_EXT = "-splitting";
254
255  /**
256   * Pattern used to validate a WAL file name see {@link #validateWALFilename(String)} for
257   * description.
258   */
259  private static final Pattern WAL_FILE_NAME_PATTERN =
260    Pattern.compile("(.+)\\.(\\d+)(\\.[0-9A-Za-z]+)?");
261
262  /**
263   * Define for when no timestamp found.
264   */
265  private static final long NO_TIMESTAMP = -1L;
266
267  /**
268   * It returns the file create timestamp (the 'FileNum') from the file name. For name format see
269   * {@link #validateWALFilename(String)} public until remaining tests move to o.a.h.h.wal
270   * @param wal must not be null
271   * @return the file number that is part of the WAL file name
272   */
273  public static long extractFileNumFromWAL(final WAL wal) {
274    final Path walPath = ((AbstractFSWAL<?>) wal).getCurrentFileName();
275    if (walPath == null) {
276      throw new IllegalArgumentException("The WAL path couldn't be null");
277    }
278    String name = walPath.getName();
279    long timestamp = getTimestamp(name);
280    if (timestamp == NO_TIMESTAMP) {
281      throw new IllegalArgumentException(name + " is not a valid wal file name");
282    }
283    return timestamp;
284  }
285
286  /**
287   * A WAL file name is of the format: &lt;wal-name&gt;{@link #WAL_FILE_NAME_DELIMITER}
288   * &lt;file-creation-timestamp&gt;[.&lt;suffix&gt;]. provider-name is usually made up of a
289   * server-name and a provider-id
290   * @param filename name of the file to validate
291   * @return <tt>true</tt> if the filename matches an WAL, <tt>false</tt> otherwise
292   */
293  public static boolean validateWALFilename(String filename) {
294    return WAL_FILE_NAME_PATTERN.matcher(filename).matches();
295  }
296
297  /**
298   * Split a WAL filename to get a start time. WALs usually have the time we start writing to them
299   * with as part of their name, usually the suffix. Sometimes there will be an extra suffix as when
300   * it is a WAL for the meta table. For example, WALs might look like this
301   * <code>10.20.20.171%3A60020.1277499063250</code> where <code>1277499063250</code> is the
302   * timestamp. Could also be a meta WAL which adds a '.meta' suffix or a synchronous replication
303   * WAL which adds a '.syncrep' suffix. Check for these. File also may have no timestamp on it. For
304   * example the recovered.edits files are WALs but are named in ascending order. Here is an
305   * example: 0000000000000016310. Allow for this.
306   * @param name Name of the WAL file.
307   * @return Timestamp or {@link #NO_TIMESTAMP}.
308   */
309  public static long getTimestamp(String name) {
310    Matcher matcher = WAL_FILE_NAME_PATTERN.matcher(name);
311    return matcher.matches() ? Long.parseLong(matcher.group(2)) : NO_TIMESTAMP;
312  }
313
314  public static final Comparator<Path> TIMESTAMP_COMPARATOR =
315    Comparator.<Path, Long> comparing(p -> AbstractFSWALProvider.getTimestamp(p.getName()))
316      .thenComparing(Path::getName);
317
318  /**
319   * Construct the directory name for all WALs on a given server. Dir names currently look like this
320   * for WALs: <code>hbase//WALs/kalashnikov.att.net,61634,1486865297088</code>.
321   * @param serverName Server name formatted as described in {@link ServerName}
322   * @return the relative WAL directory name, e.g. <code>.logs/1.example.org,60030,12345</code> if
323   *         <code>serverName</code> passed is <code>1.example.org,60030,12345</code>
324   */
325  public static String getWALDirectoryName(final String serverName) {
326    StringBuilder dirName = new StringBuilder(HConstants.HREGION_LOGDIR_NAME);
327    dirName.append("/");
328    dirName.append(serverName);
329    return dirName.toString();
330  }
331
332  /**
333   * Construct the directory name for all old WALs on a given server. The default old WALs dir looks
334   * like: <code>hbase/oldWALs</code>. If you config hbase.separate.oldlogdir.by.regionserver to
335   * true, it looks like <code>hbase//oldWALs/kalashnikov.att.net,61634,1486865297088</code>.
336   * @param serverName Server name formatted as described in {@link ServerName}
337   * @return the relative WAL directory name
338   */
339  public static String getWALArchiveDirectoryName(Configuration conf, final String serverName) {
340    StringBuilder dirName = new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME);
341    if (conf.getBoolean(SEPARATE_OLDLOGDIR, DEFAULT_SEPARATE_OLDLOGDIR)) {
342      dirName.append(Path.SEPARATOR);
343      dirName.append(serverName);
344    }
345    return dirName.toString();
346  }
347
348  /**
349   * List all the old wal files for a dead region server.
350   * <p/>
351   * Initially added for supporting replication, where we need to get the wal files to replicate for
352   * a dead region server.
353   */
354  public static List<Path> getArchivedWALFiles(Configuration conf, ServerName serverName,
355    String logPrefix) throws IOException {
356    Path walRootDir = CommonFSUtils.getWALRootDir(conf);
357    FileSystem fs = walRootDir.getFileSystem(conf);
358    List<Path> archivedWalFiles = new ArrayList<>();
359    // list both the root old wal dir and the separate old wal dir, so we will not miss any files if
360    // the SEPARATE_OLDLOGDIR config is changed
361    Path oldWalDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME);
362    try {
363      for (FileStatus status : fs.listStatus(oldWalDir, p -> p.getName().startsWith(logPrefix))) {
364        if (status.isFile()) {
365          archivedWalFiles.add(status.getPath());
366        }
367      }
368    } catch (FileNotFoundException e) {
369      LOG.info("Old WAL dir {} not exists", oldWalDir);
370      return Collections.emptyList();
371    }
372    Path separatedOldWalDir = new Path(oldWalDir, serverName.toString());
373    try {
374      for (FileStatus status : fs.listStatus(separatedOldWalDir,
375        p -> p.getName().startsWith(logPrefix))) {
376        if (status.isFile()) {
377          archivedWalFiles.add(status.getPath());
378        }
379      }
380    } catch (FileNotFoundException e) {
381      LOG.info("Seprated old WAL dir {} not exists", separatedOldWalDir);
382    }
383    return archivedWalFiles;
384  }
385
386  /**
387   * List all the wal files for a logPrefix.
388   */
389  public static List<Path> getWALFiles(Configuration c, ServerName serverName) throws IOException {
390    Path walRoot = new Path(CommonFSUtils.getWALRootDir(c), HConstants.HREGION_LOGDIR_NAME);
391    FileSystem fs = walRoot.getFileSystem(c);
392    List<Path> walFiles = new ArrayList<>();
393    Path walDir = new Path(walRoot, serverName.toString());
394    try {
395      for (FileStatus status : fs.listStatus(walDir)) {
396        if (status.isFile()) {
397          walFiles.add(status.getPath());
398        }
399      }
400    } catch (FileNotFoundException e) {
401      LOG.info("WAL dir {} not exists", walDir);
402    }
403    return walFiles;
404  }
405
406  /**
407   * Pulls a ServerName out of a Path generated according to our layout rules. In the below layouts,
408   * this method ignores the format of the logfile component. Current format: [base directory for
409   * hbase]/hbase/.logs/ServerName/logfile or [base directory for
410   * hbase]/hbase/.logs/ServerName-splitting/logfile Expected to work for individual log files and
411   * server-specific directories.
412   * @return null if it's not a log file. Returns the ServerName of the region server that created
413   *         this log file otherwise.
414   */
415  public static ServerName getServerNameFromWALDirectoryName(Configuration conf, String path)
416    throws IOException {
417    if (path == null || path.length() <= HConstants.HREGION_LOGDIR_NAME.length()) {
418      return null;
419    }
420
421    if (conf == null) {
422      throw new IllegalArgumentException("parameter conf must be set");
423    }
424
425    final String rootDir = conf.get(HConstants.HBASE_DIR);
426    if (rootDir == null || rootDir.isEmpty()) {
427      throw new IllegalArgumentException(HConstants.HBASE_DIR + " key not found in conf.");
428    }
429
430    final StringBuilder startPathSB = new StringBuilder(rootDir);
431    if (!rootDir.endsWith("/")) {
432      startPathSB.append('/');
433    }
434    startPathSB.append(HConstants.HREGION_LOGDIR_NAME);
435    if (!HConstants.HREGION_LOGDIR_NAME.endsWith("/")) {
436      startPathSB.append('/');
437    }
438    final String startPath = startPathSB.toString();
439
440    String fullPath;
441    try {
442      fullPath = FileSystem.get(conf).makeQualified(new Path(path)).toString();
443    } catch (IllegalArgumentException e) {
444      LOG.info("Call to makeQualified failed on " + path + " " + e.getMessage());
445      return null;
446    }
447
448    if (!fullPath.startsWith(startPath)) {
449      return null;
450    }
451
452    final String serverNameAndFile = fullPath.substring(startPath.length());
453
454    if (serverNameAndFile.indexOf('/') < "a,0,0".length()) {
455      // Either it's a file (not a directory) or it's not a ServerName format
456      return null;
457    }
458
459    Path p = new Path(path);
460    return getServerNameFromWALDirectoryName(p);
461  }
462
463  /**
464   * This function returns region server name from a log file name which is in one of the following
465   * formats:
466   * <ul>
467   * <li>hdfs://&lt;name node&gt;/hbase/.logs/&lt;server name&gt;-splitting/...</li>
468   * <li>hdfs://&lt;name node&gt;/hbase/.logs/&lt;server name&gt;/...</li>
469   * </ul>
470   * @return null if the passed in logFile isn't a valid WAL file path
471   */
472  public static ServerName getServerNameFromWALDirectoryName(Path logFile) {
473    String logDirName = logFile.getParent().getName();
474    // We were passed the directory and not a file in it.
475    if (logDirName.equals(HConstants.HREGION_LOGDIR_NAME)) {
476      logDirName = logFile.getName();
477    }
478    ServerName serverName = null;
479    if (logDirName.endsWith(SPLITTING_EXT)) {
480      logDirName = logDirName.substring(0, logDirName.length() - SPLITTING_EXT.length());
481    }
482    try {
483      serverName = ServerName.parseServerName(logDirName);
484    } catch (IllegalArgumentException | IllegalStateException ex) {
485      serverName = null;
486      LOG.warn("Cannot parse a server name from path={}", logFile, ex);
487    }
488    if (serverName != null && serverName.getStartCode() < 0) {
489      LOG.warn("Invalid log file path={}, start code {} is less than 0", logFile,
490        serverName.getStartCode());
491      serverName = null;
492    }
493    return serverName;
494  }
495
496  public static boolean isMetaFile(Path p) {
497    return isMetaFile(p.getName());
498  }
499
500  /** Returns True if String ends in {@link #META_WAL_PROVIDER_ID} */
501  public static boolean isMetaFile(String p) {
502    return p != null && p.endsWith(META_WAL_PROVIDER_ID);
503  }
504
505  /**
506   * Comparator used to compare WAL files together based on their start time. Just compares start
507   * times and nothing else.
508   */
509  public static class WALStartTimeComparator implements Comparator<Path> {
510    @Override
511    public int compare(Path o1, Path o2) {
512      return Long.compare(getTS(o1), getTS(o2));
513    }
514
515    /**
516     * Split a path to get the start time For example: 10.20.20.171%3A60020.1277499063250 Could also
517     * be a meta WAL which adds a '.meta' suffix or a synchronous replication WAL which adds a
518     * '.syncrep' suffix. Check.
519     * @param p path to split
520     * @return start time
521     */
522    public static long getTS(Path p) {
523      return getTimestamp(p.getName());
524    }
525  }
526
527  public static boolean isArchivedLogFile(Path p) {
528    String oldLog = Path.SEPARATOR + HConstants.HREGION_OLDLOGDIR_NAME + Path.SEPARATOR;
529    return p.toString().contains(oldLog);
530  }
531
532  /**
533   * Find the archived WAL file path if it is not able to locate in WALs dir.
534   * @param path - active WAL file path
535   * @param conf - configuration
536   * @return archived path if exists, null - otherwise
537   * @throws IOException exception
538   */
539  public static Path findArchivedLog(Path path, Configuration conf) throws IOException {
540    // If the path contains oldWALs keyword then exit early.
541    if (path.toString().contains(HConstants.HREGION_OLDLOGDIR_NAME)) {
542      return null;
543    }
544    Path walRootDir = CommonFSUtils.getWALRootDir(conf);
545    FileSystem fs = path.getFileSystem(conf);
546    // Try finding the log in old dir
547    Path oldLogDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME);
548    Path archivedLogLocation = new Path(oldLogDir, path.getName());
549    if (fs.exists(archivedLogLocation)) {
550      LOG.info("Log " + path + " was moved to " + archivedLogLocation);
551      return archivedLogLocation;
552    }
553
554    ServerName serverName = getServerNameFromWALDirectoryName(path);
555    if (serverName == null) {
556      LOG.warn("Can not extract server name from path {}, "
557        + "give up searching the separated old log dir", path);
558      return null;
559    }
560    // Try finding the log in separate old log dir
561    oldLogDir = new Path(walRootDir, new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME)
562      .append(Path.SEPARATOR).append(serverName.getServerName()).toString());
563    archivedLogLocation = new Path(oldLogDir, path.getName());
564    if (fs.exists(archivedLogLocation)) {
565      LOG.info("Log " + path + " was moved to " + archivedLogLocation);
566      return archivedLogLocation;
567    }
568    LOG.error("Couldn't locate log: " + path);
569    return null;
570  }
571
572  // For HBASE-15019
573  public static void recoverLease(Configuration conf, Path path) {
574    try {
575      final FileSystem dfs = CommonFSUtils.getCurrentFileSystem(conf);
576      RecoverLeaseFSUtils.recoverFileLease(dfs, path, conf, new CancelableProgressable() {
577        @Override
578        public boolean progress() {
579          LOG.debug("Still trying to recover WAL lease: " + path);
580          return true;
581        }
582      });
583    } catch (IOException e) {
584      LOG.warn("unable to recover lease for WAL: " + path, e);
585    }
586  }
587
588  @Override
589  public void addWALActionsListener(WALActionsListener listener) {
590    listeners.add(listener);
591  }
592
593  private static String getWALNameGroupFromWALName(String name, int group) {
594    Matcher matcher = WAL_FILE_NAME_PATTERN.matcher(name);
595    if (matcher.matches()) {
596      return matcher.group(group);
597    } else {
598      throw new IllegalArgumentException(name + " is not a valid wal file name");
599    }
600  }
601
602  /**
603   * Get prefix of the log from its name, assuming WAL name in format of
604   * log_prefix.filenumber.log_suffix
605   * @param name Name of the WAL to parse
606   * @return prefix of the log
607   * @throws IllegalArgumentException if the name passed in is not a valid wal file name
608   * @see AbstractFSWAL#getCurrentFileName()
609   */
610  public static String getWALPrefixFromWALName(String name) {
611    return getWALNameGroupFromWALName(name, 1);
612  }
613
614  private static final Pattern SERVER_NAME_PATTERN = Pattern.compile("^[^"
615    + ServerName.SERVERNAME_SEPARATOR + "]+" + ServerName.SERVERNAME_SEPARATOR
616    + Addressing.VALID_PORT_REGEX + ServerName.SERVERNAME_SEPARATOR + Addressing.VALID_PORT_REGEX);
617
618  /**
619   * Parse the server name from wal prefix. A wal's name is always started with a server name in non
620   * test code.
621   * @throws IllegalArgumentException if the name passed in is not started with a server name
622   * @return the server name
623   */
624  public static ServerName parseServerNameFromWALName(String name) {
625    String decoded;
626    try {
627      decoded = URLDecoder.decode(name, StandardCharsets.UTF_8.name());
628    } catch (UnsupportedEncodingException e) {
629      throw new AssertionError("should never happen", e);
630    }
631    Matcher matcher = SERVER_NAME_PATTERN.matcher(decoded);
632    if (matcher.find()) {
633      return ServerName.valueOf(matcher.group());
634    } else {
635      throw new IllegalArgumentException(name + " is not started with a server name");
636    }
637  }
638}