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 
19 package org.apache.hadoop.hdfs.server.datanode;
20 
21 import org.apache.hadoop.fs.FileUtil;
22 import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.StartupOption;
23 import org.apache.hadoop.hdfs.server.common.Storage;
24 import org.apache.hadoop.hdfs.server.protocol.NamespaceInfo;
25 import org.apache.hadoop.test.GenericTestUtils;
26 import org.junit.After;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.mockito.Mockito;
30 
31 import java.io.File;
32 import java.io.IOException;
33 import java.net.URISyntaxException;
34 import java.util.ArrayList;
35 import java.util.List;
36 
37 import static org.junit.Assert.assertEquals;
38 import static org.junit.Assert.assertTrue;
39 import static org.junit.Assert.fail;
40 
41 public class TestDataStorage {
42   private final static String DEFAULT_BPID = "bp-0";
43   private final static String CLUSTER_ID = "cluster0";
44   private final static String BUILD_VERSION = "2.0";
45   private final static String SOFTWARE_VERSION = "2.0";
46   private final static long CTIME = 1;
47   private final static File TEST_DIR =
48       new File(System.getProperty("test.build.data") + "/dstest");
49   private final static StartupOption START_OPT = StartupOption.REGULAR;
50 
51   private DataNode mockDN = Mockito.mock(DataNode.class);
52   private NamespaceInfo nsInfo;
53   private DataStorage storage;
54 
55   @Before
setUp()56   public void setUp() throws IOException {
57     storage = new DataStorage();
58     nsInfo = new NamespaceInfo(0, CLUSTER_ID, DEFAULT_BPID, CTIME,
59         BUILD_VERSION, SOFTWARE_VERSION);
60     FileUtil.fullyDelete(TEST_DIR);
61     assertTrue("Failed to make test dir.", TEST_DIR.mkdirs());
62   }
63 
64   @After
tearDown()65   public void tearDown() throws IOException {
66     storage.unlockAll();
67     FileUtil.fullyDelete(TEST_DIR);
68   }
69 
createStorageLocations(int numLocs)70   private static List<StorageLocation> createStorageLocations(int numLocs)
71       throws IOException {
72     return createStorageLocations(numLocs, false);
73   }
74 
75   /**
76    * Create a list of StorageLocations.
77    * If asFile sets to true, create StorageLocation as regular files, otherwise
78    * create directories for each location.
79    * @param numLocs the total number of StorageLocations to be created.
80    * @param asFile set to true to create as file.
81    * @return a list of StorageLocations.
82    */
createStorageLocations( int numLocs, boolean asFile)83   private static List<StorageLocation> createStorageLocations(
84       int numLocs, boolean asFile) throws IOException {
85     List<StorageLocation> locations = new ArrayList<StorageLocation>();
86     for (int i = 0; i < numLocs; i++) {
87       String uri = TEST_DIR + "/data" + i;
88       File file = new File(uri);
89       if (asFile) {
90         file.getParentFile().mkdirs();
91         file.createNewFile();
92       } else {
93         file.mkdirs();
94       }
95       StorageLocation loc = StorageLocation.parse(uri);
96       locations.add(loc);
97     }
98     return locations;
99   }
100 
createNamespaceInfos(int num)101   private static List<NamespaceInfo> createNamespaceInfos(int num) {
102     List<NamespaceInfo> nsInfos = new ArrayList<NamespaceInfo>();
103     for (int i = 0; i < num; i++) {
104       String bpid = "bp-" + i;
105       nsInfos.add(new NamespaceInfo(0, CLUSTER_ID, bpid, CTIME, BUILD_VERSION,
106           SOFTWARE_VERSION));
107     }
108     return nsInfos;
109   }
110 
111   /** Check whether the path is a valid DataNode data directory. */
checkDir(File dataDir)112   private static void checkDir(File dataDir) {
113     Storage.StorageDirectory sd = new Storage.StorageDirectory(dataDir);
114     assertTrue(sd.getRoot().isDirectory());
115     assertTrue(sd.getCurrentDir().isDirectory());
116     assertTrue(sd.getVersionFile().isFile());
117   }
118 
119   /** Check whether the root is a valid BlockPoolSlice storage. */
checkDir(File root, String bpid)120   private static void checkDir(File root, String bpid) {
121     Storage.StorageDirectory sd = new Storage.StorageDirectory(root);
122     File bpRoot = new File(sd.getCurrentDir(), bpid);
123     Storage.StorageDirectory bpSd = new Storage.StorageDirectory(bpRoot);
124     assertTrue(bpSd.getRoot().isDirectory());
125     assertTrue(bpSd.getCurrentDir().isDirectory());
126     assertTrue(bpSd.getVersionFile().isFile());
127   }
128 
129   @Test
testAddStorageDirectories()130   public void testAddStorageDirectories() throws IOException,
131       URISyntaxException {
132     final int numLocations = 3;
133     final int numNamespace = 3;
134     List<StorageLocation> locations = createStorageLocations(numLocations);
135 
136     // Add volumes for multiple namespaces.
137     List<NamespaceInfo> namespaceInfos = createNamespaceInfos(numNamespace);
138     for (NamespaceInfo ni : namespaceInfos) {
139       storage.addStorageLocations(mockDN, ni, locations, START_OPT);
140       for (StorageLocation sl : locations) {
141         checkDir(sl.getFile());
142         checkDir(sl.getFile(), ni.getBlockPoolID());
143       }
144     }
145 
146     assertEquals(numLocations, storage.getNumStorageDirs());
147 
148     locations = createStorageLocations(numLocations);
149     List<StorageLocation> addedLocation =
150         storage.addStorageLocations(mockDN, namespaceInfos.get(0),
151             locations, START_OPT);
152     assertTrue(addedLocation.isEmpty());
153 
154     // The number of active storage dirs has not changed, since it tries to
155     // add the storage dirs that are under service.
156     assertEquals(numLocations, storage.getNumStorageDirs());
157 
158     // Add more directories.
159     locations = createStorageLocations(6);
160     storage.addStorageLocations(mockDN, nsInfo, locations, START_OPT);
161     assertEquals(6, storage.getNumStorageDirs());
162   }
163 
164   @Test
testRecoverTransitionReadFailure()165   public void testRecoverTransitionReadFailure() throws IOException {
166     final int numLocations = 3;
167     List<StorageLocation> locations =
168         createStorageLocations(numLocations, true);
169     try {
170       storage.recoverTransitionRead(mockDN, nsInfo, locations, START_OPT);
171       fail("An IOException should throw: all StorageLocations are NON_EXISTENT");
172     } catch (IOException e) {
173       GenericTestUtils.assertExceptionContains(
174           "All specified directories are failed to load.", e);
175     }
176     assertEquals(0, storage.getNumStorageDirs());
177   }
178 
179   /**
180    * This test enforces the behavior that if there is an exception from
181    * doTransition() during DN starts up, the storage directories that have
182    * already been processed are still visible, i.e., in
183    * DataStorage.storageDirs().
184    */
185   @Test
testRecoverTransitionReadDoTransitionFailure()186   public void testRecoverTransitionReadDoTransitionFailure()
187       throws IOException {
188     final int numLocations = 3;
189     List<StorageLocation> locations = createStorageLocations(numLocations);
190     // Prepare volumes
191     storage.recoverTransitionRead(mockDN, nsInfo, locations, START_OPT);
192     assertEquals(numLocations, storage.getNumStorageDirs());
193 
194     // Reset DataStorage
195     storage.unlockAll();
196     storage = new DataStorage();
197     // Trigger an exception from doTransition().
198     nsInfo.clusterID = "cluster1";
199     try {
200       storage.recoverTransitionRead(mockDN, nsInfo, locations, START_OPT);
201       fail("Expect to throw an exception from doTransition()");
202     } catch (IOException e) {
203       GenericTestUtils.assertExceptionContains("All specified directories", e);
204     }
205     assertEquals(0, storage.getNumStorageDirs());
206   }
207 }
208