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.hdfs.server.namenode.snapshot; 019 020import java.io.DataInput; 021import java.io.DataOutput; 022import java.io.IOException; 023import java.text.SimpleDateFormat; 024import java.util.Arrays; 025import java.util.Comparator; 026import java.util.Date; 027 028import org.apache.hadoop.classification.InterfaceAudience; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hdfs.DFSUtil; 031import org.apache.hadoop.hdfs.protocol.HdfsConstants; 032import org.apache.hadoop.hdfs.server.namenode.AclFeature; 033import org.apache.hadoop.hdfs.server.namenode.ContentSummaryComputationContext; 034import org.apache.hadoop.hdfs.server.namenode.FSImageFormat; 035import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization; 036import org.apache.hadoop.hdfs.server.namenode.INode; 037import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; 038import org.apache.hadoop.hdfs.server.namenode.XAttrFeature; 039import org.apache.hadoop.hdfs.util.ReadOnlyList; 040 041import com.google.common.base.Predicate; 042import com.google.common.collect.Iterables; 043import com.google.common.collect.Lists; 044 045/** Snapshot of a sub-tree in the namesystem. */ 046@InterfaceAudience.Private 047public class Snapshot implements Comparable<byte[]> { 048 /** 049 * This id is used to indicate the current state (vs. snapshots) 050 */ 051 public static final int CURRENT_STATE_ID = Integer.MAX_VALUE - 1; 052 public static final int NO_SNAPSHOT_ID = -1; 053 054 /** 055 * The pattern for generating the default snapshot name. 056 * E.g. s20130412-151029.033 057 */ 058 private static final String DEFAULT_SNAPSHOT_NAME_PATTERN = "'s'yyyyMMdd-HHmmss.SSS"; 059 060 public static String generateDefaultSnapshotName() { 061 return new SimpleDateFormat(DEFAULT_SNAPSHOT_NAME_PATTERN).format(new Date()); 062 } 063 064 public static String getSnapshotPath(String snapshottableDir, 065 String snapshotRelativePath) { 066 final StringBuilder b = new StringBuilder(snapshottableDir); 067 if (b.charAt(b.length() - 1) != Path.SEPARATOR_CHAR) { 068 b.append(Path.SEPARATOR); 069 } 070 return b.append(HdfsConstants.DOT_SNAPSHOT_DIR) 071 .append(Path.SEPARATOR) 072 .append(snapshotRelativePath) 073 .toString(); 074 } 075 076 /** 077 * Get the name of the given snapshot. 078 * @param s The given snapshot. 079 * @return The name of the snapshot, or an empty string if {@code s} is null 080 */ 081 static String getSnapshotName(Snapshot s) { 082 return s != null ? s.getRoot().getLocalName() : ""; 083 } 084 085 public static int getSnapshotId(Snapshot s) { 086 return s == null ? CURRENT_STATE_ID : s.getId(); 087 } 088 089 /** 090 * Compare snapshot with IDs, where null indicates the current status thus 091 * is greater than any non-null snapshot. 092 */ 093 public static final Comparator<Snapshot> ID_COMPARATOR 094 = new Comparator<Snapshot>() { 095 @Override 096 public int compare(Snapshot left, Snapshot right) { 097 return ID_INTEGER_COMPARATOR.compare(Snapshot.getSnapshotId(left), 098 Snapshot.getSnapshotId(right)); 099 } 100 }; 101 102 /** 103 * Compare snapshot with IDs, where null indicates the current status thus 104 * is greater than any non-null ID. 105 */ 106 public static final Comparator<Integer> ID_INTEGER_COMPARATOR 107 = new Comparator<Integer>() { 108 @Override 109 public int compare(Integer left, Integer right) { 110 // Snapshot.CURRENT_STATE_ID means the current state, thus should be the 111 // largest 112 return left - right; 113 } 114 }; 115 116 /** 117 * Find the latest snapshot that 1) covers the given inode (which means the 118 * snapshot was either taken on the inode or taken on an ancestor of the 119 * inode), and 2) was taken before the given snapshot (if the given snapshot 120 * is not null). 121 * 122 * @param inode the given inode that the returned snapshot needs to cover 123 * @param anchor the returned snapshot should be taken before this given id. 124 * @return id of the latest snapshot that covers the given inode and was taken 125 * before the the given snapshot (if it is not null). 126 */ 127 public static int findLatestSnapshot(INode inode, final int anchor) { 128 int latest = NO_SNAPSHOT_ID; 129 for(; inode != null; inode = inode.getParent()) { 130 if (inode.isDirectory()) { 131 final INodeDirectory dir = inode.asDirectory(); 132 if (dir.isWithSnapshot()) { 133 latest = dir.getDiffs().updatePrior(anchor, latest); 134 } 135 } 136 } 137 return latest; 138 } 139 140 static Snapshot read(DataInput in, FSImageFormat.Loader loader) 141 throws IOException { 142 final int snapshotId = in.readInt(); 143 final INode root = loader.loadINodeWithLocalName(false, in, false); 144 return new Snapshot(snapshotId, root.asDirectory(), null); 145 } 146 147 /** The root directory of the snapshot. */ 148 static public class Root extends INodeDirectory { 149 Root(INodeDirectory other) { 150 // Always preserve ACL, XAttr. 151 super(other, false, Lists.newArrayList( 152 Iterables.filter(Arrays.asList(other.getFeatures()), new Predicate<Feature>() { 153 154 @Override 155 public boolean apply(Feature input) { 156 if (AclFeature.class.isInstance(input) 157 || XAttrFeature.class.isInstance(input)) { 158 return true; 159 } 160 return false; 161 } 162 163 })) 164 .toArray(new Feature[0])); 165 } 166 167 @Override 168 public ReadOnlyList<INode> getChildrenList(int snapshotId) { 169 return getParent().getChildrenList(snapshotId); 170 } 171 172 @Override 173 public INode getChild(byte[] name, int snapshotId) { 174 return getParent().getChild(name, snapshotId); 175 } 176 177 @Override 178 public ContentSummaryComputationContext computeContentSummary( 179 ContentSummaryComputationContext summary) { 180 int snapshotId = getParent().getSnapshot(getLocalNameBytes()).getId(); 181 return computeDirectoryContentSummary(summary, snapshotId); 182 } 183 184 @Override 185 public String getFullPathName() { 186 return getSnapshotPath(getParent().getFullPathName(), getLocalName()); 187 } 188 } 189 190 /** Snapshot ID. */ 191 private final int id; 192 /** The root directory of the snapshot. */ 193 private final Root root; 194 195 Snapshot(int id, String name, INodeDirectory dir) { 196 this(id, dir, dir); 197 this.root.setLocalName(DFSUtil.string2Bytes(name)); 198 } 199 200 Snapshot(int id, INodeDirectory dir, INodeDirectory parent) { 201 this.id = id; 202 this.root = new Root(dir); 203 this.root.setParent(parent); 204 } 205 206 public int getId() { 207 return id; 208 } 209 210 /** @return the root directory of the snapshot. */ 211 public Root getRoot() { 212 return root; 213 } 214 215 @Override 216 public int compareTo(byte[] bytes) { 217 return root.compareTo(bytes); 218 } 219 220 @Override 221 public boolean equals(Object that) { 222 if (this == that) { 223 return true; 224 } else if (that == null || !(that instanceof Snapshot)) { 225 return false; 226 } 227 return this.id == ((Snapshot)that).id; 228 } 229 230 @Override 231 public int hashCode() { 232 return id; 233 } 234 235 @Override 236 public String toString() { 237 return getClass().getSimpleName() + "." + root.getLocalName() + "(id=" + id + ")"; 238 } 239 240 /** Serialize the fields to out */ 241 void write(DataOutput out) throws IOException { 242 out.writeInt(id); 243 // write root 244 FSImageSerialization.writeINodeDirectory(root, out); 245 } 246}