1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 package org.apache.hadoop.hdfs.server.namenode;
19 
20 import static org.apache.hadoop.util.ExitUtil.terminate;
21 
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.Comparator;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.PriorityQueue;
30 import java.util.SortedSet;
31 import java.util.concurrent.CopyOnWriteArrayList;
32 
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.hadoop.classification.InterfaceAudience;
36 import org.apache.hadoop.hdfs.server.common.Storage;
37 import org.apache.hadoop.hdfs.server.common.StorageInfo;
38 import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo;
39 import org.apache.hadoop.hdfs.server.protocol.RemoteEditLog;
40 import org.apache.hadoop.hdfs.server.protocol.RemoteEditLogManifest;
41 
42 import static org.apache.hadoop.util.ExitUtil.terminate;
43 
44 import com.google.common.base.Preconditions;
45 import com.google.common.collect.ComparisonChain;
46 import com.google.common.collect.ImmutableList;
47 import com.google.common.collect.ImmutableListMultimap;
48 import com.google.common.collect.Lists;
49 import com.google.common.collect.Multimaps;
50 import com.google.common.collect.Sets;
51 
52 /**
53  * Manages a collection of Journals. None of the methods are synchronized, it is
54  * assumed that FSEditLog methods, that use this class, use proper
55  * synchronization.
56  */
57 public class JournalSet implements JournalManager {
58 
59   static final Log LOG = LogFactory.getLog(FSEditLog.class);
60 
61   private static final Comparator<EditLogInputStream>
62     LOCAL_LOG_PREFERENCE_COMPARATOR = new Comparator<EditLogInputStream>() {
63     @Override
64     public int compare(EditLogInputStream elis1, EditLogInputStream elis2) {
65       // we want local logs to be ordered earlier in the collection, and true
66       // is considered larger than false, so we want to invert the booleans here
67       return ComparisonChain.start().compare(!elis1.isLocalLog(),
68           !elis2.isLocalLog()).result();
69     }
70   };
71 
72   static final public Comparator<EditLogInputStream>
73     EDIT_LOG_INPUT_STREAM_COMPARATOR = new Comparator<EditLogInputStream>() {
74       @Override
75       public int compare(EditLogInputStream a, EditLogInputStream b) {
76         return ComparisonChain.start().
77           compare(a.getFirstTxId(), b.getFirstTxId()).
78           compare(b.getLastTxId(), a.getLastTxId()).
79           result();
80       }
81     };
82 
83   /**
84    * Container for a JournalManager paired with its currently
85    * active stream.
86    *
87    * If a Journal gets disabled due to an error writing to its
88    * stream, then the stream will be aborted and set to null.
89    */
90   static class JournalAndStream implements CheckableNameNodeResource {
91     private final JournalManager journal;
92     private boolean disabled = false;
93     private EditLogOutputStream stream;
94     private final boolean required;
95     private final boolean shared;
96 
JournalAndStream(JournalManager manager, boolean required, boolean shared)97     public JournalAndStream(JournalManager manager, boolean required,
98         boolean shared) {
99       this.journal = manager;
100       this.required = required;
101       this.shared = shared;
102     }
103 
startLogSegment(long txId, int layoutVersion)104     public void startLogSegment(long txId, int layoutVersion) throws IOException {
105       Preconditions.checkState(stream == null);
106       disabled = false;
107       stream = journal.startLogSegment(txId, layoutVersion);
108     }
109 
110     /**
111      * Closes the stream, also sets it to null.
112      */
closeStream()113     public void closeStream() throws IOException {
114       if (stream == null) return;
115       stream.close();
116       stream = null;
117     }
118 
119     /**
120      * Close the Journal and Stream
121      */
close()122     public void close() throws IOException {
123       closeStream();
124 
125       journal.close();
126     }
127 
128     /**
129      * Aborts the stream, also sets it to null.
130      */
abort()131     public void abort() {
132       if (stream == null) return;
133       try {
134         stream.abort();
135       } catch (IOException ioe) {
136         LOG.error("Unable to abort stream " + stream, ioe);
137       }
138       stream = null;
139     }
140 
isActive()141     boolean isActive() {
142       return stream != null;
143     }
144 
145     /**
146      * Should be used outside JournalSet only for testing.
147      */
getCurrentStream()148     EditLogOutputStream getCurrentStream() {
149       return stream;
150     }
151 
152     @Override
toString()153     public String toString() {
154       return "JournalAndStream(mgr=" + journal +
155         ", " + "stream=" + stream + ")";
156     }
157 
setCurrentStreamForTests(EditLogOutputStream stream)158     void setCurrentStreamForTests(EditLogOutputStream stream) {
159       this.stream = stream;
160     }
161 
getManager()162     JournalManager getManager() {
163       return journal;
164     }
165 
isDisabled()166     boolean isDisabled() {
167       return disabled;
168     }
169 
setDisabled(boolean disabled)170     private void setDisabled(boolean disabled) {
171       this.disabled = disabled;
172     }
173 
174     @Override
isResourceAvailable()175     public boolean isResourceAvailable() {
176       return !isDisabled();
177     }
178 
179     @Override
isRequired()180     public boolean isRequired() {
181       return required;
182     }
183 
isShared()184     public boolean isShared() {
185       return shared;
186     }
187   }
188 
189   // COW implementation is necessary since some users (eg the web ui) call
190   // getAllJournalStreams() and then iterate. Since this is rarely
191   // mutated, there is no performance concern.
192   private final List<JournalAndStream> journals =
193       new CopyOnWriteArrayList<JournalSet.JournalAndStream>();
194   final int minimumRedundantJournals;
195 
196   private boolean closed;
197 
JournalSet(int minimumRedundantResources)198   JournalSet(int minimumRedundantResources) {
199     this.minimumRedundantJournals = minimumRedundantResources;
200   }
201 
202   @Override
format(NamespaceInfo nsInfo)203   public void format(NamespaceInfo nsInfo) throws IOException {
204     // The operation is done by FSEditLog itself
205     throw new UnsupportedOperationException();
206   }
207 
208   @Override
hasSomeData()209   public boolean hasSomeData() throws IOException {
210     // This is called individually on the underlying journals,
211     // not on the JournalSet.
212     throw new UnsupportedOperationException();
213   }
214 
215 
216   @Override
startLogSegment(final long txId, final int layoutVersion)217   public EditLogOutputStream startLogSegment(final long txId,
218       final int layoutVersion) throws IOException {
219     mapJournalsAndReportErrors(new JournalClosure() {
220       @Override
221       public void apply(JournalAndStream jas) throws IOException {
222         jas.startLogSegment(txId, layoutVersion);
223       }
224     }, "starting log segment " + txId);
225     return new JournalSetOutputStream();
226   }
227 
228   @Override
finalizeLogSegment(final long firstTxId, final long lastTxId)229   public void finalizeLogSegment(final long firstTxId, final long lastTxId)
230       throws IOException {
231     mapJournalsAndReportErrors(new JournalClosure() {
232       @Override
233       public void apply(JournalAndStream jas) throws IOException {
234         if (jas.isActive()) {
235           jas.closeStream();
236           jas.getManager().finalizeLogSegment(firstTxId, lastTxId);
237         }
238       }
239     }, "finalize log segment " + firstTxId + ", " + lastTxId);
240   }
241 
242   @Override
close()243   public void close() throws IOException {
244     mapJournalsAndReportErrors(new JournalClosure() {
245       @Override
246       public void apply(JournalAndStream jas) throws IOException {
247         jas.close();
248       }
249     }, "close journal");
250     closed = true;
251   }
252 
isOpen()253   public boolean isOpen() {
254     return !closed;
255   }
256 
257   /**
258    * In this function, we get a bunch of streams from all of our JournalManager
259    * objects.  Then we add these to the collection one by one.
260    *
261    * @param streams          The collection to add the streams to.  It may or
262    *                         may not be sorted-- this is up to the caller.
263    * @param fromTxId         The transaction ID to start looking for streams at
264    * @param inProgressOk     Should we consider unfinalized streams?
265    */
266   @Override
selectInputStreams(Collection<EditLogInputStream> streams, long fromTxId, boolean inProgressOk)267   public void selectInputStreams(Collection<EditLogInputStream> streams,
268       long fromTxId, boolean inProgressOk) throws IOException {
269     final PriorityQueue<EditLogInputStream> allStreams =
270         new PriorityQueue<EditLogInputStream>(64,
271             EDIT_LOG_INPUT_STREAM_COMPARATOR);
272     for (JournalAndStream jas : journals) {
273       if (jas.isDisabled()) {
274         LOG.info("Skipping jas " + jas + " since it's disabled");
275         continue;
276       }
277       try {
278         jas.getManager().selectInputStreams(allStreams, fromTxId, inProgressOk);
279       } catch (IOException ioe) {
280         LOG.warn("Unable to determine input streams from " + jas.getManager() +
281             ". Skipping.", ioe);
282       }
283     }
284     chainAndMakeRedundantStreams(streams, allStreams, fromTxId);
285   }
286 
chainAndMakeRedundantStreams( Collection<EditLogInputStream> outStreams, PriorityQueue<EditLogInputStream> allStreams, long fromTxId)287   public static void chainAndMakeRedundantStreams(
288       Collection<EditLogInputStream> outStreams,
289       PriorityQueue<EditLogInputStream> allStreams, long fromTxId) {
290     // We want to group together all the streams that start on the same start
291     // transaction ID.  To do this, we maintain an accumulator (acc) of all
292     // the streams we've seen at a given start transaction ID.  When we see a
293     // higher start transaction ID, we select a stream from the accumulator and
294     // clear it.  Then we begin accumulating streams with the new, higher start
295     // transaction ID.
296     LinkedList<EditLogInputStream> acc =
297         new LinkedList<EditLogInputStream>();
298     EditLogInputStream elis;
299     while ((elis = allStreams.poll()) != null) {
300       if (acc.isEmpty()) {
301         acc.add(elis);
302       } else {
303         EditLogInputStream accFirst = acc.get(0);
304         long accFirstTxId = accFirst.getFirstTxId();
305         if (accFirstTxId == elis.getFirstTxId()) {
306           // if we have a finalized log segment available at this txid,
307           // we should throw out all in-progress segments at this txid
308           if (elis.isInProgress()) {
309             if (accFirst.isInProgress()) {
310               acc.add(elis);
311             }
312           } else {
313             if (accFirst.isInProgress()) {
314               acc.clear();
315             }
316             acc.add(elis);
317           }
318         } else if (accFirstTxId < elis.getFirstTxId()) {
319           // try to read from the local logs first since the throughput should
320           // be higher
321           Collections.sort(acc, LOCAL_LOG_PREFERENCE_COMPARATOR);
322           outStreams.add(new RedundantEditLogInputStream(acc, fromTxId));
323           acc.clear();
324           acc.add(elis);
325         } else if (accFirstTxId > elis.getFirstTxId()) {
326           throw new RuntimeException("sorted set invariants violated!  " +
327               "Got stream with first txid " + elis.getFirstTxId() +
328               ", but the last firstTxId was " + accFirstTxId);
329         }
330       }
331     }
332     if (!acc.isEmpty()) {
333       Collections.sort(acc, LOCAL_LOG_PREFERENCE_COMPARATOR);
334       outStreams.add(new RedundantEditLogInputStream(acc, fromTxId));
335       acc.clear();
336     }
337   }
338 
339   /**
340    * Returns true if there are no journals, all redundant journals are disabled,
341    * or any required journals are disabled.
342    *
343    * @return True if there no journals, all redundant journals are disabled,
344    * or any required journals are disabled.
345    */
isEmpty()346   public boolean isEmpty() {
347     return !NameNodeResourcePolicy.areResourcesAvailable(journals,
348         minimumRedundantJournals);
349   }
350 
351   /**
352    * Called when some journals experience an error in some operation.
353    */
disableAndReportErrorOnJournals(List<JournalAndStream> badJournals)354   private void disableAndReportErrorOnJournals(List<JournalAndStream> badJournals) {
355     if (badJournals == null || badJournals.isEmpty()) {
356       return; // nothing to do
357     }
358 
359     for (JournalAndStream j : badJournals) {
360       LOG.error("Disabling journal " + j);
361       j.abort();
362       j.setDisabled(true);
363     }
364   }
365 
366   /**
367    * Implementations of this interface encapsulate operations that can be
368    * iteratively applied on all the journals. For example see
369    * {@link JournalSet#mapJournalsAndReportErrors}.
370    */
371   private interface JournalClosure {
372     /**
373      * The operation on JournalAndStream.
374      * @param jas Object on which operations are performed.
375      * @throws IOException
376      */
apply(JournalAndStream jas)377     public void apply(JournalAndStream jas) throws IOException;
378   }
379 
380   /**
381    * Apply the given operation across all of the journal managers, disabling
382    * any for which the closure throws an IOException.
383    * @param closure {@link JournalClosure} object encapsulating the operation.
384    * @param status message used for logging errors (e.g. "opening journal")
385    * @throws IOException If the operation fails on all the journals.
386    */
mapJournalsAndReportErrors( JournalClosure closure, String status)387   private void mapJournalsAndReportErrors(
388       JournalClosure closure, String status) throws IOException{
389 
390     List<JournalAndStream> badJAS = Lists.newLinkedList();
391     for (JournalAndStream jas : journals) {
392       try {
393         closure.apply(jas);
394       } catch (Throwable t) {
395         if (jas.isRequired()) {
396           final String msg = "Error: " + status + " failed for required journal ("
397             + jas + ")";
398           LOG.fatal(msg, t);
399           // If we fail on *any* of the required journals, then we must not
400           // continue on any of the other journals. Abort them to ensure that
401           // retry behavior doesn't allow them to keep going in any way.
402           abortAllJournals();
403           // the current policy is to shutdown the NN on errors to shared edits
404           // dir. There are many code paths to shared edits failures - syncs,
405           // roll of edits etc. All of them go through this common function
406           // where the isRequired() check is made. Applying exit policy here
407           // to catch all code paths.
408           terminate(1, msg);
409         } else {
410           LOG.error("Error: " + status + " failed for (journal " + jas + ")", t);
411           badJAS.add(jas);
412         }
413       }
414     }
415     disableAndReportErrorOnJournals(badJAS);
416     if (!NameNodeResourcePolicy.areResourcesAvailable(journals,
417         minimumRedundantJournals)) {
418       String message = status + " failed for too many journals";
419       LOG.error("Error: " + message);
420       throw new IOException(message);
421     }
422   }
423 
424   /**
425    * Abort all of the underlying streams.
426    */
abortAllJournals()427   private void abortAllJournals() {
428     for (JournalAndStream jas : journals) {
429       if (jas.isActive()) {
430         jas.abort();
431       }
432     }
433   }
434 
435   /**
436    * An implementation of EditLogOutputStream that applies a requested method on
437    * all the journals that are currently active.
438    */
439   private class JournalSetOutputStream extends EditLogOutputStream {
440 
JournalSetOutputStream()441     JournalSetOutputStream() throws IOException {
442       super();
443     }
444 
445     @Override
write(final FSEditLogOp op)446     public void write(final FSEditLogOp op)
447         throws IOException {
448       mapJournalsAndReportErrors(new JournalClosure() {
449         @Override
450         public void apply(JournalAndStream jas) throws IOException {
451           if (jas.isActive()) {
452             jas.getCurrentStream().write(op);
453           }
454         }
455       }, "write op");
456     }
457 
458     @Override
writeRaw(final byte[] data, final int offset, final int length)459     public void writeRaw(final byte[] data, final int offset, final int length)
460         throws IOException {
461       mapJournalsAndReportErrors(new JournalClosure() {
462         @Override
463         public void apply(JournalAndStream jas) throws IOException {
464           if (jas.isActive()) {
465             jas.getCurrentStream().writeRaw(data, offset, length);
466           }
467         }
468       }, "write bytes");
469     }
470 
471     @Override
create(final int layoutVersion)472     public void create(final int layoutVersion) throws IOException {
473       mapJournalsAndReportErrors(new JournalClosure() {
474         @Override
475         public void apply(JournalAndStream jas) throws IOException {
476           if (jas.isActive()) {
477             jas.getCurrentStream().create(layoutVersion);
478           }
479         }
480       }, "create");
481     }
482 
483     @Override
close()484     public void close() throws IOException {
485       mapJournalsAndReportErrors(new JournalClosure() {
486         @Override
487         public void apply(JournalAndStream jas) throws IOException {
488           jas.closeStream();
489         }
490       }, "close");
491     }
492 
493     @Override
abort()494     public void abort() throws IOException {
495       mapJournalsAndReportErrors(new JournalClosure() {
496         @Override
497         public void apply(JournalAndStream jas) throws IOException {
498           jas.abort();
499         }
500       }, "abort");
501     }
502 
503     @Override
setReadyToFlush()504     public void setReadyToFlush() throws IOException {
505       mapJournalsAndReportErrors(new JournalClosure() {
506         @Override
507         public void apply(JournalAndStream jas) throws IOException {
508           if (jas.isActive()) {
509             jas.getCurrentStream().setReadyToFlush();
510           }
511         }
512       }, "setReadyToFlush");
513     }
514 
515     @Override
flushAndSync(final boolean durable)516     protected void flushAndSync(final boolean durable) throws IOException {
517       mapJournalsAndReportErrors(new JournalClosure() {
518         @Override
519         public void apply(JournalAndStream jas) throws IOException {
520           if (jas.isActive()) {
521             jas.getCurrentStream().flushAndSync(durable);
522           }
523         }
524       }, "flushAndSync");
525     }
526 
527     @Override
flush()528     public void flush() throws IOException {
529       mapJournalsAndReportErrors(new JournalClosure() {
530         @Override
531         public void apply(JournalAndStream jas) throws IOException {
532           if (jas.isActive()) {
533             jas.getCurrentStream().flush();
534           }
535         }
536       }, "flush");
537     }
538 
539     @Override
shouldForceSync()540     public boolean shouldForceSync() {
541       for (JournalAndStream js : journals) {
542         if (js.isActive() && js.getCurrentStream().shouldForceSync()) {
543           return true;
544         }
545       }
546       return false;
547     }
548 
549     @Override
getNumSync()550     protected long getNumSync() {
551       for (JournalAndStream jas : journals) {
552         if (jas.isActive()) {
553           return jas.getCurrentStream().getNumSync();
554         }
555       }
556       return 0;
557     }
558   }
559 
560   @Override
setOutputBufferCapacity(final int size)561   public void setOutputBufferCapacity(final int size) {
562     try {
563       mapJournalsAndReportErrors(new JournalClosure() {
564         @Override
565         public void apply(JournalAndStream jas) throws IOException {
566             jas.getManager().setOutputBufferCapacity(size);
567         }
568       }, "setOutputBufferCapacity");
569     } catch (IOException e) {
570       LOG.error("Error in setting outputbuffer capacity");
571     }
572   }
573 
getAllJournalStreams()574   List<JournalAndStream> getAllJournalStreams() {
575     return journals;
576   }
577 
getJournalManagers()578   List<JournalManager> getJournalManagers() {
579     List<JournalManager> jList = new ArrayList<JournalManager>();
580     for (JournalAndStream j : journals) {
581       jList.add(j.getManager());
582     }
583     return jList;
584   }
585 
add(JournalManager j, boolean required)586   void add(JournalManager j, boolean required) {
587     add(j, required, false);
588   }
589 
add(JournalManager j, boolean required, boolean shared)590   void add(JournalManager j, boolean required, boolean shared) {
591     JournalAndStream jas = new JournalAndStream(j, required, shared);
592     journals.add(jas);
593   }
594 
remove(JournalManager j)595   void remove(JournalManager j) {
596     JournalAndStream jasToRemove = null;
597     for (JournalAndStream jas: journals) {
598       if (jas.getManager().equals(j)) {
599         jasToRemove = jas;
600         break;
601       }
602     }
603     if (jasToRemove != null) {
604       jasToRemove.abort();
605       journals.remove(jasToRemove);
606     }
607   }
608 
609   @Override
purgeLogsOlderThan(final long minTxIdToKeep)610   public void purgeLogsOlderThan(final long minTxIdToKeep) throws IOException {
611     mapJournalsAndReportErrors(new JournalClosure() {
612       @Override
613       public void apply(JournalAndStream jas) throws IOException {
614         jas.getManager().purgeLogsOlderThan(minTxIdToKeep);
615       }
616     }, "purgeLogsOlderThan " + minTxIdToKeep);
617   }
618 
619   @Override
recoverUnfinalizedSegments()620   public void recoverUnfinalizedSegments() throws IOException {
621     mapJournalsAndReportErrors(new JournalClosure() {
622       @Override
623       public void apply(JournalAndStream jas) throws IOException {
624         jas.getManager().recoverUnfinalizedSegments();
625       }
626     }, "recoverUnfinalizedSegments");
627   }
628 
629   /**
630    * Return a manifest of what finalized edit logs are available. All available
631    * edit logs are returned starting from the transaction id passed. If
632    * 'fromTxId' falls in the middle of a log, that log is returned as well.
633    *
634    * @param fromTxId Starting transaction id to read the logs.
635    * @return RemoteEditLogManifest object.
636    */
getEditLogManifest(long fromTxId)637   public synchronized RemoteEditLogManifest getEditLogManifest(long fromTxId) {
638     // Collect RemoteEditLogs available from each FileJournalManager
639     List<RemoteEditLog> allLogs = Lists.newArrayList();
640     for (JournalAndStream j : journals) {
641       if (j.getManager() instanceof FileJournalManager) {
642         FileJournalManager fjm = (FileJournalManager)j.getManager();
643         try {
644           allLogs.addAll(fjm.getRemoteEditLogs(fromTxId, false));
645         } catch (Throwable t) {
646           LOG.warn("Cannot list edit logs in " + fjm, t);
647         }
648       }
649     }
650 
651     // Group logs by their starting txid
652     ImmutableListMultimap<Long, RemoteEditLog> logsByStartTxId =
653       Multimaps.index(allLogs, RemoteEditLog.GET_START_TXID);
654     long curStartTxId = fromTxId;
655 
656     List<RemoteEditLog> logs = Lists.newArrayList();
657     while (true) {
658       ImmutableList<RemoteEditLog> logGroup = logsByStartTxId.get(curStartTxId);
659       if (logGroup.isEmpty()) {
660         // we have a gap in logs - for example because we recovered some old
661         // storage directory with ancient logs. Clear out any logs we've
662         // accumulated so far, and then skip to the next segment of logs
663         // after the gap.
664         SortedSet<Long> startTxIds = Sets.newTreeSet(logsByStartTxId.keySet());
665         startTxIds = startTxIds.tailSet(curStartTxId);
666         if (startTxIds.isEmpty()) {
667           break;
668         } else {
669           if (LOG.isDebugEnabled()) {
670             LOG.debug("Found gap in logs at " + curStartTxId + ": " +
671                 "not returning previous logs in manifest.");
672           }
673           logs.clear();
674           curStartTxId = startTxIds.first();
675           continue;
676         }
677       }
678 
679       // Find the one that extends the farthest forward
680       RemoteEditLog bestLog = Collections.max(logGroup);
681       logs.add(bestLog);
682       // And then start looking from after that point
683       curStartTxId = bestLog.getEndTxId() + 1;
684     }
685     RemoteEditLogManifest ret = new RemoteEditLogManifest(logs);
686 
687     if (LOG.isDebugEnabled()) {
688       LOG.debug("Generated manifest for logs since " + fromTxId + ":"
689           + ret);
690     }
691     return ret;
692   }
693 
694   /**
695    * Add sync times to the buffer.
696    */
getSyncTimes()697   String getSyncTimes() {
698     StringBuilder buf = new StringBuilder();
699     for (JournalAndStream jas : journals) {
700       if (jas.isActive()) {
701         buf.append(jas.getCurrentStream().getTotalSyncTime());
702         buf.append(" ");
703       }
704     }
705     return buf.toString();
706   }
707 
708   @Override
discardSegments(long startTxId)709   public void discardSegments(long startTxId) throws IOException {
710     // This operation is handled by FSEditLog directly.
711     throw new UnsupportedOperationException();
712   }
713 
714   @Override
doPreUpgrade()715   public void doPreUpgrade() throws IOException {
716     // This operation is handled by FSEditLog directly.
717     throw new UnsupportedOperationException();
718   }
719 
720   @Override
doUpgrade(Storage storage)721   public void doUpgrade(Storage storage) throws IOException {
722     // This operation is handled by FSEditLog directly.
723     throw new UnsupportedOperationException();
724   }
725 
726   @Override
doFinalize()727   public void doFinalize() throws IOException {
728     // This operation is handled by FSEditLog directly.
729     throw new UnsupportedOperationException();
730   }
731 
732   @Override
canRollBack(StorageInfo storage, StorageInfo prevStorage, int targetLayoutVersion)733   public boolean canRollBack(StorageInfo storage, StorageInfo prevStorage, int targetLayoutVersion) throws IOException {
734     // This operation is handled by FSEditLog directly.
735     throw new UnsupportedOperationException();
736   }
737 
738   @Override
doRollback()739   public void doRollback() throws IOException {
740     // This operation is handled by FSEditLog directly.
741     throw new UnsupportedOperationException();
742   }
743 
744   @Override
getJournalCTime()745   public long getJournalCTime() throws IOException {
746     // This operation is handled by FSEditLog directly.
747     throw new UnsupportedOperationException();
748   }
749 }
750