Skip to content

Commit 410ece4

Browse files
committed
Merge branch '3.1'
2 parents 2fa2d20 + 46c4c37 commit 410ece4

File tree

7 files changed

+185
-36
lines changed

7 files changed

+185
-36
lines changed

core/src/main/java/org/apache/accumulo/core/conf/ConfigurationTypeHelper.java

+51
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,27 @@
1818
*/
1919
package org.apache.accumulo.core.conf;
2020

21+
import static java.util.Objects.requireNonNull;
2122
import static java.util.concurrent.TimeUnit.DAYS;
2223
import static java.util.concurrent.TimeUnit.HOURS;
2324
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2425
import static java.util.concurrent.TimeUnit.MINUTES;
2526
import static java.util.concurrent.TimeUnit.SECONDS;
2627

28+
import java.net.URI;
29+
import java.net.URISyntaxException;
30+
import java.util.Arrays;
2731
import java.util.Collections;
2832
import java.util.HashMap;
33+
import java.util.LinkedHashSet;
2934
import java.util.Map;
35+
import java.util.Set;
3036
import java.util.concurrent.TimeUnit;
37+
import java.util.function.Predicate;
38+
import java.util.stream.Collectors;
3139

3240
import org.apache.accumulo.core.classloader.ClassLoaderUtil;
41+
import org.apache.hadoop.fs.Path;
3342
import org.slf4j.Logger;
3443
import org.slf4j.LoggerFactory;
3544

@@ -222,4 +231,46 @@ public static int getNumThreads(String threads) {
222231
}
223232
return nThreads;
224233
}
234+
235+
/**
236+
* Get the set of volumes parsed from a volumes property type, and throw exceptions if the volumes
237+
* aren't valid, are null, contains only blanks, or contains duplicates. An empty string is
238+
* allowed (resulting in an empty set of volumes), to handle the case where the property is not
239+
* set by a user (or... is set to the same as the default, which is equivalent to not being set).
240+
* If the property is required to be set, it is the caller's responsibility to verify that the
241+
* parsed set is non-empty.
242+
*
243+
* @throws IllegalArgumentException when the volumes are set to something that cannot be parsed
244+
*/
245+
public static Set<String> getVolumeUris(String volumes) {
246+
if (requireNonNull(volumes).isEmpty()) {
247+
// special case when the property is not set and defaults to an empty string
248+
return Set.of();
249+
}
250+
var blanksRemoved = Arrays.stream(volumes.split(",")).map(String::strip)
251+
.filter(Predicate.not(String::isEmpty)).collect(Collectors.toList());
252+
if (blanksRemoved.isEmpty()) {
253+
throw new IllegalArgumentException("property contains only blank volumes");
254+
}
255+
var deduplicated = blanksRemoved.stream().map(ConfigurationTypeHelper::normalizeVolume)
256+
.collect(Collectors.toCollection(LinkedHashSet::new));
257+
if (deduplicated.size() < blanksRemoved.size()) {
258+
throw new IllegalArgumentException("property contains duplicate volumes");
259+
}
260+
return deduplicated;
261+
}
262+
263+
private static String normalizeVolume(String volume) {
264+
if (!volume.contains(":")) {
265+
throw new IllegalArgumentException("'" + volume + "' is not a fully qualified URI");
266+
}
267+
try {
268+
// pass through URI to unescape hex encoded chars (e.g. convert %2C to "," char)
269+
return new Path(new URI(volume.strip())).toString();
270+
} catch (URISyntaxException e) {
271+
throw new IllegalArgumentException(
272+
"volume contains '" + volume + "' which has a syntax error", e);
273+
}
274+
}
275+
225276
}

core/src/main/java/org/apache/accumulo/core/conf/Property.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public enum Property {
146146
+ " HDFS. To use the ChangeSecret tool, run the command: `./bin/accumulo"
147147
+ " admin changeSecret`.",
148148
"1.3.5"),
149-
INSTANCE_VOLUMES("instance.volumes", "", PropertyType.STRING,
149+
INSTANCE_VOLUMES("instance.volumes", "", PropertyType.VOLUMES,
150150
"A comma separated list of dfs uris to use. Files will be stored across"
151151
+ " these filesystems. In some situations, the first volume in this list"
152152
+ " may be treated differently, such as being preferred for writing out"

core/src/main/java/org/apache/accumulo/core/conf/PropertyType.java

+27-6
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,19 @@ public enum PropertyType {
136136
+ " interpreted based on the context of the property to which it applies."),
137137

138138
JSON("json", new ValidJson(),
139-
"An arbitrary string that is represents a valid, parsable generic json object."
140-
+ "The validity of the json object in the context of the property usage is not checked by this type."),
139+
"An arbitrary string that is represents a valid, parsable generic json object. The validity "
140+
+ "of the json object in the context of the property usage is not checked by this type."),
141+
141142
BOOLEAN("boolean", in(false, null, "true", "false"),
142143
"Has a value of either 'true' or 'false' (case-insensitive)"),
143144

144145
URI("uri", x -> true, "A valid URI"),
145146

146147
FILENAME_EXT("file name extension", in(true, RFile.EXTENSION),
147148
"One of the currently supported filename extensions for storing table data files. "
148-
+ "Currently, only " + RFile.EXTENSION + " is supported.");
149+
+ "Currently, only " + RFile.EXTENSION + " is supported."),
150+
151+
VOLUMES("volumes", new ValidVolumes(), "See instance.volumes documentation");
149152

150153
private final String shortname;
151154
private final String format;
@@ -212,13 +215,32 @@ private static class ValidJson implements Predicate<String> {
212215
public boolean test(String value) {
213216
try {
214217
if (value.length() > ONE_MILLION) {
215-
log.info("provided json string length {} is greater than limit of {} for parsing",
218+
log.error("provided json string length {} is greater than limit of {} for parsing",
216219
value.length(), ONE_MILLION);
217220
return false;
218221
}
219222
jsonMapper.readTree(value);
220223
return true;
221-
} catch (IOException ex) {
224+
} catch (IOException e) {
225+
log.error("provided json string resulted in an error", e);
226+
return false;
227+
}
228+
}
229+
}
230+
231+
private static class ValidVolumes implements Predicate<String> {
232+
private static final Logger log = LoggerFactory.getLogger(ValidVolumes.class);
233+
234+
@Override
235+
public boolean test(String volumes) {
236+
if (volumes == null) {
237+
return false;
238+
}
239+
try {
240+
ConfigurationTypeHelper.getVolumeUris(volumes);
241+
return true;
242+
} catch (IllegalArgumentException e) {
243+
log.error("provided volume string is not valid", e);
222244
return false;
223245
}
224246
}
@@ -392,5 +414,4 @@ public static IntStream parse(String portRange) {
392414
}
393415

394416
}
395-
396417
}

core/src/main/java/org/apache/accumulo/core/volume/VolumeConfiguration.java

+3-29
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,10 @@
1919
package org.apache.accumulo.core.volume;
2020

2121
import java.io.IOException;
22-
import java.net.URI;
23-
import java.net.URISyntaxException;
24-
import java.util.Arrays;
25-
import java.util.LinkedHashSet;
2622
import java.util.Set;
27-
import java.util.stream.Collectors;
2823

2924
import org.apache.accumulo.core.conf.AccumuloConfiguration;
25+
import org.apache.accumulo.core.conf.ConfigurationTypeHelper;
3026
import org.apache.accumulo.core.conf.Property;
3127
import org.apache.hadoop.conf.Configuration;
3228
import org.apache.hadoop.fs.FileSystem;
@@ -39,34 +35,12 @@ public static FileSystem fileSystemForPath(String path, Configuration conf) thro
3935
}
4036

4137
public static Set<String> getVolumeUris(AccumuloConfiguration conf) {
42-
String volumes = conf.get(Property.INSTANCE_VOLUMES);
38+
var volumes = conf.get(Property.INSTANCE_VOLUMES);
4339
if (volumes == null || volumes.isBlank()) {
4440
throw new IllegalArgumentException(
4541
"Missing required property " + Property.INSTANCE_VOLUMES.getKey());
4642
}
47-
String[] volArray = volumes.split(",");
48-
LinkedHashSet<String> deduplicated =
49-
Arrays.stream(volArray).map(VolumeConfiguration::normalizeVolume)
50-
.collect(Collectors.toCollection(LinkedHashSet::new));
51-
if (deduplicated.size() < volArray.length) {
52-
throw new IllegalArgumentException(
53-
Property.INSTANCE_VOLUMES.getKey() + " contains duplicate volumes (" + volumes + ")");
54-
}
55-
return deduplicated;
56-
}
57-
58-
private static String normalizeVolume(String volume) {
59-
if (volume == null || volume.isBlank() || !volume.contains(":")) {
60-
throw new IllegalArgumentException("Expected fully qualified URI for "
61-
+ Property.INSTANCE_VOLUMES.getKey() + " got " + volume);
62-
}
63-
try {
64-
// pass through URI to unescape hex encoded chars (e.g. convert %2C to "," char)
65-
return new Path(new URI(volume.strip())).toString();
66-
} catch (URISyntaxException e) {
67-
throw new IllegalArgumentException(Property.INSTANCE_VOLUMES.getKey() + " contains '" + volume
68-
+ "' which has a syntax error", e);
69-
}
43+
return ConfigurationTypeHelper.getVolumeUris(volumes);
7044
}
7145

7246
}

core/src/test/java/org/apache/accumulo/core/conf/ConfigurationTypeHelperTest.java

+50
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
import static java.util.concurrent.TimeUnit.SECONDS;
2525
import static org.junit.jupiter.api.Assertions.assertEquals;
2626
import static org.junit.jupiter.api.Assertions.assertThrows;
27+
import static org.junit.jupiter.api.Assertions.assertTrue;
2728

29+
import java.util.Set;
2830
import java.util.function.Function;
2931
import java.util.stream.Stream;
3032

@@ -132,4 +134,52 @@ public void testGetFractionFailureCase2() {
132134
public void testGetFractionFailureCase3() {
133135
assertThrows(IllegalArgumentException.class, () -> ConfigurationTypeHelper.getFraction(".%"));
134136
}
137+
138+
@Test
139+
public void testGetVolumeUris() {
140+
// test property not set
141+
assertEquals(Set.of(), ConfigurationTypeHelper.getVolumeUris(""));
142+
143+
// test blank cases
144+
assertThrows(NullPointerException.class, () -> ConfigurationTypeHelper.getVolumeUris(null));
145+
for (String s : Set.of(" ", ",", ",,,", " ,,,", ",,, ", ", ,,")) {
146+
var e = assertThrows(IllegalArgumentException.class,
147+
() -> ConfigurationTypeHelper.getVolumeUris(s));
148+
assertEquals("property contains only blank volumes", e.getMessage());
149+
}
150+
151+
// test 1 volume
152+
for (String s : Set.of("hdfs:/volA", ",hdfs:/volA", "hdfs:/volA,")) {
153+
var uris = ConfigurationTypeHelper.getVolumeUris(s);
154+
assertEquals(1, uris.size());
155+
assertTrue(uris.contains("hdfs:/volA"));
156+
}
157+
158+
// test more than 1 volume
159+
for (String s : Set.of("hdfs:/volA,file:/volB", ",hdfs:/volA,file:/volB",
160+
"hdfs:/volA,,file:/volB", "hdfs:/volA,file:/volB, ,")) {
161+
var uris = ConfigurationTypeHelper.getVolumeUris(s);
162+
assertEquals(2, uris.size());
163+
assertTrue(uris.contains("hdfs:/volA"));
164+
assertTrue(uris.contains("file:/volB"));
165+
}
166+
167+
// test invalid URI
168+
for (String s : Set.of("hdfs:/volA,hdfs:/volB,volA", ",volA,hdfs:/volA,hdfs:/volB",
169+
"hdfs:/volA,,volA,hdfs:/volB", "hdfs:/volA,volA,hdfs:/volB, ,")) {
170+
var iae = assertThrows(IllegalArgumentException.class,
171+
() -> ConfigurationTypeHelper.getVolumeUris(s));
172+
assertEquals("'volA' is not a fully qualified URI", iae.getMessage());
173+
}
174+
175+
// test duplicates
176+
var iae = assertThrows(IllegalArgumentException.class,
177+
() -> ConfigurationTypeHelper.getVolumeUris("hdfs:/volA,hdfs:/volB,hdfs:/volA"));
178+
assertEquals("property contains duplicate volumes", iae.getMessage());
179+
180+
// test syntax error in URI
181+
iae = assertThrows(IllegalArgumentException.class,
182+
() -> ConfigurationTypeHelper.getVolumeUris("hdfs:/volA,hdfs :/::/volB"));
183+
assertEquals("volume contains 'hdfs :/::/volB' which has a syntax error", iae.getMessage());
184+
}
135185
}

core/src/test/java/org/apache/accumulo/core/conf/PropertyTypeTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,15 @@ public void testTypeFILENAME_EXT() {
219219
invalid(null, "RF", "map", "", "MAP", "rF", "Rf", " rf ");
220220
}
221221

222+
@Test
223+
public void testTypeVOLUMES() {
224+
// more comprehensive parsing tests are in ConfigurationTypeHelperTest.testGetVolumeUris()
225+
valid("", "hdfs:/volA", ",hdfs:/volA", "hdfs:/volA,", "hdfs:/volA,file:/volB",
226+
",hdfs:/volA,file:/volB", "hdfs:/volA,,file:/volB", "hdfs:/volA,file:/volB, ,");
227+
invalid(null, " ", ",", ",,,", " ,,,", ",,, ", ", ,,", "hdfs:/volA,hdfs:/volB,volA",
228+
",volA,hdfs:/volA,hdfs:/volB", "hdfs:/volA,,volA,hdfs:/volB",
229+
"hdfs:/volA,volA,hdfs:/volB, ,", "hdfs:/volA,hdfs:/volB,hdfs:/volA",
230+
"hdfs:/volA,hdfs :/::/volB");
231+
}
232+
222233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.accumulo.core.volume;
20+
21+
import static org.junit.jupiter.api.Assertions.assertNull;
22+
import static org.junit.jupiter.api.Assertions.assertThrows;
23+
24+
import org.apache.accumulo.core.conf.ConfigurationCopy;
25+
import org.apache.accumulo.core.conf.Property;
26+
import org.junit.jupiter.api.Test;
27+
28+
public class VolumeConfigurationTest {
29+
@Test
30+
public void testEmptyVolumes() {
31+
ConfigurationCopy config = new ConfigurationCopy();
32+
assertNull(config.get(Property.INSTANCE_VOLUMES.getKey()));
33+
assertThrows(IllegalArgumentException.class, () -> VolumeConfiguration.getVolumeUris(config));
34+
35+
config.set(Property.INSTANCE_VOLUMES.getKey(), "");
36+
assertThrows(IllegalArgumentException.class, () -> VolumeConfiguration.getVolumeUris(config));
37+
config.set(Property.INSTANCE_VOLUMES.getKey(), " ");
38+
assertThrows(IllegalArgumentException.class, () -> VolumeConfiguration.getVolumeUris(config));
39+
config.set(Property.INSTANCE_VOLUMES.getKey(), ",");
40+
assertThrows(IllegalArgumentException.class, () -> VolumeConfiguration.getVolumeUris(config));
41+
}
42+
}

0 commit comments

Comments
 (0)